Skip to content

feat(web): first-run onboarding — intro overlay + 60-second worktree game + workspace echoes - #123

Draft
danyaberezun wants to merge 22 commits into
mainfrom
workspace-learning-game
Draft

feat(web): first-run onboarding — intro overlay + 60-second worktree game + workspace echoes#123
danyaberezun wants to merge 22 commits into
mainfrom
workspace-learning-game

Conversation

@danyaberezun

Copy link
Copy Markdown
Collaborator

What

Users unfamiliar with git worktrees hit three surprises in ThinkRail: uncommitted/untracked files don't carry into a new workspace, the environment (node_modules, .env) must be rebuilt, and the workspace lives in a different folder. This PR ships the education third of the product answer (#105's Default workspace removes the forced strangeness; #93's hooks automate the environment): a first-run intro overlay hosting a 60-second worktree prediction game, plus two just-in-time echoes at the moments confusion actually strikes.

  • The game (5 beats, predict → reveal → why, skippable everywhere): what carries over (tap-select; copies fly across while originals stay put and pulse), where it lives (filesystem-tree reveal), will it run (pivot to hooks + a CSS-only lifecycle mini-movie: create → fresh folder → onCreate hooks → you work → onDelete cleanup), one-repo-many-folders, and the payoff. Canned demo repo — deterministic, zero wire deps. Fate-based colors (gold = stays, green = arrives), neutral predict pills so color never leaks answers; prefers-reduced-motion renders everything statically.
  • Echo 1: the New-Workspace dialog gains a quiet "stays behind" line with live project-root dirty counts (new git.projectStatus wire method) and an .env hooks nudge. Fail-soft: no spinner, never gates Create.
  • Echo 2: a dismiss-once path banner on worktree workspaces, deep-linking the game ("How it works").

How

  • contractsAppConfig.onboarding (two ISO timestamps: introSeenAt, workspaceBannerDismissedAt), git.projectStatus { projectId } → GitStatus, protocol 15.
  • serverprojectStatus (project root vs HEAD + untracked; shares collectChanges with gitStatus); settings reads became per-request (cache removed) to match the file-seeded e2e isolation doctrine.
  • web — new bounded module apps/web/src/onboarding/ (SPEC-first) absorbing Onboarding technical handoff #113's overlay: the "Isolated git worktrees" feature card's media placeholder becomes the live game entry. Seen-state is host-synced in AppConfig (a per-device flag would re-block every new browser — incl. the V2 phone) with a one-time localStorage→config migration for Onboarding technical handoff #113-era devices; onboarding config writes are serialized client-side (shallow server merge × two writers). Cross-client auto-dismiss included.
  • e2e — suite seeding switched from Playwright storageState to canonical config.json seeding in global-setup/resetState; onboarding specs cover virgin first-run, persistence, migration, full 5/5 game playthrough, skip semantics, both echoes, banner dismiss persistence.

Verification

  • Full gate at HEAD: check:deps · lint · typecheck (10/10) · test (568) · e2e 63/63 (no-agent suite).
  • Built via 12 spec-driven tasks, each with an independent implementation + adversarial task review (fix loops included); final whole-branch review verdict: ready to merge. Design spec: apps/web/src/onboarding/SPEC.md (status: active — truth-checked against shipped code in the final pass).

Notes for review

Julia.Shilova and others added 22 commits July 24, 2026 01:01
…ng-overlay-setup (d467f72)

Final-state transfer per the onboarding technical handoff (§12 squash-copy):
- New: panels/Onboarding.tsx, store/onboardingStorage.ts, e2e/onboarding.spec.ts,
  e2e/fixtures/onboarding-seen.json (verbatim from d467f72)
- Patched: dialog.tsx (overlayClassName prop), appStore.ts (onboarding open-mode slice),
  index.css (--animate-fill keyframe), Shell.tsx (mount), ProjectTree.tsx (help-button
  re-open trigger), playwright.config.ts (seed the seen flag via storageState)
- SPEC.md onboarding prose carried over (panels, shell, store, components/ui)
…us steals can't dismiss it

The xterm terminal focuses itself when its async init lands (lazy chunk + visible-effect
focus()); if that landed after a workspace-remove confirm opened, Radix's non-modal popover
dismissed on focus-outside — a self-closing confirm for users and a pre-existing e2e race
(workspace-lifecycle removal failed 3/5 at clean HEAD, near-deterministic once onboarding
shifted bundle timing). Esc + pointer outside-click still cancel, per the documented contract.
…ch rule, tree-view beat, lifecycle mini-movie, fate-based reveal colors
… localStorage migration + e2e config seeding
…-spec rule)

FoldersBoard/reveals/WorktreeGame hardcoded the folder headers, fate tags,
reveal prose, and prompt scaffold instead of sourcing them from content.ts,
violating the onboarding module SPEC's "all game copy lives in content.ts"
rule. Move them into new BOARD_COPY / REVEAL_COPY / PROMPT_FRAME constants;
components now only compose JSX around them. Rendered text verified
byte-for-byte unchanged via a before/after Playwright text dump.
… deep-links the game

Add WorktreeBanner, mounted in Shell above the editor for the active
workspace: a one-time "you are in a separate folder" cue gated on
AppConfig.onboarding.workspaceBannerDismissedAt, dismissible or
deep-linking into the onboarding game's review mode. Fix the center
column's flex layout in Shell.tsx (min-h-0 flex-1 wrapper around the
editor ErrorBoundary) so the banner's height no longer overflows and
gets clipped by the resizable panel's overflow:hidden.

Also serialize onboarding config writes in state.ts (two-writer
compose race): markIntroSeen and markBannerDismissed both read-modify-
write the shallowly-merged onboarding config, so two near-simultaneous
writes could drop one another's flag. Chain writes through a
module-level promise and fold each settings.update response in via
applyConfig immediately, instead of waiting on the settings.changed
broadcast.

Extend e2e/onboarding.spec.ts with a "first-worktree banner" spec
covering show-until-dismissed and host-persisted dismissal, and
document WorktreeBanner in panels/SPEC.md.
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