Unified Control Center — Migrating an Operator Dashboard Across Two Repos in 11 Days
- Version
- v7.8
- Date
- 2026-05-06
- Tier
- light
Retired the legacy Astro operator dashboard, migrated it inside the public showcase as a basic-auth-gated /control-room route, instrumented all 8 GA4 events, killed the standalone Vercel project. 11 days, 42/44 tasks done + 1 deferred, 20 PRs across 2 repos, 0 showcase regressions. Pre-state TTC baseline unmeasurable; post-state establishes the production baseline.
How to read this case studyT1/T2/T3 · ledger · kill criterion▾
- T1Instrumented
- Numbers come from a machine-generated ledger or commit. Reproducible. Highest reader trust.
- T2Declared
- Numbers stated by a structured declaration (PRD, plan, frontmatter) but not directly measured.
- T3Narrative
- Estimates and observations from session memory. Useful for context; not citable as evidence.
- Ledger
- Where to verify the claim — a file path, GitHub issue, or backlog entry. Anything labelled
ledger:is the audit trail. - Kill criterion
- The pre-registered threshold under which this work would have been killed mid-flight. Not fired = work shipped without hitting the threshold.
- Deferred
- Items intentionally not closed in this version. Each cites the ledger that tracks remaining work.
Visual aid · key numbers at a glance
Default · no specialised visual declaredThe legacy Astro operator dashboard at fit-tracker2.vercel.app had drifted from the FitMe brand, maintained its own component library, and by the end of its life couldn't reliably emit the GA4 events its own measurement plan required. UCC retired all of it. The dashboard is now fitme-story.vercel.app/control-room/* — a basic-auth-gated route inside the public showcase, indigo/coral/warm-stone tokens shared with the rest of the site, all 8 PRD §10.1 GA4 events instrumented from day one, and the legacy Vercel project deleted post-verification.
11 days from research to formal close · 42 of 44 implementation tasks done · 1 deferred (intentionally) · 20 PRs across two repos · 0 regressions on the public showcase throughout.
The honest disclosure up front: the case study's primary metric (Time-to-Confidence) does not have a clean before/after. Pre-migration baseline is T2 Declared with sample size 2 (the legacy dashboard's instrumentation broke before the 7-day window closed). Post-migration measurement is T1 Instrumented but pending — the new dashboard ships with all events wired; ≥7 days of operator traffic accrue before a meaningful read. The "before" wasn't measurable, so the "after" establishes the baseline. That's the most honest framing we could write.
Architecture decisions (locked, not re-litigated)
- Arch A — same Vercel project, separate routes. Dashboard code lives next to the showcase under
fitme-story/src/{app,components,lib}/control-room/*. ESLint rule (T13) blocks reverse imports from showcase → control-room. - Pattern 4.b — pre-build sync.
scripts/sync-from-fittracker2.tscopies.claude/shared/*.json+ per-featurestate.json+.claude/integrity/snapshots/*.json+ the FT2 docs tree intosrc/data/beforenext build. Vercel build clones FT2 (shallow, deploy key) before sync. - Blind-switch 3 layers —
proxy.tsbasic-auth on/control-room/*,sitemap.ts+robots.tsexclude,next.config.tsDASHBOARD_BUILDflag. - Extraction-ready —
EXTRACTION-RECIPE.md(T39) documents the 7-step playbook to lift the dashboard back to a standalone project if needed.
What shipped
| Surface | Source | Outcome |
|---|---|---|
/control-room (Overview) | Hero + NumbersPanel + Phase legend + Recent activity + Framework Health card | Live, gated |
/control-room/board (Kanban) | Phase-bucketed cards · clickable to PRD on GitHub | Live |
/control-room/table | 6-column sortable + searchable + localStorage-persisted view | Live |
/control-room/tasks | Flat TaskCard grid grouped by status | Live |
/control-room/knowledge | Doc index + case-study cards with click-tracking | Live |
/control-room/framework | Tier 1.1 adoption trend + doc-debt coverage + 72h cycle snapshot + automation map | Live (data flowing after fitme-story #39 fixed the sync gap) |
| Cmd+K command palette | Linear-style overlay · 60+ commands across Navigate / Actions / Features groups | Live |
| Public showcase nav | "Control Center 🔒" entry · opens new tab · auth dialog appears only on click | Live |
| Daily sync routine | trig_01ThxQphvQQa8tyWMsxiyhdm · fires 09:00 IDT · auto-attached Linear + Notion + 6 other MCPs | Live (first fire 2026-05-07) |
Outcomes (tier-tagged)
| Dimension | Pre-UCC | Post-UCC | Tier |
|---|---|---|---|
| Operator dashboard host | Separate Vercel project | Same project as showcase, gated route | T1 |
| Stack | Astro 6 + React 19 + Tailwind v3 | Next.js 16 + React 19 + Tailwind v4 | T1 |
| Auth | None (URL was effectively obscure) | Basic-auth via proxy.ts Layer 1 | T1 |
| GA4 instrumentation | 2 events broken at end of window | 8/8 events wired + 13-test unit suite | T1 |
| TTC primary metric | T2 Declared ~8s, n=2 noisy | T1 Instrumented pending (≥7d post-launch) | T2 → deferred |
| Showcase regression | n/a | 0 | T1 (build success across all PRs) |
Five honest disclosures
- Pre-state baseline is unmeasurable. Documented in
state.json::tasks[T2.5].status = "deferred". Post-launch GA4 establishes the production T1 baseline. - CI side-fix during the window. Multi-week parallel-clone simulator hang env-flake (24+ failed runs) diagnosed and fixed mid-UCC via FT2 #225. Two-layer root cause:
FitTracker.xcschemeparallelizable=YESoverriding the CI's-parallel-testing-enabled NOflag, plus a secondaryAuthPolishV2UITestszombie-app-instance bug surfaced after the scheme fix. Research atdocs/case-studies/meta-analysis/ci-env-flake-research-2026-05-05.md. - Turbopack
new URLregression. UCC T26 was the first route to import the parser chain reachingtypes.ts; surfaced a Turbopack module-resolution bug. Fixed in fitme-story #34. - PR #37 → #38 squash-merge race. First commit shipped with default
next/linkprefetch, which fired a background 401 fromproxy.tsand surfaced an unexpected auth dialog. The follow-up commit (target=_blank, no prefetch) was orphaned by an early squash-merge; recovered in fitme-story #38. - PRD §13 30-day rollback window deviated. Original plan kept the legacy dashboard alive 30 days as a safety net. User authorized immediate deletion 2026-05-06 because (a) new dashboard verified working, (b) legacy instrumentation already broken (no rollback value), (c) discovery preserved via showcase nav.
Lessons for future migrations
- Measure pre-state instrumentation BEFORE committing to "before/after" framing. UCC's TTC metric was right; the legacy surface couldn't reliably emit the events. Add a "measurement viability check" sub-task to the start of implementation; if pre-state can't be measured, switch to "post-state baseline" framing immediately.
- For browser auth UX, prefer
target="_blank"over inline navigation. Cancel-on-401 strands users; a new tab preserves the originating context. vercel env addinteractive only. Noecho/printfpipes — they silently append\nand your auth gate becomes a debugging nightmare.- Squash-merge race is a real failure mode. When iterating right before merge, double-check the branch HEAD.
deferredtask status is honest, not failure. Better than (a) faking the task done with a placeholder or (b) leaving it perpetuallypendingwithout explanation.
Where things go from here
- T2.5 follow-up (non-blocking) — once ≥7 days of operator traffic accrue on the new dashboard, query GA4 for
dashboard_load+dashboard_blocker_acknowledgedsessions; compute TTC p50/p90; updatebaseline-ttc.jsonto T1 Instrumented. - Drag-to-update on Kanban — the Wave 1 Kanban port is status-only.
dashboard_kanban_dragGA4 helper is shipped as a stub for when drag UX lands. - Per-feature drill-down route —
TaskTreecomponent is shipped as a reusable primitive without an immediate consumer. Natural future host.
Cross-cutting framework signals
- v7.5 data integrity — All 4 paired write-time + cycle-time defenses applied to UCC's state.json across the implementation window. Zero gate violations.
- v7.6 mechanical enforcement — All write-time gates fired correctly on every reconcile commit. Zero skirts.
- v7.7 validity closure —
cu_v2schema present; case-study tier tags present. - v7.8 bridge — Cache-hit logging captured ~6 events during the implementation window; honesty ledger entry pending.
Closing
This is the first FitMe feature that spanned two repositories within a single 11-day window. Cross-repo overhead was real (2 PR queues, 2 CI pipelines, 2 Vercel projects → 1) but the architectural payoff (one design system, one deploy pipeline, one auth gate, one analytics surface) justified it. The pattern for future cross-repo migrations: Pattern 4.b sync + ESLint reverse-import lock + a single source-of-truth state.json + post-launch baseline measurement when pre-state instrumentation is unreliable.
Full source case study with timeline detail, hard/easy retrospective, and per-task notes: docs/case-studies/unified-control-center-case-study.md.