# UX Phase 1 Corrections Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Remove the trust, coherence, responsive layout, shortcut, visual-regression, and route-evidence blockers that currently prevent the five-persona UX judge gate from honestly reaching 9/10. **Architecture:** Keep all fixes inside existing Waggle surfaces. Do not create new pages, flows, or abstractions unless a tiny local helper is needed to make an existing surface testable. Use current route shell, Settings app, auth provider, marketplace/visual tests, and judge artifacts as the proof path. **Tech Stack:** React 19, TypeScript, Vite, Tailwind 4, Fastify local sidecar, Vitest, Playwright. --- ## Source Artifacts - Findings: `docs/audits/2026-07-08-complete-ux-usage-audit.md` - Route/scenario manifest: `docs/audits/2026-07-08-ux-route-scenario-manifest.md` - Judge scorecards: `docs/audits/2026-07-08-five-persona-judge-scorecards.md` - Correction register: `docs/audits/2026-07-08-ux-correction-register.md` - Mobile Executive evidence supplement: `docs/audits/2026-07-08-mobile-executive-t2-t12-analysis.md` - First-run onboarding evidence supplement: `docs/audits/2026-07-08-first-run-onboarding-t1-t2-t12-analysis.md` ## Files by Responsibility - `packages/server/src/local/security-middleware.ts`: local CSP/security headers. - `packages/server/tests/local/security-middleware.test.ts`: CSP/security header assertions. - `apps/web/src/lib/clerk.ts`: Clerk publishable-key resolution and accountless decision point. - `apps/web/src/providers/WaggleClerkProvider.tsx`: optional Clerk mounting. - `apps/web/src/components/os/apps/SettingsApp.tsx`: Settings layout, copy, backup alerts, billing copy. - `apps/web/src/components/os/overlays/OnboardingWizard.tsx`: first-run wizard shell and mobile action row. - `apps/web/src/components/os/overlays/onboarding/WhoAreYouStep.tsx`: first-run Profile step density and Continue reachability. - `apps/web/src/components/os/AppShell.tsx`: active workspace resolution, route-changing overlay behavior, shortcut target. - `apps/web/src/hooks/useKeyboardShortcuts.ts`: `Ctrl+Shift+N` dispatch path. - `apps/web/src/components/os/overlays/WorkspaceSwitcher.tsx`: modal close behavior and route traversal interactions. - `apps/web/src/components/os/apps/MarketplaceApp.tsx`: Marketplace/MCP copy. - `apps/web/src/components/os/apps/mcp/AddCustomMcpForm.tsx`: custom MCP gating copy. - `apps/web/src/lib/command-catalog.ts`: Command Center group labels. - `apps/web/src/components/os/overlays/LoginBriefing.tsx`: login/team copy. - `apps/web/src/components/os/apps/skills/SkillRow.tsx`: skill verification copy. - `apps/web/src/components/os/apps/PaymentSuccessApp.tsx`: Teams success and legacy Pro copy boundary. - `tests/visual/views.spec.ts`: visual regression route list/readiness and baseline triage. - `tests/e2e/phase-ab-verification.spec.ts`: `Ctrl+Shift+N` and CSP console assertions. - `tests/e2e/power-user-stress.spec.ts`: shortcut stress assertion. - `tests/e2e/full-wiring-audit.spec.ts`: full traversal/console route behavior. - `tests/e2e/full-product-audit.spec.ts`: load console health. - `tests/e2e/user-journeys.spec.ts`: route smoke expansion for thin routes. - `docs/audits/2026-07-08-ux-route-scenario-manifest.md`: update route evidence statuses after fixes. - `docs/audits/2026-07-08-five-persona-judge-scorecards.md`: update readiness notes after fixes. ## Phase Rules - Keep work inside the files above unless a test exposes a direct local dependency. - Re-read a file immediately before editing it. - Write or adjust the failing test before the implementation for each task. - Do not update visual baselines until the rendered screenshots are reviewed. - Do not mark the five-persona gate ready until all P0 findings are closed. - If a task reveals unrelated pre-existing dirty files, leave them untouched. ## Task 1: Local Auth, Clerk, and CSP Console Health **Files:** - Modify: `packages/server/src/local/security-middleware.ts` - Modify: `packages/server/tests/local/security-middleware.test.ts` - Modify: `apps/web/src/lib/clerk.ts` - Modify: `apps/web/src/providers/WaggleClerkProvider.tsx` - Test: `tests/e2e/full-product-audit.spec.ts` - Test: `tests/e2e/phase-ab-verification.spec.ts` - [x] **Step 1: Add/adjust CSP test for strict local accountless mode** Add assertions in `packages/server/tests/local/security-middleware.test.ts` that document the accountless default: ```ts expect(csp).toContain("script-src 'self'"); expect(csp).not.toMatch(/script-src[^;]*clerk/i); expect(csp).not.toMatch(/connect-src[^;]*clerk/i); ``` Run: ```powershell npx vitest run packages/server/tests/local/security-middleware.test.ts ``` Expected before implementation review: current assertions pass for strict CSP, but rendered E2E still fails because client-side Clerk mounts when the local env contains a key. - [x] **Step 2: Make accountless local mode the default client behavior** In `apps/web/src/lib/clerk.ts`, keep `clerkPublishableKey()` shape validation, but add a local opt-in guard so the desktop/local audit lane does not mount Clerk just because `.env.local` contains `VITE_CLERK_PUBLISHABLE_KEY`. Use this behavior: ```ts function clerkEnabledForThisBuild(): boolean { return import.meta.env.VITE_WAGGLE_ENABLE_CLERK === '1'; } export function clerkPublishableKey(): string | undefined { if (!clerkEnabledForThisBuild()) return undefined; const k = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY; return k && isValidPublishableKey(k) ? k : undefined; } ``` Keep the existing invalid-key fallback. Update the surrounding comment so it says Clerk is enabled only when the publishable key is valid and `VITE_WAGGLE_ENABLE_CLERK=1`. - [x] **Step 3: Add a web unit test for accountless default** Create `apps/web/src/test/clerk-accountless.test.ts` if no equivalent exists. Test the exported function by stubbing `import.meta.env` in the same style used by nearby web tests. The test must assert: ```ts expect(clerkPublishableKey()).toBeUndefined(); ``` with a valid-looking key present and `VITE_WAGGLE_ENABLE_CLERK` absent. Run: ```powershell cd apps/web npx vitest run src/test/clerk-accountless.test.ts cd ../.. ``` Expected: pass after Step 2. - [x] **Step 4: Verify rendered console health** Run a fresh-port focused lane: ```powershell $env:WAGGLE_E2E_PORT='3391' $env:WAGGLE_E2E_BASE_URL='http://127.0.0.1:3391' $env:WAGGLE_E2E_SKIP_LITELLM='1' node node_modules/playwright/cli.js test tests/e2e/full-product-audit.spec.ts tests/e2e/phase-ab-verification.spec.ts --project=chromium --reporter=list ``` Expected after the task: no Clerk 429/load errors and no CSP failure caused by Clerk or the inline startup script. - [x] **Step 5: Verify clean-data first-run console health** Run or codify a no-skip first-run smoke with a fresh data dir. It must navigate to `/` without `skipOnboarding`, wait for the onboarding takeover, and capture console/page errors from navigation start. Minimum manual lane if no test exists yet: ```powershell $env:WAGGLE_PORT='3392' $env:WAGGLE_TRUST_LOCALHOST='1' $env:WAGGLE_DISABLE_MARKETPLACE_SYNC='1' $env:EMBEDDING_PROVIDER='mock' $env:VITE_CLERK_PUBLISHABLE_KEY='' $env:CLERK_SECRET_KEY='' $env:WAGGLE_DATA_DIR="$env:TEMP\waggle-first-run-auth-3392" npm run build npx tsx packages/server/src/local/start.ts --skip-litellm ``` Expected after the task: clean-data first-run onboarding has no Clerk/CSP/page errors, and the wizard still renders before shell chrome. **Completed 2026-07-08:** `clerkPublishableKey()` now requires `VITE_WAGGLE_ENABLE_CLERK=1` in addition to a valid key; the pre-hydration theme bootstrap moved from inline HTML to `/theme-boot.js` so local CSP keeps `script-src 'self'`; console-health E2E assertions now explicitly fail on Clerk/CSP noise. Verification: web auth Vitest 10/10, server security middleware Vitest 43/43, phase-ab initial-load console Playwright 1/1, full-product console Playwright 1/1, clean first-run onboarding Playwright 1/1, and `git diff --check`. ## Task 2: Mobile Settings and First-Run Onboarding Responsive Layout **Files:** - Modify: `apps/web/src/components/os/apps/SettingsApp.tsx` - Modify: `apps/web/src/components/os/overlays/OnboardingWizard.tsx` - Modify: `apps/web/src/components/os/overlays/onboarding/WhoAreYouStep.tsx` - Test: `apps/web/src/test/pr5-settings-reskin.test.tsx` - Test: add or extend Playwright mobile coverage in `tests/e2e/user-journeys.spec.ts` - [x] **Step 1: Add mobile Settings assertions that catch visible clipping** In `tests/e2e/user-journeys.spec.ts`, add a test that sets the viewport to `390 x 844`, opens `/settings`, `/settings?tab=models`, `/settings?tab=billing`, and `/settings/profile`, and asserts both no document-level horizontal overflow and no visible critical-control overflow. Do not rely on this check alone: ```ts document.documentElement.scrollWidth > document.documentElement.clientWidth ``` The fresh mobile smoke in `docs/audits/2026-07-08-mobile-executive-t2-t12-analysis.md` found clipped/squeezed controls while document scroll width stayed clean. Use a helper shaped like this: ```ts test('J-mobile: Settings is usable at 390px width', async ({ page }) => { await page.setViewportSize({ width: 390, height: 844 }); const routes = ['/settings', '/settings?tab=models', '/settings?tab=billing', '/settings/profile']; for (const route of routes) { await gotoApp(page, route); if (route !== '/settings/profile') { await expect(page.getByRole('tablist', { name: 'Settings sections' })).toBeVisible(); await expect(page.getByRole('tabpanel').first()).toBeVisible(); } else { await expect(page.locator('body')).toContainText(/profile|identity|save|writing style/i); } const documentOverflow = await page.evaluate( () => document.documentElement.scrollWidth > document.documentElement.clientWidth, ); expect(documentOverflow, `${route} document overflow`).toBe(false); const visibleOverflow = await page.locator( 'button:visible, [role="tab"]:visible, [role="tabpanel"]:visible, input:visible, select:visible, textarea:visible', ).evaluateAll(elements => elements .map(el => { const rect = el.getBoundingClientRect(); return { text: (el.textContent || el.getAttribute('aria-label') || el.getAttribute('placeholder') || el.tagName).trim(), left: Math.floor(rect.left), right: Math.ceil(rect.right), }; }) .filter(item => item.left < -1 || item.right > window.innerWidth + 1)); expect(visibleOverflow, `${route} visible control overflow`).toEqual([]); } }); ``` Run: ```powershell node node_modules/playwright/cli.js test tests/e2e/user-journeys.spec.ts --project=chromium --grep "Settings is usable at 390px" ``` Expected before implementation: fail on `/settings`, `/settings?tab=models`, or `/settings?tab=billing` because the current two-rail layout squeezes or clips visible controls even without document-level overflow. - [x] **Step 2: Add first-run mobile onboarding assertions** Add a no-skip mobile first-run assertion using a clean data dir or an isolated server fixture. At 390 x 844: - `/` renders the Onboarding Wizard, not shell chrome. - Welcome has no horizontal overflow. - After pressing Continue, the Profile step shows the progress/top bar, profile content, and primary Continue action without the action starting below the visible viewport, or the action is sticky/clearly reachable by design. - Console capture is shared with Task 1, so Clerk/CSP errors remain visible until T1 is fixed. Use `docs/audits/2026-07-08-first-run-onboarding-t1-t2-t12-analysis.md` as the red evidence source. The current trace puts the Profile Continue button bottom at `880` in an `844` px viewport. - [x] **Step 3: Replace the fixed Settings rail on narrow screens** In `SettingsApp.tsx`, replace the root/tabs layout classes: ```tsx