Repository navigation
Conversation
Tool calls from known apps (Gmail, Google Calendar, Google Maps, Uber,
Lyft, GitHub, Slack, Spotify, DoorDash) now show the app's colored
monogram and a plain sentence ("Searching Gmail · invoices from Acme")
instead of the raw MCP server and tool name, on web and mobile. Grouped
rows name the apps ("Used Gmail, Uber, and Google Maps").
Matching is by MCP server name, so it works for every provider, and it
undoes Hermes's editor_<name>_<hex> rename. No logos are bundled.
…ests Chart boards (openui-v2) can now hold four read-only app cards drawn in their service's colors: RideOptions (Uber/Lyft, cheapest tagged), MailList (Gmail), Route (Google Maps) and PullRequest (GitHub progress track, conflict count and check chips). Taps stay local to the card; nothing is booked, sent or merged. A merged pull request plays its fill once on insertion and respects reduced motion. The shared inspector rejects incoherent cards (a conflict state without conflicts, a ready PR with failing checks, duplicate ride options) the same way it rejects bad charts, on web and Android.
demo-connectors.mjs serves canned Gmail, Uber, Google Maps and GitHub tools over MCP stdio (nothing is fetched, booked or merged). acp-with-connectors.mjs wraps any ACP agent and adds them to each session, so a sandbox provider instance gets the tools without editing the agent's own config.
Dogfooding showed the running turn header and single-call rows still read "Get pull request": they resolve their labels separately from the row. All three label paths on web and mobile now use the shared app activity label first, with the live status taking precedence.
Inside chat Markdown the cards' lists inherited bullets and numbers, so the Route card showed "3. (3)" and mail rows had stray dots.
The row that shows the running tool now draws the app's monogram, so a turn that is searching Gmail reads as Gmail while it happens, not only after it finishes.
This was referenced Nov 3, 2026
Cards now read like the apps they come from: ride options with a selected row and a Request button that opens the ride app, mail rows with avatars and attachments plus Open in Gmail, a step-by-step route with Open in Maps, and a pull request card with a small commit graph and conflict marks after @pitus_'s merge-conflicts card. New fields are optional, so existing boards still parse; Android accepts them and shows the simpler layout.
…utes App cards now copy each service's design system instead of sharing T3's palette: Primer for GitHub, Base Web for Uber and Lyft, Material 3 for Gmail and Google Maps, each in its own typeface and in light and dark. - Route draws the path the maps tool returned, projected in Web Mercator onto the tool's basemap, so the line follows the road; no path, no map. - PullRequest shows checks as a timeline when the tool reports timing, opens a failing check's detail, and ends with GitHub's merge-box line. - RideOptions shows vehicle pictures and a sliding selection. - Cards reflow at phone width (container queries) instead of clipping. - Android renders the same cards natively (react-native-svg, embedded fonts). - Demo connectors return a real OSRM route, timed checks and commits.
… in-app film - A streaming openui-v2 board now renders its complete statements as they arrive, so cards appear one by one instead of showing source until the fence closes (web and desktop; Android still renders once complete). - Replay (dev) gains an "App cards" scenario: four demo connector calls, then the board, so the real server, adapter and clients can be checked for free. - test/appCardsFilm.renders.tsx shoots the launch film inside the real app (AppRoot on the fake environment), one PNG per frame on a virtual clock. - Ride card hides its price note at phone width; Android dims the basemap in dark mode.
- test/harnessKits.renders.tsx mounts the demo board inside each kit's colours, fonts and radii (mapped onto T3's semantic variables) at 412 and 760 px, light and dark, and fails if a phone-width thread scrolls sideways. Kits and their provenance live outside the repo. - The demo board moves to test/fixtures/appCardsBoard.ts so the browser test and the preview share it. - The app cards film starts its minute clock at the fixtures' time, so the sidebar no longer reads "230d".
This branch has not been deployed
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.
What you'll see
Launch film v4 MP4 with music (1080p60, 69 s). Shot inside the real T3 Code app:
apps/web/test/appCardsFilm.renders.tsxmounts AppRoot on the test environment and saves one frame per tick of a virtual clock, with demo connector data. The Android scene is a screen recording of the native cards on a Galaxy S25. Music: "November Snow", The Cynic Project / cynicmusic.com (CC0). Map data © OpenStreetMap contributors, route by OSRM.A real model turn on Android. Recording: the T3 Code Android app on an S25 sends the prompt to a T3 server, Hermes runs the four demo connectors on a free model (Nemotron Ultra,
kilo-free), and the board renders as native cards in about 40 s. Every value on the cards matches what the connectors returned.Phone width in real Chrome. S25 Ultra emulation (412 CSS px, 3.5x) on a headless display: all eight cards in a replayed thread report 0 px of sideways overflow.
Earlier films (v3 motion studies, v2, raw recording)
Launch film MP4 with music (1080p60, 71 s). Every scene is an HTML page rendered frame by frame at 4K, not a screen recording. The Gmail, Uber and Maps scenes are motion studies of these cards with demo data (the tilted map is a concept; the PR ships a route line). The GitHub scene recreates @pitus_'s "Generative UI #2 – Merge conflicts"; the cards in this PR are read-only and don't resolve or merge. Music: "November Snow", The Cynic Project / cynicmusic.com (CC0).
The cards as this PR renders them (browser test, demo data):
Earlier film and the unedited recording of a real turn (cards before commit 8)
Film v2 MP4 (40 s) · the "before" is a mock; the rest is the real turn below at a labeled 3× and 1.5×.
Raw recording MP4 (40 s) · typing and the model's wait run at 3× and say so; the board and taps are real time.
Media lives on
media/openui-app-cardsso this diff stays code only.Problem
When an agent uses one of the person's apps, T3 shows a generic row ("Used editor gmail 63ea3519, editor uber 46f286a1, …") and a wall of Markdown. You can't tell at a glance which app was touched or what came back.
What changed (8 commits, read in order)
feat(clients): brand-colored activity rows— tool calls from known apps (Gmail, Google Calendar, Google Maps, Uber, Lyft, GitHub, Slack, Spotify, DoorDash) show the app's colored monogram and a sentence: "Searched Gmail · invoices from Acme", "Checked Uber prices". Grouped rows name the apps: "Used Gmail, Uber, Google Maps, and GitHub". Matching is by MCP server name, so every provider gets it; it also undoes Hermes'seditor_<name>_<hex>rename. Shared resolver inpackages/client-runtime/src/appConnectors.ts; web row inTimelineWorkEntryRow.tsx, mobile row inthread-work-log.tsx.feat(clients): OpenUI app cards— four read-only cards for openui-v2 boards: RideOptions (Uber/Lyft, cheapest tagged), MailList (Gmail), Route (Google Maps), PullRequest (GitHub progress track, conflict count, check chips; a merged PR fills its track once via@starting-style/ Reanimatedentering, reduced motion respected, no continuous animation). Rules live once inopenUiAppCards.ts; web and Android bind them to their own zod/OpenUI copies, like the charts. The inspector rejects incoherent cards (conflict state with 0 conflicts, "ready" with a failing check, duplicate ride options).test(openui-lab): demo connectors—experiments/openui-lab/dogfood/: canned MCP connectors and an ACP proxy that adds them to a sandbox provider instance. Nothing is fetched, booked or merged.docs(user)—docs/user/openui-reports.md: the cards, the activity rows, and design credits.fix(clients): use the app sentence in live and single-call work labels— found by dogfooding: the running header and single-call rows resolved their labels on a separate path and still read "Get pull request". All three label paths on web and mobile now use the shared app label.fix(web): no list markers inside mail and route cards— found by dogfooding: inside chat Markdown the Route card read "3. (3)".feat(web): brand mark on the live activity row— the running row draws the app's monogram while the call is in flight.feat(web): richer app cards with commit graph, app links and selection— the cards now read like the apps they come from: RideOptions selects the cheapest row and its Request button opens the ride app; MailList has sender avatars, attachments and "Open in Gmail"; Route steps through one direction at a time over a route line with "Open in Maps"; PullRequest draws recent commits on a small commit graph with conflicting commits marked, after @pitus_. New fields (arrival,attachment,commits) are optional, so existing boards still parse; Android accepts them and keeps its simpler layout. Still monogram tiles, no bundled logos.Cards stay read-only on purpose: taps only change local selection. Acting through apps (booking, sending) is the RFC's job, with opt-in grants and approvals.
Dogfood (real model turns, real T3)
Sandbox T3 from this branch (worktree
.t3,vp run dev), provider = stock Hermes ACP (gpt-6.1-sol) behind the demo-connector proxy, project = an empty folder. Prompt, typed into the composer:Seven real turns across the session; every one called all four tools once and produced a board the inspector accepts (
{kind: "ready"}), with no invented values. The recorded run: board on screen 35 s after send.Found by dogfooding and fixed here, each with a test: grouped rows named the renamed servers ("Used editor gmail 63ea3519, …"); Hermes tool names carry the detail after a colon (
editor_gmail_63ea3519.search_messages: invoices from Acme); the running and single-call labels bypassed the app sentence; list markers leaked into two cards.Known gap, not fixed: Hermes runs the four calls in parallel, so the live row shows only the newest call (in the recording: Thinking → Tool → GitHub).
Verification
vp test runclient-runtime (appConnectors,openUiAppCards,work-log/presentation),apps/web/.../MessagesTimeline.logic,apps/mobile/src/lib/threadActivityopenUiAppCards3 passed)vp test run apps/web/src/components/chatvp test run --project browser OpenUiAppCards.browser.test.tsx(commit 8: every card renders; ride selection, route steps and PR commits work)@openuidev/*in an isolated install, renders web catalog incl. app cards)tsc --noEmitapps/web, apps/mobile, packages/client-runtimevp lint+vp fmt --checkon touched filesWeb build for the recording: built from the PR head on a GitHub runner (run), served locally against the branch server.
Not verified: iOS and the packaged desktop shell. Android: built in CI from this branch, run on an S25 (replayed and real turns). Commits were made with
--no-verifybecause the repo-wide knip hook flags two*.node-test.tsfiles from the base branch (openUiCharts.node-test.ts,openUiMarkdown.node-test.ts); this PR's own tests are vitest.Surfaces
Credits
OpenUI renders the boards. The PullRequest card and the film's GitHub sequence follow @pitus_'s "Generative UI #2 – Merge conflicts". Brand-colored app cards in place of a bare "thinking" row: @kvnloo.
Built with Claude Opus 5.5 in Hermes, driven from T3 Code.