feat: First-Run Clarity — guided tour, tooltips, and starter questions - #8
Merged
Conversation
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>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
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.
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.
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.—with "no data yet" instead of three zeroes, andmovementLabel(0)no longer cheerfully claims you moved "now".CoachTutorial+StarterQuestions— a scripted, app-authored Buddy intro plus intent-derived starter chips. Suggestions round-trip throughCoachIntent.classify, and render as inert examples when no key is connected, so the app never implies it will answer without one.TourStategate, deliberately not an onboarding step, so norbOnboardingPageseed shifts.One deviation from the plan
The plan specified
hasDayData(steps:activeMinutes:distanceMiles:). That signature returnsfalsefor the existingtoday-zero-step-morningscenario (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 reading0.That scenario's
rbMinutesSinceMovement: 8is the discriminator: Health recorded movement 8 minutes ago, so the day has been observed.hasDayDatatakes it as a fourth input, and a test pins the exact scenario seeds.Coverage
TourStateTests(incl. a scenario-index guard in the style ofOnboardingScenarioIndexTests),StarterQuestionsTests,CoachTutorialTests,HintCopyTests.FormattersTests,RaceGoalsTests,OnboardingStateTests(asserts the tour did not become an onboarding step).Owed before merge
Pushed with
--allow-deferred; the codeyam finalize (glossary/journal/reconcile) is still owed.🤖 Generated with Claude Code