Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,13 +118,13 @@ npm run docker:run # docker run -p 3000:3000 ...
- **⚠️ אילוץ קריטי — רינדור מחדש של טאב לא-פעיל**: `updUI()` (שרץ מ-`schedUI()`/`procSA`/`visibilitychange` וכו') קורא ל-`refreshCurrentTab()` ולא ל-`renderSB()` ישירות — `refreshCurrentTab()` בודק את `currentTab` ומעדכן רק את `#sbC` של הטאב הפעיל בפועל. **לעולם אל תקראו ל-`renderSB()`/`renderHistoryTab()` בלי בדיקת `currentTab` קודם** — קריאה לא-מותנית כותבת על `#sbC` את תוכן האזעקות/היסטוריה גם כשמשתמש צופה בטאב אחר (זה בדיוק הבאג שתוקן: מעבר טאב בדפדפן החזיר את התצוגה לאזעקות במקום להישאר בטאב האחרון).
- **⚠️ אילוץ קריטי — שדות חיפוש חיים (`#fQ`, `#hQ`)**: `renderSB()`/`renderHistoryTab()` מחליפים את כל ה-`innerHTML` של `#sbC`, כולל את שדה החיפוש עצמו. כל שינוי בפונקציות האלה **חייב** לשמר `document.activeElement`/`selectionStart`/`selectionEnd` לפני ה-render ולשחזר אותם (`focus()`+`setSelectionRange()`) אחריו, אחרת כל הקשה במקלדת מוציאה את הפוקוס מהשדה (המשתמש לא יכול להקליד ברצף). `renderHistoryTab()` גם מדלג על ה-skeleton loader כשהטאב כבר מרונדר (רק בטעינה ראשונה), ומגן מפני race condition עם `historySeq`.
- **⚠️ אילוץ קריטי — `sDB()` לא שומר `type`**: רשומות ב-IndexedDB (`sDB()`) שומרות רק `typeKey` (מחרוזת), לא את אובייקט `type` המלא (icon/css/color) ש-`rItem()`/`addMrk()` דורשים. `renderHistoryTab()` משחזר אותו ידנית (`a.type=TM[a.typeKey]||TM.rockets`) אחרי `gDB()` — **זה היה באג אמיתי וארוך-טווח**: לפני התיקון, כל ביקור בטאב היסטוריה עם נתוני IndexedDB אמיתיים קרס על `Cannot read properties of undefined (reading 'icon')` ותקוע על ה-skeleton loader לנצח. אם מוסיפים עוד מקום שקורא רשומות גולמיות מ-`gDB()`/IndexedDB — לבדוק את זה שוב. יש טסט E2E ל-regression הזה (`test/e2e.js`, זורע רשומה ידנית ב-IndexedDB בפורמט המדויק של `sDB()`).
- **מפה**: Leaflet, כל שכבות הבסיס דרך Esri (`server.arcgisonline.com`, ללא מפתח API בשום מצב — hostname אחד ל-CSP `img-src`/SW tile-cache). מצב רגיל (בהיר/כהה): `Canvas/World_{Dark,Light}_Gray_Base` + שכבת שמות/גבולות נפרדת `Canvas/World_{Dark,Light}_Gray_Reference` (`basemapRef`, `pane:'labelsPane'`, תמיד דלוקה — לא ניתנת לכיבוי) — **עד 2026-08 היה זה CartoDB** (`basemaps.cartocdn.com`); הוחלף כי CARTO דרשו מפתח API והתחילו למרוח "API KEY REQUIRED" על כל אריח. שכבת לוויין חינמית — Esri World Imagery — דרך כפתור 🛰️ ב-`.mc` (`tglSat()`); מעליה אפשר להציג/להסתיר שכבת שמות מקומות נפרדת (`Reference/World_Boundaries_and_Places`, גם היא ב-`labelsPane`) דרך כפתור 🏷️ (`satLblB`/`tglSatLabels()`, מוצג רק כש-`satOn` — זו כן ניתנת לכיבוי, בשונה מ-`basemapRef`).
- **PWA**: SW מקודד בתוך `server.js` (משתנה `SW`), מטמון `red-alert-v12` + מטמון אריחים נפרד `red-alert-tiles-v1` (Leaflet basemap כולל לוויין ושכבת שמות, cache-first). שינוי ל-SW דורש bump של `CN` ב-server.js.
- **מפה**: Leaflet, כל שכבות הבסיס דרך Esri (`server.arcgisonline.com`, ללא מפתח API בשום מצב — hostname אחד ל-CSP `img-src`/SW tile-cache). מצב רגיל (בהיר/כהה): `Canvas/World_{Dark,Light}_Gray_Base` + שכבת שמות/גבולות נפרדת `Canvas/World_{Dark,Light}_Gray_Reference` (`basemapRef`, `pane:'labelsPane'`, תמיד דלוקה — לא ניתנת לכיבוי) — **עד 2026-08 היה זה CartoDB** (`basemaps.cartocdn.com`); הוחלף כי CARTO דרשו מפתח API והתחילו למרוח "API KEY REQUIRED" על כל אריח. **חשוב**: ל-Canvas layers (base+reference) אין כיסוי אמיתי מעבר לזום 16 ברוב האזורים (בניגוד ל-World_Imagery) — `tileOpts()` קובע `maxNativeZoom:16` כדי ש-Leaflet יגדיל (upscale) את האריח האחרון במקום לבקש אריחים לא-קיימים/ריקים. שכבת לוויין חינמית — Esri World Imagery — דרך כפתור 🛰️ ב-`.mc` (`tglSat()`); מעליה אפשר להציג/להסתיר שכבת שמות מקומות נפרדת (`Reference/World_Boundaries_and_Places`, גם היא ב-`labelsPane`) דרך כפתור 🏷️ (`satLblB`/`tglSatLabels()`, מוצג רק כש-`satOn` — זו כן ניתנת לכיבוי, בשונה מ-`basemapRef`).
- **PWA**: SW מקודד בתוך `server.js` (משתנה `SW`), מטמון `red-alert-v17` + מטמון אריחים נפרד `red-alert-tiles-v2` (Leaflet basemap כולל לוויין ושכבת שמות, cache-first). שינוי ל-SW דורש bump של `CN` ב-server.js. **שים לב**: `TILE` הוא מטמון נפרד שה-`activate` handler *לעולם לא* מנקה (כדי ששטחים שכבר נצפו יעבדו אופליין) — אם משנים URL/ספק של אריחים, חובה ל-bump גם את `TILE`, אחרת אריחים ישנים/פגומים נשארים במטמון של משתמשים קיימים לצמיתות. v1.6.1 (מעבר CARTO→Esri) פספס את זה; תוקן ב-v1.6.2.

## איך להוסיף תכונה / לשנות קוד

1. **שינויים בלוגיקת השרת** — `server.js` ערוך ישירות. אין hot reload — `node server.js` מחדש.
2. **שינויים בקליינט** — `index.html` ערוך ישירות. השרת מזהה את שינוי ה-mtime ומגיש את הגרסה החדשה (refresh בדפדפן). זכור ש-Service Worker עלול להגיש cached גרסה — חשוב ל-bump את `CN` (כרגע `red-alert-v12`) ב-`server.js` (משתנה `SW`) כדי להפעיל invalidate, או לפתוח DevTools → Application → Service Workers → Unregister.
2. **שינויים בקליינט** — `index.html` ערוך ישירות. השרת מזהה את שינוי ה-mtime ומגיש את הגרסה החדשה (refresh בדפדפן). זכור ש-Service Worker עלול להגיש cached גרסה — חשוב ל-bump את `CN` (כרגע `red-alert-v17`) ב-`server.js` (משתנה `SW`) כדי להפעיל invalidate, או לפתוח DevTools → Application → Service Workers → Unregister. אם שינית URL/ספק של אריחי מפה — bump גם את `TILE` (כרגע `red-alert-tiles-v2`), אחרת אריחים ישנים נשארים במטמון הנפרד לצמיתות (ה-`activate` handler לא מנקה אותו).
3. **הוספת עיר** — ערוך את `CITIES` ב-[lib.js](lib.js). פורמט: `"שם":{lat:X,lng:Y,r:"אזור",s:זמן_מיגון}`.
4. **הוספת שפה** — הוסף ערך ל-`LN`, ל-`LANG_META` (שם native + דגל) ול-`TTS_LOCALE` (קוד BCP-47) ב-[lib.js](lib.js) — כולל **כל** המפתחות שקיימים ב-`LN.en` (test/unit.js/הקוד לא בודקים זאת אוטומטית, אבל חוסר מפתח נופל חזרה ל-Hebrew בשקט). `<select id="langS">` ב-index.html נבנה אוטומטית מ-`LANG_META` דרך `initLangSelect()` — אין לערוך אותו ידנית.
5. **endpoint חדש** — הוסף `if (p === '/api/...')` ב-`server.js` ל-pipeline הקיים בתוך `http.createServer`. תזכור `track(p, code)` ו-`gz(req, res, body, ct)`.
Expand Down Expand Up @@ -210,6 +210,8 @@ npm run docker:run # docker run -p 3000:3000 ...
- **בדיקת `node test/unit.js` כושלת על `health fetch error`** — הפורט תפוס, או `server.js` לא קיים. הבדיקה משתמשת בפורט 3001-4000 רנדומלי.
- **PWA לא מתעדכן** — DevTools → Application → Service Workers → Unregister, או bump של `CN` ב-`SW` בתוך server.js.
- **אזעקות כפולות** — הדפדפן פתוח ב-2 טאבים; כל אחד מקבל SSE עצמאי. תקין.
- **המפה מטושטשת בזום עמוק מאוד (light/dark)** — תקין; `maxNativeZoom:16` על שכבות ה-Esri Canvas גורם ל-Leaflet להגדיל (upscale) את האריח האחרון מעבר לזום 16 במקום לבקש אריחים לא-קיימים. מרקרים/מקלטים לא מושפעים (הם overlay וקטורי, לא חלק מהאריח).
- **אריחי מפה ישנים/שבורים ממשיכים להופיע אצל משתמש ספציפי אחרי תיקון בקוד** — כנראה שכחת לבמפ את `TILE` (לא רק `CN`) ב-server.js; `TILE` הוא מטמון SW נפרד שלעולם לא מתנקה אוטומטית.

## אזהרות בטיחות

Expand Down
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,27 @@ All notable changes to this project are documented here. Format loosely follows
(`/openapi.yaml`, `/api/spec`, etc.) are unaffected — only internal file paths moved.
- Set the GitHub repo's "About" description.

## [1.6.2]

### Fixed
- **The service worker's separate tile cache was never invalidated when v1.6.1 swapped the map
tile provider (CARTO → Esri).** That cache (`TILE`) is deliberately excluded from the SW
`activate` handler's cleanup, by design, so previously-viewed map areas keep working offline —
but that also meant it survives every `CN` (app cache) bump. Anyone who'd loaded the map while
it was showing CARTO's "API KEY REQUIRED" watermark kept serving those same cached watermarked
tiles indefinitely after the fix shipped, since their cache key (URL) never changed... except it
did, since the URLs moved from `cartocdn.com` to `arcgisonline.com` — the real risk was just an
ever-growing pile of dead, unreachable cache entries crowding the ~500-tile cap. Bumped `TILE` to
force a clean cache regardless.
- Removed leftover `basemaps.cartocdn.com` entries from the CSP `img-src` allowlist and the service
worker's tile-cache hostname check — dead since v1.6.1, now that every basemap layer is Esri.
- Added `maxNativeZoom:16` to the light/dark Canvas base+reference tile layers. Unlike
`World_Imagery`, their real tile coverage doesn't extend past zoom ~16 in most regions; without
the cap, Leaflet would request tiles beyond that depth, which Esri doesn't reliably serve
(comes back blank/incomplete rather than a clean 404). The cap makes Leaflet upscale the deepest
real tile instead once you zoom in further, which stays legible since these are label/line-art
tiles rather than photography.

## [1.6.1]

### Fixed
Expand Down
8 changes: 5 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,13 +118,13 @@ npm run docker:run # docker run -p 3000:3000 ...
- **⚠️ אילוץ קריטי — רינדור מחדש של טאב לא-פעיל**: `updUI()` (שרץ מ-`schedUI()`/`procSA`/`visibilitychange` וכו') קורא ל-`refreshCurrentTab()` ולא ל-`renderSB()` ישירות — `refreshCurrentTab()` בודק את `currentTab` ומעדכן רק את `#sbC` של הטאב הפעיל בפועל. **לעולם אל תקראו ל-`renderSB()`/`renderHistoryTab()` בלי בדיקת `currentTab` קודם** — קריאה לא-מותנית כותבת על `#sbC` את תוכן האזעקות/היסטוריה גם כשמשתמש צופה בטאב אחר (זה בדיוק הבאג שתוקן: מעבר טאב בדפדפן החזיר את התצוגה לאזעקות במקום להישאר בטאב האחרון).
- **⚠️ אילוץ קריטי — שדות חיפוש חיים (`#fQ`, `#hQ`)**: `renderSB()`/`renderHistoryTab()` מחליפים את כל ה-`innerHTML` של `#sbC`, כולל את שדה החיפוש עצמו. כל שינוי בפונקציות האלה **חייב** לשמר `document.activeElement`/`selectionStart`/`selectionEnd` לפני ה-render ולשחזר אותם (`focus()`+`setSelectionRange()`) אחריו, אחרת כל הקשה במקלדת מוציאה את הפוקוס מהשדה (המשתמש לא יכול להקליד ברצף). `renderHistoryTab()` גם מדלג על ה-skeleton loader כשהטאב כבר מרונדר (רק בטעינה ראשונה), ומגן מפני race condition עם `historySeq`.
- **⚠️ אילוץ קריטי — `sDB()` לא שומר `type`**: רשומות ב-IndexedDB (`sDB()`) שומרות רק `typeKey` (מחרוזת), לא את אובייקט `type` המלא (icon/css/color) ש-`rItem()`/`addMrk()` דורשים. `renderHistoryTab()` משחזר אותו ידנית (`a.type=TM[a.typeKey]||TM.rockets`) אחרי `gDB()` — **זה היה באג אמיתי וארוך-טווח**: לפני התיקון, כל ביקור בטאב היסטוריה עם נתוני IndexedDB אמיתיים קרס על `Cannot read properties of undefined (reading 'icon')` ותקוע על ה-skeleton loader לנצח. אם מוסיפים עוד מקום שקורא רשומות גולמיות מ-`gDB()`/IndexedDB — לבדוק את זה שוב. יש טסט E2E ל-regression הזה (`test/e2e.js`, זורע רשומה ידנית ב-IndexedDB בפורמט המדויק של `sDB()`).
- **מפה**: Leaflet, כל שכבות הבסיס דרך Esri (`server.arcgisonline.com`, ללא מפתח API בשום מצב — hostname אחד ל-CSP `img-src`/SW tile-cache). מצב רגיל (בהיר/כהה): `Canvas/World_{Dark,Light}_Gray_Base` + שכבת שמות/גבולות נפרדת `Canvas/World_{Dark,Light}_Gray_Reference` (`basemapRef`, `pane:'labelsPane'`, תמיד דלוקה — לא ניתנת לכיבוי) — **עד 2026-08 היה זה CartoDB** (`basemaps.cartocdn.com`); הוחלף כי CARTO דרשו מפתח API והתחילו למרוח "API KEY REQUIRED" על כל אריח. שכבת לוויין חינמית — Esri World Imagery — דרך כפתור 🛰️ ב-`.mc` (`tglSat()`); מעליה אפשר להציג/להסתיר שכבת שמות מקומות נפרדת (`Reference/World_Boundaries_and_Places`, גם היא ב-`labelsPane`) דרך כפתור 🏷️ (`satLblB`/`tglSatLabels()`, מוצג רק כש-`satOn` — זו כן ניתנת לכיבוי, בשונה מ-`basemapRef`).
- **PWA**: SW מקודד בתוך `server.js` (משתנה `SW`), מטמון `red-alert-v12` + מטמון אריחים נפרד `red-alert-tiles-v1` (Leaflet basemap כולל לוויין ושכבת שמות, cache-first). שינוי ל-SW דורש bump של `CN` ב-server.js.
- **מפה**: Leaflet, כל שכבות הבסיס דרך Esri (`server.arcgisonline.com`, ללא מפתח API בשום מצב — hostname אחד ל-CSP `img-src`/SW tile-cache). מצב רגיל (בהיר/כהה): `Canvas/World_{Dark,Light}_Gray_Base` + שכבת שמות/גבולות נפרדת `Canvas/World_{Dark,Light}_Gray_Reference` (`basemapRef`, `pane:'labelsPane'`, תמיד דלוקה — לא ניתנת לכיבוי) — **עד 2026-08 היה זה CartoDB** (`basemaps.cartocdn.com`); הוחלף כי CARTO דרשו מפתח API והתחילו למרוח "API KEY REQUIRED" על כל אריח. **חשוב**: ל-Canvas layers (base+reference) אין כיסוי אמיתי מעבר לזום 16 ברוב האזורים (בניגוד ל-World_Imagery) — `tileOpts()` קובע `maxNativeZoom:16` כדי ש-Leaflet יגדיל (upscale) את האריח האחרון במקום לבקש אריחים לא-קיימים/ריקים. שכבת לוויין חינמית — Esri World Imagery — דרך כפתור 🛰️ ב-`.mc` (`tglSat()`); מעליה אפשר להציג/להסתיר שכבת שמות מקומות נפרדת (`Reference/World_Boundaries_and_Places`, גם היא ב-`labelsPane`) דרך כפתור 🏷️ (`satLblB`/`tglSatLabels()`, מוצג רק כש-`satOn` — זו כן ניתנת לכיבוי, בשונה מ-`basemapRef`).
- **PWA**: SW מקודד בתוך `server.js` (משתנה `SW`), מטמון `red-alert-v17` + מטמון אריחים נפרד `red-alert-tiles-v2` (Leaflet basemap כולל לוויין ושכבת שמות, cache-first). שינוי ל-SW דורש bump של `CN` ב-server.js. **שים לב**: `TILE` הוא מטמון נפרד שה-`activate` handler *לעולם לא* מנקה (כדי ששטחים שכבר נצפו יעבדו אופליין) — אם משנים URL/ספק של אריחים, חובה ל-bump גם את `TILE`, אחרת אריחים ישנים/פגומים נשארים במטמון של משתמשים קיימים לצמיתות. v1.6.1 (מעבר CARTO→Esri) פספס את זה; תוקן ב-v1.6.2.

## איך להוסיף תכונה / לשנות קוד

1. **שינויים בלוגיקת השרת** — `server.js` ערוך ישירות. אין hot reload — `node server.js` מחדש.
2. **שינויים בקליינט** — `index.html` ערוך ישירות. השרת מזהה את שינוי ה-mtime ומגיש את הגרסה החדשה (refresh בדפדפן). זכור ש-Service Worker עלול להגיש cached גרסה — חשוב ל-bump את `CN` (כרגע `red-alert-v12`) ב-`server.js` (משתנה `SW`) כדי להפעיל invalidate, או לפתוח DevTools → Application → Service Workers → Unregister.
2. **שינויים בקליינט** — `index.html` ערוך ישירות. השרת מזהה את שינוי ה-mtime ומגיש את הגרסה החדשה (refresh בדפדפן). זכור ש-Service Worker עלול להגיש cached גרסה — חשוב ל-bump את `CN` (כרגע `red-alert-v17`) ב-`server.js` (משתנה `SW`) כדי להפעיל invalidate, או לפתוח DevTools → Application → Service Workers → Unregister. אם שינית URL/ספק של אריחי מפה — bump גם את `TILE` (כרגע `red-alert-tiles-v2`), אחרת אריחים ישנים נשארים במטמון הנפרד לצמיתות (ה-`activate` handler לא מנקה אותו).
3. **הוספת עיר** — ערוך את `CITIES` ב-[lib.js](lib.js). פורמט: `"שם":{lat:X,lng:Y,r:"אזור",s:זמן_מיגון}`.
4. **הוספת שפה** — הוסף ערך ל-`LN`, ל-`LANG_META` (שם native + דגל) ול-`TTS_LOCALE` (קוד BCP-47) ב-[lib.js](lib.js) — כולל **כל** המפתחות שקיימים ב-`LN.en` (test/unit.js/הקוד לא בודקים זאת אוטומטית, אבל חוסר מפתח נופל חזרה ל-Hebrew בשקט). `<select id="langS">` ב-index.html נבנה אוטומטית מ-`LANG_META` דרך `initLangSelect()` — אין לערוך אותו ידנית.
5. **endpoint חדש** — הוסף `if (p === '/api/...')` ב-`server.js` ל-pipeline הקיים בתוך `http.createServer`. תזכור `track(p, code)` ו-`gz(req, res, body, ct)`.
Expand Down Expand Up @@ -210,6 +210,8 @@ npm run docker:run # docker run -p 3000:3000 ...
- **בדיקת `node test/unit.js` כושלת על `health fetch error`** — הפורט תפוס, או `server.js` לא קיים. הבדיקה משתמשת בפורט 3001-4000 רנדומלי.
- **PWA לא מתעדכן** — DevTools → Application → Service Workers → Unregister, או bump של `CN` ב-`SW` בתוך server.js.
- **אזעקות כפולות** — הדפדפן פתוח ב-2 טאבים; כל אחד מקבל SSE עצמאי. תקין.
- **המפה מטושטשת בזום עמוק מאוד (light/dark)** — תקין; `maxNativeZoom:16` על שכבות ה-Esri Canvas גורם ל-Leaflet להגדיל (upscale) את האריח האחרון מעבר לזום 16 במקום לבקש אריחים לא-קיימים. מרקרים/מקלטים לא מושפעים (הם overlay וקטורי, לא חלק מהאריח).
- **אריחי מפה ישנים/שבורים ממשיכים להופיע אצל משתמש ספציפי אחרי תיקון בקוד** — כנראה שכחת לבמפ את `TILE` (לא רק `CN`) ב-server.js; `TILE` הוא מטמון SW נפרד שלעולם לא מתנקה אוטומטית.

## אזהרות בטיחות

Expand Down
Loading
Loading