From 879222f728fb6269c392420534e2532acc0d8ab8 Mon Sep 17 00:00:00 2001 From: Marco Supino Date: Mon, 25 May 2026 04:50:32 +0000 Subject: [PATCH 1/8] Add quick-action row above sections (Proposal A v2, replaces #191) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 5 most-used buttons moved out of their sections into a new top row (#tb-quick) so they work without expanding anything: โœ๏ธ Add Waypoint, ๐Ÿ“ Add Note, โŒ– Fit, ๐Ÿ“‹ Flight Plan, โฌ‡ Export The original buttons are removed from Build / Numbers / Export sections โ€” no duplicate IDs. Build keeps the search input + Reverse + Clear; Numbers keeps Airport Charts; Export keeps Import + Open in Google Earth. CSS-only #tb-quick โ€” column inside the toolbar's existing flex, divider below to separate from the collapsible sections. No JS rewiring. Replaces the original PR #191 which was structurally incompatible with the activity-based sections shipped in #192. Co-Authored-By: Claude Sonnet 4.6 --- docs/index.html | 16 +++++++++++----- docs/style.css | 12 ++++++++++++ 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/docs/index.html b/docs/index.html index 75830649..47c13368 100644 --- a/docs/index.html +++ b/docs/index.html @@ -111,16 +111,24 @@ }()); + +
+ + + + + +
+
- - -
@@ -188,7 +196,6 @@
-
@@ -197,7 +204,6 @@
- diff --git a/docs/style.css b/docs/style.css index 83f9fd82..c7f9eaac 100644 --- a/docs/style.css +++ b/docs/style.css @@ -970,3 +970,15 @@ html, body { 50% { opacity: 0.3; } } .blink-warn { animation: blink-warn 1.2s ease-in-out infinite; } + +/* Quick-action row at the top of the toolbar (#191). Always visible so the + * 5 most-used actions don't require expanding a section. */ +#tb-quick { + display: flex; + flex-direction: column; + gap: 4px; + padding-bottom: 4px; + border-bottom: 1px solid #3a3636; + margin-bottom: 2px; +} +#tb-quick button { font-weight: 600; } From 6797d6100c70e0989a1d2c00543ff1ef3fa092fb Mon Sep 17 00:00:00 2001 From: Marco Supino Date: Wed, 27 May 2026 09:14:41 +0300 Subject: [PATCH 2/8] Add tests for quick-action row (#245) 14 tests: existence, no-duplicate-IDs, CSS, and functional tests for all 5 buttons (tool-add, tool-note, fit, plan, save). --- tests/quick-action-row.spec.js | 141 +++++++++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 tests/quick-action-row.spec.js diff --git a/tests/quick-action-row.spec.js b/tests/quick-action-row.spec.js new file mode 100644 index 00000000..198c4a76 --- /dev/null +++ b/tests/quick-action-row.spec.js @@ -0,0 +1,141 @@ +// @ts-check +// PR #245 โ€” Quick-action row at the top of the toolbar: 5 most-used buttons +// are always visible above the collapsible sections, with no duplicate IDs. +const { test, expect } = require('./_setup'); + +async function boot(page) { + await page.addInitScript(() => { + try { + if (localStorage.getItem('__test_qar_init') !== '1') { + localStorage.clear(); + sessionStorage.clear(); + localStorage.setItem('__test_qar_init', '1'); + } + } catch (e) {} + }); + await page.goto('/?lang=en'); + await page.waitForFunction(() => typeof state !== 'undefined'); +} + +test.describe('Quick-action row (#tb-quick)', () => { + test('exists with the 5 expected buttons', async ({ page }) => { + await boot(page); + const quick = page.locator('#tb-quick'); + await expect(quick).toBeVisible(); + + const ids = await quick.locator('button').evaluateAll(btns => btns.map(b => b.id)); + expect(ids).toEqual(['tool-add', 'tool-note', 'fit', 'plan', 'save']); + }); + + test('is not a collapsible section (no .tb-section class, no header)', async ({ page }) => { + await boot(page); + const quick = page.locator('#tb-quick'); + await expect(quick).not.toHaveClass(/tb-section/); + await expect(quick.locator('.tb-section-head')).toHaveCount(0); + }); + + test('always visible when toolbar is expanded', async ({ page }) => { + await boot(page); + await expect(page.locator('#tb-quick')).toBeVisible(); + }); + + test.describe('no duplicate IDs โ€” buttons removed from collapsible sections', () => { + test('tool-add and tool-note not in Build section', async ({ page }) => { + await boot(page); + const build = page.locator('.tb-section[data-sec="build"]'); + await expect(build.locator('#tool-add')).toHaveCount(0); + await expect(build.locator('#tool-note')).toHaveCount(0); + }); + + test('fit not in Build section', async ({ page }) => { + await boot(page); + const build = page.locator('.tb-section[data-sec="build"]'); + await expect(build.locator('#fit')).toHaveCount(0); + }); + + test('plan not in Charts section', async ({ page }) => { + await boot(page); + const charts = page.locator('.tb-section[data-sec="charts"]'); + await expect(charts.locator('#plan')).toHaveCount(0); + }); + + test('save not in Export section', async ({ page }) => { + await boot(page); + const exportSec = page.locator('.tb-section[data-sec="export"]'); + await expect(exportSec.locator('#save')).toHaveCount(0); + }); + }); + + test.describe('CSS styling', () => { + test('buttons have font-weight 600', async ({ page }) => { + await boot(page); + const btn = page.locator('#tb-quick button').first(); + const fw = await btn.evaluate(el => getComputedStyle(el).fontWeight); + expect(fw).toBe('600'); + }); + + test('row has a bottom border divider', async ({ page }) => { + await boot(page); + const quick = page.locator('#tb-quick'); + const bb = await quick.evaluate(el => getComputedStyle(el).borderBottom); + expect(bb).toMatch(/1px/); + }); + }); + + test.describe('quick-action buttons are functional', () => { + test('tool-add enters add-waypoint mode', async ({ page }) => { + await boot(page); + await page.locator('#tb-quick #tool-add').click(); + const hasClass = await page.locator('#map').evaluate(el => el.classList.contains('add')); + expect(hasClass).toBe(true); + // Exit mode + await page.keyboard.press('Escape'); + }); + + test('tool-note enters add-note mode', async ({ page }) => { + await boot(page); + await page.locator('#tb-quick #tool-note').click(); + const hasAddClass = await page.locator('#map').evaluate(el => el.classList.contains('add')); + expect(hasAddClass).toBe(true); + const isActive = await page.locator('#tb-quick #tool-note').evaluate(el => el.classList.contains('active')); + expect(isActive).toBe(true); + await page.keyboard.press('Escape'); + }); + + test('fit triggers fitView', async ({ page }) => { + await boot(page); + await page.evaluate(() => { + state.waypoints.push({ lat: 32.0, lng: 34.8, name: 'A' }); + state.waypoints.push({ lat: 32.1, lng: 34.9, name: 'B' }); + }); + await page.locator('#tb-quick #fit').click(); + const center = await page.evaluate(() => map.getCenter()); + expect(center.lat).toBeCloseTo(32.05, 1); + }); + + test('plan toggles flight plan modal', async ({ page }) => { + await boot(page); + await page.evaluate(() => { + state.waypoints.push({ lat: 32.0, lng: 34.8, name: 'A' }); + state.waypoints.push({ lat: 32.1, lng: 34.9, name: 'B' }); + syncLegs(); + draw(); + }); + await page.locator('#tb-quick #plan').click(); + await expect(page.locator('.modal-back.flight-plan')).toBeVisible(); + await page.locator('#tb-quick #plan').click(); + await expect(page.locator('.modal-back.flight-plan')).not.toBeVisible(); + }); + + test('save triggers JSON export', async ({ page }) => { + await boot(page); + await page.evaluate(() => { + state.waypoints.push({ lat: 32.0, lng: 34.8, name: 'A' }); + state.waypoints.push({ lat: 32.1, lng: 34.9, name: 'B' }); + }); + const download = page.waitForEvent('download', { timeout: 3000 }); + await page.locator('#tb-quick #save').click(); + await download; + }); + }); +}); From b5d7c903f4c649cb3267b869ff3ac72cb9f364e3 Mon Sep 17 00:00:00 2001 From: Marco Supino Date: Wed, 27 May 2026 09:29:59 +0300 Subject: [PATCH 3/8] Simplify READMEs, point to wiki for documentation --- README.md | 45 ++++-------------------- docs/README.md | 92 +++----------------------------------------------- 2 files changed, 11 insertions(+), 126 deletions(-) diff --git a/README.md b/README.md index cfd78767..6450a28c 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,14 @@ # NavAid Browser-based CVFR / Israel-area flight-route planner. Plain HTML + -CSS + JavaScript on top of Leaflet, no build step. Plot waypoints -on a slippy chart, label legs with altitude / speed / time, drop -free-text notes, and export the framed result as a high-resolution -PNG. +CSS + JavaScript on top of Leaflet, no build step. ## Links - **Live (production):** https://msupino.github.io/NavigationApp/ - **Live (staging):** https://msupino.github.io/NavigationApp/staging/ - **Repo:** https://github.com/msupino/NavigationApp -- **Wiki:** https://github.com/msupino/NavigationApp/wiki -- **App docs:** [`docs/README.md`](docs/README.md) +- **Wiki:** https://github.com/msupino/NavigationApp/wiki โ€” full documentation / ืชื™ืขื•ื“ ืžืœื ## Run locally @@ -23,38 +19,9 @@ python3 -m http.server -d docs 8000 ## License & data -NavAid (the source code) is released under the [MIT License](LICENSE) โ€” do -whatever you want, no warranty, no liability. +NavAid is released under the [MIT License](LICENSE) โ€” no warranty, no liability. -Data layers retain their own terms: charts are ยฉ flight-maps.com / CAAI; -imagery is ยฉ Esri (World Imagery); map data is ยฉ OpenStreetMap contributors; -VFR reporting points are derived from the -[ForeFlight Israel Base Pack](https://www.foreflightisrael.xyz/) / -ICAO / CAAI public AIP data. +Charts ยฉ flight-maps.com / CAAI; imagery ยฉ Esri; map data ยฉ OpenStreetMap +contributors; VFR reporting points ยฉ ICAO / CAAI. -NavAid is a planning aid only and is not certified for primary navigation. - ---- - -
- -## ืขื‘ืจื™ืช - -**NavAid** โ€” ื›ืœื™ ืœืชื›ื ื•ืŸ ืžืกืœื•ืœื™ ื˜ื™ืกืช CVFR ื‘ืื–ื•ืจ ื™ืฉืจืืœ. ืคื•ืขืœ ื‘ื“ืคื“ืคืŸ, ืœืœื ื”ืชืงื ื”. - -- **ื’ืจืกื” ื—ื™ื”:** https://msupino.github.io/NavigationApp/ -- **ื’ืจืกืช ื‘ื“ื™ืงื•ืช:** https://msupino.github.io/NavigationApp/staging/ -- **ื”ื•ืกืคืช ื ืงื•ื“ืช ืฆื™ื•ืŸ** โ€” ืœื—ื™ืฆื” ืขืœ ื”ืžืคื” ืžื•ืกื™ืคื” ื ืงื•ื“ื”; ื”ืงื˜ืขื™ื ืžืชื—ื‘ืจื™ื ืื•ื˜ื•ืžื˜ื™ืช. - ืœื—ื™ืฆื” ืื• ื’ืจื™ืจื” ืฉืœ ื ืงื•ื“ื” ืื• ืงื˜ืข ืงื™ื™ืžื™ื ืคื•ืชื—ืช ืื•ืชื ืœืขืจื™ื›ื”. -- **ื”ื•ืกืคืช ื”ืขืจื”** โ€” ืชื™ื‘ืช ื˜ืงืกื˜ ื—ื•ืคืฉื™ ืขืœ ื”ืžืคื”, ืขื ื‘ื—ื™ืจืช ืฆื‘ืข. -- **ื”ื™ืคื•ืš ืžืกืœื•ืœ** โ€” ื”ื™ืคื•ืš ื›ื™ื•ื•ืŸ ื”ื˜ื™ืกื”. -- **ืฉืžื™ืจื” / ื˜ืขื™ื ื”** โ€” ืงื•ื‘ืฅ JSON ืขื ื”ืžืกืœื•ืœ ื•ื”ื”ืขืจื•ืช. -- **ื ืงื•ื“ื•ืช ื ื™ื•ื•ื˜** โ€” ืฉื›ื‘ืช ื ืงื•ื“ื•ืช ื”ื“ื™ื•ื•ื— (VFR) ื”ืžืคื•ืจืกืžื•ืช ื‘ื™ืฉืจืืœ. -- **ืชื•ื›ื ื™ืช ื˜ื™ืกื”** โ€” ื˜ื‘ืœื” ืขื ื›ื™ื•ื•ืŸ, ืžืจื—ืง, ืžื”ื™ืจื•ืช, ื’ื•ื‘ื” ื•ื–ืžืŸ ืœื›ืœ ืงื˜ืข. -- **ื”ื“ืคืกื” / ื™ื™ืฆื•ื PNG** โ€” ืฉืžื™ืจืช ื”ืžืคื” ื•ื”ืžืกืœื•ืœ ื›ืชืžื•ื ื” ื‘ืจื–ื•ืœื•ืฆื™ื” ื’ื‘ื•ื”ื” (A3 / A4). - -ื”ืžืกืœื•ืœ ื•ื”ืชืฆื•ื’ื” ื ืฉืžืจื™ื ื‘ื“ืคื“ืคืŸ โ€” ืจืขื ื•ืŸ ื”ื“ืฃ ืžืฉื—ื–ืจ ืืช ื”ืขื‘ื•ื“ื” ื”ืื—ืจื•ื ื”. - -ืžืคื•ืช ืชืขื•ืคื”: ยฉ flight-maps.com / ืจืช"ื โ€” ืœืฉื™ืžื•ืฉ ืื™ืฉื™. - -
+NavAid is a planning aid only and not certified for primary navigation. diff --git a/docs/README.md b/docs/README.md index b0d3f6ee..13bc7253 100644 --- a/docs/README.md +++ b/docs/README.md @@ -5,6 +5,7 @@ CSS + JavaScript on top of Leaflet, no build step. - **Live (production):** https://msupino.github.io/NavigationApp/ - **Live (staging):** https://msupino.github.io/NavigationApp/staging/ +- **Wiki:** https://github.com/msupino/NavigationApp/wiki โ€” full documentation / ืชื™ืขื•ื“ ืžืœื ## Run locally @@ -15,94 +16,11 @@ python3 -m http.server -d docs 8000 # http://localhost:8000 ``` -## Use - -The toolbar is a vertical column with a `โ‹ฏ` grip on top โ€” drag it -anywhere on screen (its position is remembered). - -- **Add Waypoint** โ€” click the map to drop a waypoint; legs connect - them. With no mode active, click / drag an existing waypoint or leg - to select and edit it. -- **Add Note** โ€” drop a free-text annotation box at the click point; - pick a colour for it in the inspector. -- **Reverse Route** โ€” invert the route, swapping each leg's altitude - pair and rotating waypoint name text 180ยฐ so the chart turned around - still reads upright. -- **Clear map** โ€” remove all waypoints + notes (with confirm). -- **Save / Load** โ€” JSON file with the full route + notes. -- **Fit** โ€” frame the route in view. -- **๐Ÿ“‹ Plan** โ€” open a modal with a per-leg flight plan table - (From, To, Hdg, Dist, Speed, Alt, Time, totals). -- **Show return path** โ€” render the outbound (return-direction) - marker. -- **Show leg dist** โ€” yellow distance badge in the middle of each - leg. -- **Highlight diff** โ€” purple halo on legs whose altitude differs - from the adjacent leg, marking the climb / descent point. -- **Show Nav Waypoints** (default on) โ€” overlays 238 published - Israeli VFR reporting points; the 5-letter ID labels appear at - higher zoom. -- **Transparency** slider โ€” opacity of every label background. -- **Text size** slider โ€” waypoint name / number font + circle size. -- **Mag var** โ€” magnetic variation in the "value added to true" - convention (Israel โ‰ˆ โˆ’5, shown as `(5ยฐE)` next to the input). -- **A3 / A4** โ€” show a print-frame rectangle at 1:250 000; clicking - the same button again clears it. A modal asks Landscape vs - Portrait. -- **โฌ‡ Save PNG** โ€” exports the framed map + route as a high-resolution - PNG, rendered at the highest practical native tile zoom. Tiles - are pulled through `images.weserv.nl` so the export canvas stays - CORS-clean. - -Click a waypoint to open the inspector โ€” the title doubles as the -editable name. If a name is set it's drawn inside the circle -(replacing the sequence number). Editing a leg's altitude -propagates along the same cruise level until a different altitude -already exists. - -## Map layers - -CVFR ยท Nav ยท Low Altitude ยท Helicopters ยท Satellite (Esri) ยท OSM. -Selected base layer is remembered across reloads. - -## Storage - -Route, notes, current view, label opacity, text size, magnetic -variation, toolbar position, base layer, and the nav-waypoints -toggle are all kept in `localStorage` (under the `navaid.*` prefix) -so a reload picks up where you left off. - ## License & data -NavAid (the source code) is released under the [MIT License](../LICENSE) โ€” -do whatever you want, no warranty, no liability. - -Charts are ยฉ flight-maps.com / CAAI. Imagery: ยฉ Esri (World Imagery). -Map data: ยฉ OpenStreetMap contributors. VFR reporting points: ICAO/CAAI -public AIP data. - -NavAid is a planning aid only and is not certified for primary navigation. - ---- - -
- -## ืขื‘ืจื™ืช - -**NavAid** โ€” ื›ืœื™ ืœืชื›ื ื•ืŸ ืžืกืœื•ืœื™ ื˜ื™ืกืช CVFR ื‘ืื–ื•ืจ ื™ืฉืจืืœ. ืคื•ืขืœ ื‘ื“ืคื“ืคืŸ, ืœืœื ื”ืชืงื ื”. - -- **ื’ืจืกื” ื—ื™ื”:** https://msupino.github.io/NavigationApp/ -- **ื”ื•ืกืคืช ื ืงื•ื“ืช ืฆื™ื•ืŸ** โ€” ืœื—ื™ืฆื” ืขืœ ื”ืžืคื” ืžื•ืกื™ืคื” ื ืงื•ื“ื”; ื”ืงื˜ืขื™ื ืžืชื—ื‘ืจื™ื ืื•ื˜ื•ืžื˜ื™ืช. - ืœื—ื™ืฆื” ืื• ื’ืจื™ืจื” ืฉืœ ื ืงื•ื“ื” ืื• ืงื˜ืข ืงื™ื™ืžื™ื ืคื•ืชื—ืช ืื•ืชื ืœืขืจื™ื›ื”. -- **ื”ื•ืกืคืช ื”ืขืจื”** โ€” ืชื™ื‘ืช ื˜ืงืกื˜ ื—ื•ืคืฉื™ ืขืœ ื”ืžืคื”, ืขื ื‘ื—ื™ืจืช ืฆื‘ืข. -- **ื”ื™ืคื•ืš ืžืกืœื•ืœ** โ€” ื”ื™ืคื•ืš ื›ื™ื•ื•ืŸ ื”ื˜ื™ืกื”. -- **ืฉืžื™ืจื” / ื˜ืขื™ื ื”** โ€” ืงื•ื‘ืฅ JSON ืขื ื”ืžืกืœื•ืœ ื•ื”ื”ืขืจื•ืช. -- **ื ืงื•ื“ื•ืช ื ื™ื•ื•ื˜** โ€” ืฉื›ื‘ืช ื ืงื•ื“ื•ืช ื”ื“ื™ื•ื•ื— (VFR) ื”ืžืคื•ืจืกืžื•ืช ื‘ื™ืฉืจืืœ. -- **ืชื•ื›ื ื™ืช ื˜ื™ืกื”** โ€” ื˜ื‘ืœื” ืขื ื›ื™ื•ื•ืŸ, ืžืจื—ืง, ืžื”ื™ืจื•ืช, ื’ื•ื‘ื” ื•ื–ืžืŸ ืœื›ืœ ืงื˜ืข. -- **ื”ื“ืคืกื” / ื™ื™ืฆื•ื PNG** โ€” ืฉืžื™ืจืช ื”ืžืคื” ื•ื”ืžืกืœื•ืœ ื›ืชืžื•ื ื” ื‘ืจื–ื•ืœื•ืฆื™ื” ื’ื‘ื•ื”ื” (A3 / A4). - -ื”ืžืกืœื•ืœ ื•ื”ืชืฆื•ื’ื” ื ืฉืžืจื™ื ื‘ื“ืคื“ืคืŸ โ€” ืจืขื ื•ืŸ ื”ื“ืฃ ืžืฉื—ื–ืจ ืืช ื”ืขื‘ื•ื“ื” ื”ืื—ืจื•ื ื”. +NavAid is released under the [MIT License](../LICENSE) โ€” no warranty, no liability. -ืžืคื•ืช ืชืขื•ืคื”: ยฉ flight-maps.com / ืจืช"ื โ€” ืœืฉื™ืžื•ืฉ ืื™ืฉื™. +Charts ยฉ flight-maps.com / CAAI; imagery ยฉ Esri; map data ยฉ OpenStreetMap +contributors; VFR reporting points ยฉ ICAO / CAAI. -
+NavAid is a planning aid only and not certified for primary navigation. From f19071ada27bfbdd3410608a8c36f3322e9bab25 Mon Sep 17 00:00:00 2001 From: Marco Supino Date: Wed, 27 May 2026 09:32:02 +0300 Subject: [PATCH 4/8] Revert "Simplify READMEs, point to wiki for documentation" This reverts commit b5d7c903f4c649cb3267b869ff3ac72cb9f364e3. --- README.md | 45 ++++++++++++++++++++---- docs/README.md | 92 +++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 126 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 6450a28c..cfd78767 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,18 @@ # NavAid Browser-based CVFR / Israel-area flight-route planner. Plain HTML + -CSS + JavaScript on top of Leaflet, no build step. +CSS + JavaScript on top of Leaflet, no build step. Plot waypoints +on a slippy chart, label legs with altitude / speed / time, drop +free-text notes, and export the framed result as a high-resolution +PNG. ## Links - **Live (production):** https://msupino.github.io/NavigationApp/ - **Live (staging):** https://msupino.github.io/NavigationApp/staging/ - **Repo:** https://github.com/msupino/NavigationApp -- **Wiki:** https://github.com/msupino/NavigationApp/wiki โ€” full documentation / ืชื™ืขื•ื“ ืžืœื +- **Wiki:** https://github.com/msupino/NavigationApp/wiki +- **App docs:** [`docs/README.md`](docs/README.md) ## Run locally @@ -19,9 +23,38 @@ python3 -m http.server -d docs 8000 ## License & data -NavAid is released under the [MIT License](LICENSE) โ€” no warranty, no liability. +NavAid (the source code) is released under the [MIT License](LICENSE) โ€” do +whatever you want, no warranty, no liability. -Charts ยฉ flight-maps.com / CAAI; imagery ยฉ Esri; map data ยฉ OpenStreetMap -contributors; VFR reporting points ยฉ ICAO / CAAI. +Data layers retain their own terms: charts are ยฉ flight-maps.com / CAAI; +imagery is ยฉ Esri (World Imagery); map data is ยฉ OpenStreetMap contributors; +VFR reporting points are derived from the +[ForeFlight Israel Base Pack](https://www.foreflightisrael.xyz/) / +ICAO / CAAI public AIP data. -NavAid is a planning aid only and not certified for primary navigation. +NavAid is a planning aid only and is not certified for primary navigation. + +--- + +
+ +## ืขื‘ืจื™ืช + +**NavAid** โ€” ื›ืœื™ ืœืชื›ื ื•ืŸ ืžืกืœื•ืœื™ ื˜ื™ืกืช CVFR ื‘ืื–ื•ืจ ื™ืฉืจืืœ. ืคื•ืขืœ ื‘ื“ืคื“ืคืŸ, ืœืœื ื”ืชืงื ื”. + +- **ื’ืจืกื” ื—ื™ื”:** https://msupino.github.io/NavigationApp/ +- **ื’ืจืกืช ื‘ื“ื™ืงื•ืช:** https://msupino.github.io/NavigationApp/staging/ +- **ื”ื•ืกืคืช ื ืงื•ื“ืช ืฆื™ื•ืŸ** โ€” ืœื—ื™ืฆื” ืขืœ ื”ืžืคื” ืžื•ืกื™ืคื” ื ืงื•ื“ื”; ื”ืงื˜ืขื™ื ืžืชื—ื‘ืจื™ื ืื•ื˜ื•ืžื˜ื™ืช. + ืœื—ื™ืฆื” ืื• ื’ืจื™ืจื” ืฉืœ ื ืงื•ื“ื” ืื• ืงื˜ืข ืงื™ื™ืžื™ื ืคื•ืชื—ืช ืื•ืชื ืœืขืจื™ื›ื”. +- **ื”ื•ืกืคืช ื”ืขืจื”** โ€” ืชื™ื‘ืช ื˜ืงืกื˜ ื—ื•ืคืฉื™ ืขืœ ื”ืžืคื”, ืขื ื‘ื—ื™ืจืช ืฆื‘ืข. +- **ื”ื™ืคื•ืš ืžืกืœื•ืœ** โ€” ื”ื™ืคื•ืš ื›ื™ื•ื•ืŸ ื”ื˜ื™ืกื”. +- **ืฉืžื™ืจื” / ื˜ืขื™ื ื”** โ€” ืงื•ื‘ืฅ JSON ืขื ื”ืžืกืœื•ืœ ื•ื”ื”ืขืจื•ืช. +- **ื ืงื•ื“ื•ืช ื ื™ื•ื•ื˜** โ€” ืฉื›ื‘ืช ื ืงื•ื“ื•ืช ื”ื“ื™ื•ื•ื— (VFR) ื”ืžืคื•ืจืกืžื•ืช ื‘ื™ืฉืจืืœ. +- **ืชื•ื›ื ื™ืช ื˜ื™ืกื”** โ€” ื˜ื‘ืœื” ืขื ื›ื™ื•ื•ืŸ, ืžืจื—ืง, ืžื”ื™ืจื•ืช, ื’ื•ื‘ื” ื•ื–ืžืŸ ืœื›ืœ ืงื˜ืข. +- **ื”ื“ืคืกื” / ื™ื™ืฆื•ื PNG** โ€” ืฉืžื™ืจืช ื”ืžืคื” ื•ื”ืžืกืœื•ืœ ื›ืชืžื•ื ื” ื‘ืจื–ื•ืœื•ืฆื™ื” ื’ื‘ื•ื”ื” (A3 / A4). + +ื”ืžืกืœื•ืœ ื•ื”ืชืฆื•ื’ื” ื ืฉืžืจื™ื ื‘ื“ืคื“ืคืŸ โ€” ืจืขื ื•ืŸ ื”ื“ืฃ ืžืฉื—ื–ืจ ืืช ื”ืขื‘ื•ื“ื” ื”ืื—ืจื•ื ื”. + +ืžืคื•ืช ืชืขื•ืคื”: ยฉ flight-maps.com / ืจืช"ื โ€” ืœืฉื™ืžื•ืฉ ืื™ืฉื™. + +
diff --git a/docs/README.md b/docs/README.md index 13bc7253..b0d3f6ee 100644 --- a/docs/README.md +++ b/docs/README.md @@ -5,7 +5,6 @@ CSS + JavaScript on top of Leaflet, no build step. - **Live (production):** https://msupino.github.io/NavigationApp/ - **Live (staging):** https://msupino.github.io/NavigationApp/staging/ -- **Wiki:** https://github.com/msupino/NavigationApp/wiki โ€” full documentation / ืชื™ืขื•ื“ ืžืœื ## Run locally @@ -16,11 +15,94 @@ python3 -m http.server -d docs 8000 # http://localhost:8000 ``` +## Use + +The toolbar is a vertical column with a `โ‹ฏ` grip on top โ€” drag it +anywhere on screen (its position is remembered). + +- **Add Waypoint** โ€” click the map to drop a waypoint; legs connect + them. With no mode active, click / drag an existing waypoint or leg + to select and edit it. +- **Add Note** โ€” drop a free-text annotation box at the click point; + pick a colour for it in the inspector. +- **Reverse Route** โ€” invert the route, swapping each leg's altitude + pair and rotating waypoint name text 180ยฐ so the chart turned around + still reads upright. +- **Clear map** โ€” remove all waypoints + notes (with confirm). +- **Save / Load** โ€” JSON file with the full route + notes. +- **Fit** โ€” frame the route in view. +- **๐Ÿ“‹ Plan** โ€” open a modal with a per-leg flight plan table + (From, To, Hdg, Dist, Speed, Alt, Time, totals). +- **Show return path** โ€” render the outbound (return-direction) + marker. +- **Show leg dist** โ€” yellow distance badge in the middle of each + leg. +- **Highlight diff** โ€” purple halo on legs whose altitude differs + from the adjacent leg, marking the climb / descent point. +- **Show Nav Waypoints** (default on) โ€” overlays 238 published + Israeli VFR reporting points; the 5-letter ID labels appear at + higher zoom. +- **Transparency** slider โ€” opacity of every label background. +- **Text size** slider โ€” waypoint name / number font + circle size. +- **Mag var** โ€” magnetic variation in the "value added to true" + convention (Israel โ‰ˆ โˆ’5, shown as `(5ยฐE)` next to the input). +- **A3 / A4** โ€” show a print-frame rectangle at 1:250 000; clicking + the same button again clears it. A modal asks Landscape vs + Portrait. +- **โฌ‡ Save PNG** โ€” exports the framed map + route as a high-resolution + PNG, rendered at the highest practical native tile zoom. Tiles + are pulled through `images.weserv.nl` so the export canvas stays + CORS-clean. + +Click a waypoint to open the inspector โ€” the title doubles as the +editable name. If a name is set it's drawn inside the circle +(replacing the sequence number). Editing a leg's altitude +propagates along the same cruise level until a different altitude +already exists. + +## Map layers + +CVFR ยท Nav ยท Low Altitude ยท Helicopters ยท Satellite (Esri) ยท OSM. +Selected base layer is remembered across reloads. + +## Storage + +Route, notes, current view, label opacity, text size, magnetic +variation, toolbar position, base layer, and the nav-waypoints +toggle are all kept in `localStorage` (under the `navaid.*` prefix) +so a reload picks up where you left off. + ## License & data -NavAid is released under the [MIT License](../LICENSE) โ€” no warranty, no liability. +NavAid (the source code) is released under the [MIT License](../LICENSE) โ€” +do whatever you want, no warranty, no liability. + +Charts are ยฉ flight-maps.com / CAAI. Imagery: ยฉ Esri (World Imagery). +Map data: ยฉ OpenStreetMap contributors. VFR reporting points: ICAO/CAAI +public AIP data. + +NavAid is a planning aid only and is not certified for primary navigation. + +--- + +
+ +## ืขื‘ืจื™ืช + +**NavAid** โ€” ื›ืœื™ ืœืชื›ื ื•ืŸ ืžืกืœื•ืœื™ ื˜ื™ืกืช CVFR ื‘ืื–ื•ืจ ื™ืฉืจืืœ. ืคื•ืขืœ ื‘ื“ืคื“ืคืŸ, ืœืœื ื”ืชืงื ื”. + +- **ื’ืจืกื” ื—ื™ื”:** https://msupino.github.io/NavigationApp/ +- **ื”ื•ืกืคืช ื ืงื•ื“ืช ืฆื™ื•ืŸ** โ€” ืœื—ื™ืฆื” ืขืœ ื”ืžืคื” ืžื•ืกื™ืคื” ื ืงื•ื“ื”; ื”ืงื˜ืขื™ื ืžืชื—ื‘ืจื™ื ืื•ื˜ื•ืžื˜ื™ืช. + ืœื—ื™ืฆื” ืื• ื’ืจื™ืจื” ืฉืœ ื ืงื•ื“ื” ืื• ืงื˜ืข ืงื™ื™ืžื™ื ืคื•ืชื—ืช ืื•ืชื ืœืขืจื™ื›ื”. +- **ื”ื•ืกืคืช ื”ืขืจื”** โ€” ืชื™ื‘ืช ื˜ืงืกื˜ ื—ื•ืคืฉื™ ืขืœ ื”ืžืคื”, ืขื ื‘ื—ื™ืจืช ืฆื‘ืข. +- **ื”ื™ืคื•ืš ืžืกืœื•ืœ** โ€” ื”ื™ืคื•ืš ื›ื™ื•ื•ืŸ ื”ื˜ื™ืกื”. +- **ืฉืžื™ืจื” / ื˜ืขื™ื ื”** โ€” ืงื•ื‘ืฅ JSON ืขื ื”ืžืกืœื•ืœ ื•ื”ื”ืขืจื•ืช. +- **ื ืงื•ื“ื•ืช ื ื™ื•ื•ื˜** โ€” ืฉื›ื‘ืช ื ืงื•ื“ื•ืช ื”ื“ื™ื•ื•ื— (VFR) ื”ืžืคื•ืจืกืžื•ืช ื‘ื™ืฉืจืืœ. +- **ืชื•ื›ื ื™ืช ื˜ื™ืกื”** โ€” ื˜ื‘ืœื” ืขื ื›ื™ื•ื•ืŸ, ืžืจื—ืง, ืžื”ื™ืจื•ืช, ื’ื•ื‘ื” ื•ื–ืžืŸ ืœื›ืœ ืงื˜ืข. +- **ื”ื“ืคืกื” / ื™ื™ืฆื•ื PNG** โ€” ืฉืžื™ืจืช ื”ืžืคื” ื•ื”ืžืกืœื•ืœ ื›ืชืžื•ื ื” ื‘ืจื–ื•ืœื•ืฆื™ื” ื’ื‘ื•ื”ื” (A3 / A4). + +ื”ืžืกืœื•ืœ ื•ื”ืชืฆื•ื’ื” ื ืฉืžืจื™ื ื‘ื“ืคื“ืคืŸ โ€” ืจืขื ื•ืŸ ื”ื“ืฃ ืžืฉื—ื–ืจ ืืช ื”ืขื‘ื•ื“ื” ื”ืื—ืจื•ื ื”. -Charts ยฉ flight-maps.com / CAAI; imagery ยฉ Esri; map data ยฉ OpenStreetMap -contributors; VFR reporting points ยฉ ICAO / CAAI. +ืžืคื•ืช ืชืขื•ืคื”: ยฉ flight-maps.com / ืจืช"ื โ€” ืœืฉื™ืžื•ืฉ ืื™ืฉื™. -NavAid is a planning aid only and not certified for primary navigation. +
From 936df9a7649e41a6fa89a7bc31d771655bf0457b Mon Sep 17 00:00:00 2001 From: Marco Supino Date: Wed, 27 May 2026 09:47:01 +0300 Subject: [PATCH 5/8] Fix test BASE_URL resolution for deployed previews Change page.goto('/?lang=en') to page.goto('?lang=en') in all test files so they resolve relative to the baseURL path instead of the domain root. Fixes e2e-deployed tests hitting the production site instead of the PR preview. --- SECURITY.md | 21 +++++++++++++++++++++ tests/bugfix-173-halo.spec.js | 2 +- tests/bugfix-174-tohms.spec.js | 2 +- tests/bugfix-175-drag-r5.spec.js | 2 +- tests/bugfix-176-escape-leak.spec.js | 4 ++-- tests/bugfix-177-octx.spec.js | 4 ++-- tests/bugfix-178-180-sw.spec.js | 6 +++--- tests/bugfix-212-214-215.spec.js | 2 +- tests/bugfix-issues-coverage.spec.js | 2 +- tests/dev-306-regression.spec.js | 4 ++-- tests/export-png-options.spec.js | 2 +- tests/flight-plan.spec.js | 2 +- tests/fuel-flight-plan.spec.js | 2 +- tests/ga-blocked.spec.js | 6 +++--- tests/google-earth.spec.js | 2 +- tests/import.spec.js | 2 +- tests/multi-waypoint-search.spec.js | 2 +- tests/og-preview.spec.js | 2 +- tests/orient-pageexport.spec.js | 2 +- tests/pwa.spec.js | 10 +++++----- tests/routes.spec.js | 4 ++-- tests/runway-directions.spec.js | 2 +- tests/search-overlay.spec.js | 2 +- tests/section-accordion.spec.js | 2 +- tests/share-route.spec.js | 12 ++++++------ tests/smoke.spec.js | 10 +++++----- tests/ui-deep-coverage.spec.js | 2 +- tests/ui-toolbar-controls.spec.js | 2 +- 28 files changed, 69 insertions(+), 48 deletions(-) create mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..cd5089f6 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,21 @@ +# Security Policy + +## Supported Versions + +Only the latest production deployment (on `main`) receives security +updates. Staging (`dev`) and PR previews are ephemeral. + +## Reporting a Vulnerability + +If you find a security issue, please open a +[GitHub issue](https://github.com/msupino/NavigationApp/issues/new/choose) +rather than a public discussion. + +NavAid is a client-side planning aid with no backend, no user +accounts, and no data collection. Reports typically involve +malicious route files (XSS via imported JSON/KML) or CDN +supply-chain risks. + +Do **not** file a public issue if the vulnerability could impact +users of the live site โ€” use + instead. diff --git a/tests/bugfix-173-halo.spec.js b/tests/bugfix-173-halo.spec.js index aceff1d0..2c19a2f0 100644 --- a/tests/bugfix-173-halo.spec.js +++ b/tests/bugfix-173-halo.spec.js @@ -5,7 +5,7 @@ const { test, expect } = require('./_setup'); test.describe('#173 needsHalo outboundSpeed', () => { test.beforeEach(async ({ page }) => { - await page.goto('/?lang=en'); + await page.goto('?lang=en'); await page.waitForFunction(() => typeof state !== 'undefined' && typeof needsHalo === 'function'); await page.evaluate(() => { window.highlightDiff = true; // needsHalo bails when off diff --git a/tests/bugfix-174-tohms.spec.js b/tests/bugfix-174-tohms.spec.js index f42d752e..880daeff 100644 --- a/tests/bugfix-174-tohms.spec.js +++ b/tests/bugfix-174-tohms.spec.js @@ -5,7 +5,7 @@ const { test, expect } = require('./_setup'); test.describe('#174 toHMS extracts hours', () => { test.beforeEach(async ({ page }) => { - await page.goto('/?lang=en'); + await page.goto('?lang=en'); await page.waitForFunction(() => typeof toHMS === 'function'); }); diff --git a/tests/bugfix-175-drag-r5.spec.js b/tests/bugfix-175-drag-r5.spec.js index e08c56fe..40d05434 100644 --- a/tests/bugfix-175-drag-r5.spec.js +++ b/tests/bugfix-175-drag-r5.spec.js @@ -5,7 +5,7 @@ const { test, expect } = require('./_setup'); async function boot(page) { - await page.goto('/?lang=en'); + await page.goto('?lang=en'); await page.waitForFunction(() => typeof state !== 'undefined' && typeof r5 === 'function'); } diff --git a/tests/bugfix-176-escape-leak.spec.js b/tests/bugfix-176-escape-leak.spec.js index 80506e0a..bbbaa769 100644 --- a/tests/bugfix-176-escape-leak.spec.js +++ b/tests/bugfix-176-escape-leak.spec.js @@ -26,7 +26,7 @@ async function installListenerCounter(page) { test.describe('#176 Escape listener leak in modals', () => { test('charts modal: open / โœ• / open / โœ• โ€” keydown adds match removes', async ({ page }) => { await installListenerCounter(page); - await page.goto('/?lang=en'); + await page.goto('?lang=en'); await page.waitForFunction(() => typeof showChartsModal === 'function'); await page.evaluate(() => { window.airfields = window.airfields || []; }); @@ -48,7 +48,7 @@ test.describe('#176 Escape listener leak in modals', () => { test('plate viewer: โœ• button removes the Escape listener', async ({ page }) => { await installListenerCounter(page); - await page.goto('/?lang=en'); + await page.goto('?lang=en'); await page.waitForFunction(() => typeof showPlateViewer === 'function'); // Block the network fetch so the viewer doesn't try to load a real PDF. diff --git a/tests/bugfix-177-octx.spec.js b/tests/bugfix-177-octx.spec.js index 2b4967c3..3489bc65 100644 --- a/tests/bugfix-177-octx.spec.js +++ b/tests/bugfix-177-octx.spec.js @@ -6,7 +6,7 @@ const { test, expect } = require('./_setup'); test.describe('#177 exportPNG restores octx on throw', () => { test('octx restored after a draw function throws (try/finally)', async ({ page }) => { - await page.goto('/?lang=en'); + await page.goto('?lang=en'); await page.waitForFunction(() => typeof octx !== 'undefined'); const result = await page.evaluate(() => { @@ -36,7 +36,7 @@ test.describe('#177 exportPNG restores octx on throw', () => { }); test('multiple consecutive exports do not poison the screen ctx', async ({ page }) => { - await page.goto('/?lang=en'); + await page.goto('?lang=en'); await page.waitForFunction(() => typeof octx !== 'undefined'); const okAfter = await page.evaluate(() => { diff --git a/tests/bugfix-178-180-sw.spec.js b/tests/bugfix-178-180-sw.spec.js index 079642f4..855b2da9 100644 --- a/tests/bugfix-178-180-sw.spec.js +++ b/tests/bugfix-178-180-sw.spec.js @@ -15,7 +15,7 @@ async function waitForSW(page) { test.describe('#178 cache-first awaits cache.put', () => { test('After fetch resolves the asset is in the cache (synchronous from caller view)', async ({ page }) => { - await page.goto('/?lang=en'); + await page.goto('?lang=en'); await waitForSW(page); // First reload makes the SW intercept; second reload confirms cached asset. await page.reload(); @@ -39,7 +39,7 @@ test.describe('#178 cache-first awaits cache.put', () => { test.describe('#179 ?v= pruning awaits cache.delete', () => { test('After fetching a new ?v= the previous version is evicted', async ({ page }) => { - await page.goto('/?lang=en'); + await page.goto('?lang=en'); await waitForSW(page); await page.reload(); await waitForSW(page); @@ -68,7 +68,7 @@ test.describe('#179 ?v= pruning awaits cache.delete', () => { test.describe('#180 navigation fallback to cached /', () => { test('Offline navigation to uncached URL serves the cached / HTML', async ({ page, context }) => { - await page.goto('/?lang=en'); + await page.goto('?lang=en'); await waitForSW(page); await page.reload(); await waitForSW(page); diff --git a/tests/bugfix-212-214-215.spec.js b/tests/bugfix-212-214-215.spec.js index fbe1e91d..2157b9f1 100644 --- a/tests/bugfix-212-214-215.spec.js +++ b/tests/bugfix-212-214-215.spec.js @@ -31,7 +31,7 @@ async function boot(page) { } } catch (e) {} }); - await page.goto('/?lang=en'); + await page.goto('?lang=en'); await page.waitForFunction(() => typeof state !== 'undefined' && typeof syncLegs === 'function'); } diff --git a/tests/bugfix-issues-coverage.spec.js b/tests/bugfix-issues-coverage.spec.js index 2e8cf2c7..37dc5991 100644 --- a/tests/bugfix-issues-coverage.spec.js +++ b/tests/bugfix-issues-coverage.spec.js @@ -18,7 +18,7 @@ async function boot(page) { } } catch (e) {} }); - await page.goto('/?lang=en'); + await page.goto('?lang=en'); await page.waitForFunction(() => typeof state !== 'undefined' && typeof fitView === 'function'); } diff --git a/tests/dev-306-regression.spec.js b/tests/dev-306-regression.spec.js index 6bb6b0e9..d9602182 100644 --- a/tests/dev-306-regression.spec.js +++ b/tests/dev-306-regression.spec.js @@ -16,7 +16,7 @@ async function boot(page) { } } catch (e) {} }); - await page.goto('/?lang=en'); + await page.goto('?lang=en'); await page.waitForFunction(() => typeof state !== 'undefined'); } @@ -62,7 +62,7 @@ test.describe('#306 โ€” Magnetic Variation removed from toolbar', () => { await page.addInitScript(() => { try { localStorage.setItem('navaid.magVar', '-12'); } catch (e) {} }); - await page.goto('/?lang=en'); + await page.goto('?lang=en'); await page.waitForFunction(() => typeof magVar !== 'undefined'); expect(await page.evaluate(() => window.magVar)).toBe(-5); }); diff --git a/tests/export-png-options.spec.js b/tests/export-png-options.spec.js index 0d8af20a..316b419c 100644 --- a/tests/export-png-options.spec.js +++ b/tests/export-png-options.spec.js @@ -15,7 +15,7 @@ async function boot(page) { } } catch (e) {} }); - await page.goto('/?lang=en'); + await page.goto('?lang=en'); await page.waitForFunction(() => typeof state !== 'undefined' && typeof exportPNG === 'function'); } diff --git a/tests/flight-plan.spec.js b/tests/flight-plan.spec.js index 0bc56445..89bcf151 100644 --- a/tests/flight-plan.spec.js +++ b/tests/flight-plan.spec.js @@ -41,7 +41,7 @@ test.describe('Flight plan', () => { } } catch (e) {} }); - await page.goto('/?lang=en'); + await page.goto('?lang=en'); await page.waitForFunction(() => typeof state !== 'undefined' && typeof showFlightPlan !== 'undefined'); await page.evaluate(route => { state.waypoints = route.waypoints.map(w => ({ lat: w.lat, lng: w.lng, name: w.name })); diff --git a/tests/fuel-flight-plan.spec.js b/tests/fuel-flight-plan.spec.js index fa738c3c..3c3be960 100644 --- a/tests/fuel-flight-plan.spec.js +++ b/tests/fuel-flight-plan.spec.js @@ -21,7 +21,7 @@ async function boot(page) { } } catch (e) {} }); - await page.goto('/?lang=en'); + await page.goto('?lang=en'); await page.waitForFunction(() => typeof state !== 'undefined' && typeof syncLegs === 'function'); await page.evaluate(wps => { state.waypoints = wps.map(w => ({ lat: w.lat, lng: w.lng, name: w.name })); diff --git a/tests/ga-blocked.spec.js b/tests/ga-blocked.spec.js index 2f450e76..5e449dfd 100644 --- a/tests/ga-blocked.spec.js +++ b/tests/ga-blocked.spec.js @@ -12,7 +12,7 @@ test.describe('Google Analytics blocking', () => { page.on('requestfinished', req => { if (GA_RE.test(req.url())) completed.push(req.url()); }); - await page.goto('/?lang=en'); + await page.goto('?lang=en'); await page.waitForFunction(() => typeof state !== 'undefined'); // Mimic the production tag's flow: route the call through the real gtag // (which the inline