# Cohesion audit — danchilton.com design system Where the nine apps agree and where they have diverged. Generated from source at build time, so it cannot drift from the code it describes. Rendered board: https://danchilton.com/styleguide/ ## Cohesion audit 2 of 10 patterns are drifting, as of 2026-08-24. The 8 that are aligned: ListRow, DetailPanel, Overlay, Settings, AppShell, PageHeader, StatCard, Colour tokens. The headline: cohesion is **not** a token problem — it is that the same handful of compositions were built independently in each app. Anything generated against this system should route through the shared component rather than adding a tenth implementation. | Pattern | Status | Shared component | On the shared component | Own implementation | | --- | --- | --- | --- | --- | | ListRow | holding | `shared/ListRow.svelte` | bookmarks, cars, dexa, exercise, quotes, reading | — | | DetailPanel | holding | `shared/OverlayDetail.svelte` | bookmarks, quotes, reading | — | | Overlay | holding | `shared/Overlay.svelte` | bookmarks, exercise, quotes, reading, stats | — | | Sidebar wrapper | drifting | `shared/SidebarItem.svelte` | every app uses SidebarItem | BookmarksSidebar, CarsSidebar, ExerciseSidebar, QuotesSidebar, ReadingSidebar, StatsSidebar | | Settings | holding | `shared/settings/* · shared/lib/settings-state.svelte.ts` | bookmarks, exercise, hub, quotes, reading, stats | — | | AppShell | holding | `shared/AppShell.svelte` | bookmarks, cars, dexa, exercise, games, quotes, reading, stats | hub | | Primitive adoption | drifting | `ui/*` | exercise, reading, bookmarks, stats, quotes, cars, hub, dexa (via shared composites) | games — zero, by design | | PageHeader | holding | `shared/PageHeader.svelte` | all nine apps | — | | StatCard | holding | `shared/StatCard.svelte` | cars, dexa, exercise, quotes, reading, stats | — | | Colour tokens | holding | `shared/styles/theme.css` | all nine apps | — | ### Notes - **ListRow** (holding) — Closed 2026-08-25. All six per-app row/card components are deleted; one slot-based row covers both layouts. It drifted because the row knew about domain concepts — ontagClick, ontoggleArchive, ontoggleFavorite — so every app needed its own. Everything domain-specific now arrives through leading/meta/trailing: a domain word in the signature means the abstraction has slipped again. - **DetailPanel** (holding) — Closed 2026-08-25. DetailPanel is deleted; the record inspector is now an Overlay preset and, more importantly, is non-modal — opening a record no longer locks the list behind it. Cars and dexa still show records in the main pane, which is a deliberate layout choice rather than drift. - **Overlay** (holding) — Closed 2026-08-25. Modal, SideDrawer and DetailPanel are deleted; one component takes presentation (center/side/bottom) and modal as separate props. An ESLint rule now blocks importing ui/dialog or ui/sheet outside shared/components, which is how the third mechanism appeared in the first place. - **Sidebar wrapper** (drifting) — The leaf is shared, the container is not. Six wrappers repeat the same section-header + item-list shape. - **Settings** (holding) — Closed 2026-08-25. Five apps had five answers to "when does a setting save?"; that is now decided once — selects and switches commit on change, text defers to a sticky bar. Account-scope settings (theme, passkeys, sign-out) moved to the hub, which had no settings screen at all. The original entry listed cars as a stray, which was wrong: cars has no settings screen. - **AppShell** (holding) — Eight of nine. Hub is the login gateway and legitimately different — it has no sidebar and no app-switcher of its own. - **Primitive adoption** (drifting) — Partly closed 2026-08-25. Dexa now inherits primitives through ListRow, StatCard and PageHeader rather than importing any directly, so it picks up their focus and sizing behaviour; it still uses no ui/* import of its own. Games is the sanctioned exception. - **PageHeader** (holding) — Opened and closed 2026-08-25. Five screens had no page title at all, and three apps put the active filter in the h1 — so the page renamed itself as you browsed and nothing named the view. Every screen now renders exactly one h1 naming the view, with the filter as a scope chip. Named exceptions: the exercise live session, the hub login, and the games themselves. - **StatCard** (holding) — Opened and closed 2026-08-25. The same idea shipped four ways, and cars showed "39.9" under nothing but "GALLONS". One container now, and every value carries a caption saying what it means. tone has no destructive: red is for irreversible actions, never a number. - **Colour tokens** (holding) — Zero raw Tailwind ramps anywhere in the repo. This layer is not the problem — worth saying plainly, because it is the layer people assume is at fault.