Skip to content

feat(render_map_tool): live traffic and Satellite/Traffic buttons on the map - #278

Merged
mattpodwysocki merged 8 commits into
mainfrom
feat/render-map-traffic-toggles
Nov 5, 2026
Merged

mattpodwysocki merged 8 commits into
mainfrom
feat/render-map-traffic-toggles

Conversation

@mattpodwysocki

@mattpodwysocki mattpodwysocki commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Description

Fixes #277. Asked to show traffic downtown, ChatGPT told a user "the Mapbox map available here doesn't support a live street-traffic overlay". That was true: the map only draws GeoJSON, baseMapConfig has no traffic option, and traffic only showed up in route summaries. Users also couldn't switch to satellite without asking the model.

Buttons on the map

Two icon buttons sit under the zoom control, the same size as zoom and compass: a filled globe for satellite view and a filled car for live traffic (per the design feedback), with tooltips and aria-labels.

Traffic layer

  • Mapbox Traffic v1 (mapbox://mapbox.mapbox-traffic-v1, source layer traffic), as lines colored by congestion: light, moderate, heavy, severe.
  • It's in the middle slot, so road labels and our own routes/isochrones stay on top.
  • Light traffic shows only on main roads (motorway through tertiary, plus ramps), since on every side street it turned the map green. Moderate and worse show on every road.
  • Each direction is offset to its own side of the road, and lines run 1 px to 4 px from zoom 12 to 18.
  • While it's on, a white "Fast [four colors] Slow" pill sits in the bottom left, below any payload legend, as in the design. The lines use the same colors (#00c94e, #ff9900, #ff2b3a, #8b0a18).
  • Tiles come from api.mapbox.com, which the CSP already allows, so the CSP is unchanged.

showTraffic input

  • A new render_map_tool field, plus a line in the description, so the model knows traffic is possible and can open the map with it on. false turns it off.
  • Left out, the map keeps whatever the user picked, like baseStyle does.
  • A showTraffic-only call is renderable, like baseStyle.

Style switches keep what's drawn

setStyle() drops every source and layer the page added. The page now keeps a copy of each GeoJSON layer it draws, and after a switch from the Satellite button it re-adds:

  • those layers (self-fetched routes and isochrones included, deferred data once it lands)
  • the base map config
  • traffic

It does this without moving the camera. When the model switches baseStyle, traffic stays on.

Summary chip

The chip sat on top of the zoom buttons, since both use the top-left corner. While it shows, the corner's controls now move below it.

Worth a close look

  • Re-adding after setStyle: the restore guards on getSource/getLayer, so it's safe whether or not GL JS kept a layer across the switch.
  • Persistence: traffic persists across renders unless the model says otherwise. That's a change from "every call is a fresh render", but the user's toggle surviving a follow-up render seemed right. Happy to flip it.
  • Stale tiles: the map doesn't refresh traffic tiles on a timer, so a map left open goes stale until it's moved or reloaded.
  • Model context: the page doesn't tell the model what the user toggled.

Testing

  • Buttons and traffic:
    • Both buttons render with their labels, start off and report aria-pressed.
    • Traffic adds the vector source and line layer (middle slot, congestion colors, light traffic only on main roads), shows the Fast/Slow pill alongside the payload legend, and removes it all on a second click.
  • Satellite:
    • It switches style and re-adds the drawn route (same slot) and traffic without refitting.
    • Switching back works.
    • Base map config is reapplied.
    • A model baseStyle updates the button.
  • showTraffic:
    • true turns it on, left out keeps it on, false turns it off.
    • It survives a model baseStyle switch.
    • A showTraffic-only call works and round-trips through the inline ref.
  • Summary chip: the top-left controls move below it and back.
  • Test sandbox: the fake map now tracks sources and layers like a style (setStyle clears them) and mounts controls through onAdd. The 65 existing script tests pass unchanged.
  • Checks run:
    • The tileset's TileJSON lists the traffic source layer.
    • npm test: 1060 pass.
    • npm run lint: 0 errors.
    • npm run build OK.
    • The built script parses (new Function).
  • Live tile: a San Francisco tile has congestion, class and structure, which the filter relies on.
  • Claude Desktop: checked on both styles: the model passed showTraffic: true for "show traffic in downtown San Francisco", and both buttons work. Not tried in ChatGPT yet.

Plain:
plain

Traffic:
traffic

Satellite Traffic:
satellite-traffic

Checklist

  • Tests added
  • CHANGELOG.md updated under Unreleased

🤖 Generated with Claude Code

…the map

Adds Satellite and Traffic buttons under the zoom control, and a
showTraffic input so the model can open the map with traffic on. Traffic
is the Mapbox Traffic v1 tileset colored by congestion in the middle
slot. The page keeps a copy of what it drew so a style switch puts it
back, and the top-left controls move below the summary chip.

Fixes #277

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@mattpodwysocki
mattpodwysocki requested a review from a team as a code owner October 15, 2026 15:05
mattpodwysocki and others added 4 commits October 15, 2026 11:05
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The text buttons didn't fit GL JS's 29px control column, so they're now
icon buttons (globe, traffic light) like zoom and compass. Light traffic
on every side street turned the map green, so it shows only on main
roads; each direction is offset to its side of the road, and lines are
thinner.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The legend sat on the logo and an open results panel on the attribution.
Move the legend above the logo, stop the panel above the attribution,
and fit data above the legend's new spot.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ic-toggles

# Conflicts:
#	CHANGELOG.md
#	src/tools/render-map-tool/assembleMapPayload.ts
mattpodwysocki and others added 2 commits November 5, 2026 09:22
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@zmofei zmofei left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, just two nits inline.

'To show live traffic congestion on the roads in view, pass ' +
'`showTraffic: true`. The map also has Satellite and Traffic buttons ' +
'users can toggle themselves. ' +
'Every call is a fresh render — it does not accumulate state — so to ' +

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: can you add showTraffic to this sentence too? Otherwise the model might send just showTraffic: true and the route disappears. Also, "does not accumulate state" isn't quite true anymore, since traffic now stays on.


function applyBaseMapConfig(baseMapConfig) {
if (!baseMapConfig || typeof baseMapConfig !== 'object') return;
currentBaseMapConfig = baseMapConfig;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: this never gets reset. After a render without baseMapConfig, the Satellite button can bring back an old config, like night mode. Maybe set it on every render, or clear it in teardown()?

@mattpodwysocki
mattpodwysocki merged commit 3994926 into main Nov 5, 2026
5 checks passed
mattpodwysocki added a commit that referenced this pull request Nov 5, 2026
* feat(render_map_tool): results carousel instead of the side list

Place results now show as swipeable cards along the bottom of the map,
like ChatGPT's place results. The selected card and its pin stay in
sync: swiping or clicking a card highlights its pin (white, on top,
with the name under it) and pans only when the pin is hidden; clicking
a pin scrolls to its card. Pins with a card no longer open popups.
Cards show today's hours parsed from simple OSM opening_hours.

Closes #283

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: CHANGELOG PR number for the carousel

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(render_map_tool): keep the selected pin's label clear of the cards

Found in a headless render: a pin just above the carousel kept its name
label hidden behind the cards. Pan when the label (not just the pin)
would be hidden, do it for pin clicks too, and pan only as far as
needed instead of recentering, so nearby pins stay in view.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(render_map_tool): move the zoom buttons below the summary chip

The "6 results for ..." chip sat on top of the zoom-in button. Same
placeTopLeftControls fix as #278, copied as-is so the two merge cleanly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(render_map_tool): follow OpenAI's UI guidelines for the carousel

- Every card has a picture: Assembly's marker icon on a muted tile until
  a photo loads, or when there is none, labeled for screen readers.
- Pin numbers pick dark or white text by WCAG contrast; white on the
  default orange was 2.8:1.
- The tool description asks the model not to repeat the places the
  cards already show.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(render_map_tool): white place pins with Maki icons instead of orange numbers

Matches ChatGPT's place results and OpenAI's UI guidance: search results
and other places in the carousel are white pins with a Maki icon for
their category, black when selected, and their cards drop the number
badge. The icon comes from Search Box's maki name, else the category,
else a plain marker; about 20 common Maki icons (CC0) are inlined.
Numbered badges stay where the number means something: a caller's
numbered label (visit order), optimized stops, start/end.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(render_map_tool): light and dark mode for the map's cards and chips

The cards, summary chip, legend and messages follow the host's theme
(MCP Apps hostContext.theme, at initialize and on host-context-changed),
falling back to prefers-color-scheme. Colors use the host's style
variables (--color-background-primary etc.) when sent, with light/dark
fallbacks otherwise. The map and its pins are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat: carousel cards and pin labels per design feedback (PDES-471)

- Capitalize the category on cards.
- Show the Places price level ($ to $$$$) instead of "% popularity";
  Places has no star rating.
- Hours say "Open until 8:30 PM" / "Closed · opens 7:30 AM" when Places
  gives the place's time zone, otherwise "Open 7:30–15:00" for today.
- Drop the phone number from the card until there's a place detail card.
- Label every carousel pin with a white halo instead of a box.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test: drop duplicate sandbox keys left by the main merge

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
mattpodwysocki added a commit that referenced this pull request Nov 5, 2026
* fix: review follow-ups for #278, #309 and #311

- render_map_tool: clear the base map config on teardown so the Satellite
  button can't bring back an earlier render's config; the description
  says traffic and satellite carry over and to re-pass showTraffic with
  the content.
- resource_reader_tool: return an image blob once (as an image under
  700 KB, else a short note) instead of base64 text plus a structured
  copy; point mapbox://selffetch/ refs to render_map_tool.
- Eval: list token_embedded in both hillclimb metrics.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: add PR number to CHANGELOG

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: review follow-ups for #312

- Say when the 1x fallback replaced a too-large @2x image.
- Test that the large-image ref's decoded data= has no access token.
- docs/mcp-ui.md describes the static map viewer and public-token link.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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.

render_map_tool: live traffic overlay and in-map Satellite/Traffic toggles

2 participants