Releases
Changelog
Every published version of @codeatlasdev/ui, newest first. Follow it as an Atom feed or JSON.
3.0.0 latest
Major Changes
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:
defaultStyle: "solid",paper: "plain",radius: 0.8(--ca-radius-mdis 8px),iconStroke: 1.75, heading weight 600 (and no extra h1 tracking).- Surfaces, text and outlines carry almost no chroma (the brand hue stays on fills, focus, links and charts); shadows are neutral black (
--ca-shadow-*,--ca-glass-shadow,--ca-mat-*-shadow). [data-style="solid"]and[data-style="flat"]set--ca-glow-primary(-strong)and--ca-glass-rimto transparent and draw--ca-mat-edgeas a hairline, so.glass-tinted,.glass-prominent,.glass-glow,.glass-active,.glass-btn-prominentand the AppShell nav pills lose their halo and rim without a component change. The page mesh (--ca-page-mesh) is drawn only byglassandfrosted.- New recipe
designStyle("liquid")reproduces the former look exactly (glass, mesh, glow, round corners, bold headings, tinted shadows).designStyle("default")now means the sober default.
Migration: to keep the old look, use
designStyle("liquid", { ...yourTheme }), or setdefaultStyle: "glass",paper: "mesh",radius: 1,iconStroke: 2andtypography: { weights: { heading: 700 } }.colorScheme.setStyle("glass")still switches at runtime. Apps that override--ca-*tokens are unaffected; apps that read--ca-radius-*or the shadow tokens get the new values.defineTheme()with no options now resolves the new defaults (designTokensemits nothing for them).
Minor Changes
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.
persistPanel(defaulttrue) now keeps the panel open/closed, the selected module and the section open map inlocalStorage["ca-shell"](the oldca-panel-openkey is migrated), and the panel scroll per module insessionStorage. An inline script emitted right after the sidebar restores them before the first paint and setsdata-ca-panel/data-ca-moduleon<html>, 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's last choice for a section wins overdefaultOpen; the section holding the current page always opens. The shell now starts at its target width instead of animating open on load.persistPanel={false}keeps the old always-default behavior for demos and embeds.3476d9a: Brand assets from one mark.
caBrandPluginandgenerateBrand(@codeatlasdev/ui/vite) render a content-hashed favicon (SVG that followsprefers-color-scheme, ICO, PNG),apple-touch-icon, 192/512/maskable manifest icons and a web app manifest from one SVG with{{ink}}/{{spark}}placeholders and your theme;resolveBrandandrenderMarkexpose the resolved sRGB colors.iconLinksandogCard(@codeatlasdev/ui/seo) emit the head links and the 1200x630 social-card HTML forrenderOgImage.<Seo>now also writesog:image:typeandtwitter:image.@resvg/resvg-jsandpng-to-icoare optional peer dependencies.1eeca1c: Surface engine and layout primitives (#112).
New components:
Box,Flex,Stack,Grid,Container,Section(layout),Surface,CardandSurfacePanel.- Layout primitives take token props (
gap,p/px/py,align,justify,direction,columns,wrap) and a polymorphicas. A prop renders as adata-*attribute (data-gap="4"), a responsive object ({ base: "2", md: "4" }) asdata-md-gap="4", styled bystyles/layout.cssin@layer ca.utilities. No inlinestyle. Surfacehas orthogonal axes:elevation0-5,materialauto | solid | frosted | glass | liquid,tone,variantfilled | outlined | ghost,radius,interactive,glow,as. One.ca-surfacerule reads--ca-surface-*variables.material="auto"follows<html data-style>; the explicit materials look the same under every style. It never setswill-changeorisolation, and it hasforced-colorsand reduced-transparency entries.Card(title,headingLevel,href,flush,full,headerActions) is built onSurface;SurfacePanelis a raised<section>(the AppShell sidebarPanelkeeps its name).- Docs: Surface, Card, Layout primitives;
docs/css-api.mdlists the newca-*classes and the data attributes.
Behavior changes to review in
Stack,GridandRow(they were the only layout components with inlinestyle):StackandGridgapis now a spacing step string (gap="4"is 16px), not pixels.align/justifyusestart | center | end | between | ...instead of raw CSS values.Gridcolsis nowcolumns(1-12, or a responsive object);rowGap,colGapand CSS template strings ("2fr 1fr",repeat(auto-fill, ...)) are gone: writegrid-template-columnsin a class.Rowis deprecated in favor ofFlex. It still renders.ca-rowand maps a pixelgapto the matching step.GlassCardandGlassSurfaceare deprecated in favor ofCardandSurface, and removed in the next major. They render the new components, keep theirca-glass-card*andglass*classes and look the same under everydata-style, and log a[ca-ui]warning in development.
Size:
styles/core.cssgrows from 9.6 to 13.0 kB gzip (limit 9.6 to 13.6 kB),global.cssfrom 12.6 to 15.9 kB:layout.cssis ~2.2 kB (every token prop and step at the base and four breakpoints, generated byscripts/build-layout.ts) andsurface.css~1.2 kB (the five materials by six elevations). JS entries are unchanged.Migration:
bunx @codeatlasdev/ui migrate --write srcrewritesGlassCardtoCard material="glass"(thicknesstoelevation,tintedtotone="brand",glowingtoglow, staticpaddingtop),GlassSurfacetoSurface(refractiontomaterial="liquid"),RowtoFlex, andStack/Gridpixel gaps,colsandalign/justifyvalues. Dynamic or off-scale usages are left as they are, with their import, for you to finish.- Layout primitives take token props (
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 (
primitives/layer.ts).- Modals are native
<dialog>+showModal(); menus, popovers and tooltips arepopoverelements. Both live in the top layer, so stacking no longer depends onz-index: a menu inside a dialog is always on top. The--ca-z-dropdownand--ca-z-tooltiptokens remain for your own in-flow UI but are no longer used by overlays. - Anchored layers use CSS anchor positioning when supported and Floating UI otherwise.
- One Escape / outside-press / focus stack: only the topmost layer reacts, so nested overlays dismiss in order. Focus returns to the trigger on close.
- New
onLayerChange(listener)(and alayerchangeCustomEventondocument) fires on every layer open and close with the open layers and their rectangles, so hosts can park or snapshot native views. - The
.ca-floatingwrapperdivis gone:.ca-floatingis now on the layer element itself (the one carryingdata-state/data-side), and.ca-date__floatwas removed.
- Modals are native
2.3.2 latest
Patch Changes
- 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
sessionStorage(on a sidebar click and onpagehide) and restored by an inline script emitted right after the sidebar, before the first paint; the current page is scrolled into the sidebar'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.
2.3.1 latest
Patch Changes
3280fc2: Theme islands: a
data-theme-scopeinside adata-presetisland now takes that island's preset instead of the page's. Before, the page preset's rule also matched the scope, so a brand preset on<html>(unlayered) always won, and between two built-ins the one emitted later inglobal.csswon. The landing hero demo ignored its preset swatches because of this (#87).Bundle budget:
styles/global.css+122 B gzip (limit raised from 12800 to 12900 B). Each preset's scope selectors (light, dark and theirprefers-contrast: morecopies) now carry the nearer-island exclusion;core.cssstays within its limit.
2.3.0 latest
Minor Changes
1f858d3: The generated API reference now documents every non-component export of every entry point (root,
kit,vite,astro,actions,seo,forms,tokens,table,charts):setLocale,registerLocale,caErrorPlugin,createErrorHandler,nativeFeel,usePageTitle,primeKeyboard, the stores, theseobuilders and the types, each with its signature and a JSDoc summary.components.jsongains anexportsarray,API.mdgains a "Non-component exports" section, theca-ui-mcpserver gains aget_exporttool, and components ofkitsuch asRouteAnnouncernow appear in the component list. The summaries were added as JSDoc, so they also show in editor tooltips.648a1e6: New
.ca-sr-onlyutility (visually hidden, still in the accessibility tree) and.ca-sr-only--focusable(shown again while it or a descendant has focus, for skip links). Apps can now add a table<caption>or an AT-only label without custom CSS. Both live instyles/utilities.css(ca.utilitieslayer), socore.cssandglobal.csscarry them. Documented ingetting-started/accessibilityanddocs/css-api.md.The kit now uses it instead of eight copies of the clip pattern (
Dialog,Select,DocsSearch,Lightbox, the date pickers, and the page titles ofPageShell,AppForm,AppListandAppSettings; theTablecards-mode header row shares the same rule). Visual output is unchanged.Migration: four internal class names are gone because they only existed to hide text. If your CSS targeted
.ca-dialog__sr-only,.ca-sel__sr,.ca-docs-search__sror.ca-lightbox__sr, target.ca-sr-onlyinstead (these elements now carry that class). The page<h1>hidden insideAppShellLayoutbelow 900px now also carries.ca-sr-only--shell.Bundle budget:
styles/core.css+173 B gzip andstyles/global.css+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.19386b1: Add the
@codeatlasdev/ui/testingsubpath:expectNoA11yViolations,checkA11y,formatViolations,settledandA11Y_EXCEPTIONS. It is the library's own axe helper (animations settled,regiondisabled for isolated components), framework-agnostic (takes anElementorDocument), withaxe-coreas an optional peer dependency.a88b8a9:
EmptyStateandGlassCardacceptheadingLevel(2 | 3 | 4 | 5 | 6) to render their title as that heading without changing how it looks.EmptyStateinsidePageShellnow defaults to<h2>(it was always<h5>, which made axe reportheading-orderunder the page<h1>); outside aPageShellit stays<h5>.GlassCardrenderstitleas a<p>unlessheadingLevelis set, so existing outlines are unchanged. New exported typeHeadingLevel.5f7d842: New
KpiGrid: a responsive row ofKpiCards that never leaves an empty cell.Grid cols="repeat(auto-fill, minmax(160px, 1fr))"ignores how many cards it holds, so 3 cards on a 390px phone became 2 + 1 with a hole.KpiGridcounts its children in CSS (:has()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 toauto-fill. Props:gap,class. New classes:ca-kpi-grid,ca-kpi-grid__grid.The docs, templates, demos and agent skill now use
KpiGridfor KPI rows instead of theGridauto-fill recipe.4db46ab: Three patterns for result views, from the DX feedback in #48.
LiveListshows rows that stream in (SSE, WebSocket, bulk jobs): a namedrole="log"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 "N new" jump button) and never moves focus.ExternalLinkis a link to another site withtarget="_blank",rel="noopener noreferrer", a decorative icon and a localised "(opens in a new tab)" for screen readers (externalLink.newTabin the i18n catalog), made for table cells.ExportMenuis an Export button with a CSV / JSONDropdownMenu; the helperstoCsv,toJson,downloadFileand theExportColumntype are exported too (RFC 4180, UTF-8 BOM in the downloaded CSV, spreadsheet formulas neutralised). New i18n keys:externalLink,exportMenu,liveList(en, pt-BR).11809ff:
MoneyInputacceptscurrency(ISO 4217; decimals come fromIntl, soJPYhas none andBHDhas three) andlocale(BCP 47).valueis now the currency's minor unit rather than always cents. Existing BRL usage is unchanged: with neither prop set the field still rendersR$ 1.234,56. Withcurrencyset and nolocale, the locale follows the active i18n locale (I18nProvider/setLocale).centsToDisplayandparseInputToCentstake an optional{ currency, locale }(defaultsBRL/pt-BR),parseInputToCentsreads the locale's decimal separator, andcurrencyDecimals(currency)and theMoneyOptionstype are exported from@codeatlasdev/ui/forms.
Patch Changes
- 5871641: AppShellLayout: the panel/main offset now uses the Svelte 5
Springclass and lands on the exact target (252px, not 251.984px). The extra CSSmargin-lefttransition that lagged the spring is gone, andprefers-reduced-motionskips the animation and jumps straight to the target. - ef7b0fa: CommandPalette: keyboard selection no longer gets stolen by the pointer. A row appearing under a stationary pointer (list renders, dialog animates in) fired
mouseenterand moved the highlight; hover now follows realmousemove. ArrowDown on an empty list no longer stores-1. - 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
mouseenterand moved the highlight; hover now follows realmousemove. A lazyindex()resolving while the dialog is open re-ran the open effect and wiped the typed query. ArrowDown on an empty list no longer stores-1. - 7f5d8f7:
initnow writes the full SvelteKit setup:caErrorPlugin({ scaffold: true })next tocaThemePlugin(theme)invite.config.ts, andhandleError = createErrorHandler()next to the themehandleinsrc/hooks.server.ts(both patched into existing files with--write, each only when missing; your ownhandleErroris never replaced). The installation guide shows one complete SvelteKit setup, the exact filesinitwrites, and states that plugin order inpluginsdoes not matter. - ea072c2: DropdownMenu, DataTable column toggle (and any
FloatingLayerconsumer): closing before the frame-delayed focus move ran no longer lets that move steal focus back from the trigger.FloatingLayer'sonMountedmay now return a cleanup that runs on close/unmount. - ba4c458:
PageShellactions below 900px: a lone icon-only control (IconButton,ThemeToggle) no longer stretches to the full row with its icon centred. Icon-only children (.ca-icon-btn,.ca-rail-btn) keep their natural size and sit at the end of the row; labeled buttons still share the row full-width as before.
2.2.0 latest
Minor Changes
d9087e8: Accessible tokens and states, solved per preset and scheme by the generator (every built-in and brand preset, light and dark):
- Disabled controls no longer fade with
opacity. Button (all variants, including the newdestructiveandglassdisabled 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--ca-text-disabled,--ca-action-disabled-bgand--ca-outline-default. A loading button keeps its enabled look. --ca-text-disabledis now guaranteed >= 3:1 on every surface and on the disabled fill, and documented (comment,tokens.jsondescription, docs) as the label of disabled controls only.lint:tokensflagscolor: var(--ca-text-disabled)outside a disabled selector.- New
--ca-outline-control(>= 3:1 on every surface and on the 5% field tint, WCAG 1.4.11).--ca-field-border-colordefaults to it, so TextField, TextArea, Select, Combobox, NumberField, PinInput and the date fields, plus the Checkbox and Radio boxes, reach 3:1.--ca-outline-subtlestays for dividers. Overridable like any--ca-*token. - New
--ca-semantic-error-contrast, the pair of--ca-semantic-error-fill.Button variant="destructive"reads it instead of a literal white.defineThemederives it from a customcolors.*.error. - Menus, popovers, pickers and panels are near-opaque by default (
--ca-overlay-bg/--ca-panel-bgare 94% of--ca-surface-overlay, the blur is kept), so label contrast no longer depends on the page behind. The old tints are--ca-overlay-bg-glassand--ca-panel-bg-glass. Thefrostedstyle keeps the opaque default. --ca-focus-ringis solved per preset and scheme (>= 3:1 on every surface and on the hover/selected fills) and every focusable component reads it (it was a literal--ca-brand-mainin about 20 components; the date fields now draw a real 2px ring instead of a 40% tint). New--ca-state-indicator(same guarantee) for the switch track, slider range and segmented thumb edge.lint:tokensrejects a literal--ca-brand-mainin a focus rule.prefers-contrast: moreandforced-colorskeep working: the contrast block also lifts--ca-outline-controland--ca-text-disabled.tokens.jsonentries now carry adescriptiontaken from the comment above the declaration.
Migration: apps that doubled the class to patch these (
.ca-btn.ca-btn:disabled,.ca-tf__wrap.ca-tf__wrap,.glass-overlay.glass-overlay,.ca-btn--destructive.ca-btn--destructive, a global:focus-visibleredraw) can delete those overrides.Bundle budget:
styles/core.css+61 B gzip andstyles/global.css+277 B gzip (limits raised to the measured 9639 / 12594 B). The cost is the three solved tokens (--ca-focus-ring,--ca-outline-control,--ca-semantic-error-contrast) and the twoprefers-contrast: morelifts in 16 preset blocks, which carry the guarantees above.- Disabled controls no longer fade with
d9087e8: Component CSS now lives in
@layer ca.components, declared last in the kit's layer order, so the documented contract is true: an unlayered app rule with the same single-class selector (.ca-btn:disabled,.ca-tf__wrap,.ca-tooltip-content,.ca-dialog__body) overrides the kit, with no doubled classes and no!important. Every component<style>block,Dialog/dialog.cssandDate/date.cssare wrapped, and each starts with the full@layerorder statement so bundler chunk order cannot demote the layer. The order incore.csschanged toca.reset, ca.tokens, ca.themes, ca.base, ca.materials, ca.utilities, ca.components. The redundant!importanton 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.ca-btn.ca-btn/!importantworkarounds. Layered app CSS (for example Tailwind@layer components) must be declared after the kit order statement to outrank it.d9087e8: i18n: open locales and request-scoped language (fixes #46).
Localeis nowstring(BCP 47).registerLocale(id, messages)adds any language with a partial dictionary; missing keys fall back to English, and region tags resolve to the base language (es-MX→es).getLocales()andgetMessages(locale, override?)are exported.- New
<I18nProvider locale messages?>: the language lives in Svelte context, so concurrent SSR requests never share it, and it stays reactive to prop changes.useLocale()anduseT()read it from components. setLocaleis 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 useI18nProvider; client-only apps keepsetLocale. A provider always wins oversetLocale.createErrorHandlerno longer reads global locale state. It takeslocale(a tag or(event) => tag) for the fallback message.
Migration:
- SSR (SvelteKit) apps: replace
setLocale(x)in the root layout with<I18nProvider locale={x}>…</I18nProvider>. Before, the server-rendered HTML used the locale of whichever request calledsetLocalelast. - Custom language via
setLocale("en", fullOverride): callregisterLocale("es", dictionary)and uselocale="es"/setLocale("es"). Date and number formatting can now follow the real locale. createErrorHandler(): addlocaleif the fallback message must not be English.- Removed export: the internal global
t()is gone (it was never exported from the package root). Library code usesconst t = useT()at component init.
Patch Changes
d9087e8: Fix the component descriptions in
components.json, theca-ui-mcpserver and the skill'sAPI.md: the manifest no longer takes the first JSDoc found in a component (usually a prop's, e.g.TextFieldread@deprecated Use description instead). Only a detached component-level doc comment counts, and tag-only comments are skipped.bunx @codeatlasdev/ui initnow supports SvelteKit 3: it reads the installed Kit version, never writes the removed$libimport (uses#lib/theme.tsor a relative path), and reports the changes it does not make for you (svelte.config.jsmoved intosveltekit({ ... }),tsconfig.jsonextending$app/tsconfig). The installation guide and the agent skill gain a SvelteKit 3 section and import the theme invite.config.tswith its.tsextension.d9087e8:
Select: an option whosevalueis""(e.g. "All projects") is now a real selection. The trigger shows that option's label instead of the placeholder and drops the placeholder styling, so filters no longer need a sentinel value. The placeholder shows only whenvalueisundefinedor matches no option.onchange("")was already delivered and is now covered by a test. Withrequired, a selected""option counts as a selection.
2.1.1 latest
Patch Changes
- 01b5188:
Steps: a leading<strong>/<b>step title is a block, so its line no longer mixes the bold face with the list item's regular-face strut. That mixed line rounded to a different height between page loads, shifting content below each step by 1px.
2.1.0 latest
Minor Changes
62d18f8: Theme: port a palette by role and pick the page paper, without raw
--ca-*overrides.PresetDefinition.colors: { light?, dark? }takes role colors (brand,background,sunken,surface,overlay,text*,border*,success/warning/info/error). A per-schemebrandregenerates that scheme's brand ramp;backgroundalso drives<meta name="theme-color">. Scoped to the preset, so islands and preset switching keep working.paper: "mesh" | "plain" | "grid" | "dots" | "lines"replaces the brand glow mesh with flat paper drawn in--ca-outline-subtle.- New
sketchbookdesign style: journal on grid paper, serif display, square outlined cards with hard shadows. - Opt-in
@codeatlasdev/ui/styles/marks.css: hand-drawn.ca-mark-underlineand.ca-mark-note(arrow), painted with--ca-mark-color. @codeatlasdev/ui/astrore-exportsdesignStyle,DESIGN_STYLESandmergeTheme, soastro.configcan use a recipe.
2.0.0 latest
Major Changes
dbb7193: bits-ui is gone. Every interactive component now runs on the library's own headless layer (WAI-ARIA Authoring Practices, platform first): native
<dialog>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 anddata-stateattributes are unchanged.- Smaller bundles: AppShellLayout 41.2 → 35.1 kB gzip, form controls −18.7 kB, date family −45.2 kB.
- No more
ssr.noExternalfor bits-ui and no WKWebView dialog workaround needed. Select/Comboboxwithrequiredblock native form submit while empty and focus the trigger.- Disabled
Slidernow renders dimmed (the selector never matched before). - Interaction patterns were designed after studying Bits UI (credited in NOTICE).
Breaking (upgrade notes)
Props, exported types,
ca-*classes anddata-stateattributes did not change. What changed is the DOM around overlays and the dependency set:- Modal overlays (Dialog, AlertDialog, Sheet, Drawer, Lightbox, CommandPalette, DocsSearch) are a single native
<dialog>moved to<body>; the scrim is its::backdrop. CSS that targeted a separate overlay/scrim element or the old portal container no longer matches. Styledialog.ca-overlay::backdropinstead. --bits-*CSS variables anddata-bits-*attributes are gone (--ca-anchor-width,--ca-accordion-height,--ca-collapsible-height,data-ca-dayreplace the ones used internally).bits-uiis no longer installed with the package. If your app imports frombits-uidirectly, add it to your own dependencies.- Select no longer renders bits-ui's hidden
<input>;requiredstill blocks native form submit.
1.2.0 latest
Minor Changes
e5b0fe8: Design tokens v2: every visual dimension is a theme knob, so one component set can render any design language.
- New
defineThemeoptions:controlRadius, numeric or nameddensity(compact/comfortable/airy),border,typography(scale, weights, heading/label tracking and case, line height, measure),elevation(soft/none/hard/glow),motion(scale, easing personality),iconStroke,noise.designTokens()exposes the mapping. - Scales are parametric: spacing = 4px × step ×
--ca-density(new half steps 0.5/1.5/2.5/3.5 and 20/24), radius ×--ca-radius-scale, type ×--ca-font-scale, durations ×--ca-motion-scale(0 under reduced motion).[data-ca-tokens]re-derives them inside an island. - Fluid layout tokens:
--ca-gutter,--ca-section-gap,--ca-content-max, fluid display sizes--ca-font-size-display-1..3. Role tokens:--ca-radius-control,--ca-control-height*,--ca-border-width*,--ca-border-color,--ca-font-weight-*,--ca-heading-*,--ca-label-*,--ca-line-height-*,--ca-measure,--ca-icon-stroke,--ca-noise-opacity,--ca-ease-emphasized. - Material tiers read
--ca-mat-{tier}-{filter,fill,shadow}and--ca-mat-edge. New surface styles:outlined,neumorphic,clay,tonal(alongside glass, frosted, solid, flat). - Design style recipes:
designStyle(id, overrides)andDESIGN_STYLESfor Swiss, Neobrutalism, Neumorphism, Glassmorphism, Material 3, Fluent 2, Apple HIG, Bento, Claymorphism, Y2K, Editorial and Terminal. - Headings read the theme's display family, heading weight, tracking and case; Lucide icons read
--ca-icon-stroke. - One breakpoint scale (600/900/1200/1536) everywhere, enforced by the token lint, which also rejects malformed
svelte-ignorecomments.
- New
e5b0fe8: RadioGroup:
variant="cards"(selectable cards with label anddescription, whole card is the hit area) andaria-label. Option ids are now unique per instance, so two groups with the same values on one page no longer share label targets. CodeTabs: a plaincodetab renders inside the framed.ca-codeblock as a focusable scroll region.e5b0fe8: Remove the legacy theme editor generator:
editorTokensToCSS,editorPreviewCSS,DEFAULT_CONFIGand theThemeConfigtype are gone, with no aliases.Replacement:
defineTheme()+themeCSS()(ordesignStyle(id, overrides)for a recipe) cover every dimension the old hue/chroma/radius/spacing/shadow config exposed, and[data-ca-tokens]+[data-style]+[data-theme-scope]scope a theme to a single element. The theme editor at /theme/ is rebuilt on them: it starts from a design style recipe, edits anyThemeDefinitionkey and prints the exactdefineTheme({ ... })call to paste.Migrate:
ThemeConfig.hue/chroma/lightL→presets: [{ id, color }];radiusBase→radius;spacingBase/paddings →density;fontSans/fontMono→fonts;shadowIntensity→elevation;surfaceStyle→defaultStyle.e5b0fe8: Viewport hardening:
--ca-vh-min(small viewport,svhwithvhfallback) for full-screen sections, so a hero never hides its call to action behind the browser UI.- Focus is never obscured by sticky chrome:
scroll-padding-blockon:rootaccounts for the header, safe areas and--ca-sticky-top/--ca-sticky-bottom. - Prose heading anchors are always visible on touch screens (no hover-only affordance).
- BottomNav labels use an 11px floor.
- SimpleTable
responsive="cards"captions every cell with its column header automatically;data-labelonly overrides. caThemeHandlewarns in dev when the viewport meta blocks zoom (user-scalable=no,maximum-scale=1).
1.1.0 latest
Minor Changes
f7d9a25: Accessibility foundations.
- New
SkipLinkcomponent (ca-skip-link).AppShellLayout,DocsShellandAuthLayoutrender it as the first tab stop and own the#maintarget.DocsShellkeeps itsca-docs-skipclass anddocs.skipToContentlabel, but its<main>id is nowmain(wasca-docs-content): update any CSS or links that targeted#ca-docs-content.AppShellLayoutgetsmainId(default"main"; passnullwhen your page renders its own<main id="main" tabindex="-1">) andskipLink(setfalsewhen the shell is embedded in a page that already has one). - New
announce(message, politeness)backed by one shared live region.CopyButtonnow announces through it (its localca-copy__statuselement is gone), andButton/IconButtonannounce "Finished loading" whenloadingends. NewRouteAnnouncerin@codeatlasdev/ui/kitannounces SvelteKit navigations (document title, or the page<h1>). forced-colors: activesupport: glass, blur and shadows are removed, surfaces get aCanvasTextborder and focus usesHighlight.prefers-contrast: moretokens (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 fromdefineThemeboth get them. Built-in theme CSS is regenerated.Comboboxgets anariaLabelprop to name the control when there is no visiblelabel.
- New
fa63ddd: Developer and agent tooling.
ca-ui-mcp: a local MCP server (stdio) withlist_components,get_component,search_tokensandget_guide, backed by manifests generated from the component source. Works offline.components.jsonandtokens.jsonare generated on every build and exported as@codeatlasdev/ui/components.jsonand@codeatlasdev/ui/tokens.json(props, types, defaults, snippets, publicca-*classes,--ca-*tokens).codeatlas-uiCLI.initwires the theme, hook or integration,global.css,colorScheme.init()and the mobile viewport into a SvelteKit or Astro app without overwriting existing files (it prints a diff;--writeapplies it).migrateis a codemod that renamesBrandPresettoPresetDefinition.- Dev-only
[ca-ui]warnings forIconButtonwithout an accessible name andDialogwithout atitle.
0128aee: Performance foundations.
- New
Imagecomponent: requiredwidth/height(no layout shift), lazy by default,priorityfor the LCP image (eager,fetchpriority="high", preload link),decorativefor presentational images, dev warnings for missing dimensions andalt=""withoutdecorative. defineTheme({ fonts })accepts font descriptors (family,src,weight,display,preload,fallback,fallbackMetrics): the theme CSS emits@font-faceplus asize-adjustfallback face, andthemeHead/caThemeHandleemit<link rel="preload" as="font" crossorigin>. Plain stack strings keep working. New exported typesFontDescriptor,FontFallbackMetrics,FontSpec.- New stylesheet entries
styles/core.css(everything plus the default amber preset) andstyles/presets/<id>.css(one built-in preset each).styles/global.cssstill imports everything. - Internal: bundle-size budget (
bun run size), token lint (lint:tokens), both part ofvalidate. Built-in preset sources moved fromstyles/themes/tostyles/presets/(not a published path).
- New
7abc0f9: Add the
@codeatlasdev/ui/seoentry point: aSeohead component (title template, canonical, robots, hreflang, Open Graph, Twitter, JSON-LD with<escaped), typed JSON-LD builders (organization,websitewithSearchAction,softwareApplication,product,faqPage,breadcrumbList,article,graph),sitemapXmlandrobotsTxt,llmsTxtand amarkdownNegotiationSvelteKit handle for AI crawlers,ogImageRoutefor 1200x630 social images through an injected headless browser, and PWA helpersmanifest()andappleMeta().caThemeHandlenow warns in development when the final HTML has noviewport-fit=coveror nolangon<html>.esm-envis now a direct dependency (dev-only warnings).
Patch Changes
- 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
requestAnimationFrame, and the color re-syncs when the tab becomes visible. - 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.
- c9099ba:
CommandPalette: 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 onkeyup. Also builds with@sveltejs/package3 and TypeScript 6.
1.0.0 latest
Major Changes
ef015e6: 1.0.0: stable API. From here on, breaking changes to props, exports,
--ca-*tokens or the publicca-*classes (seedocs/css-api.md) only land in a major.Breaking
- Default UI strings are English and come from a typed catalog (
en,pt-BR). Portuguese apps callsetLocale("pt-BR")once in the root layout; the wording is the same as before. Text props (label,placeholder,confirmLabel,emptyText, …) no longer have hardcoded defaults: omit them to use the catalog, pass them to override. ThemeOptionsis nowPartial<ThemeDefinition>(defineTheme).BrandPresetis removed: usePresetDefinition({ id, color, label? }).brandPresetCSSoutput changed (theme islands) and the head style id isca-theme(wasca-brand-presets).- CSS is layered:
global.cssdeclares@layer ca.reset, ca.tokens, ca.themes, ca.base, ca.components, ca.materials, ca.utilities. App CSS stays unlayered and wins without!important; override a token by declaring--ca-*on:root. - Page background:
htmlpaints a solid--ca-surface-baseand the decorative mesh moved tobody::before(Safari 26 and the overscroll area sample the root color).bodyis transparent. MobileHeaderandBottomNavare opaque (--ca-chrome-surface) so the browser status bar and toolbar match them.autofocuson touch devices only focuses when the keyboard was primed in the opening tap (primeKeyboard()); pass{ touch: true }to force. This removes the iOS "focus without keyboard" jump.responsiveisundefined-width untilinit()runs (no hydration mismatch); derived values treat it as desktop.- The
.ca-app-scoped 16px input rule and the.ca-input-ios-safeclass are gone: every text field is covered globally.
Theme engine
defineTheme()+themeCSS(): typed theme object compiled once to deterministic CSS: brand presets from one hex, fonts, radius multiplier, density, raw token overrides.- Built-in presets are generated from
theme/builtins.ts(bun run --cwd packages/ui themes;validatefails ifstyles/themes/*.cssis stale). - The init script validates stored preset/scheme/style against what the app knows (an old
ca-presetno longer repaints the app in amber), appliesdata-stylebefore first paint, sets<meta name="theme-color">, follows the OS undersystembefore hydration and replays runtime tokens. - Theme islands:
data-theme-scope="light" | "dark"anddata-preseton any element, nested in any order. colorScheme.setTokens(vars)writes one<style id="ca-runtime-tokens">, persisted and replayed before paint.colorScheme.init()is idempotent and returns a cleanup; storage errors (Safari private mode) no longer throw.colorScheme.presetslists valid ids.
Mobile and WebKit
- Tokens
--ca-safe-top|right|bottom|left,--ca-vh(dvh with vh fallback),--ca-kb-inset,--ca-input-font-size(-sm),--ca-chrome-surface,--ca-bottom-nav-h. viewportstore (on-screen keyboard inset fromvisualViewport, ignores URL-bar and pinch-zoom changes) and thekeyboardAwareaction.primeKeyboard()/focusTextField()(from@codeatlasdev/ui/actions): open the iOS keyboard from the tap that opens a dialog, sheet or search. Used byDocsSearch,CommandPaletteandCombobox.- Browser chrome color sync:
theme-colorfollows the opaque color under the top edge (header, theme island or page) on Chrome/Samsung Android, Safari ≤ 18 and PWAs. - New components:
StickyActions,MobilePane.Sheetswipe-to-dismiss (swipeToDismiss),AuthLayoutmobileBrand. - 44px touch targets for every
Buttonsize, press feedback on touch, no tap flash, 16px minimum on every text field (no iOS zoom),-webkit-text-size-adjust. - Overlays contain overscroll;
backdrop-filterfalls back to opaque surfaces where unsupported. nativeFeel()(kit): keyboard-only focus rings, no browser context menu or selection on chrome,.ca-selectableopt-in, optional zoom lock for desktop shells;html[data-ca-surface="transparent"]for vibrancy windows.
Integrations
- Astro:
caUI(theme)from@codeatlasdev/ui/astroinjects the theme before first paint and configures SSR. CodeTabsacceptstabsas data (for MDX/Astro where Svelte context cannot reach children);CodeSnippetand code tabs follow the scheme of their context through dual shiki themes.
Fixes
- Glass lost its blur in production builds: Lightning CSS (Vite 8) drops
backdrop-filterdeclared before-webkit-backdrop-filter. Order fixed everywhere and guarded by a test. Dialogbody no longer collapses to zero height in WKWebView (Tauri, Safari).Selectno longer putsaria-requiredon the trigger button; required state goes to the form control and the label.isItemActivehandles trailing slashes (trailingSlash: "always"sites).Tooltipmeets AA contrast in both schemes (--ca-tooltip-bg).- New tokens
--ca-focus-ring,--ca-focus-ring-offset,--ca-selection-bg,--ca-scrollbar-thumb(-hover); reduced motion zeroes--ca-duration-*.
- Default UI strings are English and come from a typed catalog (
0.7.1 latest
Patch Changes
- Charts now re-theme on a color scheme, preset or surface style change. Their options read design tokens from CSS custom properties, which aren't reactive, so a theme switch re-applied the previous theme's axis, text and palette colors. Token reads now track the theme store, so every chart option rebuilds with the new values.
0.7.0 latest
Minor Changes
Accessibility and behavior fixes found by the new test suite.
- New tokens
--ca-text-hint,--ca-brand-textand--ca-semantic-error-fill(WCAG AA contrast). Hints, placeholders and brand-colored text now use them. DropdownMenu,PopoverandTooltipaccept atriggerChildsnippet that receives the trigger props, so aButtoncan be the trigger without nesting interactive elements.ButtonandIconButtonforward extra attributes.CheckboxandSwitchacceptaria-labelandaria-labelledby.Switchis now 44×24 px.- Fixed: Combobox popup position and keyboard focus; MoneyInput lost typed input;
use:maskfiredinputtwice per keystroke; DataTablearia-sort, page reset and column toggle;colorScheme.resolveddid not follow OS changes insystemmode; calendar navigation labels in pt-BR; DatePicker popover focus; Toaster, NotificationBell, Select, FileUpload, ScrollArea and SettingItem ARIA issues. - CodeSnippet uses the high-contrast GitHub shiki themes.
DocsSearchresults are real links again (<a role="option" href>), so middle-click, open in new tab and copy link work. A plain click still goes throughonselect.CommandPalettegainsminQueryLength(default1). The query is now trimmed before matching, so" rel "finds "Relatórios".
- New tokens
0.6.0 latest
Minor Changes
Docs primitives for developer documentation sites, mobile-first.
DocsShell: page frame with sticky glass header (brand, section tabs, search, theme), sidebar + "Nesta página" rail on desktop, and ONE compact header on phones (safe-area insets, 44px targets) whose menu opens a bottomSheetwith a section switcher and the sidebar tree. The page index becomes an inline disclosure on small screens. ⌘K / Ctrl+K //open search; skip link included. Props:sections,active,groups,pathname,headings,searchIndex,onnavigate,brand,actions,menuFooter.DocsNav(sidebar tree withMethodBadge),TableOfContents(variant="rail" | "inline", scrollspy),DocsSearch(dialog on desktop, full-height sheet with a 16px input on phones; accepts entries or an async loader) and thesearchDocsranking helper.CodeTabs/CodeTab: language tabs with keyboard support, 44px targets, scrollable strip, copy button, and preference sync across groups (synckey, persisted in localStorage; first paint always renders the first tab so SSR hydrates cleanly).Callout(note, tip, caution, danger; built onAlert),Steps,LinkCard,CardGrid,MethodBadge..ca-prosetypography inglobal.css: headings with balanced wrapping, links, inline code that wraps, tables in.ca-table-wrap(scrollable, focusable), shiki dual-theme code blocks in.ca-code, tight mobile spacing, reduced motion.- Showcase: Navigation / Docs.
0.5.2 latest
Patch Changes
- AppShellLayout: skip the desktop panel for modules with a single destination, so the rail item, panel entry and page title no longer repeat the same label.
0.5.1 latest
Patch Changes
Mobile polish.
- Inputs no longer trigger Safari's focus zoom: TextField, TextArea, Select, NumberField, Combobox, CommandPalette and the DataTable search/page-size fields use
max(16px, var(--ca-font-size-body2))under(pointer: coarse), (max-width: 767px).base.cssadds the same rule for rawinput, textarea, selectinside.ca-appand a.ca-input-ios-safehelper class. Zoom itself is never disabled. - New
Sheetcomponent: bottom sheet (bind:open,title,description,onclose,children,footer) with drag handle, safe-area padding, focus trap, Escape/backdrop close and reduced-motion support; centered dialog from 768px. AppShellLayoutgainsmobileTitlesnippet that replaces the plain page-title text in the mobile header (tappable session title, etc.).AppList,AppSettings,AppForm,PageShell: insideAppShellLayoutthe h1 is visually hidden below 900px (kept for a11y) because the mobile header already shows the title; below 768px the actions wrap under the description, full-width.EmptyStategainsvariant="hero": larger brand-tinted icon tile with glow, bigger title, actions stacked full-width on mobile.- Button: disabled
filledkeeps the brand hue (AA fill + contrast text at 0.45 opacity) instead of looking muddy. AppSettings(mobile): tabs in the horizontal strip were inheritingwidth: 100%and each filled the row; they now size to content, with a 36px touch target, scroll-snap and a trailing edge fade.AppList/useListState: newofLabeloption (default"of") for the word in the auto count and page info; pass"de"for pt-BR ("5 de 5"). On mobile the auto search field now fills the toolbar instead of keeping its 180px minimum.
- Inputs no longer trigger Safari's focus zoom: TextField, TextArea, Select, NumberField, Combobox, CommandPalette and the DataTable search/page-size fields use
0.5.0 latest
Minor Changes
AppShell (mobile): one compact sticky header. New
mobileHeaderActionssnippet onAppShellLayoutrenders at the header's trailing edge (switchers, account menu), so apps no longer need a second bar under the header. The header pads forenv(safe-area-inset-top);BottomNavextends its background throughenv(safe-area-inset-bottom)over an opaque base (no dark band above Safari's toolbar), uses equal shrinkable columns with ellipsised 10px labels so five items fit at 320px, and has 44px touch targets. Apps must setviewport-fit=coverin their viewport meta for the insets to apply.Theme: new
--ca-brand-fill/--ca-brand-fill-hovertokens in every static theme and in generated brand presets.--ca-brand-contrasttext on--ca-brand-fillis at least WCAG AA (4.5:1): white is preferred, the fill is darkened (hue and chroma kept) when needed, and ink text is used only for very light brands.generatePresetnow picks the text colour by real contrast instead of an L threshold; Button/IconButton filled, Badge brand, active Tabs, Pagination, Checkbox, Stepper and the nav badge now use the fill tokens. Disabled filled Button keeps the AA pair at 0.6 opacity.New
Lightboxcomponent: full-screen image viewer (bind:open,src,alt,onclose) with focus trap/restore, Escape, backdrop and close-button dismissal, safe-area insets and reduced-motion support.
0.4.4 latest
Patch Changes
Dark themes:
--ca-brand-contrastis now white. Text and icons on brand fills (primary Button, IconButton, Badge, active Tabs, Checkbox, Pagination, Stepper, PanelNavItem) were near-black on the dark presets. APCA contrast on the dark brand fills: white Lc 70–84 vs near-black Lc 31–45 on every preset (WCAG 2 ratios favor black on crimson/cyan/emerald/ocean/slate, a known WCAG 2 error for mid-tone fills in dark mode).Dialog: new
size="xl"(820px) for two-pane layouts such as settings.Kbd: flat keycap (tinted fill, heavier bottom edge) instead of a glass blur, which rendered as a near-black block on dark surfaces.
0.4.3 latest
Patch Changes
- SvelteKit 3 support:
useBreadcrumbsandusePageTitleread$app/state(the removed$app/storesbroke Kit 3 apps at import), andcaThemeHandlederives itsHandletype from@sveltejs/kit/hooksso it type-checks on Kit 2 and 3. The@sveltejs/kitpeer range is now^2.12.0 || ^3.0.0. Hook return types are unchanged (Readable).
0.4.2 latest
Patch Changes
MobileHeaderfills the width of the mobile shell bar instead of shrinking to its title (the bar background covered only part of the screen).- Tints no longer drift toward purple/pink. Every
color-mixof a token with a neutral (gray tokens such as--ca-glass-tint, black, white) now mixes in OKLab: in OKLCH an achromatic color has hue 0°, so the hue was interpolated between the brand hue and 0°. SelectedChip, destructiveButton/DropdownMenuItemhovers and other tinted surfaces now keep the brand or semantic hue.
0.4.1 latest
Patch Changes
Drawer: overlay no longer renders above the panel. Depending on CSS load order the base.ca-overlayz-index (modal) won over the drawer one and blurred the open drawer.
0.4.0 latest
Minor Changes
Brand themes and brand slot.
caThemeHandle(options)andcaThemePlugin(options)acceptThemeOptions:defaultPreset,defaultSchemeandpresets, brand presets generated from one hex color and inlined in<head>. Before this, the init script always fell back toamber/darkand overrodedata-presetset inapp.html.colorSchemestarts from the configured defaults instead of hardcodedamber/dark.PresetIdaccepts custom ids;BuiltinPresetIdlists the eight shipped presets.- Charts use the brand hex as the first series color for brand presets (they fell back to amber).
hexToOklchandbrandPresetCSS/themeHead/themeScriptexported for custom setups.tokensToCSSdark block now includes the neutral outline/glass/scrim tokens, so generated presets no longer inherit light-mode outlines in dark mode.AuthLayoutpanel mixes brand with neutrals in OKLab: in OKLCH the hue swung toward 0° (blue brands rendered purple).AuthLayoutbrand panel now hides below 768px (the override lost to the scoped rule);AuthCardfooter links use the brand color.ThemeToggle: light/dark button for the AppShell rail, SSR-safe icon.brandsnippet for the logo inAppShellLayout(top of the Rail and mobile header;MobileHeadergainsleading) and inAuthLayout. Both default to the previous "CA" text mark.
0.3.0 latest
Minor Changes
Breaking: charts, DataTable and CodeSnippet moved out of the root import into subpaths with optional peers, so the core install no longer pulls ECharts, TanStack Table or Shiki.
import from install @codeatlasdev/ui/chartsecharts@codeatlasdev/ui/table@tanstack/svelte-table@codeatlasdev/ui/codeshikiMigrate: change
import { LineChart } from '@codeatlasdev/ui'to'@codeatlasdev/ui/charts'(same forDataTable*,Table,createCaTable,createCaColumnHelper,caTableFeatures,PaginationState,SortingState,Updater→/table, andCodeSnippet→/code), then install the matching peer.SimpleTableandUserCellstay in the root.
0.2.2 latest
Patch Changes
- New README: AI-first quick start with
npx skills add codeatlasdev/ui.
0.2.1 latest
Patch Changes
- Charts load a modular ECharts build with only the series and components they use. The lazy chart chunk drops from 1,124 kB to about 650 kB (374 to about 218 kB gzip).
0.2.0 latest
Minor Changes
- Package exports now point to
dist(fixes@codeatlasdev/ui/vitefailing to load fromnode_modules). Adapted to the bits-ui 2 API (Accordiondropscollapsible), swapped deprecatedlucide-sveltefor@lucide/svelte, and exported the sharedIconComponent/IconPropstypes plus TanStackPaginationState,SortingStateandUpdater. The theme plugin no longer warns about FOUC whencaThemeHandleis set inhooks.server.
All notable changes to @codeatlasdev/ui are documented here.
Format based on Keep a Changelog. Versioning follows Semantic Versioning.
0.1.0 2026-09-26
Added
- 60+ components across page templates, navigation, data, forms, charts, overlays, and feedback
- Page templates:
AppList,AppForm,AppSettings,PageShell: complete screen patterns in one component - Navigation shell:
AppShellLayoutwithRail,Panel,BottomNav,MobileHeader,SiteHeader - Layout primitives:
Grid,Row,Stack: replace manual flex/grid entirely - Charts (8):
LineChart,BarChart,PieChart,ScatterChart,HeatmapChart,GaugeChart,RadarChart,Sparkline - Data:
DataTable(TanStack Table v9),SimpleTable,Pagination,KpiCard,EmptyState,FilterGroup - Forms (11):
Button,TextField,TextArea,NumberField,Select,Combobox,Checkbox,RadioGroup,Switch,Slider,FileUpload - Auth:
AuthLayout,AuthCard - Overlays:
Dialog,Drawer,Popover,Tooltip,DropdownMenu,CommandPalette - Feedback:
Toast,Spinner,Skeleton,Progress,ErrorPage - 8 built-in OKLCH themes: amber, ocean, crimson, cyan, violet, emerald, rose, slate: set via
data-presetattribute - Liquid Glass material system:
GlassCard,GlassSurface,GlassAppBar,LiquidGlass - CSS token system:
--ca-*custom properties for color, spacing, radius, typography, z-index - SvelteKit helpers (
@codeatlasdev/ui/kit): theme handle, breadcrumbs, page title, error templates - Vite plugin (
@codeatlasdev/ui/vite): fixes SSR externalization for@tanstack/svelte-table - Dark mode: independent from themes, compose freely (
data-preset="ocean" class="dark") - Svelte 5 runes throughout:
$state,$derived,$props,$effect, no legacy APIs .agents/skills/: AI agent skills for codeatlas-ui, DX, and writing