Skip to content

feat: light, extensible first-run onboarding coachmarks (0206) - #209

Merged
crs48 merged 5 commits into
mainfrom
feat/onboarding-coachmarks
Jun 19, 2026
Merged

feat: light, extensible first-run onboarding coachmarks (0206)#209
crs48 merged 5 commits into
mainfrom
feat/onboarding-coachmarks

Conversation

@crs48

@crs48 crs48 commented Jun 19, 2026

Copy link
Copy Markdown
Owner

What

Light, lovely, non-blocking first-run onboarding — a few contextual tips that appear the first time you open a view, dismissible and never in the way. Implements exploration 0206.

Not a product tour. The research is lopsided (~70% skip linear tours; ~78% abandon by step three), so this is the modern coachmark pattern: one tip at a time, on first visit to a surface, Escape/✕/"Got it" to dismiss, replayable from Settings.

How it works

A declarative, plugin-extensible tip registry keyed by view. The "seen" set is persisted in the existing workbench store (xnet:workbench:v1) — the engine is a structural twin of the shipped What's-New plumbing. Zero new runtime dependencies (builds on the Base-UI Presence motion vocabulary + lucide-react + react-dom, all already present).

viewIdForPath(pathname) → useCoachmarks(view) → Coachmark (portal + Presence)
       route                registry +              non-modal card,
      segment            seenTips (store)          Esc / ✕ / "Got it"

The "grow with us" property: a feature or bundled plugin registers tips with contributeTips([...]) — the engine never changes. As the app pluginizes (0205), features bring their own onboarding.

Design choices

  • Non-modal by design. Deliberately not a Base-UI Popover — a coachmark must never trap focus or block the app. It portals a fixed-position card next to its anchor (the editor's CommentPopover pattern), so the user's first action is never hijacked.
  • One at a time, capped per session (≈2 new tips), so a long-dormant user reopening many views isn't buried.
  • Dormant until hasOnboarded() — never piles on top of the welcome/auth flow.
  • Reduced motion inherited from motion.css (no Framer Motion).
  • Fails silent — a tip whose anchor isn't in the DOM simply waits for a later visit.

Changes

  • apps/web/src/coachmarks/ — registry, useCoachmarks engine, Coachmark (+ story), useAnchorEl, view mapping, CoachmarkLayer, seed tips
  • workbench store: seenTips + markTipSeen/resetTips
  • Rail: data-coach anchors; Workbench mounts CoachmarkLayer (desktop + mobile)
  • Settings: Tips & tours → Replay onboarding
  • docs/ONBOARDING.md (how to add a tip) + changelog fragment

Seed tips

Command palette (⌘K) on Home, plus CRM, Tasks, and Discover — each anchored to its Rail icon.

Verification

  • 19 new unit tests (registry purity, view mapping, engine serialization + per-session cap + versioned re-surface + enabled-gate, store seen-state, component dismiss via Got it / ✕ / Escape). Full apps/web dom suite green (144 tests); full repo suite green (8840 passed).
  • Typecheck + eslint + prettier clean.
  • Browser-verified the Coachmark in Storybook (renders, positions to anchor, "Got it" dismisses end-to-end); app boots with no console errors. (The passkey gate can't be passed headlessly, so per-view in-app confirmation is via Storybook + unit tests.)

🤖 Generated with Claude Code

xNet Test and others added 5 commits June 19, 2026 08:15
Light, non-blocking, one-at-a-time tips shown the first time you open a
view. Declarative, plugin-extensible tip registry; 'seen' state persisted
in the workbench store; rail anchors; Settings → Replay onboarding.

- apps/web/src/coachmarks/: registry, useCoachmarks engine, Coachmark
  (non-modal portal + Presence motion), useAnchorEl, view mapping, seeds
- workbench store: seenTips + markTipSeen/resetTips (twin of lastSeenChangelogId)
- Rail: data-coach anchors; Workbench mounts CoachmarkLayer (desktop+mobile)
- Settings: 'Tips & tours' replay control
- 19 tests (registry, views, engine serialization/cap, component dismiss)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…0206)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@crs48
crs48 temporarily deployed to pr-209 June 19, 2026 15:44 — with GitHub Actions Inactive
@github-actions

Copy link
Copy Markdown
Contributor

🖼️ UI changes in this PR

Components

🆕 Web/Coachmark — Below Anchor

Web/Coachmark — Below Anchor

🆕 Web/Coachmark — Command Palette

Web/Coachmark — Command Palette

Screens

✏️ Settings _(SSIM 0.932)_
before after diff
before after diff

Auto-captured by CI · run. Informational — not a blocking check.

github-actions Bot added a commit that referenced this pull request Jun 19, 2026
@github-actions

github-actions Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Preview removed for PR #209.

github-actions Bot added a commit that referenced this pull request Jun 19, 2026
@crs48
crs48 merged commit 5f3322b into main Jun 19, 2026
13 checks passed
@crs48
crs48 deleted the feat/onboarding-coachmarks branch June 19, 2026 15:50
github-actions Bot added a commit that referenced this pull request Jun 19, 2026
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