Skip to content

osm cockpit: click a dot, get the real OSM element and its tags - #114

Merged
AdaWorldAPI merged 1 commit into
mainfrom
claude/q2-osm-map-reencoding-56p5e2
Aug 11, 2026
Merged

osm cockpit: click a dot, get the real OSM element and its tags#114
AdaWorldAPI merged 1 commit into
mainfrom
claude/q2-osm-map-reencoding-56p5e2

Conversation

@AdaWorldAPI

Copy link
Copy Markdown
Owner

Closes the last functional gap in the POC. The overlay drew dots with no identity — a map of dots is a POC; knowing what the dots are is a map.

No upstream change was needed

Everything already existed in osm-soa-bake and only had to be composed:

piece where
cluster::facets(row)Vec<(slot, Facet)> already public
Facet::Tag { member, key, value } (ordinals) already public
codebook::read_booksBooks { identities, tag_keys, tag_values } already public
IdentityCodebook::key(ordinal) -> Option<&str> lance-graph-contract

The .books sidecar was already hydrating alongside the slab (#112 carries both artifacts), so the data was on disk and merely unread.

Why a second endpoint, not inline tags

A city tile returns 15,016 features and each row carries up to TAGS_PER_ROW tags — inline tags would multiply an already-large response by the tag fan-out, for data a viewer wants about one dot at a time. FeatureOut gains a 4-byte idx (its slab row index) and GET /api/osm/feature/:idx answers on demand. The detail is a click, not a download.

Tags bind by ORDINAL, not adjacency

query_feature keeps only the tag facets whose member equals the row's own identity ordinal — cluster's own documented property, the one that lets a continuation row be read alone. Filtering on it is what stops a continuation row's tags being attributed to the wrong element.

Measured, on the real Berlin bake

GET /api/osm/feature/1740603
{ "osm_key": "0f02:1433098796",
  "tags": { "access":"private", "emergency":"designated", "highway":"service",
            "service":"driveway", "tunnel":"building_passage" } }

An emergency-access driveway passing under a building in central Berlin. Sampled for variety: a 10-tag parking amenity with a conditional-fee expression, a street lamp, a lift gate, a give-way sign; 0f01 = node, 0f02 = way. osm_key is the real OSM element id, resolved through Books::identities ("{kind:04x}:{osm_id}").

The UI, browser-verified

.pt markers carried pointer-events:none — they were not clickable at all. Removing that is the whole interaction; panning still works because mousedown bubbles from the dot to #map, and the existing moved guard already suppresses the click that ends a drag.

Two-sided, both measured in a hermetic run:

action result
click a marker panel shows osm key 0f01:9710028117, highway=street_lamp, layer=1; .pt.sel highlight applied; 0 page errors
drag starting on a marker transform moves -562793,-343436-563033,-343526, markers repaint 64,707 → 59,845, panel unchanged — the drag did not open a detail

The second row is the one that matters: enabling pointer events could have made every pan open a random feature, and it does not.

Two verification-method notes, each cost a run:

  • The first click landed on the .ctl button stack (left:12, top:12, ~170 px tall) rather than a marker. Marker selection now requires x > 320, y > 220.
  • Playwright refused the first .pt in DOM order as "outside of the viewport" — markers live inside the translated #tiles layer, so DOM order says nothing about visibility. The probe picks by measured getBoundingClientRect.

Verification

cargo test --bin q2-cockpit90 passed, 0 failed, 3 ignored.

⚠️ CI on this fork is skipped, not passing (if: github.repository == 'quarto-dev/q2').

🤖 Generated with Claude Code

https://claude.ai/code/session_01NMeiLmtDKhomJNSo2ecbJw


Generated by Claude Code

Closes the last functional gap in the POC — the overlay drew dots with no
identity. A map of dots is a POC; knowing what the dots ARE is a map.

NO UPSTREAM CHANGE WAS NEEDED. Everything already existed in osm-soa-bake and
only had to be composed: cluster::facets(row), Facet::Tag{member,key,value},
codebook::read_books -> Books{identities,tag_keys,tag_values}, and
IdentityCodebook::key(ordinal). The .books sidecar was ALREADY hydrating
alongside the slab (Phase 6 carries both artifacts), so the data was on disk
and merely unread.

WHY A SECOND ENDPOINT, NOT INLINE TAGS. A city tile returns 15,016 features and
each row carries up to TAGS_PER_ROW tags, so inline tags would multiply an
already-large response by the tag fan-out — for data a viewer wants about ONE
dot at a time. FeatureOut gains a 4-byte `idx` (its slab row index) and
GET /api/osm/feature/:idx answers on demand. The detail is a click, not a
download.

TAGS BIND BY ORDINAL, NOT ADJACENCY. query_feature keeps only tag facets whose
`member` equals the row's own identity ordinal — cluster's own documented
property, the one that lets a continuation row be read alone. Filtering on it is
what stops a continuation row's tags being attributed to the wrong element.

MEASURED on the real Berlin bake:

  GET /api/osm/feature/1740603
  { "osm_key": "0f02:1433098796",
    "tags": { "access":"private", "emergency":"designated", "highway":"service",
              "service":"driveway", "tunnel":"building_passage" } }

An emergency-access driveway passing under a building in central Berlin.
Sampled for variety: a 10-tag parking amenity with a conditional-fee
expression, a street lamp, a lift gate, a give-way sign. osm_key is the real
OSM element id via Books::identities ("{kind:04x}:{osm_id}").

THE UI. `.pt` markers carried `pointer-events:none` — they were not clickable at
all. Removing that is the whole interaction; panning still works because
mousedown bubbles from the dot to #map and the existing `moved` guard already
suppresses the click that ends a drag.

Two-sided, both measured in a hermetic browser run:

  click a marker            panel shows osm key 0f01:9710028117,
                            highway=street_lamp, layer=1; .pt.sel applied;
                            0 page errors
  drag FROM a marker        transform -562793,-343436 -> -563033,-343526,
                            markers repaint 64,707 -> 59,845, panel UNCHANGED

The second is the one that matters: enabling pointer events could have made
every pan open a random feature, and it does not.

90 passed, 0 failed, 3 ignored.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NMeiLmtDKhomJNSo2ecbJw
@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@AdaWorldAPI, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 35 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: cf6a42a5-46b9-405b-aec3-21da4b4891da

📥 Commits

Reviewing files that changed from the base of the PR and between b9eebd1 and f70e47c.

📒 Files selected for processing (4)
  • claude-notes/plans/2026-08-08-osm-soa-cockpit-wiring.md
  • crates/cockpit-server/src/main.rs
  • crates/cockpit-server/src/osm.rs
  • crates/cockpit-server/src/osm_features.rs

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cursor

cursor Bot commented Aug 11, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_3277af56-dff0-4ffb-9db9-5bf045e71532)

@AdaWorldAPI
AdaWorldAPI marked this pull request as ready for review August 11, 2026 11:52
@AdaWorldAPI
AdaWorldAPI merged commit 88c7455 into main Aug 11, 2026
5 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.

2 participants