{"releases":[{"version":"4.6.0","id":"v4-6-0","date":"2026-10-25","published":"2026-10-25T22:41:10Z","channel":"latest","url":"https://github.com/codeatlasdev/ui/releases/tag/%40codeatlasdev%2Fui%404.6.0","npm":"https://www.npmjs.com/package/@codeatlasdev/ui/v/4.6.0","html":"<h3 id=\"v4-6-0-minor-changes\">Minor Changes</h3>\n<ul>\n<li><p>0219812: AI agents get one canonical entry point. <code>llms.txt</code> opens with a recipe for every screen type (app shell, list, form, settings, auth, dashboard, landing, theming), then decision tables and a &quot;common workarounds&quot; page; <code>get_guide</code> and the new per-page files <code>/llms/&lt;slug&gt;.txt</code> carry the complete demo source of each page, while <code>llms-full.txt</code> stays under its old size. <code>ca-ui-mcp</code> adds <code>get_recipe</code> and reports deprecated components (<code>Row</code>, <code>GlassCard</code>, <code>GlassSurface</code>) with their replacement. <code>init</code> writes a marked, idempotent block into the app&#39;s <code>AGENTS.md</code>. The agent skill no longer teaches the removed <code>cols</code>/pixel-<code>gap</code> layout API or deprecated components.</p>\n</li>\n<li><p>1b1b993: <code>AspectRatio</code> (#446, part of #432): a box that holds <code>ratio</code> for a video, an embed or any child with no size of its own; a direct <code>img</code>, <code>video</code>, <code>iframe</code>, <code>canvas</code> or <code>svg</code> child fills it. <code>Image</code> gains <code>ratio</code> (crop to a width / height box, filling the container) and <code>fit</code> (<code>cover</code> or <code>contain</code>). Replaces the Radix <code>AspectRatio</code> row of the React equivalence table.</p>\n</li>\n<li><p>23fa9ee: Auth building blocks (#379): <code>Turnstile</code> in <code>@codeatlasdev/ui/forms</code> (Cloudflare captcha, explicit render, loads the vendor script on demand with no dependency and no server access, follows the colour scheme and the locale, <code>bind:handle</code> with <code>getToken()</code> / <code>reset()</code>, removed on unmount, silent no-op without a <code>siteKey</code>), <code>PasswordStrength</code> (3-step meter with a polite text label, pure <code>passwordScore()</code> and a <code>score</code> / <code>labels</code> override), <code>SplashScreen</code> (<code>role=&quot;status&quot;</code>, <code>ready</code> + <code>minDuration</code>, skipped under reduced motion) and <code>Avatar</code> <code>decorative</code> (no accessible name next to a visible name). New i18n groups <code>passwordStrength</code>, <code>splashScreen</code> and <code>turnstile</code> (en, pt-BR).</p>\n</li>\n<li><p>00757fe: <code>AuthLayout</code>: <code>panelTone</code> (<code>auto</code> | <code>brand</code> | <code>flat</code>) and the <code>--ca-auth-panel-bg</code> / <code>--ca-auth-panel-fg</code> tokens. A flat panel is a solid surface with a hairline, themed text and no blobs; the default <code>auto</code> picks it under the <code>flat</code> and <code>outlined</code> surface styles, so themes that forbid gradients no longer override <code>.ca-auth__panel</code> and <code>.ca-auth__blobs</code> from app CSS (#409). The panel slot&#39;s <code>--panel-*</code> variables are now CSS (derived from <code>--ca-auth-panel-fg</code>) instead of an inline style.</p>\n</li>\n<li><p>78ddb7f: <code>AuthLayout</code>: <code>panelSide</code> (<code>left</code>/<code>right</code>), <code>panelBleed</code> (full-bleed, sticky panel with no padding, logo or blobs, and a hairline divider), <code>panelHidden</code> (<code>md</code> 900px or <code>lg</code> 1200px; the panel snippet is unmounted while hidden, so media inside is never fetched on mobile), a <code>header</code> snippet (sticky bar over the form column that replaces the mobile brand strip) and <code>align=&quot;start&quot;</code>. <code>AuthCard</code> gets <code>variant=&quot;plain&quot;</code> (display-size title, no card width cap). New template: Waitlist with map (#347).</p>\n</li>\n<li><p>b192b0a: <code>Badge</code> renders an inline status pill: <code>&lt;Badge tone=&quot;danger&quot; subtle dot size=&quot;md&quot;&gt;Overdue&lt;/Badge&gt;</code> (tones <code>brand</code>, <code>success</code>, <code>danger</code>, <code>warning</code>, <code>info</code>, <code>neutral</code>; <code>subtle</code> is a tinted background whose label clears 4.5:1 in light and dark). <code>Button</code> gains <code>trailingHint</code> (a <code>Kbd</code> in <code>trailing</code> is hidden below <code>md</code> and on touch with no reserved gap) and <code>align=&quot;between&quot;</code> (label at the start, <code>trailing</code> at the end; the spinner keeps its place). Defaults are unchanged.</p>\n</li>\n<li><p>acdf4f3: <code>ButtonGroup</code> (#448, part of #432): attaches <code>Button</code>, <code>IconButton</code> and a <code>DropdownMenu</code> trigger into one <code>role=&quot;group&quot;</code> control, horizontal or vertical (<code>orientation</code>), with square inner corners, a single-line seam and the hovered or keyboard-focused member raised over it. Replaces the shadcn Button Group row in the React equivalents table.</p>\n</li>\n<li><p>8c31f6a: <code>Button</code> and <code>IconButton</code> get <code>tone=&quot;neutral&quot;</code> (#357): <code>outlined</code> and <code>ghost</code> buttons drawn with a neutral label, border and hover/pressed fills instead of the brand colour, for secondary actions beside a brand <code>filled</code> one. <code>IconButton</code> also gets the <code>outlined</code> variant. Label and border contrast is checked for every preset in <code>tests/contrast.test.ts</code>.</p>\n</li>\n<li><p>61912be: <code>Carousel</code> and <code>CarouselItem</code> (#445, part of #432): native scroll-snap slides that follow the WAI-ARIA carousel pattern (labelled region, slides named &quot;n of m&quot;, off-screen slides <code>inert</code>, labelled dot buttons with <code>aria-current</code>, arrows / Home / End on the focused carousel). <code>perView</code>, <code>loop</code>, <code>bind:index</code>, <code>onchange</code>; <code>autoplay</code> (ms) is opt in, adds a Pause button, stops on focus or hover, is a polite live region only when stopped, and never runs under <code>prefers-reduced-motion</code>. No carousel engine dependency. Replaces shadcn <code>Carousel</code>. New i18n group <code>carousel</code> (en, pt-BR).</p>\n</li>\n<li><p>82edb9d: <code>ca-ui-mcp</code> gains <code>get_release(version)</code>: a release as a post for agents, with its highlights, the numbers and the conditions each was measured under, the behaviour changes to check after upgrading, the upgrade command and every merged change grouped by area with its pull request and issue. Without a version it returns the digest of every release. It reads <code>dist/releases.json</code>, built from the release posts in the docs site.</p>\n</li>\n<li><p>7b30608: New <code>ChipGroup</code>: single choice among a few short options as chips that wrap (#355). A real <code>radiogroup</code> of native radios (roving tab stop, arrows/Home/End/Space/Enter, disabled items skipped), three columns below <code>sm</code> and free wrap from <code>sm</code>, a check icon on the selected chip, <code>error</code> as <code>role=&quot;alert&quot;</code> with <code>aria-invalid</code>/<code>aria-describedby</code> on the group, <code>name</code>/<code>required</code> for native forms, 44px chips. Use it instead of <code>RadioGroup</code> (too tall) or <code>SegmentedControl</code> (never wraps) for options like team size.</p>\n</li>\n<li><p>689d2c5: New token <code>--ca-code-line-height</code> (20px): an integer px line height for code surfaces. <code>CodeSnippet</code>, <code>.ca-code pre</code> and a bare Markdown <code>pre</code> inside <code>.ca-prose</code> use it instead of the unitless 1.6 / 1.7, which rounded to a different pixel height in a text layer and in an overlay (a caret and an active-line highlight drifting apart). The <code>code</code> inside a <code>CodeSnippet</code> also pins <code>--ca-font-mono</code> now: it used the UA&#39;s bare <code>monospace</code>, resolved per lookup. The docs gain &quot;Code surfaces: integer line-height, one font stack&quot; for apps that build an editor.</p>\n<p>The stylesheet also drops <code>--ca-action-focus</code>, a token no component, doc or template ever read, to stay inside its size budget.</p>\n</li>\n<li><p>caf82ad: Component tokens for <code>Button</code>, <code>IconButton</code> and <code>Kbd</code>. Weight, tracking, line-height, gap, per-size padding, height and font size, ghost border width, disabled background and the touch minimum are <code>--ca-btn-*</code>, <code>--ca-icon-btn-*</code> and <code>--ca-kbd-*</code> custom properties; set them on <code>:root</code>, on a wrapper or through the new <code>ThemeOptions.components</code> (<code>button</code>, <code>iconButton</code>, <code>kbd</code>, merged by <code>mergeTheme</code>). Unset, every component draws exactly what it drew in 4.5.0. One naming convention for all components (<code>--ca-&lt;component&gt;-&lt;property&gt;</code>, <code>--ca-&lt;component&gt;-&lt;size&gt;-&lt;property&gt;</code>) is documented in the theming guide. No more selectors against <code>.ca-btn--lg</code>, <code>.ca-icon-btn</code> or <code>.ca-kbd</code>.</p>\n</li>\n<li><p>3b76318: <code>ca-ui-mcp</code> gains <code>list_concepts</code> and <code>get_concept</code>: the Concepts pages (viewport-complete UX, motion that respects people, targets and focus, appeal is not usability) with every claim&#39;s strength label, scope, sources, limits and last-reviewed date. The llms-full concept digest now carries the contract and the prompt only, so the file stays under its size cap.</p>\n</li>\n<li><p>e125cc8: <code>ContextMenu</code> (#455): wraps a region and opens a <code>DropdownMenu</code> at the pointer on right-click (a second one moves it), at the finger on a touch long-press (<code>longPressDelay</code>, default 500 ms, cancelled by lifting early or dragging) and from the keyboard with Shift+F10 or the ContextMenu key. The <code>menu</code> snippet takes the same items as <code>DropdownMenu</code> (items, checkbox and radio items, submenus, labels, separators). Text fields keep the browser&#39;s menu; <code>disabled</code> keeps it everywhere; <code>bind:open</code>, <code>onOpenChange</code>, <code>focusable</code>, <code>menuClass</code>.</p>\n</li>\n<li><p>c19ba2b: Density and appearance tokens for <code>Avatar</code>, <code>TextArea</code>, <code>TextField</code>, <code>Card</code>, <code>SegmentedControl</code> and <code>EmptyState</code>, on the same <code>--ca-&lt;component&gt;-&lt;property&gt;</code> convention as the Button tokens, also settable through <code>ThemeOptions.components</code>. <code>Avatar</code> gets <code>tone=&quot;neutral&quot;</code> (surface background, secondary text, hairline ring) and <code>--ca-avatar-fallback-{bg,color,size,weight}</code> with per-size <code>--ca-avatar-&lt;size&gt;-fallback-*</code>. <code>TextField</code> and <code>TextArea</code> read <code>--ca-field-gap</code> and <code>--ca-field-label-leading</code> (or their own <code>--ca-tf-*</code> and <code>--ca-ta-*</code>); <code>--ca-ta-min-height</code> is the height of the visible box, border included. <code>Card</code> reads <code>--ca-card-{bg,radius,border,shadow}</code>. <code>SegmentedControl</code> reads <code>--ca-seg-item-{padding-x,font-size,line-height}</code> (and per size). <code>EmptyState</code> gets <code>bare</code>, <code>actionsFill</code> and <code>--ca-empty-{padding,title-size,title-weight}</code>. Unset, every component draws what it drew in 4.5.0.</p>\n</li>\n<li><p>ab42958: <code>ca-ui-mcp</code> gains <code>search_terms</code>, <code>get_term</code> and <code>get_reference</code>: the 269-term design atlas and the verified sources behind it, offline from <code>dist/atlas.json</code>. Scores are labelled heuristic, every term carries its limits and review date, and a reference returns what it does not support and the wording that was read.</p>\n</li>\n<li><p>73f38cd: Design style pages (#583): <code>/styles/&lt;id&gt;/</code> for <code>swiss</code>, <code>editorial</code>, <code>brutalism</code>, <code>sketchbook</code>, <code>glassmorphism</code> and <code>terminal</code>, each rendered in its own style, with the origin, the traits (type, colour, shape, depth, motion), do and don&#39;t, accessibility pitfalls with their sources, before and after on real components, the <code>designStyle()</code> recipe and a prompt. The <code>ca-ui-mcp</code> server gains <code>get_style</code> (a style as markdown, or the digest of all of them) and <code>llms.txt</code> links the digest at <code>/llms/styles.txt</code>. <code>designDefaults()</code> is exported: the stylesheet default of every knob a theme can write, so a scoped preview can reset what an outer theme set.</p>\n</li>\n<li><p>300ac7c: <code>Dialog</code> split is configurable: <code>splitSize={{ width, height }}</code> (any CSS length), <code>splitColumns=&quot;2fr 3fr&quot;</code> and <code>fullscreenBelow=&quot;sm&quot; | &quot;md&quot;</code> (any size: exactly the viewport under the breakpoint, square corners, safe-area padding kept). They set the tokens <code>--ca-dialog-split-width</code>, <code>--ca-dialog-split-height</code> and <code>--ca-dialog-split-columns</code>. Defaults are unchanged. The insertion-line-free column drop requested in the same issue already ships as <code>sortable({ drop: &#39;container&#39; })</code>; it now has a test for the announced target container on keyboard and touch.</p>\n</li>\n<li><p>ca5cc0e: Display text role (#350). New <code>Heading</code> component (<code>level</code>, <code>size=&quot;display&quot; | &quot;display-2&quot; | &quot;display-3&quot; | &quot;h1&quot;..&quot;h6&quot;</code>) bound to <code>--ca-font-display</code> (the body font until <code>fonts.display</code> is set), the new <code>--ca-display-tracking</code> / <code>--ca-display-leading</code> tokens and the fluid <code>--ca-font-size-display-*</code> sizes. <code>AuthCard</code>, <code>PageShell</code>, <code>EmptyState</code> and <code>SiteHeader</code> take a <code>display</code> prop. <code>defineTheme({ typography: { displayTracking, displayLeading } })</code> sets the tokens.</p>\n</li>\n<li><p>e5c7c0f: <code>DocsHeader</code> is the top bar of <code>DocsShell</code> as its own component, so the pages of a site that are not a docs frame (a landing page, a template gallery) render the same header as the docs: one height, container width, brand position, section tabs with an underline for the current one, search (⌘K), actions and theme toggle, and the same phone menu. The bar is now opaque (it was an 82% translucent fill with a blur), so content scrolled under it no longer shows through.</p>\n</li>\n<li><p>4e4bd67: <code>Drawer</code> gets <code>chromeless</code> and <code>fullBleedBelow</code> (#362), mirroring <code>Dialog</code>&#39;s <code>chromeless</code>. <code>chromeless</code> renders no header, close button or footer actions and no body padding, so a detail view can bring its own header and scroll area; the panel is still named by <code>title</code> (screen readers only), <code>aria-label</code> or <code>aria-labelledby</code>. <code>fullBleedBelow=&quot;sm&quot; | &quot;md&quot; | &quot;lg&quot;</code> makes a left/right panel exactly the viewport width and <code>--ca-vh</code> height below that breakpoint (no radius, border or page strip, safe-area padding kept). <code>.ca-drawer__body</code> is now a flex column with <code>min-height: 0</code>, so a child can own the scroll.</p>\n</li>\n<li><p>2cc7983: Editorial primitives (#353). New tokens <code>--ca-font-size-display-hero</code> (fluid, 60-216px) and <code>--ca-ease-out-expo</code> / <code>--ca-ease-out-quart</code> / <code>--ca-ease-in-out-quart</code> (also in the <code>easing</code> export). New <code>Highlight</code> and <code>StrikeThrough</code> components with <code>animated</code>, drawn in their final state under <code>prefers-reduced-motion</code>. <code>Surface</code> takes <code>grain</code> (<code>true | &quot;animated&quot;</code>), a paper-noise overlay tuned by <code>--ca-grain-opacity</code> / <code>--ca-grain-size</code>. Fix: <code>--ca-font-mono</code> (and <code>fontFamily.mono</code>) now starts with the system monospace instead of JetBrains Mono, which the kit does not load; set <code>fonts.mono</code> to pick another.</p>\n</li>\n<li><p>446c722: <code>HoverCard</code> (#454): a rich preview that opens on pointer hover (after <code>openDelay</code>) and on keyboard focus (at once), stays open while the pointer crosses into it (<code>closeDelay</code>), closes on Escape and blur, and is reachable with Tab from the trigger. A hint layer: it never takes Escape or presses from the layer below. Touch never opens it, so a tap only runs the trigger&#39;s own action (use <code>Popover</code> for a tap-to-open preview). <code>triggerChild</code>/<code>trigger</code>, <code>bind:open</code>, <code>onOpenChange</code>, <code>side</code>, <code>align</code>, <code>sideOffset</code>. <code>FloatingLayer</code> forwards <code>onfocusout</code>.</p>\n</li>\n<li><p>c8e9492: <code>ca-ui-mcp</code> searches the gallery index. <code>search_ui</code> takes words plus facet filters (kind, subtype, style, motion, interaction, layout, colorMode, density, tech, cost, component), <code>list_facets</code> lists every value with its count for the current selection, and <code>get_related</code> returns the entries related to a demo. They read the same published facets as <code>api/v1/index.json</code>: a facet still under review is never searchable.</p>\n</li>\n<li><p>a5bc3b7: <code>init --write</code> now produces a SvelteKit app that passes <code>vite build</code>, <code>svelte-check --fail-on-warnings</code> and <code>codeatlas-ui lint</code> as written, on Kit 2 and Kit 3.</p>\n<ul>\n<li><strong>PWA and SEO are opt-in.</strong> The manifest route, service worker, offline page, <code>robots.txt</code> and <code>sitemap.xml</code> are written only with <code>init --with pwa</code> (also combinable: <code>--with lint,e2e,pwa</code>). A plain <code>init</code> writes no <code>manifest</code> link in <code>&lt;Seo&gt;</code> (the old default failed the prerender with a 404 as soon as the manifest route was removed). <code>--with pwa</code> can be added later to an app <code>init</code> already set up.</li>\n<li><strong>Lint-clean output.</strong> The copy <code>init</code> writes (layout title, offline page, manifest name) lives in <code>src/lib/messages.ts</code> (<code>export const m</code>), so <code>codeatlas-ui lint</code> no longer reports <code>i18n-literal</code> on a freshly initialised app.</li>\n<li><strong>Kit 3 <code>#lib/*</code> map.</strong> <code>init</code> adds <code>&quot;imports&quot;: { &quot;#lib/*&quot;: &quot;./src/lib/*&quot; }</code> to <code>package.json</code> (never replacing an existing map) and imports the theme and messages through it, instead of relative paths.</li>\n<li><strong>Missing config files are created</strong>: <code>vite.config.ts</code>, <code>src/app.html</code> (without the inline-styled <code>display: contents</code> wrapper) and <code>tsconfig.json</code> instead of <code>skip ... (not found)</code>. Kit 2&#39;s <code>tsconfig.json</code> gets <code>allowImportingTsExtensions</code>, <code>noEmit</code> and <code>skipLibCheck</code>, which its generated base config lacks; an existing file only gains the options it does not set.</li>\n<li><strong><code>@sveltejs/adapter-static</code></strong>: the adapter output folder (<code>build</code>, or the <code>pages</code> option) goes into the tsconfig <code>exclude</code>, so <code>svelte-check</code> no longer scans it, and <code>src/routes/+layout.ts</code> gets <code>export const prerender = true;</code>. <code>init</code> notes when the adapter major does not pair with the Kit major (<code>@sveltejs/adapter-static@4</code> for Kit 3, <code>@3</code> for Kit 2).</li>\n<li><strong><code>prerender.handleUnseenRoutes: &quot;ignore&quot;</code></strong> is written into the <code>vite.config.ts</code> that <code>--with pwa</code> creates on Kit 3 (no page links to <code>/robots.txt</code> or <code>/sitemap.xml</code>), and noted otherwise.</li>\n<li><strong>Fixed the public-origin note.</strong> Kit 2 rejects <code>kit.paths.origin</code>; the mechanism is <code>kit.prerender.origin</code> there and <code>sveltekit({ paths: { origin } })</code> on Kit 3.</li>\n<li>A re-run no longer mistakes the theme import <code>init</code> wrote into <code>vite.config.ts</code> for an app-owned theme.</li>\n</ul>\n</li>\n<li><p>a73cb68: <code>inline</code> on <code>Dialog</code>, <code>Sheet</code>, <code>Drawer</code>, <code>Popover</code>, <code>DropdownMenu</code> and <code>Toaster</code> renders the overlay in place instead of the top layer: <code>position: absolute</code> inside its container, never moved to <code>&lt;body&gt;</code>, no <code>showModal()</code> / <code>showPopover()</code>, no scroll lock, no focus trap, no inert page, no global Escape or outside-press ownership (Escape closes it from inside). It resolves the tokens of its container, so it follows a <code>data-preset</code> or <code>data-theme-scope</code> island. <code>openLayer</code> takes the same <code>inline</code> option. The Theme Studio overlays screen now shows the real components instead of static replicas.</p>\n</li>\n<li><p>d101e43: <code>TextField</code> addons, the InputGroup (#447, part of #432): <code>prefix</code> and <code>suffix</code> put text or a <code>Button</code>/<code>IconButton</code>/select inside the field border, flush with it and set apart by a divider. A string addon is read as part of the field&#39;s description and focuses the input on click; a snippet addon keeps its own name and Tab stop. The focus ring wraps the whole group; <code>error</code>, <code>disabled</code> and <code>size=&quot;sm&quot;</code> reach the addons; <code>--ca-tf-addon-bg</code> sets the fill. <code>.ca-tf__addon</code> classes documented in <code>docs/css-api.md</code>.</p>\n</li>\n<li><p>1263da3: New <code>Kanban</code> board (#361) on top of <code>useDnd</code>: columns with a fixed header (colour rail, title, count), independently scrolling card lists, a board that pans with the mouse and fades only where content is hidden, per-column <code>onloadmore</code> with skeleton cards and a &quot;Load more (N)&quot; fallback, a mobile tab strip (one column at a time) and a &quot;Move to…&quot; menu next to every card. Mouse, pen, touch and keyboard moves are announced. The engine gains <code>sortable({ drop: &quot;container&quot; })</code> (column highlight, no insertion line, <code>to.index</code> always 0) and <code>dnd.scroller()</code> (horizontal board auto-scroll, faster the closer the pointer is to the edge); Left/Right move a card to the same index of the neighbouring column, an empty column included.</p>\n</li>\n<li><p>46129ad: <code>Kanban</code> takes a <code>summary</code> snippet that renders per-column content (a money total, a weight) after the count while the colour rail, title and count stay; on the mobile tab layout it shows as a row above the active column&#39;s cards. <code>header</code> still replaces the whole title row. The kanban docs show a per-column money total and a board-height parent without <code>:global</code>.</p>\n</li>\n<li><p>62a8d05: <code>Kanban</code> takes <code>headingLevel</code> (2–6, like <code>EmptyState</code> and <code>FormSection</code>) for its column titles (#501). <strong>The default level changes from <code>&lt;h3&gt;</code> to <code>&lt;h2&gt;</code></strong> (inside a <code>PageShell</code>, the page&#39;s section level), so a board under the page <code>&lt;h1&gt;</code> no longer skips a level and passes axe <code>heading-order</code>. A board under an <code>&lt;h2&gt;</code> of its own passes <code>headingLevel={3}</code> to keep the old markup. Only the tag changes, never the look; a custom <code>header</code> snippet and the mobile tab layout (no column headings) are unaffected.</p>\n</li>\n<li><p>f6085f9: <code>KpiCard</code> takes a <code>chart</code> snippet (a <code>Sparkline</code> under the number, next to the icon) and a <code>hint</code> line, so an icon + sparkline tile needs no <code>Card</code> + <code>Stat</code> + <code>Sparkline</code> composition. The chart is a snippet rather than a data prop to keep the <code>/charts</code> subpath and <code>echarts</code> out of the core entry. New part: <code>.ca-kpi__chart</code>.</p>\n</li>\n<li><p>25bd205: New <code>layers</code> store: <code>layers.modalOpen</code> is reactive and true while a modal layer (Dialog, AlertDialog, Drawer, Sheet) is open; <code>layers.blocks(trigger)</code> is the same but false for a trigger inside the topmost modal. <code>Tooltip</code>, <code>HoverCard</code> and <code>NavigationMenuItem</code> no longer open on pointer movement while a modal is open (unless their trigger is inside it), so a hover panel can no longer appear under a dialog. Gate your own hover or edge UI with the store.</p>\n</li>\n<li><p>d55e2a5: New <code>LocaleSwitcher</code>: a compact language selector for public headers. Options are data (<code>{ value, label, short?, lang? }</code>), each named in its own language with a <code>lang</code> attribute per item; <code>onselect(locale)</code> may return a promise (the switcher is busy until it settles, <code>value</code> changes only on success, <code>onerror</code> on failure); the trigger shows the native name from 600px and a short code below (or always with <code>compact</code>). Compose it in the <code>trailing</code> slot of <code>SiteHeader</code>, now documented as the end of the bar. New i18n group <code>localeSwitcher</code> (en, pt-BR) (#349).</p>\n</li>\n<li><p>c172f2c: New optional entry <code>@codeatlasdev/ui/map</code> for map products. <code>MapStage</code> wraps <code>maplibre-gl</code> (default) or <code>mapbox-gl</code> and keeps the basemap in step with the theme (<code>styles: { light, dark }</code>, swapped with <code>setStyle</code> on the same map), draws deck.gl <code>layers</code> interleaved through <code>MapboxOverlay</code>, fits bounds with <code>padding</code>, and turns <code>flyTo</code> into <code>jumpTo</code> under <code>prefers-reduced-motion</code>. <code>MapControls</code> adds glass zoom, compass (and optional geolocate) buttons with 44px touch targets. <code>tokenColor(token, alpha?)</code>, <code>mapColors()</code>, <code>useMapTheme()</code> and <code>rgbaCss()</code> resolve <code>oklch()</code> / <code>color-mix()</code> tokens to <code>[r, g, b, a]</code> through a canvas, cached by resolved value so a theme change needs no manual reset. All engines are optional peers loaded inside <code>onMount</code>, so the stage renders in SSR. Pin <code>@deck.gl/*</code> to <code>~9.4</code>.</p>\n</li>\n<li><p>8607da1: <code>Menubar</code> and <code>MenubarMenu</code> (#456): the WAI-ARIA menubar for application menus. One tab stop; Left/Right/Home/End move between bar items (disabled skipped, wraps); Enter, Space or Down open a menu on its first item, Up on its last; inside an open menu Left/Right switch to the adjacent menu, while a submenu keeps its own arrows; Escape returns focus to the bar item; hover switches menus only once one is open. Menu content is the <code>DropdownMenu</code> items unchanged. <code>DropdownMenu</code> gains <code>onmenukeydown</code>, <code>ontriggerkeydown</code> and <code>openFocus</code>.</p>\n</li>\n<li><p>abb6269: Migration tooling per major (#382). <code>migrations/&lt;major&gt;.json</code> is the machine-readable record of every breaking change from 1.0.0 to 4.0.0 (before, after, why, and whether <code>migrate</code> covers it: <code>yes</code>, <code>partial</code>, <code>no</code>); it generates <code>MIGRATION.md</code>, <code>dist/migrations.json</code> and a new MCP tool <code>get_migration(from, to)</code>. <code>codeatlas-ui migrate --from 2 --to 4 [--write] [--report json|md] [--strict]</code> runs only the codemods of the majors in that range, lists what it could not rewrite with file:line (Grid <code>cols</code> template strings, <code>.ca-overlay</code> selectors, <code>setLocale</code>, a <code>bits-ui</code> import, ...) and the changes left to review by hand, and exits 1 under <code>--strict</code>. It no longer answers a bare &quot;Nothing to migrate&quot;: it names the codemods it checked. New codemod <code>selectable-to-ca-selectable</code> (4.0.0: <code>class=&quot;selectable&quot;</code>, <code>class:selectable</code>, <code>.selectable</code> in a style block). The dev warnings of <code>GlassCard</code>, <code>GlassSurface</code>, <code>Row</code> and <code>setLocale</code> on the server cite their migration id. A major changeset must now carry a <code>Migration: &lt;id&gt;</code> line naming an existing entry, and a test fails otherwise.</p>\n<p><code>NavModule</code> is <code>basePath</code>/<code>items</code> in every release from 1.0.0 on: the <code>path</code>/<code>sections</code> shape in the old skill examples never shipped, so there is nothing to migrate.</p>\n</li>\n<li><p>043c32e: Migration tooling for teams moving an existing app onto the library (#383). New commands of <code>codeatlas-ui</code>: <code>tokens import &lt;css|json&gt;</code> turns the custom properties of an existing design system (hex, <code>rgb()</code>, <code>hsl()</code> or <code>oklch()</code>, with or without alpha, one rule per theme) into a typed <code>theme.ts</code> with a preset per theme, composites every translucent token over its background and prints each token it flattened and each text or outline pair under 4.5:1 or 3:1 (<code>--strict</code> exits 1); <code>doctor</code> checks peer versions, optional peers, theme wiring, viewport meta, a root <code>font-size</code>, <code>TODO(lib#n)</code> markers and <code>.ca-*</code> selectors, one line per finding with a fix hint, and <code>--fix</code> renames the markers <code>workarounds</code> reads; <code>lint</code> is a UI guard that reads <code>.svelte</code>, <code>.astro</code> and <code>.css</code> (no hex, raw px, inline style, <code>:global(.ca-*)</code> outside <code>ui-guard.config.json</code>, literal text in templates); <code>compare &lt;urlA&gt; &lt;urlB&gt;</code> opens two pages in Chromium and prints the elements whose box, computed styles or pixels differ, sorted by deviation. <code>init --with lint,e2e</code> writes the guard scripts and a Playwright config with a 390/768/1280 matrix and an axe spec. <code>migrate</code> renames the pre-1.0 <code>NavModule</code> keys <code>path</code>/<code>sections</code> to <code>basePath</code>/<code>items</code> (registry entry <code>v1-navmodule-basepath-items</code>). <code>@codeatlasdev/ui/testing</code> gains <code>withTheme({ scheme, preset, style })</code>, <code>coarsePointer()</code> and <code>measure(el, props)</code>.</p>\n</li>\n<li><p>330d761: <code>NavigationMenu</code>, <code>NavigationMenuItem</code> and <code>NavigationMenuLink</code> (#457): disclosure navigation for marketing headers, not <code>role=&quot;menu&quot;</code>. A <code>nav</code> with a list of links and <code>aria-expanded</code>/<code>aria-controls</code> buttons that reveal a panel of links; one tab stop with Left/Right/Home/End across the top level, Enter/Space toggle, Down opens into the panel, Tab flows through the panel links and on, Escape returns focus to the button. Hover opens after <code>openDelay</code>, switches at once while one is open and closes after <code>closeDelay</code>; a click toggles (touch). <code>current</code> sets <code>aria-current=&quot;page&quot;</code>; <code>description</code> adds a second line to a panel link.</p>\n</li>\n<li><p>98a7396: PageFrame: page container for map-first apps. <code>mode</code> map | split | page, <code>side</code>, <code>width</code> sm | md | lg | xl, a non-modal bottom sheet below 900px with <code>peek | half | full</code> detents (drag or keyboard), <code>padded</code>, <code>headerBorder</code> and fixed <code>header</code> / <code>toolbar</code> / <code>footer</code> slots. New <code>stageInsets</code> store (and <code>--ca-stage-inset-*</code> on <code>&lt;html&gt;</code>) publishes the rectangle the frame covers so the stage can keep pins and routes clear of it; <code>mode=&quot;page&quot;</code> sets <code>data-ca-stage=&quot;hidden&quot;</code> on <code>&lt;html&gt;</code> (#344).</p>\n</li>\n<li><p>cbc214c: PageShell: new <code>aside</code> snippet (companion column, sticky beside the content from a 60rem-wide shell, after it below) with <code>asideLabel</code>, and <code>width=&quot;reading&quot; | &quot;wide&quot;</code> to cap the content column at 46rem. AppShellLayout: new <code>collapseShortPanel</code> prop collapses the panel to the rail for modules with 2 destinations or fewer (#342).</p>\n</li>\n<li><p>539c4be: Preset role colors take alpha, and a preset carries domain tokens. A role in <code>colors.&lt;scheme&gt;</code> is any CSS color (<code>oklch(1 0 0 / 0.62)</code>, <code>rgb()</code>, <code>color-mix(…)</code>) or <code>{ color, alpha }</code>, so a translucent text or border follows the surface behind it instead of being flattened to a hex; a value that is not one CSS color throws at build time, and <code>caThemePlugin</code>/<code>caUI</code> warn when a hand-set <code>text</code>, <code>textSecondary</code> or <code>textHint</code> is under 4.5:1 after compositing its alpha. <code>PresetDefinition.tokens</code> (<code>{ status: { new: { light, dark } } }</code>) emits <code>--ca-token-status-new</code> plus derived <code>-subtle</code> and <code>-contrast</code> per preset and scheme, with names typed through the <code>CaTokenRegistry</code> augmentation. <code>tokenRgba(&quot;status.new&quot;)</code> and <code>onTokensChange(fn)</code> (<code>@codeatlasdev/ui/tokens</code>) read any token as <code>[r, g, b, a]</code> within 1/255 of the CSS value, translucent colors included; the map entry&#39;s <code>tokenColor</code> now shares that resolver. <code>#rrggbb</code> presets behave as before.</p>\n</li>\n<li><p>5eebf5e: Theme presets can own the primary-button and field roles: <code>colors.&lt;scheme&gt;.brandFill</code>, <code>brandFillHover</code> and <code>brandContrast</code> (<code>--ca-brand-fill</code>, <code>--ca-brand-fill-hover</code>, <code>--ca-brand-contrast</code>) and <code>sizes.inputFontSize</code> (<code>--ca-input-font-size</code>, still <code>max(16px, …)</code> on touch and below 900px). They are emitted with the same selectors as the other preset roles, so <code>Button variant=&quot;filled&quot;</code> inside <code>[data-preset=&quot;x&quot;]</code> needs no app CSS. <code>caThemePlugin</code> warns when <code>brandContrast</code> on <code>brandFill</code> is under 4.5:1 (<code>caUI</code> for Astro does too) (#354).</p>\n</li>\n<li><p>70cce85: <code>PreviewStage</code>: a resizable device frame for showcasing a component or a whole page at 390, 768, 1280 or any width. A segmented width control and a keyboard-operable splitter set the frame width; a frame wider than the stage is scaled whole. Inline content is themed in its own <code>ThemeIsland</code> (<code>scheme</code>, <code>preset</code>); <code>src</code> shows a same-origin page in an isolated iframe.</p>\n</li>\n<li><p>c99fb14: <code>Progress</code> gets <code>size=&quot;sm&quot; | &quot;md&quot; | &quot;lg&quot;</code> (4px, 6px, 8px; the default stays 8px) and honours a <code>--ca-progress-height</code> custom property for any other height (#358). Determinate and indeterminate bars both follow it and the ends stay pill-shaped.</p>\n</li>\n<li><p>2ff23ff: <code>AppShellLayout</code> navigation for apps with many modules (#346): <code>railLabels</code> captions under rail icons, a <code>railTop</code> slot for a search / ⌘K trigger, <code>NavModule.badge</code> (number or <code>&#39;dot&#39;</code>) with <code>badgeLabel</code> folded into the button&#39;s accessible name, a bottom navigation that keeps <code>bottomNavPrimary</code> tabs and moves the rest into a &quot;More&quot; dialog by default (native <code>&lt;dialog&gt;</code>, <code>bottomNavOverflow</code> slot; opt-in <code>moreSheet={NavMoreSheet}</code> swaps in a full <code>Sheet</code>), and <code>filterModules</code> for role-based navigation (the active module is resolved on the filtered list). <code>Rail</code> takes <code>top</code> and <code>labels</code>, <code>BottomNav</code> takes <code>primary</code> and <code>overflow</code>.</p>\n</li>\n<li><p>043c32e: Migrating a React app is now data, not prose (#383). <code>equivalents/react-ui.json</code> maps every shadcn/ui component (66) to the Radix primitive, Base UI package and MUI component that mean the same thing, the <code>@codeatlasdev/ui</code> target with its import path and prop renames, and, for the nine components the library lacks, the gap issue (#432) and a workaround. It is served by the new MCP tool <code>get_equivalent(&quot;Dialog&quot;)</code>, shipped as <code>dist/equivalents.json</code>, and generated into the migration guide, so the page cannot drift from the data. <code>codeatlas-ui migrate-react</code> lists every shadcn, Radix, Base UI and MUI import of a <code>.tsx</code> app with its replacement, and in an already-ported <code>.svelte</code> file renames <code>className</code>, <code>onClick</code>, <code>htmlFor</code> and the shadcn <code>Button</code> variants, reporting with file:line the props it cannot rewrite safely (<code>onChange</code>, <code>onCheckedChange</code>, <code>asChild</code>, spreads). The guide gains a Tailwind map and the React patterns that change shape (context, data fetching, routing, forms, SSR global state, per-request i18n), each with a type-checked Svelte block.</p>\n</li>\n<li><p>5b50d09: The reset layer (<code>@layer ca.reset</code>) now gives every interactive shape a pointer cursor: <code>button</code>, <code>a[href]</code>, <code>label[for]</code>, <code>summary</code>, <code>[role=button]</code>, <code>[role=tab]</code>, <code>[role=option]</code> and <code>[role=menuitem*]</code>. <code>:disabled</code> and <code>[aria-disabled=true]</code> get <code>not-allowed</code>. A custom clickable tile no longer shows the arrow cursor; an app overrides the rule with an unlayered <code>cursor</code> declaration, no <code>!important</code>. The stylesheet drops the now redundant <code>cursor: pointer</code> of <code>.glass-btn*</code> and <code>.ca-rail-btn</code>, the unused <code>.ca-bar__actions</code> rule and the unused <code>--ca-grid-columns</code> token to stay inside its 17250 B budget.</p>\n</li>\n<li><p>5931a06: <code>ResizablePanels</code>, <code>ResizablePanel</code> and <code>ResizableHandle</code> (#444, part of #432): panels split by WAI-ARIA window splitters. Sizes are percentages that add up to 100; a handle trades only with its two neighbours and stops at each panel&#39;s <code>minSize</code> / <code>maxSize</code>. Arrow keys move 1%, Shift 10%, Home and End go to the limits (Up and Down in a <code>direction=&quot;vertical&quot;</code> group, mirrored in RTL). <code>persist</code> keeps the layout in <code>localStorage</code> and ignores a stored layout that does not match the panels; <code>onlayout</code> reports it. Replaces <code>react-resizable-panels</code> / shadcn <code>Resizable</code>. New i18n group <code>resizablePanels</code> (en, pt-BR).</p>\n</li>\n<li><p>194723a: <code>AppShellLayout</code> <code>stage</code> snippet (#343): a persistent background layer (map, canvas, video) rendered once behind the pages, so it keeps its state across route changes. The content region passes pointer events through (panels opt back in with <code>.ca-stage-panel</code>), the shell paints nothing behind it, and <code>stageVisible={false}</code> or <code>data-ca-stage=&quot;hidden&quot;</code> on <code>&lt;html&gt;</code> hides and pauses it without unmounting. The shell exposes <code>--ca-shell-inset-top|right|bottom|left</code> (rail and panel, mobile header, bottom navigation and safe area).</p>\n</li>\n<li><p>b46a5f7: <code>SimpleTable</code> gets a dense, spreadsheet-like mode (#360): <code>density=&quot;compact&quot;</code> (rows up to 40px), <code>frame={false}</code>, <code>scroll=&quot;none&quot;</code> (the caller owns scrolling, so the sticky header sticks to your page or panel), <code>stickyColumns={n}</code> (pinned columns keep an opaque background in hover, selected and focus states) and <code>headerCase=&quot;sentence&quot;</code>. New <code>TableRow</code> (<code>selected</code>, <code>onactivate</code>: Tab, Enter/Space and click, nested controls never bubble), <code>TableGroupRow</code> (collapsible, <code>aria-expanded</code>, sticky label) and <code>TableSortHeader</code> (<code>sort</code>, <code>onsort</code>, <code>aria-sort</code>).</p>\n</li>\n<li><p>b8edc32: New optional entry <code>@codeatlasdev/ui/sound</code>: opt-in, synthesized interface sound with no audio files (under 4 KB gzip). One shared <code>AudioContext</code> is created only inside the first pointer or key event after the user turns sound on with <code>&lt;SoundToggle /&gt;</code> (<code>aria-pressed</code>, accessible name, optional volume slider); the choice persists in <code>localStorage</code> key <code>ca-sound</code> and sound is off by default. Every cue is a short oscillator and noise recipe behind four profiles (<code>system</code>, <code>glass</code>, <code>brutal</code>, <code>terminal</code>) that follow the design style through <code>--ca-sound-profile</code> (written by <code>designStyle()</code>), tunable with <code>--ca-sound-gain</code>, <code>--ca-sound-pitch</code> and <code>--ca-sound-brightness</code>. Hover and slider tick cues are off under <code>prefers-reduced-motion</code>; sound is suspended while the tab is hidden or media plays, with one cue per name per 40 ms and at most four voices. Button, Switch, Toast (<code>toast.*(msg, { sound })</code>), Dialog, Tabs and Slider take a <code>sound</code> prop (<code>boolean | CueName</code>) that emits through <code>soundBus</code>, a no-op hook exported from the root, so core stays silent without the entry. <code>use:sound</code> (the <code>sound</code> export is also the action) plays a cue on any DOM event. Docs: Feedback &gt; Sound, and the Sound principles guide.</p>\n</li>\n<li><p>b476462: Official list of stable override hooks (#381). The manifest gets a <code>styling</code> section per component (<code>components.json</code>): stable <code>ca-*</code> parts, internal parts (<code>ca-x-*</code> classes and <code>@keyframes</code> names, which can change in any release), component-scoped <code>--ca-*</code> variables and the <code>data-*</code> attributes its CSS selects on. It is surfaced in <code>API.md</code>, in <code>dist/parts.json</code> and in a new MCP tool <code>list_parts</code>; <code>get_component</code> returns it too. <code>codeatlas-ui workarounds</code> now also scans <code>.css</code>, <code>.svelte</code> and <code>.astro</code> for <code>.ca-*</code> selectors that are not a stable part, and <code>--strict</code> exits 1 on them (a <code>ca-ui-workaround(#n)</code> marker on the line or the one above excuses it). A test fails when a documented part is no longer applied by any markup; it found and removed the dead <code>.ca-date__sep</code> rule.</p>\n</li>\n<li><p>3b50a5c: New <code>Stat</code> (#359): a card-less statistic with <code>label</code>, <code>value</code>, <code>unit</code>, <code>hint</code>, <code>trend</code>, <code>size</code> (<code>sm | md | lg</code>), <code>align</code> and <code>skeleton</code>. It renders a description list (label <code>&lt;dt&gt;</code>, value <code>&lt;dd&gt;</code>), has no surface, border or padding, uses tabular numerals, truncates the label and never wraps the value. <code>KpiCard</code> is now a <code>Stat</code> inside a <code>Card</code>, so both share one typography: the doubled inner padding (card padding + <code>.ca-kpi__inner</code> padding) is gone, and the unused <code>.ca-kpi__top</code> and <code>.ca-kpi__value-row</code> wrapper classes were removed; <code>.ca-kpi__title</code>, <code>__value</code>, <code>__unit</code>, <code>__trend(--up/--down)</code> and <code>__icon</code> stay.</p>\n</li>\n<li><p>b34ffd4: <code>Stat</code> and <code>KpiCard</code> take <code>trendTone</code> (<code>&#39;positive-up&#39; | &#39;positive-down&#39; | &#39;neutral&#39;</code>) so a trend can be good news when it falls (churn, cost, latency). The arrow still follows the sign, the colour follows the tone (hook: <code>data-tone=&quot;good|bad|neutral&quot;</code> on <code>.ca-stat__trend</code>) and the accessible label says better or worse (<code>Trend: -1.8%, better</code>, <code>Tendência: -1.8%, melhor</code>). Without a tone nothing changes. Apps that dropped <code>trend</code> and wrote the delta in <code>hint</code> can pass it again.</p>\n</li>\n<li><p>7bc89c7: <code>TextField</code>: <code>revealable</code> adds a show/hide eye button to <code>type=&quot;password&quot;</code> (<code>aria-pressed</code>, localized name, 44px hit area on touch, no layout shift), <code>below</code> renders a snippet under the field and its hint or error (a &quot;Forgot password?&quot; link, a strength meter) and <code>requiredIndicator={false}</code> hides the asterisk of a <code>required</code> field. New i18n group <code>textField</code> (en, pt-BR) (#348).</p>\n</li>\n<li><p>3aacf62: <code>ThemeIsland</code>: a block or a whole page with its own preset and scheme. <code>document</code> also mirrors <code>data-preset</code>, <code>data-theme-scope</code> and the <code>dark</code> class onto <code>&lt;html&gt;</code> while mounted (so <code>html</code>/<code>body</code>, overscroll and <code>color-scheme</code> match) and restores the app&#39;s values on unmount, with overlapping islands and theme-store writes handled. SSR parity: <code>setRouteTheme(event, { preset, scheme })</code> in a <code>load</code>, or <code>caThemeHandle(theme, { routes })</code> by route id prefix, writes the island on <code>&lt;html&gt;</code> in the server response, and the init script and the Astro <code>&lt;ClientRouter /&gt;</code> swap keep the app&#39;s own preset aside for the navigation back. The docs page explains how nested presets and <code>data-theme-scope</code> resolve (#352).</p>\n</li>\n<li><p>c39a426: New <code>ColorField</code> (swatch, native picker, <code>#hex</code>/<code>rgb()</code>/<code>oklch()</code> entry and a live WCAG contrast readout) and <code>SliderField</code> (label, slider and an editable number with a unit). <code>Slider</code> takes a <code>label</code> so a thumb is announced as &quot;Radius&quot; instead of &quot;Value 40&quot;. The color helpers behind them are public: <code>parseColorInput</code>, <code>contrast</code>, <code>wcagLevel</code>, <code>toHex</code>, <code>toOklch</code>, <code>formatOklch</code>, <code>extractPalette</code> and <code>pickBrand</code> (derive a brand from the pixels of a logo), plus <code>presetContrastIssues</code> and <code>presetTokens</code> for theme editors.</p>\n</li>\n<li><p>37cda52: toast: optional <code>description</code> (secondary line) and a single inline <code>action: { label, onClick }</code> on <code>toast.show</code> and the <code>success</code>/<code>error</code>/<code>warning</code>/<code>info</code> shortcuts, which now also take the options object as the second argument (positional <code>duration</code> still works). Clicking the action runs it and dismisses the toast; an actionable toast lasts 8 s by default. The auto-dismiss timer pauses while the toast is hovered or focused and resumes with the time left (<code>toast.pause(id)</code> / <code>toast.resume(id)</code>), and Escape dismisses the focused toast. Errors and warnings are now <code>role=&quot;alert&quot;</code>, the others stay <code>role=&quot;status&quot;</code>. The action is a text button with a 44px touch target on touch devices.</p>\n</li>\n<li><p>15074bf: A named type scale by role. <code>title</code>, <code>heading</code>, <code>body</code>, <code>label</code>, <code>caption</code> and <code>num</code> are size / leading / tracking / weight bundles (<code>--ca-text-&lt;role&gt;-size|leading|tracking|weight</code>, and <code>--ca-text-&lt;role&gt;</code> as a <code>font</code> shorthand), applied with <code>&lt;Text role&gt;</code> or <code>.ca-text-&lt;role&gt;</code>; <code>num</code> renders tabular digits so a ticking counter never shifts its neighbours. <code>ThemeOptions.type = { label: { size: &quot;13px&quot;, leading: 1.25, tracking: &quot;0&quot;, weight: 500 } }</code> overrides any part for the whole app, library components included: <code>--ca-font-size-label</code>, <code>-body1</code>, <code>-caption</code> and <code>--ca-label-tracking</code> are now derived from the roles, with the same default values, so existing components follow without edits. Unknown roles and values that are not a length, number or keyword throw at build time.</p>\n</li>\n</ul>\n<h3 id=\"v4-6-0-patch-changes\">Patch Changes</h3>\n<ul>\n<li><p>817b5c4: <code>get_recipe</code>, <code>llms.txt</code> and the decision tables now teach the 4.6 APIs: a map-first recipe (<code>AppShellLayout</code> <code>stage</code>, <code>MapStage</code>, <code>PageFrame</code>), rail labels and the &quot;More&quot; sheet, <code>ChipGroup</code>, <code>Stat</code>, dense tables, <code>Kanban</code>, <code>Drawer chromeless</code>, toast actions, <code>tone=&quot;neutral&quot;</code>, <code>ThemeIsland</code> and per-preset button colors. The &quot;common workarounds&quot; page lists what each one replaces.</p>\n</li>\n<li><p>caf1fa7: <code>AppList</code> is generic: <code>T</code> is inferred from <code>data</code>, so the <code>list</code> of its <code>filters</code>, <code>children</code> and <code>empty</code> snippets is a <code>UseListStateReturn&lt;T&gt;</code> (rows are typed, <code>searchKeys</code> and <code>idKey</code> are checked against <code>T</code>) instead of <code>any</code>, and a snippet annotated with <code>UseListStateReturn&lt;User&gt;</code> type-checks. The recipes now keep app copy in a typed <code>src/lib/messages.ts</code>, so they pass <code>codeatlas-ui lint</code>; the new App copy page explains the <code>i18n-literal</code> rule and that pattern.</p>\n</li>\n<li><p>7667a22: <code>AppShellLayout</code>: only the most specific nav item is active. With items at <code>/items</code> and <code>/items/a</code>, visiting <code>/items/a</code> marks just <code>Item A</code> (<code>aria-current</code>, active style, section auto-open) and the mobile header title is its label, no longer the overview&#39;s. Same rule as modules: longest path wins whatever the order. The overview item stays active on its other sub-paths. <code>findActiveItemPath</code> follows it and the new <code>findActiveItem(entries, pathname)</code> returns the item itself.</p>\n</li>\n<li><p>2ccbb9f: <code>caUI</code> (Astro) now puts the theme first in <code>&lt;head&gt;</code>, ahead of every stylesheet. It used <code>injectScript(&quot;head-inline&quot;)</code>, which Astro renders AFTER every bundled <code>&lt;style&gt;</code>: with <code>inlineStylesheets: &quot;always&quot;</code> the init script sat behind hundreds of KB of CSS and ran only once the browser had parsed it, so a slow link could paint the wrong theme first. A middleware now rewrites the rendered page, at build time and in SSR and dev alike, so the order holds whatever the layout or the bundler does. Font preloads move ahead of the CSS too. <code>caUI(theme, { head })</code> takes extra first-paint markup (a boot script) that lands right after the theme. <code>caThemeHandle</code> and <code>caThemePlugin</code> already emitted at the start of <code>&lt;head&gt;</code>.</p>\n</li>\n<li><p>6d65542: <code>AuthLayout</code>: a <code>brand</code> snippet now replaces the whole logo. The default &quot;CodeAtlas UI&quot; name no longer renders beside it, in the side panel or the mobile strip; pass <code>logoName</code> to show a name next to your brand (#340).</p>\n</li>\n<li><p>06fb8aa: AuthLayout: on a flat panel (<code>panelTone=&quot;flat&quot;</code>, or <code>auto</code> under the <code>flat</code> and <code>outlined</code> surface styles) <code>--panel-text-secondary</code> and <code>--panel-text-tertiary</code> are the theme&#39;s <code>--ca-text-secondary</code> and <code>--ca-text-hint</code> instead of alpha mixes of the foreground, which measured 4.39:1 and 2.4:1 in dark and 2.21:1 in light over the raised surface. The brand gradient keeps the alpha levels.</p>\n</li>\n<li><p>2b04e94: <code>AuthLayout</code>: the compact mobile brand mark now sits on <code>--ca-brand-fill</code> (the surface <code>--ca-brand-contrast</code> is paired with) instead of <code>--ca-brand-main</code>, so its label holds 4.5:1 in dark on every preset. It failed color-contrast in dark at 390px on six of the eight built-in presets.</p>\n</li>\n<li><p>1cf8c69: <code>--ca-brand-text</code> is now solved per preset and scheme instead of aliasing <code>--ca-brand-dark</code>/<code>--ca-brand-light</code>. A light brand (lime, yellow, light cyan) no longer gets a 1.6:1 label on outlined and ghost buttons, links and active nav: the shade is darkened (light scheme) or lifted (dark scheme) along the brand hue until it holds 4.5:1 on every surface and on the hover/selected fills. Built-in presets keep their light value; dark values lift slightly where they were under 4.5:1. <code>tests/contrast.test.ts</code> measures every preset, light brands and <code>colors.&lt;scheme&gt;.brand</code>.</p>\n</li>\n<li><p>35b5c9b: <code>&lt;Button type=&quot;submit&quot;&gt;</code> and <code>&lt;Button type=&quot;submit&quot; form=&quot;id&quot;&gt;</code> type-check in apps without <code>strict</code> (the SvelteKit 2 scaffold): svelte-check widens a static <code>type=&quot;submit&quot;</code> to <code>string</code>, which matched neither branch of the Button props union (<code>form</code> was <code>never</code>). The button branch now accepts the widened <code>string</code>. Documented on Button and Dialog: a submit button in a Dialog <code>actions</code> footer names its form with <code>form</code>.</p>\n</li>\n<li><p>ad005c0: <code>Card</code> and <code>GlassCard</code>: the header no longer adds its own padding on top of the card padding, so the title shares the left edge and top spacing of the content (#341). <code>flush</code> / <code>padding=&quot;none&quot;</code> cards keep the header inset. New <code>Card</code> prop <code>gap</code> (<code>&#39;sm&#39; | &#39;md&#39; | &#39;lg&#39; | length | number</code>) stacks several inline controls (Checkbox, Switch, TextField) in a column.</p>\n</li>\n<li><p>419d775: <code>Carousel</code>: the slide strip is now the focusable region (axe <code>scrollable-region-focusable</code>; arrows, Home and End work while it has focus, also when no slide holds a control), it no longer widens a flex or grid slot to the slides&#39; width, and previous / next / dots reach the 44px touch target. <code>Menubar</code> items get the 44px hit area under a coarse pointer. <code>TextField</code> can shrink inside a grid or flex slot narrower than the input&#39;s intrinsic width (addon fields overflowed phones).</p>\n</li>\n<li><p>c39a426: Charts follow a theme scope. They read their colors with <code>getComputedStyle</code> on <code>&lt;html&gt;</code>, so a chart inside a <code>ThemeIsland</code> or any <code>data-preset</code> / <code>data-theme-scope</code> element painted the page&#39;s colors, and its series palette ignored the island&#39;s preset. <code>ThemeIsland</code> now provides its element as the scope, and the new <code>ChartScope</code> (from <code>@codeatlasdev/ui/charts</code>) does the same for your own scoped containers. Without a scope nothing changes.</p>\n</li>\n<li><p>73f38cd: <code>CodeSnippet</code> highlights any language shiki bundles (<code>markdown</code>, <code>yaml</code>, <code>diff</code>, ...), loading it on first use, and falls back to plain text for a name it does not know. Before, a language outside the eight preloaded ones made the highlighter throw inside the promise and the snippet stayed on its unhighlighted fallback for good.</p>\n</li>\n<li><p>73f38cd: <code>CodeSnippet</code>: a code block that scrolls sideways is keyboard-reachable. The raw fallback shown before the highlighter loads (and the highlighted block) now use <code>scrollFocusable</code>, so a long line no longer leaves a scroll region that only a mouse can reach (axe <code>scrollable-region-focusable</code>).</p>\n</li>\n<li><p>475158b: The stylesheet drops 12 utility classes that no component, template or documented API ever used: <code>.glass-bg-extend</code>, <code>.glass-inner-sm|md|lg</code>, <code>.glass-prominent</code>, <code>.glass-scroll-edge-top|bottom</code>, <code>.ca-toolbar-sep</code>, <code>.ca-clear-btn</code>, <code>.ca-row-check</code>, <code>.ca-trend-up|down</code>. <code>styles/global.css</code> shrinks by 242 B gzip (17458 B to 17216 B) and its size budget goes back from 17500 B to 17250 B. An app that styled an element with one of these classes keeps the rule in its own CSS.</p>\n</li>\n<li><p>6aafbb5: A page with no <code>data-style</code> on <code>&lt;html&gt;</code> now renders as <code>data-style=&quot;solid&quot;</code>, the package default: no backdrop blur, opaque surfaces, no glow, the pulsing skeleton. Before, an app without the theme plugin (no <code>themeScript</code>, no <code>caUI</code>) kept the glass tokens, so every <code>data-ca-blur</code> element (chips, tabs, toasts, rail, panels) blurred. Glass, frosted and the other styles are unchanged: set <code>data-style</code> to opt in.</p>\n</li>\n<li><p>ce8ed51: Docs, skill, AGENTS.md, README, MCP <code>list_surfaces</code> and component JSDoc present the library as theme-driven with <code>style</code> as one knob (default <code>solid</code>) and glass as an opt-in style. Glass-only rules (<code>backdrop-filter</code>, <code>data-ca-blur</code>, <code>will-change</code>) are scoped to a &quot;Glass and frosted styles&quot; section with their wording unchanged. Examples, templates and demo specs no longer pin <code>material=&quot;glass&quot;</code>. The <code>liquid-glass</code> npm keyword is replaced by <code>theming</code> and <code>design-tokens</code>. No API changes.</p>\n</li>\n<li><p>866912f: <code>FileUpload</code>: the drop zone&#39;s accessible name now includes the visible hint (<code>aria-labelledby</code> over the label and the hint), fixing the axe <code>label-content-name-mismatch</code> / WCAG 2.5.3 failure (#490). With a custom <code>children</code> snippet the zone keeps <code>aria-label</code>.</p>\n</li>\n<li><p>fbc0d75: The gallery filters by facet. Chips for kind, subtype, style, motion, interaction, layout, colour mode, density, tech and cost narrow the cards instantly, show how many entries each value would match, and drop combinations that match nothing. Words match titles, aliases, tags and components. Facets, words and the group live in the URL (<code>?kind=motion&amp;style=aurora&amp;q=fade</code>), so every filter is a shareable link. Only published facets are offered.</p>\n</li>\n<li><p>99a3dbd: A gallery item opens on Preview, Code, Prompt and Anatomy tabs (<code>?tab=prompt</code>, <code>?tab=anatomy</code>). Prompt is the text to hand an AI: the <code>get_demo</code> call, the library parts, the behaviour to keep, the exact motion timings and the reduced-motion and keyboard promises read from the index. Anatomy lists the components with their package and docs link, how the demo is built, and related items. Pages show Preview and Prompt.</p>\n</li>\n<li><p>678d673: <code>init --write</code> on SvelteKit 2 with <code>@sveltejs/adapter-static</code> creates <code>svelte.config.js</code> with the adapter and <code>vitePreprocess</code>, or adds <code>adapter: adapter()</code> and its import to an existing config that names no adapter. Before, <code>vite build</code> ran with no adapter and no <code>build/</code> output. A config that already sets <code>adapter</code> is untouched; Kit 3 is unchanged (the adapter goes into <code>vite.config</code>).</p>\n</li>\n<li><p>7fe318d: <code>init</code> on SvelteKit adds <code>&quot;prepare&quot;: &quot;svelte-kit sync&quot;</code> to <code>package.json</code> when the app has no <code>prepare</code> script, so <code>svelte-check</code> on a fresh clone no longer stops with <code>File &#39;$app/tsconfig&#39; not found</code>. <code>src/lib/messages.ts</code> carries only <code>appTitle</code> unless <code>--with pwa</code> is given: the <code>offline</code> keys are written (or added to the file <code>init</code> wrote) only with the offline page that reads them.</p>\n</li>\n<li><p>b8e217e: Surfaces and chips inside a <code>ThemeIsland</code> (and a <code>PreviewStage</code> with a <code>scheme</code> or <code>preset</code>) follow the island, not the page around it. The layer tokens (<code>--ca-overlay-bg</code>, <code>--ca-panel-bg</code>, <code>--ca-chrome-bg</code>, <code>--ca-upload-bg</code>) and the solid style&#39;s tint overrides are re-declared on <code>[data-theme-scope]</code>, so a dark island on a light page no longer inherits the page&#39;s resolved light surface (a <code>FileUpload</code> zone, an <code>AppForm</code> footer) under dark text. The <code>success</code>, <code>error</code>, <code>warning</code> and <code>info</code> chip text follows <code>[data-theme-scope]</code> as well as <code>.dark</code>.</p>\n</li>\n<li><p>fe9c5d3: <code>Kanban</code> no longer collapses to 0px wide inside an auto-width flex wrapper (a wrapping flex row, an island): the size-contained board now has a one-column floor width (<code>contain-intrinsic-inline-size</code>), while a narrower parent still shrinks it and the columns never widen the page (#530).</p>\n</li>\n<li><p>7873a89: The Landing page recipe links only to in-page anchors and to <code>/sign-in</code> and <code>/sign-up</code> (the Auth pages recipe), so copying it whole prerenders with <code>adapter-static</code>; before, <code>/pricing</code>, <code>/docs</code> and <code>/contact</code> failed the build with a 404.</p>\n</li>\n<li><p>85086c5: The Landing page recipe now prerenders when copied on its own: the sign-up and sign-in buttons and the plan CTAs use <code>signUp</code>/<code>signIn</code> constants at the top of the page that are in-page anchors (<code>#pricing</code>), instead of linking to <code>/sign-up</code> and <code>/sign-in</code>, which 404 the prerender unless the Auth pages recipe is also in the app. Point the constants at the real routes once they exist.</p>\n</li>\n<li><p>82edb9d: The close button of <code>Dialog</code>, <code>AlertDialog</code>, <code>Drawer</code> and <code>Sheet</code> (<code>.ca-layer-close</code>, drawn 28px) gets the invisible 44px hit area under a coarse pointer, like every other small control. It was missing from the touch list, so an overlay measured 27 by 27 on a phone, inline overlays included.</p>\n</li>\n<li><p>b8edc32: A tooltip or hover card that was open when its trigger opened a dialog no longer leaves a second, stray copy in the page after the dialog closes. The layer stack put a hosted hint back at its old position even when the dialog had already been removed with the hint inside, resurrecting a node its owner had dropped (no <code>popover</code>, in normal flow, same <code>id</code>).</p>\n</li>\n<li><p>503a842: <code>init --write</code> adds the <code>&quot;imports&quot;: { &quot;#lib/*&quot;: &quot;./src/lib/*&quot; }</code> map to <code>package.json</code> and writes <code>#lib/theme.ts</code> / <code>#lib/messages.ts</code> imports on SvelteKit 2 as well as 3 (Kit 2&#39;s Vite and TypeScript resolve the subpath import, verified by building and type-checking a Kit 2 app). The recipes, app-copy, installation and PWA pages now use the one spelling <code>#lib/...ts</code> on both majors instead of telling Kit 2 users to rewrite every import to <code>$lib</code>. An existing <code>$lib</code> import on Kit 2 keeps working and is never rewritten.</p>\n</li>\n<li><p>9e751a2: <code>llms.txt</code> and the <code>AGENTS.md</code> block written by <code>init</code> say that <code>dist/components.json</code> keeps the components under <code>.components[]</code> and every other export under <code>.exports[]</code>.</p>\n</li>\n<li><p>3b76318: <code>llms-full.txt</code> keeps only the props table of each non-core page. The summary also carried the prose and examples that follow a table (the SEO page alone added 9 KB), which pushed the file past its size cap once the Concepts pages were added; the descriptions stay in each page&#39;s own file.</p>\n</li>\n<li><p>ce45681: <code>llms.txt</code> links the agent-readable <code>.txt</code> form of every docs page (<code>/llms/&lt;group&gt;/&lt;slug&gt;.txt</code>, index pages <code>/llms/&lt;slug&gt;.txt</code>) and of each recipe (<code>/llms/recipes/&lt;id&gt;.txt</code>), with the HTML URL beside it, and states the fetch rule at the top. <code>dist/components.json</code> (and <code>get_component</code>) now list the layout token props of <code>Flex</code>, <code>Stack</code>, <code>Grid</code>, <code>Box</code>, <code>Container</code>, <code>Section</code> and <code>Surface</code> (<code>gap</code>, <code>align</code>, <code>justify</code>, <code>direction</code>, <code>wrap</code>, <code>columns</code>, <code>p</code>, <code>px</code>, <code>py</code>) from their <code>LayoutProps</code> pick.</p>\n</li>\n<li><p>b8edc32: <code>LoadingBoundary</code> holds the placeholder for the full <code>minDuration</code>: the hold re-checks the clock when its timer fires and re-arms for the remainder, instead of ending up to a millisecond early when a timer fires ahead of <code>performance.now()</code>.</p>\n</li>\n<li><p>de1c873: <code>components.json</code> (<code>exports[].members</code>), <code>API.md</code> and the MCP <code>get_export</code> now list the fields of every exported interface (<code>NavModule</code>, <code>NavItem</code>, <code>NavSection</code>, option and state types) with type, optionality and JSDoc, instead of one collapsed line.</p>\n</li>\n<li><p>573950f: <code>components.json</code>, <code>API.md</code> and the MCP <code>get_component</code> spell out the values of a prop typed with a literal-union alias (<code>Avatar</code> <code>shape: &#39;circle&#39; | &#39;rounded&#39;</code>, <code>size</code>, <code>status</code>, <code>Card</code> <code>headingLevel: 2 | 3 | 4 | 5 | 6</code>, <code>Button</code> <code>variant</code> and about 100 more) instead of the bare alias name, and <code>API.md</code> keeps the description of each snippet (<code>SkipLink</code> <code>children</code> replaces the label).</p>\n</li>\n<li><p>48e1d42: <code>@codeatlasdev/ui/map</code>: <code>MapStage</code> works with <code>maplibre-gl</code> 6, so the peer range is <code>^5.0.0 || ^6.0.0</code> again and you can take the fix for the critical attribution-sanitizer XSS in <code>maplibre-gl</code> 6.4.0 and earlier (GHSA-jrc7-96c5-q579, fixed in 6.4.1). With <code>engine=&quot;maplibre&quot;</code> the deck.gl <code>layers</code> now go through <code>MapLibreOverlay</code> from the new optional peer <code>@deck.gl/maplibre</code> (<code>~9.4.0</code>, which supports <code>maplibre-gl</code> 4.5.1, 5 and 6) instead of <code>MapboxOverlay</code>, which throws <code>Cannot read properties of undefined (reading &#39;height&#39;)</code> on every frame there. <code>engine=&quot;mapbox&quot;</code> keeps <code>@deck.gl/mapbox</code>. Install <code>@deck.gl/maplibre</code> instead of <code>@deck.gl/mapbox</code> when you use <code>maplibre-gl</code>. On <code>maplibre-gl</code> 6 the app must also call <code>setWorkerUrl</code> once (Vite: <code>import workerUrl from &quot;maplibre-gl/dist/maplibre-gl-worker.mjs?worker&amp;url&quot;</code>), as MapLibre&#39;s v6 migration guide requires for every bundler; the Map page shows it.</p>\n</li>\n<li><p>b53e1c2: <code>MapStage</code>: fitting clear of a wide <code>PageFrame</code> no longer throws <code>Invalid LngLat object: (-Infinity, NaN)</code>. The stage set the map&#39;s padding and passed the same padding to <code>fitBounds</code>, which subtracted it twice and left a wide frame no area; the bounds now fit inside the visible area exactly once (#407).</p>\n</li>\n<li><p>0169158: <code>nativeFeel()</code> no longer forces the arrow cursor on buttons, links, tabs, menu items and options. <code>html[data-ca-native]</code> set <code>cursor: default</code> in <code>ca.base</code>, which outranks the pointer cursor of <code>ca.reset</code>, so an app shell never showed the hand on clickable controls (nor <code>not-allowed</code> on disabled ones). The rule keeps blocking text selection and the touch callout on chrome; the cursor now comes from the reset layer alone.</p>\n</li>\n<li><p>b8edc32: <code>NavigationMenu</code>: Enter or Space on a trigger always toggles its panel. The click grace that lets a pointer click confirm a hover-opened panel also swallowed a keyboard activation made within 600 ms of a hover open, so a keyboard user could not close the panel.</p>\n</li>\n<li><p>2ccbb9f: The theme init script no longer fails with the page. When <code>localStorage</code> throws on access (private mode, blocked cookies, a sandboxed iframe) it used to abort before setting <code>data-preset</code>, <code>data-style</code> and the <code>dark</code> class, so the page painted unthemed and then switched once the store hydrated. It now falls back to the theme&#39;s defaults on the first frame, and a failing <code>removeItem</code> while discarding a corrupt value no longer stops the rest of the script. A size-limit entry pins the emitted script (under 1 kB gzip).</p>\n<p><code>colorScheme.setTokens</code> now actually applies in dark mode with a brand preset. Runtime tokens were written as <code>:root{…}</code> (specificity 0,1,0) and lost to the preset&#39;s <code>.dark[data-preset]</code> block (0,2,0) whatever the source order, so the stored tokens replayed by the init script, and the ones the store wrote, silently did nothing. Both now use <code>:root:root:root{…}</code>.</p>\n</li>\n<li><p>8e950af: <code>NotificationBell</code>: the trigger&#39;s accessible name now starts with the visible unread count (<code>2 unread notifications</code> / <code>2 notificações não lidas</code>, plain <code>Notifications</code> at zero), so it contains the visible badge text (WCAG 2.5.3, axe <code>label-content-name-mismatch</code>). Custom <code>notificationBell.label</code> overrides should keep the count in the name.</p>\n</li>\n<li><p>ae3b5bd: <code>PageFrame</code>: on mobile (&lt;=900px) the full-screen panel and the bottom sheet no longer overflow the viewport by the frame gap with <code>side=&quot;left&quot;</code>. The desktop <code>data-side</code> position rules out-ranked the mobile <code>inset: 0</code> reset, so the panel kept <code>left: 12px</code> with <code>width: 100%</code> (right edge 402px on 390px). Fixes #489.</p>\n</li>\n<li><p>57ff10e: PageShell: below 900px only <code>Button</code>s in <code>actions</code> stretch across the row. <code>StatusChip</code>, <code>Chip</code>, <code>Badge</code> and plain text keep their natural width instead of becoming a full-width bar (#339).</p>\n</li>\n<li><p>e5cb35d: <code>PageShell</code> and <code>PageFrame</code> warn once in development when the page has no <code>&lt;main&gt;</code> landmark (no <code>main</code>/<code>[role=main]</code> ancestor and none in the document), pointing at the Standalone page recipe. Silent inside <code>AppShellLayout</code>, <code>AuthLayout</code>, <code>DocsShell</code> or your own <code>&lt;main&gt;</code>. <code>llms.txt</code> lists Standalone page first under &quot;Start here&quot;.</p>\n</li>\n<li><p>99ab5db: <code>.ca-prose</code> and <code>.ca-code</code> fences: the <code>code</code> inside a <code>pre</code> inherits the pinned <code>--ca-font-mono</code> stack instead of the browser&#39;s bare <code>monospace</code>, which Chromium could resolve to a different font (and line height) between two paints.</p>\n</li>\n<li><p>0168dcf: Theme: <code>--ca-font-size-body1</code> no longer sets the root font-size. <code>:root</code> keeps the kit&#39;s rem base (<code>calc(0.9375rem * var(--ca-font-scale))</code>, 15px by default, still relative to the user&#39;s browser size), so default rendering is unchanged and overriding body1 only sizes the text on <code>body</code>. A denser body no longer shrinks labels, inputs, buttons and captions (#351).</p>\n</li>\n<li><p>b8e217e: <code>scrollFocusable</code> re-measures when content is added at any depth, not only as a direct child of the scroller. A fixed-height wrapper never resizes when the content it holds grows, so a lazily loaded demo inside a <code>PreviewStage</code> frame left its scrollable region without a tab stop.</p>\n</li>\n<li><p>b8e217e: <code>PreviewStage</code> frames and <code>ResizablePanel</code>s that scroll are keyboard-reachable while their content overflows (<code>scrollFocusable</code>, WCAG 2.1.1): a demo taller than the stage, or a narrow panel whose content is wider than it, no longer traps its content out of keyboard reach. Without overflow they add no tab stop.</p>\n</li>\n<li><p>b8e217e: <code>SegmentedControl</code> items are at least 44px wide as well as tall under a coarse pointer: in a narrow slot the equal shrinkable columns squeezed short labels (a &quot;390&quot; preset) to 38px, under the touch target.</p>\n</li>\n<li><p>03c5f65: The Settings page recipe no longer tells a <code>Switch</code> inside a <code>SettingItem</code> to carry its own <code>aria-label</code>: the row is a <code>&lt;label&gt;</code> and already names it (as the SettingItem page says), so the redundant attribute and the rule are gone.</p>\n</li>\n<li><p>f45caa6: <code>SiteHeader</code>: a <code>NavigationMenu</code> placed directly in <code>trailing</code> is hidden below 600px, and <code>trailing</code> can shrink (<code>min-width: 0</code>). A marketing bar with three nav links, a <code>LocaleSwitcher</code> and two buttons overflowed the page horizontally at 390px (right edge 437px); the locale switcher and the buttons now stay in the bar and the links belong in a mobile menu of your own (#476).</p>\n</li>\n<li><p>169db3e: toast: identical messages no longer stack. <code>toast.error(&quot;x&quot;)</code> ten times leaves one toast with a <code>×10</code> badge and a restarted timer, at most three toasts show at once (the oldest is dropped), and <code>{ id }</code> replaces a specific toast in place. The shortcuts (<code>success</code>, <code>error</code>, <code>warning</code>, <code>info</code>) also accept an options object (<code>{ duration, id }</code>) as the second argument; positional <code>duration</code> still works. <code>ToastItem</code> gains <code>count</code> and <code>ToastOptions</code> is exported.</p>\n</li>\n<li><p>9a10232: Touch targets: the password reveal button (<code>TextField revealable</code>) gets its 44px hit area on touch again. A merge had fused two selectors in the coarse-pointer list (#417).</p>\n</li>\n<li><p>b8edc32: <code>VirtualList</code> no longer drags the user back down after older rows are prepended. A prepend always takes its own scroll anchor, and a restore that was waiting for its tick when the prepend landed no longer applies the anchor it captured against the old offsets (the list jumped to the old first row, about 20 rows down, and skipped the next <code>onloadmore</code>).</p>\n</li>\n<li><p>cb6da91: <code>VirtualList</code>: a scroll made while the anchor restore after a prepend or a row re-measure is still queued is no longer undone. On a slow machine the late restore dragged the list back to the old anchor, so a user scrolling to the top to load the next page was thrown ~1000px down.</p>\n</li>\n</ul>\n","thanks":"To our main supporters and special contributors, Fábio Henrique Araújo (@FabioHAraujo) and Richardson Douglas (@Richards0nd). 💛"},{"version":"4.5.0","id":"v4-5-0","date":"2026-10-17","published":"2026-10-17T15:35:39Z","channel":"latest","url":"https://github.com/codeatlasdev/ui/releases/tag/%40codeatlasdev%2Fui%404.5.0","npm":"https://www.npmjs.com/package/@codeatlasdev/ui/v/4.5.0","html":"<h3 id=\"v4-5-0-minor-changes\">Minor Changes</h3>\n<ul>\n<li>c23cff4: i18n: the message catalog is split per group and each component bundles only its own strings. <code>root: Button only</code> drops from 27.89 to 23.91 kB gzip and <code>AppShellLayout</code> from 39.88 to 36.07 kB. <code>Messages</code>, <code>getMessages</code>, <code>useT</code>, <code>I18nProvider</code>, <code>setLocale</code> and <code>registerLocale</code> behave as before; <code>useT()</code> and <code>getMessages()</code> still return the full catalog, which is bundled only when you import them.</li>\n</ul>\n","thanks":"To our main supporters and special contributors, Fábio Henrique Araújo (@FabioHAraujo) and Richardson Douglas (@Richards0nd). 💛"},{"version":"4.4.0","id":"v4-4-0","date":"2026-10-17","published":"2026-10-17T14:03:53Z","channel":"latest","url":"https://github.com/codeatlasdev/ui/releases/tag/%40codeatlasdev%2Fui%404.4.0","npm":"https://www.npmjs.com/package/@codeatlasdev/ui/v/4.4.0","html":"<h3 id=\"v4-4-0-minor-changes\">Minor Changes</h3>\n<ul>\n<li><p>1c6653e: <code>NavItem</code> in the AppShell panel gets <code>color</code> (a decorative dot), <code>leading</code> (a snippet that replaces the icon), <code>count</code> (a neutral total, announced as a bare number instead of &quot;N unread&quot;, <code>1.2k</code> above 999) and <code>actions</code> (a snippet of <code>DropdownMenuItem</code>s behind a &quot;…&quot; button named &quot;Actions for &lt;label&gt;&quot;) (#275). <code>NavSection.onReorder</code> and <code>NavModule.onReorder</code> make the rows reorderable with the <code>useDnd</code> primitive: a grip per row, mouse, touch press-and-hold and keyboard, with announcements; the shell reports <code>{ id, from, to }</code> (new <code>NavReorderEvent</code> type) and never reorders <code>items</code> itself. <code>PanelNavItem</code> now renders a row that contains the <code>&lt;a class=&quot;ca-nav-item__link&quot;&gt;</code> (a link cannot hold the actions button or the grip), so <code>.ca-nav-item</code> is the row, not the anchor. New messages <code>appShell.itemActions</code> and <code>appShell.moveItem</code>.</p>\n</li>\n<li><p>1c6653e: <code>AppShellLayout</code> takes <code>panelWidth</code> (px, held between 160 and 480, default 200) and <code>resizablePanel</code> (#292). The content offset and the open and close animation follow the width, so a panel for long labels no longer misaligns the page. <code>resizablePanel</code> adds a keyboard-operable drag handle on the panel&#39;s trailing edge (Arrow keys 8px, Shift 32px, Home and End for the bounds); with <code>persistPanel</code> the chosen width is stored in <code>ca-shell</code> and restored by the inline script before the first paint. New <code>appShell.resizePanel</code> message and <code>.ca-panel__resize</code> class.</p>\n</li>\n<li><p>ca80cfd: <code>Combobox</code>: <code>multiple</code> turns it into a searchable multi-select (<code>value</code> is <code>string[]</code>, <code>aria-multiselectable</code> listbox, list stays open, removable chips, <code>Backspace</code> on an empty query removes the last chip) and <code>creatable</code> works in both modes. <code>CommandPalette</code>: <code>search(query, signal)</code> adds debounced (<code>searchDebounce</code>), abortable server-side results with loading, empty and failed states announced through <code>announce()</code>, plus <code>onSelect</code> and the exported <code>CommandResult</code> type.</p>\n</li>\n<li><p>49116d1: <code>DataTable</code> gains row groups, column resize, row reorder and safer interactive cells (#283). <code>groupBy</code> renders one <code>&lt;tbody&gt;</code> per key with a collapsible header (<code>aria-expanded</code>, <code>groupLabel</code> to customize). <code>meta.resizable</code> on a column adds a window-splitter handle (mouse, touch, keyboard arrows, Shift, Home, End, Enter) driven by <code>size</code>, <code>minSize</code> and <code>maxSize</code>; the new <code>DataTableColumnResize</code> is exported for hand-built headers. <code>onReorder</code> adds a drag grip per row on top of the new <code>useDnd</code> primitive, moves rows between groups and reports <code>{ id, from, to }</code> (<code>DataTableReorderEvent</code>); <code>rowId</code> gives rows stable ids and <code>pageSize</code> sets the page size in simple mode. A click on a link, button, input or other control inside a cell no longer fires <code>onRowClick</code> (add <code>data-no-row-click</code> to opt other elements out). The new labels (Reorder, Move, Resize) follow the active locale (en, pt-BR). Needs the drag and drop primitive from #284.</p>\n</li>\n<li><p>d138bd8: New drag and drop primitive, <code>useDnd()</code>, <code>applyMove</code> and <code>&lt;DndLayer&gt;</code>, for sortable lists, moves between lists and drop zones (#284). It works with mouse and pen (4px threshold), touch (press and hold, so the page still scrolls) and keyboard (Space lifts, arrows move, Home/End jump, Enter drops, Escape cancels), speaks every step through <code>announce()</code> (English and Portuguese built in, <code>useDnd({ messages })</code> for others), auto-scrolls near the edges of a scrolling container and drops its animations under <code>prefers-reduced-motion</code>. The engine measures the boxes once per drag and only moves a clone, a drop line and <code>data-dnd-*</code> attributes, so the list does not re-render until <code>onDrop</code>. <code>&lt;DndLayer {dnd} /&gt;</code> renders the keyboard instructions and the forced-colors aware styles (<code>.ca-dnd-overlay</code>, <code>.ca-dnd-indicator</code>), so apps that never drag add no bytes to <code>core.css</code> or to the i18n catalog.</p>\n</li>\n<li><p>946d69d: <code>Drawer</code> can be named without its default title: pass <code>aria-label</code>, <code>aria-labelledby</code> (your own header) or <code>hideTitle</code>, and <code>id</code>, <code>data-*</code> and other attributes now reach the <code>&lt;dialog&gt;</code> (#285). <code>AlertDialog</code> gains <code>confirmProps</code> and <code>cancelProps</code> (attributes for the two buttons) and <code>cancelVariant</code> (#287), plus a <code>children</code> body, a <code>description</code> that accepts a snippet and <code>confirmDisabled</code> for type-to-confirm (#278).</p>\n</li>\n<li><p>e983caa: <code>DropdownMenu</code> is controllable and can open at a point. <code>bind:open</code> and <code>onOpenChange</code> read and set the open state; <code>anchor={{ x, y }}</code> opens the menu at a viewport point (a right-click menu, no <code>trigger</code> needed) and changing it while open moves the menu instead of toggling it. A menu opened from code starts on its first item and returns focus to the element that had it. On the built-in trigger Shift+F10 and the ContextMenu key open the menu like ArrowDown.</p>\n</li>\n<li><p>e983caa: <code>DropdownMenu</code> gains selection items. <code>DropdownMenuCheckboxItem</code> (<code>role=&quot;menuitemcheckbox&quot;</code>, <code>bind:checked</code>, <code>onCheckedChange</code>) and <code>DropdownMenuRadioGroup</code> + <code>DropdownMenuRadioItem</code> (<code>role=&quot;menuitemradio&quot;</code>, <code>bind:value</code>, <code>onValueChange</code>) render <code>aria-checked</code> and a check indicator, stay open after a pick (<code>closeOnSelect</code> closes), and join arrow, Home, End and typeahead navigation (the menu now also recognizes <code>menuitemradio</code>).</p>\n</li>\n<li><p>e983caa: <code>DropdownMenu</code> gains submenus. <code>DropdownMenuSub</code> takes a <code>trigger</code> snippet holding a <code>DropdownMenuSubTrigger</code> item (<code>aria-haspopup=&quot;menu&quot;</code>, <code>aria-expanded</code>, chevron) and the submenu items as children; they nest to any depth. ArrowRight, Enter or Space opens the submenu on its first item, ArrowLeft or Escape closes only that level, hovering the trigger opens it after a short intent delay and the pointer can cross into it. Selecting an item closes every level. A popover layer that is closing no longer owns Escape or outside presses, so a closing submenu hands them to its parent.</p>\n</li>\n<li><p>ff460ad: Form controls forward native attributes and expose their element, with one vocabulary (<code>ref</code>, <code>hideLabel</code>, rest props on the native element):</p>\n<ul>\n<li><code>Checkbox</code>, <code>TextField</code>, <code>TextArea</code> and <code>FileUpload</code> spread every other attribute and handler (<code>data-*</code>, <code>maxlength</code>, <code>inputmode</code>, <code>onclick</code>, <code>onkeydown</code>, <code>name</code>, <code>capture</code>...) on the native <code>&lt;input&gt;</code>/<code>&lt;textarea&gt;</code>, and take a bindable <code>ref</code> with that element. <code>Checkbox</code> <code>onchange</code> now receives the native event as a second argument, and its forwarded <code>onclick</code> makes Shift range selection possible. <code>TextField</code>/<code>TextArea</code> gain <code>onfocus</code> and <code>hideLabel</code> (label kept for screen readers with <code>.ca-sr-only</code>; <code>aria-label</code> also works without a label).</li>\n<li><code>Select</code> accepts <code>aria-label</code> and <code>aria-labelledby</code> for a compact select without a visible label. The trigger is named by that text followed by the current value (visible text stays in the name); without either, the placeholder names it as before.</li>\n<li><code>FileUpload</code> gains <code>ref</code>, an <code>open()</code> instance method for a button outside the dropzone, and <code>list={false}</code>: a plain picker whose <code>onchange</code> receives only the files just picked, with no built-in list and nothing kept in <code>files</code> (the native input is cleared, so the same file can be picked again).</li>\n</ul>\n</li>\n<li><p>8c24c31: Theme option <code>motion: { enter: &quot;none&quot; }</code> (token <code>--ca-enter-scale: 0</code>) turns off entrance and exit animations only: <code>Dialog</code>, <code>AlertDialog</code>, <code>Drawer</code>, <code>Sheet</code>, <code>Lightbox</code>, <code>CommandPalette</code>, <code>DocsSearch</code>, date pickers, <code>DropdownMenu</code>, <code>Popover</code>, <code>Tooltip</code> and <code>Toast</code> appear and leave in place, while hover and focus color transitions keep <code>--ca-duration-*</code>. New derived tokens <code>--ca-enter-fast</code>, <code>--ca-enter-base</code> and <code>--ca-enter-slow</code>; <code>MotionStage</code> demos render their final state when the theme turns entrances off, as under <code>prefers-reduced-motion</code> (#268).</p>\n</li>\n<li><p>16f89dd: <code>Popover</code> is controllable. <code>bind:open</code> and <code>onOpenChange</code> read and set the open state, and the <code>children</code> snippet receives <code>close</code>, which closes the popover and returns focus to the trigger, so a choice made inside it (a <code>Calendar</code> day, an option) can dismiss it. Existing popovers are unchanged.</p>\n</li>\n<li><p>8c24c31: New <code>--ca-radius-round</code> token (<code>999px × --ca-radius-scale</code>): &quot;fully rounded at the current scale&quot;. <code>SwatchGroup</code> swatches, the AppShell active pips (<code>.ca-nav-item__pip</code>, <code>.ca-rail__pip</code>) and the new-item dot, and <code>.ca-nav-item__badge</code> use it, so a theme with <code>radius: 0</code> squares them with no app CSS (#267). <code>--ca-radius-chip</code> now aliases it. <code>--ca-radius-pill</code> stays a fixed 999px for functional shapes (Switch, Slider, Progress, scrollbars) and for <code>controlRadius: &quot;pill&quot;</code>.</p>\n</li>\n<li><p>946d69d: <code>Tabs</code> gains <code>variant=&quot;line&quot;</code> (bare text tabs with an underline on the active one, for page headers), and <code>TabsList</code> and <code>TabsTrigger</code> forward rest attributes: <code>aria-label</code> names the tablist, <code>data-*</code>, <code>id</code> and event handlers reach the trigger button (#286). A <code>TabsTrigger</code> now points <code>aria-controls</code> at its panel only while a <code>TabsContent</code> with the same <code>value</code> is mounted, so tabs used as view switchers no longer reference missing elements.</p>\n</li>\n<li><p>2ab0d93: <code>ToggleGroup</code> gets <code>required</code>: pressing the active item no longer clears the selection (radio-like, <code>onchange</code> stays silent; <code>type=&quot;multiple&quot;</code> keeps its last pressed item). An empty group still selects on the first press. Closes the gap with <code>SegmentedControl</code> for filters that must look like contiguous buttons.</p>\n</li>\n</ul>\n<h3 id=\"v4-4-0-patch-changes\">Patch Changes</h3>\n<ul>\n<li>05cfbfc: <code>AppShellLayout</code>: an item at <code>path: &quot;/&quot;</code> is active on <code>/</code> only, no longer on every page, and a module at <code>basePath: &quot;/&quot;</code> only gets the pages no other module claims. Module prefixes (<code>basePath</code>, <code>pathPrefixes</code>) match whole path segments and the longest one wins, whatever the module order (<code>/settingsfoo</code> no longer matches <code>/settings</code>).</li>\n<li>98d2ace: Astro: the theme now survives <code>&lt;ClientRouter /&gt;</code> (View Transitions) navigations. <code>caUI</code> registers an <code>astro:before-swap</code> handler that carries <code>#ca-theme</code>, <code>#ca-runtime-tokens</code>, the <code>theme-color</code> meta and the theme attributes onto the incoming document (#265). <code>codeatlas-ui init</code> on Astro also adds <code>svelte()</code> from <code>@astrojs/svelte</code> to <code>integrations</code>, and notes <code>npx astro add svelte</code> when the dependency is missing (#273).</li>\n<li>3ae1f41: <code>Button</code> with <code>href</code> now types the anchor&#39;s own attributes: <code>download</code>, <code>rel</code>, <code>hreflang</code>, <code>ping</code>, <code>referrerpolicy</code> and <code>type</code> (the linked resource&#39;s MIME type) are accepted instead of rejected, as <code>...rest</code> was already spread on the <code>&lt;a&gt;</code>. The props are a union keyed on <code>href</code>: a link takes anchor attributes, a button takes button attributes (<code>form</code>, <code>formaction</code>, <code>name</code>, <code>value</code>...), and <code>&lt;Button download&gt;</code> without <code>href</code> is a type error. At run time a <code>type</code> passed with <code>href</code> reaches the <code>&lt;a&gt;</code>, and a caller <code>rel</code> is kept and merged with <code>noopener noreferrer</code> when <code>target=&quot;_blank&quot;</code> (it used to be overwritten).</li>\n<li>5c9ac81: CommandPalette: new <code>shortcut</code> prop (default <code>true</code>). With two palettes on one page, every instance opened on Mod+K; set <code>shortcut={false}</code> on all but one, or when the app binds its own shortcut.</li>\n<li>a61874b: Calendar, RangeCalendar, DatePicker and DateRangePicker: fast arrow-key sequences no longer lose keys. The roving focus now moves synchronously instead of after <code>tick()</code>, so the next key always starts from the day it lands on, with or without Svelte&#39;s <code>experimental.async</code>.</li>\n<li>e983caa: <code>DropdownMenuItem</code> forwards its remaining attributes (<code>data-*</code>, <code>id</code>, <code>aria-*</code>, <code>title</code>, ...) to the <code>menuitem</code> element. <code>data-testid</code> and <code>aria-keyshortcuts</code> used to be dropped. <code>role</code>, <code>tabindex</code> and the selection handler stay owned by the item.</li>\n<li>ff8686f: <code>DropdownMenuItem</code> and <code>TabsTrigger</code> icon slots now size any <code>svg</code> or <code>img</code> inside the 14px box, not only a direct child. A <code>SchemeAsset</code> logo (its variants sit in <code>display: contents</code> spans) used to collapse to 0×0 when the svg had no <code>width</code>/<code>height</code>, and an svg with a fixed <code>width=&quot;24&quot;</code> overflowed the box; both now fill the box.</li>\n<li>d01c039: <code>LoadingBoundary</code> counts <code>minDuration</code> from the moment the placeholder reaches the DOM instead of from the state change that schedules it, so a busy main thread no longer shortens the time the placeholder is visible.</li>\n<li>23276cf: Fix Escape going to the wrong layer. A Dialog, AlertDialog, Drawer, Sheet, Lightbox, CommandPalette or DocsSearch that is playing its exit animation no longer takes Escape or presses: they reach the layer below, so closing a confirm dialog and pressing Escape right after closes the Drawer under it (#288). A menu or popover that was open when a modal opened now ranks above that modal, so Escape closes the menu first and the dialog second instead of closing the dialog and leaving the menu open (#299). Exit animations of components that use <code>createPresence</code> (Dialog, Drawer, AlertDialog, menus, popovers) now keep the node mounted through the exit instead of removing it in the flush that closes it, and a menu hosted in a dialog is no longer left stranded after its exit.</li>\n<li>05cfbfc: <code>Rail</code>: the dock magnification scales the module icon (the glyph), not the whole button, so the hovered background no longer covers the active neighbour&#39;s background and pip. The effect follows <code>--ca-motion-scale</code>: it is off with <code>motion: { scale: 0 }</code> and under <code>prefers-reduced-motion</code>.</li>\n<li>05cfbfc: Native <code>button</code>, <code>input</code>, <code>select</code> and <code>textarea</code> inherit the page font (<code>font: inherit</code> in the new <code>ca.reset</code> layer, <code>styles/reset.css</code>) instead of the browser&#39;s UA font. The AppShell section header (<code>.ca-section__trigger</code> / <code>.ca-section__label</code>) and any control without its own font rule now render in <code>--ca-font-sans</code>. Component and app rules still win: the layer is the first one.</li>\n<li>8c24c31: <code>Skeleton</code> and the <code>skeleton</code> prop follow the theme surface style: under <code>flat</code>, <code>solid</code> and <code>outlined</code> no gradient band is drawn, the placeholder is a flat block that pulses its opacity (<code>ca-skeleton-pulse</code>, stopped under reduced motion). Glass, frosted, neumorphic, clay, chrome and tonal keep the shimmer sweep (#280).</li>\n</ul>\n","thanks":"To our main supporters and special contributors, Fábio Henrique Araújo (@FabioHAraujo) and Richardson Douglas (@Richards0nd). 💛"},{"version":"4.3.0","id":"v4-3-0","date":"2026-10-16","published":"2026-10-16T23:40:47Z","channel":"latest","url":"https://github.com/codeatlasdev/ui/releases/tag/%40codeatlasdev%2Fui%404.3.0","npm":"https://www.npmjs.com/package/@codeatlasdev/ui/v/4.3.0","html":"<h3 id=\"v4-3-0-minor-changes\">Minor Changes</h3>\n<ul>\n<li>a1c0d04: Demo sidecars accept <code>names</code> (motion vocabulary) and <code>motion</code> (exact property, duration, easing, delay, stagger, spring). They are printed in <code>e/NNN.txt</code> and <code>p/NNN.txt</code>, carried in <code>catalog.json</code> and <code>catalog.tsv</code>, and <code>ca-ui-mcp</code> <code>search_demos</code> matches names and aliases before summary and tags.</li>\n<li>a1c0d04: <code>MotionStage</code> and the <code>MotionCtx</code> type: the frame of a motion demo. It autoplays only while at least half of it is in view and the tab is visible, hands control to the user on pointer, key or focus (autoplay stops, Replay appears), always shows a Pause button with <code>aria-pressed</code>, and under <code>prefers-reduced-motion</code> never autoplays and tells the demo to render its final state (<code>ctx.reduced</code>). <code>ctx.cycle</code> increments each loop so a demo can re-run its animation. Labels come from the i18n catalog (<code>motionStage.pause</code>, <code>motionStage.replay</code>) and can be overridden with <code>pauseLabel</code> / <code>replayLabel</code>.</li>\n</ul>\n","thanks":"To our main supporters and special contributors, Fábio Henrique Araújo (@FabioHAraujo) and Richardson Douglas (@Richards0nd). 💛"},{"version":"4.2.0","id":"v4-2-0","date":"2026-10-16","published":"2026-10-16T21:18:00Z","channel":"latest","url":"https://github.com/codeatlasdev/ui/releases/tag/%40codeatlasdev%2Fui%404.2.0","npm":"https://www.npmjs.com/package/@codeatlasdev/ui/v/4.2.0","html":"<h3 id=\"v4-2-0-minor-changes\">Minor Changes</h3>\n<ul>\n<li>b65d56d: AI feedback loop. The <code>ca-ui-mcp</code> server has a new <code>report_issue</code> tool: an agent that hits a library problem (bug, missing prop or component, migration gap, wrong docs) gets a sanitized issue draft (paths, foreign URLs, emails, IPs, tokens, env assignments and foreign package scopes are redacted, and the redactions are listed) with a prefilled link to the new &quot;AI report&quot; issue form and a <code>gh issue create</code> command. It never posts. <code>bunx @codeatlasdev/ui workarounds</code> lists the <code>// ca-ui-workaround(#&lt;n&gt;): &lt;why&gt;</code> markers an agent left in your code and which issues are already closed (read-only, offline-safe, exit 0).</li>\n<li>81910d1: Avatar renders the photo as a real <code>&lt;img&gt;</code> (no detached <code>Image()</code> probe, so no double download) and passes <code>loading</code>, <code>referrerpolicy</code> and <code>crossorigin</code> through, so <code>loading=&quot;lazy&quot;</code> keeps a 200-row list from firing 200 requests and <code>referrerpolicy=&quot;no-referrer&quot;</code> works with CDNs that reject a cross-origin <code>Referer</code>. <code>fallback</code> now also takes a snippet (an icon for groups, channels and bots), and <code>onerror</code> fires when the image fails so you can refetch a signed URL. The fallback is always rendered beneath the image, so the box never changes size. The <code>data-status</code> attribute on <code>.ca-avatar</code> is gone.</li>\n<li>80c0336: <code>Card</code> has a <code>padding</code> prop (<code>&#39;none&#39; | &#39;sm&#39; | &#39;md&#39; | &#39;lg&#39;</code>, any CSS length, or a number of pixels) so a one-off density no longer needs an inner wrapper; <code>flush</code> stays as the alias of <code>&#39;none&#39;</code>. The docs now say that Card&#39;s content is already padded, and in development a <code>[ca-ui]</code> warning (<code>card-double-padding</code>) fires once when the first child adds its own padding on top of the card&#39;s (the 20px + 20px = 40px &quot;oversized card&quot;).</li>\n<li>cd655bb: Chat primitives: <code>MessageBubble</code> (direction, grouped tails, sender and avatar slot, quote, reactions, delivery ticks), <code>ConversationItem</code> (inbox row with unread, pinned, muted, typing, active), <code>AudioPlayer</code> (lazy src, waveform from <code>peaks</code>, slider semantics, playback rate), <code>MediaGrid</code> (image, video and document tiles opening a <code>Lightbox</code> gallery) and <code>VirtualList</code> (windowing plus load-older at the top that keeps the scroll position). New <code>chat</code>, <code>audioPlayer</code>, <code>mediaGrid</code> and <code>virtualList</code> messages in en and pt-BR.</li>\n<li>80c0336: <code>CopyButton</code> takes <code>aria-label</code> for the icon-only form (<code>label=&quot;&quot;</code>), so each row of a list or table can announce its own name (&quot;Copy client_id of Acme&quot;) instead of the same localized &quot;Copy&quot;. The default stays the i18n <code>copyButton.label</code>. The label is ignored when <code>label</code> is visible, so the accessible name keeps containing the visible text. Remaining attributes (<code>data-*</code>, <code>id</code>, <code>aria-describedby</code>, …) are now forwarded to the underlying button.</li>\n<li>6e08799: Every numbered demo now has a reviewed <code>&lt;Variant&gt;.meta.json</code> sidecar (<code>summary</code>, <code>use</code>, <code>spec</code>, <code>tags</code>, <code>shape</code>), so <code>get_demo</code>, <code>search_demos</code> and <code>/api/v1/en/{e,p}/NNN.txt</code> return what the demo shows, when to use it and a Spec list of its literal values (props, snippets, state, counts). The catalog check (<code>manifest --check</code>, run by every build) now fails on a demo without a sidecar, a sidecar without a demo, an unknown key, a bad <code>shape</code>, or a spec line that quotes a token that is not in the demo source. New <code>codeatlas-ui pick [ids...]</code> opens the gallery, listens on <code>127.0.0.1</code> with a random per-run token and prints the ids you send with &quot;Send to AI&quot; (<code>#005 #012</code>).</li>\n<li>d99cead: <code>codeatlas-ui init</code> scaffolds SEO and PWA on SvelteKit (#205): prerendered <code>manifest.webmanifest</code>, <code>robots.txt</code> and <code>sitemap.xml</code> routes built with the <code>seo</code> helpers, a service worker (<code>src/service-worker/index.ts</code> on <code>$app/service-worker</code>, <code>$app/manifest</code> and <code>$app/env</code> for Kit 3; <code>src/service-worker.ts</code> on <code>$service-worker</code> for Kit 2) with a prerendered <code>/offline</code> page, and a default <code>&lt;Seo&gt;</code> with canonical and manifest link in the root layout. Existing files are never overwritten; after creating them <code>init</code> reminds the app to set <code>paths.origin</code> (otherwise Kit&#39;s prerender placeholder origin reaches the sitemap and canonical URLs), add the icons and exclude <code>src/service-worker</code> from <code>tsconfig.json</code> on Kit 3.</li>\n<li>d99cead: New <code>InstallPrompt</code> (#208): a dismissible banner that offers to install the app. On Chromium it keeps <code>beforeinstallprompt</code> (no browser mini-infobar) and shows an Install button that opens the native dialog and reports the choice with <code>onchoice</code>; on iOS Safari and iPadOS, which have no install API, it shows the Share, Add to Home Screen steps. It renders nothing when the app already runs standalone, and stays hidden for <code>dismissDays</code> (default 14) after the user closes it. Strings are in the <code>installPrompt</code> i18n group (<code>en</code>, <code>pt-BR</code>); the <code>root: Button only</code> size budget rises by 240 B gzip because both catalogs ship with every root import.</li>\n<li>81910d1: Lightbox opens videos and galleries. <code>items</code> (<code>{ type?: &#39;image&#39; | &#39;video&#39;, src, alt?, caption?, meta?, poster?, download? }</code>, exported as <code>LightboxItem</code>) plus a bindable <code>index</code> add previous/next buttons, ArrowLeft/ArrowRight and swipe (no wrap; the end buttons are <code>aria-disabled</code> so focus stays put), a counter, caption and sender/time line, video with controls (paused when you move on), and a Download button (<code>ondownload</code> or a plain <code>&lt;a download&gt;</code>). <code>src</code>/<code>alt</code> still work as a single-item shortcut. New <code>lightbox.previous</code>, <code>next</code>, <code>download</code> and <code>counter</code> labels in en and pt-BR; the close button moved into a <code>.ca-lightbox__tools</code> bar.</li>\n<li>3180023: Leading icon slot for <code>DropdownMenuItem</code> and <code>TabsTrigger</code>, and a documented pattern for brand logos (#194). <code>icon</code> is a snippet rendered in a fixed 14px box with the standard gap and <code>aria-hidden</code> (the label stays the accessible name), so icon size and alignment no longer drift between menus. <code>brandIcon</code> marks the icon as a fixed-color brand mark: no highlighted, disabled, active or destructive color reaches it (trademark guidelines forbid recoloring). New <code>SchemeAsset</code> takes <code>light</code> and <code>dark</code> snippets and shows the one for the kit&#39;s color scheme (<code>.dark</code> on <code>&lt;html&gt;</code> or a <code>data-theme-scope</code> island) with CSS only, so it is right in SSR and on first paint and apps no longer read the theme store for logo variants. New classes: <code>.ca-dropdown-item__icon</code>, <code>.ca-tabs-trigger__icon</code>, <code>.ca-scheme-asset</code>, <code>.ca-scheme-asset__light</code>, <code>.ca-scheme-asset__dark</code>.</li>\n<li>7a112eb: Navigation: <code>LoadingBoundary</code> wraps <code>&lt;svelte:boundary pending&gt;</code> with a 200 ms delay and 500 ms minimum so a loading UI never flashes (default placeholder is <code>Skeleton</code> rows, <code>aria-busy</code>, named region). <code>@codeatlasdev/ui/kit</code> gains <code>NavigationProgress</code> (top bar driven by <code>navigating</code> from <code>$app/state</code>) and the <code>pendingLink()</code> attachment that sets <code>data-pending</code> on the link being navigated to. A test now proves AppShell stays mounted across 10 client-side navigations.</li>\n<li>df50d86: New <code>RecordList</code>, <code>RecordRow</code> and <code>RecordCell</code>: records as dense rows (identity, a few columns, a row menu) that re-flow to a compact two-line row on a phone. The list owns the flush container, the separators, one <code>columns</code> template shared by the header and every row, the re-flow (a container query on the list&#39;s own width, 36rem), the 44px actions cell and an <code>aria-hidden</code> header whose labels become per-cell <code>dt</code> when stacked. It replaces the hand-rolled flush <code>Card</code> + grid + <code>:global</code> row CSS, and is much shorter on a phone than <code>Table responsive=&quot;cards&quot;</code>.</li>\n<li>d99cead: <code>&lt;Seo&gt;</code> completes the social and language tags (#207): <code>og.defaultImage</code> is the site-wide card used when a page has no <code>og.image</code> (also feeds <code>twitter:image</code> and the <code>summary_large_image</code> card), <code>twitter:title</code> and <code>twitter:description</code> mirror the page title and description, and the new <code>lang</code> prop sets <code>og:locale</code> and keeps <code>&lt;html lang&gt;</code> in sync on the client.</li>\n<li>81910d1: <code>skeleton</code> prop on <code>Avatar</code>, <code>KpiCard</code>, <code>TextField</code>, <code>Table</code>, <code>Card</code> and <code>GlassCard</code>: the component keeps its real DOM, so the placeholder has exactly the loaded size (no layout shift). Content is laid out but hidden, the host paints the same shimmer as <code>&lt;Skeleton&gt;</code>, and it is announced with <code>aria-busy=&quot;true&quot;</code>; a skeleton <code>TextField</code> is disabled and an <code>Avatar</code> makes no image request. Reduced motion stops the sweep and forced colors draw an outline. New global <code>[data-skeleton]</code> rule and <code>ca-shimmer</code> keyframes in <code>utilities.css</code>.</li>\n<li>f889334: Every backdrop blur now comes from one place. Components no longer declare <code>backdrop-filter</code>: they set <code>data-ca-blur=&quot;&lt;role&gt;&quot;</code> (or are a <code>.ca-surface</code>) and <code>styles/surface.css</code> paints it from the tokens <code>&lt;html data-style&gt;</code> rewrites, so one attribute turns every blur off or down together. The look is unchanged (guarded by a computed-style test over 9 styles, light and dark). <code>FloatingLayer</code> gains a <code>blur</code> prop (<code>overlay</code> or <code>tooltip</code>). <code>lint:tokens</code> rejects <code>backdrop-filter</code> outside <code>surface.css</code> and <code>materials.css</code>. The MCP server gains <code>list_surfaces</code>, generated from the <code>[data-style]</code> selectors in <code>materials.css</code>, and <code>dist/surfaces.json</code> ships with the package.</li>\n<li>3180023: <code>TabsList</code> handles its own overflow (#190). It keeps the width of its tabs inside <code>Tabs</code> (no more full-column pill on desktop), and when the tabs do not fit it scrolls sideways on its own with the scrollbar hidden and scroll snapping, instead of wrapping or widening the page on a phone. The selected tab is scrolled into view on mount and when it changes (only the strip scrolls, never the page). New optional <code>fade</code> prop masks the edge that still hides tabs, like the <code>AppSettings</code> nav. The wrapper <code>div</code> with <code>overflow-x: auto</code> and the <code>:global(.ca-tabs-list) { width: max-content }</code> workaround can be deleted.</li>\n</ul>\n<h3 id=\"v4-2-0-patch-changes\">Patch Changes</h3>\n<ul>\n<li>526fee2: A menu, Select list, popover or tooltip that is already open when a Dialog, Sheet or Drawer opens (a shortcut or an async action opening the modal over it) now moves into that modal and stays clickable and above it. Before, it stayed outside the modal, so it went inert and could not be used. When a modal closes before the popovers hosted in it, they are handed to the next modal or put back where they were declared instead of being removed with it. The layer stack and the stacking contract are covered by new tests and documented on the Dialog page (#225).</li>\n<li>3afd417: Overlays return focus to the control that opened them on iPhone/iPad and Safari too. WebKit does not focus a tapped button (it focuses a focusable ancestor such as <code>&lt;main tabindex=&quot;-1&quot;&gt;</code>, or nothing), so focus used to land there on close and VoiceOver users lost their place. CommandPalette: Mod+K focuses the search field on touch devices with a keyboard (covered by a test now).</li>\n<li>c76c837: New guide <code>getting-started/migrate-react</code>, readable offline through the MCP <code>get_guide(&quot;migrate-react&quot;)</code> and on the docs site: decision flow (incremental or rewrite), React hook to Svelte 5 rune map with the effect pitfalls, MUI / shadcn-Radix / Chakra to <code>@codeatlasdev/ui</code> component maps, layout templates, theme mapping, TanStack Table to <code>@codeatlasdev/ui/table</code>, and how to report a gap (<code>report_issue</code> kind <code>migration-gap</code> plus a <code>ca-ui-workaround</code> marker). A unit test fails when a component named in the maps stops being an export.</li>\n<li>d99cead: The manifest <code>generateBrand</code> / <code>caBrandPlugin</code> write is installable: <code>display: &quot;standalone&quot;</code> (was <code>&quot;browser&quot;</code>), <code>id</code> and <code>scope</code> set to <code>&quot;/&quot;</code>, and <code>theme_color</code> / <code>background_color</code> taken from the theme&#39;s default scheme instead of always the light one (#206). The icon hash now includes the default scheme, so switching it renames the files.</li>\n<li>81910d1: Skeleton&#39;s shimmer is a translated <code>::after</code> band instead of an animated <code>background-position</code>, so it no longer repaints on every frame. Reduced motion still gets a static, dimmed placeholder.</li>\n<li>9c9df3b: <code>@codeatlasdev/ui/vite</code> is safe in the SSR graph: importing <code>designStyle</code>, <code>mergeTheme</code> or <code>ThemeOptions</code> from a <code>theme.ts</code> that <code>hooks.server.ts</code> shares no longer fails the SvelteKit server build with <code>UNLOADABLE_DEPENDENCY</code> on <code>@resvg/resvg-js</code> (#184). The native peers are loaded by name at run time and <code>caBrandPlugin</code> marks them <code>ssr.external</code>, so the <code>ssr.external</code> workaround is no longer needed. <code>runBrand</code> and <code>brandUrls</code> are now exported from <code>@codeatlasdev/ui/vite</code>, as the 4.0.0 changelog announced (#185).</li>\n</ul>\n","thanks":"To our main supporters and special contributors, Fábio Henrique Araújo (@FabioHAraujo) and Richardson Douglas (@Richards0nd). 💛"},{"version":"4.1.0","id":"v4-1-0","date":"2026-10-16","published":"2026-10-16T14:28:58Z","channel":"latest","url":"https://github.com/codeatlasdev/ui/releases/tag/%40codeatlasdev%2Fui%404.1.0","npm":"https://www.npmjs.com/package/@codeatlasdev/ui/v/4.1.0","html":"<h3 id=\"v4-1-0-minor-changes\">Minor Changes</h3>\n<ul>\n<li>ccae97c: Numbered demo catalog (<code>ca-prompt</code>): every docs demo has a stable <code>#NNN</code> id. The package ships <code>demos.json</code> (export <code>@codeatlasdev/ui/demos.json</code>) and <code>dist/api/v1</code> reference texts, and <code>ca-ui-mcp</code> gains <code>search_demos</code> and <code>get_demo</code>. The agent skill documents picking demos by number.</li>\n</ul>\n<h3 id=\"v4-1-0-patch-changes\">Patch Changes</h3>\n<ul>\n<li>6ab52ee: Checkbox, Switch and RadioGroup: on touch screens (<code>pointer: coarse</code>) a tap on the box toggles the control again. The 44px hit-area overlay painted over the native input and swallowed the tap; the native input now is the 44px hit area.</li>\n</ul>\n","thanks":"To our main supporters and special contributors, Fábio Henrique Araújo (@FabioHAraujo) and Richardson Douglas (@Richards0nd). 💛"},{"version":"4.0.0","id":"v4-0-0","date":"2026-10-15","published":"2026-10-15T21:07:59Z","channel":"latest","url":"https://github.com/codeatlasdev/ui/releases/tag/%40codeatlasdev%2Fui%404.0.0","npm":"https://www.npmjs.com/package/@codeatlasdev/ui/v/4.0.0","html":"<h3 id=\"v4-0-0-major-changes\">Major Changes</h3>\n<ul>\n<li>b99075a: <code>AppShellLayout</code> renders its content region as <code>&lt;main id={mainId} tabindex=&quot;-1&quot;&gt;</code> by default, so a page built on it has a <code>&lt;main&gt;</code> landmark (axe <code>landmark-one-main</code>, <code>region</code>) and the skip link lands on it. It was a plain <code>div</code>. Pass <code>mainId={null}</code> when your page renders its own <code>&lt;main id=&quot;main&quot; tabindex=&quot;-1&quot;&gt;</code>: the region is then a plain <code>div</code>, never a second landmark. If you wrapped your content in <code>&lt;main&gt;</code> inside the shell, remove it or pass <code>mainId={null}</code>.</li>\n<li>fdb6798: <code>nativeFeel()</code> now has one opt-out class, <code>.ca-selectable</code>, for both the context menu and text selection (#136). The context-menu filter used <code>.selectable</code> while the selection CSS used <code>.ca-selectable</code>, so neither class restored both. The filter and its docs now say <code>.ca-selectable</code>, matching the <code>ca-*</code> class contract. Clean cutover: <code>.selectable</code> was never documented outside a JSDoc line, and no stylesheet ever targeted it, so apps using it must rename it to <code>ca-selectable</code> (right-click on a <code>.selectable</code> region is now blocked again until they do).</li>\n</ul>\n<h3 id=\"v4-0-0-minor-changes\">Minor Changes</h3>\n<ul>\n<li>6e39edc: <code>caBrandPlugin</code> writes <code>src/generated/brand.ts</code> as soon as the Vite config resolves (the URLs depend only on a hash of the inputs, so no rendering is needed), not only at build start. New <code>codeatlas-ui brand</code> command and <code>brandUrls()</code> / <code>runBrand()</code>: render the icons and the module without a Vite run, so a job that only runs <code>svelte-check</code> on a fresh clone type-checks (<code>svelte-kit sync</code> does not load Vite plugins).</li>\n<li>c2d255d: <code>FormSection</code> accepts <code>headingLevel</code> (<code>2 | 3 | 4 | 5 | 6</code>) and <code>AppSettings</code>/<code>AppForm</code> provide the section heading level like <code>PageShell</code>, so <code>FormSection</code> and <code>EmptyState</code> titles inside them render as <code>&lt;h2&gt;</code> under the page <code>&lt;h1&gt;</code> (axe <code>heading-order</code>) instead of <code>&lt;h5&gt;</code>. Outside a page layout the default stays <code>&lt;h5&gt;</code>. Only the tag changes, never the look.</li>\n<li>af02ce2: <code>RadioGroup</code>, <code>Checkbox</code>, <code>Switch</code> and <code>Select</code> accept <code>name</code>, so their value is part of <code>FormData</code> on a native form submit (progressive enhancement, SvelteKit form actions without a hidden-input workaround). <code>Select</code> renders a hidden input carrying the selected value when <code>name</code> is set.</li>\n<li>e7b85e7: <code>ogCard</code> accepts <code>lang</code> (BCP 47, default <code>&quot;en&quot;</code>, escaped) for the card&#39;s <code>&lt;html lang&gt;</code>, so non-English cards get the right hyphenation and locale font selection.</li>\n<li>55ecc1a: <code>Box</code>, <code>Flex</code>, <code>Stack</code> and <code>Grid</code> are generic on <code>as</code>: <code>&lt;Stack as=&quot;form&quot; novalidate onsubmit={...}&gt;</code> accepts the form&#39;s attributes, and the handler&#39;s <code>currentTarget</code> is an <code>HTMLFormElement</code> (same for <code>as=&quot;a&quot;</code>, <code>as=&quot;label&quot;</code>, <code>as=&quot;button&quot;</code>...). An attribute that does not belong to the tag is a type error. Exports <code>PolymorphicProps</code> and <code>ElementTag</code>.</li>\n</ul>\n<h3 id=\"v4-0-0-patch-changes\">Patch Changes</h3>\n<ul>\n<li>153be87: <code>centsToDisplay</code> keeps the no-break space <code>Intl</code> puts after the currency symbol (<code>&quot;R$\\u00a0123,45&quot;</code>), so a price no longer wraps between the symbol and the number. <code>MoneyInput</code> still shows and parses a plain space in its editable text.</li>\n<li>a517102: <code>toCsv</code>, <code>toJson</code>, <code>ExportColumn</code> and <code>ExportMenu</code> accept any object row type (<code>T extends object</code>), so rows typed with an <code>interface</code> no longer need a cast. <code>key</code> is still checked against <code>keyof T</code>.</li>\n<li>8d25c2f: <code>caErrorPlugin({ scaffold: true })</code> writes a valid <code>src/error.html</code>: the theme init script now sits in <code>&lt;head&gt;</code> instead of inside <code>&lt;style&gt;</code>, the page uses the app theme (<code>theme</code> option, same object as <code>caThemePlugin</code>) instead of hard-coded amber, its copy and <code>lang</code> follow the new <code>locale</code> option (<code>en</code> default, warns when unset), and the <code>javascript:history.back()</code> link is gone (CSP-safe). The scaffolded <code>+error.svelte</code> uses tabs and double quotes like the other generated files. <code>init</code> now writes <code>caErrorPlugin({ scaffold: true, theme })</code>.</li>\n<li>9e5655f: <code>init</code> without <code>--write</code> is now a true dry run: it prints <code>would create &lt;file&gt;</code> instead of writing new files. It also stops creating <code>src/lib/theme.ts</code> (<code>src/theme.ts</code> on Astro) when <code>vite.config.ts</code> / <code>hooks.server.ts</code> already import <code>theme</code> from elsewhere, and wires the files it adds to that import.</li>\n<li>a7f7e96: Docs and agent skill: apps must install <code>@lucide/svelte</code> themselves. Under Bun&#39;s isolated linker the package&#39;s own dependency is not importable from the app.</li>\n<li>3ef5b11: <code>migrate</code> keeps the default thickness of <code>GlassCard</code> and <code>GlassSurface</code>: a usage without <code>thickness</code> (default <code>regular</code>) becomes <code>elevation={3}</code> instead of dropping it, so the migrated card keeps its fill, blur, shadow and rim (<code>Card</code> and <code>Surface</code> default to <code>elevation={1}</code>).</li>\n<li>33ad505: <code>migrate</code> looks up <code>typescript</code> in the project directory first (then next to the CLI) and prints <code>migrate needs typescript (bun add -d typescript)</code> with exit code 1 when it is missing, instead of crashing with <code>Cannot find package &#39;typescript&#39;</code>. This also fixes <code>bunx @codeatlasdev/ui migrate</code> in layouts where the CLI cache directory cannot see the project&#39;s <code>typescript</code>.</li>\n<li>b7cdd79: <code>migrate</code> now ends with a list of the usages it left for you (<code>file:line Component: reason (recipe)</code>), and <code>--strict</code> exits with code 1 when any remain. A <code>Grid</code> or <code>Stack</code> whose <code>cols</code>/<code>align</code> could not be mapped still gets its <code>gap</code> converted: <code>gap={20}</code> next to <code>cols=&quot;2fr 1fr&quot;</code> becomes <code>gap=&quot;5&quot;</code> instead of silently turning into step 20 (80px). Dynamic or off-scale gaps are reported, since their meaning changed from pixels to a step.</li>\n<li>98ea2ed: Nested modals: only the newest focus trap owns focus, so opening a <code>Dialog</code>/<code>AlertDialog</code>/<code>Sheet</code>/<code>Drawer</code> over another no longer loops on <code>focusin</code> (<code>Maximum call stack size exceeded</code>); closing it hands focus back to the one below.</li>\n<li>daccdc8: <code>.ca-prose</code> heading anchors stay visible on touch screens: a stray second <code>@media (hover: none)</code> block hid them with <code>display: none</code>, contradicting the rule above it. New &quot;Release lists&quot; docs page documents the <code>ca-release*</code> markup and the version-id anchor convention.</li>\n<li>c0bc624: <code>--ca-scrim-blur</code> now drives the modal scrim (#135). It was declared as <code>none</code> but never read: the <code>::backdrop</code> of Dialog, AlertDialog, Sheet, Drawer, CommandPalette and DocsSearch hardcoded <code>blur(4px) saturate(180%)</code>. The scrim reads the token and the token defaults to that same value, so nothing changes visually; <code>:root { --ca-scrim-blur: none }</code> now removes the blur from all of them. The CommandPalette scrim shares it (it used a plain <code>blur(4px)</code>, now with the shared <code>saturate(180%)</code>).</li>\n<li>5a3ede3: Agent skill and AGENTS.md import <code>ThemeOptions</code> from <code>@codeatlasdev/ui/vite</code> (<code>/astro</code> in Astro), like the docs and <code>init</code>. The package root pulls <code>.svelte</code> files that Node cannot load from <code>vite.config</code>.</li>\n<li>8185835: New <code>--ca-tooltip-blur</code> token for the Tooltip surface blur (#137). It defaults to <code>var(--ca-blur-ultra-thin)</code>, so the look is unchanged; <code>--ca-tooltip-blur: none</code> gives an opaque tooltip without re-tiering the shared ultra-thin blur (<code>.glass-ultra-thin</code>, <code>.glass-btn-clear</code>, <code>--ca-upload-blur</code>).</li>\n<li>3dda885: <code>@codeatlasdev/ui/vite</code> exports <code>designStyle</code>, <code>DESIGN_STYLES</code>, <code>mergeTheme</code> and <code>DesignStyleId</code>, so a <code>theme.ts</code> shared with <code>vite.config</code> can use a design recipe (including the 3.0 migration <code>designStyle(&quot;liquid&quot;, { ... })</code>) in plain Node. The theming guide and the <code>init</code> theme comment now point to this entry instead of <code>/astro</code>.</li>\n</ul>\n","thanks":"To our main supporters and special contributors, Fábio Henrique Araújo (@FabioHAraujo) and Richardson Douglas (@Richards0nd). 💛"},{"version":"3.1.0","id":"v3-1-0","date":"2026-10-15","published":"2026-10-15T04:44:10Z","channel":"latest","url":"https://github.com/codeatlasdev/ui/releases/tag/%40codeatlasdev%2Fui%403.1.0","npm":"https://www.npmjs.com/package/@codeatlasdev/ui/v/3.1.0","html":"<h3 id=\"v3-1-0-minor-changes\">Minor Changes</h3>\n<ul>\n<li>0579bb3: <code>.ca-prose</code> styles release lists, so a changelog page needs no custom CSS (#126). Wrap one <code>&lt;section class=&quot;ca-release&quot;&gt;</code> per version (heading with its channel <code>.ca-chip</code>, then a <code>.ca-release__meta</code> row of date and links, then the rendered notes), add a <code>.ca-release-jump</code> pill index and close with <code>.ca-release-thanks</code>. Anchored releases clear a sticky <code>DocsShell</code> header (<code>scroll-margin-top</code>), and the meta links and pills keep 44px touch targets. Classes only, no JS or component: the markup can come from Markdown rendered at build time or at the edge.</li>\n<li>973fe00: Add <code>SwatchGroup</code>: a single-choice color swatch picker (<code>role=&quot;radiogroup&quot;</code>, roving focus, preset or custom <code>color</code>). Its coarse-pointer hit area is the swatch itself, so neighbouring swatches never overlap and every click lands on the swatch the user aimed at.</li>\n</ul>\n<h3 id=\"v3-1-0-patch-changes\">Patch Changes</h3>\n<ul>\n<li>b27c73a: <code>generateBrand</code> and the brand Vite plugin no longer load every system font for each PNG they render. resvg did that by default on every construction (~450 ms per render on Windows, ~2.7 s per icon set, at every build and dev start); fonts now load only when the mark has <code>&lt;text&gt;</code> (#120).</li>\n<li>5c79b24: <code>FilterGroup</code> draws its label with <code>--ca-text-secondary</code> instead of <code>--ca-text-hint</code>. The hint role can be set low by a custom preset, and the label failed the 4.5:1 axe check on a raised surface in dark mode.</li>\n<li>5c79b24: <code>KpiCard</code> is built on <code>Card</code> instead of the deprecated <code>GlassCard</code>, so rendering it no longer logs the <code>[ca-ui]</code> deprecation warning in dev. The look is unchanged (elevation 3).</li>\n</ul>\n","thanks":"To our main supporters and special contributors, Fábio Henrique Araújo (@FabioHAraujo) and Richardson Douglas (@Richards0nd). 💛"},{"version":"3.0.0","id":"v3-0-0","date":"2026-10-15","published":"2026-10-15T03:07:23Z","channel":"latest","url":"https://github.com/codeatlasdev/ui/releases/tag/%40codeatlasdev%2Fui%403.0.0","npm":"https://www.npmjs.com/package/@codeatlasdev/ui/v/3.0.0","html":"<h3 id=\"v3-0-0-major-changes\">Major Changes</h3>\n<ul>\n<li><p>a1d14f5: Sober default theme, Liquid Glass is now opt-in (#111). The package used to look like a UI-kit demo: a glass material over a three-hue mesh, glow on selected states, a white rim on every surface, hue-tinted 24-40px shadows, 10px controls, 2px icons and 700 headings. The default is now solid and neutral:</p>\n<ul>\n<li><code>defaultStyle: &quot;solid&quot;</code>, <code>paper: &quot;plain&quot;</code>, <code>radius: 0.8</code> (<code>--ca-radius-md</code> is 8px), <code>iconStroke: 1.75</code>, heading weight 600 (and no extra h1 tracking).</li>\n<li>Surfaces, text and outlines carry almost no chroma (the brand hue stays on fills, focus, links and charts); shadows are neutral black (<code>--ca-shadow-*</code>, <code>--ca-glass-shadow</code>, <code>--ca-mat-*-shadow</code>).</li>\n<li><code>[data-style=&quot;solid&quot;]</code> and <code>[data-style=&quot;flat&quot;]</code> set <code>--ca-glow-primary(-strong)</code> and <code>--ca-glass-rim</code> to transparent and draw <code>--ca-mat-edge</code> as a hairline, so <code>.glass-tinted</code>, <code>.glass-prominent</code>, <code>.glass-glow</code>, <code>.glass-active</code>, <code>.glass-btn-prominent</code> and the AppShell nav pills lose their halo and rim without a component change. The page mesh (<code>--ca-page-mesh</code>) is drawn only by <code>glass</code> and <code>frosted</code>.</li>\n<li>New recipe <code>designStyle(&quot;liquid&quot;)</code> reproduces the former look exactly (glass, mesh, glow, round corners, bold headings, tinted shadows). <code>designStyle(&quot;default&quot;)</code> now means the sober default.</li>\n</ul>\n<p>Migration: to keep the old look, use <code>designStyle(&quot;liquid&quot;, { ...yourTheme })</code>, or set <code>defaultStyle: &quot;glass&quot;</code>, <code>paper: &quot;mesh&quot;</code>, <code>radius: 1</code>, <code>iconStroke: 2</code> and <code>typography: { weights: { heading: 700 } }</code>. <code>colorScheme.setStyle(&quot;glass&quot;)</code> still switches at runtime. Apps that override <code>--ca-*</code> tokens are unaffected; apps that read <code>--ca-radius-*</code> or the shadow tokens get the new values. <code>defineTheme()</code> with no options now resolves the new defaults (<code>designTokens</code> emits nothing for them).</p>\n</li>\n</ul>\n<h3 id=\"v3-0-0-minor-changes\">Minor Changes</h3>\n<ul>\n<li><p>8b36a1e: AppShellLayout: the shell keeps its state across page loads (#109). On an MPA (Astro, a full reload) every navigation is a new document, and the panel, the selected module, the expanded sections and the panel scroll all reset. <code>persistPanel</code> (default <code>true</code>) now keeps the panel open/closed, the selected module and the section open map in <code>localStorage[&quot;ca-shell&quot;]</code> (the old <code>ca-panel-open</code> key is migrated), and the panel scroll per module in <code>sessionStorage</code>. An inline script emitted right after the sidebar restores them before the first paint and sets <code>data-ca-panel</code> / <code>data-ca-module</code> on <code>&lt;html&gt;</code>, so a stored-closed panel is laid out closed with no flash, on the same model as the theme script. Stored values are validated and discarded when malformed. The user&#39;s last choice for a section wins over <code>defaultOpen</code>; the section holding the current page always opens. The shell now starts at its target width instead of animating open on load. <code>persistPanel={false}</code> keeps the old always-default behavior for demos and embeds.</p>\n</li>\n<li><p>3476d9a: Brand assets from one mark. <code>caBrandPlugin</code> and <code>generateBrand</code> (<code>@codeatlasdev/ui/vite</code>) render a content-hashed favicon (SVG that follows <code>prefers-color-scheme</code>, ICO, PNG), <code>apple-touch-icon</code>, 192/512/maskable manifest icons and a web app manifest from one SVG with <code>{{ink}}</code>/<code>{{spark}}</code> placeholders and your theme; <code>resolveBrand</code> and <code>renderMark</code> expose the resolved sRGB colors. <code>iconLinks</code> and <code>ogCard</code> (<code>@codeatlasdev/ui/seo</code>) emit the head links and the 1200x630 social-card HTML for <code>renderOgImage</code>. <code>&lt;Seo&gt;</code> now also writes <code>og:image:type</code> and <code>twitter:image</code>. <code>@resvg/resvg-js</code> and <code>png-to-ico</code> are optional peer dependencies.</p>\n</li>\n<li><p>1eeca1c: Surface engine and layout primitives (#112).</p>\n<p>New components: <code>Box</code>, <code>Flex</code>, <code>Stack</code>, <code>Grid</code>, <code>Container</code>, <code>Section</code> (layout), <code>Surface</code>, <code>Card</code> and <code>SurfacePanel</code>.</p>\n<ul>\n<li><strong>Layout primitives</strong> take token props (<code>gap</code>, <code>p</code>/<code>px</code>/<code>py</code>, <code>align</code>, <code>justify</code>, <code>direction</code>, <code>columns</code>, <code>wrap</code>) and a polymorphic <code>as</code>. A prop renders as a <code>data-*</code> attribute (<code>data-gap=&quot;4&quot;</code>), a responsive object (<code>{ base: &quot;2&quot;, md: &quot;4&quot; }</code>) as <code>data-md-gap=&quot;4&quot;</code>, styled by <code>styles/layout.css</code> in <code>@layer ca.utilities</code>. No inline <code>style</code>.</li>\n<li><strong><code>Surface</code></strong> has orthogonal axes: <code>elevation</code> 0-5, <code>material</code> <code>auto | solid | frosted | glass | liquid</code>, <code>tone</code>, <code>variant</code> <code>filled | outlined | ghost</code>, <code>radius</code>, <code>interactive</code>, <code>glow</code>, <code>as</code>. One <code>.ca-surface</code> rule reads <code>--ca-surface-*</code> variables. <code>material=&quot;auto&quot;</code> follows <code>&lt;html data-style&gt;</code>; the explicit materials look the same under every style. It never sets <code>will-change</code> or <code>isolation</code>, and it has <code>forced-colors</code> and reduced-transparency entries.</li>\n<li><strong><code>Card</code></strong> (<code>title</code>, <code>headingLevel</code>, <code>href</code>, <code>flush</code>, <code>full</code>, <code>headerActions</code>) is built on <code>Surface</code>; <code>SurfacePanel</code> is a raised <code>&lt;section&gt;</code> (the AppShell sidebar <code>Panel</code> keeps its name).</li>\n<li>Docs: <a href=\"/docs/surfaces/surface/\">Surface</a>, <a href=\"/docs/surfaces/card/\">Card</a>, <a href=\"/docs/layout/layout-primitives/\">Layout primitives</a>; <code>docs/css-api.md</code> lists the new <code>ca-*</code> classes and the data attributes.</li>\n</ul>\n<p><strong>Behavior changes to review in <code>Stack</code>, <code>Grid</code> and <code>Row</code></strong> (they were the only layout components with inline <code>style</code>):</p>\n<ul>\n<li><code>Stack</code> and <code>Grid</code> <code>gap</code> is now a spacing step string (<code>gap=&quot;4&quot;</code> is 16px), not pixels. <code>align</code> / <code>justify</code> use <code>start | center | end | between | ...</code> instead of raw CSS values. <code>Grid</code> <code>cols</code> is now <code>columns</code> (1-12, or a responsive object); <code>rowGap</code>, <code>colGap</code> and CSS template strings (<code>&quot;2fr 1fr&quot;</code>, <code>repeat(auto-fill, ...)</code>) are gone: write <code>grid-template-columns</code> in a class.</li>\n<li><code>Row</code> is deprecated in favor of <code>Flex</code>. It still renders <code>.ca-row</code> and maps a pixel <code>gap</code> to the matching step.</li>\n<li><code>GlassCard</code> and <code>GlassSurface</code> are deprecated in favor of <code>Card</code> and <code>Surface</code>, and removed in the next major. They render the new components, keep their <code>ca-glass-card*</code> and <code>glass*</code> classes and look the same under every <code>data-style</code>, and log a <code>[ca-ui]</code> warning in development.</li>\n</ul>\n<p>Size: <code>styles/core.css</code> grows from 9.6 to 13.0 kB gzip (limit 9.6 to 13.6 kB), <code>global.css</code> from 12.6 to 15.9 kB: <code>layout.css</code> is ~2.2 kB (every token prop and step at the base and four breakpoints, generated by <code>scripts/build-layout.ts</code>) and <code>surface.css</code> ~1.2 kB (the five materials by six elevations). JS entries are unchanged.</p>\n<p>Migration: <code>bunx @codeatlasdev/ui migrate --write src</code> rewrites <code>GlassCard</code> to <code>Card material=&quot;glass&quot;</code> (<code>thickness</code> to <code>elevation</code>, <code>tinted</code> to <code>tone=&quot;brand&quot;</code>, <code>glowing</code> to <code>glow</code>, static <code>padding</code> to <code>p</code>), <code>GlassSurface</code> to <code>Surface</code> (<code>refraction</code> to <code>material=&quot;liquid&quot;</code>), <code>Row</code> to <code>Flex</code>, and <code>Stack</code>/<code>Grid</code> pixel gaps, <code>cols</code> and <code>align</code>/<code>justify</code> values. Dynamic or off-scale usages are left as they are, with their import, for you to finish.</p>\n</li>\n<li><p>49b2bf4: Unified layer engine. Dialog, AlertDialog, Sheet, Drawer, Lightbox, CommandPalette, DocsSearch, DropdownMenu, Popover, Tooltip, Select, Combobox, NotificationBell and the date pickers now share one layer stack (<code>primitives/layer.ts</code>).</p>\n<ul>\n<li>Modals are native <code>&lt;dialog&gt;</code> + <code>showModal()</code>; menus, popovers and tooltips are <code>popover</code> elements. Both live in the top layer, so stacking no longer depends on <code>z-index</code>: a menu inside a dialog is always on top. The <code>--ca-z-dropdown</code> and <code>--ca-z-tooltip</code> tokens remain for your own in-flow UI but are no longer used by overlays.</li>\n<li>Anchored layers use CSS anchor positioning when supported and Floating UI otherwise.</li>\n<li>One Escape / outside-press / focus stack: only the topmost layer reacts, so nested overlays dismiss in order. Focus returns to the trigger on close.</li>\n<li>New <code>onLayerChange(listener)</code> (and a <code>layerchange</code> <code>CustomEvent</code> on <code>document</code>) fires on every layer open and close with the open layers and their rectangles, so hosts can park or snapshot native views.</li>\n<li>The <code>.ca-floating</code> wrapper <code>div</code> is gone: <code>.ca-floating</code> is now on the layer element itself (the one carrying <code>data-state</code>/<code>data-side</code>), and <code>.ca-date__float</code> was removed.</li>\n</ul>\n</li>\n</ul>\n","thanks":"To our main supporters and special contributors, Fábio Henrique Araújo (@FabioHAraujo) and Richardson Douglas (@Richards0nd). 💛"},{"version":"2.3.2","id":"v2-3-2","date":"2026-10-15","published":"2026-10-15T00:52:42Z","channel":"latest","url":"https://github.com/codeatlasdev/ui/releases/tag/%40codeatlasdev%2Fui%402.3.2","npm":"https://www.npmjs.com/package/@codeatlasdev/ui/v/2.3.2","html":"<h3 id=\"v2-3-2-patch-changes\">Patch Changes</h3>\n<ul>\n<li>9b7015f: DocsShell: the sidebar keeps its place between pages. On a static docs site every sidebar link loads a new document, and the sidebar used to jump back to the top, hiding the page just picked (#99). The scroll position is now saved per section in <code>sessionStorage</code> (on a sidebar click and on <code>pagehide</code>) and restored by an inline script emitted right after the sidebar, before the first paint; the current page is scrolled into the sidebar&#39;s view when it is outside it. Only the sidebar scrolls: the window and focus are untouched. Client-only mounts and pages whose CSP blocks inline scripts get the same restore on hydration.</li>\n</ul>\n","thanks":"To our main supporters and special contributors, Fábio Henrique Araújo (@FabioHAraujo) and Richardson Douglas (@Richards0nd). 💛"},{"version":"2.3.1","id":"v2-3-1","date":"2026-10-14","published":"2026-10-14T20:04:21Z","channel":"latest","url":"https://github.com/codeatlasdev/ui/releases/tag/%40codeatlasdev%2Fui%402.3.1","npm":"https://www.npmjs.com/package/@codeatlasdev/ui/v/2.3.1","html":"<h3 id=\"v2-3-1-patch-changes\">Patch Changes</h3>\n<ul>\n<li><p>3280fc2: Theme islands: a <code>data-theme-scope</code> inside a <code>data-preset</code> island now takes that island&#39;s preset instead of the page&#39;s. Before, the page preset&#39;s rule also matched the scope, so a brand preset on <code>&lt;html&gt;</code> (unlayered) always won, and between two built-ins the one emitted later in <code>global.css</code> won. The landing hero demo ignored its preset swatches because of this (#87).</p>\n<p>Bundle budget: <code>styles/global.css</code> +122 B gzip (limit raised from 12800 to 12900 B). Each preset&#39;s scope selectors (light, dark and their <code>prefers-contrast: more</code> copies) now carry the nearer-island exclusion; <code>core.css</code> stays within its limit.</p>\n</li>\n</ul>\n","thanks":""},{"version":"2.3.0","id":"v2-3-0","date":"2026-10-14","published":"2026-10-14T18:50:38Z","channel":"latest","url":"https://github.com/codeatlasdev/ui/releases/tag/%40codeatlasdev%2Fui%402.3.0","npm":"https://www.npmjs.com/package/@codeatlasdev/ui/v/2.3.0","html":"<h3 id=\"v2-3-0-minor-changes\">Minor Changes</h3>\n<ul>\n<li><p>1f858d3: The generated API reference now documents every non-component export of every entry point (root, <code>kit</code>, <code>vite</code>, <code>astro</code>, <code>actions</code>, <code>seo</code>, <code>forms</code>, <code>tokens</code>, <code>table</code>, <code>charts</code>): <code>setLocale</code>, <code>registerLocale</code>, <code>caErrorPlugin</code>, <code>createErrorHandler</code>, <code>nativeFeel</code>, <code>usePageTitle</code>, <code>primeKeyboard</code>, the stores, the <code>seo</code> builders and the types, each with its signature and a JSDoc summary. <code>components.json</code> gains an <code>exports</code> array, <code>API.md</code> gains a &quot;Non-component exports&quot; section, the <code>ca-ui-mcp</code> server gains a <code>get_export</code> tool, and components of <code>kit</code> such as <code>RouteAnnouncer</code> now appear in the component list. The summaries were added as JSDoc, so they also show in editor tooltips.</p>\n</li>\n<li><p>648a1e6: New <code>.ca-sr-only</code> utility (visually hidden, still in the accessibility tree) and <code>.ca-sr-only--focusable</code> (shown again while it or a descendant has focus, for skip links). Apps can now add a table <code>&lt;caption&gt;</code> or an AT-only label without custom CSS. Both live in <code>styles/utilities.css</code> (<code>ca.utilities</code> layer), so <code>core.css</code> and <code>global.css</code> carry them. Documented in <code>getting-started/accessibility</code> and <code>docs/css-api.md</code>.</p>\n<p>The kit now uses it instead of eight copies of the clip pattern (<code>Dialog</code>, <code>Select</code>, <code>DocsSearch</code>, <code>Lightbox</code>, the date pickers, and the page titles of <code>PageShell</code>, <code>AppForm</code>, <code>AppList</code> and <code>AppSettings</code>; the <code>Table</code> cards-mode header row shares the same rule). Visual output is unchanged.</p>\n<p>Migration: four internal class names are gone because they only existed to hide text. If your CSS targeted <code>.ca-dialog__sr-only</code>, <code>.ca-sel__sr</code>, <code>.ca-docs-search__sr</code> or <code>.ca-lightbox__sr</code>, target <code>.ca-sr-only</code> instead (these elements now carry that class). The page <code>&lt;h1&gt;</code> hidden inside <code>AppShellLayout</code> below 900px now also carries <code>.ca-sr-only--shell</code>.</p>\n<p>Bundle budget: <code>styles/core.css</code> +173 B gzip and <code>styles/global.css</code> +206 B gzip (limits raised to the measured 9812 / 12800 B). The cost is the new public utility (the box, the focusable variant and two scoped copies that a class cannot express as one rule: the shell page title under a media query, the Table cards header under a container query); the eight component-level copies it replaces were already part of component CSS, not these entry files.</p>\n</li>\n<li><p>19386b1: Add the <code>@codeatlasdev/ui/testing</code> subpath: <code>expectNoA11yViolations</code>, <code>checkA11y</code>, <code>formatViolations</code>, <code>settled</code> and <code>A11Y_EXCEPTIONS</code>. It is the library&#39;s own axe helper (animations settled, <code>region</code> disabled for isolated components), framework-agnostic (takes an <code>Element</code> or <code>Document</code>), with <code>axe-core</code> as an optional peer dependency.</p>\n</li>\n<li><p>a88b8a9: <code>EmptyState</code> and <code>GlassCard</code> accept <code>headingLevel</code> (<code>2 | 3 | 4 | 5 | 6</code>) to render their title as that heading without changing how it looks. <code>EmptyState</code> inside <code>PageShell</code> now defaults to <code>&lt;h2&gt;</code> (it was always <code>&lt;h5&gt;</code>, which made axe report <code>heading-order</code> under the page <code>&lt;h1&gt;</code>); outside a <code>PageShell</code> it stays <code>&lt;h5&gt;</code>. <code>GlassCard</code> renders <code>title</code> as a <code>&lt;p&gt;</code> unless <code>headingLevel</code> is set, so existing outlines are unchanged. New exported type <code>HeadingLevel</code>.</p>\n</li>\n<li><p>5f7d842: New <code>KpiGrid</code>: a responsive row of <code>KpiCard</code>s that never leaves an empty cell. <code>Grid cols=&quot;repeat(auto-fill, minmax(160px, 1fr))&quot;</code> ignores how many cards it holds, so 3 cards on a 390px phone became 2 + 1 with a hole. <code>KpiGrid</code> counts its children in CSS (<code>:has()</code> quantity queries, no JavaScript, SSR-safe) and only uses column counts that divide that number: 3 cards are 3 across or one column, 4 are 4, then 2 x 2, then one column, 6 are 6, 3 + 3, 2 + 2 + 2. It follows its own width (container query), so it also works inside a sidebar layout. Above 12 children it falls back to <code>auto-fill</code>. Props: <code>gap</code>, <code>class</code>. New classes: <code>ca-kpi-grid</code>, <code>ca-kpi-grid__grid</code>.</p>\n<p>The docs, templates, demos and agent skill now use <code>KpiGrid</code> for KPI rows instead of the <code>Grid</code> auto-fill recipe.</p>\n</li>\n<li><p>4db46ab: Three patterns for result views, from the DX feedback in #48. <code>LiveList</code> shows rows that stream in (SSE, WebSocket, bulk jobs): a named <code>role=&quot;log&quot;</code> with batched polite announcements instead of one per row, determinate or indeterminate progress, and auto-scroll that follows the newest row only while the user is at the bottom, pauses when they scroll up (with a &quot;N new&quot; jump button) and never moves focus. <code>ExternalLink</code> is a link to another site with <code>target=&quot;_blank&quot;</code>, <code>rel=&quot;noopener noreferrer&quot;</code>, a decorative icon and a localised &quot;(opens in a new tab)&quot; for screen readers (<code>externalLink.newTab</code> in the i18n catalog), made for table cells. <code>ExportMenu</code> is an Export button with a CSV / JSON <code>DropdownMenu</code>; the helpers <code>toCsv</code>, <code>toJson</code>, <code>downloadFile</code> and the <code>ExportColumn</code> type are exported too (RFC 4180, UTF-8 BOM in the downloaded CSV, spreadsheet formulas neutralised). New i18n keys: <code>externalLink</code>, <code>exportMenu</code>, <code>liveList</code> (en, pt-BR).</p>\n</li>\n<li><p>11809ff: <code>MoneyInput</code> accepts <code>currency</code> (ISO 4217; decimals come from <code>Intl</code>, so <code>JPY</code> has none and <code>BHD</code> has three) and <code>locale</code> (BCP 47). <code>value</code> is now the currency&#39;s minor unit rather than always cents. Existing BRL usage is unchanged: with neither prop set the field still renders <code>R$ 1.234,56</code>. With <code>currency</code> set and no <code>locale</code>, the locale follows the active i18n locale (<code>I18nProvider</code> / <code>setLocale</code>). <code>centsToDisplay</code> and <code>parseInputToCents</code> take an optional <code>{ currency, locale }</code> (defaults <code>BRL</code> / <code>pt-BR</code>), <code>parseInputToCents</code> reads the locale&#39;s decimal separator, and <code>currencyDecimals(currency)</code> and the <code>MoneyOptions</code> type are exported from <code>@codeatlasdev/ui/forms</code>.</p>\n</li>\n</ul>\n<h3 id=\"v2-3-0-patch-changes\">Patch Changes</h3>\n<ul>\n<li>5871641: AppShellLayout: the panel/main offset now uses the Svelte 5 <code>Spring</code> class and lands on the exact target (252px, not 251.984px). The extra CSS <code>margin-left</code> transition that lagged the spring is gone, and <code>prefers-reduced-motion</code> skips the animation and jumps straight to the target.</li>\n<li>ef7b0fa: CommandPalette: keyboard selection no longer gets stolen by the pointer. A row appearing under a stationary pointer (list renders, dialog animates in) fired <code>mouseenter</code> and moved the highlight; hover now follows real <code>mousemove</code>. ArrowDown on an empty list no longer stores <code>-1</code>.</li>\n<li>ee47b59: DocsSearch: keyboard selection no longer gets stolen by the pointer or reset by a lazy index. A row appearing under a stationary pointer (results render, dialog animates in) fired <code>mouseenter</code> and moved the highlight; hover now follows real <code>mousemove</code>. A lazy <code>index()</code> resolving while the dialog is open re-ran the open effect and wiped the typed query. ArrowDown on an empty list no longer stores <code>-1</code>.</li>\n<li>7f5d8f7: <code>init</code> now writes the full SvelteKit setup: <code>caErrorPlugin({ scaffold: true })</code> next to <code>caThemePlugin(theme)</code> in <code>vite.config.ts</code>, and <code>handleError = createErrorHandler()</code> next to the theme <code>handle</code> in <code>src/hooks.server.ts</code> (both patched into existing files with <code>--write</code>, each only when missing; your own <code>handleError</code> is never replaced). The installation guide shows one complete SvelteKit setup, the exact files <code>init</code> writes, and states that plugin order in <code>plugins</code> does not matter.</li>\n<li>ea072c2: DropdownMenu, DataTable column toggle (and any <code>FloatingLayer</code> consumer): closing before the frame-delayed focus move ran no longer lets that move steal focus back from the trigger. <code>FloatingLayer</code>&#39;s <code>onMounted</code> may now return a cleanup that runs on close/unmount.</li>\n<li>ba4c458: <code>PageShell</code> actions below 900px: a lone icon-only control (<code>IconButton</code>, <code>ThemeToggle</code>) no longer stretches to the full row with its icon centred. Icon-only children (<code>.ca-icon-btn</code>, <code>.ca-rail-btn</code>) keep their natural size and sit at the end of the row; labeled buttons still share the row full-width as before.</li>\n</ul>\n","thanks":""},{"version":"2.2.0","id":"v2-2-0","date":"2026-10-14","published":"2026-10-14T12:40:47Z","channel":"latest","url":"https://github.com/codeatlasdev/ui/releases/tag/%40codeatlasdev%2Fui%402.2.0","npm":"https://www.npmjs.com/package/@codeatlasdev/ui/v/2.2.0","html":"<h3 id=\"v2-2-0-minor-changes\">Minor Changes</h3>\n<ul>\n<li><p>d9087e8: Accessible tokens and states, solved per preset and scheme by the generator (every built-in and brand preset, light and dark):</p>\n<ul>\n<li><strong>Disabled controls no longer fade with <code>opacity</code>.</strong> Button (all variants, including the new <code>destructive</code> and <code>glass</code> disabled looks), IconButton, Checkbox, Switch, Slider, Tabs, ToggleGroup, SegmentedControl, Pagination, PinInput, NumberField, RadioGroup, Select, Combobox, TextField, TextArea, date fields and the data-table controls draw the disabled state from <code>--ca-text-disabled</code>, <code>--ca-action-disabled-bg</code> and <code>--ca-outline-default</code>. A loading button keeps its enabled look.</li>\n<li><strong><code>--ca-text-disabled</code></strong> is now guaranteed &gt;= 3:1 on every surface and on the disabled fill, and documented (comment, <code>tokens.json</code> description, docs) as the label of disabled controls only. <code>lint:tokens</code> flags <code>color: var(--ca-text-disabled)</code> outside a disabled selector.</li>\n<li><strong>New <code>--ca-outline-control</code></strong> (&gt;= 3:1 on every surface and on the 5% field tint, WCAG 1.4.11). <code>--ca-field-border-color</code> defaults to it, so TextField, TextArea, Select, Combobox, NumberField, PinInput and the date fields, plus the Checkbox and Radio boxes, reach 3:1. <code>--ca-outline-subtle</code> stays for dividers. Overridable like any <code>--ca-*</code> token.</li>\n<li><strong>New <code>--ca-semantic-error-contrast</code></strong>, the pair of <code>--ca-semantic-error-fill</code>. <code>Button variant=&quot;destructive&quot;</code> reads it instead of a literal white. <code>defineTheme</code> derives it from a custom <code>colors.*.error</code>.</li>\n<li><strong>Menus, popovers, pickers and panels are near-opaque by default</strong> (<code>--ca-overlay-bg</code> / <code>--ca-panel-bg</code> are 94% of <code>--ca-surface-overlay</code>, the blur is kept), so label contrast no longer depends on the page behind. The old tints are <code>--ca-overlay-bg-glass</code> and <code>--ca-panel-bg-glass</code>. The <code>frosted</code> style keeps the opaque default.</li>\n<li><strong><code>--ca-focus-ring</code> is solved per preset and scheme</strong> (&gt;= 3:1 on every surface and on the hover/selected fills) and every focusable component reads it (it was a literal <code>--ca-brand-main</code> in about 20 components; the date fields now draw a real 2px ring instead of a 40% tint). New <code>--ca-state-indicator</code> (same guarantee) for the switch track, slider range and segmented thumb edge. <code>lint:tokens</code> rejects a literal <code>--ca-brand-main</code> in a focus rule.</li>\n<li><code>prefers-contrast: more</code> and <code>forced-colors</code> keep working: the contrast block also lifts <code>--ca-outline-control</code> and <code>--ca-text-disabled</code>.</li>\n<li><code>tokens.json</code> entries now carry a <code>description</code> taken from the comment above the declaration.</li>\n</ul>\n<p>Migration: apps that doubled the class to patch these (<code>.ca-btn.ca-btn:disabled</code>, <code>.ca-tf__wrap.ca-tf__wrap</code>, <code>.glass-overlay.glass-overlay</code>, <code>.ca-btn--destructive.ca-btn--destructive</code>, a global <code>:focus-visible</code> redraw) can delete those overrides.</p>\n<p>Bundle budget: <code>styles/core.css</code> +61 B gzip and <code>styles/global.css</code> +277 B gzip (limits raised to the measured 9639 / 12594 B). The cost is the three solved tokens (<code>--ca-focus-ring</code>, <code>--ca-outline-control</code>, <code>--ca-semantic-error-contrast</code>) and the two <code>prefers-contrast: more</code> lifts in 16 preset blocks, which carry the guarantees above.</p>\n</li>\n<li><p>d9087e8: Component CSS now lives in <code>@layer ca.components</code>, declared last in the kit&#39;s layer order, so the documented contract is true: an unlayered app rule with the same single-class selector (<code>.ca-btn:disabled</code>, <code>.ca-tf__wrap</code>, <code>.ca-tooltip-content</code>, <code>.ca-dialog__body</code>) overrides the kit, with no doubled classes and no <code>!important</code>. Every component <code>&lt;style&gt;</code> block, <code>Dialog/dialog.css</code> and <code>Date/date.css</code> are wrapped, and each starts with the full <code>@layer</code> order statement so bundler chunk order cannot demote the layer. The order in <code>core.css</code> changed to <code>ca.reset, ca.tokens, ca.themes, ca.base, ca.materials, ca.utilities, ca.components</code>. The redundant <code>!important</code> on the error state of TextField, TextArea, NumberField and Combobox was removed (it would have beaten app CSS now that it sits in a layer). Behavior change: app rules that previously lost to a kit rule of equal or higher specificity now win; apps can delete their <code>.ca-btn.ca-btn</code> / <code>!important</code> workarounds. Layered app CSS (for example Tailwind <code>@layer components</code>) must be declared after the kit order statement to outrank it.</p>\n</li>\n<li><p>d9087e8: i18n: open locales and request-scoped language (fixes #46).</p>\n<ul>\n<li><code>Locale</code> is now <code>string</code> (BCP 47). <code>registerLocale(id, messages)</code> adds any language with a partial dictionary; missing keys fall back to English, and region tags resolve to the base language (<code>es-MX</code> → <code>es</code>). <code>getLocales()</code> and <code>getMessages(locale, override?)</code> are exported.</li>\n<li>New <code>&lt;I18nProvider locale messages?&gt;</code>: the language lives in Svelte context, so concurrent SSR requests never share it, and it stays reactive to prop changes. <code>useLocale()</code> and <code>useT()</code> read it from components.</li>\n<li><code>setLocale</code> is now a no-op on the server (with a dev warning), because the module state it wrote was shared across every concurrent request. SSR apps must use <code>I18nProvider</code>; client-only apps keep <code>setLocale</code>. A provider always wins over <code>setLocale</code>.</li>\n<li><code>createErrorHandler</code> no longer reads global locale state. It takes <code>locale</code> (a tag or <code>(event) =&gt; tag</code>) for the fallback message.</li>\n</ul>\n<p>Migration:</p>\n<ul>\n<li><strong>SSR (SvelteKit) apps:</strong> replace <code>setLocale(x)</code> in the root layout with <code>&lt;I18nProvider locale={x}&gt;…&lt;/I18nProvider&gt;</code>. Before, the server-rendered HTML used the locale of whichever request called <code>setLocale</code> last.</li>\n<li><strong>Custom language via <code>setLocale(&quot;en&quot;, fullOverride)</code>:</strong> call <code>registerLocale(&quot;es&quot;, dictionary)</code> and use <code>locale=&quot;es&quot;</code> / <code>setLocale(&quot;es&quot;)</code>. Date and number formatting can now follow the real locale.</li>\n<li><strong><code>createErrorHandler()</code>:</strong> add <code>locale</code> if the fallback message must not be English.</li>\n<li><strong>Removed export:</strong> the internal global <code>t()</code> is gone (it was never exported from the package root). Library code uses <code>const t = useT()</code> at component init.</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"v2-2-0-patch-changes\">Patch Changes</h3>\n<ul>\n<li><p>d9087e8: Fix the component descriptions in <code>components.json</code>, the <code>ca-ui-mcp</code> server and the skill&#39;s <code>API.md</code>: the manifest no longer takes the first JSDoc found in a component (usually a prop&#39;s, e.g. <code>TextField</code> read <code>@deprecated Use description instead</code>). Only a detached component-level doc comment counts, and tag-only comments are skipped.</p>\n<p><code>bunx @codeatlasdev/ui init</code> now supports SvelteKit 3: it reads the installed Kit version, never writes the removed <code>$lib</code> import (uses <code>#lib/theme.ts</code> or a relative path), and reports the changes it does not make for you (<code>svelte.config.js</code> moved into <code>sveltekit({ ... })</code>, <code>tsconfig.json</code> extending <code>$app/tsconfig</code>). The installation guide and the agent skill gain a SvelteKit 3 section and import the theme in <code>vite.config.ts</code> with its <code>.ts</code> extension.</p>\n</li>\n<li><p>d9087e8: <code>Select</code>: an option whose <code>value</code> is <code>&quot;&quot;</code> (e.g. &quot;All projects&quot;) is now a real selection. The trigger shows that option&#39;s label instead of the placeholder and drops the placeholder styling, so filters no longer need a sentinel value. The placeholder shows only when <code>value</code> is <code>undefined</code> or matches no option. <code>onchange(&quot;&quot;)</code> was already delivered and is now covered by a test. With <code>required</code>, a selected <code>&quot;&quot;</code> option counts as a selection.</p>\n</li>\n</ul>\n","thanks":""},{"version":"2.1.1","id":"v2-1-1","date":"2026-10-14","published":"2026-10-14T11:12:39Z","channel":"latest","url":"https://github.com/codeatlasdev/ui/releases/tag/%40codeatlasdev%2Fui%402.1.1","npm":"https://www.npmjs.com/package/@codeatlasdev/ui/v/2.1.1","html":"<h3 id=\"v2-1-1-patch-changes\">Patch Changes</h3>\n<ul>\n<li>01b5188: <code>Steps</code>: a leading <code>&lt;strong&gt;</code>/<code>&lt;b&gt;</code> step title is a block, so its line no longer mixes the bold face with the list item&#39;s regular-face strut. That mixed line rounded to a different height between page loads, shifting content below each step by 1px.</li>\n</ul>\n","thanks":""},{"version":"2.1.0","id":"v2-1-0","date":"2026-10-14","published":"2026-10-14T05:49:01Z","channel":"latest","url":"https://github.com/codeatlasdev/ui/releases/tag/%40codeatlasdev%2Fui%402.1.0","npm":"https://www.npmjs.com/package/@codeatlasdev/ui/v/2.1.0","html":"<h3 id=\"v2-1-0-minor-changes\">Minor Changes</h3>\n<ul>\n<li><p>62d18f8: Theme: port a palette by role and pick the page paper, without raw <code>--ca-*</code> overrides.</p>\n<ul>\n<li><code>PresetDefinition.colors: { light?, dark? }</code> takes role colors (<code>brand</code>, <code>background</code>, <code>sunken</code>, <code>surface</code>, <code>overlay</code>, <code>text*</code>, <code>border*</code>, <code>success</code>/<code>warning</code>/<code>info</code>/<code>error</code>). A per-scheme <code>brand</code> regenerates that scheme&#39;s brand ramp; <code>background</code> also drives <code>&lt;meta name=&quot;theme-color&quot;&gt;</code>. Scoped to the preset, so islands and preset switching keep working.</li>\n<li><code>paper: &quot;mesh&quot; | &quot;plain&quot; | &quot;grid&quot; | &quot;dots&quot; | &quot;lines&quot;</code> replaces the brand glow mesh with flat paper drawn in <code>--ca-outline-subtle</code>.</li>\n<li>New <code>sketchbook</code> design style: journal on grid paper, serif display, square outlined cards with hard shadows.</li>\n<li>Opt-in <code>@codeatlasdev/ui/styles/marks.css</code>: hand-drawn <code>.ca-mark-underline</code> and <code>.ca-mark-note</code> (arrow), painted with <code>--ca-mark-color</code>.</li>\n<li><code>@codeatlasdev/ui/astro</code> re-exports <code>designStyle</code>, <code>DESIGN_STYLES</code> and <code>mergeTheme</code>, so <code>astro.config</code> can use a recipe.</li>\n</ul>\n</li>\n</ul>\n","thanks":""},{"version":"2.0.0","id":"v2-0-0","date":"2026-10-14","published":"2026-10-14T00:41:23Z","channel":"latest","url":"https://github.com/codeatlasdev/ui/releases/tag/%40codeatlasdev%2Fui%402.0.0","npm":"https://www.npmjs.com/package/@codeatlasdev/ui/v/2.0.0","html":"<h3 id=\"v2-0-0-major-changes\">Major Changes</h3>\n<ul>\n<li><p>dbb7193: bits-ui is gone. Every interactive component now runs on the library&#39;s own headless layer (WAI-ARIA Authoring Practices, platform first): native <code>&lt;dialog&gt;</code> for Dialog, AlertDialog, Sheet, Drawer, Lightbox, CommandPalette and DocsSearch; native inputs for Checkbox, Switch and RadioGroup; own floating, dismiss, focus, roving-focus, listbox and presence primitives for Popover, Tooltip, DropdownMenu, Select, Combobox, Tabs, ToggleGroup, SegmentedControl, Slider, PinInput, Accordion, Collapsible, ScrollArea, Pagination, Avatar and the date family. Public props, classes and <code>data-state</code> attributes are unchanged.</p>\n<ul>\n<li>Smaller bundles: AppShellLayout 41.2 → 35.1 kB gzip, form controls −18.7 kB, date family −45.2 kB.</li>\n<li>No more <code>ssr.noExternal</code> for bits-ui and no WKWebView dialog workaround needed.</li>\n<li><code>Select</code> / <code>Combobox</code> with <code>required</code> block native form submit while empty and focus the trigger.</li>\n<li>Disabled <code>Slider</code> now renders dimmed (the selector never matched before).</li>\n<li>Interaction patterns were designed after studying Bits UI (credited in NOTICE).</li>\n</ul>\n<h3 id=\"v2-0-0-breaking-upgrade-notes\">Breaking (upgrade notes)</h3>\n<p>Props, exported types, <code>ca-*</code> classes and <code>data-state</code> attributes did not change. What changed is the DOM around overlays and the dependency set:</p>\n<ul>\n<li>Modal overlays (Dialog, AlertDialog, Sheet, Drawer, Lightbox, CommandPalette, DocsSearch) are a single native <code>&lt;dialog&gt;</code> moved to <code>&lt;body&gt;</code>; the scrim is its <code>::backdrop</code>. CSS that targeted a separate overlay/scrim element or the old portal container no longer matches. Style <code>dialog.ca-overlay::backdrop</code> instead.</li>\n<li><code>--bits-*</code> CSS variables and <code>data-bits-*</code> attributes are gone (<code>--ca-anchor-width</code>, <code>--ca-accordion-height</code>, <code>--ca-collapsible-height</code>, <code>data-ca-day</code> replace the ones used internally).</li>\n<li><code>bits-ui</code> is no longer installed with the package. If your app imports from <code>bits-ui</code> directly, add it to your own dependencies.</li>\n<li>Select no longer renders bits-ui&#39;s hidden <code>&lt;input&gt;</code>; <code>required</code> still blocks native form submit.</li>\n</ul>\n</li>\n</ul>\n","thanks":""},{"version":"1.2.0","id":"v1-2-0","date":"2026-10-13","published":"2026-10-13T21:40:30Z","channel":"latest","url":"https://github.com/codeatlasdev/ui/releases/tag/%40codeatlasdev%2Fui%401.2.0","npm":"https://www.npmjs.com/package/@codeatlasdev/ui/v/1.2.0","html":"<h3 id=\"v1-2-0-minor-changes\">Minor Changes</h3>\n<ul>\n<li><p>e5b0fe8: Design tokens v2: every visual dimension is a theme knob, so one component set can render any design language.</p>\n<ul>\n<li>New <code>defineTheme</code> options: <code>controlRadius</code>, numeric or named <code>density</code> (<code>compact</code>/<code>comfortable</code>/<code>airy</code>), <code>border</code>, <code>typography</code> (scale, weights, heading/label tracking and case, line height, measure), <code>elevation</code> (<code>soft</code>/<code>none</code>/<code>hard</code>/<code>glow</code>), <code>motion</code> (scale, easing personality), <code>iconStroke</code>, <code>noise</code>. <code>designTokens()</code> exposes the mapping.</li>\n<li>Scales are parametric: spacing = 4px × step × <code>--ca-density</code> (new half steps 0.5/1.5/2.5/3.5 and 20/24), radius × <code>--ca-radius-scale</code>, type × <code>--ca-font-scale</code>, durations × <code>--ca-motion-scale</code> (0 under reduced motion). <code>[data-ca-tokens]</code> re-derives them inside an island.</li>\n<li>Fluid layout tokens: <code>--ca-gutter</code>, <code>--ca-section-gap</code>, <code>--ca-content-max</code>, fluid display sizes <code>--ca-font-size-display-1..3</code>. Role tokens: <code>--ca-radius-control</code>, <code>--ca-control-height*</code>, <code>--ca-border-width*</code>, <code>--ca-border-color</code>, <code>--ca-font-weight-*</code>, <code>--ca-heading-*</code>, <code>--ca-label-*</code>, <code>--ca-line-height-*</code>, <code>--ca-measure</code>, <code>--ca-icon-stroke</code>, <code>--ca-noise-opacity</code>, <code>--ca-ease-emphasized</code>.</li>\n<li>Material tiers read <code>--ca-mat-{tier}-{filter,fill,shadow}</code> and <code>--ca-mat-edge</code>. New surface styles: <code>outlined</code>, <code>neumorphic</code>, <code>clay</code>, <code>tonal</code> (alongside glass, frosted, solid, flat).</li>\n<li>Design style recipes: <code>designStyle(id, overrides)</code> and <code>DESIGN_STYLES</code> for Swiss, Neobrutalism, Neumorphism, Glassmorphism, Material 3, Fluent 2, Apple HIG, Bento, Claymorphism, Y2K, Editorial and Terminal.</li>\n<li>Headings read the theme&#39;s display family, heading weight, tracking and case; Lucide icons read <code>--ca-icon-stroke</code>.</li>\n<li>One breakpoint scale (600/900/1200/1536) everywhere, enforced by the token lint, which also rejects malformed <code>svelte-ignore</code> comments.</li>\n</ul>\n</li>\n<li><p>e5b0fe8: RadioGroup: <code>variant=&quot;cards&quot;</code> (selectable cards with label and <code>description</code>, whole card is the hit area) and <code>aria-label</code>. Option ids are now unique per instance, so two groups with the same values on one page no longer share label targets. CodeTabs: a plain <code>code</code> tab renders inside the framed <code>.ca-code</code> block as a focusable scroll region.</p>\n</li>\n<li><p>e5b0fe8: Remove the legacy theme editor generator: <code>editorTokensToCSS</code>, <code>editorPreviewCSS</code>, <code>DEFAULT_CONFIG</code> and the <code>ThemeConfig</code> type are gone, with no aliases.</p>\n<p>Replacement: <code>defineTheme()</code> + <code>themeCSS()</code> (or <code>designStyle(id, overrides)</code> for a recipe) cover every dimension the old hue/chroma/radius/spacing/shadow config exposed, and <code>[data-ca-tokens]</code> + <code>[data-style]</code> + <code>[data-theme-scope]</code> scope a theme to a single element. The theme editor at /theme/ is rebuilt on them: it starts from a design style recipe, edits any <code>ThemeDefinition</code> key and prints the exact <code>defineTheme({ ... })</code> call to paste.</p>\n<p>Migrate: <code>ThemeConfig.hue/chroma/lightL</code> → <code>presets: [{ id, color }]</code>; <code>radiusBase</code> → <code>radius</code>; <code>spacingBase</code>/paddings → <code>density</code>; <code>fontSans</code>/<code>fontMono</code> → <code>fonts</code>; <code>shadowIntensity</code> → <code>elevation</code>; <code>surfaceStyle</code> → <code>defaultStyle</code>.</p>\n</li>\n<li><p>e5b0fe8: Viewport hardening:</p>\n<ul>\n<li><code>--ca-vh-min</code> (small viewport, <code>svh</code> with <code>vh</code> fallback) for full-screen sections, so a hero never hides its call to action behind the browser UI.</li>\n<li>Focus is never obscured by sticky chrome: <code>scroll-padding-block</code> on <code>:root</code> accounts for the header, safe areas and <code>--ca-sticky-top</code> / <code>--ca-sticky-bottom</code>.</li>\n<li>Prose heading anchors are always visible on touch screens (no hover-only affordance).</li>\n<li>BottomNav labels use an 11px floor.</li>\n<li>SimpleTable <code>responsive=&quot;cards&quot;</code> captions every cell with its column header automatically; <code>data-label</code> only overrides.</li>\n<li><code>caThemeHandle</code> warns in dev when the viewport meta blocks zoom (<code>user-scalable=no</code>, <code>maximum-scale=1</code>).</li>\n</ul>\n</li>\n</ul>\n","thanks":""},{"version":"1.1.0","id":"v1-1-0","date":"2026-10-13","published":"2026-10-13T11:20:17Z","channel":"latest","url":"https://github.com/codeatlasdev/ui/releases/tag/%40codeatlasdev%2Fui%401.1.0","npm":"https://www.npmjs.com/package/@codeatlasdev/ui/v/1.1.0","html":"<h3 id=\"v1-1-0-minor-changes\">Minor Changes</h3>\n<ul>\n<li><p>f7d9a25: Accessibility foundations.</p>\n<ul>\n<li>New <code>SkipLink</code> component (<code>ca-skip-link</code>). <code>AppShellLayout</code>, <code>DocsShell</code> and <code>AuthLayout</code> render it as the first tab stop and own the <code>#main</code> target. <code>DocsShell</code> keeps its <code>ca-docs-skip</code> class and <code>docs.skipToContent</code> label, but its <code>&lt;main&gt;</code> id is now <code>main</code> (was <code>ca-docs-content</code>): update any CSS or links that targeted <code>#ca-docs-content</code>. <code>AppShellLayout</code> gets <code>mainId</code> (default <code>&quot;main&quot;</code>; pass <code>null</code> when your page renders its own <code>&lt;main id=&quot;main&quot; tabindex=&quot;-1&quot;&gt;</code>) and <code>skipLink</code> (set <code>false</code> when the shell is embedded in a page that already has one).</li>\n<li>New <code>announce(message, politeness)</code> backed by one shared live region. <code>CopyButton</code> now announces through it (its local <code>ca-copy__status</code> element is gone), and <code>Button</code>/<code>IconButton</code> announce &quot;Finished loading&quot; when <code>loading</code> ends. New <code>RouteAnnouncer</code> in <code>@codeatlasdev/ui/kit</code> announces SvelteKit navigations (document title, or the page <code>&lt;h1&gt;</code>).</li>\n<li><code>forced-colors: active</code> support: glass, blur and shadows are removed, surfaces get a <code>CanvasText</code> border and focus uses <code>Highlight</code>.</li>\n<li><code>prefers-contrast: more</code> tokens (text-secondary and text-hint at 7:1 or more, outlines at 3:1 or more) are emitted per preset by the theme generator, so built-in and brand presets from <code>defineTheme</code> both get them. Built-in theme CSS is regenerated.</li>\n<li><code>Combobox</code> gets an <code>ariaLabel</code> prop to name the control when there is no visible <code>label</code>.</li>\n</ul>\n</li>\n<li><p>fa63ddd: Developer and agent tooling.</p>\n<ul>\n<li><code>ca-ui-mcp</code>: a local MCP server (stdio) with <code>list_components</code>, <code>get_component</code>, <code>search_tokens</code> and <code>get_guide</code>, backed by manifests generated from the component source. Works offline.</li>\n<li><code>components.json</code> and <code>tokens.json</code> are generated on every build and exported as <code>@codeatlasdev/ui/components.json</code> and <code>@codeatlasdev/ui/tokens.json</code> (props, types, defaults, snippets, public <code>ca-*</code> classes, <code>--ca-*</code> tokens).</li>\n<li><code>codeatlas-ui</code> CLI. <code>init</code> wires the theme, hook or integration, <code>global.css</code>, <code>colorScheme.init()</code> and the mobile viewport into a SvelteKit or Astro app without overwriting existing files (it prints a diff; <code>--write</code> applies it). <code>migrate</code> is a codemod that renames <code>BrandPreset</code> to <code>PresetDefinition</code>.</li>\n<li>Dev-only <code>[ca-ui]</code> warnings for <code>IconButton</code> without an accessible name and <code>Dialog</code> without a <code>title</code>.</li>\n</ul>\n</li>\n<li><p>0128aee: Performance foundations.</p>\n<ul>\n<li>New <code>Image</code> component: required <code>width</code>/<code>height</code> (no layout shift), lazy by default, <code>priority</code> for the LCP image (eager, <code>fetchpriority=&quot;high&quot;</code>, preload link), <code>decorative</code> for presentational images, dev warnings for missing dimensions and <code>alt=&quot;&quot;</code> without <code>decorative</code>.</li>\n<li><code>defineTheme({ fonts })</code> accepts font descriptors (<code>family</code>, <code>src</code>, <code>weight</code>, <code>display</code>, <code>preload</code>, <code>fallback</code>, <code>fallbackMetrics</code>): the theme CSS emits <code>@font-face</code> plus a <code>size-adjust</code> fallback face, and <code>themeHead</code>/<code>caThemeHandle</code> emit <code>&lt;link rel=&quot;preload&quot; as=&quot;font&quot; crossorigin&gt;</code>. Plain stack strings keep working. New exported types <code>FontDescriptor</code>, <code>FontFallbackMetrics</code>, <code>FontSpec</code>.</li>\n<li>New stylesheet entries <code>styles/core.css</code> (everything plus the default amber preset) and <code>styles/presets/&lt;id&gt;.css</code> (one built-in preset each). <code>styles/global.css</code> still imports everything.</li>\n<li>Internal: bundle-size budget (<code>bun run size</code>), token lint (<code>lint:tokens</code>), both part of <code>validate</code>. Built-in preset sources moved from <code>styles/themes/</code> to <code>styles/presets/</code> (not a published path).</li>\n</ul>\n</li>\n<li><p>7abc0f9: Add the <code>@codeatlasdev/ui/seo</code> entry point: a <code>Seo</code> head component (title template, canonical, robots, hreflang, Open Graph, Twitter, JSON-LD with <code>&lt;</code> escaped), typed JSON-LD builders (<code>organization</code>, <code>website</code> with <code>SearchAction</code>, <code>softwareApplication</code>, <code>product</code>, <code>faqPage</code>, <code>breadcrumbList</code>, <code>article</code>, <code>graph</code>), <code>sitemapXml</code> and <code>robotsTxt</code>, <code>llmsTxt</code> and a <code>markdownNegotiation</code> SvelteKit handle for AI crawlers, <code>ogImageRoute</code> for 1200x630 social images through an injected headless browser, and PWA helpers <code>manifest()</code> and <code>appleMeta()</code>. <code>caThemeHandle</code> now warns in development when the final HTML has no <code>viewport-fit=cover</code> or no <code>lang</code> on <code>&lt;html&gt;</code>. <code>esm-env</code> is now a direct dependency (dev-only warnings).</p>\n</li>\n</ul>\n<h3 id=\"v1-1-0-patch-changes\">Patch Changes</h3>\n<ul>\n<li>7bcfd57: Browser chrome color sync no longer gets stuck when the page starts in the background (or WebKit pauses its frames): a timeout backs up <code>requestAnimationFrame</code>, and the color re-syncs when the tab becomes visible.</li>\n<li>cd94a19: Browser chrome color sync re-samples after CSS transitions end, so the status bar no longer keeps an in-between color when a theme switch animates the header background.</li>\n<li>c9099ba: <code>CommandPalette</code>: pressing Enter on a result closes the palette again with bits-ui 2.19. Closing returns focus to the element that opened it while the Enter key is still down, and the browser then activated that element and reopened the palette; the palette now closes on <code>keyup</code>. Also builds with <code>@sveltejs/package</code> 3 and TypeScript 6.</li>\n</ul>\n","thanks":""},{"version":"1.0.0","id":"v1-0-0","date":"2026-10-13","published":"2026-10-13T00:55:14Z","channel":"latest","url":"https://github.com/codeatlasdev/ui/releases/tag/%40codeatlasdev%2Fui%401.0.0","npm":"https://www.npmjs.com/package/@codeatlasdev/ui/v/1.0.0","html":"<h3 id=\"v1-0-0-major-changes\">Major Changes</h3>\n<ul>\n<li><p>ef015e6: <strong>1.0.0: stable API.</strong> From here on, breaking changes to props, exports, <code>--ca-*</code> tokens or the public <code>ca-*</code> classes (see <code>docs/css-api.md</code>) only land in a major.</p>\n<h3 id=\"v1-0-0-breaking\">Breaking</h3>\n<ul>\n<li><strong>Default UI strings are English</strong> and come from a typed catalog (<code>en</code>, <code>pt-BR</code>). Portuguese apps call <code>setLocale(&quot;pt-BR&quot;)</code> once in the root layout; the wording is the same as before. Text props (<code>label</code>, <code>placeholder</code>, <code>confirmLabel</code>, <code>emptyText</code>, …) no longer have hardcoded defaults: omit them to use the catalog, pass them to override.</li>\n<li><strong><code>ThemeOptions</code> is now <code>Partial&lt;ThemeDefinition&gt;</code></strong> (<code>defineTheme</code>). <code>BrandPreset</code> is removed: use <code>PresetDefinition</code> (<code>{ id, color, label? }</code>). <code>brandPresetCSS</code> output changed (theme islands) and the head style id is <code>ca-theme</code> (was <code>ca-brand-presets</code>).</li>\n<li><strong>CSS is layered:</strong> <code>global.css</code> declares <code>@layer ca.reset, ca.tokens, ca.themes, ca.base, ca.components, ca.materials, ca.utilities</code>. App CSS stays unlayered and wins without <code>!important</code>; override a token by declaring <code>--ca-*</code> on <code>:root</code>.</li>\n<li><strong>Page background:</strong> <code>html</code> paints a solid <code>--ca-surface-base</code> and the decorative mesh moved to <code>body::before</code> (Safari 26 and the overscroll area sample the root color). <code>body</code> is transparent.</li>\n<li><strong><code>MobileHeader</code> and <code>BottomNav</code> are opaque</strong> (<code>--ca-chrome-surface</code>) so the browser status bar and toolbar match them.</li>\n<li><strong><code>autofocus</code> on touch devices</strong> only focuses when the keyboard was primed in the opening tap (<code>primeKeyboard()</code>); pass <code>{ touch: true }</code> to force. This removes the iOS &quot;focus without keyboard&quot; jump.</li>\n<li><strong><code>responsive</code></strong> is <code>undefined</code>-width until <code>init()</code> runs (no hydration mismatch); derived values treat it as desktop.</li>\n<li>The <code>.ca-app</code>-scoped 16px input rule and the <code>.ca-input-ios-safe</code> class are gone: every text field is covered globally.</li>\n</ul>\n<h3 id=\"v1-0-0-theme-engine\">Theme engine</h3>\n<ul>\n<li><code>defineTheme()</code> + <code>themeCSS()</code>: typed theme object compiled once to deterministic CSS: brand presets from one hex, fonts, radius multiplier, density, raw token overrides.</li>\n<li>Built-in presets are generated from <code>theme/builtins.ts</code> (<code>bun run --cwd packages/ui themes</code>; <code>validate</code> fails if <code>styles/themes/*.css</code> is stale).</li>\n<li>The init script validates stored preset/scheme/style against what the app knows (an old <code>ca-preset</code> no longer repaints the app in amber), applies <code>data-style</code> before first paint, sets <code>&lt;meta name=&quot;theme-color&quot;&gt;</code>, follows the OS under <code>system</code> before hydration and replays runtime tokens.</li>\n<li>Theme islands: <code>data-theme-scope=&quot;light&quot; | &quot;dark&quot;</code> and <code>data-preset</code> on any element, nested in any order.</li>\n<li><code>colorScheme.setTokens(vars)</code> writes one <code>&lt;style id=&quot;ca-runtime-tokens&quot;&gt;</code>, persisted and replayed before paint. <code>colorScheme.init()</code> is idempotent and returns a cleanup; storage errors (Safari private mode) no longer throw. <code>colorScheme.presets</code> lists valid ids.</li>\n</ul>\n<h3 id=\"v1-0-0-mobile-and-webkit\">Mobile and WebKit</h3>\n<ul>\n<li>Tokens <code>--ca-safe-top|right|bottom|left</code>, <code>--ca-vh</code> (dvh with vh fallback), <code>--ca-kb-inset</code>, <code>--ca-input-font-size(-sm)</code>, <code>--ca-chrome-surface</code>, <code>--ca-bottom-nav-h</code>.</li>\n<li><code>viewport</code> store (on-screen keyboard inset from <code>visualViewport</code>, ignores URL-bar and pinch-zoom changes) and the <code>keyboardAware</code> action.</li>\n<li><code>primeKeyboard()</code> / <code>focusTextField()</code> (from <code>@codeatlasdev/ui/actions</code>): open the iOS keyboard from the tap that opens a dialog, sheet or search. Used by <code>DocsSearch</code>, <code>CommandPalette</code> and <code>Combobox</code>.</li>\n<li>Browser chrome color sync: <code>theme-color</code> follows the opaque color under the top edge (header, theme island or page) on Chrome/Samsung Android, Safari ≤ 18 and PWAs.</li>\n<li>New components: <code>StickyActions</code>, <code>MobilePane</code>. <code>Sheet</code> swipe-to-dismiss (<code>swipeToDismiss</code>), <code>AuthLayout</code> <code>mobileBrand</code>.</li>\n<li>44px touch targets for every <code>Button</code> size, press feedback on touch, no tap flash, 16px minimum on every text field (no iOS zoom), <code>-webkit-text-size-adjust</code>.</li>\n<li>Overlays contain overscroll; <code>backdrop-filter</code> falls back to opaque surfaces where unsupported.</li>\n<li><code>nativeFeel()</code> (kit): keyboard-only focus rings, no browser context menu or selection on chrome, <code>.ca-selectable</code> opt-in, optional zoom lock for desktop shells; <code>html[data-ca-surface=&quot;transparent&quot;]</code> for vibrancy windows.</li>\n</ul>\n<h3 id=\"v1-0-0-integrations\">Integrations</h3>\n<ul>\n<li><strong>Astro:</strong> <code>caUI(theme)</code> from <code>@codeatlasdev/ui/astro</code> injects the theme before first paint and configures SSR.</li>\n<li><code>CodeTabs</code> accepts <code>tabs</code> as data (for MDX/Astro where Svelte context cannot reach children); <code>CodeSnippet</code> and code tabs follow the scheme of their context through dual shiki themes.</li>\n</ul>\n<h3 id=\"v1-0-0-fixes\">Fixes</h3>\n<ul>\n<li>Glass lost its blur in production builds: Lightning CSS (Vite 8) drops <code>backdrop-filter</code> declared before <code>-webkit-backdrop-filter</code>. Order fixed everywhere and guarded by a test.</li>\n<li><code>Dialog</code> body no longer collapses to zero height in WKWebView (Tauri, Safari).</li>\n<li><code>Select</code> no longer puts <code>aria-required</code> on the trigger button; required state goes to the form control and the label.</li>\n<li><code>isItemActive</code> handles trailing slashes (<code>trailingSlash: &quot;always&quot;</code> sites).</li>\n<li><code>Tooltip</code> meets AA contrast in both schemes (<code>--ca-tooltip-bg</code>).</li>\n<li>New tokens <code>--ca-focus-ring</code>, <code>--ca-focus-ring-offset</code>, <code>--ca-selection-bg</code>, <code>--ca-scrollbar-thumb(-hover)</code>; reduced motion zeroes <code>--ca-duration-*</code>.</li>\n</ul>\n</li>\n</ul>\n","thanks":""}]}