roiez1-60-seconds-hazardous-materials

Zuckermanía Italy Trip PWA — Working Guide

Further splits have diminishing returns. Remaining large sections (LOGISTICS ~985, TOOLS ~978, BUILDING IDENTIFIER ~606) are tangled feature code with renderers, event handlers, and state — splitting them requires careful function-by-function analysis.

Resources

10
GitHub

Install

npx skillscat add roiez1-60-seconds-hazardous-materials/italy-trip

Install via the SkillsCat registry.

SKILL.md

Zuckermanía Italy Trip PWA — Working Guide

Comprehensive reference for anyone (including future Claude) working on this repo.

Live: https://italy-trip-psi.vercel.app
Repo: roiez1-60-seconds-hazardous-materials/italy-trip
Owner: Roei (רועי) — Hebrew-first, mobile-first, autonomous-execution-preferred
Family: רועי · עדי · מאיה (18) · עמית (14) · נועם (10) · רומי (5)
Trip dates: Sep 22 – Oct 5, 2026 (14 days)


Critical rules (read first)

  1. api.github.com is BLOCKED in the Claude bash environment. Only github.com over HTTPS works. Push via PAT embedded in URL:
    https://x-access-token:$PAT@github.com/roiez1-60-seconds-hazardous-materials/italy-trip.git
  2. Vercel domain vercel.app is also blocked — can't curl-verify deployments from bash. Rely on node --check for syntax.
  3. Never put API keys in code/chat/README — Google/vendors scan public sources and auto-revoke. Keys live only in Vercel env vars.
  4. Before answering "do we visit X" or any itinerary question: grep the code first, never answer from memory.
  5. Small stages for destructive ops. Tag stable versions before risky changes: git tag v{N}-stable && git push origin v{N}-stable.
  6. Complete file rewrites preferred over partial edits when the user sends a long piece of content. For normal dev, use str_replace precisely.

Architecture overview (current: v322)

italy-trip/
├── index.html                  (6,980 lines — down from 9,159 in v301)
│   └── <script> inline         (~6,200 lines)
├── sw.js                       (service worker, PRECACHE list)
├── manifest.json               (PWA manifest)
├── version.json                ({"v":"322","ts":...})
├── js/
│   ├── data.js                 (async JSON loader, exposes window.dataReady)
│   ├── map.js                  (Mapbox GL — pre-existing, not our split)
│   ├── games.js                (kids games: trivia, race, word, catch, memory, Tetris)
│   ├── chat.js                 (family chat + read tracking + push subscription)
│   ├── translator.js           (Gemini text + photo translate via /api/ai, /api/identify)
│   ├── trip-utils.js           (audio guide, nav, URL parsing, map utils, countdown)
│   └── data/
│       ├── itinerary.json      (DAYS — 14-day itinerary)
│       ├── stickers.json       (STICKER_LOCS — 19 GPS sticker locations)
│       ├── notif-tips.json     (NOTIF_TIPS — 14 daily push tips)
│       ├── rain-plans.json     (RAIN_PLANS — 13 rainy-day alternatives)
│       ├── bases.json          (BAS — 4 regional bases)
│       ├── bookings.json       (BKS — 11 pre-trip bookings)
│       ├── checklist.json      (CHKLIST — 10 packing categories)
│       ├── spotify.json        (SPOTIFY — 8 regional playlists)
│       └── ai-qa.json          (AI_SYNS + AI_QA — local search synonyms + FAQ)
└── api/                        (Vercel serverless functions)
    ├── ai.js                   (Gemini chat — Pinocchio AI guide)
    ├── avatars.js
    ├── cron/                   (scheduled functions)
    ├── debug.js
    ├── identify.js             (Gemini vision for photo translate/identify)
    ├── passport.js             (passport photo storage via Google Drive)
    ├── photos.js               (Google Drive gallery)
    ├── push.js                 (web-push via VAPID, Supabase subscriptions)
    ├── resolve-location.js     (server-side URL resolver — Google Maps/Waze ll=)
    ├── setup.js                (OAuth setup helper)
    └── teaser.js

Script load order (index.html around line 17-24)

<script src="https://cdn.jsdelivr.net/npm/hls.js@latest/..."></script>
<script src="https://api.mapbox.com/mapbox-gl-js/v3.3.0/mapbox-gl.js"></script>
<script src="/js/data.js?v=321"></script>       <!-- async JSON loader → window.dataReady -->
<script src="/js/map.js?v=301"></script>         <!-- Mapbox wrapper -->
<script src="/js/games.js?v=311"></script>       <!-- kids games -->
<script src="/js/chat.js?v=318"></script>        <!-- family chat + push -->
<script src="/js/translator.js?v=314"></script>  <!-- translate -->
<script src="/js/trip-utils.js?v=320"></script>  <!-- audio/nav/maps utils -->
<!-- then inline <script> -->

All modules are classic scripts (not ES modules). Top-level var/const/function become window globals, cross-script access works at call-time.

Data loader gate (v309+)

js/data.js fetches all 9 JSONs in parallel, sets window.DAYS, window.AI_QA etc., then resolves window.dataReady.

Inline script has this at the top (~line 800) to defer any data-dependent callback:

// Wrap setTimeout/setInterval so data-hungry callbacks wait for dataReady
var _origST = window.setTimeout, _origSI = window.setInterval;
window.setTimeout = function(cb, ms) { ... dataReady.then(cb) ... };
// similar for setInterval

And the eager-init at the bottom of the inline script is explicitly gated:

(window.dataReady || Promise.resolve()).then(function(){
  rH(); rB(); rI(); updateNotifUI(); renderExpenses(); updateParkingBtn();
});

If you add new code that touches DAYS/BAS/etc. at load time (not inside a function), gate it.


Version bumping

Three files must all stay in sync:

File What to change
index.html >v322< (footer display) AND BUILD_VERSION = 'v322'.slice(1)
sw.js var CACHE='zuck-v322';
version.json {"v":"322","ts":<unix-ms>}

When a js/*.js file changes, also bump its cache-buster in the <script src="...?v=NNN"> tag in index.html. Otherwise, service worker serves the stale cached copy.

sw.js PRECACHE list must include every js/*.js and js/data/*.json file — missing entries break offline.

Quick bump snippet:

NEW=322
sed -i "s/>v$((NEW-1))</>v${NEW}</; s/'v$((NEW-1))'\\.slice/'v${NEW}'.slice/" index.html
sed -i "s/zuck-v$((NEW-1))/zuck-v${NEW}/" sw.js
ts=$(date +%s) && echo "{\"v\":\"${NEW}\",\"ts\":$ts}" > version.json

Deployment

Vercel auto-deploys on push to main. No deploy hook needed manually (but if ever: prj_78v53y1Zaad2aDr3yTN9oQvQLfLC/PwaDFzAVzw).

Git push pattern (only workable path from Claude bash):

git push origin main
# Behind the scenes the remote URL uses embedded PAT; env var setup in .gitconfig/remotes

Cold deploy takes ~30-60s. Users need hard refresh to skip cached sw.js.


Stable tags (rollback targets)

v298-stable v301-stable v306-stable v308-stable v309-stable
v311-stable v313-stable v317-stable v320-stable

Rollback: git reset --hard v{N}-stable && git push -f origin main.


Environment / services

Service What it's for Key
Vercel Hosting, serverless
Supabase Chat messages, read receipts, push subscriptions, checklist sync dmgxvcvjzgmorfibvezr project; publishable key in code
Gemini API Pinocchio AI, translator, photo identify GEMINI_KEY in Vercel env vars
Mapbox GL Interactive maps NEXT_PUBLIC_MAPBOX_TOKEN in Vercel env
VAPID Web push VAPID_PUBLIC_KEY + VAPID_PRIVATE_KEY in Vercel env (public key also duplicated client-side in chat.js)
Google Drive Trip photos gallery OAuth refresh token in GOOGLE_REFRESH_TOKEN Vercel env

Known issues / gotchas

Waze share_drive URLs

URLs of the form waze.com/ul?a=share_drive&sd=... cannot be resolved without Waze's official partner API. The app detects these upfront and shows a message asking the user to share a location (not drive) or use Google Maps. See v320 commit for history.

Push subscription identity drift

subscribePush in chat.js must use chatUser.name (not gpsUser) as the subscription's user_name, because the server's notify filter uses neq.sender where sender is always chatUser.name. If mismatched, users get self-notifications and others get duplicates. Fixed in v318. Server also does endpoint-based dedup on every subscribe (v313).

Notification toggle persistence

subscribePush checks localStorage.getItem('zuck_notif') === 'true' before subscribing. Users who never explicitly toggled the bell won't auto-subscribe (opt-in). Fix in v316.

Android safe-area

.hdr and floating buttons use max(env(safe-area-inset-*), N) to respect device cutouts. Min left-inset set to 8-16px so Android hole-punch cameras don't obscure icons. iOS unaffected (env is already 0 in portrait). See v322.

Map legend bleeds through modals

#mapLegendBox is position:fixed z-index:9997. Chat modal is z-index:98. When user opens chat from map tab, legend appears over chat. Fix: chat open/close explicitly hides/shows #mapLegendBox. See v318.

Chat badge counting own messages

checkChatBadge must filter by name=neq.<self> so the user's just-sent message doesn't count as unread. See v318.


Common dev workflows

Add a new itinerary entry

Edit js/data/itinerary.json, bump js/data.js V= counter, version bump the 3 files.

Add a new feature that uses DAYS/BAS/BKS

Add code inside a function (not at top level). Function will be called after dataReady resolves via the setTimeout/setInterval gate wrapper.

Add a new module

  1. Create js/foo.js
  2. Add <script src="/js/foo.js?v=322"></script> to index.html before the inline <script>
  3. Add /js/foo.js to sw.js PRECACHE
  4. Bump version

Debug production issues

User lacks easy access to Vercel logs. Add a debug flag or error-surfacing via alert() or a toast. Don't ship debug to prod; remove after diagnosed.


User-supplied info kept in memory (not in repo)

  • Anthropic API key (for shamur project, different repo) — in Vercel env
  • Gemini API key (this repo) — in Vercel env as GEMINI_KEY
  • PAT for GitHub push — already embedded in the git remote URL
  • Deploy hook URL — rarely needed, Vercel auto-deploys on push

Related projects (separate repos, same user)

  • Shamur (roiez1-.../shamur) — Hebrew journal PWA for wife Adi, at shamur.vercel.app
  • Iran missile propellants (roiez1-.../iran-missile-propellants) — bilingual intel dossier, at iran-missile-propellants.vercel.app
  • IHU chemical weapons (roiez1-.../ihu-chemical-weapons)
  • Various HazMat/CBRN tools for Israel Fire & Rescue Services (user's day job)

Session history milestones

  • v301 (start of recent major refactor): 9,159 lines inline
  • v302-v308: Data → JSON files (8 files)
  • v309: Async loader + dataReady gate
  • v310: games.js split (515 lines)
  • v312: chat.js split (504 lines)
  • v313: Endpoint-based push dedup
  • v314: translator.js split (95 lines)
  • v315: trip-utils.js split (1,017 lines) — biggest win
  • v316: Notification toggle fixes
  • v317: /api/resolve-location for Google Maps/Waze URLs
  • v318: Chat badge, map legend z-index, push identity preference
  • v320: Removed Waze share_drive API probing (unreachable)
  • v321: AI_SYNS + AI_QA → ai-qa.json (final data extraction)
  • v322: Android safe-area for bell/chat icons

Current: 6,980 lines in index.html (−23.8% from v301).

Further splits have diminishing returns. Remaining large sections (LOGISTICS ~985, TOOLS ~978, BUILDING IDENTIFIER ~606) are tangled feature code with renderers, event handlers, and state — splitting them requires careful function-by-function analysis.