Skip to content

UI redesign v3 - #1

Merged
coinmandeer merged 16 commits into
mainfrom
ui-redesign-v3
Sep 25, 2026
Merged

coinmandeer merged 16 commits into
mainfrom
ui-redesign-v3

Conversation

@coinmandeer

Copy link
Copy Markdown
Owner

Change

Describe the user-visible outcome and the current-to-target delta.

Evidence

  • Relevant unit and contract tests pass.
  • Type, lint and formatting checks pass.
  • Integration/E2E, visual, accessibility or load evidence is attached when relevant.
  • No test was presented as run when it was skipped; skipped checks have a reason and gate.

Layer or provider integration (if applicable)

  • Versioned manifest, query/100-feature budget and cancellation.
  • Loading, empty, partial, error and provider-outage states.
  • Filters, legend, provider-neutral detail/actions and accessible non-map alternative.
  • Permissions, private-data boundary, AI exposure and entitlement policy.
  • Fixture/contract tests, health/SLO owner and disable/rollback path.
  • No provider secret or untrusted extension JavaScript reaches the client.

Release safety

  • Migration is additive/idempotent or has an approved expand/migrate/contract plan.
  • Feature flag, owner, removal date and rollback are documented.
  • Security, privacy, dependency and bundle impact were reviewed.
  • Traceability and release/upgrade documentation were updated.

If a box does not apply, say why instead of checking it silently.

promptstudio3000 and others added 16 commits September 2, 2026 16:18
The screenshots the brief was written from lived in /tmp and would have been
lost; they are the only record of the before state, so they move into
docs/shots/audit-2026-09-02 re-encoded to stay under the 300 kB per-file rule.
Adds the progress ledger and blocker tracker §33 expects, and anchors the
gitignore progress.md pattern so the ledger is not swept up with the operator's
personal deployment log.

Co-authored-by: Cursor <cursoragent@cursor.com>
Adds the layer everything else in the redesign sits on: Base UI as the
headless primitive source, Inter and a subsetted Material Symbols Rounded
as the two fonts, Material 3-derived tokens, an explicit `ui/kit`, and a
Czech string table so panels stop hardcoding copy.

The icon font is subsetted from a closed vocabulary (`ui/kit/icons.ts`)
because the full variable font is 5 MB — too much for the mobile-data
budget the map has to fit in.

`?kit=1` renders a gallery of every primitive in both themes, which is how
the token work gets reviewed without booting the whole shell.

Co-authored-by: Cursor <cursoragent@cursor.com>
The chrome was a stack of separate bars: a mode bar the sidebar had to dodge,
a strip of single-letter source badges, and an expandable task centre. This
replaces it with the composition from §3 of the redesign plan — a full-height
sidebar from the top edge, one floating pill centred over whatever map area is
left, the Podklady/Vrstvy rail, and a single activity pill for everything that
is loading.

Because the pill's space is the strip between the panel and the rail rather
than the window, narrow desktops now stand the rail up as a column and, when
even that is not enough, hand the settings button over to it, instead of
sliding the pill underneath the Vrstvy button.

The mobile sheet gets the peek/half/full snaps from §21.2, the map's camera
padding follows the chrome so a selected pin is never behind the sheet, and at
the full snap the strip above the sheet is a hit area that snaps back to half
rather than a scrim that dims the map it is meant to leave readable.

Also adds `test:css-tokens`, which fails on a `var(--x)` nothing declares —
the class of bug that rendered twenty corners square via an undefined
`--radius-md`.

Co-authored-by: Cursor <cursoragent@cursor.com>
…nd the pill

A stop, a route profile and a share action each carried their own explanatory
card, and the AI offer sat behind a two-step preview. Departure context, the
detour finder and the three footer overlays now report where the user already
looks — at the segments, under the itinerary, and in one tabbed dialog — while
provider names, request parameters and per-message disclaimers move into
InfoTips, popovers and `?debug=1`.

The activity pill keeps cancel and retry now that the task centre is gone, and
the accent colour in the legacy panel stylesheet reads from the token.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Phase 4 of the redesign. The place detail stops being a modal over the map:
it docks in the left context with a photo hero (or none at all rather than an
empty gradient), the five Google-Maps-style actions, and a summary that loads
itself instead of hiding behind a tab. The selected pin keeps a pulsing ring
and the map only pans when the panel would cover it.

The footer surfaces follow §4.11: legends collapse to one compact row per
layer with the rest behind a dialog, and the timeline is a single header with
a context chip, a live toggle and playback — visible only while something on
the map actually has a time.

A question typed into search can now continue as a conversation in the new
Asistent panel, which answers with place cards that fly the map and open the
detail. Toasts moved to the bottom above the footer with an optional undo.

Co-authored-by: Cursor <cursoragent@cursor.com>
The gateway could only answer "nearest bar" and ten of the eleven catalogued
tools threw "not composed", so every other question the client offered came
back 422. AI-1 turns that into one conversation: a tool-calling adapter with
tool-as-schema output, fast/strong model slots resolved once in modelRuntime,
and POST /v2/ai/chat streaming the steps it takes as it takes them.

Tools now answer from the same sources the map draws from — place search is
the fused POI source find_nearest_poi already used, so search and the
assistant cannot disagree. Without a model the deterministic path answers the
same question from the same tool rather than apologising.

In the client the panel reads the stream, names the running tool instead of
spinning, and renders places as cards with one action; nothing moves the map
until that action is taken. briefService also asks with verifiedPublic, which
is why place summaries were never generated.

Co-authored-by: Cursor <cursoragent@cursor.com>
…t layers and plans

AI-2 gathers a region's guide from parallel collectors behind per-source timeouts, so the
panel always has something to show instead of an empty state when one source is slow. AI-3
lets the model hand back work the user can act on: an inline layer that can be saved into
Moje vrstvy, and plan edits that arrive as a reviewable diff rather than a silent mutation.

Playwright now waits on /health instead of a bound port. A listening socket only proved the
API had reached listen(), and the first specs of a run were reaching Vite's proxy while
routes were still registering, which failed eight tests a run for no reason of their own.

Co-authored-by: Cursor <cursoragent@cursor.com>
… control overflowing its row

The route-preference control had a fixed height while the planner lets its labels wrap, so
four profiles in a 380 px panel grew the segments out of the track and over the row beneath —
far enough to swallow clicks meant for the departure-context InfoTip. `min-height` lets the
control be as tall as its own labels.

Segment variants move onto the segment line as chips (§16.2). They had been a "Varianty (2)"
toggle over a grid of cards, which charged two clicks and a block of vertical space at exactly
the point where the user has already decided they want the other route.

`set_layer_selection_draft` was reaching the browser and being dropped: the card type existed
in the panel's union with no branch to render it, so "zapni mi vrstvy pro…" answered with
nothing. It now lists the proposed layers by catalogue name behind one "Zapnout v mapě".

Three specs were measuring the wrong thing rather than finding bugs: the alternatives test read
the first LineString, which is a dimmed variant drawn under the route; the bike test read a
storage key from before the overlay decision; and `openPanel` returned before the carried-over
question was answered, so a later stream could be served to that first turn too.

Co-authored-by: Cursor <cursoragent@cursor.com>
…at shout

The §31.3 audit round over 40 captures (10 shell states, both viewports, both
themes) started at 376 findings and ends at 0.

Most of it was one mistake repeated: a small control is a comfortable mouse
target at 32 px and an uncomfortable thumb one. `--control-h-sm` now reaches
the tap target on a phone, while `--control-visual-sm` keeps the small icon
button's state layer at 32 px — the box and the drawn circle stop being the
same measurement, so a row of icons is still compact without being fiddly.
The same gap turned up hardcoded in three more places: segmented buttons gave
the indicator's 3 px inset away from the segment that gets tapped, the stop
field was 40 px with a real border, and the mobile top bar's 44 px included
the pill's border and left the search field at 40.

Following §21.1 to the letter had also gone wrong: explanations moved behind
an info icon, and then eight info icons stacked down the Layers drawer were
the same wall of ink drawn a different way. In rows they now wait for hover or
focus; touch keeps them, having no hover to reveal them with.

And a flake that predates this branch, 1 in 4 on a clean tree: the activity
pill unmounts the moment its row list empties, but a layer refresh retires its
task before starting the next one — so the pill and any popover open on it
vanished mid-refresh, out from under whoever was reaching for Zkusit znovu.
It lingers 400 ms now, and a row is keyed by layer rather than by attempt.

Six of the audit's own checks were measuring the wrong thing and account for
336 of those 376 findings — holding icons to text contrast, comparing rects
across a scrollport, a 4 px grid where the tokens step in 2, separator rules
counted as enclosures, a fold a screen below the viewport, and widgets counted
where the guidance means things. Each correction states the standard it now
holds to, in visual-audit.mjs.

Co-authored-by: Cursor <cursoragent@cursor.com>
Wave A of §6.6, built global-first: the items that serve every user before
the country-specific ones.

Legends were 0 of 8 despite Phase 2 claiming otherwise — they reach the
footer through the v2 manifest, and the six structural overlays register
the v1 way, which had no way to carry one. The v1 adapter now passes a
legend through.

Terrain comes from its own global DEM rather than the active background,
so it works over imagery too, unlike the extruded buildings it sits beside.

OpenInfraMap ships no style, so the networks are drawn with ours — which is
why its legend can state the voltage scale exactly. It needed a vector
overlay helper that supports several drawing rules over one source; the
single-fill helper is now expressed through it.

Also fills the offline fixture gaps this exposed: four overlay tile hosts
and the EOX imagery background had no fixture, which only showed up once a
test switched them on.

Co-authored-by: Cursor <cursoragent@cursor.com>
The picker already preferred a screenshot over its schematic fallback; the
script that makes them and the assets themselves were missing, so every
card fell back.

All eight are shot over one shared viewport, because the question a picker
answers is "which of these do I want" and that comparison is impossible if
each card shows a different city.

Only backgrounds whose licence permits redistribution are rendered — a
thumbnail is a copy of the provider's cartography — and a render that
carries no information is dropped rather than committed: NASA's daily
mosaic stops at z8 and paints black at this zoom, and a black card reads as
a bug where a schematic reads as a style.

Co-authored-by: Cursor <cursoragent@cursor.com>
…-quests layer

GPX joins the layer import. A track is the one shape the parser could not hold:
candidates carried a single point and non-Point geometry was rejected outright,
so a ride would have imported as one dot. GeoFeature now admits a LineString,
a route pin keeps its line in user_pins.path (migration 0011), and lng/lat stay
as the anchor so a track is still one findable place in every list. GPX is read
without an XML parser, which also removes XXE and entity expansion as concerns.
Dense tracks are simplified with Ramer-Douglas-Peucker, which keeps the corners
that uniform sampling would round off, and the user is told when it happened.
The clustered pin source cannot draw lines at all, so routes get their own.

Quest anchors move behind a cache. All four adapters were called live on every
viewport, making a map pan a burst of requests to volunteer-run services for
data that does not move. Migration 0012 adds quest_anchors with a GiST index
and a sweep log, because 'nothing here' is an answer worth remembering. Each
source states its own refresh window, a regional viewport never sweeps, and a
failed sweep is not recorded so an outage is not cached as emptiness. Quests
are also a plain pins layer now, available outside the 3D game mode.

Co-authored-by: Cursor <cursoragent@cursor.com>
test
Remove a duplicated blank line that failed format:check in CI.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J3QFugXbaTxPuuP3q3rR16
@coinmandeer
coinmandeer merged commit 4625360 into main Sep 25, 2026
1 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants