moving
Some checks failed
Installer Smoke / installer-smoke (push) Has been cancelled

This commit is contained in:
Oleg Maslov
2026-09-02 10:10:29 +02:00
commit 0c3e2ead3b
3841 changed files with 970576 additions and 0 deletions

View File

@@ -0,0 +1,187 @@
# Onboarding — Day-2 Backlog
**Authored:** 2026-04-30 (post-investigation `b34897c` + Option A2 implementation)
**Owner:** Engineering + Product
**Priority:** Day-2 (post-launch). Day-0 unblock = `?forceWizard=true` URL param shipped.
---
## Why this exists
The onboarding investigation (`docs/ONBOARDING-INVESTIGATION-2026-04-30.md`) surfaced three distinct issues that are best handled separately from the immediate launch:
1. **The auto-complete heuristic is too coarse.** `useOnboarding.ts:91-120` flips the wizard to "completed" whenever `/api/workspaces.length > 0`. But the backend ALWAYS creates `default-workspace` at boot via `wsManager.ensureDefault()`, so the heuristic fires even for genuinely-fresh installs.
2. **Production users have no "redo onboarding" affordance.** Once the wizard is completed (or auto-completed), there is no in-product way to re-trigger it short of editing `~/.waggle/` on disk — friction for support, demos, persona-evaluation marathons, and product education.
3. **The wizard's actual production behavior is undocumented.** Support team / new hires / partners need to know: "the wizard appears at most once per machine, even after browser cache clear, even after Tauri webview reset". That assumption isn't written down anywhere user-facing.
---
## A3 — Smarter auto-complete heuristic
**Current code (`apps/web/src/hooks/useOnboarding.ts:91-120`):**
```ts
const workspaces = await adapter.getWorkspaces();
if (Array.isArray(workspaces) && workspaces.length > 0) {
// auto-complete the wizard
}
```
**Problem:** `workspaces.length > 0` is true even when the only workspace is the boot-time stub from `wsManager.ensureDefault()`. So:
- Genuine first-launch → wizard would render… but `ensureDefault()` runs BEFORE the hook's first render, so wizard is auto-completed before the user ever sees it.
- Tauri webview switch on real user → correct (workspaces exist with content).
- localStorage clear on real user → correct (workspaces exist with content).
- localStorage clear after manual workspace deletion → wizard auto-completes again because `ensureDefault()` recreates the stub.
**Net effect:** the wizard only shows up if `~/.waggle/` doesn't exist at all when the backend boots. After that, it's invisible until a developer manually deletes the `default-workspace` directory (and even then, see Option A2 — backend recreates it). For 99%+ of installs, the wizard fires exactly **once** in the user's lifetime.
That might be desirable. But if not, the heuristic should be **content-aware**:
**Proposed:**
```ts
const [workspaces, health] = await Promise.all([
adapter.getWorkspaces(),
adapter.getSystemHealth().catch(() => null),
]);
if (cancelled) return;
// Treat "zero memory across all workspaces" as fresh-state regardless of stubs.
// The default-workspace shell from wsManager.ensureDefault has frameCount=0
// until the user actually does something. Distinguish that from a real
// returning user who has built up memory.
const frameCount = health?.memoryStats?.frameCount ?? 0;
const isReturningUser = Array.isArray(workspaces)
&& workspaces.length > 0
&& frameCount > 0;
if (isReturningUser) {
// auto-complete (existing logic)
}
```
**Side effects:**
- Genuine first-launch → wizard renders (frameCount=0).
- Tauri webview switch on real user → still auto-completes (frames > 0).
- localStorage clear on real user → still auto-completes (frames > 0).
- After "reset onboarding" (proposed below) clears memory → wizard renders again.
**Cost:** ~10 LOC, single file. One extra `/health` call per hook mount (cheap, idempotent).
**Risk:** if a returning user has somehow lost all memory (recovery-from-corruption, mind file deleted manually), they'd see the wizard again — but that's actually the right UX in that scenario.
**Test plan:**
- Add `apps/web/src/hooks/useOnboarding.test.ts` with mocked adapter:
- workspaces=[], frames=0 → wizard renders
- workspaces=[{id:'default-workspace'}], frames=0 → wizard renders (the new behavior)
- workspaces=[{id:'default-workspace'}], frames=5 → auto-completes
- workspaces=[], frames=0, sidecar throws → wizard renders (unchanged fallback)
**Status:** **deferred to Day-2.** The Option A2 URL bypass is sufficient for PM walkthrough. Production behavior with the current heuristic is "wizard fires once per fresh install" which is *plausibly* the right default. Worth a product call before changing.
---
## Reset Onboarding affordance
**Where it lives:** Settings → Advanced → "Reset onboarding wizard" (button).
**What it does:**
```ts
async function resetOnboarding() {
// 1. Confirm via dialog (destructive action — clears workspace + memory).
if (!await confirmDestructive('Reset onboarding will clear all workspaces and memory. Continue?')) return;
// 2. Backend: drop default-workspace + clear personal mind.
// New endpoint POST /api/admin/reset-onboarding does the disk wipe
// server-side (rm -rf workspaces/default-workspace, drop personal.mind,
// drop sessions/*, drop awareness/preferences). Atomic. Logs to audit.
await adapter.resetOnboarding();
// 3. Clear localStorage onboarding state.
localStorage.removeItem('waggle:onboarding');
localStorage.removeItem('waggle:tooltips_done');
// 4. Reload to ?forceWizard=true so the wizard renders even before the
// backend has a chance to re-ensureDefault.
window.location.assign(window.location.pathname + '?forceWizard=true');
}
```
**Use cases:**
- Demo prep: reset to clean state before showing the product.
- Support escalation: "let's start over" when the user's workspace is in a weird state.
- Persona-evaluation marathon: between persona swaps, reset to baseline.
- New-hire training: walk through the wizard as designed.
**Threat model:** destructive button. Must be:
- Behind Settings → Advanced (not surfaced casually).
- Confirmation dialog with strong copy ("This will erase your memory. Continue?").
- Requires the user to type "RESET" or click two distinct buttons (defense against fat-finger).
- Audit-logged via `auditStore` so it's traceable.
**Cost:** ~80 LOC across:
- `apps/web/src/components/os/apps/SettingsApp.tsx` — Advanced tab + button + dialog
- `apps/web/src/lib/adapter.ts``resetOnboarding()` method
- `packages/server/src/local/routes/admin.ts` — new `POST /api/admin/reset-onboarding` endpoint (or existing admin route)
- One vitest spec exercising the disk-side wipe
**Status:** **Day-2.** Not needed for launch. PM can use the URL bypass for walkthrough.
---
## Document the production behavior
**Audience:** Support team, customer success, product education content authors.
**Content (rough draft):**
> ### Onboarding wizard frequency
>
> The Waggle OS onboarding wizard appears **at most once per install**. After
> the user dismisses it (either via "Let's go!" on the Ready step or via
> the Skip option), the wizard does not re-trigger automatically.
>
> The wizard does NOT re-appear on:
> - Browser cache / localStorage clear (the backend has workspace data).
> - Tauri webview profile reset (same — the backend has workspace data).
> - Computer reboot (data persists in `~/.waggle/`).
> - App upgrade (data persists across versions).
>
> The wizard DOES re-appear on:
> - Fresh install on a new machine (no `~/.waggle/` directory exists).
> - User explicit "Reset onboarding" via Settings → Advanced (Day-2 feature, see backlog).
> - Developer URL bypass `?forceWizard=true` (DEV mode only).
>
> ### Why
>
> A returning user who clears their browser cookies or switches Tauri
> profiles still has all their memory and workspaces on the sidecar
> (`~/.waggle/`). Forcing them through the wizard again would be jarring
> ("why does Waggle want me to re-pick a template I already have?").
> The wizard's job is to teach + configure on the very first encounter;
> after that, the same surfaces are reachable through the normal UI
> (Settings, dock, persona switcher, harvest tab, etc.).
**Where it goes:**
- `docs/user-guide/onboarding.md` (or whatever the user-facing docs entry point is)
- Internal Notion page for support team
**Cost:** ~30 min of writing.
**Status:** **Day-2.** Not blocking launch. Should land before any large support cohort hits production (week 2-3 post-launch).
---
## Summary
| Item | Cost | Day-X | Notes |
|---|---|---|---|
| **A2 URL bypass** (this commit) | ~12 LOC + tests | Day-0 ✅ | Shipped. PM uses `?forceWizard=true` for walkthrough. |
| **A3 smarter heuristic** | ~10 LOC + 4 tests | Day-2 | Awaiting product decision: keep "once per machine" or move to "once until memory exists". |
| **Reset Onboarding button** | ~80 LOC + admin route + 1 test | Day-2 | Demo / support / training affordance. |
| **Document production behavior** | ~30 min writing | Day-2 | User-facing docs + support runbook. |
**No work scheduled today** beyond the A2 ship + this backlog doc. Marko reviews Day-2 ordering when launch dust settles.