Skip to content

feat: First-Run Clarity — guided tour, tooltips, and starter questions - #8

Merged
nseldeib merged 3 commits into
mainfrom
first-run-clarity
Aug 3, 2026
Merged

feat: First-Run Clarity — guided tour, tooltips, and starter questions#8
nseldeib merged 3 commits into
mainfrom
first-run-clarity

Conversation

@nseldeib

@nseldeib nseldeib commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

A new user reported not knowing what they were looking at, what to do next, or what to ask the coach. This makes the first session self-explaining on all five fronts.

Note on provenance: this plan was filed in .codeyam/plans/completed/ but had never been implemented — commit 6d14db1 moved the file as a pure rename, zero lines changed, and git log --all over every path it describes was empty. The staleness check flagged it as "already landed upstream"; that was a bookkeeping artifact, not real completion.

What changed

  • InfoHint / HintCopy — tap-to-reveal ⓘ captions. Inline rather than .popover/.help(): the macOS 12 test build has no .help(), and an inline disclosure captures deterministically and reflows at accessibility text sizes.
  • Buddy mood caption — "Buddy's read on today" gives the mood chip a referent instead of a bare "Ready" floating under the mascot.
  • Honest stat tiles — a fresh install reads with "no data yet" instead of three zeroes, and movementLabel(0) no longer cheerfully claims you moved "now".
  • CoachTutorial + StarterQuestions — a scripted, app-authored Buddy intro plus intent-derived starter chips. Suggestions round-trip through CoachIntent.classify, and render as inert examples when no key is connected, so the app never implies it will answer without one.
  • Today spotlight tour — its own overlay with its own TourState gate, deliberately not an onboarding step, so no rbOnboardingPage seed shifts.
  • Race banner — arrives after day one, explains what adding a race actually does, and snoozes twice before going away instead of self-destructing on the first ✕.

One deviation from the plan

The plan specified hasDayData(steps:activeMinutes:distanceMiles:). That signature returns false for the existing today-zero-step-morning scenario (seeded 0 steps / 0 active / 0.0 mi), which would have rendered it as — contradicting the plan's own requirement that a real zero-step morning keeps reading 0.

That scenario's rbMinutesSinceMovement: 8 is the discriminator: Health recorded movement 8 minutes ago, so the day has been observed. hasDayData takes it as a fourth input, and a test pins the exact scenario seeds.

Coverage

  • 12 registered scenarios: three tour steps, day-one no-data, both open-hint states, the rewritten race banner, large-text accessibility, and four Ask Coach tutorial/starter states.
  • New: TourStateTests (incl. a scenario-index guard in the style of OnboardingScenarioIndexTests), StarterQuestionsTests, CoachTutorialTests, HintCopyTests.
  • Extended: FormattersTests, RaceGoalsTests, OnboardingStateTests (asserts the tour did not become an onboarding step).
  • 433 Swift tests passing, up from 369.

Owed before merge

Pushed with --allow-deferred; the codeyam finalize (glossary/journal/reconcile) is still owed.

🤖 Generated with Claude Code

nseldeib and others added 3 commits August 3, 2026 16:28
A new user reported not knowing what they were looking at, what to do next,
or what to ask the coach. This makes the first session self-explaining.

- InfoHint/HintCopy: tap-to-reveal ⓘ captions (inline, not .popover/.help(),
  so they compile on the macOS 12 test build and capture deterministically).
- BuddySummaryCard: "Buddy's read on today" caption gives the mood chip a
  referent instead of a bare "Ready" floating under the mascot.
- Honest stat tiles: a fresh install reads "—" with "no data yet" rather than
  three zeroes, and movementLabel(0) no longer claims you moved "now".
  A genuine zero-step morning still reads 0.
- CoachTutorial + StarterQuestions: a scripted, app-authored Buddy intro and
  intent-derived starter chips. Suggestions round-trip through
  CoachIntent.classify, and render as inert examples when no key is connected.
- Today spotlight tour: its own overlay + TourState gate, deliberately NOT an
  onboarding step, so rbOnboardingPage seeds cannot shift.
- Race banner: arrives after day one, explains what adding a race does, and
  snoozes twice before going away instead of self-destructing on the first ✕.

hasDayData additionally reads minutesSinceLastMovement: the plan's three-metric
signature would have collapsed the real today-zero-step-morning scenario
(0 steps / 0 active / 0.0 mi, but movement recorded 8m ago) into the no-data
bucket and swallowed a true zero.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A connected, valid OpenAI key returned "I couldn't reach Buddy just now. Check
your connection and try again." Neither the key nor the network was at fault:
every failure that was not a 401 collapsed into one generic message, so a
provider-side configuration fault was reported as a connectivity problem and
pointed the user at the one thing that was working.

Two problems, both fixed:

1. Undiagnosable. `throwForStatus` mapped every non-401/429 status — including
   400 (bad request) and 404 (unknown model) — to "could not be reached", and
   the app flattened everything except an invalid key into the connection copy.
   A misconfigured model and a real outage were indistinguishable from the
   chat. 400/404 now report `model_unavailable` and carry the provider's own
   error text, and the app renders that reason instead of the network line.
   A genuine 5xx still reads as an outage, so the new codes stay meaningful.

2. Reasoning tokens consumed the whole reply budget. `max_completion_tokens`
   bounds reasoning AND visible output, so 1024 — sized for a 2-4 sentence
   answer — could be spent before a single visible character was emitted. That
   returns content "" with finish_reason "length", which read as "no text" and
   surfaced as a 502. The floor is now 16k, and an exhausted budget raises
   `token_budget_exhausted` rather than hiding as an empty completion.

The existing tests stub fetch, so they never exercised the real token budget or
finish_reason and could not have caught this; the new cases pin both.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…stions

Twelve captured scenarios covering the feature's states: the three tour
spotlight steps, the day-one no-data dashboard, both open-hint states, the
rewritten race banner, large-text accessibility, and the four Ask Coach
tutorial/starter states.

Slugs in TourStateTests match what `register` assigned (tour-step-1-buddy
rather than tour-step-buddy), so the scenario-index guard resolves.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 3, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
otterpace Ready Ready Preview Aug 3, 2026 9:39pm

@nseldeib
nseldeib merged commit efdd27a into main Aug 3, 2026
3 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