Skip to content

fix(web): make onboarding shortcuts easier to follow - #2770

Merged
tyler-dane merged 2 commits into
mainfrom
claude/onboarding-ux-improvements-c6e80c
Aug 12, 2026
Merged

fix(web): make onboarding shortcuts easier to follow#2770
tyler-dane merged 2 commits into
mainfrom
claude/onboarding-ux-improvements-c6e80c

Conversation

@tyler-dane

Copy link
Copy Markdown
Contributor

Summary

First-run walkthrough surfaced several points of confusion in the Shortcut Showcase and the post-showcase practice checklist. This fixes them:

  • The assist button is always available and says what it does. It previously appeared only after 15 seconds idle or 2 stray keypresses, so its arrival read as arbitrary, and "Show me" did not convey that clicking it performs the action and advances the step. The useShowcaseAssist hook (idle timer + document keydown listener) is gone; the button now renders on every step except graduation and is labelled "Do it for me".
  • Step 7 teaches the stretch in two phases. It was the only step that flattened a sequence and a chord into one keycap row (Tab Shift ArrowDown), which reads as a single three-key press. The row now shows Tab alone until the practice board's end edge has focus, then swaps to Shift + arrow. Body copy also names the up/down arrows, since left/right do not stretch.
  • No more "Mod" in prose. Keycap chips were already platform-aware, but step 9's sentence leaked the raw hotkey token. It now resolves to Cmd or Ctrl.
  • Copy fixes. The practice events are unsaved and anonymous, so the checklist is "Practice on sample events", not "real events". "Drop a new event on the grid" becomes "Place a new event on the grid" (nothing is being dropped from anywhere); same verb fixed in step 8's body.
  • The sign-up row looks clickable. It was already a button but styled identically to the read-only checklist rows; it is now an accent pill matching the welcome modal's sign-up CTA.

Analytics: shortcut_showcase_assist_shown is replaced by shortcut_showcase_assist_used { step }, which fires on click rather than on reveal. Any insight built on the old event needs repointing. Worth stating as a deliberate trade: the old event was the only signal for "which step do people get stuck on", since it fired on the idle/failed-attempt inference. The new one measures a decision to hand the step over instead. If the struggle signal turns out to matter, time-on-step is derivable from the existing shortcut_showcase_step_completed events rather than by restoring the hook.

Simplicity

The change deletes the useShowcaseAssist hook entirely (two constants, a 15s timer, a document keydown listener, three refs, and a state variable) rather than adding a config flag to it, so it removes per-keystroke work rather than adding any. No useEffect, useRef, or useState was added.

A second commit (refactor(web): tidy the onboarding hint and checklist rendering) applied the cleanup pass:

  • The phase condition is a named isStretchPhase binding above the return instead of a ternary buried in a JSX prop.
  • The phase-two keycaps moved into showcase.steps.ts as STRETCH_KEYCAPS, next to the rest of the lesson content, carrying the note that explains why that arrow stays literal: stretching reuses the Shift+Arrow family that KEYMAP.moveEvent binds, and the arrow demonstrates one direction of it. (I checked whether the resize chord deserved its own KEYMAP entry; it does not, because the real handler binds KEYMAP.moveEvent.hotkeys.* for both moving an event and stretching a focused edge. Inventing an entry would have been fiction.)
  • The doItForMe switch lost its unreachable graduation arm. Falling through to advance() is equivalent, since advance() calls finish() on the last step.
  • The checklist's sign-up CTA is an early return, so the row fragment is no longer built and thrown away for that item, and the list item owns its spacing instead of the button.
  • The tests share one showStep() helper instead of two spellings of the same setup.

The two-phase hint deliberately does not become a general per-step phase model. Keeping the swap in the component preserves the reference identity that keymap.test.ts asserts between step keycaps and KEYMAP, and resizeEdge was added to that parity list now that its declared keycaps are a pure KEYMAP.edgeFocus reference rather than a hand-built array.

Two cleanup findings were considered and skipped:

  • Building the CTA from c-button c-button-primary. That utility hardcodes h-11, too tall for the compact checklist card, so adopting it means overriding the height it exists to set. The pill instead matches the app's established compact-CTA recipe used by WelcomeModal, AnonymousCalendarRow, CalendarListHeader, and TasksRemovalNotice. Worth noting the real cost: those compact pills hover with brightness-110 while c-button-primary hovers with bg-accent-hover, so a change to the accent-hover token will not reach any of them. That divergence predates this PR and deserves its own pass rather than a sixth variant here.
  • Interleaving keycaps into step prose (the ShortcutTipPart model that shortcut-tips.data.ts already uses). That would let step 9 render real chips inside the sentence and drop MOD_KEY entirely, but it reshapes how every step body renders, which is well outside this change.

Automated validation

Full browser walkthrough of all 11 showcase steps plus the checklist on the local dev server:

  • "Do it for me" is present on step 1 with no idle wait, and on every subsequent step; clicking it performs the lesson action and advances. The three-button footer (Do it for me / Previous / Skip) does not crowd.
  • Step 7 shows only the Tab chip on entry; pressing Tab swaps the row to Shift + down-arrow. Body reads "the up or down arrow".
  • Step 9 body renders "Press Cmd+Z ... then Cmd+Shift+Z" on macOS.
  • Step 11 shows only "Enter Compass", with no assist button.
  • After graduating: checklist header reads "Practice on sample events", the item reads "Place a new event on the grid", undo shows the Cmd chip, and the sign-up row renders as a filled accent pill. Clicking it opens the sign-up modal.
  • Console showed no errors attributable to this change.

Regression-guard check: temporarily reverting the phased-hint conditional makes the new ShortcutShowcase test fail, then restoring it makes it pass, confirming the test protects real behavior rather than asserting a tautology.

After the refactor commit, the showcase was walked again in the browser from the welcome modal through step 7, confirming the hint still shows Tab alone and swaps to Shift + down-arrow on Tab, and the checklist card still renders identically.

Independent review

A fresh read-only reviewer was run against the final two-commit diff, given the worktree, base ref, task intent, and AGENTS.md, but not the implementing agent's conclusions. It was pointed specifically at the refactor commit, since that restructured code after the first commit had already been validated.

Result: no confirmed defects. It independently reached the same conclusions I verified by hand on the two paths worth worrying about:

  • keycaps can never be truthy for a step that declares none. isStretchPhase requires stepId === "resizeEdge", which does have keycaps, so graduation still renders no chip row.
  • Dropping the unreachable graduation arm from the assist handler is not just safe but restores the original semantics: falling through to advance() calls finish() on the last step, which is exactly what that arm used to do.

It also confirmed the checklist's ul/li structure and the sign-up button's accessible name survive the restructure, that showStep() does not mask a regression (the component depends only on isActive/stepIndex, and beforeEach already resets what start() would), and that the keymap parity assertion is still truthful about the phase it covers.

An earlier reviewer on the first commit was likewise clean. Separately, a four-angle cleanup pass (reuse, simplification, efficiency, altitude) produced the refactor commit above; its two remaining findings are the ones recorded as skipped under Simplicity.

Test plan

bun run test:web          # 2188 pass, 0 fail
bun run type-check        # clean
bun run lint              # 11 pre-existing warnings, none in changed files
bun run verify            # all checks passed (includes a11y e2e, 7 passed)
bunx playwright test e2e/onboarding/   # 3 passed

Two tests added to ShortcutShowcase.test.tsx: the assist button's immediate availability and graduation swap, and the two-phase keycap hint.

The showcase's assist button appeared only after 15s idle or two stray
keypresses, so its arrival read as arbitrary, and "Show me" did not say
that clicking it performs the action and advances. It is now always
offered and labelled "Do it for me".

Step 7 flattened a sequence and a chord into one keycap row, which reads
as a single three-key press. The row now shows Tab alone until the end
edge has focus, then swaps to the stretch chord; the copy also names the
up and down arrows, since left and right do not stretch.

Also: resolve Cmd/Ctrl in step 9's prose instead of leaking the raw "Mod"
token, call the practice events "sample" rather than "real" since they
are never saved, replace the "drop" verb for placing an event, and style
the checklist's sign-up row as a real CTA instead of a read-only row.
Name the stretch phase instead of branching inside a JSX prop, and keep
its keycaps in the steps module beside the rest of the lesson content,
with the note explaining why that arrow stays literal: stretching reuses
the Shift+Arrow family, and the arrow shows one direction of it.

Drop the now-unreachable graduation arm of the assist handler, build the
checklist's sign-up CTA through an early return so the row fragment is
no longer assembled and discarded, and let the list item own its spacing.

Give the tests one helper for jumping to a lesson rather than two ways of
writing the same setup.
@tyler-dane
tyler-dane merged commit 5e5bbe2 into main Aug 12, 2026
27 of 28 checks passed
@tyler-dane
tyler-dane deleted the claude/onboarding-ux-improvements-c6e80c branch August 12, 2026 23:32
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