Releases: glw907/cairn-cms
Release list
v0.91.1: restore the admin-sheet classes 0.91.0 dropped
0.91.1
Fixed
-
The shipped admin sheet restores the nineteen class rules
0.91.0silently dropped
(#12). The sheet compiles from Tailwind
scanning cairn's own source, and the0.91.0window migrated cairn's tree off the named type
steps onto the grammar roles, so Tailwind tree-shook the newly unused utilities out of the
compiled sheet:text-sm,text-xs,text-lg,text-base,text-2xl,text-3xl,gap-6,
tracking-tight,badge-ghost, and ten bracketed arbitrary sizes (text-[0.625rem]through
text-[1.0625rem]). Consumer admin markup resolves its classes from that sheet, so markup
riding any of the nineteen rendered unstyled on0.91.0while the upgrade guide promised no
rendering change. All nineteen are back through a labeled compatibility safelist in the sheet's
compile input, and the sheet's class inventory is now a gated contract: a snapshot test diffs
the built sheet's inventory against a committed snapshot, so a class can leave the shipped
sheet only as a deliberate, changelog-carried act.Consumers must: nothing, coming from
0.90.1or earlier; the sheet again carries every class
it did there. Coming from0.91.0, upgrade and any custom admin markup that lost its styling
(most commonlytext-sm) styles again with no markup change.
v0.91.0: the design language, enforced — grammar tokens, cairn-audit, and the admin-screens skill
The design-infrastructure initiative's window: three passes held under one cut. The admin's design language becomes named structure (the grammar tokens and role utilities), mechanically checkable (the cairn-audit bin, nine static and eleven rendered rules, the norms manifest), and loadable by an agent (the packaged cairn-admin-screens skill, installed by cairn-doctor --fix). Two Consumers must: lines below; the type-grammar adoption recipe is in Upgrade cairn.
Added
-
The admin's structural type and spacing scale is now named: ten CSS custom properties
(--cairn-type-title/subtitle/body/meta/label/chip,--cairn-gap-label/control/group/section)
declared once incairn-admin.css, outside the light and dark theme blocks, so a heading's size
or a control-to-control gap holds across both themes. Ten role utilities
(type-title/type-subtitle/type-body/type-meta/type-label/type-chip,
gap-control/gap-label/gap-group/gap-section) are the supported way to reach a token from
markup; each sets one property and nothing else. All ten ship in the compiled admin stylesheet
whether or not cairn's own screens use them, so a role is available to a custom admin route on
the strength of the documentation alone. See Admin grammar
tokens. -
src/lib/admin-toolkitand the built-in engine admin screens migrate their on-scale type and
spacing literals to the new role utilities and tokens, pixel-identically: the committed
admin-visual snapshots do not move. The toolkit's scoped styles carry the measured literal as a
var()fallback, since those components can mount outside the admin theme root; engine screens
reference the token bare. -
The palette/grammar boundary is now a documented contract: a site re-tunes the palette tokens
(--color-*) to its own brand and never redeclares a grammar token, since grammar names
structure and palette is the brand layer. -
The type scale closes at seven roles, each carrying a ruled line-height, and the grammar
inventory grows from ten custom properties to eighteen. Every type role gains a paired
--cairn-type-<role>--leadingtoken, and a seventh role,--cairn-type-headingwith its
type-headingutility (18px, bold, the display face), unifies the two heading recipes the admin
had been running side by side. Eachtype-*utility now declares
line-height: var(--tw-leading, var(--cairn-type-<role>--leading)), so aleading-*utility
still composes over a role rather than being overridden by it. cairn's own admin screens migrate
onto the closed scale: 129 named type steps, 120 twelve-pixel sites resolved ontotype-metaor
type-labelby the relationship each site expresses, both heading families ontotype-heading,
and the stray 9.6px and 11.2px slips ontotype-chipandtype-label. Five sites keep a value
off the scale and each carries a counted suppression directive naming why: the three wordmark
sites (the K4 keming fix) and the editor canvas and its document title, which set the editor's
own type scale rather than the admin chrome's. The losing heading family and part of the
twelve-pixel set change size by ruling, so theadmin-visualbaselines were regenerated once on
CI and read by eye. See Admin grammar tokens. -
Two container role utilities,
card-shellandcard-shadow, are the supported replacement for
hand-copying the admin's card-shell class string
(rounded-box border border-[var(--cairn-card-border)] bg-base-100, optionally with
shadow-[var(--cairn-shadow)]).card-shellcarries the shell's radius, hairline border, and
fill;card-shadowcarries its elevation, kept separate because a surface already nested inside
a shadowed container takescard-shellalone. Both ship in the compiled admin stylesheet
regardless of cairn's own usage, the same safelist discipline as the eleven grammar role utilities.
cairn's own admin components migrate every verbatim shell site onto the new utilities,
pixel-identically. See Admin grammar tokens. -
The package ships a new bin,
cairn-audit, the design-language audit. An install puts it on the
project's path, andnpx cairn-auditruns the static rules over the admin surfaces. The
substrate issvelte/compilerfor markup and the builtcairn-admin.cssfor resolution, never a
regex over source text: a class token is matched exactly, sotext-base(the size utility) and
text-base-content(the daisyUI color utility) can never read as the same class, and a class
written as an array, an object, a template literal, or aclass:directive is seen the same as
one written in a plain attribute. Configuration is one optionalcairn-audit.config.jsonwhose
every key defaults, so a site that has written no config still gets a meaningful run; a config
that names a scan path the tree does not have fails the run instead of quietly auditing less.
See Thecairn-auditCLI. -
Nine static rules ship, all error tier.
no-uncompiled-class: every markup class token compiles
into the built sheet or is a name the component's own<style>block defines.type-scale:
every font size a text-sizing token resolves to comes from a--cairn-type-*role.
gap-scale: an arbitrary margin, padding, or gap literal resolves to a--cairn-gap-*role or
lands on Tailwind's own spacing grid.stock-default-hazards: four stock daisyUI patterns
cairn's own recipes replace (badge-ghost, the focus-driven bare.dropdown, nativedisabled
on a guarded button, a flatbase-300card border), each finding citing the refuted alternative
on record.token-colors: no raw hex,rgb(), named-color, or pure-achromatic literal outside a
declared palette site.grammar-boundary: consumer CSS never redeclares a grammar token.
focus-parity: every hand-authored:hoverselector has a matching:focus-visibleor
:focus-within.motion-band: every declared duration lands in the admin's 150 to 250ms band,
andtransition: allnever ships.reduced-motion: every motion-bearing selector is named again
inside aprefers-reduced-motion: reduceguard. -
Suppression is a co-located, reasoned, counted comment:
cairn-audit-disable-next-line <rule-id> -- <reason>, in HTML,//, or/* */form. The reason
is required, a directive that silences nothing is itself a finding, and neither of those errors
can be suppressed in turn, so a build that passes by suppression reads as one. "Next line"
resolves to the next AST node rather than the next physical line, which is what makes a directive
above a multi-line element attach to that element's whole source range. This replaces the
file-plus-token JSON allowlists the repo's own gates carried, whose entries orphaned silently on
a rename and exempted a whole file at a time. -
Two repo gates graduate onto the packaged engine and keep their names and their places in CI.
npm run check:invisible-craftnow runsmotion-band,gap-scale, andtoken-colors, and
npm run check:admin-css-classesrunsno-uncompiled-class; both are thin wrappers, and the
hand-rolled comment strippers, brace-matched attribute scanners, and duration and color regexes
they carried are gone. Their two JSON side files go with them: every entry in
admin-css-classes-allowlist.jsonproved already dead, and each entry in
invisible-craft-budget.jsonresolved to a regex false positive the new rules recognize as
compliant, a site outside the audited scope, a declared palette file, or a co-located suppression
directive at the site itself. -
The package now ships a norms manifest: the admin's measured design norms as data, derived by
rendering the admin screens in both themes and reading the computed styles of twelve semantic
roles.npx cairn-audit norms <selector-or-role>answers from it, so a developer or an agent
building a new admin surface reads a measured control height, padding ratio, border treatment,
radius, or type recipe instead of inferring one from a screenshot. Each entry states the band, how
many distinct element sites it rests on, whether a ratified decision settles it, and every caveat:
an entry an open design question governs is flagged and never reads as settled, a band resting on
one site says so, and a palette-dependent property is stored as a relationship
(var(--cairn-card-border), acolor-mixformula) rather than a resolved Warm Stone value, so a
re-tuned palette invalidates nothing. See Thecairn-auditCLI. -
cairn-audit's rendered mode has a working harness. It renders every configured admin page in
both themes, always, since a rendered rule can pass one theme and fail the other; it never starts
a server (BASE_URL must already answer, or the run fails naming the URL it tried), and it imports
Playwright dynamically from the consumer's own install, printing a one-line
npm i -D playwright && npx playwright install chromiuminstruction when it is absent. A rule
declares which interaction states it reads from (a rest render, an open menu, a keyboard
focus-visible pass), so a rule that never needs a menu-open pass never pays for one. Exemptions
live in a page+selector+reason JSON allowlist (rendered.allowlistin
cairn-audit.config.json), the same reason-required discipline the static suppression comments
carry; an allowlist entry whose selector matches nothing the run actually visited is reported as
a stale entry rather than silently doing nothing, the same fail-loud discipline every other part
of the engine holds. See Thecairn-auditCLI. -
`npx cairn-aud...
v0.90.1: ListToolbar select sizing and menu-facet disclosure/a11y fixes
Three fixes inside ListToolbar, all surfaced by a fresh-context coherence read of a consumer's Members screen. No consumer action is required.
Fixed
ListToolbar's'select'facet no longer pins to daisyUI's own fixed 320px clamp (width: clamp(3rem, 20rem, 100%), whose middle value is a fixed length, not a container-relative one); it now sizes to its own content (width: auto; max-width: 100%), so a short-option facet renders narrow and a container of real-world facets (four selects plus a menu facet plus search) fits one line at the widths the Members-refinement recomposition targeted. A select facet's border also now shares the'menu'facet's ownvar(--cairn-card-border)treatment (via the same--input-colorcustom property daisy's compiled rule already reads), so the two read as one control family rather than two vocabularies side by side.- A
'menu'facet's option list and the overflow disclosure's own panel no longer open on keyboard focus alone: daisyUI's.dropdownshows.dropdown-contenton:focus-withinfor free, which let Tabbing onto a trigger reveal the panel whilearia-expanded(driven purely by this component's owndropdown-openclass) stayedfalse. Both disclosures' visibility now tracksdropdown-openexactly, soaria-expandedalways matches what is actually shown. - A
'menu'facet's option list carries real ARIA menu semantics (role="menu"withrole="menuitemradio"options, previously bare buttons in a plain<ul>): the applied value is exposed programmatically viaaria-checked, not just the sighted-only check glyph, and a roving tabindex makes only the focused option a Tab stop, with ArrowUp/ArrowDown/Home/End moving that focus, wrapping at the ends, mirroring the segmented filter's own keyboard model. Escape-closes-and-returns-focus and the click-to-select behavior are unchanged.
v0.90.0: ExpandableRow graduation, a ListToolbar menu facet, formatPhone
0.90.0
Added
ExpandableRowgraduates into theadmin-toolkitsubpath (second consumer landed:
aksailingclub-org's ownsrc/admin-club/toolkit/ExpandableRow.svelte), carrying three visual
fixes from the Members-refinement audit: the summary row's hover wash now reaches the sticky
trigger cell with its own opaque tint (a transparent-based wash would let content scrolling
underneath a pinned column show through); the trigger cell follows zebra parity
(base-200on striped rows) instead of a hardcodedbase-100, closing the right-edge seam;
and the expanded panel's<td>getsbackground: var(--color-base-300)with an inset
top-border, so the drawer reads as its own surface instead of merging with the zebra stripe.
All of the component's contracts (colspan mechanics, the 390 column-hiding pattern,
aria-expanded/keyboard behavior) are unchanged.ListToolbargains adisplay: 'menu'filter variant: a quiet bordered button showing the
facet's name at rest ("Standing") and its applied value in-control ("Standing: Overdue") with
a separate inline clear affordance, opening a keyboard-operable option list (focus moves to
the first option on open, Escape and outside-pointerdown close it, only one facet stays open
at a time). The existing'select'variant is unchanged behaviorally and now shares the same
30px control height and 13px text as every other control in the row.formatPhone(phone: string): stringjoins theadmin-toolkitformatters: a stored E.164 NANP
number (+19075550100) renders as the hyphenated907-555-0100for a table cell; anything
outside that shape passes through unchanged.
Changed
ListToolbar's controls row recomposes from a rigidrepeat(auto-fill, minmax(11rem, 1fr))
grid to a wrapped flex row (searchflex: 1 1 240px), so five promoted facets plus search fit
one line at a realistic container width and wrap cleanly at narrow widths instead of forcing
ragged per-cell wrapping. The applied-pills row is retired;computeAppliedFiltersnow only
feeds the count line. The 13px count line gainstabular-nums, as doesPagination's range
line.StatusChip's border demotes frombadge-outline's full-strengthcurrentColor(which read
as a clickable button) tocolor-mix(in oklab, currentColor 35%, transparent), the value
verified against zebra stripes in both themes; thesmsize keeps its5remmin-width floor.OfficeList's header stack (eyebrow/title/subtitle) no longer leaks the browser's default
h1/pmargins past its owngap-0.5(flex does not collapse child margins), so the
rendered gaps settle to the intended few px instead of a ~32px leak; the header's action slot
no longer stretches full-width below thesmbreakpoint.- cairn's own
ConceptListcreate-button label now resolves its singular noun through the same
itemNoungrammarPaginationandListToolbaruse, instead of a bespoke fallback, so a
concept that only declares a plurallabel(the showcase'spages, before this change) no
longer reads "New Pages" where "New Page" was intended.
No consumer action is required. ExpandableRow and formatPhone are new, additive exports;
ListToolbar's 'menu' display value widens an existing string union; every other change is a
visual refinement inside cairn's own admin-toolkit and built-in admin screens.
v0.89.1: itemNoun grammatical number on toolkit count lines
Patch release: grammatical number on the admin-toolkit's count and range lines.
Added
itemNoun(count, label)andItemLabel({ one: string; many: string }) join the
admin-toolkitsubpath's formatters, andPagination's andListToolbar'sitemLabelprop
widens tostring | ItemLabel. An{ one, many }pair picks the grammatical number on every
count and range line ("1 class", "6 classes"), graduating the fix aksailingclub-org's own
toolkit proved for the "1 households" defect. A plain-stringitemLabelrenders exactly as
before.
No consumer action is required.
v0.89.0: the admin toolkit — a public subpath, and cairn's admin re-expressed on it
The admin-toolkit organization pass: a new public subpath, and cairn's own admin re-expressed on it.
Added
- A new public subpath,
@glw907/cairn-cms/admin-toolkit, packages the general-purpose admin
components and formatters a consumer site's own admin build proved first:PageHeader,
ListToolbar,AdminTable,StatusChip,Pagination,EmptyState, and the
formatMoney/formatCivilDate/formatTimestamp/ageFromBirthdateformatters. A site building
its own/admin/screen reaches for the same shared vocabulary cairn's own admin now uses,
instead of hand-rolling a bespoke parallel. See the admin-toolkit reference page
(docs/reference/admin-toolkit.md).
Changed
- cairn's own admin screens now build on that toolkit.
ConceptList,CairnMediaLibrary,
ManageEditors,VocabularyAdmin,CairnTidySettings,NavTree, andHelpHomeall render
their page header through the toolkit'sPageHeader, converging five ad hoc header markups
into one visible idiom: an optional eyebrow, the display-face title, an optional muted meta
line, and one right-aligned action.ConceptListandCairnMediaLibraryalso converge their
search, filter, count, table, and pager markup ontoListToolbar,AdminTable,StatusChip,
andPagination. A site that already matched the old per-screen headers and lists by eye may
notice the rhythm settle to one shape; no prop or route contract changed.
Fixed
- The admin CSS build's
@sourcescan never includedsrc/lib/admin-toolkit, so any daisyUI or
Tailwind class used only there silently never compiled. A new CI gate,
check:admin-css-classes, checks every admin component template's class tokens against the
built sheet so a class miss like this fails CI instead of shipping.
No consumer action is required. The new subpath is additive, and the visible header convergence
touches only cairn's built-in admin screens, with no exported contract behind it.
v0.88.3: a blessed daisyUI safelist for the admin, plus the banked showcase window
0.88.3
Changed
- A new documented safelist source,
src/lib/components/admin-css-safelist.ts, makes the admin CSS
build compile a curated blessed set of daisyUI 5 classes that no shipped admin component references
yet: thestats/stat-*family,table-zebra/table-xs,toastplus its placement modifiers,
theindicator/status/joinplacement and orientation modifiers, andbadge-soft/
badge-outline/badge-dash. The pass-B "admin CSS class-inventory gap" harvest finding named the
trap (a daisy class only styles anything once cairn's build compiles it); the ASC admin-toolkit
design survey defines this blessed set, so a site-authored admin screen can use the vocabulary ahead
of any cairn component adopting it. No consumer action is required: this is an internal build change
no consumer imports. - The showcase's banked
IntroLedgersyncs to cairn.pub's ratified masthead form: the title
at the full step-5 masthead grade with the scaled mark, the answer one grade up, the summary
as full-ink paragraphs (summaryis nowstring[]), and theleadInprop replaced by
ledgerTitle, a tracked small-caps label on a running hairline. - A new showcase theme component,
Carousel.svelte, banks cairn.pub's screenshot carousel in
generic form: stacked cross-fading slides with a caller-supplied label row, one 7-second
auto-advance cycle that then rests, pause on hover and focus, permanent manual control on any
dot click, and no auto-advance or transition under reduced motion. - The showcase's content component set banks a new
micro-cta, the cta's compact
further-reading sibling: the same label-plus-link shape without the primary/secondary
variant choice, plus an optional note line for a short gloss on where the link goes. The
styleguide's reading surface demonstrates it beside the fullcta.
No consumer action is required for the three showcase template components either; existing sites
keep their own copies until they choose to pull the updated forms.
v0.88.2: the template's nav wiring and the arm-index gate
The chassis-nav window: the template's nav wiring and a docs coverage gate, the pre-Topo pass.
Changed
- The showcase's public header nav now renders from
site.config.yaml'smenus.primary
(resolved through the engine'sextractMenuby the root layout server load) instead of a
hardcoded array, so an editor's/admin/navchanges reach the rendered site. A lean
src/theme/site-config.tsmodule now owns the template's oneparseSiteConfigcall, and
cairn.config.tsre-exportssiteConfigfrom it, keeping the full adapter out of the client
bundle. The footer's nav stays hardcoded on purpose; its list is different content, not a copy
of the primary menu. - The tutorial's nav milestone teaches the same shape: parse the site config in a lean module,
read the menu in a+layout.server.ts, and render fromdata.nav, instead of importing the
config module in a client script. The theme tutorial (build-a-theme.md) is now linked from
the docs front door. - A new repo gate,
check:arm-indexes, fails CI when a published docs page is missing from its
arm's index (the inverse ofcheck:docs's dead-link direction).
No consumer action is required. The template changes land in newly scaffolded or copied sites;
an existing site keeps its own chrome.
v0.88.1: dev-backend fixtures, mermaid passthrough
The dev-backend pass. Fixes the two local-development blindspots and one renderer gap:
- A mermaid fence now passes through the build-time Shiki highlighter untouched, keeping its
language-mermaidclass on the<code>element. A site's client-side mermaid renderer can key on the class directly; the site-side marker-plugin workaround is no longer needed. @glw907/cairn-cms-devseeds two published fragments (manifest rows plus bodies, one mirroring the showcase'strail-safety-notice), so the fragment picker, include-chip title resolution, and preview splice all work undervite dev.@glw907/cairn-cms-dev's seeded media objects are real, browser-decodable 240x160 PNGs (a distinct solid color per asset) instead of a 12-byte stub, so the Media Library shows real thumbnails locally instead of "Image missing" on every tile.
Also in this window: a getPlatformProxy media-delivery smoke so the miniflare RPC-serialization class fails in CI rather than on a consumer's first vite dev; the check:custom-surface and check:chassis-boundary gates wired into CI; the editor's media chip no longer decorates a media: token inside a fenced code block, and figureRoleAtLine reads the anchored attrs group; theme-import and preview-iframe documentation.
Consumers must: nothing. All changes are additive, dev-package-internal, or non-breaking behavior fixes.
v0.88.0: the admin access map, collapse defaults, icon overrides, and attention badges
0.88.0
Added
- The admin access map: a site declares
defineAccess(roles, map)once, mapping engine screens
and its own/adminroutes to the role names admitted to each, and one function,canReach,
is the single authority the guard's newrequireAccesshelper, every engine route gate, and
the sidebar resolver all read, so route enforcement and nav visibility can never drift apart.
Capability stays the floor (owner always passes,nonenever reaches anything,editors
keeps its owner-only floor); the map only narrows an editor-capability session further. A site
that declares no map sees no behavior change. See Restrict admin access by
role. New log eventauth.access.denied(warn:
email,role,target) makes a map denial observable. NavLayoutSectiongainscollapsed?: boolean, the group's declared starting state for a
visitor with no persisted nav-collapse cookie (defaultfalse, today's behavior); the
existing cookie still wins entirely once any header is touched.NavLayoutEngineRefgainsicon?: AdminNavIcon, overriding the engine-owned glyph for that
door, and the bundledAdminNavIcon/ADMIN_NAV_ICON_NAMESallowlist widens from nine names to
twenty-seven (the full working set surfaced by ASC's own declared sidebar).- A new
attentiondependency (ContentRoutesDeps.attention,CairnAdminDeps.attention),
awaited once per request and never cached: a site returnsAttentionItem[](href,count,
an optionallabel) and the shell renders a quiet pending-work pill on the matching visible
nav entry, summed on a collapsed section's header, dropped at zero, and dropped entirely for
an item whosehrefthe current session cannot see (a count never leaks to a role that can't
act on it).
No Consumers must: action: every addition above is additive, and a site that declares none of
it sees no behavior change.