Skip to content

Inbox: the onboarding tour (a replay of how we built the Inbox with the Inbox) - #2156

Merged
backnotprop merged 12 commits into
mainfrom
feat/inbox-onboarding-tour
Oct 25, 2026
Merged

backnotprop merged 12 commits into
mainfrom
feat/inbox-onboarding-tour

Conversation

@backnotprop

@backnotprop backnotprop commented Oct 25, 2026 •

Copy link
Copy Markdown
Owner

Builds the owner-approved Inbox onboarding tour (approval inbox-onboarding-tour, 2026-10-24; record, prototype and BRIEF in .product/approved/inbox-onboarding-tour/, not in this repo). All seven §7 choices are built as recommended.

What it is

The idea: "To show you how the Inbox works, here is how we used it to build the Inbox."

The tour replays the real thread Guided Reviews in VS Code: spec and prototype. It uses the window's own InboxList, MessageBody (the real question card with #2144's picture tiles) and Sidebar, fed from a fixture instead of the person's store.

  • Entry, "Two paths": on a first run, two equal cards, Start the tour and Connect my agents. The second opens the existing first-run screen, unchanged (Inbox first run: notifications ask on the first-run screen; your sent reply in your own words #2148's screen is untouched).
  • Steps 1–5:
    1. Open the question.
    2. Pick a picture. The magnifier opens a viewer that offers "Pick A".
    3. Send. This is a replay: nothing is written.
    4. Watch the next messages arrive: the reply is delivered, then the 12:18 PM message, then PR VS Code Guided Reviews: our guide reader in one tab #2134 with its screenshots. Each is tagged Replay.
    5. Open the 77 KB prototype.html in a sandbox="allow-scripts" frame.
  • Coach cards: at most two sentences each. They sit beside their target, and on a phone they are a sheet that never covers it. The bar's Exit tour is there at every step.
  • Step 6 is real: the person's own first-run screen, headed "Now, let's connect your agents". It never runs installs. Notifications are asked only on a click; if they are skipped, the first real thread asks once more, inline.
  • A pick other than A is replayed with an honest note.
  • A real message during the tour adds a chip to the tour bar; the chip leaves the tour and opens that thread. Over the replay (steps 1–5) with the tab in front, nothing notifies or rings. On step 6, and whenever the tab is in the background, notifications and the sound work as always. ⌘K search does nothing over the replay.
  • Returning users get Settings → General → "Getting started" → Take the tour, and a quiet What's new line that stops once the tour is skipped or finished. There is no dialog. Exit and Finish go back where the tour started (Settings stays Settings) through the browser's Back, so a later Back never reopens the tour.
  • Picture labels: "Mockup · VS Code" ("Mockup" on a phone) and "Screenshot · Inbox", through a new optional ui seam, QuestionBlock's pictureBadge (absent by default, so every other card is unchanged).
  • Step 6's Try it carries the brief's prompt (one real question, with pictures), only in the tour.
  • The first real thread's inline ask follows the ask above the list (never when notifications are off or dismissed), uses a real thread's words, and the list's line stands down while it shows.

Server

  • GET /api/inbox/tour/fixture.json and GET /api/inbox/tour/assets/<name> (packages/server/inbox-tour.ts):
    • read only, GET and HEAD;
    • only names in the manifest are served;
    • nosniff, same-origin CORP, ETag;
    • pictures are sandboxed, and the mock gets sandbox allow-scripts, never allow-same-origin.
  • The content (about 775 KB) lives in packages/inbox/tour/content/. It is embedded through literal type: "file" imports (inbox-tour-content.ts) and served lazily, never inlined into inbox.html.
  • POST /api/inbox/settings takes { tour: { seen?, finished?, skipped?, notify_again? } }. It is stored as config.json inboxTour and returned in GET as tour. No other routes were added.

Rules kept

  • No new keyboard shortcuts.
  • Component tooltips only.
  • 44 px targets on a phone.
  • Reduced motion: no pulse and a shorter replay.
  • Keyboard focus never stays on the page.
  • Light and dark.
  • Phone (WebKit) and stacked windows.
  • Over the tailnet the tour plays in full; step 6 says to connect agents from their own computer.

Tests

  • Unit: packages/inbox/tour/model.test.ts covers:

    • the state machine and coach cards;
    • that every question block parses with core's parser;
    • assets: each one exists and is in the manifest;
    • the content stays under 800 KB;
    • the privacy lint.

    packages/server/inbox-tour.test.ts covers the routes and the tour key.

  • DOM: packages/inbox/tour.test.tsx, added to test.yml's DOM list. It covers:

    • Two paths;
    • the walk writing nothing;
    • Exit at every step;
    • the note;
    • the chip;
    • Settings;
    • the tailnet.
  • e2e: tests/e2e/inbox-tour.spec.ts, against the compiled binary:

    • the paced newcomer walk, timed and filmed;
    • keyboard only;
    • an iPhone in WebKit;
    • reduced motion and a 900 px stacked window;
    • Exit at every step, with the person's list read before and after and found unchanged;
    • a real message mid-tour;
    • the routes.
  • Existing first-run specs and DOM tests now mark the tour decided first (tests/helpers/tour-decided.ts; the DOM server's first-run mode does it). inbox.spec.ts's rhythm check now counts the new quiet "Take the 2-minute tour" line.

Results:

  • bun run typecheck passes.
  • The inbox DOM list (--isolate), with the ui picture tests: 780 pass.
  • The new spec passes on a freshly compiled plannotator binary, and so do inbox, inbox-first-run, notifications, inbox-agents, inbox-phones, inbox-tailscale, inbox-settings, inbox-sound and inbox-mobile.

Timing (the brief's paced model: 220 wpm, 1.5 s to find a control, 3 s for the closer look, 5 s in the mock; screenshot time left out):

  • the aha moment (the 12:18 PM message arriving): 36.8 s after Start;
  • steps 1–5 done: 74.9 s;
  • Finish, after reading step 6 for 35 s: 110 s.

Differs from the prototype

  • Step 6 reuses the real first-run screen ("One next step" plus its lists) instead of the prototype's list of agent rows. The record puts changes to the connect logic out of scope.
  • The folded "Quiet 9" band is not drawn: drawing it would mean inventing rows.
  • Graphite's three full-size screenshots were dropped to keep the content under budget. That side thread's zoom shows its thumbnails.
  • The What's new line shows while the tour was never started, skipped or finished, rather than being tied to one release's version.
  • Finish returns to the person's Inbox with a toast, rather than a "Waiting for Claude Code's question" card. A first question sent from compose appears in the list as Sent.

🤖 Generated with Claude Code

backnotprop and others added 12 commits October 24, 2026 21:40
…ne and the tour window

The approved tour (approval inbox-onboarding-tour): a replay of the real
Guided Reviews in VS Code thread, drawn with the window's own list, thread
and picture-choice card over a fixture served lazily from the binary, then
step 6 in the person's own first-run screen. Two paths entry, Settings and
What's new entry points, tour bar with a chip for real arrivals.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…prompt; shot helper fixes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Notifications and the sound work as always on step 6 and with the tab in the
  background; only the replay in front stays quiet (the chip says it).
- The first real thread's inline ask follows the list's ask (never off or
  dismissed), in a real thread's words, and the list's line stands down.
- Search does not open over the replay.
- Exit and Finish go back where the tour started (Settings stays Settings);
  Back never reopens the tour or records a second skip.
- Picture labels: ui QuestionBlock's pictureBadge seam (default absent);
  "Mockup · VS Code" ("Mockup" on a phone) and "Screenshot · Inbox".
- The What's new line stops once the tour is skipped or finished.
- Step 6's Try it carries the brief's prompt (one real question, with pictures).
- Tests for each; the routes check now compares the binary's content byte for
  byte; shots wait for the coach's entrance.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The picture badge is styled inline: no new utility class, so the guides.show
  viewer CSS and its pinned manifest stay byte for byte (check:manifest).
- The chip and the tour sidebar's Settings replace the tour's history entry,
  so Back never reopens #tour=N.
- The tour's start point is kept only for a tour started from a screen and is
  forgotten when it ends: a tour reached by a link exits to the Inbox.
- e2e: Back after the chip and after Settings, and the link-entered tour's Exit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The picture badge moves into the extracted QuestionPictureTiles (its
pictureBadge prop; the card passes it through), still styled inline so the
guides.show viewer's CSS and manifest stay unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The landing loop counted the pressed Exit or Finish as focus while the tour's
screen was still up; on a slow runner the Inbox redrew after that and focus
dropped to the page (the Inbox window e2e on Linux CI). It now waits for the
tour bar to go, then for focus to hold over a few frames, for up to 3 s.

Tests: focusKept also checks focus is still held a moment later; the link case
checks the person's own list (a thread that arrived mid-tour waits behind
"N new"); PLANNOTATOR_E2E_CPU_THROTTLE reproduces a slow runner.

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

- endTour(kind, to?): what it records (exit or finish) apart from where it
  goes. Step 6's How to allow opens Settings → General and records finished.
- Step 6's real sidebar rows (Inbox, projects, Settings, Drafts, pins, What's
  new, Reload Claude Code) end the tour, finished.
- writeRoute replaces whenever the route leaves a #tour entry, and the Exit
  fallback after a reload or a link replaces too, so Back never reopens
  #tour=N. Leaving step 6 by Back records finished, before it skipped.
- e2e: How to allow, the step 6 sidebar's Inbox and Settings rows, Exit after
  a reload; Back never reopens the tour.
- AGENTS.md: the wording, and PLANNOTATOR_E2E_CPU_THROTTLE.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
settingsReturn and whatsNewReturn skip #tour routes, so ← Inbox after
leaving step 6 (sidebar Settings, How to allow, What's new) goes to the
screen before the tour. The step 6 e2e case presses both buttons; the
blocked-notifications init script now runs before the first navigation.

Co-Authored-By: Claude <noreply@anthropic.com>
@backnotprop
backnotprop merged commit d400906 into main Oct 25, 2026
32 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.

1 participant