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

187 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# UX Transformation Report — 2026-07-04
Mission: experience Waggle OS as a real user (headless-Chromium walkthroughs of every
core journey), fix what breaks the experience, iterate until marginal. Benchmark: Apple.
**Commits:** `c03ca816` (wave 1) · `ad885d49` (wave 2) · `3091b04a` + `c04d049e` (wave 3) ·
`1e27933a` + `60616f8a` (wave 4) · `27e94077` (wave 5) · `8a4d2549` (wave-5 QA follow-up:
briefing dedup phrase-strip). All on local `main`, NOT pushed.
**Evidence:** `ux-audit-2026-07-04.md` (F1F12), `ux-audit-2026-07-04-part2.md` (F13F32),
`ux-wave2-plan-2026-07-04.md` (plan + deferred + incidental defects).
**Gates at close:** web tsc 0 · server tsc 0 · build:packages clean · web vitest 1323/1323 ·
every wave browser-verified by an independent QA agent (wave 1: 8/8, wave 2: 7/7 + regressions,
wave 4: 3 clean PASS + 4 patched post-QA, wave 5: verified — see below).
## Shipped
**Wave 1 — the first-run interruption stack (F1F5, F8F11):**
trial-paywall re-firing on every navigation → gated (session-once + 7-day snooze +
post-onboarding quiet window); "Let's go!" now actually sends the first task;
model-gate claim probe-backed (new `POST /api/settings/probe-provider`); LLM errors
became actionable ErrorBlocks (Retry + settings deep-link); coach marks defer on
first landing; sidebar active-state, fresh-workspace briefing copy, ⌘K collision.
**Wave 2 — the app no longer contradicts itself (F6, F7, F12F16, F19, F21, F27):**
active workspace never silently switches; one source of truth for workspace counts
(dev-noise filter, switcher checkmark + overflow); friendly model labels everywhere +
fallback≠primary; Memory Graph scope fix (renders 1,698 nodes where it said "no data")
+ server stats undercount fix + scope-labeled counts; onboarding polish (Recommended
template from role, goals unselected, single Back, tailored suggestions, static
language badge); IA renames (Agents / Library / General); briefing-modal a11y
(focus trap, Escape, labeled close); failed turns re-render as ErrorBlock on reload.
**Wave 3 — QA residuals:** paywall never stacks on the briefing modal; probe banner
names the verified provider; step counter 4-of-5/5-of-5; redundant storage pill hidden.
**Wave 4 — root-cause dig + remaining-list cleanup (browser QA in flight):**
- **DB-flake ROOT-CAUSED** (not yet fixed — see below): `MultiMindCache` LRU evicts
and closes an in-use `MindDB` mid-turn; post-response writes then throw "database
connection is not open" and are silently swallowed. Confirmed exact mechanism.
Added a classifier + structured warn logs at the two silent seams (autoSaveFromExchange,
KG extraction) in `chat.ts` so it's now observable. **The real fix (pin/refcount on
MultiMindCache, or a reopen-on-closed-handle guard in db.ts) lives in
`packages/hive-mind-core/src/{multi-mind-cache,db}.ts` — off-limits to this arc per
§7.5 (substrate changes land there first, not as a side effect of a UI sprint).**
Also flagged: `WorkspaceSessionManager.closeIdleSessions()` is dead code (defined,
never invoked) — likely related, needs wiring to an interval.
- F22 memory dedup: fixed. Cards collapse by normalized content with a "×N" badge;
briefing highlights route through the same normalizer.
- F25 notifications: badge/panel count now share one derivation (no more independent
drift); copy humanized; dead deep-links corrected across 5 server files; relative
timestamps. QA-verified clean.
- F28 responsive: top bar protected (no-wrap brand, shrink-safe chips); sidebar
collapses to an icon rail with tooltips under `lg`. QA-verified clean.
- F15 long tail: model-label wired into ChatApp, TelemetryApp, WorkspaceDesktopApp,
ChatWindowInstance toast.
- F31 Solo→Free rename; F24 Weaver plain-language explainer + "Last Decay" tooltip.
QA-verified clean.
**Wave 4 post-QA patch round (browser-verified after fix):**
QA ran the fresh build and found F22 had NOT shipped despite the implementer's claim,
plus two partial misses. Root-caused and fixed all three directly (no new workflow):
- **F22 dedup — real root cause was different from the original hypothesis.** Live
data doesn't just differ by a stripped volatile token; a benchmark "anchor" memory
gets re-written repeatedly with more appended turns each time (same title, content
is a strict prefix of the next copy) — 6 visibly-duplicate cards, a growing chain
pass-1 exact-match dedup can never merge. Added a second dedup pass:
bucket by normalized title, absorb any entry whose content is a normalized prefix
of a longer sibling's. Also found the normalizer's volatile-token regexes missed
the *actual* live shapes: a raw epoch `Timestamp: 1782400441971` (not ISO-8601),
a bare `audit-<digits>` id (RUN_ID_RE required the literal word "run"), and a
`BENCH-SECRET-<token>`. Fixed all three; live-verified — the 6-card BENCHMARK
family now renders as one card with an accurate `×7` badge.
- **F15 StatusBar chip — real bug, not stale HMR.** `formatModelLabel`'s catalog-match
branch trusts any provider-catalog `name` that differs from the `id` as an
already-friendly display name — true for the static cloud catalog, but Ollama's
catalog `name` is *deliberately* the bare installed tag (`routes/providers.ts`
`fetchOllamaModels`, "Display name stays the bare tag"), so it short-circuited
before the heuristic humanized it. Fixed by skipping the catalog-trust branch for
`ollama/`-prefixed ids. Live-verified: chip now reads "Gemma4 (31b)".
- **F24 Weaver tooltip — NOT a bug.** Verified live after a full daemon + Vite
restart: the tooltip renders correctly (a real `[role=tooltip]` DOM node appears
on hover). QA's long-running session (hours, 4 waves of live edits) is the more
likely explanation than a code defect here — the same explanation QA itself gave
for the sidebar/model-chip tooltips not screenshotting.
- F30 copy nit: found the SAME missing-pluralization bug duplicated in two server
files (`workspace-context.ts` + `workspaces.ts`, 4 near-identical string templates
each) — fixed all 8 occurrences.
**Wave 5 — final polish (F32, F17/F18, F12, F23):**
- F32: top-bar breadcrumb now reflects the active workspace sub-tab (was stuck on
"Chat" for every tab — root cause: dock-tiers chat entry `route:'/workspaces'` was
the only longest-prefix match); context-rail/detail-drawer cleared on real route
change so it can't pin over the next page.
- F17 (safety posture, revertable): "Erase All Data" wrapped in a bordered all-tier
"Danger Zone" section at the bottom of General. **The implementer correctly refused
the literal "move to Advanced" plan** — Advanced is tier-gated (`settings-tier-filter.ts`
POWER_SETTINGS_TAB_IDS), so moving a GDPR Art.17 erase control there would hide it
from FREE/PRO tiers = a real compliance regression. Danger-zone framing resolves the
actual complaint (plain 4th item under the theme picker) without tier-gating.
- F18 (safety posture, revertable): "Never ask" (auto-pass-everything) autonomy level
now fires a one-time confirm on the transition INTO it + amber danger styling only
while selected. Default stays 'normal'; safe levels never gated/restyled.
- F12: Agent Center sparse state — 2-3 suggested-agent template cards render under the
list when ≤2 agents exist on the unfiltered tab (fills the honeycomb void).
- F23: Wiki tab display-level quality floor (hide entity pages with name <3 chars OR
<2 sources), all counts derive from the floored set. Conservative — catches genuinely
thin entities in clean data; a no-op on the benchmark-polluted dev corpus (whose junk
all reports "30 sources"). A fragment heuristic for those was deliberately NOT shipped
blind (false-positive-prone).
- Files-tab empty state: copy + "Open chat" CTA only — NO upload button, because
`getWorkspaceFiles` (ingest registry) and `uploadFile` (storage-provider fs) are
provably disjoint stores; an upload button would succeed yet leave the tab empty.
- W5D **surfaced a real contradiction, resolved conservatively**: the CreateWorkspaceDialog
"Free plan includes one workspace" copy — canonical `tiers.ts` says FREE=5 / PRO=unlimited,
but the legacy `feature-gates.ts` gates `multi-workspace` behind `minTier:'teams'`, so
the runtime blocks FREE (and PRO-as-'solo') at workspace #2. Quoting either "1" or "5"
contradicts a source and (for "5") creates a broken promise in the paywall's own dialog.
Made the copy number-free ("Upgrade to add more workspaces — each keeps its own separate
memory") pending a founder decision on the gate↔config mismatch (see below).
## Remaining high-impact issues (no product decision needed — just work)
_Reconciled 2026-07-05 after wave 5 + the workspace-limit resolve; several items
originally listed here shipped in wave 5 (F32 drawer/breadcrumb, Agents sparse-state
cards, wiki floor, workspace copy) and were removed._
1. **DB-flake real fix** — fully diagnosed (Wave 4): `MultiMindCache` LRU closes an
in-use `MindDB` mid-turn. Needs a substrate change in `packages/hive-mind-core`
(pin/refcount-aware eviction, or a reopen-on-closed-handle guard in `db.ts`) — a
separate arc per the OSS-sync policy (§7.5), not a UI-sprint side quest. Also wire
up the dead `WorkspaceSessionManager.closeIdleSessions()` while there. **Highest
real-user impact of anything remaining** (it silently drops post-turn memory writes).
2. **Files-tab Upload** — blocked on a design step: `getWorkspaceFiles` (ingest
registry, `files.jsonl`) and `uploadFile` (storage-provider fs) are disjoint stores,
so an upload button would succeed yet leave the tab empty. Needs the registry↔storage
merge first; until then the empty state is copy + "Open chat" only (shipped wave 5).
3. **W2G part C — server-side retry-dedup** — after a chat error, a local Retry can
leave a duplicated failed user+assistant pair on reload. ~4-file server-side change
(chat-persistence strip + retry-body handling). Cosmetic-on-reload only.
4. **F23 wiki fragment heuristic** — the conservative quality floor (name<3 / <2 sources)
shipped, but the named junk pages ("Act Aug", "Abu Dhabi Airport") are ≥4-char,
multi-word, all "30 sources" in the polluted dev corpus, so nothing catches them.
A fragment heuristic (all-words-≤3-chars, standalone month tokens) is false-positive
-prone — needs a clean corpus to tune against. Low value.
5. **Watch, not a task:** the wave-4 growing-composite dedup pass (same-title +
content-is-a-prefix) has only been validated against the one benchmark "anchor"
family. Watch for false negatives on other real duplicate shapes.
_Cosmetic tail: onboarding slug-collision still logs a single 409 (create path
auto-suffixes, so no user-visible break); cached workspace summaries keep old
"1 memories" text until regenerated (the generator is fixed)._
## Needs a product decision
- ~~**Workspace-limit gate ↔ config contradiction**~~ — RESOLVED (`593e6b40`). It
turned out to be a client↔server *sync bug*, not a business decision: the server
already enforces the canonical `tiers.ts` limit (FREE=5 / PRO=unlimited), only the
client dialog gated off the stale `feature-gates.ts` `multi-workspace` flag and
blocked at #2. Aligned the client to the same rule (new pure `canCreateWorkspaceAtTier`
mirroring the server, boundary property-tested); paywall copy now truthfully quotes
"The Free plan includes 5." **Still open (a real product decision, not this bug):**
the legacy `feature-gates.ts` still carries the outdated 'solo'/'business' tier
vocabulary (F31's deeper root) and the now-unreferenced `multi-workspace` entry — a
broader migration of that whole file onto the canonical 5-tier system is worth doing.
- **Model-gate truth-gap:** the gate verifies a provider key, not the workspace
chat's configured model (QA saw "Anthropic verified" while the chat's model 401'd).
Should the gate probe the actual default model instead?
- **F20 marketplace:** same integration listed 3× from three catalogs with three
verbs (Add/Connect/Enable) — needs a catalog-merge design, not a patch.
- ~~**F17/F18 safety UX**~~ — SHIPPED in wave 5 as a revertable posture change
(Danger Zone + "Never ask" confirm). If a stronger posture is wanted (e.g. move
Erase fully into Advanced *and* accept tier-gating it, or a higher-polish inline
amber confirm-row instead of a native `confirm()`), that's the remaining decision.
- **F29 locale:** Serbian date fragments inside an all-English UI — localize fully
or pin dates to UI language.
- **F26 Team tab:** dead end ("You — Online") that misses the Teams-tier upsell moment.
- **Sidebar active state on workspace Overview:** wave 1 made no spine item active
there (previously Chat, wrongly). Design may prefer the workspace pill to carry it.
## Environment notes for the next session
- Dev recipe: sidecar `WAGGLE_SKIP_LITELLM=1 node --env-file=.env node_modules/tsx/dist/cli.mjs
packages/server/src/local/start.ts` (RESTART after server edits — no watch);
`npm run dev` (vite :8080); browse CLI daemon for walkthroughs.
- esbuild trap: `npm i @esbuild/win32-x64@0.28.0 --no-save` if tsx fails on version mismatch.
- Tree has heavy dev-noise workspaces (`ai-os-audit-*`) — now filtered in UI counts,
but consider a real cleanup.