22 KiB
Phase 2D — Onboarding Chain · Build Plan
Scope: the day-0 onboarding/first-run chain (PRD §12.12 / DoD #6). Execution model is LOCKED: in-place incremental refactor of
apps/web+ targeted sidecar extension. PRD acceptance criteria win over mockups (PRD §24). Every claim below is grounded in live source underD:/Projects/waggle-os-ux-refactor(paths cited inline).Critical reuse note: Harvest preview-classification (C33 unreviewed + B2 confidence + B6 kind) was already built in Phase 2B.3 (
harvest-classify.ts,harvest.tspreview/commit,harvest-kind-map.ts) and the Memory Center "Needs review" review surface was built in Phase 2B-FE.2 (MemoryCenterTab.tsx). 2D reuses these; it does NOT rebuild classification, confidence, or the review queue.
1. CURRENT vs TARGET
Current OnboardingWizard (8 steps, apps/web/src/components/os/overlays/OnboardingWizard.tsx)
STEP_NAMES (OnboardingWizard.tsx:35):
welcome → why-waggle → tier → memory-import → template → persona → api-key → ready (steps 0–7).
| Step | Component | Wired vs hardcoded |
|---|---|---|
| 0 welcome | WelcomeStep |
Hardcoded brand copy; 3s auto-advance (:136-141). |
| 1 why-waggle | WhyWaggleStep |
Hardcoded VALUE_PROPS (constants.ts:111); "Skip and set me up" escape hatch (:271-304). |
| 2 tier | TierStep |
selectedTier → onUpdate({tier}) (UI density, not billing). |
| 3 memory-import | ImportStep |
Wired: harvestPreview/harvestCommit/scanClaudeCode (:156-202). Commits inline then goToStep(4). |
| 4 template | TemplateStep |
Hardcoded 15 TEMPLATES (constants.ts:10), TEMPLATE_PERSONA map. |
| 5 persona | PersonaStep |
ALL_ONBOARDING_PERSONAS (constants.ts:46) + custom-persona create. |
| 6 api-key | ModelTierStep |
Model/key; calls handleFinish → adapter.createWorkspace (:306-354). |
| 7 ready | ReadyStep |
Auto-finish after 2s. |
State backbone: useOnboarding.ts OnboardingState (:10-19) = { completed, step, tier?, workspaceId?, apiKeySet?, templateId?, personaId?, tooltipsDismissed? }, localStorage key waggle:onboarding. Resume is
half-built: state.step persists; returning-user auto-complete via Tauri flag + getWorkspaces().length>0
(:102-176). ?forceWizard=true (DEV) forces step 0.
There is NO profile/"Who Are You" step, NO Tool Discovery step. The current IA (tier/api-key/model) is the OLD onboarding, not the PRD §12.12 flow.
Target — PRD §12.12 5-step chain (S12–S17)
PRD order: First Launch (S12) → Who Are You (S13) → Tool Discovery (S14) → Memory Import (S15) →
Memory Review (S16) → Workspace Creation (S17) → Home Cockpit. Per the ratified C33 decision, Memory
Review (S16) is NOT a blocking onboarding step — import commits immediately as status:'unreviewed',
and review happens later in the Memory Center "Needs review" filter / J08 queue. So the in-wizard chain is
effectively 5 interactive steps: First Launch → Who Are You → Tool Discovery → Memory Import →
Workspace Creation, with a Ready/handoff terminal.
Target step mapping (keep the existing wizard shell + AnimatePresence switch; re-key STEP_NAMES):
0 first-launch (S12) rework WelcomeStep
1 who-are-you (S13) NEW WhoAreYouStep ← B8 profile + identity seed
2 tool-discovery (S14) NEW ToolDiscoveryStep (DEFERRABLE — see §6 note)
3 memory-import (S15) rework ImportStep — commit-as-unreviewed (C33)
4 workspace-create (S17) route through CreateWorkspaceDialog step bodies (or inline)
5 ready keep ReadyStep
S16 Memory Review is delivered by the already-shipped Memory Center "Needs review" filter (
MemoryCenterTab.tsx:25-31), NOT a wizard step. This is the C33 ratification (below).
Explicit gate ratifications (verbatim, from deltas/open-questions.md)
- B8 (
open-questions.md:28, :350): "Onboarding writes profile AND seeds theidentitytable." / "Write profile + seed identity in the same onboarding commit. Rationale: prevents the two-store drift… makes the Phase-1 Home greeting correct." [BLOCKS P1 greeting] - C33 — RESOLVED to the middle path (
open-questions.md:45-53): "Commit-as-unreviewed, non-blocking review: onboarding Import commits immediately (memory feels alive on first run), but frames land withstatus:'unreviewed'+ the B2 confidence score; Review is a non-blocking curation surface (Memory Center "needs review" filter + the standing J08 queue), NOT a blocking onboarding step… Supersedes the original §C C33 'blocking split' recommendation." - B2 (
:39): "cheap heuristic at preview (source-trust × adapter-type × dedup), persisted inmetadata… LLM scoring reserved for the standing J08 queue." - B6 (
:41): "PRD §15.2 canonical in@waggle/shared; pure harvest + display-category mapping helpers. Drop FEevent/insightdrift." - C28 (
:420): language selector → "static disabledEnglish (US)chip (no i18n exists…)." - C29 (
:422): "drop the auto-advance" on the first-launch privacy screen. - C30 (
:424): "keep Work type as a distinct personalization signal; don't re-ask what the template already implies." - C31 (
:426): Tool Discovery id-space → "namespaced scheme (connector:gmail/tool:cursor)." - C32 (
:428): "pre-select fromGET /api/tools/detectwith a visible 'detected' badge." - C34 (
:433): Hermes/Codex/Cursor tiles → "render asupload/'Other' file pickers or 'coming soon' v1." - C35 (
:435): Workspace Creation → "record connector/MCP ids as workspace intent on the config; do NOT run install/OAuth at create time." - C6 (
:373):WorkspaceType= "project | client | research | personal(+team/organizationreserved);typecoexists with the free-stringgroup."
2. B8 — Identity seed ("capture who-you-are + greet by name")
What already exists
- Profile store (
profile.ts):UserProfile(:41-94) carriesname/role/company/industry/bio+ writingStyle/brand/interests/language/timezone.GET /api/profile(:162) +PUT /api/profile(:167, partial-merge allow-list:172-196). On save it ALSO mirrors aUser identity: …P/I frame to personal memory (:201-224). Stored inprofile.json(file, no SQLite). Adapter:getProfile/updateProfile(adapter.ts:1357-1365, typedany). - Identity table (
identity.tsroute +hive-mind-core/src/mind/identity.tsIdentityLayer): single-row per-mindidentitytable (name/role/department/personality/capabilities/system_prompt).GET /api/identity(:62) +POST /api/identityupsert (:104-156).IdentityLayerexposescreate/get/exists/update/toContext(identity.ts:18-73). This is what backs the Home greeting name viaadapter.getIdentity()(adapter.ts:742). - UserProfileApp (
apps/web/.../apps/UserProfileApp.tsx, "My Profile") already renders a "Who Are You?" Identity tab capturing Name/Role/Company/Industry/Bio withINDUSTRIES+ chip-toggle patterns — the reuse target for the form fields.
The GAP to close (B8)
- No
setIdentityadapter method.adapter.getIdentity()exists (:742) but there is noadapter.setIdentity(...)callingPOST /api/identity. → ADD it. - Onboarding never writes profile or identity. The wizard has no profile step;
OnboardingStatehas no profile fields. → NEWWhoAreYouStepposts toPUT /api/profileANDPOST /api/identityin one save. - 3 new profile fields (
workType?,teamSize?,goals?: string[]) are net-new onUserProfile(S13 §5).PUT /api/profileallow-list (:172-196) must be EXTENDED to merge them;DEFAULT_PROFILE(:96-135) extended; FEUserProfile/types updated. - Identity-seed payload: map profile → identity fields:
name→name,role→role,industry→department(or leave department blank), optionallybio→personality.system_promptleft empty (the agent path already injects the profile frame). No schema change —IdentityLayer.create/updatealready upserts.
Backend substrate: profile.json + the existing identity table. No .mind migration.
3. C33 — Import → review (the EXACT existing contract to reuse)
Existing harvest preview/commit contract (Phase 2B.3 — DO NOT rebuild)
POST /api/harvest/preview (harvest.ts:227-258). Request { data, source }. Response:
{
source: ImportSourceType,
itemCount: number, // true total
types: Record<string, number>, // count by ImportItemType
preview: ClassifiedItem[], // back-compat: first 10
items: ClassifiedItem[], // NEW: full list (capped at PREVIEW_ITEM_CAP=5000)
itemsTruncated: boolean,
}
// ClassifiedItem = { id, title, type, source, kind: MemoryKind, confidence: number } (harvest.ts:239-246)
kind from importItemTypeToMemoryKind (harvest-classify.ts:32); confidence (0–100) from
harvestConfidence (harvest-classify.ts:66, B2 heuristic = source-trust × item-type, no LLM).
POST /api/harvest/commit (harvest.ts:265-582). Request:
{ data?, source?, resumeFromRun?, selectedIds?: Array<string|number> } // harvest.ts:266-275
selectedIdspresent + non-empty → filters items to that subset (:344-347); absent → commits ALL (the C33 onboarding default).- Each committed frame is stamped (
:449-456), guarded on empty metadata, with:→ exactly the C33 "commit-as-unreviewed" behavior. Response carries{ kind: importItemTypeToMemoryKind(item.type), confidence: harvestConfidence(item), status: 'unreviewed', sourceId: item.id }saved,cognifySkippedReason,wikiSkippedReason(no_real_embedder),runId, etc.
The review surface already exists. MemoryCenterTab.tsx (Phase 2B-FE.2) has a "Needs review"
status filter (:25-31, value:'unreviewed') + MemoryCard renders an "Needs review" badge
(MemoryCard.tsx:18). So S16's review is satisfied by Memory Center — 2D does not build a Review step.
The GAP the wizard must close
- Adapter
harvestCommitcannot passselectedIds. Current signatureharvestCommit(data, source)(adapter.ts:1898-1901) — the body omitsselectedIds. → WIDEN toharvestCommit(data, source, opts?: { selectedIds?, resumeFromRun? }). (Backend already accepts it.) harvestPreview/harvestCommittypedany(adapter.ts:1893,1898) — tighten to the sharedHarvestPreview/HarvestCommitResultshapes (repo bansany; CLAUDE.md §10 —no-explicit-anyis error).ImportStepreadsresult.preview(first 10), not the classifieditems[]with kind/confidence (OnboardingWizard.tsx:164setsimportPreviewfromresult.preview). → rework to consumeitems[]and render kind chips + confidence so the user sees what is being imported (matches S15 §4). Selection is OPTIONAL per C33 (default = import all as unreviewed); if a "deselect" affordance is added it postsselectedIds. Minimum viable 2D = keep commit-all; just surface kind/confidence + the "you can review later in Memory Center" pointer.ImportStepProps.importPreviewisreadonly unknown[](types.ts:39-40) — widen to the classified item type.
No backend change is required for C33 — the contract shipped in 2B.3. 2D is FE wiring only here.
4. S17 — Workspace creation: exists vs 2D adds
Exists
- Backend
POST /api/workspaces(workspaces.ts:162-230): acceptsname/group/icon/model/personaId/ templateId/storageType/storagePath/teamId/...; enforces tier workspace-limit (403,:194-203); auto-creates storage structure + starter skills + template starter-memory.WorkspaceManager.createwritesworkspace.json(no SQLite). - Adapter
createWorkspace(adapter.ts:253-260): bridgespersona→personaId. - Onboarding already creates a workspace via
handleFinish/handleSkipSetup(OnboardingWizard.tsx:278-326) — bespokeadapter.createWorkspacecalls, NOT the dialog. CreateWorkspaceDialog.tsx(1129 LOC): the full standalone modal (template/persona/storage/folder picker). The richer S17 wizard (type picker, suggested capabilities, summary card) is a separate S17 card — most of that is the standalone-modal rework, not the onboarding embed.
What 2D adds (minimal, onboarding-scoped)
2D needs a workspace-create step in the chain, not the full S17 modal rework. Minimal:
- Reuse the existing
handleFinishcreate path (name + template + persona) OR render a trimmed type-picker + name. Per C6 addtype?: WorkspaceType(project|client|research|personal) coexisting withgroup; per C35 record any selected connector/MCP ids as intent (config fields), not installs. - Backend: EXTEND
POST /api/workspacesBody +WorkspaceManager.createwith the additive optional V2 fields (description?, type?, status?, agentIds?, connectorIds?, mcpIds?) — JSON file, no migration. (This field extension is shared with the Phase-0WorkspaceConfigV2 work and the standalone S17 card.) - Adapter
createWorkspacebody type widened to carrydescription/type(+ capability-id arrays if used).
The full stepped S17 wizard (Suggested Skills/Agents/Connectors/MCPs panels + summary card) is the standalone S17 card's scope. 2D's workspace-create is the onboarding-embedded variant; keep it thin and route it through the SAME create call so onboarding and the modal converge later (S17 §4 parity note).
5. WHAT ALREADY EXISTS (do-not-rebuild) vs NET-NEW
Do-not-rebuild (verified present)
| Capability | Where | Status |
|---|---|---|
Harvest preview w/ per-item kind+confidence |
harvest.ts:227-258 + harvest-classify.ts |
DONE (2B.3) |
Harvest commit w/ selectedIds + status:'unreviewed' stamp |
harvest.ts:265-456 |
DONE (2B.3) |
| B2 heuristic confidence | harvest-classify.ts:66 |
DONE |
B6 ImportItemType→MemoryKind (server) |
harvest-classify.ts:32 |
DONE |
| B6 FE display-category map | apps/web/src/lib/harvest-kind-map.ts |
DONE (2B.3) |
| Memory Center "Needs review" filter (the S16 review surface) | MemoryCenterTab.tsx:25-31, MemoryCard.tsx:18 |
DONE (2B-FE.2) |
Profile store + GET/PUT /api/profile + identity-frame mirror |
profile.ts |
EXISTS |
Identity table + GET/POST /api/identity + IdentityLayer |
identity.ts, mind/identity.ts |
EXISTS |
adapter.getProfile/updateProfile/getIdentity |
adapter.ts:1357,1362,742 |
EXISTS |
| Profile form fields/INDUSTRIES/chip-toggle to copy | UserProfileApp.tsx |
EXISTS |
| Claude-Code auto-detect + harvest | harvest.ts:831, wizard :182-202 |
EXISTS |
Connector catalog GET /api/connectors; tool-detect GET /api/tools/detect (S14) |
connectors.ts, tools.ts |
EXISTS |
POST /api/workspaces + tier-limit + starter skills |
workspaces.ts:162 |
EXISTS |
| Wizard shell (progress/dots/Back/Skip/AnimatePresence) | OnboardingWizard.tsx:399-575 |
EXISTS — keep |
Resume backbone (state.step persist) |
useOnboarding.ts |
EXISTS |
Net-new in 2D
- FE:
WhoAreYouStep.tsx(S13);ToolDiscoveryStep.tsx+lib/tool-recommendations.ts(S14, deferrable); reworkedWelcomeStep/FirstLaunchStep(S12); reworkedImportStep(S15 surface kind/confidence); workspace-create step (S17 thin);lib/onboarding-profile.ts(option constants +buildProfilePreview). - Adapter:
setIdentity(...)(new);harvestCommit3rdoptsarg; tightenharvestPreview/harvestCommittypes; widencreateWorkspacebody type. - Backend: EXTEND
PUT /api/profileallow-list (+workType/teamSize/goals) +DEFAULT_PROFILE+UserProfile; EXTENDPOST /api/workspaces+WorkspaceManager.createV2 fields. No new route, no.mindmigration. - Types:
OnboardingState+=profileSeeded?,toolsUsed?: string[](localStorage, additive); sharedWorkspaceType(C6) +HarvestPreview/HarvestCommitResult; FEUserProfile(+3 fields). - Wizard: re-key
STEP_NAMES, the step switch (:498-572), progress math (progressPct :396, dots:472,aria-valuemax :426), Back-range (:444), and thegoToStep(N)auto-advances.
6. Minimal sub-phase build plan (each independently tsc + test verifiable)
Verify gate per sub-phase (CLAUDE.md §2):
tscon touched packages (shared → hive-mind-core → server, thennpm run buildfor apps/web),npm run test -- --run,npm run lint. Build order: shared → hive-mind-core → core → agent → server.
2D.1 — Backend + shared + adapter (no UI)
Ordered, each file with the exact change + contract:
packages/shared/src/types.ts— addWorkspaceType = 'project'|'client'|'research'|'personal'(C6;team/organizationreserved). (MemoryKindalready canonical here from B6/2B.3 — do NOT redefine.)packages/server/src/local/routes/profile.ts—UserProfile(:41-94): addworkType?: string; teamSize?: string; goals?: string[].DEFAULT_PROFILE(:96-135): add the three defaults ('','',[]).PUTallow-list (:172-196): merge the three (if (updates.workType != null) …).- Identity-frame mirror string (
:205-210): optionally append role/industry (pure edit).
packages/server/src/local/routes/workspaces.ts— extend thePOSTBody (:163-181) + theWorkspaceManager.create(...)call (:223-230) with additive optionaldescription?, type?, status?, agentIds?, connectorIds?, mcpIds?(JSON, no migration). (Coordinate with Phase-0 V2 field work.)packages/hive-mind-core/src/workspace-manager.ts— add the same optional fields toWorkspaceConfig+CreateWorkspaceOptions; persist increate()/update()(stampupdatedAt). No DB migration.apps/web/src/lib/adapter.ts—- NEW
async setIdentity(body): Promise<IdentityResponse>→POST /api/identity(mirrorgetIdentity:742). harvestCommit(:1898): addopts?: { selectedIds?: Array<string|number>; resumeFromRun?: number }and include in the POST body.- Tighten
harvestPreview/harvestCommitreturn types to sharedHarvestPreview/HarvestCommitResult(dropany);getProfile/updateProfiletoUserProfile(dropany). createWorkspacebody type (:253): adddescription?, type?: WorkspaceType, connectorIds?, mcpIds?.
- NEW
Verify 2D.1: tsc shared + hive-mind-core + server (+ apps/web build for adapter); add/extend route tests
(profile merge of 3 fields; identity upsert; commit selectedIds filter — the last already covered by 2B.3
tests, just confirm green).
2D.2 — Frontend (the chain)
apps/web/src/lib/onboarding-profile.ts(NEW, pure +.test.ts) —WORK_TYPES,TEAM_SIZES,GOALSoption constants +buildProfilePreview(partial)(keeps the step thin).apps/web/src/lib/types.ts— promote sharedUserProfile(+workType/teamSize/goals); importWorkspaceType,HarvestPreview/HarvestCommitResultfrom@waggle/shared.apps/web/src/hooks/useOnboarding.ts—OnboardingState(:10-19): add additive optionalprofileSeeded?: boolean,toolsUsed?: string[](localStorage only).apps/web/.../onboarding/WelcomeStep.tsx(rework → S12): align copy; add staticEnglish (US)chip (C28) + privacy note; consumeuseOfflineStatus.OnboardingWizard.tsx:136-141: drop the 3s auto-advance (C29).apps/web/.../onboarding/WhoAreYouStep.tsx(NEW → S13/B8): Name/Role/Industry (reuseUserProfileAppINDUSTRIES/chips) + WorkType/TeamSize/Goals + live preview. On Continue:adapter.updateProfile(...)ANDadapter.setIdentity({name, role, department: industry}). Props{ profile, onChange, onContinue, onBack, saving }. AddWhoAreYouStepPropstoonboarding/types.ts.apps/web/.../onboarding/ToolDiscoveryStep.tsx(NEW → S14): grouped grid fromgetConnectors+detectTools(pre-check detected, C32); namespaced idsconnector:*/tool:*(C31); writestoolsUsed[]. DEFERRABLE — if descoping 2D, omit this step and renumber; S14 has zero backend and can land in a 2D follow-up. (Flag in the chain as optional.)apps/web/.../onboarding/ImportStep.tsx(rework → S15): consumeresult.items[](kind+confidence) notpreview[]; render kind chips +ConfidenceBadge(reusecomponents/ui/confidence-badge); add the "review anytime in Memory → Needs review" pointer (C33). Hermes/Codex/Cursor = upload/"Other"/coming-soon (C34). Keep commit-all default; commit still landsunreviewedserver-side. WidenImportStepProps.apps/web/.../onboarding/workspace-create step (S17 thin): trimmed type-picker (C6) + name → existinghandleFinishcreate path; record connector/MCP intent ids (C35). (Full stepped S17 = separate card.)apps/web/.../overlays/OnboardingWizard.tsx— re-keySTEP_NAMES(:35) tofirst-launch/who-are-you/tool-discovery/memory-import/workspace-create/ready; rewrite the step switch (:498-572); fix progress math (:396, dots:472,aria-valuemax :426, Back-range:444); addhandleProfileSave; point import commit at the new step order; remove the dead tier/api-key/model steps from the chain (or keep behind a flag — surgical, prefer removal of the now-orphaned wiring).
Verify 2D.2: npm run build (apps/web tsc), npm run test -- --run (new onboarding-profile.test.ts
- any wizard tests),
npm run lint. Manual:?forceWizard=truewalks First Launch → Who Are You (writes profile+identity → Home greets by name) → [Tool Discovery] → Import (commits unreviewed; appears in Memory "Needs review") → Workspace Create → Home.
Largest single risk
The OnboardingWizard.tsx step-index rewire. It re-keys STEP_NAMES, the AnimatePresence step switch,
progress %, the dot array, aria-valuemax, the Back-button range, AND every goToStep(N) literal
(:174, :199, :300, :350 etc.) — plus removal of the orphaned tier/api-key/model handlers and their
step-local state (selectedTier/apiKey/keyValid/..., :47-60). An off-by-one or a stale goToStep(4)
strands the user mid-flow or skips identity-seed/commit. Mitigation: change the step set in ONE pass, drive
all navigation off STEP_NAMES.indexOf(name) (not magic numbers), keep an E2E that asserts the full chain
reaches Home, and verify resume (state.step) lands on the correct re-keyed step. This is exactly the churn
S13/S14/S15/S16 §7 all warn must be "rewired once, not per-card."