Skip to content
On this page In short

Changelog · latest

Interfaces with soul: Theme Studio, a UI index and a faster gate

4.6.0 is the biggest release so far: 111 changesets, 130 merged pull requests. You can now design a whole theme in a studio built on the real engine, search every demo and page by facet, read each design style as a live page, check every claim about users against a source, add opt-in sound, render overlays inside a themed preview, and trust that a hover panel never opens under a modal. It also ships a maplibre-gl 6 fix for a critical advisory and a test system that runs only what your change reaches.

Release 4.6.0: one page tied to five feature cardsA large sketchbook page tabbed 4.6.0 sits in the middle of a desk with pencil hatching in its lower corner. Five small cards, Theme Studio, UI index, Design styles, Sound and Test gate, are joined to the page by dashed arrows.4.6.0Theme StudioUI indexDesign stylesSoundTest gateOne release, many stories.
Changesets
111
59 minor and 52 patch, counted from .changeset/ at the commit this post was written on. No major.
Merged pull requests
130
Between the 4.5.0 tag and commit a73cb682, from git log; 62 fixes, 57 features, 11 docs, chores and tests.
Test run for a one-component edit
28 s
Fresh dist, load 15 on a 12-core machine: unit 0.4 s, components 4.6 s, screenshots 6.7 s, routes 16.6 s. Recorded in PR 547.
Sound engine, gzip
3.55 kB
Own code of the sound entry with Svelte external, hard limit 4096 B in size-limit.json. Core carries 0 engine bytes.

In short

  1. Theme Studio. Design a whole theme on the real engine, preview it on eight screens at three widths, and export the code.
  2. The UI index and gallery viewer. Every demo and page is searchable by facet, every filter is a shareable link, and every item opens on Preview, Code, Prompt and Anatomy.
  3. Design style pages. Six styles as live pages, each rendered in its own style, with before and after on real components.
  4. Evidence, atlas and concepts. 269 design terms and 45 sources that were opened and read, with every claim labelled by how strong its support is.
  5. Opt-in sound. Synthesized interface sound in a separate entry, off by default and started only by a gesture.
  6. Inline overlays. Dialog, Sheet, Drawer, Popover, DropdownMenu and Toaster can render in place, inside a themed island.
  7. Hover that respects modals. A new layers store keeps tooltips, hover cards and menus from opening under a dialog.
  8. A larger kit. Carousel, ResizablePanels, Menubar, ContextMenu, HoverCard, NavigationMenu, ButtonGroup, Stat, Kanban and more.
  9. A test system that runs what you changed. Affected-only tests, one pass per route, a machine-wide lock and time budgets that fail a slow run.
  10. maplibre-gl 6 and the advisory. MapStage works on maplibre-gl 6, which carries the fix for a critical XSS in the attribution sanitizer.
  11. Accessibility fixes. Fixes found by axe and by keyboard, from the label that must contain the visible text to scroll regions you can reach.
01

Theme Studio

The theme editor at /theme/ is now a studio built on the real engine. Every control maps one to one onto a ThemeOptions key; the studio invents nothing of its own, so what you see is what your app gets.

Theme Studio: options, real engine, live previews, exportsA stack of four option rows, brand, style, radius and type, feeds a box called real engine. The engine drives a live preview of three screens at widths 390, 768 and 1280, each with a light half and a darker hatched half. A dashed arrow leads from the preview to a box named theme.ts plus CSS plus share URL.ThemeOptionsbrandstyleradiustyperealenginelive preview3907681280theme.ts + CSS + share URLLight and dark side by side.

You start from one of 12 curated looks (or “Surprise me”, which is seeded and always compiles), then shape it: the brand colour with an inline contrast report, 18 colour roles per scheme, nine surface materials, radius, density, fonts, the type scale by role and motion. The preview shows eight screens (dashboard, settings, list, sign in, landing, pricing, form and overlays) at 390, 768 and 1280 with light and dark side by side. It starts from designDefaults(), so it shows what a fresh app gets, not this site’s sketchbook theme.

The colour controls are in the library now. ColorField takes a hex, rgb() or oklch() value and shows the WCAG contrast of the pair as you type; SliderField pairs a slider with an editable number and a unit. Both are real components, so this is the same code the studio runs:

ColorField and SliderFieldLive, real components

6.7:1 Passes AA

Radius
px

Brand #1d4ed8, radius 10px.

Change the colour: the readout tells you whether the text on it passes.

Code
svelte
<script lang="ts">
  import { Button, ColorField, SliderField } from '@codeatlasdev/ui';

  let brand = $state('#1d4ed8');
  let radius = $state(10);
  // Buttons use --ca-radius-md, which is 10px times --ca-radius-scale: 10px of "Radius" is a scale of 1.
  const scale = $derived(radius / 10);
</script>

<div class="col">
  <ColorField label="Brand colour" bind:value={brand} against="#ffffff" role="fill" minRatio={3} />
  <SliderField label="Radius" unit="px" min={0} max={20} bind:value={radius} />
  <div class="preview" style="--ca-radius-scale:{scale}">
    <Button>Start free trial</Button>
    <Button variant="outlined">Learn more</Button>
    <span class="swatch" style="background:{brand}" role="img" aria-label="Brand colour {brand}"></span>
  </div>
  <p class="hint">Brand {brand}, radius {radius}px.</p>
</div>

<style>
  .col { display: grid; gap: var(--ca-space-4); max-inline-size: 24rem; }
  .preview { display: flex; flex-wrap: wrap; align-items: center; gap: var(--ca-space-3); }
  .swatch { inline-size: var(--ca-space-8); block-size: var(--ca-space-8); border-radius: var(--ca-radius-md); }
  .hint { margin: 0; color: var(--ca-text-secondary); font-size: var(--ca-font-size-caption); font-family: var(--ca-font-mono); }
</style>

The same stage that frames the studio’s previews is exported as PreviewStage: a resizable device frame with a width control and a keyboard-operable splitter. Drag the splitter, or press an arrow key on it, and the components inside reflow at that width:

PreviewStageLive, real components
0 px

Workspace

Pick 390, 768 or 1280, or drag the handle.

Code
svelte
<script lang="ts">
  import { Button, Card, PreviewStage, Switch, TextField } from '@codeatlasdev/ui';

  // 'fill' is the component's default: the frame is as wide as the stage, so on a phone it is never scaled down
  // (a scaled frame draws every control smaller than it is). Pick 390, 768 or 1280 to see the other widths.
  let width = $state<number | 'fill'>('fill');
  let name = $state('Acme Inc.');
  let updates = $state(true);
</script>

<PreviewStage label="Workspace settings form" bind:width scheme="light" preset="ocean" height={380}>
  <Card title="Workspace" headingLevel={3} gap="md">
    <TextField label="Workspace name" bind:value={name} />
    <Switch bind:checked={updates} label="Email me about releases" />
    <Button>Save changes</Button>
  </Card>
</PreviewStage>

When you are done, the Code tab gives you theme.ts, the compiled CSS, the SvelteKit and Astro wiring and a share URL that round-trips (#t=v1...). “Apply to site” writes the compiled CSS under a key of its own in your browser only, replayed by an inline script before first paint; “Reset” removes it. There is no account and no server.

import { defineTheme } from "@codeatlasdev/ui";

export const theme = defineTheme({
  preset: "ocean",
  defaultStyle: "solid",
  radius: 12,
  type: { label: { size: "13px", leading: 1.25, tracking: "0", weight: 500 } },
});
02

The UI index and gallery viewer

The gallery used to be a long list. It is now an index: every numbered demo and every page template carries a block of facets (what it is, how it moves, which style, which technique, what it costs) and the gallery filters on them.

UI index: facets filter items into a page, a twin and MCP searchFour facet chips, kind, motion, style and tech, sit on the left, with the counts 346 next to kind and 39 next to motion. A filter arrow leads to a grid of six item cards, two of them highlighted. The result flows on to three outputs: a page, a markdown twin and the MCP search_ui tool.facetskind346motion39styletechfilterpage.md twinMCPsearch_uiEvery filter is a shareable URL.same filters, same link

Chips for kind, subtype, style, motion, interaction, layout, colour mode, density, tech and cost narrow the cards as you click, 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, so ?kind=motion&style=aurora&q=fade is a link you can share.

An item opens on four tabs. Preview is a large resizable stage; Code is the source; Prompt is the text to hand an AI (the get_demo 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 its related items.

Item page with four tabs and a resizable preview stageAn item page shows four tabs, Preview, Code, Prompt and Anatomy, with Preview active. Under it a framed stage has a splitter handle in the middle of its right edge, and three width marks, 390, 768 and 1280, sit below the frame.PreviewCodePromptAnatomy3907681280Prompt is the text to hand your AI.

The index is the same data for agents. ca-ui-mcp gains search_ui, list_facets and get_related, and the build writes api/v1/index.json plus one markdown twin per item.

There is a rule about what the index is allowed to say. A facet that a person read by hand reaches search only when the one-sided 95% upper bound of its error rate, on a seeded and folder-stratified audit, is at most 5%. 0 wrong of 73 reads is “at most about 4% wrong”, never “0%”. Today industry is withheld: 5 wrong of 73, an upper bound of about 14%. Only 203 of 385 entries were ever re-read by a person; the other 182 hold the first pass’s value as corrected by rules, and we say so.

claude mcp add codeatlas-ui -- npx -y -p @codeatlasdev/ui ca-ui-mcp
# then ask: search_ui { "q": "fade", "motion": "subtle", "style": "aurora" }
03

Design style pages

/styles/ has six pages: Swiss, Editorial, Brutalism, Sketchbook, Glassmorphism and Terminal. Each page is rendered in the style it describes, so the type, corners, depth and motion you see are designStyle("...") applied to that page.

Six design styles as thumbnails, applied through designStyleSix small page thumbnails in a grid of three by two: Swiss with rule lines, Editorial with two columns, Brutalism with a hard offset shadow, Sketchbook with pencil hatching, Glassmorphism with translucent overlapping rectangles, and Terminal with a prompt cursor. One arrow leads from the grid to a box labelled designStyle with an id.SwissEditorialBrutalismSketchbookGlassmorphism>_TerminaldesignStyle("id")

Each page has the origin, the traits (type, colour, shape, depth, motion), do and don’t, the accessibility pitfalls with their sources, the same components before and after, the recipe to copy and a prompt for your coding agent. A recipe is a proposal built from the library’s design knobs, not a measurement of any named visual language, and no claim says one style performs better than another.

“Apply to site” works here too: it wears the recipe in your browser only. designDefaults() is now exported, so a scoped preview can reset whatever an outer theme set. And the docs no longer present glass as the identity of the library: the default style is solid, and glass is one style among many.

import { defineTheme, designStyle } from "@codeatlasdev/ui";

export const theme = defineTheme(designStyle("editorial"));
04

Evidence, atlas and concepts

If a page says “people read faster with X”, where does that come from? 4.6.0 adds a contract for exactly that, and the machinery to keep it.

Evidence chain: claim, source, then supports or does not supportA box named Claim points to a box named Source that carries a DOI tag. From the source the chain splits into two outcomes: supports, drawn as a solid filled box, and does not support, drawn as a dashed box. Below, five strength tags read established, supported, preliminary, practice and heuristic. A note records 269 atlas terms and 45 sources read.ClaimSourceDOIsupportsdoes not supportstrength labelestablishedsupportedpreliminarypracticeheuristic269 atlas terms, 45 sources read

The design atlas lists 269 terms (DS001 to DS269) with category, definition, traits, sectors, UX risk and mitigation, three heuristic scales from 1 to 5, the status in 2026, a 2027 to 2030 trend (an editorial projection, not a forecast), how standard the term is and its references. You can browse it with facets at /atlas/, and agents get search_terms, get_term and get_reference.

The evidence layer holds 45 sources. Every one was opened on a recorded date, with the wording relied on kept as an excerpt and the route used to read it. A claim carries one of five labels (established, supported, preliminary, practice, heuristic), a scope (who or what was studied, which task, which context) and its sources. A standard counts as practice, a vendor study as preliminary. A reference also records what it does not support. Tests fail on a banned phrase, on a claim with no scope and on a citation to a source that was not read.

The four Concepts pages (viewport-complete UX, motion that respects people, targets and focus, appeal is not usability) are written to that template, each with a dated Limits box.

05

Opt-in sound

@codeatlasdev/ui/sound is a new optional entry: interface sound with no audio files. Every cue is a short oscillator and noise recipe, so the whole engine is small.

Sound path: gesture, AudioContext, recipe, profile, speakerA user gesture, pointer or key, is the first step; a lock above the first arrow says off by default. The gesture creates the AudioContext, which runs an oscillator and noise recipe, which is shaped by one of four profiles, system, glass, brutal or terminal, and ends at a speaker. A note records 3.55 kB gzip of own code, no audio files, and the persisted key ca-sound.gesturepointer/keyoff by defaultAudioContextcreated hereoscillator+ noise recipeprofilesystemglassbrutalterminal3.55 kB gzip, own code. No audio files.persisted key: ca-sound

Sound is off by default. One shared AudioContext is created only inside the first pointer or key event after the visitor turns sound on with SoundToggle (aria-pressed, an accessible name, an optional volume slider); the choice persists in localStorage under ca-sound. Four profiles (system, glass, brutal, terminal) follow the design style through --ca-sound-profile, and --ca-sound-gain, --ca-sound-pitch and --ca-sound-brightness tune them. Hover and slider tick cues are off under prefers-reduced-motion, sound is suspended while the tab is hidden or media plays, and sound is never the only signal.

Hear itLive, real components

Turn sound on, then press the buttons.

Sound is off until you turn it on. Nothing plays before your first click or key press.

Nothing plays until you turn sound on. That is the point.

Code
svelte
<script lang="ts">
  import { Button, Switch } from '@codeatlasdev/ui';
  import { SoundToggle } from '@codeatlasdev/ui/sound';

  let notify = $state(false);
</script>

<div class="col">
  <p class="label">Turn sound on, then press the buttons.</p>
  <div class="row">
    <SoundToggle volume />
    <Button sound>Save</Button>
    <Button variant="outlined" sound="success">Approve</Button>
    <Switch sound bind:checked={notify} label="Email me about releases" />
  </div>
  <p class="hint">Sound is off until you turn it on. Nothing plays before your first click or key press.</p>
</div>

<style>
  .col { display: grid; gap: var(--ca-space-3); }
  .row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--ca-space-3); }
  .label { margin: 0; color: var(--ca-text-primary); font-size: var(--ca-font-size-body1); }
  .hint { margin: 0; color: var(--ca-text-secondary); font-size: var(--ca-font-size-caption); }
</style>
Sound engine size lower is better
  1. Own code of ./soundlimit 4096 B
    3.55 kB gzip

Conditions. Own code of the ./sound entry with the Svelte runtime external, gzip, as measured for pull request 577; the limit is 4096 B in packages/ui/size-limit.json. Core is a no-op hook (soundBus), and a test fails if any core module imports the engine.

<script>
  import { SoundToggle } from "@codeatlasdev/ui/sound";
  import { Button } from "@codeatlasdev/ui";
</script>

<SoundToggle />
<Button sound>Save</Button>
06

Inline overlays

Overlays live in the browser’s top layer, which is the right place for a real dialog and the wrong place for a preview: a dialog opened inside a themed island escapes to body and loses the island’s tokens. That is why the old Theme Studio overlays screen showed static replicas.

Top layer dialog escapes the themed island; inline stays insideTwo columns compare a dialog. On the left, Top layer: the dialog sits outside the dashed outline of the themed island and a cross marks that the tokens are lost. On the right, inline: the same dialog is drawn inside the island, with pencil hatching around it and a check marking that the tokens are kept.Top layerislandx tokens lostinlineislandtokens keptinline renders in place, inside the island.

inline on Dialog, Sheet, Drawer, Popover, DropdownMenu and Toaster renders the overlay in place: position: absolute inside its container, never moved to body, no showModal() or showPopover(), no scroll lock, no focus trap, no inert page and no global Escape or outside-press ownership (Escape closes it from inside). It resolves the tokens of its container, so it follows a data-preset or data-theme-scope island. openLayer takes the same option. The studio’s overlays screen now shows the real components.

A dialog inside a dark islandLive, real components
Nothing picked yet

Publish release 4.6.0?

This dialog is inside the box, wearing the box's own theme.

The page around it is whatever you chose. The dialog wears the island.

Code
svelte
<script lang="ts">
  import { Button, Dialog, DropdownMenu, DropdownMenuItem, DropdownMenuLabel, ThemeIsland } from '@codeatlasdev/ui';

  let open = $state(true);
  let last = $state('Nothing picked yet');
</script>

<ThemeIsland scheme="dark" preset="ocean" class="island">
  <div class="bar">
    <Button size="sm" onclick={() => (open = !open)}>{open ? 'Close' : 'Open'} dialog</Button>
    <DropdownMenu inline>
      {#snippet triggerChild(props)}
        <Button size="sm" variant="outlined" {...props}>Environment</Button>
      {/snippet}
      <DropdownMenuLabel>Deploy to</DropdownMenuLabel>
      <DropdownMenuItem onclick={() => (last = 'Staging')}>Staging</DropdownMenuItem>
      <DropdownMenuItem onclick={() => (last = 'Production')}>Production</DropdownMenuItem>
    </DropdownMenu>
    <span class="note">{last}</span>
  </div>
  <!-- The dialog is absolutely positioned inside this region: a stage of its own, below the controls, so nothing overlaps. -->
  <div class="stage">
    <Dialog
      inline
      bind:open
      size="sm"
      title="Publish release 4.6.0?"
      description="This dialog is inside the box, wearing the box's own theme."
    >
      {#snippet actions()}
        <Button size="sm" variant="outlined" onclick={() => (open = false)}>Not yet</Button>
        <Button size="sm" onclick={() => (open = false)}>Publish</Button>
      {/snippet}
    </Dialog>
  </div>
</ThemeIsland>

<style>
  :global(.island) {
    inline-size: min(100%, 28rem);
    border: var(--ca-border-width) solid var(--ca-outline-default);
    border-radius: var(--ca-radius-md);
    overflow: hidden;
  }
  .bar { display: flex; flex-wrap: wrap; align-items: center; gap: var(--ca-space-3); padding: var(--ca-space-3) var(--ca-space-4); }
  .stage { position: relative; min-block-size: 15rem; }
  .note { font-size: var(--ca-font-size-caption); color: var(--ca-text-secondary); }
</style>
<ThemeIsland scheme="dark" preset="ocean">
  <div style="position: relative; min-height: 240px">
    <Dialog inline bind:open title="Invite a teammate">...</Dialog>
  </div>
</ThemeIsland>
07

A hover card that opens under a dialog is a bug you only see on a trackpad: you open the dialog, move the pointer, and a panel appears behind the scrim.

Hover trigger on the page is blocked by an open modalA page holds a hover card trigger, with a scrim and a modal dialog stacked above it. A pointer arrow aimed at the page trigger stops at a barrier labelled layers.modalOpen. A second pointer aimed at a trigger inside the dialog passes through and is marked with a check.pagehover cardmodal dialoghover cardscrimlayers.modalOpenpasses: trigger inside the modalA hover panel never opens under a modal.

A new layers store has layers.modalOpen, reactive and true while a modal layer (Dialog, AlertDialog, Drawer, Sheet) is open, and layers.blocks(trigger), which is the same but false for a trigger inside the topmost modal. Tooltip, HoverCard and NavigationMenuItem use it and no longer open on pointer movement while a modal is open. A tooltip that was open when its trigger opened a dialog also no longer leaves a stray second copy in the page after the dialog closes. Gate your own hover or edge UI with the same store.

Open the dialog, then hoverLive, real components

Modal open: no

Hover the first button: a tooltip opens. Open the dialog, then try hovering again: nothing opens under it. This is the real top-layer dialog.

The readout is layers.modalOpen.

Code
svelte
<script lang="ts">
  import { Button, Dialog, Tooltip, layers } from '@codeatlasdev/ui';

  let open = $state(false);
</script>

<div class="col">
  <div class="row">
    <Tooltip content="Copies a link to this release" delay={150}>
      {#snippet triggerChild(props)}
        <Button variant="outlined" {...props}>Hover me</Button>
      {/snippet}
    </Tooltip>
    <Button onclick={() => (open = true)}>Open dialog</Button>
  </div>
  <p class="state" aria-live="polite">Modal open: {layers.modalOpen ? 'yes' : 'no'}</p>
  <p class="hint">
    Hover the first button: a tooltip opens. Open the dialog, then try hovering again: nothing opens under it.
    This is the real top-layer dialog.
  </p>
</div>

<Dialog bind:open size="sm" title="Rename release" description="The page behind is inert, so hover panels stay closed.">
  {#snippet actions()}
    <Button onclick={() => (open = false)}>Done</Button>
  {/snippet}
</Dialog>

<style>
  .col { display: grid; gap: var(--ca-space-3); }
  .row { display: flex; flex-wrap: wrap; gap: var(--ca-space-3); }
  .state { margin: 0; font-family: var(--ca-font-mono); font-size: var(--ca-font-size-body2); color: var(--ca-text-primary); }
  .hint { margin: 0; font-size: var(--ca-font-size-caption); color: var(--ca-text-secondary); }
</style>
<script>
  import { layers } from "@codeatlasdev/ui";
  const hint = $derived(layers.modalOpen ? "paused" : "active");
</script>
08

A larger kit

Many of the 57 feature pull requests are components, driven by the work of moving real apps onto the library.

A tourLive, real components

Five new components in one place.

Code
svelte
<script lang="ts">
  import {
    Button,
    ButtonGroup,
    Carousel,
    CarouselItem,
    DropdownMenuItem,
    HoverCard,
    Menubar,
    MenubarMenu,
    ResizableHandle,
    ResizablePanel,
    ResizablePanels,
    Tabs,
    TabsContent,
    TabsList,
    TabsTrigger,
  } from '@codeatlasdev/ui';

  const slides = ['Plan', 'Build', 'Ship'];
  let range = $state('Week');
</script>

<Tabs value="carousel">
  <TabsList fade aria-label="New components">
    <TabsTrigger value="carousel">Carousel</TabsTrigger>
    <TabsTrigger value="panels">Panels</TabsTrigger>
    <TabsTrigger value="group">ButtonGroup</TabsTrigger>
    <TabsTrigger value="menubar">Menubar</TabsTrigger>
    <TabsTrigger value="hover">HoverCard</TabsTrigger>
  </TabsList>
  <TabsContent value="carousel">
    <div class="pane">
      <Carousel label="Release steps">
        {#each slides as s (s)}
          <CarouselItem><div class="slide">{s}</div></CarouselItem>
        {/each}
      </Carousel>
    </div>
  </TabsContent>
  <TabsContent value="panels">
    <div class="pane frame">
      <ResizablePanels label="Workspace">
        <ResizablePanel id="files" defaultSize={35} minSize={15}><p class="cell">Files</p></ResizablePanel>
        <ResizableHandle label="Resize files" />
        <ResizablePanel id="editor" defaultSize={65}><p class="cell">Editor</p></ResizablePanel>
      </ResizablePanels>
    </div>
  </TabsContent>
  <TabsContent value="group">
    <div class="pane">
      <ButtonGroup aria-label="Range">
        {#each ['Day', 'Week', 'Month'] as r (r)}
          <Button variant={range === r ? 'filled' : 'outlined'} tone="neutral" aria-pressed={range === r} onclick={() => (range = r)}>{r}</Button>
        {/each}
      </ButtonGroup>
    </div>
  </TabsContent>
  <TabsContent value="menubar">
    <div class="pane">
      <Menubar aria-label="Document">
        <MenubarMenu label="File">
          <DropdownMenuItem>New</DropdownMenuItem>
          <DropdownMenuItem>Open</DropdownMenuItem>
        </MenubarMenu>
        <MenubarMenu label="Edit">
          <DropdownMenuItem>Undo</DropdownMenuItem>
          <DropdownMenuItem>Redo</DropdownMenuItem>
        </MenubarMenu>
      </Menubar>
    </div>
  </TabsContent>
  <TabsContent value="hover">
    <div class="pane">
      <HoverCard openDelay={150}>
        {#snippet triggerChild(props)}<Button variant="outlined" {...props}>Hover or focus me</Button>{/snippet}
        <p class="cell">Team plan: 10 seats and priority support.</p>
      </HoverCard>
    </div>
  </TabsContent>
</Tabs>

<style>
  .pane { padding-block-start: var(--ca-space-4); max-width: 28rem; }
  .frame { height: 9rem; border: var(--ca-border-width) solid var(--ca-outline-default); border-radius: var(--ca-radius-md); overflow: hidden; margin-block-start: var(--ca-space-4); padding: 0; }
  .slide { display: grid; place-items: center; height: 6rem; margin-inline: var(--ca-space-1); border-radius: var(--ca-radius-md); background: var(--ca-surface-sunken); color: var(--ca-text-primary); font-size: var(--ca-font-size-h5); }
  .cell { margin: 0; padding: var(--ca-space-3); color: var(--ca-text-secondary); font-size: var(--ca-font-size-body2); }
</style>

New: Carousel (scroll-snap, WAI-ARIA carousel, autoplay opt-in and never under reduced motion), ResizablePanels (window splitters, arrow keys 1%, Shift 10%), Menubar, ContextMenu (right-click, long-press and Shift+F10), HoverCard, NavigationMenu, ButtonGroup, AspectRatio, Kanban (on the new useDnd primitive from 4.5.0, with cross-column moves and per-column infinite scroll), Stat, ChipGroup, Heading, Highlight, StrikeThrough, LocaleSwitcher, PageFrame and a map-first AppShellLayout stage, Turnstile, PasswordStrength, SplashScreen and PreviewStage. Extended: Toast gets descriptions, one inline action and dedupe, TextField gets a password reveal, a below slot and input-group addons, Button gets tone="neutral" and a trailing hint, Badge gets a subtle pill, and Tabs, Progress, Drawer, Dialog and SimpleTable gain the options real screens asked for.

Theming reaches further too. ThemeOptions.components sets --ca-btn-*, --ca-avatar-*, --ca-card-* and other component tokens on one naming convention; ThemeIsland gives a block or a whole page its own preset and scheme with SSR parity; a named type scale by role (title, heading, body, label, caption, num) overrides any part for the whole app; presets can carry alpha colours and domain tokens (--ca-token-status-new).

A small one worth seeing: a trend is not always good news when it goes up. Stat and KpiCard take trendTone, so a falling churn reads as good.

View

Before: arrow up is good

Revenue
48,200USD
↑+12.4%
Churn
2.1%
↓-1.8%
Active seats
1,204
↑+0.4%

After: trendTone

Revenue
48,200USD
↑+12.4%
Churn
2.1%
↓-1.8%
Active seats
1,204
↑+0.4%
Same data. Churn fell 1.8%; the second card says so in colour and in the accessible label.
svelte
<script lang="ts">
  import { Stat } from '@codeatlasdev/ui';
</script>

<div class="grid">
  <Stat label="Revenue" value="48,200" unit="USD" trend={12.4} trendTone="positive-up" />
  <Stat label="Churn" value="2.1" unit="%" trend={-1.8} trendTone="positive-down" />
  <Stat label="Active seats" value="1,204" trend={0.4} trendTone="neutral" />
</div>

<style>
  .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr)); gap: var(--ca-space-5); }
</style>
Toasts that do not pile upLive, real components

Ten identical errors leave one toast with a ×10 badge. Upload replaces its toast in place. The archive toast carries a description and an Undo action.

Fail ten times, then upload.

Code
svelte
<script lang="ts">
  import { Button, Toaster, toast } from '@codeatlasdev/ui';

  function fail() {
    for (let i = 0; i < 10; i++) toast.error('Could not reach the server.');
  }

  function upload() {
    toast.info('Uploading report.pdf…', { id: 'upload', duration: 0 });
    setTimeout(() => toast.success('report.pdf uploaded.', { id: 'upload' }), 1500);
  }

  function archive() {
    toast.success('Report archived', {
      description: 'It moved to the Archive folder.',
      action: { label: 'Undo', onClick: () => toast.info('Report restored.') },
    });
  }
</script>

<Toaster />

<div class="col">
  <div class="row">
    <Button variant="outlined" onclick={fail}>Fail 10 times</Button>
    <Button variant="outlined" onclick={upload}>Upload</Button>
    <Button variant="outlined" onclick={archive}>Archive report</Button>
  </div>
  <p class="hint">
    Ten identical errors leave one toast with a ×10 badge. Upload replaces its toast in place.
    The archive toast carries a description and an Undo action.
  </p>
</div>

<style>
  .col { display: grid; gap: var(--ca-space-3); }
  .row { display: flex; flex-wrap: wrap; gap: var(--ca-space-3); }
  .hint { margin: 0; color: var(--ca-text-secondary); font-size: var(--ca-font-size-caption); }
</style>
Fields for real formsLive, real components
Sales team size
https:// .com

Read with the label: https:// acme .com

Reveal the password; pick a team size.

Code
svelte
<script lang="ts">
  import { ChipGroup, TextField } from '@codeatlasdev/ui';

  let size = $state('2-5');
  let site = $state('acme');
  let password = $state('');

  const sizes = [
    { value: '1', label: 'Just me' },
    { value: '2-5', label: '2 to 5' },
    { value: '6-20', label: '6 to 20' },
    { value: '21-50', label: '21 to 50' },
    { value: '50+', label: '50+' },
  ];
</script>

<div class="col">
  <ChipGroup label="Sales team size" items={sizes} bind:value={size} />
  <TextField label="Website" prefix="https://" suffix=".com" bind:value={site} description="Read with the label: https:// acme .com" />
  <TextField
    label="Password"
    type="password"
    revealable
    required
    requiredIndicator={false}
    autocomplete="new-password"
    bind:value={password}
  >
    {#snippet below()}
      <a class="link" href="#forgot-password">Forgot password?</a>
    {/snippet}
  </TextField>
</div>

<style>
  .col { display: grid; gap: var(--ca-space-5); width: 100%; max-width: 26rem; }
  .link { color: var(--ca-brand-text); }
</style>
09

A test system that runs what you changed

The suites froze a 12-core machine: 2433 Playwright tests loaded all 182 docs routes four to six times, and visual tests ran in an emulated amd64 container. 4.6.0 changes the shape of testing, not just the speed.

Test pipeline: diff selects lanes, one lock, a time budgetA git diff feeds an import graph and route map, which selects three lanes, unit, components and routes, labelled only what the diff reaches. The lanes pass a lock gate, one suite at a time with a machine-wide lock, and then a clock box, time budget, fails over. A second row shows the release path: test:full, then test:vrt. A note says native baselines per platform.on every changegit diffimport graph+ route mapunitcomponentsroutesonly what the diff reachesone suite at a time(machine-wide lock)time budget, fails overnative baselinesper platformreleasetest:fulltest:vrt

bun run test now runs only what the diff against origin/main affects. Component tests are chosen by a static import graph (scripts/test-graph.ts), because Vitest’s own --changed selected 198 of 198 files for a one-line Avatar edit in browser mode. Routes are chosen by scripts/route-impact.ts, which fails safe: a shared path or a file it cannot place selects everything and says why. Every route is loaded once per project by three specs (smoke and layout, axe in dark, light and forced colors, and static HTML rules read from dist with no browser). Visual baselines are native per platform, the Docker path is gone, and a machine-wide lock lets one suite run at a time.

Every command has a time budget that is enforced: a run over its budget fails and prints its 10 slowest tests. On an oversubscribed machine the verdict is printed as not enforced, because it would only measure the other processes.

Gate wall time before and after, route sweep and componentsTwo pairs of horizontal bars, drawn to scale. For the route sweep the before bar is dashed and long, killed at 467 s over 1998 tests, and the after bar is solid and short, 168 s at load 29. For components the before bar is dashed, 178 to 227 s, and the after bar is solid, 109 s at load 25.Route sweepbeforekilled at 467 s (1998 tests)after168 sload 29Componentsbefore178 to 227 safter109 sload 25Wall time on a shared 12-core machine, load noted.See the numbers table below.
One-component edit, bun run test lower is better
  1. Total
    28 s
  2. Routes
    16.6 s
  3. Screenshots
    6.7 s
  4. Components
    4.6 s
  5. Unit
    0.4 s

Conditions. A one-component edit with a fresh dist, wall time on the maintainer's 12-core machine at load 15 (shared with other work), from the pull request that introduced the system (547). The budget for this command is 60 s.

Full sweeps, before and after lower is better
  1. Route sweep, before1998 tests
    killed at 467 s
  2. Route sweep, afterload 29
    168 s
  3. Components, before
    178 to 227 s
  4. Components, afterload 25
    109 s

Conditions. Wall time, shared 12-core machine, load noted per row, from pull request 547. The old route sweep was killed at 467 s, so that bar is a floor, not a completion time. The numbers are comparable only because they were taken on the same machine in the same window; they are not a benchmark.

Later work held the land gate under its budget: with a shared change (every component test, 210 files) it measured 224.8 s with a cold social-card cache and 193.5 s with a warm one, at load 7 on 12 cores, against a 240 s budget (pull request 599). bun run test:full has a 300 s budget and, by the record in that same pull request, was not measured on a quiet machine: one run took 1092 s at load 50 and is not a result. We state it so you do not read the budget as a promise.

Three dev-tooling targets were not met in this release. A shared-change land (every component test) took 255 s against a 120 s target. Our own tooling peaks at 8 cores in one land against a 4-core cap, and that cap is not enforced yet (issue 632). The warm bun run test still takes 11 to 13 s against a 10 s target. The enforced budgets above hold; these targets are what we are still working toward, and the numbers are wall time on the maintainer’s shared 12-core machine.

10

maplibre-gl 6 and the advisory

maplibre-gl 6.4.0 and earlier carry a critical XSS in the attribution sanitizer (GHSA-jrc7-96c5-q579), fixed in 6.4.1. MapStage now works on maplibre-gl 6, so the peer range is ^5.0.0 || ^6.0.0 again and you can take the fix.

With engine="maplibre" the deck.gl layers go through MapLibreOverlay from the new optional peer @deck.gl/maplibre (~9.4.0, which supports maplibre-gl 4.5.1, 5 and 6), because MapboxOverlay throws Cannot read properties of undefined (reading 'height') on every frame there. engine="mapbox" keeps @deck.gl/mapbox. Fitting clear of a wide PageFrame no longer throws Invalid LngLat object: the padding was subtracted twice.

bun add maplibre-gl@^6.4.1 @deck.gl/maplibre@~9.4.0

On maplibre-gl 6 the app must also call setWorkerUrl once; the map docs show the Vite form.

11

Accessibility fixes

The fixes came from running axe over every route, in dark, light and forced colors, and from using the keyboard.

  • FileUpload, NotificationBell: the accessible name now contains the visible text (WCAG 2.5.3, axe label-content-name-mismatch).
  • Kanban column titles are h2 by default, so a board under a page h1 passes heading-order.
  • --ca-brand-text is solved per preset and scheme: a light brand (lime, yellow, light cyan) no longer gets a 1.6:1 label on outlined buttons, links and active nav; it holds 4.5:1 on every surface. tests/contrast.test.ts measures every preset.
  • AuthLayout: the compact mobile mark holds 4.5:1 in dark on every preset (it failed on six of eight at 390px), and a flat panel’s secondary text uses theme text levels (it measured 2.21:1 in light).
  • Carousel, PreviewStage, ResizablePanel and CodeSnippet scroll regions are keyboard-reachable while they overflow (scrollable-region-focusable); Carousel and Menubar reach the 44px touch target; SegmentedControl items are 44px wide too, and the password reveal button got its hit area back.
  • NavigationMenu: Enter or Space always toggles, also within 600 ms of a hover open.
  • PageFrame no longer overflows a 390px viewport by the frame gap, and SiteHeader no longer overflows at 390px.
  • Switch inside a SettingItem no longer needs a redundant aria-label: the row already names it.

Upgrade

Published under the latest dist-tag.

bun
bash
bun add @codeatlasdev/[email protected]
npm
bash
npm install @codeatlasdev/[email protected]
pnpm
bash
pnpm add @codeatlasdev/[email protected]
yarn
bash
yarn add @codeatlasdev/[email protected]

Then run your checks. Moving between majors? bunx @codeatlasdev/ui migrate --from <major> --to <major> lists what to change, and agents get the same data from the MCP tool get_migration. The migration notes per major are inUpgrading.

Behaviour changes

This release carries no major changeset: the new APIs are additive. These are the changes you can observe in an app that did nothing, with what to do about each.

A page with no data-style now renders the solid default
An app that does not use the theme plugin used to keep the glass tokens, so every blurred element (chips, tabs, toasts, rail, panels) blurred. Now it renders data-style solid: opaque surfaces, no blur, no glow.
What to do. Nothing, if you want the default. To keep glass, set data-style on html.
Kanban column titles are h2 by default
They were h3, which skipped a level under a page h1 and failed axe heading-order. Only the tag changes, never the look.
What to do. Pass headingLevel={3} to keep the old markup under an h2 of your own.
Only the most specific nav item is active
With items at /items and /items/a, visiting /items/a marks just Item A, and the mobile header shows its label. The overview item stays active on its other sub-paths.
What to do. Nothing, unless your own CSS relied on both being active.
DocsHeader is opaque
The bar was an 82% translucent fill with a blur, so scrolled content showed through. It is now a solid surface, and landing and template pages can use the same header.
What to do. Nothing.
AuthLayout brand replaces the whole logo
A brand snippet no longer renders the default CodeAtlas UI name beside it.
What to do. Pass logoName to show a name next to your brand.
Identical toasts no longer stack
toast.error with the same text ten times leaves one toast with a x10 badge; at most three show at once and the oldest is dropped.
What to do. Pass an id to replace a specific toast in place.
Interactive shapes get a pointer cursor from the reset layer
button, a[href], label[for], summary and the button, tab, option and menuitem roles get cursor pointer; disabled ones get not-allowed. nativeFeel no longer forces the arrow cursor back.
What to do. An unlayered cursor declaration in your own CSS wins, no important needed.
Twelve unused utility classes left the stylesheet
.glass-bg-extend, .glass-inner-sm|md|lg, .glass-prominent, .glass-scroll-edge-top|bottom, .ca-toolbar-sep, .ca-clear-btn, .ca-row-check and .ca-trend-up|down were never used by a component or a documented API. styles/global.css shrinks by 242 B gzip.
What to do. If you styled an element with one of them, keep that rule in your own CSS.

All changes

130 merged pull requests between 4.5.0 and a73cb682, grouped by area. Generated from git: bun scripts/changelog-data.ts 4.6.0.

Theme and Studio13
  • featuretheme studio: presets, colour roles, live preview, apply to site#562issue #560
  • docscode surfaces guide and --ca-code-line-height token (integer px line-height)#554issue #551
  • fixwithout data-style on <html> the page renders glass blur instead of the solid default#565issue #564
  • featuretypography: named type scale by role (title, heading, body, label, caption, mono-num) with tracking and line-heigh#433issue #375
  • featureauthLayout: panel background is a fixed brand gradient with blobs, no token or prop for a flat panel#423issue #409
  • featuredensity tokens for Avatar, TextArea, TextField, Card, SegmentedControl and EmptyState (no selectors against intern#431issue #373
  • featurebutton / IconButton / Kbd: weight, tracking, line-height, gap and padding as component tokens#421issue #373
  • featuretheme presets: colors with alpha/OKLCH and extensible domain tokens (status, tier, channel) per preset and scheme#427issue #374
  • featureeditorial primitives: display scale, out-expo/quart easings, animated highlight/strike, paper grain#394issue #353
  • featuretypography: display font role for marketing/auth headlines (`--ca-font-display`) used by components#370issue #350
  • featuretheme island: full-page preset/scheme override with SSR parity (light landing inside a dark app)#399issue #352
  • featuretheme presets: override primary-button fill/contrast and field font size per preset#390issue #354
  • fixtheme: body text size is also the root font-size, so changing it rescales every rem token#371issue #351
UI index and gallery6
  • featurethe index, phase 2b: item page tabs Preview, Code, Prompt, Anatomy#587issue #580
  • featurethe index, phase 2a: faceted gallery search with URL state and instant filtering#585issue #579
  • featurethe index, phase 2c: MCP search_ui, list_facets, get_related#582issue #581
  • featurethe index, phase 1: facet schema, 4-digit id ranges, backfill of every demo, index.json and .md twins#570issue #569
  • featuregallery viewer: big resizable stage, pages (templates) as a category, better cards, lib PreviewStage#558issue #557
  • fixsite: landing and templates render a different top header than docs, changelog and gallery#556issue #555
Design styles, atlas and evidence4
  • featuredesign style pages: /styles/ index and six pages rendered in their own style (brutalism, editorial, sketchbo#584issue #583
  • docsconcepts viewport, motion, targets and focus, appeal#586issue #573
  • featuredesign atlas, 269 terms with browse, term pages and MCP search_terms#578issue #572
  • docsevidence foundation, verified references, concept template#576issue #571
Components14
  • fixnativeFeel keeps the arrow cursor on buttons, tabs and menu items (beats the ca.reset pointer)#567issue #566
  • featurereset layer gives every clickable control a pointer cursor (and not-allowed when disabled)#552issue #549
  • fixmotion demo: Press spring stays pressed (scale 0.94) when Pause lands during the autoplay press#536issue #535
  • featureKpiCard: chart slot / sparkline prop so an icon + sparkline tile needs no Card+Stat+Sparkline composition#510issue #505
  • featurestat, KpiCard: trend colour assumes up is good (churn, cost, latency need down is good)#487issue #486
  • featureCarousel: scroll-snap slides with prev/next, dots and optional autoplay#451issue #445
  • featurebuttonGroup: attached buttons with role=group and orientation#452issue #448
  • featureImage: ratio prop and AspectRatio wrapper#453issue #446
  • featurebadge: subtle tone with dot for inline pills; Button: responsive trailing hint and label/trailing alignment#422issue #378
  • featurelocale switcher component (and a SiteHeader slot for it)#404issue #349
  • featureprogress: configurable bar height (size prop or --ca-progress-height), currently fixed at 8px#395issue #358
  • featurebutton: neutral/secondary tone for outlined and ghost (no brand-coloured label or border)#389issue #357
  • featurestat: card-less statistic primitive (label, value, hint, trend) sharing KpiCard typography#397issue #359
  • fixglassCard: header padding stacks on card padding, so title is misaligned with content#368issue #341
Overlays and layers10
  • featureinline (non-modal, non-top-layer) mode for overlays, for theme previews and showcases#608issue #563
  • featurelayers.modalOpen store and hover UI that never opens under a modal#553issue #550
  • featurenavigationmenu: disclosure navigation for marketing headers#461issue #457
  • featuremenubar: APG menubar reusing the DropdownMenu items#460issue #456
  • featurecontextmenu: right-click, long-press and Shift+F10 menu#459issue #455
  • featurehovercard: hover and focus preview card with open/close delays#458issue #454
  • featuredialog split: configurable size, columns and mobile full-screen; Dnd: container-target mode without insertion line#428issue #377
  • featuredrawer: chromeless/full-bleed mode (no header, no body padding, full width) for side detail panels#391issue #362
  • featuretoast: optional description and a single inline action (label + handler)#392issue #356
  • fixtoast: mensagens idênticas empilham sem limite (sem dedupe nem teto)#367issue #363
Forms and inputs5
  • featurebutton: form and type passthrough to submit from a Dialog actions footer#523issue #518
  • featureinputgroup: prefix and suffix addons inside the textfield border#450issue #447
  • featureauth building blocks: Turnstile, PasswordStrength, decorative Avatar and SplashScreen#426issue #379
  • featuretextField: password reveal toggle, and a field-level trailing link slot ("Forgot password?")#396issue #348
  • featurechipGroup: single-choice chips that wrap (radiogroup, roving tabindex, check icon)#365issue #355
App shell and layout14
  • fixkanban: board collapses to 0px wide in an auto-width flex wrapper (documented usage), layout audit fails on mobile#531issue #530
  • featurekanban: per-column money total without losing the colour rail, title and count (header snippet replaces all three)#498issue #496
  • fixpageFrame: bottom sheet overflows the viewport by the frame gap on mobile (side=left keeps left:12px with width:100#492issue #489
  • featureResizablePanels: panel group with separator role and keyboard resize#449issue #444
  • fixappShellLayout: a nav item stays active on its sub-paths, so an overview item and a more specific sibling are both#424issue #408
  • featurekanban/Board: columns with cross-column drag, board pan, per-column infinite scroll and mobile stage tabs#393issue #361
  • featuretable: dense mode, pinned columns, group rows and interactive rows for CRM-style lists#372issue #360
  • featurepage frame primitive for map-first apps: floating panel / split / full page, non-modal bottom sheet on mobile, and#402issue #344
  • featureappShellLayout: persistent background stage (map, canvas, video) that survives route changes, with pass-through co#401issue #343
  • featurenavigation: Rail labels and a search/⌘K trigger, plus a BottomNav overflow ("More") for more than 5 modules, with#364issue #346
  • featurepageShell/AppShellLayout: no aside column and panel always open, leaving dead space on wide screens#388issue #342
  • fixpageShell: mobile actions stretch StatusChip/Chip/Badge to full width#366issue #339
  • featureauthLayout: panel side, full-bleed custom panel (live map), header slot and form-first layout#387issue #347
  • fixauthLayout: brand snippet still renders logoName (defaults to "CodeAtlas UI")#369issue #340
Map and charts3
  • fixmap: MapStage on maplibre-gl 6 (critical XSS GHSA-jrc7-96c5-q579): use deck.gl MapLibreOverlay#602issue #601
  • fixmap: deck.gl overlay throws on maplibre-gl 6 (`reading 'height'`) on every frame#479issue #470
  • featuremap integration entry (`@codeatlasdev/ui/map`): Mapbox GL / MapLibre + deck.gl that follow the theme#403issue #345
Sound1
  • feature@codeatlasdev/ui/sound: synthesized, opt-in UI sound engine (phase 3)#577issue #575
Accessibility8
  • fixsettings recipe tells Switch in SettingItem to add aria-label, SettingItem names it#527issue #525
  • fixkanban: column title is a hard-coded h3, so a board under a PageShell h1 fails axe heading-order (no headingLevel p#503issue #501
  • fixfileUpload: drop zone aria-label omits the visible hint (axe label-content-name-mismatch)#493issue #490
  • fixnotificationBell: trigger label does not contain its visible unread count (axe label-content-name-mismatch)#480issue #471
  • fixauthLayout: mobile brand mark fails color contrast in dark at 390px#483issue #472
  • fixauthLayout: flat panel text levels fail color-contrast (--panel-text-secondary/tertiary are alpha mixes of the fg)#442issue #440
  • fixtheme: light brand presets get a --ca-brand-text that fails contrast in the light scheme (~1.6:1)#420issue #410
  • fixtouch-target list in base.css fused into a descendant selector (.glass-btn-clear .ca-tf__reveal)#418issue #417
Agents, MCP and docs22
  • fix14 pages emit the same heading id more than once (e.g. id="props" ×4 on /docs/display/table/)#600issue #548
  • fixglass reads as the lib's identity and default; present it as one style among many#561issue #559
  • fixlanding recipe copied alone fails prerender on /sign-up#528issue #526
  • fixrecipes import #lib which only exists on Kit 3, no Kit 2 form shown#520issue #515
  • fixlanding recipe hrefs 404 on prerender when copied whole#522issue #517
  • fixllms.txt should say components.json is { components: [] }#524issue #519
  • fixmanifest: union prop values missing from props tables (Avatar shape, SkipLink props)#514issue #509
  • fixmanifest: NavModule and NavItem fields missing from components.json/API.md (get_export/get_component)#513issue #508
  • fixlist-page recipe Export link 404s on prerender#512issue #507
  • fixtemplates: Billing uses :global(.grid-auto-180) instead of Grid props (templates are what AIs copy)#511issue #506
  • fixmap recipe offline basemap in the shell-less variant, scatterplot radius units, centsToDisplay options#504issue #502
  • featurepageShell: dev warning when there is no <main> landmark (docs-only agents ship pages without one)#499issue #495
  • fixinstallation says nothing about the TypeScript major Kit 3 needs (ERESOLVE with typescript 5)#500issue #497
  • fixstandalone single-page app (no AppShellLayout) has no recipe; PageShell alone has no <main>#481issue #473
  • fixdocs+lint: i18n-literal rule is undocumented and every recipe fails it; app copy pattern missing#484issue #474
  • fixllms.txt: links point at HTML pages; the readable /llms/<group>/<slug>.txt form and index pages are not discoverabl#482issue #477
  • fixSiteHeader overflows at 390px with nav + LocaleSwitcher + 2 buttons; no marketing-header recipe#485issue #476
  • fixsite build fails on button-group.mdx (escaped quote in a JSX attribute)#463issue #462
  • docsteach the 4.6 APIs in recipes, choosing and workarounds#416issue #415
  • featuredX: official list of stable .ca-* parts, variables and data attributes for overrides (API.md, manifest, MCP)#425issue #381
  • fixgive llms-full.txt a fixed shape instead of a growing byte budget#437issue #436
  • docsmake the site and skill one-shot friendly for AI agents#386issue #384
CLI and migration5
  • fixinit: Kit 2 gets no svelte.config.js with the detected adapter#521issue #516
  • fixfresh Kit 3 app needs svelte-kit sync before svelte-check; init leaves a root <Seo> and PWA-only keys in mess#494issue #491
  • fixinit: Kit 3 scaffold does not build/lint out of the box (tsconfig exclude build, #lib map, PWA optional, prerender,#478issue #475
  • featuremigration tooling: help teams move an existing app (React/Tailwind/shadcn) onto @codeatlasdev/ui#430issue #383
  • featurerelease: migration changelog per major, codemods for known breaking changes and deprecation warnings#429issue #382
Tests and release gates22
  • testreplace the remaining real-time sleeps in component tests with conditions or a fake clock#603issue #568
  • fixbudget of test:changed follows the selection scope#605issue #604
  • fixland gate: bring landShared back under 4 minutes#599issue #598
  • fixtest budget: landShared 360 s cannot hold components ALL + routes ALL + the mandatory rebuild#597issue #596
  • fixroute-impact: a CLI source under packages/ui/bin selects every docs route#594issue #593
  • fixtest:changed --land: dist counts as fresh when it predates the rebase#592issue #591
  • fixissue-land: new generated files (demo e/ and p/ api/v1 files) are never committed#590issue #589
  • testlighter suites: one pass per route, targeted local runs, machine-wide lock, native VRT#547issue #546
  • fixbrowser tests fail intermittently under machine load (Menubar, VirtualList, NestedLayers)#545issue #544
  • fixbring styles/global.css back under 17450 B#543issue #542
  • fixplaywright config picks 100% workers whenever CI is set, so agent shells on a shared machine get random 30s t#540issue #539
  • fixa11y routes spec times out on /docs/getting-started/recipes/ (10k DOM nodes) and on light pages under load#534issue #533
  • fixunit tests leave their temporary project directories behind in the system temp directory#532issue #529
  • fixrender visual baselines locally in Docker while CI is paused#469issue #468
  • fixHoverCard closeDelay and CommandPalette announce tests fail intermittently in the full browser suite#467issue #466
  • fixsite e2e gate fails on carousel, image, text-field and menubar (a11y, mobile overflow, touch targets)#465issue #464
  • fixcheck: bun --check (check:native) reports 10 type errors that validate does not run#443issue #441
  • fixsite build fails: StatusTokens demo canvas role=img (a11y_no_interactive_element_to_noninteractive_role)#439issue #438
  • fixmobile motion e2e flakes in settle, scroll takeover, ripple (#411)#434issue #411
  • fixfull browser suite poisoned by an unresolved site-demo import (#407)#419issue #407
  • choreraise styles/global.css budget for the 4.6 component batch#414issue #413
  • fixcheck:native fails on main: implicit-any in build-manifest.ts and strict errors in two tests#400issue #398
Dependencies3
  • chorebump @vitest/ui from 5.0.4 to 5.0.5 in /packages/ui#435
  • chorebump happy-dom from 21.0.1 to 21.0.4 in /site#588
  • chorebump happy-dom from 21.0.1 to 21.0.4 in /packages/ui#541