ADR-0012: Web UI redesign — operator-first IA + flow-driven layout¶
Status: Proposed
Date: 2026-05-30
Deciders: Ramón Kamibayashi + Claude Opus 4.7
Related: ADR-0010 WP-Pulse, ADR-0011 Snapshot model
Technical story: WP-Pulse v1.0 shipped 7 phases of functionality across the web UI by additive panels, each phase landing its component on the existing site detail page. The result is a 7-tab spaghetti page where the operator has to know which tab does what. Real user-testing today surfaced four classes of confusion: (1) Snapshots tab pointed at the wrong table, (2) error messages rendered as [object Object], (3) "where are my backups" wasn't obvious, (4) destructive operations had no surfaced rollback. This ADR redesigns the IA around what the operator wants to DO, not what the data model looks like.
Context¶
Current IA (audited 2026-05-30)¶
/ Fleet dashboard (totals + alerts + batches)
/sites List of sites (table)
/sites/[slug] Site detail — 7-tab page
├─ Overview Hero + PullButton + LocalStackPanel + PendingChanges + VisualDiff + Versions + Schedule
├─ Snapshots ← was empty F4 placeholder, just rewired to versions
├─ Manifest Read-only manifest JSON viewer
├─ Plugins Per-site plugin table
├─ Logs Stub (F2 work pending)
├─ WP-CLI Stub (F2 work pending)
└─ Settings Per-site config form
/fleet Drift dashboard (older, parallel to home)
/onboard Enrollment wizard
/settings Server-wide config
Component sizes: VersionsPanel.tsx 1231 LOC, FleetDashboard.tsx 930 LOC, SchedulePanel.tsx 598 LOC, VisualDiffPanel.tsx 408 LOC. Total ~5,500 LOC across 19 components.
Pain points observed in real operator use¶
Listed in order of severity from the day's session:
-
"Snapshots" tab showed empty state forever — pointed at a dead table while real backups lived in a different panel. Operator clicked → "where are my backups?" Confused for minutes. (P0; fixed today commit
cbf0b25+ ADR-0011.) -
Error rendering as
[object Object]— FastAPI 422 validation errors leaked their array-of-objectsdetailintoString()which produced literal "[object Object]". Operator couldn't tell what was wrong. (P0; fixed today commit8b2f0e9.) -
Site detail has 7 tabs — operator scans header looking for "where do I save a backup" and has to read every tab label. Versions is split between Overview (panel mounted) AND Snapshots tab (alias) — same content reachable from two places, confusing.
-
Verb confusion: Pull vs Push — for non-git operators (the target audience), "pull" and "push" are not intuitive. "Pull" sounds like extraction, "push" sounds like force. Real semantic: "fetch from production" vs "ship to production".
-
No "what just happened" feed — operations are scattered: PullButton on Overview, VersionsPanel auto-creates a version, ScheduleRuns table elsewhere, alerts in dashboard. No single "activity stream" the operator can scan.
-
Destructive actions have no rollback CTA in-flow — after a Push completes, success toast disappears. Operator who realises 5 min later they want to undo has to find Pushes → click row → click rollback. The undo path should be one click from the completion banner for 10 minutes.
-
Long-running ops poll with a dot animation — no progress bar, no ETA, no real-time status. Operators don't know "is this 30 seconds or 30 minutes". For Pulls especially (2-5 min depending on site size).
-
Three list-like views with overlapping purpose — Home
/(FleetDashboard with cards),/sites(table list),/fleet(drift dashboard). Operator opens/then/sitesand sees overlapping data, wonders which is canonical. -
Empty states lack next-step CTA — "No pulls yet" without a button to trigger one. Operator has to navigate to the right tab/panel.
-
No glossary tooltip — "Version", "Snapshot", "Push", "Pull", "Schedule run", "Visual diff" — the vocab is large. New operators need on-page explanations.
-
Mobile responsiveness uneven — TopBar has MobileBottomNav but the 7-tab nav is not collapsible. Site detail is unusable on phone.
-
"Logs" and "WP-CLI" tabs are stubs — clicking either shows nothing useful. Either ship F2 or hide them.
Operator journey maps¶
Five typical operator tasks and how they map to the current UI:
Task A — "I want to edit a theme locally and push to prod"¶
Current: / → click site card → Overview tab → scan for "Pull Now" → click → wait → SSH to LXC → edit files → return to UI → scroll to PendingChangesPanel → confirm diff → push → wait → done.
Hops: 7 UI screens + SSH + IDE. Pain: where exactly to edit (no in-app file editor), uncertainty about when the local stack updates, no "ready to edit" signal.
Task B — "I want a backup before doing something risky"¶
Current: / → site card → Overview → scroll to VersionsPanel → click "Save current state" → name modal → confirm. OR Snapshots tab → same panel.
Hops: 5 screens (or 4 if you go via Snapshots tab).
Pain: confusion which tab; the panel is mid-page so you scroll past pulls/visual-diff.
Task C — "I want to roll back yesterday's push"¶
Current: /sites/[slug]/page → Overview → scroll to recent pushes (does this exist? you have to know to scroll). Pushes panel is buried below versions. Click row → see detail → Rollback button.
Hops: 3-4 screens.
Pain: the rollback affordance is not obvious from a fresh page load; the operator has to remember where pushes live.
Task D — "Is everything OK with my fleet right now?"¶
Current: / → FleetDashboard. Hero shows 🟢🟡🔴⚫ counts + alerts list + recent batches.
Hops: 1 screen.
Pain: relatively good! This page works. But it doesn't link forward to "what should I do about the critical sites" — operator clicks a 🔴 card → arrives at site detail → has to re-orient.
Task E — "I want to schedule auto-pulls for all my sites"¶
Current: per-site /sites/[slug]/page → Overview → SchedulePanel → set cron, retention. Repeat for each site.
Hops: N × 4 screens for N sites.
Pain: no bulk-edit. No "apply this schedule to all sites" affordance.
Design principles for the redesign¶
-
Action-first IA: organise top-level navigation around verbs (Pull, Push, Save, Restore), not nouns (Sites, Versions, Snapshots).
-
Activity stream is canonical: one chronological feed per site of every operation. Versions appear there as "saved", pulls as "fetched", pushes as "shipped", schedule runs as "auto-pulled". Replaces the per-data-type panel jungle.
-
One screen, one decision: the site detail page should be scannable in 5 seconds. Hero = status. Body = activity. Sidebar = actions.
-
Reversibility surfaced: every completion banner of a destructive op keeps a "Rollback" button visible for 10 minutes after success. After 10 min, fades to the activity stream where it's still 1-click.
-
Progress over polling-dots: every long op (pull, push, load, recapture) shows a real progress component with stages and ETA. Backend already has state machine stages; expose them.
-
Glossary chips: hover any unfamiliar term ("Version", "Push", "Schedule") for a 1-sentence inline tooltip.
-
Empty-state CTAs: every empty state has the next-step button embedded.
-
Mobile-first nav: bottom tab bar on mobile, sidebar on desktop. Site detail collapses into accordion sections under mobile.
-
Consistent vocab between UI and CLI: ADR-0011 establishes "Version" as the primitive — use it everywhere (no "snapshot" except as URL alias).
-
No empty stub tabs: hide tabs whose feature isn't shipped (Logs, WP-CLI in v1) instead of showing empty states.
Proposed IA¶
/ Home — pick a site to work on
Hero: pending alerts + critical sites + "what's new"
Quick actions: Pull all / Recapture all / Save all
Sites list (cards): clicked → site detail
/sites/[slug] Site detail — single page, 3 sections
Hero Site name, status dot, prod url, "Open prod" + "Open preview" buttons
Last activity strip: "Last pulled 3h ago • 2 changes pending • Loaded: v_save_xyz"
Sidebar (sticky) What do you want to do?
• Pull from prod (fetch latest) [if no pull in last 1h]
• Push changes to prod [if worktree dirty]
• Save snapshot [always]
• Restore from snapshot [if versions exist]
• Schedule (manage cron) [always]
• Visual diff (re-capture) [if pulls exist]
• Open in WP-CLI / Logs [v2]
Body Activity stream — chronological, last 50 events:
━ Apr 30 14:06 ✓ Pushed 1 file to prod [Rollback]
━ Apr 30 13:55 ✓ Saved version v_save_a24e [Load]
━ Apr 30 12:50 ✓ Pulled 80 MB from prod [View diff]
━ Apr 30 11:30 ✓ Recaptured visual diff [Open]
━ Apr 30 10:00 ✓ Scheduled pull (next: Sun 3am)
Each row clickable → detail drawer.
/fleet Fleet view (replaces home + /sites + /fleet)
Tabs:
• Health (current FleetDashboard)
• Activity (cross-site stream)
• Batches (current batch table)
• Schedules (cross-site cron view + bulk edit)
/sites/[slug]/settings Settings drawer accessible from site detail header (not a tab)
/onboard Unchanged — enrollment wizard
/settings Unchanged — server-wide config
What goes away¶
/sitesindex page — folded into/fleetHealth tab + Home shortcut.- Per-site tabs
Snapshots,Manifest,Plugins,Logs,WP-CLI,Settings— folded into activity stream (Manifest as a row that expands), settings header button (no more tab), and v2 for Logs/WP-CLI. LocalStackPanelas separate panel — folded into the site hero's status line ("Stack running on port 8100").VersionsPanelas 1200-LOC monolith — split into smaller activity-stream rows + a dedicated/sites/[slug]/versionsdrawer for filtered view.
What becomes new¶
SiteHerocomponent — name, status, URLs, last-activity strip.ActionSidebarcomponent — context-aware (what's available now).ActivityFeedcomponent — chronological cross-data-type log.ActivityRowvariants — pull, push, save, load, schedule-run, alert, etc. Each clickable to expand inline.GlossaryChipcomponent — small?icon next to terms with a popover.OpProgresscomponent — for in-flight ops, shows stages from server state machine + estimated remaining time.RollbackBannercomponent — sticky element after destructive ops with "Undo this" button for 10 min.
CLI parity¶
CLI commands should mirror the UI verbs:
- wpdev pull → "Pull from prod"
- wpdev push → "Push changes to prod"
- wpdev save (alias of wpdev version save) → "Save snapshot"
- wpdev restore <id> (alias of wpdev version load) → "Restore from snapshot"
Add aliases so help text matches UI labels.
Migration plan¶
Phase 1 — Quick wins (4-6h)¶
These ship independently and remove the worst friction without touching the IA:
- Hide stub tabs (Logs, WP-CLI) — comment out from
SiteTabNav.tsxuntil F2 ships them. - Snapshots → Versions tab rename (cosmetic) — already aliased today; just relabel the nav item.
- Add rollback-from-banner: when a push completes, the success banner stays for 10 min with a "Undo this push" button.
- Glossary tooltips: 30-LOC
<GlossaryChip term="version" />next to first occurrence on each page. - Empty-state CTAs: 1 button per empty state pointing at the natural next action.
- Activity feed lite: collapsed list of last 10 events on site detail Overview hero, links to full feed later.
Phase 2 — Site detail rewrite (1-2 days)¶
Rewrite /sites/[slug]/page.tsx from the 7-tab page to the 3-section single-page layout (hero, sidebar, activity feed). Existing panels still render their content but are reachable via activity rows (inline expand) or via a query-param filter on /sites/[slug]?filter=versions.
Phase 3 — Fleet consolidation (1 day)¶
- Replace
/sitesindex with redirect to/fleet?tab=health. - Add Activity tab to
/fleet. - Add Schedules tab with bulk-edit grid.
Phase 4 — Real-time progress (1-2 days)¶
Backend already has state-machine stages on pulls/pushes/loads. Wire them to a Server-Sent Events endpoint (GET /api/v1/sites/{slug}/events with text/event-stream) so the UI gets real-time updates without polling. OpProgress component subscribes per in-flight op.
Phase 5 — Mobile pass (1 day)¶
- Site detail accordion on
<mdbreakpoints. - Bottom tab nav.
- Touch-target spacing audit.
Phase 6 — Polish (open-ended)¶
- Dark mode toggle.
- Keyboard shortcuts (
g s→ go to sites,n p→ new pull, etc). - Per-operator preferences (default site, default filters).
Decision¶
Proceed in order: Phase 1 (today) → Phase 2 (next session) → Phase 3 → Phase 4 → Phase 5 → Phase 6.
Phase 1 ships zero-risk improvements that immediately address the four observed pain points (Snapshots confusion, stub tab clutter, missing rollback CTA, vocab confusion). Phase 2 onwards is bigger work that warrants its own day each.
After Phase 2 the operator should be able to:
- Open /sites/<slug> and answer "is this OK?" in 5 seconds (hero + activity feed top row).
- See "what can I do now?" without scanning labels (action sidebar).
- Undo the last destructive action in 1 click (rollback banner).
- Scan activity history without changing tab.
Implementation status¶
- Phase 1 — Quick wins. Targeted for tonight if user approves.
- Phase 2 — Site detail rewrite. Next session.
- Phase 3 — Fleet consolidation.
- Phase 4 — Real-time progress via SSE.
- Phase 5 — Mobile pass.
- Phase 6 — Polish.
Open questions¶
- Spanish vs English UI. Operator is Spanish-native. CLI/code is English. UI strings should be one language consistently; pick.
- Activity feed retention. Keep all events forever? Aged-out? Filter UI?
- WebSocket vs SSE. SSE is simpler (one-way push from server). Pulls already have a state machine; WebSocket for bidi is overkill in v1.
- Mobile bottom nav with 4-5 sections: Home / Activity / Sites / Settings (or simpler 3: Home / Sites / Settings)?
- Multi-tenant prep: the redesign should not block ADR-0010 v2.0 "RBAC + multi-operator" — current model is single-operator; revisit when that lands.