Repository navigation
AI puts named places on the map; quieter chrome, audit fixes and a green e2e suite - #2
Merged
Merged
Conversation
… answered Models differ in how they answer: some geocode each place and submit ids, others list places in prose or write the submission as JSON text. The map depended on the first style, so most answers showed nothing. - mentionedPlaces: read places from a submitted list, a JSON block, GPS pairs or a list of names, and resolve each through resolve_location - submit_answer takes places; submit_plan takes stops by name/address/GPS; two places and a route question become a routed plan card - a prose answer is kept and its places geocoded instead of discarded - rejected mapData goes back to the model once; rows without placeId are geocoded by placeName - tool arguments in a Markdown fence or with a trailing comma are repaired - offline profile gets a gazetteer and straight-line router; /geocode no longer answers every query with Plzeň Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J3QFugXbaTxPuuP3q3rR16
- header is the thread name plus a menu instead of a native details popup - answers render paragraphs, lists and bold as React elements, never HTML - composer is one row on phones; follow-ups scroll sideways - an answer frames its route, stops and places once; the route preview no longer refits when a panel rebuilds the same route Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J3QFugXbaTxPuuP3q3rR16
Places come first as one line of name and one of hierarchy, with the kind shown by an icon. At most three layers follow. The 'Ask AI' row is gone: anything that is not a suggestion goes to the assistant on Enter, which a footer hint says once, next to the geocoder attribution. Arrow keys move through places. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J3QFugXbaTxPuuP3q3rR16
Layers and basemaps: every section has an icon and reads as a hairline list instead of a stack of boxes. The 'N on · M selected' row that appeared above the groups on the first toggle is gone (the Active tab already counts them), so the panel no longer shifts under the pointer. Basemap groups get English names. Place detail: sections have an icon and chevron instead of the browser's triangle; field labels are bilingual and cover district/updatedAt/ dataScope/verifiedAt; dates read as dates; statistic tabs no longer break words and the grid no longer overflows. Pin preview strings use i18n. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J3QFugXbaTxPuuP3q3rR16
- the fallback style crashed on map.getStyle().name when the first style failed, leaving a blank map; it now applies - one failed tile no longer swaps the whole basemap (8 errors / 15 s) - route segments use promoteId, so selecting one finally highlights it - raster and fallback styles carry a glyph endpoint, so cluster counts and stop numbers render instead of being rejected - the selected pin survives a basemap switch; its pulse stops after three rings instead of repainting every frame - the location watch starts only once permission is granted or the user asks, not as a prompt on first load Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J3QFugXbaTxPuuP3q3rR16
- fields are at least 16px on touch devices so iOS stops zooming in - no tap flash, rubber-band scroll or long-press callout on chrome - PNG apple-touch-icon and manifest icons (iOS ignores SVG), generated by scripts/render-app-icons.mjs; web app title and theme colour - map controls stay below the top rail on phones; +/- hidden on touch - preset delete is visible on touch instead of hover-only Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J3QFugXbaTxPuuP3q3rR16
Golemio v2 addresses are objects, which the text-only normaliser dropped; districts are slugs and opening hours arrays. All three now read as text. The in-memory server answers stateless live layers (Golemio, BTC Map, Europeana, SpaceAPI, GBFS) outside the offline profile instead of 404. GOLEMIO_API_KEY and EUROPEANA_API_KEY are documented. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J3QFugXbaTxPuuP3q3rR16
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J3QFugXbaTxPuuP3q3rR16
- Game: a style switch replaces the world handle, and the old handle's late detach removed the new scene (shared layer id); a handle detached inside a style.load dispatch also re-attached itself, because MapLibre calls the listeners it copied before off(). Each handle now attaches as soon as a style exists, re-attaches after style loads, removes only its own layer and refuses to attach once detached. Opening ?mode=game starts the scene. - A shared plan link (/s?mode=planning#plan=…) lost its mode on the first camera rewrite, so a reload or a copied address opened Discover without the plan. - A preset with events fell back to Custom immediately: the timeline's from/to window counted as drift. The time window is no longer part of preset identity. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J3QFugXbaTxPuuP3q3rR16
…hosen area's name - The boundary overlay bailed whenever isStyleLoaded() was false, which is also true while any tile is loading, and nothing retried: outlines appeared only after an unrelated state change. It now syncs again on idle, and fetches the manifest as soon as a local world is shown (was up to a minute later). - DiscoverHereButton and its styles existed but the chip was never mounted. It floats above the sheet on a phone, hides while the sheet is half or full height (the panel already answers for the centre there) and while a place detail covers the panel. - The header names the selected area even when its guide cannot be fetched (was "Neidentifikovaná oblast"). - Search suggestions are left-aligned again (a row inherited the column's centring). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J3QFugXbaTxPuuP3q3rR16
…hooks - A failed layer's alert stays up while the layer retries (a pan re-queries it) instead of blinking out and back; it clears once the layer answers, leaves range or is turned off. - The empty planning panel opens half-height on a phone like the other panels. - Map panel tabs and layer views carry test ids; the basemaps tab keeps the tiles-sheet hook. - Route preview keeps its string segment ids: MapLibre 5 keys feature state by them, and the promoteId change broke segment selection. - Remove the orphaned GooglePlaceSearch component. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J3QFugXbaTxPuuP3q3rR16
- One way into the map panel (e2e/fixtures/mapPanel.ts: layers/basemaps tabs, catalogue search rows, settings, user layers) instead of the removed basemap-btn and overflow ids; shared fixtures for Discover boundary tiles and for creating and sharing a plan. - The URL keeps only the camera since v0.1: reload tests reopen the mode, and layer/mode/preset checks read application state. - Search: suggestions show name + hierarchy with one source credit, and Enter asks the assistant (the Ask-AI row is gone). - Discover: three tests exercised the legacy GeoJSON overlay that the default VITE_DISCOVER_BOUNDARIES flag hides; they now cover the same behaviour on the vector-tile overlay. The app starts without layers, so tests that need POIs switch them on through the URL. - The area-hover check measured how fast the runner's software GL paints (a plain repaint of the unchanged map takes ~80 ms there). It now requires the handler to fit one frame and the hover frame to cost no more than an ordinary repaint, keeping the absolute 50 ms where a machine paints that fast. - Security: the production bundle exposes no map handle, so the untrusted name is opened through Discover's accessible place list. - Map status, AI panel session menu, visual captures and offline smoke follow the new controls. No test was skipped or disabled. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J3QFugXbaTxPuuP3q3rR16
Adds the game-scene race, the unmounted Discover chip, boundary outlines waiting for an unrelated change, shared-plan links losing their mode, preset drift from the event window and the orphaned component to the fixed list, and replaces the stale CI row with the current e2e state. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J3QFugXbaTxPuuP3q3rR16
The 6- and 12-layer runs take 1.3 and 1.6 min alone, identical before and after this branch's changes, but exceeded the 180 s budget beside two other WebGL workers. The wall-clock budget goes to 360 s; the heap allowance and bounded counts it asserts are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J3QFugXbaTxPuuP3q3rR16
With the scene now started by ?mode=game, opening the game panel from the HUD left a spinner for tens of seconds on a slow machine (CI, 4x-throttled Chromium). The lazy panel resumed through a Suspense retry, which React never expires, and the board's redraws plus the HUD clock kept interrupting it. The panel now loads through ordinary state, commits synchronously once its module is ready, and starts loading when game mode begins; a failed load surfaces through the module boundary's retry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J3QFugXbaTxPuuP3q3rR16
performanceMap.spec.ts already had playwright.performance.config.ts (production build, one worker), but the default profile also ran it beside two other WebGL workers, where it timed out on main and on this branch alike. The default profile now ignores it like the other auxiliary specs, test:e2e:performance runs the profile in CI, and the profile points its base URL at its own preview port (it only worked with MAPOS_E2E_WEB_PORT set). The 360 s budget from the previous commit goes back to 180 s. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J3QFugXbaTxPuuP3q3rR16
On the CI runner a late camera move after the phone resize replaced the grid between the hover and the tap, so they showed 20 °C and 16 °C. The test now waits until grid requests stop and the map is idle before choosing the point. The audit records the pre-existing camera shift behind it: opening or closing the right drawer moves the view by a panel width and refetches the layers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J3QFugXbaTxPuuP3q3rR16
A Deploy VPS workflow runs scripts/deploy-vps.sh (staged build, verified backup and restore drill, atomic switch, automatic rollback) and the public smoke check from a GitHub runner. It runs when a same-repository pull request is labelled deploy:vps, deploying its head commit and removing the label afterwards, or manually once on the default branch. SSH access comes only from repository secrets: VPS_SSH_KEY, or VPS_PASSWORD through sshpass wrappers so the password never reaches a command line; VPS_KNOWN_HOSTS optionally pins the host key. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J3QFugXbaTxPuuP3q3rR16
Closing the layers drawer with Escape changes the map padding, so the view settles about half a second later and the grid is fetched once more for the final bbox. On the CI runner that request landed after the count was taken, so the pollutant change looked like a refetch. The test now waits until grid requests stop and the map is idle before counting; the pollutant change itself still must not fetch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J3QFugXbaTxPuuP3q3rR16
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Change
Follows the merged UI redesign (coinmandeer/map0S-codex#1): the AI puts every place it names on the map, the chrome is quieter, the renderer and iOS glitches found in the audit are fixed, and the e2e suite is back to describing the product that ships.
AI → map, whichever model answers
placesfield, a JSON block, GPS pairs, list items) are geocoded on the server and drawn automatically, with one camera fit for route + stops + places. No "Show on map" click (apps/api/src/services/ai/mentionedPlaces.ts,chatService.ts).submit_planaccepts stops by name/address; two or more places + a "route" question yields a computed route.mapDatagoes back to the model once instead of ending the turn; tool arguments wrapped in Markdown or with a trailing comma are repaired syntactically instead of silently discarded.AnswerText.tsx); compact header with the last conversation's title and one ⋯ menu.Search, layers, place detail
aria-activedescendant. The "Ask AI: …" row is replaced by a quiet "Enter asks the assistant" hint; anything not suggested goes to the AI. OrphanedGooglePlaceSearch.tsxremoved.Renderer and map
getStyle().namewhen the first style fails; one bad tile no longer swaps the whole basemap (8 errors / 15 s budget); glyphs for raster styles; the selected pin survives a basemap switch; the pulse stops after three cycles.detachof the replaced handle removed the new scene (shared layer id), and a handle detached inside astyle.loaddispatch re-attached itself. Fixed inworldLayer.ts;?mode=gamenow starts the scene. With the scene running, the game panel opened from the HUD could stay a spinner for tens of seconds on a slow machine (a lazy component's Suspense retry never expires, and the board's redraws kept interrupting it); it now loads through ordinary state, commits as soon as its module is ready and is preloaded when game mode starts.sync()bailed while any tile was loading — now retried onidle; the boundary manifest is fetched as soon as a local world is shown. The "What is here?" chip (component and styles existed) is mounted; on a phone it floats above the sheet and steps aside while the sheet is half or full height or a place detail covers the panel. The header shows the selected area's name even when the guide is unavailable.Plans and presets
/s?mode=planning#plan=…) kept losingmodeon the first camera rewrite, so a reload or a copied address opened Discover without the plan.from/towindow counted as drift; the time window is no longer part of preset identity.iOS (PWA now, Capacitor in the roadmap)
apple-touch-iconand manifest icons,apple-mobile-web-app-title; GPS is requested only after "My location" or when already granted; map controls no longer collide with the Layers/Settings column.Data
GOLEMIO_API_KEY/EUROPEANA_API_KEYdocumented — keys only via env, none in the repo.Audit
docs/audits/2026-09-25-mapos-audit.md: findings by severity (renderer/perf, UI/UX, mobile/iOS, AI, tests), answers on GBFS, SpaceAPI, Ookla, Eurostat, BTC Map, Europeana, and a phased roadmap (F1 performance, F2 native-feeling mobile, F3 "wow", F4 Capacitor, F5 data).E2E suite
e2e/fixtures/mapPanel.ts) instead of the removedbasemap-btn/overflow ids; URL assertions replaced by application state (the URL deliberately keeps only the camera since v0.1); boundary tiles have a shared fixture (e2e/fixtures/boundaryTiles.ts).VITE_DISCOVER_BOUNDARIESflag hides; they now test the same user-visible behaviour on the vector-tile overlay.performanceMap.spec.ts) runs in its existing production profile (playwright.performance.config.ts, one worker) as a new CI steptest:e2e:performanceinstead of beside the WebGL workers of the default profile, where it timed out onmaintoo; the profile now points its base URL at its own preview port.window.__maposMapsince it was written).Evidence
npm test(web 583 pass, API 848 pass, packages 111/84/4 pass; skips are the existing environment-gated ones: live model, PostgreSQL, rights inventories),test:contracts,test:privacy(218 pass),test:migrations(37),test:load,test:secrets,test:css-tokens. New unit tests: preset drift vs the event window, shared-plan link keeps its mode,?mode=gamestarts the scene.npm run typecheck,npm run lint,npm run format:check.MAPOS_LONG_PERFgate, unchanged; the 2 failures were that benchmark timing out beside other WebGL workers, 1.3/1.6 min alone with and without this branch's source changes). Profiles: offline 5/5, security 1/1, accessibility 8/8, visual 18/18, runtime-starter 2/2, performance 2/2 (production build, 1.1/1.3 min). After the first CI run: game panel under 4x CPU throttling opens in ~3–5 s (was never / >30 s), weather + game + Discover specs 23/23 on three loaded workers. Live check at 1440 px and iPhone 14 Pro size (Discover, layers, basemaps, "Brno" suggestions, a question sent with Enter, AI panel, place detail): no page errors.game.spec.ts:68fails only when several copies of itself run at once (--repeat-each), because they fight the same shared-world enemy.Layer or provider integration (if applicable)
resolve_locationregistry; no new data leaves the server.Release safety
VITE_DISCOVER_BOUNDARIESrollback path is unchanged.sharp, already installed with the API's glTF tooling). GPS permission is requested later than before.docs/data-sources.md,docs/environment.md,docs/plans/blockers.mdupdated.🤖 Generated with Claude Code
https://claude.ai/code/session_01J3QFugXbaTxPuuP3q3rR16