# Spry conversion conventions (goal 41, binding for T3–T10) ClankerSaddle runs on the Spry CSS framework that ships inside libspry. This document is the binding contract for every page task in the UI/UX goal: what Spry provides, what `app.css` owns, how classes map from the old stylesheet, and the rules for new markup. ## How the framework is loaded `main.html` and `login.html` link, in this order: 1. `spry.css` — core + default theme (light palette, dark palette on `prefers-color-scheme: dark`). This IS the product default; no `data-theme` is set anywhere by default. 2. `spry-theme-{airy,dwarf,formal,glassy,h2o,living-sky,modern}.css` — the seven optional themes, loaded but INERT until `` is set. T2 wires the account-settings picker that sets that attribute; until then they cost a few KB of cache and do nothing. 3. `app.css` — the app component layer, always LAST so it wins ties. Color mode is `data-mode="auto"` on `` (spry's default behavior — omitting the attribute is also auto). Never hard-code a mode. ## Where styles live — the layering contract - **spry.css owns every primitive**: typography, links, focus rings, buttons (`button`, `.btn`, `.btn-primary/danger/outline/ghost/link`, `.btn-sm/lg/block`, `.btn-group`), forms (`input/select/textarea` element styles, `.field`, `.label`, `.hint`, `.is-invalid/valid`, `.choice(s)(-inline)`, `.switch`), tables (`table`, `.table-wrap`, `.table-striped/hover/compact/borderless`), cards (`.card`, `-flat/-header/-body/-footer/-title/-subtitle`), alerts (`.alert` + `-danger/success/warning/info`), badges (`.badge` + `-danger/success/warning/info/muted`), progress/meter/spinners, dialogs (``, `.dialog-sm/lg/titlebar/title/close/body/footer`, `[popover]`, `.popover-menu`, `.anchor`/`.popover-anchor`), expanders (`details`/`summary`, `.expander-group`), hero/avatar/otp/ drawer/pagination, topbar/nav/menu/submenu/breadcrumb/tabs, layout utilities (`.container(-sm/-lg)`, `.stack`, `.cluster`, `.center`, `.with-sidebar`, `.block/inline/hidden/visually-hidden`, width/ position/overflow utilities), spacing utilities (`.{m,p}{t,r,b,l,x,y}-{0..5}`, `.mx-auto`), and text utilities (`.text-{xs..2xl}`, `.text-muted/accent/danger/success/warning/info`, `.fw-*`, `.mono`, `.num`, `.truncate`, `.clamp-2/3`, `.measure`, `.lead`, `.small`). - **app.css owns only ClankerSaddle components**: status/event badges (the `.badge-*` family below), the goal identity, comments and footnote treatment, blocks + plan review, the event log, advisor chat, the terminal, toasts, the quote popover, the login shell, `.cards` grid, `.grid-2`, and chrome shims (`.brand-emblem`, `.nav-auth`, `.nav-user`, `.site-footer`). - **Never restyle a Spry primitive from app.css** and never re-declare Spry's reset/typography. If a page needs something Spry has, USE the Spry class; only add to app.css when the component is genuinely app-specific. ### app.css append-only discipline app.css is organised in sections with `==== header ====` comments. Within a section, new rules are APPENDED under the section header with a `/* comment */` naming the component and the reason (which page, which plan/goal). Never edit another task's rules in place — merge conflicts in this file are append-per-section and resolved trivially. Sections may be added at the END of the file. ## Class mapping (old app.css → Spry) | old class | use instead | | --- | --- | | `body` 64rem column | `.container` on `
` (72rem, fluid padding) | | `.form-row` | `.field` (label + input inside) | | `button.primary` | `.btn-primary` | | `button.approve` | `.btn-primary` | | `button.reject` | `.btn-danger` | | `form.inline` | `.cluster` (wrap the fields/buttons) | | `label.inline` (checkbox row) | `.choice` | | `.muted` | `.text-muted` | | `.error` (text color) | `.text-danger` | | `details` custom | bare `details`/`summary` (Spry styles them) | | `.card` | `.card` (Spry) + `.card-body` etc. as needed | | `header` custom chrome | `.topbar` + `.topbar-brand` + `ul.nav`/`.has-submenu`/`ul.submenu` | | `.nav-menu`/`.nav-dropdown` | deleted — see topbar row above | | tables | bare `` (+ `.table-hover`/`.table-compact` where dense) inside `.table-wrap` when wide | Kept as app components (now tokenised): `.comment`, `.footnote`, `.block-card`, `.plan-review`, `.plan-popover`, `.review-banner`, `.goal-*`, `.log-*`, `.event-*`, `.term-*`, `.terminal`, `.chat-*`, `.toast*`, `.quote-*`, `.ssh-key`, `.badge-*` status family, `.cards`, `.grid-2`, `.status-head`, `.status-corner`, `.waiting-drafts`, `.rule-card`, `.chip-remove`, `.yeehaw-*`. App tokens: `--goal: var(--spry-accent)` and `--goal-surface: var(--spry-accent-soft)` (goal identity derives from the theme accent; goal pages read as themselves through layout and badges, not a competing hue). All other old app tokens (`--ink`, `--line`, `--surface`, `--accent`, `--success`, …) are DELETED — use the `--spry-*` equivalents. ## The badge-colour contract Every status/health/event badge consumes a semantic Spry variable — NO hard-coded hex anywhere. A `.badge-` rule in app.css maps to exactly one of: - **muted** (`--spry-bg-alt` bg, `--spry-fg-muted` fg): draft, paused, cancelled, offline, reference, command, created, shell-reset, mode-implement, and their goal-* twins. - **queued** (`--spry-warning-soft`/`--spry-warning`): queued, enqueued. - **info** (`--spry-info-soft`/`--spry-info`): assigned, planning, started, resumed, dispatch, query, question, user-comment, co-developed, mode-investigate, chat-awaiting_answer, kind-task, state_change. - **warning** (`--spry-warning-soft`/`--spry-warning`): working, blocked, block, suspended, review_requested, review, completion_review, mode-review, busy, chat-running, suggestion-pending, yeehaw, and the block-raised family. - **success** (`--spry-success-soft`/`--spry-success`): done, idle, active, complete, completed, progress, mode-test, chat-idle, suggestion-approved, and the answered family. - **danger** (`--spry-danger-soft`/`--spry-danger`): failed and every rejected/failed goal transition. - **accent** (`--spry-accent-soft`/`--spry-accent`): plan, plan_review, rfc, mode-orchestrate, block-plan, admin, capability, kind-goal, goal-plan-submitted. (The old violet plan token retired: plans and goals now ride the theme accent.) - **neutral inset** (`--spry-bg-inset`/`--spry-fg-muted`): comment, tool. - Health badges are FILLED (strong var as bg, `--spry-accent-fg` fg) so they read at a glance on the saddles page. New statuses in page tasks: add ONE line to the matching group in the badge section of app.css, never a new hex. ## Markup rules for page tasks (T3–T10) 1. **Page shell**: every page's content sits directly under `
`; page-level stacks use `.stack` or `.stack`-like sections with spacing utilities. Detail pages may adopt `.with-sidebar` from T5/T6 for side navigation. 2. **Spacing** comes from Spry utilities (`.mt-3`, `.p-2`, `.gap-*` via `.cluster`/`.stack`), not bespoke margins. If you write `margin: …` in app.css, you need a reason comment. 3. **Every colour via `--spry-*`** (or an app token derived from one). Dark mode must work with zero extra rules — if you find yourself writing `@media (prefers-color-scheme)`, stop: you hard-coded something. 4. **Buttons**: the element default is already Spry-styled; add `.btn-primary` for the one primary action per view, `.btn-danger` for destructive, `.btn-ghost` for quiet/inline actions (the old `button.inline`), `.btn-sm` inside dense rows/tables. 5. **Forms**: `
…

…

`. Checkboxes/radios use `.choice` / `.choices(-inline)`; toggles use `.switch`. Wide editors (textareas) live in `.field`s capped by `.form-rows .field { max-width: 34rem }` — group a page's edit fields in `.form-rows`. 6. **History folds / actions surfaces**: per the goal plan, the collapsed-history expander pattern and "actions surface" reshaping (modals via native `` with `.dialog-titlebar/-title/ -close/-body/-footer`) arrive WITH the page tasks (T3–T9), not in this foundation. When you add one: `` with a `.dialog-titlebar` carrying `

` and a `.dialog-close` button, body in `.dialog-body`, actions right- aligned in `.dialog-footer`. 7. **Expanders**: plain `

`/`` (Spry styles them; group siblings in `.expander-group` to fuse borders). 8. **Badges**: `` per the contract above; event kinds on the log/narrative use the same semantic groups via `.event-kind-*` in app.css. 9. **Wide tables** sit in `.table-wrap`; dense tables add `.table-compact`, row-hover `.table-hover`. 10. **Login is the reference** for form markup conventions (`src/pages/login.html`). ## The breadcrumb partial `src/pages/breadcrumb.html` is a `pstm-fragment` partial (the `progress_feed` pattern) that renders a Spry `.breadcrumb` from a `crumbs` array of `{href?, label}` items — the last crumb carries `aria-current="page"`. Include it as: Adoption by detail pages happens in T5/T6/T9 (it needs each page's entrypoint to expose a `crumbs` vector — do not fake it client-side).