apps/internal → @repo/ui Design-System Adoption
Status: In progress — foundation + pilot landed; phased rollout planned Author: David Boone (research-assisted) Date: 2026-07-02 Branch: claude/trusting-lamport-1f2vxc · PR base: staging
0. The finding (why this exists)
Section titled “0. The finding (why this exists)”apps/internal barely uses the shared design system, and what it does use is
silently broken:
- Only ~6 files imported
@repo/uicomponents (Button,toast, a few whole panels —AuditLogViewer,OrganizationMembers,LangfusePanel,SystemConfigEditor,UserSearch). Everything else is hand-rolled Tailwind. - Internal forked its own partial
--om-*tokens (src/styles/tokens.css+ an inline@layer baseblock) and never imported@repo/ui/tokens/tokens.css. Every DS token the components reference —--om-success-light,--om-panel-2,--om-radius-sm,--om-line,--om-orange-press, … — was undefined in internal. So the@repo/uicomponents internal already renders were showing with missing variables (transparent/invalid styles). - Internal’s
global.csshad no@sourcedirective forpackages/ui, so Tailwind never generated the DS components’ utility classes either. - Internal even maintains a parallel local mini-DS in
src/components/ui/(GapBtn,GapPill,GapStat,GapSection) that duplicatesButton,StatusBadge, andStatTile.
The reference consumer, apps/webapp, does it correctly: it imports
@repo/ui/tokens/tokens.css and @source-scans packages/ui. Internal simply
never adopted the foundation. This is both a consistency gap and a latent
rendering bug.
1. Goal
Section titled “1. Goal”Make apps/internal render on the shared design system: canonical --om-*
tokens as the single source of truth, @repo/ui primitives instead of
hand-rolled Tailwind, and the local ui/ shims retired. One visual language
across the platform, and the existing broken DS components fixed as a side effect.
Non-goals: redesigning any screen (this is a like-for-like re-skin onto the
DS, not a UX change); touching @repo/ui itself beyond additive needs; changing
behavior or copy.
2. What already landed (this branch)
Section titled “2. What already landed (this branch)”Phase 0 — token foundation (src/styles/global.css):
@import '@repo/ui/tokens/tokens.css'(real path — Tailwind v4 ignores the export map) so the canonical--om-*tokens are defined and win for shared tokens; internal’s app-specific tokens are kept.@sourcedirectives for the app andpackages/ui/srcso DS component classes are emitted.- Verified:
astro buildgreen; the previously-missing DS tokens are now in the emitted CSS bundle. This fixes internal’s existing@repo/uiusage too.
Phase 1 — pilot (the onboarding UI, as the reference pattern):
OnboardingChecklist.tsx:StatusBadge(intent-mapped),Button,Skeleton,Banner, DS token classes — replacing hand-rolled pills/buttons/gray palette.NewOrgForm.tsx:Field+controlBase+Select/Textarea/Checkboxfrom@repo/ui/form;Bannerfor env warning + errors;Buttonfor submit.
Caveat that governs the rest of the rollout: adopting the DS tokens restyles internal to the DS palette/spacing. A headless
astro buildvalidates that CSS compiles and bundles — it does not validate pixels. Every phase below needs a visual-QA pass (dev server or screenshot) before merge.
3. Component mapping (raw pattern → DS)
Section titled “3. Component mapping (raw pattern → DS)”| Hand-rolled pattern in internal | Replace with | Source |
|---|---|---|
<span className="rounded … bg-*-100 text-*-800"> status pill |
StatusBadge (intent, dot, size) + statusToIntent |
@repo/ui |
<button className="rounded bg-… "> |
Button (variant, size, loading, href) |
@repo/ui |
local GapBtn / GapPill / GapStat |
Button / StatusBadge or Chip / StatTile |
@repo/ui |
<div className="rounded border bg-white p-…"> card |
DS token classes (bg-[var(--om-panel)] border-[var(--om-line)]) or Surface |
@repo/ui |
<input>/<select>/<textarea> + labels |
Field + controlBase / Select / Textarea / Checkbox / Switch |
@repo/ui/form |
| loading spinners / “Loading…” | Skeleton |
@repo/ui |
| empty-state divs | EmptyState |
@repo/ui |
| page-level notices / env banner | Banner (inline for in-card) |
@repo/ui |
ad-hoc modals / confirms (window.confirm) |
Modal / ConfirmDialog |
@repo/ui |
tables built from <table> |
Table / DataTable |
@repo/ui |
text-gray-* / bg-gray-* / bg-white |
--om-text* / --om-panel* / --om-bg tokens |
tokens |
AdminPageHeader.astro |
PageHeader |
@repo/ui (nav) |
4. Inventory (migration targets, measured)
Section titled “4. Inventory (migration targets, measured)”- 23 React components, 24
.astrofiles. - 12 files with raw
<button>, 11 with hand-rolled status/badge divs, 16 with the hardcoded gray palette — the prime targets. - Local
ui/shims to retire:GapBtn,GapPill,GapStat,GapSection. - Biggest raw-Tailwind components (line count):
| Component | Lines | Notes |
|---|---|---|
OrganizationConnectionsPanel.tsx |
779 | The onboarding connections centerpiece — high value |
OrganizationsList.tsx |
759 | Table + filters + status → DataTable/StatusBadge/FilterChip |
admin/FeatureFlagDashboard.tsx |
460 | Already imports @repo/ui/form partially — finish it |
OrganizationPipelineSection.tsx |
399 | |
billing/BillingSummary.tsx |
376 | (distinct from the @repo/ui BillingSummary) |
billing/BillingConfigModal.tsx |
251 | → Modal / ConfirmDialog |
OrganizationOverview.tsx |
222 | |
OrganizationPromptSection.tsx |
217 |
5. Phased plan
Section titled “5. Phased plan”Each phase is independently shippable, gated on astro build green and
a visual-QA pass. Order by value ÷ risk.
- P0 — Foundation. ✅ Done (tokens +
@source). - P1 — Pilot. ✅ Done (onboarding UI).
- P2 — Retire the local
ui/shims. ReplaceGapBtn/GapPill/GapStat/GapSectionusages withButton/StatusBadge/StatTile/Surface, then deletesrc/components/ui/. Removes the parallel mini-DS so there’s one way to do it. Small, high-consistency. - P3 — Shared primitives sweep (mechanical). Across all components: raw
status pills →
StatusBadge; raw<button>→Button;text-gray-*/bg-gray-*/bg-white→ tokens. Touches ~16 files but each change is local and low-risk; do a few files per PR with QA. - P4 — Structural components. The big ones (§4):
OrganizationsList→DataTable/FilterChip;OrganizationConnectionsPanel→Field/Button/StatusBadge/Modal(coordinate with the connections-panel honesty fixes from the onboarding plan); billing modals →Modal/ConfirmDialog; forms →@repo/ui/form. One component per PR. - P5 — Layout/nav.
AdminPageHeader→PageHeader; considerAppShell/RailNavfor the sidebar; wrap the app in aSurface. Highest visual blast radius — do last, with the most QA.
6. Execution notes
Section titled “6. Execution notes”- Verification per PR:
astro check(types) +astro build(CSS/Tailwind resolves and bundles) + a manual visual pass. The build is necessary but not sufficient — it cannot catch a wrong-but-valid color. - Import conventions (match webapp): display components from
@repo/ui; form controls from@repo/ui/form(note: top-levelInputis the Astro component — usecontrolBaseon a native<input>insideField, or@repo/ui/form). - Fanning out: P3 is a good candidate for a multi-agent migration workflow
(one agent per file, mechanical pattern swaps,
astro buildgate) once the pattern is settled; P4/P5 are per-component and better done deliberately. - Token reconciliation (follow-up): internal’s
tokens.css+ inline@layer basestill define a few--om-*overrides that now lose to the DS. Once the sweep confirms nothing depends on the fork values, thin that file to only genuinely internal-specific tokens (mirrors webapp’s “gradually thinned local block”).
7. Risks
Section titled “7. Risks”- Whole-app restyle, unverifiable headlessly — the token adoption is the point, but pixels need human QA. Mitigation: per-phase QA gate; foundation is one revertible commit.
- Token cascade — DS vs. internal fork values resolve by import order (DS imported last → wins for shared tokens). If a screen looks off, it’s almost always a token override in the fork; reconcile per §6.
@sourcecost — scanningpackages/uigrows the Tailwind content set; build stayed fast (~13s), so no concern.- Behavioral parity — DS controls have their own a11y/keyboard behavior; re-skinning must not change form semantics. Covered by visual + interaction QA.