Files
stack/docs/scratchpads/install-ux-v2-20260405.md
Jarvis d6117f1a4a
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
docs: scaffold install-ux-v2 mission + archive install-ux-hardening
Parent mission install-ux-hardening-20260405 shipped at mosaic-v0.0.25
but a real-run test uncovered a critical bootstrap regression plus a
cluster of first-run UX failings. New mission install-ux-v2-20260405
closes the regression as a hotfix (M01 → mosaic-v0.0.26), polishes
CORS/skill installer UX (M02), then rethinks the flow around a
drill-down main menu with a provider-first intent intake and real
quick-start (M03 → mosaic-v0.0.27).

Linked issues: #436 (M01), #437 (M02), #438 (M03)
Archives install-ux-hardening-20260405 under docs/archive/missions/

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-05 16:23:51 -05:00

6.7 KiB

Install UX v2 — Orchestrator Scratchpad

Session 1 — 2026-04-05 (orchestrator scaffold)

Trigger

Real-run testing of @mosaicstack/mosaic@0.0.25 (fresh install of the release we just shipped from the parent mission install-ux-hardening-20260405) surfaced a critical regression and a cluster of UX failings. User feedback verbatim:

The skill/additional feature installation section of install.sh is unsable The "quick-start" is asking way too many questions. This process should be much faster to get a quick start. The installater should have a main menu that allows for a drill-down install approach. "Plugins" — Install Recommended Plugins / Custom "Providers" — … The gateway port is not prefilling with 14242 for default What is the CORS origin for? Is that the webUI that isn't working yet? Maybe we should ask for the fqdn/hostname instead? There must be a better way to handle this.

Plus the critical bug, reproduced verbatim:

◇  Admin email
│  jason@woltje.com
Admin password (min 8 chars): ****************
Confirm password:            ****************
│
▲  Bootstrap failed (400): {"message":["property email should not exist","property password should not exist"],"error":"Bad Request","statusCode":400}
✔  Wizard complete.
✔  Install manifest written: /home/jarvis/.config/mosaic/.install-manifest.json

✔  Done.

Note the ✔ Wizard complete and ✔ Done lines after the 400. That's a second bug — failure didn't propagate in interactive mode.

Diagnosis — orchestrator pre-scope

To avoid handing workers a vague prompt, pre-identified the concrete fix sites:

Bug 1 (critical) — DTO class erasure. apps/gateway/src/admin/bootstrap.controller.ts:16:

import type { BootstrapSetupDto, BootstrapStatusDto, BootstrapResultDto } from './bootstrap.dto.js';

import type erases the class at runtime. @Body() dto: BootstrapSetupDto then has no runtime metatype — design:paramtypes reflects Object. Nest's ValidationPipe with whitelist: true + forbidNonWhitelisted: true receives a plain Object metatype, treats every incoming property as non-whitelisted, and 400s with "property email should not exist", "property password should not exist".

One-character fix: drop the type keyword on the BootstrapSetupDto import. BootstrapStatusDto and BootstrapResultDto are fine as type-only imports because they're used only in return type positions, not as @Body() metatypes.

Must be covered by an integration test that binds through Nest, not a controller unit test that imports the DTO directly — the unit test path would pass even with import type because it constructs the pipe manually. An e2e test with @nestjs/testing + supertest against the real /api/bootstrap/setup endpoint is the right guard.

Bug 2 — interactive silent failure. packages/mosaic/src/wizard.ts:147-150:

if (!bootstrapResult.completed && headlessRun) {
  prompter.warn('Admin bootstrap failed in headless mode — aborting wizard.');
  process.exit(1);
}

The guard is && headlessRun. In interactive mode, completed: false is silently swallowed and the wizard continues to the success lines. Fix: propagate failure in both modes. Decision for the worker — either throw or process.exit(1) with a clear error.

Bug 3 — port prefill. packages/mosaic/src/stages/gateway-config.ts:77-88:

const raw = await p.text({
  message: 'Gateway port',
  defaultValue: defaultPort.toString(),
  ...
});

The stage is passing defaultValue. Either the WizardPrompter.text adapter is dropping it, or the underlying @clack/prompts call expects initialValue (which actually prefills the buffer) vs defaultValue (which is used only if the user submits an empty string). Worker should verify the adapter and likely switch to initialValue semantics so the user sees 14242 in the field.

Bug 4 — Pi SDK copy gap. The "What is Mosaic?" intro text enumerates Claude Code, Codex, and OpenCode but never mentions Pi SDK, which is the actual agent runtime behind those frontends. Purely a copy edit — find the string, add Pi SDK.

Mission shape

Three milestones, three tracks, different tiers:

  1. IUV-M01 Hotfix (sonnet) — the four bugs above + release mosaic-v0.0.26. Small, fast, unblocks the 0.0.25 happy path.
  2. IUV-M02 UX polish (sonnet) — CORS origin → FQDN/hostname abstraction; diagnose and rework the skill installer section. Diagnostic-heavy.
  3. IUV-M03 Provider-first intelligent flow (opus) — the big one: drill-down main menu, Quick Start path that's actually quick, provider-first natural-language intake with agent self-naming (OpenClaw-style). Architectural.

Sequencing: strict. M01 ships first as a hotfix release (mosaic-v0.0.26). M02 is diagnostic-heavy and can share groundwork with M03 but ships separately for clean release notes. M03 is the architectural anchor and lands last as mosaic-v0.0.27.

Open design questions (to be resolved by workers, not pre-decided)

  • M01: does process.exit(1) vs throw matter for how tools/install.sh surfaces the error? Worker should check the install.sh call site and pick the behavior that surfaces cleanly.
  • M03: what LLM call powers the intent intake, and what's the offline fallback? Options: (a) reuse the provider the user is configuring (chicken-and-egg — provider setup hasn't happened yet), (b) a bundled deterministic "advisor" that hard-codes common intents, (c) require a provider key up-front before intake. Design doc (IUV-03-01) must resolve.
  • M03: is the "agent self-naming" persistent across all future mosaic invocations, or a per-session nickname? Probably persistent — lives in ~/.config/mosaic/agent.json or similar. Worker to decide + document.

Non-goals for this mission

  • No GUI / web UI
  • No registry / pipeline migration
  • No multi-user / multi-tenant onboarding
  • No rework of mosaic uninstall (stable from parent mission)

Known tooling caveats (carry forward from parent mission)

  • issue-create.sh / pr-create.sh wrappers have an eval bug with multiline bodies — use Gitea REST API fallback with load_credentials gitea-mosaicstack
  • pr-ci-wait.sh reports state=unknown against Woodpecker (combined-status endpoint gap) — use tea pr glyphs or poll the commit status endpoint directly
  • Protected main, squash-merge only, PR-required
  • CI queue guard before push/merge: ~/.config/mosaic/tools/git/ci-queue-wait.sh --purpose push|merge

Next action

  1. Create Gitea issues for M01, M02, M03
  2. Open the mission-scaffold docs PR (same pattern as parent mission's PR #430)
  3. After merge, delegate IUV-M01 to a sonnet subagent in an isolated worktree with the concrete fix-site pointers above