Files
waggle-os/docs/ux-refactor/2D-BUILD-PLAN.md
Oleg Maslov 0c3e2ead3b
Some checks failed
Installer Smoke / installer-smoke (push) Has been cancelled
moving
2026-09-02 10:10:29 +02:00

22 KiB
Raw Blame History

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 under D:/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.ts preview/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 07).

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 selectedTieronUpdate({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 handleFinishadapter.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 (S12S17)

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 the identity table." / "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 with status:'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 in metadata… LLM scoring reserved for the standing J08 queue."
  • B6 (:41): "PRD §15.2 canonical in @waggle/shared; pure harvest + display-category mapping helpers. Drop FE event/insight drift."
  • C28 (:420): language selector → "static disabled English (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 from GET /api/tools/detect with a visible 'detected' badge."
  • C34 (:433): Hermes/Codex/Cursor tiles → "render as upload/'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/organization reserved); type coexists with the free-string group."

2. B8 — Identity seed ("capture who-you-are + greet by name")

What already exists

  • Profile store (profile.ts): UserProfile (:41-94) carries name/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 a User identity: … P/I frame to personal memory (:201-224). Stored in profile.json (file, no SQLite). Adapter: getProfile/updateProfile (adapter.ts:1357-1365, typed any).
  • Identity table (identity.ts route + hive-mind-core/src/mind/identity.ts IdentityLayer): single-row per-mind identity table (name/role/department/personality/capabilities/system_prompt). GET /api/identity (:62) + POST /api/identity upsert (:104-156). IdentityLayer exposes create/get/exists/update/toContext (identity.ts:18-73). This is what backs the Home greeting name via adapter.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 with INDUSTRIES + chip-toggle patterns — the reuse target for the form fields.

The GAP to close (B8)

  1. No setIdentity adapter method. adapter.getIdentity() exists (:742) but there is no adapter.setIdentity(...) calling POST /api/identity. → ADD it.
  2. Onboarding never writes profile or identity. The wizard has no profile step; OnboardingState has no profile fields. → NEW WhoAreYouStep posts to PUT /api/profile AND POST /api/identity in one save.
  3. 3 new profile fields (workType?, teamSize?, goals?: string[]) are net-new on UserProfile (S13 §5). PUT /api/profile allow-list (:172-196) must be EXTENDED to merge them; DEFAULT_PROFILE (:96-135) extended; FE UserProfile/types updated.
  4. Identity-seed payload: map profile → identity fields: name→name, role→role, industry→department (or leave department blank), optionally bio→personality. system_prompt left empty (the agent path already injects the profile frame). No schema change — IdentityLayer.create/update already 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 (0100) 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
  • selectedIds present + 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:
    { kind: importItemTypeToMemoryKind(item.type), confidence: harvestConfidence(item),
      status: 'unreviewed', sourceId: item.id }
    
    → exactly the C33 "commit-as-unreviewed" behavior. Response carries 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

  1. Adapter harvestCommit cannot pass selectedIds. Current signature harvestCommit(data, source) (adapter.ts:1898-1901) — the body omits selectedIds. → WIDEN to harvestCommit(data, source, opts?: { selectedIds?, resumeFromRun? }). (Backend already accepts it.)
  2. harvestPreview/harvestCommit typed any (adapter.ts:1893,1898) — tighten to the shared HarvestPreview/HarvestCommitResult shapes (repo bans any; CLAUDE.md §10 — no-explicit-any is error).
  3. ImportStep reads result.preview (first 10), not the classified items[] with kind/confidence (OnboardingWizard.tsx:164 sets importPreview from result.preview). → rework to consume items[] 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 posts selectedIds. Minimum viable 2D = keep commit-all; just surface kind/confidence + the "you can review later in Memory Center" pointer.
  4. ImportStepProps.importPreview is readonly 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): accepts name/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.create writes workspace.json (no SQLite).
  • Adapter createWorkspace (adapter.ts:253-260): bridges personapersonaId.
  • Onboarding already creates a workspace via handleFinish/handleSkipSetup (OnboardingWizard.tsx:278-326) — bespoke adapter.createWorkspace calls, 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 handleFinish create path (name + template + persona) OR render a trimmed type-picker + name. Per C6 add type?: WorkspaceType (project|client|research|personal) coexisting with group; per C35 record any selected connector/MCP ids as intent (config fields), not installs.
  • Backend: EXTEND POST /api/workspaces Body + WorkspaceManager.create with the additive optional V2 fields (description?, type?, status?, agentIds?, connectorIds?, mcpIds?) — JSON file, no migration. (This field extension is shared with the Phase-0 WorkspaceConfig V2 work and the standalone S17 card.)
  • Adapter createWorkspace body type widened to carry description/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); reworked WelcomeStep/FirstLaunchStep (S12); reworked ImportStep (S15 surface kind/confidence); workspace-create step (S17 thin); lib/onboarding-profile.ts (option constants + buildProfilePreview).
  • Adapter: setIdentity(...) (new); harvestCommit 3rd opts arg; tighten harvestPreview/harvestCommit types; widen createWorkspace body type.
  • Backend: EXTEND PUT /api/profile allow-list (+workType/teamSize/goals) + DEFAULT_PROFILE + UserProfile; EXTEND POST /api/workspaces + WorkspaceManager.create V2 fields. No new route, no .mind migration.
  • Types: OnboardingState += profileSeeded?, toolsUsed?: string[] (localStorage, additive); shared WorkspaceType (C6) + HarvestPreview/HarvestCommitResult; FE UserProfile (+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 the goToStep(N) auto-advances.

6. Minimal sub-phase build plan (each independently tsc + test verifiable)

Verify gate per sub-phase (CLAUDE.md §2): tsc on touched packages (shared → hive-mind-core → server, then npm run build for 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:

  1. packages/shared/src/types.ts — add WorkspaceType = 'project'|'client'|'research'|'personal' (C6; team/organization reserved). (MemoryKind already canonical here from B6/2B.3 — do NOT redefine.)
  2. packages/server/src/local/routes/profile.ts
    • UserProfile (:41-94): add workType?: string; teamSize?: string; goals?: string[].
    • DEFAULT_PROFILE (:96-135): add the three defaults ('', '', []).
    • PUT allow-list (:172-196): merge the three (if (updates.workType != null) …).
    • Identity-frame mirror string (:205-210): optionally append role/industry (pure edit).
  3. packages/server/src/local/routes/workspaces.ts — extend the POST Body (:163-181) + the WorkspaceManager.create(...) call (:223-230) with additive optional description?, type?, status?, agentIds?, connectorIds?, mcpIds? (JSON, no migration). (Coordinate with Phase-0 V2 field work.)
  4. packages/hive-mind-core/src/workspace-manager.ts — add the same optional fields to WorkspaceConfig + CreateWorkspaceOptions; persist in create()/update() (stamp updatedAt). No DB migration.
  5. apps/web/src/lib/adapter.ts
    • NEW async setIdentity(body): Promise<IdentityResponse>POST /api/identity (mirror getIdentity :742).
    • harvestCommit (:1898): add opts?: { selectedIds?: Array<string|number>; resumeFromRun?: number } and include in the POST body.
    • Tighten harvestPreview/harvestCommit return types to shared HarvestPreview/HarvestCommitResult (drop any); getProfile/updateProfile to UserProfile (drop any).
    • createWorkspace body type (:253): add description?, type?: WorkspaceType, connectorIds?, mcpIds?.

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)

  1. apps/web/src/lib/onboarding-profile.ts (NEW, pure + .test.ts) — WORK_TYPES, TEAM_SIZES, GOALS option constants + buildProfilePreview(partial) (keeps the step thin).
  2. apps/web/src/lib/types.ts — promote shared UserProfile (+ workType/teamSize/goals); import WorkspaceType, HarvestPreview/HarvestCommitResult from @waggle/shared.
  3. apps/web/src/hooks/useOnboarding.tsOnboardingState (:10-19): add additive optional profileSeeded?: boolean, toolsUsed?: string[] (localStorage only).
  4. apps/web/.../onboarding/WelcomeStep.tsx (rework → S12): align copy; add static English (US) chip (C28) + privacy note; consume useOfflineStatus. OnboardingWizard.tsx:136-141: drop the 3s auto-advance (C29).
  5. apps/web/.../onboarding/WhoAreYouStep.tsx (NEW → S13/B8): Name/Role/Industry (reuse UserProfileApp INDUSTRIES/chips) + WorkType/TeamSize/Goals + live preview. On Continue: adapter.updateProfile(...) AND adapter.setIdentity({name, role, department: industry}). Props { profile, onChange, onContinue, onBack, saving }. Add WhoAreYouStepProps to onboarding/types.ts.
  6. apps/web/.../onboarding/ToolDiscoveryStep.tsx (NEW → S14): grouped grid from getConnectors + detectTools (pre-check detected, C32); namespaced ids connector:*/tool:* (C31); writes toolsUsed[]. 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.)
  7. apps/web/.../onboarding/ImportStep.tsx (rework → S15): consume result.items[] (kind+confidence) not preview[]; render kind chips + ConfidenceBadge (reuse components/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 lands unreviewed server-side. Widen ImportStepProps.
  8. apps/web/.../onboarding/ workspace-create step (S17 thin): trimmed type-picker (C6) + name → existing handleFinish create path; record connector/MCP intent ids (C35). (Full stepped S17 = separate card.)
  9. apps/web/.../overlays/OnboardingWizard.tsx — re-key STEP_NAMES (:35) to first-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); add handleProfileSave; 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=true walks 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."