Commit Graph
14 Commits
Author SHA1 Message Date
Said TurkseverandClaude Opus 5.5 fb024d582b test(street-level): run the photo flow in CI against a production build
- The CI gate builds the app and serves it with `vite preview`, with a
  dummy non-secret client token (a fork PR never gets secrets), and
  answers the Graph API, image tiles and thumbnails from fixtures: 41
  photos on a line under the parked camera and one generated 640x320 JPEG
  (scripts/fixtures/street-level). All 7 photo steps now run in CI, plus a
  real click on a coverage line and on a cone, the framing flight stopping
  on close, the inert modal, the viewer returning into the panel, and a
  reload keeping a collapsed panel collapsed (share ui token t).
- Fixture interception is limited to Mapillary hosts via CDP
  Fetch.enable: page-wide request interception stalled Cesium's workers,
  so the old fixture mode never drew anything on the globe.
- Failure evidence (screenshots, UI state, console, failed requests,
  render errors) is written to qa-artifacts/ with keys and tokens redacted
  and uploaded on failure; qa-artifacts/ is gitignored.
- Cesium render-loop errors fail the gate; Google 3D branches that cannot
  run print as skips, and --strict rejects any other skip.
- Panel presses log their diagnostics, warn on a retry, and CI now fails
  on any retry (QA_FAIL_ON_RETRY=1) since the rail settles; a press with
  no pointerdown is a failure, not a retry.
- Assertion-gating sleeps 27 -> 1; the rejected-key "no requests" check
  has a positive control.

Verified locally: strict fixture gate on the production build 28 ok + 2
"no Google 3D" skips (twice, fail-on-retry on); panel gate 5/5 plus no
render errors; live gate with a real token 25 ok + 3 fixture-only skips.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-06 16:54:06 +01:00
Said TurkseverandClaude Opus 5.5 038d8facc2 fix(street-level): modal viewer, pinned redirects, real-route CI checks
From the IC8 review of #768, each with tests that fail before:

- P2: the expanded viewer is a real modal: while it is on screen every
  other <body> child is inert (only the ones it set are restored, on
  shrink, close and destroy, and when Clean View/recording/cockpit hide
  it). The voice pill and toasts are not clickable while it is expanded.
- P2: the tile proxy follows redirects by hand, at most 3 hops, each one
  HTTPS on exactly https://tiles.mapillary.com without credentials;
  anything else is a 502 and the token never leaves that origin.
- P2: the real dev and preview server smoke tests request
  /api/mapillary/status and a tile, and the browser gate first checks the
  server's real status route before any interception, so CI fails if the
  Mapillary plugin is not registered.
- P2: both gates press panel headers through one shared helper
  (scripts/qa-panelDrag.mjs): re-resolve the header until it is under the
  pointer, record what the press hit, wait on the real floating/docked
  state, and retry only a press that provably missed the header.

Verified: unit 5910 pass; fixture gate 17/17; panel gate 5/5; live gate
with a real Mapillary token 22/22.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-06 15:16:57 +01:00
Said TurkseverandClaude Opus 5.5 c2f1b54d00 refactor(street-level): remove the remaining duplicated code and fakes
A duplicate scan of every module the PR adds (repeated 4-line blocks,
normalised) after gekh's dedup round left these:

- glyphs.js: the cone, ring and position glyphs share cachedGlyph(), wedge(),
  ring() and dot(). Checked with a recording canvas: all 20 draw calls keep
  the same path and fill/stroke state, so the glyphs are pixel-identical.
- panelPositionControls.js: one placeAt() for the three places that pin a
  panel's top-left corner as a fixed window.
- tiles.js: one warn() for the three background-failure logs.
- Tests: src/testSupport/streetLevelFakes.mjs holds the provider snapshot
  (uiState + presentation tests), the stand-in provider (core + controls
  tests) and one real-ray pinhole camera (view + coverage tests, which had
  each written their own). The coverage line-of-sight test still fails when
  refresh() stops passing its focus.
- qa-street-level.mjs: one waitForCoverage() for the five copies of the
  "on, drawn, settled, not key-gated" wait.

No behaviour change: unit 5862 pass, fixture gate 16/16, panel gate 5/5.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-06 14:27:58 +01:00
Said TurkseverandClaude Opus 5.5 69beda99fa test(street-level): the CI gate covers the keyless, rejected-key and viewer paths
The fixture gate always reported a configured key, so the KEY REQUIRED path
most installs see never ran in CI, nothing checked that the lazily imported
MapillaryJS viewer loads, and the FOLLOW step (which needs an open photo)
failed for anyone with a Google key. Fixture runs now:

- check the viewer is stood up in the panel ahead of the first photo;
- serve a rejected key and assert KEY REJECTED, no further tile requests on
  a pan, and no stale message once the layer is off;
- open a second page whose server has no key and assert KEY REQUIRED;
- treat FOLLOW as a photo step.

Passes 16/16 locally against a dev server with Google 3D available.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 15:12:03 +01:00
Said TurkseverandClaude Opus 5.5 284cd0a93d ci(street-level): run the browser gate hermetically with Mapillary fixtures
The Street Level and panel gates never ran in CI, and the live gate needs a
Mapillary token, the Mapillary network and real coverage at its test site,
so everything users see was guarded only by manual runs (which a bad token
and expired tile caches blocked twice this week).

`qa-street-level.mjs --fixtures` (npm run qa:street-level:fixtures) answers
the page's /api/mapillary/status and coverage-tile requests from generated
tiles (a street grid with flat and 360 deg sequences, half captured three
years ago; overview points from orbit) and blocks mapillary.com. Only the
six steps that open a photo are skipped. Because the data is known, the 360
deg and SINCE steps also assert that the filters remove sequences, not just
that counts do not grow. A unit test pins the fixture contents.

New CI job "Street Level browser gate": install with headless Chrome, start
a key-less dev server, run the fixture gate and qa:panel-resize. Locally,
against a dev server with every key blanked: 14/14 on three consecutive
runs, panel gate all pass.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-04 23:50:21 +01:00
Said TurkseverandClaude Opus 5.5 abbf69a512 fix(street-level): camera height over Google 3D, cast only when it helps, gate token
From the code review of #768:

- view.js measured the camera's height above ground with globe.getHeight,
  but Google 3D hides the globe, so coverage picked its zoom from the
  ellipsoidal height. Over a high city (Denver, ~1,600 m) a camera 500 m up
  read 2,100 m: coverage stayed at z12/z13, whose tiles are too big to cast,
  and lines stayed draped on the roofs. With the globe hidden, the bare-earth
  height from the ground caster now answers; coverage re-picks its zoom when
  the surface mode changes. Verified in Denver: terrain mode, z14, cast lines.
  The core's surface check uses the same helper (and no longer subtracts the
  2 m lift from the camera height).
- castTile rebuilt a tile even when no line could be cast (terrain proxy
  down, tile too big); it now leaves such tiles as they are.
- The feature gate read Street Level's share token as a literal `0`; it now
  takes it from LAYER_STATE_REGISTRY, since a rebase can reassign it.

New view.test.mjs covers the shown-globe, hidden-globe and no-sample cases.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-02 01:01:40 +01:00
Said TurkseverandClaude Opus 5.5 0f2cac2ca2 feat(street-level): one colour per imagery source
As more sources arrive, each one reads the same everywhere it appears: its
PROVIDERS chip, coverage lines, overview points, image cones and a single
legend swatch. The palette lives in one place, PROVIDER_COLORS in
src/layers/streetLevel/policy.js: Mapillary green (#05cb63), Panoramax
purple, Google Street View blue.

Mapillary drops its age dimming and magenta panoramas: lines and overview
points are Mapillary green, cones are green too and 360° images keep their
ring shape; the 360°/FLAT filter still separates them, and the selected
sequence stays GEV cyan.

The provider contract loses `legend`: `colors.coverage` is the source's one
colour, and the core composes the legend as one swatch per active source
(its name, its colour) followed by Selected. The chip group takes an
optional per-chip colour as a `--chip-color` CSS variable, written only
when it changes; Street Level chips show it as a swatch dot and, while on,
as the chip's border, text and a faint fill.

The feature gate checks the legend has one swatch per source plus Selected
and that the Mapillary chip carries #05cb63.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-29 00:03:00 +01:00
Said TurkseverandClaude Opus 5.5 d5da45eacb fix(street-level): record filter and provider changes in share links
The panel changed the imagery filter and provider switches by calling the
layer directly, which bypasses the data manager's params request, the only
path saved state and share links record. A link copied after picking 360°
or a SINCE window came back with the defaults.

The controls now send those changes through
dataManager.setLayerParams('street-level', ..., { origin: 'user' }), as
the CCTV panel does; the layer applies them through its own setParams.

The feature gate's 360° step now asserts the share hash carries `0.p.p`;
it times out on the previous controls.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-28 22:12:46 +01:00
Said TurkseverandClaude Opus 5.5 d3d3898dbb feat(street-level): place coverage on the bare earth on Google 3D
Draped ground polylines and clamped billboards land on the top of the
Google Photorealistic 3D mesh, so on a street under trees or an overpass
the coverage seemed to float at roof height.

At street zoom on Google 3D (camera below 1,400 m, back above 1,800 m)
the core switches to a `terrain` surface mode. groundCast.js samples the
existing terrain service on the ground-floor grid (0.001 deg), interpolates
bilinearly and adds 2 m; Mapillary lines become plain polylines at those
heights, and cones and the position marker stand on the street, so the
mesh hides what is behind buildings and trees. A tile draws draped first
and swaps to its cast lines once the heights arrive, keeping the old lines
until the new primitives are ready. Lookups are once per tile and aborted
when the tile is dropped; a geoid fallback counts as unknown and leaves
lines draped. Other map stacks and higher views are unchanged.

Providers get getSurface() and groundCaster in their context and an
optional setSurface(mode); getUIState() exposes `surface`. The QA gate
checks draped on Esri and terrain on Google 3D.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-28 21:16:54 +01:00
Said TurkseverandClaude Opus 5.5 c7dc3146f2 feat(street-level): camera follow only on the Google 3D map
FOLLOW puts the globe camera at eye height inside the street-level view; on
flat imagery or terrain there is nothing to stand among. The layer now takes
the application map stack controller (attachMapStackController), subscribes
to stack switches, offers FOLLOW only while the stack is 'photoreal', disables
the button with a MAP SOURCE hint elsewhere, and stops following when the
stack leaves Google 3D. Selection uninstall is a no-op when never installed.

The QA gate checks the disabled path and, when Google 3D is available, that
leaving it stops following; its caption-timeout dump now forces one render to
tell a missed update from a render fault.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-26 00:58:43 +01:00
Said TurkseverandClaude Opus 5.5 e6c2c812f9 fix(street-level): fit the whole photo on phones
At phone width the right rail is the band between mid-screen and the credit
line, so a 16:10 viewer plus its bar and caption was cut off. Below 720 px
the viewer's height is derived from that band (clamped 110-320 px); floating
and expanded viewers reset to auto height. The QA harness checks the photo
and caption fit at 390x844, and CURRENT-STATE documents the docking rules
and phone sizing.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-25 21:18:40 +01:00
Said TurkseverandClaude Opus 5.5 5877f24f0b feat(street-level): movable, simplified panel with the photo first
- The viewer opens at the top of the panel, under the header, so the photo
  is on screen without scrolling; its tools (EXPAND, FIT/FILL, FOLLOW, close)
  sit in a bar above the image, clear of the provider's own overlays, and the
  caption wraps instead of truncating.
- No separate ON/OFF or OPEN NEAREST PHOTO buttons: the header pill is the
  layer switch, and a provider chip is lit only while the layer is on (the
  last lit chip switches the layer off).
- SINCE is a stepped slider (any date to the last month) whose readout names
  the cut-off date and is announced via aria-valuetext.
- The panel floats and resizes (portable spec); in a resized window the
  viewer takes the spare height and only the settings scroll. SHRINK after
  EXPAND, collapsing, or a header double-click docks it at its default size.
- FIT/FILL tooltips say what they do.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-25 20:45:17 +01:00
Said TurkseverandClaude Fable 5.1 28e6a1faa7 feat(street-level): provider contract with Mapillary as the first provider
Street Level is now a provider-neutral layer modelled on the iD editor's
photo overlay. The core owns what every imagery provider shares: the
enable state and a switch per provider, the imagery filter (360°/flat and
captured-since in relative days), one click handler routed by pick prefix,
one viewer host that mounts whichever provider's viewer adapter owns the
open image, the position marker, camera follow, one on-globe credit per
active provider, and share-link parameters (one bit per provider plus the
filter). Providers implement the contract in registry.js: coverage,
sequences, nearest-image lookup and a viewer adapter emitting a common
pose. Mapillary is the only registered provider; Google Street View,
KartaView and Panoramax slot in beside it.

Panel: a PROVIDERS chip row (one chip per registered provider, keyless
chips read as errors), relative-day SINCE options, a provider-neutral
caption whose link label follows the provider, and no vendor mark in the
header. The panel inner is a rail scroller like CCTV's.

Tests cover the registry, params codec, filter, pick router, UI-state
composition, the provider definition and coverage-tile decoding; the QA
harness switches the provider off and on and checks the filter.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-09-25 18:34:09 +01:00
Said TurkseverandClaude Fable 5.1 2dbc684fb1 refactor(street-level): rename the Mapillary layer to street-level
Pure move: the layer id, panel id, CSS prefix, classes, exports and config
paths become provider-neutral (street-level, #street-level-panel, sl-);
the Mapillary-specific modules move under providers/mapillary/. Behaviour
is unchanged. The two sprite collections now register under distinct ids
instead of overwriting each other under 'mapillary'.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-09-25 18:13:53 +01:00