chirp-ui 0.10.0
Full notes: https://chirp-ui-showcase-production.up.railway.app and the release page.
[0.10.0] - 2026-06-15
Added
-
Added
data_grid— a server-driven interactive data grid composite
(chirpui/data_grid.html) backed by a new typed Python state helper
chirp_ui.grid_state(Chirp-agnostic, stdlib + dataclasses, in the
route_tabs.pymold). The helper exposesColumn,GridSort,ColumnSort,
SelectionState, and the plain functionsparse_sort,sort_columns,
selection_state,column_aria_sort,sort_query(exported from
chirp_ui.__all__and registered as Kida template globals).sort_columns(...)projects each column into aColumnSortcarrying the
aria_sortvalue and the fully-built togglenext_urlthe macro renders but
never computes, so the server'sORDER BYand the rendered headers cannot
drift. The grid ships sortable columns (real<button class="chirpui-table__sort">
in a<th aria-sort=…>, single-sort invariant, stable sort key — never
header|lower, focus retained on the activated button after the swap), row
selection bound to a controlledselection_barvia one idempotent
chirpuiGridSelectionAlpine factory (page-scoped select-all with the
JS-propertyindeterminatestate, livearia-livecount, server-seeded so it is
correct with JavaScript off). The factory re-seeds from server-checked rows after
every swap — including the load-morebeforeendappend, via a scoped
htmx:afterSettlelistener registered ininit()— so select-all then load-more
correctly drops select-all to indeterminate (only the original page is selected
of the now-larger set) and a server-checked appended row is re-adopted (WCAG
4.1.2). Each row checkbox's accessible name comes from an optionalrow_labels
list (clean plain text) and never the rendered first cell (which may be rich
HTML or an empty spacer) — it falls back to the stable row id, so every label
stays distinct and screen-reader-clean. Sticky header + opt-in sticky first
column are pureposition: stickywith token-driven z-index (incl. a top-left
corner override) and a directional seam shadow (color-mixover
--chirpui-border) so scrolled content visibly slides under the pin. v1 pins the
first visual column (no per-columnColumn(frozen=…)— deliberately not
shipped rather than advertise a no-op). HTMX load-more uses thedata_grid_rows
fragment (beforeend). The previously-orphaned.chirpui-table__sortCSS hook is
finally emitted.table()gained additive
selectable/select_name/selection/row_id/sticky_first_col
params (defaults off = byte-identical; the legacysortable=/sort_url=
header|lowerpath is unchanged and documented as legacy).selection_bar()
gained an additivecontrolled=mode. Proven bytests/test_grid_state.py,
tests/test_components.py,tests/js/grid_selection.test.js, and the
tests/browser/test_data_grid_gauntlet.pya11y gauntlet (single-active
aria-sort, focusable sort button, focus retention, three-state select-all,
sticky pinning, load-more without duplicate ids, axe-clean). Differentiator vs
TanStack: Python/HTMX-native — the server owns the sort and selection state;
load-more replaces client virtualization. Cross-page "select all N matching" is
out of scope for v1. Seedocs/patterns/data-grid.mdand
docs/COMPONENT-OPTIONS.md § Data Grid
(#200). -
Added Docs/API/Releases/All scope chips to the Cmd+K search modal so results can be narrowed in place, reusing the same API/release predicates that drive result grouping and ranking; styled the chips to mirror the /search filter affordance and reset the scope to All when the modal closes. Gave the JS-disabled /search page a styled server-side fallback that lists every indexed page grouped by section, guarded by a kida render test that renders the real search.html noscript fragment. Authored Playwright proofs that the preload modes are honored (smart/lazy issue zero index fetches on a cold page; hover, focus, and Cmd+K each warm the index once before the modal opens; immediate warms once after idle) and that the search combobox wires aria-activedescendant to the active option id on both the modal and the /search page, clearing it on close and returning focus to the trigger.
-
Added
combobox— a typeahead autocomplete input following the WAI-ARIA combobox pattern. Arole="combobox"text input filters arole="listbox"of server-rendered options as you type (client-side substring match); ArrowDown/ArrowUp rove the visible options viaaria-activedescendant(the active option carriesaria-selected="true"), Enter selects the active option, and Escape / click-outside close the list. Selecting fills the visible input with the option label, writes the option value to a hidden input submitted undername, and dispatcheschirpui:combobox-selected{value, label}. Options are server-rendered, so the field degrades to a plain text input with JavaScript off. Behaviour lives in the idempotentchirpuiComboboxAlpine factory. Ships experimental with a keyboard/a11y browser gauntlet (#201). -
Added
context_menu— a right-click / keyboard context menu anchored at the pointer. Wrap any non-interactive region incontext_menu(items=[...]); right-clicking (or the ContextMenu key / Shift+F10 / Enter on the focused region) opens arole="menu"panel at the cursor with full keyboard support: roving tabindex via ArrowUp/Down/Home/End, Enter/Space to activate, Escape to close and return focus, click-outside dismissal, and viewport clamping. Items acceptaction/href, an icon,disabled(focusable-but-inert per WAI-ARIA), anddefault/danger/mutedvariants; selecting one dispatcheschirpui:context-menu-selectedwith{label, action?, href?}. Behaviour lives in the idempotentchirpuiContextMenuAlpine factory. Ships experimental with a keyboard/a11y browser gauntlet (#202). -
Added
date_picker— a date and date-range form control. A readonly display input opens arole="dialog"calendar popover with arole="grid"ofrole="gridcell"day buttons; the canonical value is an ISOYYYY-MM-DDstring in a hidden input (range mode adds a second hidden input underend_name, default{name}_end). The calendar is fully client-rendered, so month navigation has no server round-trip. Keyboard: roving tabindex with Arrow (Left/Right ±1, Up/Down ±7), Home/End (week), PageUp/PageDown (month), Enter/Space to pick, Escape to close and return focus; focus movement clamps into[min, max]so it never strands on a disabled day.min/max(ISO) disable out-of-range days, today is marked witharia-current, and day names announce selection/range/today state. Range mode highlights the span and orders endpoints regardless of click order. Behaviour lives in thechirpuiDatePickerAlpine factory. Ships experimental with a keyboard/range/min-max/axe browser gauntlet; v1 is Sunday-first, English labels (localization is a follow-up) (#201). -
Added a route-context rail region to
app_shell(context_rail=true+ a
context_railslot on the macro; acontext_railflag +{% block context_rail %}
onapp_shell_layout.html). It renders an optional trailing-edge secondary region
(<aside id="chirpui-context-rail">, a labelledcomplementarylandmark) for an
inspector/detail panel that updates with the current route. The update protocol
mirrors shell actions: a route response includes an out-of-band fragment targeting
the outlet — use the newcontext_rail_oob()helper inchirpui/oob.html. Works
standalone with htmx OOB; under Chirp, boosted navigation carries the rail fragment
in the same response. Responsive: the rail stacks under main below 72rem and joins
the single column below 48rem. Width is--chirpui-context-rail-width(default
20rem);context_rail_variant="muted"tints the surface. This is the one blessed
shell-region composite per the Application Chrome Posture ADR
(#195). -
Added a
make pre-pr/poe pre-prgate (fullpoe ci+ a docs-chrome browser/a11y smoke covering folder-toggle, landmark, and axe proofs) and a non-required CIbrowser-smokejob, sincepoe ciintentionally does not run the Playwright suite. -
Added a client-side Alpine runtime self-check to
chirpui-alpine.js: when
chirp-ui components that require Alpine render but Alpine never initializes (a missing,
blocked, or misconfigured CDN script; a CSP block; a network error; or
alpine=False), the runtime now logs a loudconsole.warnwith the likely
causes instead of leaving every interactive component silently inert. The
end-to-end Alpine-liveness proof (tests/browser/test_alpine_lifecycle.py,
including the new silent-disable case) is now wired into the
test-browser-chromegate, and a Vitest unit test covers the self-check logic
in the fastpoe cisuite (#189, #190). -
Added a complete, manifest-driven on-site component index at
site/content/docs/components/all.mdthat lists every public Chirp UI macro grouped by category with its one-line description, generated byscripts/build_component_index.py(with abuild-component-index-checkpoe task wired intoverify-generatedandmake release-preflight) and guarded bytests/docs_contracts/test_onsite_component_coverage.py, so all public components are discoverable on-site without leaving for GitHub or running a local app, and the catalog can never silently drift behind the registry. -
Added a multi-select (token-pill) mode to
combobox(multiple=true). Selecting an option adds a removable pill, keeps the list open, and clears the query; each selected value submits as a repeated hiddennameinput. Backspace on the empty input removes the last pill, the per-pill remove button removes a specific one, and already-selected options drop out of the list. The control wraps the pills and a borderless flexing input. Single-select is unchanged. Multi-select requires JavaScript (pills/values are Alpine-managed); v1 starts empty. Proven by the combobox browser gauntlet (pills, hidden-input submission, remove, Backspace, axe) (#201). -
Added a scoped AGENTS.md steward network with audit and SME-question records so
agent work loads local domain invariants, review hooks, and verification
expectations at major repository boundaries. -
Added an
id_suffixparameter toshell_actions_bar()andshell_action()
(chirpui/shell_actions.html). Passing e.g.id_suffix="-drawer"namespaces the
overflow dropdown id and each menu-action id, so the same actions bar can render
in two regions at once (a topbar copy plus a mobile-drawer copy) without colliding
duplicate element ids.shell_actions_barthreads the suffix down into each
shell_action. The default""preserves the canonical ids, keeping every
single-instance render byte-identical
(#224). -
Added an always-available
topbar_leadingzone to both shell entry points
(atopbar_leadingslot on theapp_shell()macro; a{% block topbar_leading %}
onapp_shell_layout.html). It renders a non-anchor leading region before the
brand — the correct home for a hamburger / back / command affordance. Interactive
controls belong here, never in thebrandslot/block (which nests inside the
brand<a>, producing invalid HTML and hijacking the click). The built-in
nav_drawerhamburger and the layout's collapsiblesidebar_togglerender in the
same wrapper. Seedocs/patterns/navigation.md § Leading affordance
(#220). -
Added an opt-in mobile nav drawer to
app_shell(nav_drawer=trueon the
macro;nav_drawer=Truein the render context forapp_shell_layout.html).
Below the 48rem breakpoint a topbar hamburger opens the sidebar as an accessible
off-canvas slide-over, and — withcontext_rail=true— a second trigger opens
the rail. It is a thin, additive affordance over the existing regions: the same
sidebar/rail<aside>is repositioned (no duplicated nav), soaria-current,
syncNav, and OOB swaps keep working; unset, the shell is byte-for-byte
unchanged (the horizontal-strip fallback). The open/close behavior — focus trap,
Esc, scrim dismiss, body scroll-lock, focus return, link-dismiss, and
auto-close when the viewport grows past the breakpoint — is vanilla JS in
shell_runtime_script()with no Alpine dependency ("works without Chirp,
better with Chirp"). The open drawer isrole="dialog" aria-modal="true"and
named (sidebar viaaria-labelledby, rail via itsaria-label); the rail's
close control is injected by the runtime so it is never stranded by the rail's
OOB content swaps. Proven end-to-end bytests/browser/test_shell_nav_drawer_gauntlet.py
(in thetest-browser-chrome-checkgate). Seedocs/patterns/navigation.md § Mobile Nav Drawer
(#196). -
Added arrow-key result navigation to
command_palette. The search input is now arole="combobox"over therole="listbox"results: ArrowUp/ArrowDown rove therole="option"items viaaria-activedescendant(the active item carriesaria-selected="true"and an--activehighlight), Enter activates the active item, and each fresh htmx result set auto-highlights its first item. A newcommand_palette_item(id, label, href|action, hint)helper renders results as roving options; legacy raw-HTML results stay Tab-navigable. Behaviour lives in the newchirpuiCommandPaletteAlpine factory (which also owns Cmd/Ctrl+K open). Proven by the command-palette browser gauntlet, now in thetest-browser-chromegate (#201). -
Added layout affinity conventions, workspace shell recipes, and relationship-owned spacing contracts so composed Chirp UI screens handle rhythm, pressure, wrapping, and local overflow with less page-specific CSS.
-
Added the Tracks learning-path feature: a top-level section that assembles separate documents into one ordered, navigable pillar page (data-file model via
site/data/tracks.yaml), with an LMS card index, in-track progress + prev/next, sidebar scroll-spy, and localStorage resume. -
Dogfooded every shipped chirp-theme template family on the live docs site (blog, tutorial, resume, authors, changelog) and added an on-site shortcodes/directives reference plus a component catalog, so the theme's surfaces are demonstrated and regression-guarded rather than undocumented.
-
Extended
check_alpine_runtime()to detect Alpine core (not just the
chirpui-alpine.jsregistration script): the result now carriescore_loaded
andcore_url_validplus a human-readableproblemstuple, and flags the
silent CDN footgun where an Alpine core<script>is present but its URL is a
barealpinejs@<version>(CommonJS) instead of the browser build ending in
/dist/cdn.min.js. Detection is framework-agnostic (matchesalpinejs@/
@alpinejs/cspsrcs and Chirp'sdata-chirp="alpine"marker, and ignores the
mask/intersect/focus plugins). The existingok/script_loadedcontract is
unchanged (#191). -
Extended the chirp-theme dogfood: a first-class
notebooksection now ships a real dogfood notebook page (so the notebook layout renders on the live site) and is guarded by the family-coverage smoke test, a structured-frontmatter changelog entry exercises the version-groupedchange_sectionpath inchangelog/single.html, andtutorial/list.htmlis deduped onto the sharedlearning_index()macro instead of an inlinedresource_indexblock. The/docs/landing destination cards each lead with a shipped Phosphor icon, an adoption quickstart documents applyingtheme: "chirp-theme"to a Bengal site (minimum Bengal 0.3.3 and thelibrary_asset_tagsrequirement), and the family-coverage smoke test now also guards that a category landing aggregates multiple pages (the singularcategory:frontmatter Bengal's defaultcategoriestaxonomy reads). -
Added eight new stable layout and control primitives:
aspect_ratio(fixed-ratio frame for media, previews, and embeds),item(reusable row anatomy for lists, menus, command results, and resource links),kbd(inline keyboard-key hint for shortcuts),ui_labelfromlabel.html(standalone label primitive for custom controls and compact forms),scroll_area(contained overflow region for sidebars, menus, code previews, and panels),separator(semantic or decorative divider),slider(native range-input wrapper for numeric settings and filters), andtoggle_group(grouped single- or multiple-selection toggle buttons) (#118).
Changed
-
Extended the existing-token typography and rhythm polish pass to navigation, segmented-control, disclosure, and overlay component defaults.
-
Demoted
workspace_shellfromstabletoexperimentalmaturity to match the
new Application Chrome Posture ADR:
the blessed application-chrome composite is the route-context rail wired into
app_shell, not the broader workbench frame, which the application-chrome plan
still defers. The macro itself is unchanged and continues to render; only its
stability signal (manifest,chirp-ui find, generated docs) moves to experimental,
aligning it withcomposer_shellanddock. -
Demoted the
data-tabledescriptor fromstabletoexperimental
(metadata-only; no render change). Per #200's
acceptance ("no longer a thin wrapper labeled stable"),data_tableis the
deliberately-thin filter+table+pagination convenience wrapper and the new
data_gridcomposite is now the real interactive grid.tableandtable-wrap
remainstable— they are genuinely complete low-level primitives (real
<table>semantics, alignment, widths, sticky header/col, slots). Agents and
docs that read the manifest will see the new maturity value; it is intentional,
not a regression. Seedocs/safety/public-surface-stabilization.md. -
Document and begin the bespoke
chirp-themerewrite by routing the desktop shell through Chirp UI navbar/footer primitives, marking core page/search/error surfaces, moving docs heroes/navigation/TOC and blog/card compatibility surfaces toward Chirp UI primitives, adding a catalog-style double-left-rail docs surface with symbol-first outer navigation, quieter contextual inner rail rows, a redesigned page-map TOC rail, and a rail-native back-to-top action, replacing raw release lists with ordered release cards, widening hover/focus nav dropdowns, removing legacy hard-coded Bengal palette controls/assets, and preserving the transitional Bengal asset shim. -
Extended the existing-token typography and rhythm polish pass to data display and loading feedback component defaults.
-
Extended the existing-token typography and rhythm polish pass to header, setup, divider, tooltip, and compact affordance component defaults.
-
Extended the existing-token typography and rhythm polish pass to message, conversation, social, and media component defaults.
-
Extended the existing-token typography and rhythm polish pass to site navigation, feature, resource, and token-input metadata surfaces.
-
Extended the existing-token typography and rhythm polish pass to special form controls, rating controls, range values, and drag affordances.
-
Extended the typography and rhythm polish pass to small controls, navigation affordances, and animated control primitives using existing Chirp UI tokens.
-
Improve registry discovery and add source-only reference proof, analysis, and recipe guidance for Chirp UI promotion candidates.
-
Made the registry
maturityfield honest about thin composition wrappers. A
maturity="stable"component that composes other registry components (non-empty
composes) is now treated as a composition wrapper and must carry the same
promote-to-stable proof collateral as any stable promotion — either a
| Promote to stable |row indocs/safety/public-surface-stabilization.mdor a
justifiedSTABLE_COMPOSERS_WITH_PROOFallowlist entry naming its asserting
proof test. The newtest_no_thin_composition_wrapper_is_stable_without_proof
(composing with the existing promote-to-stable-collateral invariant, no new
maturity tier) catches the class:data_tableshippedstablewith
composes=("filter-row","table","pagination")before #200 demoted it, and this
gate would flag any future repeat. The audit confirmed zero current offenders —
data_tableis alreadyexperimental, andtable/table-wrap/calendar/
bar_chart/donutplus the hardened ASCII set carrycomposes=()so the rule
provably never fires on those complete/low-feature primitives
(#203). -
Polished the chirp-theme home hero preview card and finished a slice of the CSS-token layering cleanup: the preview-card nav labels now read at body-copy contrast, the "example interface" overline uses the brand sans small-caps treatment, the window-chrome traffic-light dots were resized and recolored from public
--chirpui-*tokens so they read as an intentional macOS-style cluster, and the remaining rawrgba()panel gradients/shadows are now token-driven. Release history can surface a one-linehighlight:/summary:frontmatter line per card, now exercised on the latest and a feature release. Also deleted the dead.has-prose-content/base/prose-content.cssprimitive from the mermaid component CSS and the CSS scoping docs (no template ever emitted it), and lowered the legacyvar(--color-*)ceiling to lock in the migration gain. -
Raise the default visual taste floor for Chirp UI screens and high-impact components while preserving registry-owned component vocabulary.
-
Reconciled the chirp-theme JavaScript subsystems against what actually ships. Deleted the template-unreferenced dead modules
holo.js,session-path-tracker.js,data-table.js, and the bundledtabulator.min.js, and removed the Tabulator/data-table branch fromlazy-loaders.jsand thebase.htmllazy bundle so only the live Mermaid and D3-graph lazy features remain. Stripped dead SPA-navigation listeners (contentLoaded,turbo:*,pjax:*,astro:*) that Bengal never dispatches fromtoc.js,tracks.js,graph-contextual.js, andlink-previews.js, and added adata-toc-boundidempotency guard so the progressive-enhancement registry callback and theready()auto-init no longer double-bind the TOC. Enabledcontent.mermaidin the site config so the existing blog mermaid dogfood renders with the full toolbar/theme bundle. Added regression tests asserting everydata-bengalhook has a matchingBengal.enhance.register()init path, that no dead SPA listeners remain, that the retired modules stay deleted, and thatBENGAL_LAZY_ASSETScarries exactly the keys for the enabled features; plus a vitestmockDeniedStorage()helper and theme.js regression proving theme switching still flipsdata-theme(and warns) whenlocalStoragethrows. -
Refactored the chirp-theme non-documentation content types onto a shared list shell: the tutorial/resume/notebook lists (via
partials/learning-index.html) and the changelog/releases/authors lists now composepage_hero+ a grid/timeline directly instead ofchirpui/resource_index, which removes the inert "Filter…/Search" GET form (non-functional on a static build, duplicating the global Cmd+K) and the duplicate page-hero/results heading — each list now leads with a single heading and the item count in the hero metadata. Single-page reading columns are also centered (addedmargin-inline: autoto thetype-identity.cssreading-measure block, with a sibling rule for the track article that carries.prose), so prose no longer hard-anchors left while the footer centers. -
Replaced the hand-maintained static component showcase with the live
examples/component-showcaseChirp app, now deployed as a Railway service at https://chirp-ui-showcase-production.up.railway.app and reachable from the/showcase/docs route (which keeps its inline live specimens and links out to the deployed app). Removed the static gallery and its build scaffolding — theassemble-static-showcase.shscript, thedocs-assemble-showcase/showcase-guardpoe tasks, the Makefileshowcase*targets, and the Pages assemble+guard steps — sodocs-build-allis now just the Bengal build plus manifest emit. Added Railway deploy config (railway.json,examples/component-showcase/Dockerfile,.dockerignore) and a$PORTbind in the example app so it runs unchanged locally and binds0.0.0.0:$PORTon the platform. -
Reworked the docs sidebar disclosure: sections now open/close via an open/closed folder icon (a real toggle button beside the navigable label — no caret), keeping section labels visible when collapsed and adding zero nested-interactive / landmark / region axe violations.
-
Scope Chirp UI view-transition names to direct shell main boundaries to avoid duplicate transition names in embedded shell previews.
-
Unified active-link sync into one canonical implementation in
shell_runtime_script(), emitted by both shell entry points. The
app_shell()macro path now gets client active-sync it never had, and
app_shell_layout.htmlno longer carries a divergent inline copy. The shared
syncNavmirrors the server's per-itemmatch=:sidebar_link/
navbar_linkemitdata-chirpui-shell-match="exact"|"prefix"only whenmatch=
is set, and the JS toggles the--activeclass plusaria-currentfor those
links alone. The macro's#chirpui-sidebar-navand the layout's
#chirpui-topbar-breadcrumbsannouncers now reach parity.Behavior change to call out: the layout's old blind-prefix client toggle —
which ranpath === href || path.startsWith(href + "/")against every link
regardless ofmatch=— is gone. Match-less sidebar/navbar links are now
server-authoritative (active state comes from server-renderedactive=/
aria-current, not a client path guess). Shells that relied on the old
auto-highlight without settingmatch=should setmatch="prefix"(or
match="exact") on those links to restore client re-highlighting across boosted
navigation (#197). -
Wired the cross-track membership widget into
doc/single.html, so any doc whose slug appears in asite/data/tracks.yamltrack now renders an in-track prev/next + progress card after its page navigation. A doc that belongs to multiple tracks surfaces one card per membership, and a doc in no track renders nothing. The prev/next links are plain internal anchors, so site-wide htmx boost handles them without a per-link opt-out. -
Lowered the Bengal asset-path contract floor in
chirpui_asset_path()from0.3.3to0.3.2, so the library asset-path scheme is also emitted on Bengal0.3.2. Thechirp-themepackage still requires Bengal>= 0.3.3forlibrary_asset_tags()(#118).
Fixed
- Accepted
notebookas a documentation destination in the search-relevance fixture: now that the notebook family is first-class (#146/#147) and ships a "render a card from Python" dogfood page, that page legitimately ranks #1 for the query "card". Addednotebookto the test'sDOC_SECTIONSso this cross-family interaction is correct behavior rather than a failure. - Added semantic icon aliases for common integration status names so
activity,
pause,info, andwarningrender without validation warnings. - Completed the autodoc Python member detail and raised its visual floor: each documented method/function now renders its full Returns and Raises blocks inside the member accordion body (reading
member.metadata.returns,member.metadata.parsed_doc.returns, andmember.metadata.parsed_doc.raisesvia the sharedreturns.html/raises.htmlpartials, strict-undefined guarded), so a raising member likeregister_colorsfinally surfaces itsValueError. The nested Python and OpenAPI reference list pages now passtitle=nonetoresource_indexso the page hero owns the sole<h1>and the results section heading is the only subheading (matching the already-fixed/api/home), and the now-dead.chirp-theme-api-index .chirpui-search-header__content { display: none }rule was removed. - Fixed
data_grid's "Load more" button never being refreshed or removed after the last page.data_grid_rowsnow acceptsload_more_url/has_more(plus label/trigger/swap/selection_id) and emits the load-more sentinel as anhx-swap-oobupdate to a stable#{selection_id}-load-morecontainer via the newgrid_load_moremacro — so each load-more fetch refreshes the button's next-page URL or, on the last page, removes it. Previously the button persisted past exhaustion and re-clicking it silently fetched past the end of the result set. Adds a load-more-to-exhaustion browser gauntlet assertion (#231). - Fixed
segmented_control()rendering 42px tall instead of the 40px shared control-height contract. The pillsegmented_control()and the legacy form-fieldsegmented_control_field()share the.chirpui-segmentedblock class, and the form-field block'sborder: 1px solidleaked onto the borderless pill — pushing it 2px proud of its neighbouring controls in toolbars and action strips. The legacy container rule is now scoped to the form-field structure (:has(> .chirpui-segmented__input)) so it no longer applies to the pill, and the optionmin-block-sizemath is explicit, so the pill computes to exactly the control-height token. The form field is unchanged (#230). - Fixed flagship-page defects: dark-mode and home primary-button contrast, the Outfit webfont never loading,
search_barrendering a raw icon name, ~89 broken/api/<module>/<member>/autodoc links (now in-page anchors), unique nav landmark names, valid<aside>roles, a decorative reading-progress bar, and keyboard-scrollable parameter tables. - Fixed route-scoped shell regions stranding stale content on boosted navigation.
Thehtmx:beforeSwapreset that empties a shell region before its out-of-band
fragment lands previously lived only inapp_shell_layout.html's inline script
and was hardcoded to#chirp-shell-actions— so theapp_shell()macro path had
no reset at all, and the new route-context rail (#chirpui-context-rail) was
never cleared. It is now in the sharedshell_runtime_script()(emitted by both
shell entry points) and covers both regions: navigating to a route that ships no
fragment for a region now empties it rather than stranding the prior route's
content. Adds a Playwright gauntlet proving the rail swaps on boosted nav and
clears on a contextless route (wired into thetest-browser-chromegate)
(#195). - Fixed the changelog/releases timelines and the authors profile. The "newest first" timelines were not chronological: the dotted
sort(attribute="metadata.date,title")silently fell back to title-alpha (kida's attribute resolver does not descend dotted paths), so changelog now sorts by the flatdateand releases by version (title)./releases/now renders its bespokereleases/list.htmltimeline (it was orphaned astype: pageand fell through to the generic section index, which re-emitted the dead filter). The authors directory no longer shows "0 authors" for a published author (its data source now resolves the author content pages), and an author profile now renders an initials avatar, a single bio, and a real authored-post feed (matched viametadata.authoracrosssite.pages) instead of a self-referential card and a metric tile wasted on the author's own name. - Gave every
<nav>landmark a unique, static accessible name and made the floating back-to-top button honorprefers-reduced-motion. The chirp-uinavbar()andsidebar()macros now accept anaria_labelparameter so a page with multiple navigation landmarks (the theme's primary header nav, the docs catalog rail, and the docs sections tree) reads as distinct entries in a screen reader's landmark menu rather than "navigation, navigation, navigation" — the docs sections label is now baked into the template instead of relying ondocs-nav.jsruntime injection, so it survives even where that script never loads (the static showcase). The theme'sinteractive.jsback-to-top handler now picksbehavior:'auto'(an instant jump) for reduced-motion users via the sharedBengalUtils.prefersReducedMotion()helper, and the showcase demo navs each carry a unique label. - Hardened the chirp-theme document head and critical render path: the Outfit display webfont is now reliably wired (the 400/600 woff2 weights preload with crossorigin and the generated fonts.css is linked, gated on the configured display font), and the unused .ttf font duplicates were dropped so only woff2 ships. The theme stylesheet now loads non-render-blocking via a stdlib-only
rel=preload as=style+ onload swap with a<noscript>fallback, and the d3js.org preconnect is gated behind the graph feature instead of being emitted on every page. The Bengal min-version contract is now loud: theme.toml declares bothrequires_bengaland a[bengal] min_versionfloor, and base.html emits a dev-visible diagnostic (naming Bengal >= 0.3.3) whenlibrary_asset_tags()is unavailable instead of silently shipping a token-less style-only build. - Made the built-home
style.cssnon-render-blocking test (#157) fingerprint-tolerant: Bengal emitsstyle.<hash>.cssin built output, so the assertion now matches the stylesheet by stem+extension instead of the literal unhashed filename. The test skips on a clean checkout (it needs a builtsite/public) but failed a localpoe cirun afterpoe docs-build; this keeps the local build gate green. - Made the chirp-theme's SEO surface emit absolute URLs end to end: the production environment now builds canonical, Open Graph, sitemap
<loc>, and the robots.txtSitemap:directive from the full absolute origin (https://lbliii.github.io/chirp-ui) instead of a path-only baseurl, andog:image/twitter:imageare wrapped incanonical_url(...)so social scrapers receive absolute https image references instead of relative paths that yield no preview card. Added JSON-LD structured-data render tests (home → WebSite + SearchAction + Organization; doc → TechArticle + BreadcrumbList; post → BlogPosting; product → Product/Offer) that parse the emitted graphs and assert correct@types, plus SEO config guards for the absolute production origin and the enabled RSS feed.