Skip to content
Component installs need the registry setup
VegaStack Design

Changelog

Release history by design-system version — new and changed components with doc links and commit refs — plus how updates reach you.

Last updated

Release history for the whole system, versioned by the design-system (registry) version; npm package versions are listed per entry. This page is generated from the repo's root CHANGELOG.md — the two can never drift (CI enforces it).

[0.26.6] — October 30, 2026

📦 npm

  • @vegastack/design → 0.9.5 (was 0.9.4).
  • The design-system registry (@vegastack/ui) bumps 0.26.5 → 0.26.6.

🐛 Fixed

  • Settings page tabs keep the list page rhythm (page-header): a header with tabs placed directly in a SettingsLayoutContent sits 12px above the settings below, as it already does in an AppShellPage. 6728ed2

[0.26.5] — October 30, 2026

🛠 CLI & tooling

  • Registry items carry their own version: the release in which their content last changed. A release now restamps only the items that changed, so check-updates reports only real updates. 4edd4ef

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.26.4 → 0.26.5.

[0.26.4] — October 30, 2026

📦 npm

  • @vegastack/design → 0.9.4 (was 0.9.3).
  • The design-system registry (@vegastack/ui) bumps 0.26.3 → 0.26.4.

🐛 Fixed

  • + Filter with every field already a facet (add-filter-menu): with nothing typed and no field left to add, the menu offers just "Advanced filter" instead of "No field matches" above it. 50e16e6
  • Page header tabs keep the list page rhythm (page-header): the tabs row sits 24px under the title and, in an AppShellPage, 12px above the page below — the same title → tabs → toolbar spacing as a ListToolbar's tabs row — instead of 8px under the title and 24px above the page below. No prop change. 50e16e6

[0.26.3] — October 30, 2026

📦 npm

  • @vegastack/design → 0.9.3 (was 0.9.2).
  • The design-system registry (@vegastack/ui) bumps 0.26.2 → 0.26.3.

📚 Docs

  • The vegastack-consume skill updates a component with the full verify → overwrite → verify flow and lockfile-pinned pnpm exec shadcn; the internal Runbook page documents the release flow. 928adf8

[0.26.2] — October 30, 2026

🛠 CLI & tooling

  • pnpm gen runs every generator; the component contract writes the fields its source determines (exports, dependencies, data attributes, wave rosters). b06d1ed

📦 npm

  • @vegastack/design → 0.9.2 (was 0.9.1).
  • The design-system registry (@vegastack/ui) bumps 0.26.1 → 0.26.2.

🐛 Fixed

  • Filter chips: hover and open fill the whole chip (filter-row): an unset ListFacet, PeopleFacet, DateRangeFilter or unset FilterChip drops its dashed line while hovered or open, so the fill reaches the chip's edge like a chip with a value instead of sitting inside the dashes. DateRangeFilterValue takes an optional label the chip reads in place of a custom range's dates ("Due: Last 2 days"), for relative ranges a host keeps as one token. 0a51593
  • Collapsed sidebar: every icon clickable, neutral counts (sidebar, INT-15): in icon mode a SidebarGroupLabel (faded and pulled up over the item above it) no longer takes the click meant for that item, so every rail icon opens and shows its tooltip; the rail's count pill and dot use the app header's count ink (bg-primary / text-primary-foreground) in both themes instead of the blue sidebar-primary accent. 45a8890

[0.26.1] — October 30, 2026

🧩 New components

  • New: CitedList, CitedListItem, Citation (item cited-list) — an answer as numbered points, each a sentence followed by its sources as small muted chips after a real space; Citation is a link by default and takes render (a router link, a play-from-here button) and an optional icon. ActivityFeed takes actions: extra header controls placed before the filter menu. Additive — nothing changes until a consumer opts in. 91ff3d3

🔧 Changed components

  • Nested modals keep the backdrop (dialog, alert-dialog; OVL-23): a Dialog or AlertDialog opened from inside another dialog or a sheet now dims and blurs the page like any other modal — the overlay sets Base UI's forceRender, which otherwise skips a nested backdrop. Popovers and menus are unchanged. 91ff3d3

📦 npm

  • @vegastack/design → 0.9.1 (was 0.9.0).
  • The design-system registry (@vegastack/ui) bumps 0.26.0 → 0.26.1.

⚠️ Breaking

  • List toolbar, round 3 — no compatibility aliases. ListToolbarPopover is one layout for every toolbar menu: on a phone the bottom sheet's rows sit on the title's 16px edge, 8px under it (no empty band); a size="lg" (576px) for a condition builder; ListToolbarDrillHeader (new) is the one ‹ back row for every drill-in (Display › Sort / Columns, + Filter › a field / Advanced filter, a date's Custom range). ListToolbar: the search grows to 320px; search.collapsible is removed (a table's search is the same full field as every list); the tab row keeps 12px to the toolbar row. FilterRow: + Filter and Clear sit right after the last chip inside the scrolling line, and opening a chip's picker scrolls that chip into view; aiNote (+ FilterRowAiNote, the aiAdded/undo/unmatched/dismiss labels) is removed — useAiFilterApplied() toasts what an AI run added, with Undo. DateRangeFilter is the one date filter: presets add Overdue, Tomorrow, This/Last/Next week, Next 7 days and No date (weekStartsOn), shown as the menus' 28px rows with a tick; Custom range drills into the calendar (one month on a phone); a value's from/to are Date | null (open-ended ranges). AddFilterMenu: onAdvanced → advanced (the condition builder drilled into in the same, wider popover — never a dialog); ai.onApplied(result, text) fires after a run that added filters. SortBuilder / GroupByPicker: fields and directions are 28px menu rows (ghost selects), "Add sort" / "Group by" are 14px rows. ViewTabs use the same pill tabs as every page's scope tabs. RouteTabs: no pending bar under a clicked tab (it flashed). DataList: "N columns hidden" is screen-reader only. Inbox: InboxFilters loses the Unread toggle (unreadOnly, onUnreadOnlyChange, unreadCount, unreadLabel); InboxItemAction variant: "primary" (the row's main action) now draws secondary, the rest outline — a row action is never the filled button (also ApprovalCard's Approve; the rule is in Button's docs). useAiFilterApplied takes labels.note (the AI disclaimer line). FileViewer's backdrop dims and blurs like every modal (Dialog, AlertDialog, Sheet, Drawer); popovers and menus keep none. docs · 4827db3

[0.26.0] — October 30, 2026

🧩 New components

  • New: FloatingCellEditor, DropdownMenuInput / DropdownMenuSwitchItem / DropdownMenuAtPoint (item dropdown-menu-form), OptionListEditor. FloatingCellEditor opens a grid cell's editor over the cell, with the page locked behind it and pinch-zoom still passing; DataGrid takes editing="floating" to use it for its custom editors. The dropdown-menu form parts hold a small form in a menu: an input that keeps its keys, a switch row with a hint, and a menu that opens at a point. OptionListEditor edits a choice field's options as rows (hue, name, remove, add). FieldTypeIcon, FIELD_TYPE_ICONS and FIELD_TYPE_LABELS in field-cell give every field type one icon and one name. Additive — nothing changes until a consumer opts in. 55d70fb

🔧 Changed components

  • Grid cell editors that sit on the cell (field-cell, data-grid): with FieldCellEditor variant="cell", long text edits in place on the cell's own surface instead of a second popover that missed the cell; a person is the chosen people as removable chips over the people list, which leaves them out; a date and a date-time's date take the inline picker (FieldInput takes variant="cell" for it). DataGrid's fill handle now sits under frozen cells, so it no longer draws over a frozen column when the grid scrolls sideways. 55d70fb
  • Grid cells without app-side hacks (field-cell, data-grid, app-shell; additive — nothing changes until a consumer opts in): FieldCellDisplay and FieldCellEditor take variant="cell" — the display is plain (no digit grouping except currency, links without scheme, dates clipped on one line, every person named) and the editor is borderless, with a growing one-paragraph text box and a number typed as written (decimal mark of the reader, no steppers, arrows step by 1). DataGrid takes fill (viewport height to the bottom of its scroller, ≥240px), endSpacer, columnDividers (frozen columns too), editingTint, and headerAlign (grid-wide or per column) so a numeric column's header can lead while its values stay end-aligned. New AppShellBleed lets a grid reach the page's edges without a gutter wrapper. 55d70fb

📦 npm

  • @vegastack/design → 0.9.0 (was 0.8.53).
  • The design-system registry (@vegastack/ui) bumps 0.25.105 → 0.26.0.

[0.25.105] — October 29, 2026

📦 npm

  • @vegastack/design → 0.8.53 (was 0.8.52).
  • The design-system registry (@vegastack/ui) bumps 0.25.104 → 0.25.105.

🐛 Fixed

  • Display's Group by spacing: a flush SortableList with no rows draws its footer as the list's first row — no top margin, padding, border or gutters — so an empty GroupByPicker's "+ Group by" sits on the menu's inset under its heading like the rows around it. DisplayMenu's Group by heading keeps a 4px step from the row above it. 451329b

[0.25.104] — October 29, 2026

🔧 Changed components

  • ListToolbar search suggestion: search.suggestion offers one action under the toolbar's search once the query reads as a phrase (minWords, default 3) — a page's plain-language filter. Nothing runs while typing; Enter still searches, ↓ then Enter (or a click) picks the row and hands onSelect the query, and Escape hides it until the query changes. opensFilter opens the filter row once onSelect settles. The field becomes a combobox pointing at a SuggestionMenu listbox. 040c968

📦 npm

  • @vegastack/design → 0.8.52 (was 0.8.51).
  • The design-system registry (@vegastack/ui) bumps 0.25.103 → 0.25.104.

🐛 Fixed

  • Segmented controls, popup search rows and empty lists: a joined outline ToggleGroup (spacing={0}) keeps a pressed item's whole outline at every position — each item draws its four borders and overlaps its neighbour by a pixel, and the pressed one sits on top (Display's Row height, two or three options). PanelSearch's hairline is a divider in the default border colour in every state; its field is caret-only. A SortableList with handle="auto" and fewer than two movable rows keeps no handle column, so a one-key SortBuilder or one-level GroupByPicker starts at the menu's inset (no big gap left of Group by); SortBuilder's header takes the menu rhythm. A DataList with no rows never says "N columns hidden" or how its rows are sorted. 040c968

⚠️ Breaking

  • List toolbar, round 2 — no compatibility aliases. ListToolbar: the search sits at the start (left) and the tools at the end; tabs render in their own row above the toolbar (they fold by themselves); the action prop and ListToolbarAction are removed — a page's main button lives at the right end of the app's top bar on every page type; layout is one icon button that shows, and switches to, the other layout (three layouts step in order; Display's Layout section still picks any); the collapse order is search 240→144, Layout → Display, Sort → Display, search → icon, edit and state → ⋯. ListToolbarPopover, SortMenu, DisplayMenu and AddFilterMenu take the DropdownMenu's rhythm: a 4px inset, 28px rows at a 6px inset, 24px labels, edge-to-edge hairlines; Display's Reset is a menu row. FilterRow: facets and chips are one line that scrolls sideways and never wraps, with + Filter and then Clear at the right end; every control on it is the toolbar's tool height (32px, 28px in a compact toolbar). AddFilterMenu: one "Filter by…" input (the people dropdown's filled field) for field names and plain language; with ai, "✦ Filter with AI: “…”" shows first only for a phrase (two or more words, or a word no field matches). AiFilterInput (+ AiFilterInputProps) and AddFilterAi.placeholder are removed. docs · 040c968

[0.25.103] — October 29, 2026

🗑 Removed / renamed

  • List toolbar release 2 — FilterBar, ViewToggle and DensityToggle are removed, with no compatibility aliases: a list's toolbar is ListToolbar with a FilterRow (its Filter tool), the Layout tool (ListToolbar.layout) and a DisplayMenu (row height). The filter-bar and view-toggle registry items are gone. The facet pieces move into filter-row, renamed: FilterBarFacet → ListFacet (FilterBarFacetProps → ListFacetProps, FilterBarFacetOwnProps → ListFacetOwnProps), FilterBarPeopleFacet → PeopleFacet (FilterBarPeopleFacetProps → PeopleFacetProps), FilterBarFilter → ListFilter; FilterChip, FilterChipProps, FilterFacetReporter, FilterFacetReport, DateRangeFilter (+ DateRangeFilterProps, DateRangeFilterValue, DateRangeFilterPreset) and dateRangeForPreset keep their names — import them all from @/components/ui/filter-row. A facet's data-slot is now list-facet* (was filter-bar-facet*). ListView → DataListView and Density → DataListDensity (from data-list). DataList no longer mounts a view or density switch: onViewChange / onDensityChange fill a ListToolbar toolbar's Layout tool and Display row height; any other toolbar renders as it is. Blocks list-page-01 and board-01 compose ListToolbar + FilterRow + ListFacet. docs · 8a67a7e

📦 npm

  • @vegastack/design → 0.8.51 (was 0.8.50).
  • The design-system registry (@vegastack/ui) bumps 0.25.102 → 0.25.103.

[0.25.102] — October 29, 2026

📦 npm

  • @vegastack/design → 0.8.50 (was 0.8.49).
  • The design-system registry (@vegastack/ui) bumps 0.25.101 → 0.25.102.

🐛 Fixed

  • List toolbar fixes: DisplayMenu's Columns/Fields drill-in scrolls inside the menu (a 24-field table no longer runs off the screen). ViewTabs folded on a phone merges its + and the view's ⋯ into one ⋯, and ListToolbar now counts those buttons and keeps a folded tab dropdown at a readable width, so the view name no longer collapses to "All re…". SortMenu fields take fixedDirection ("asc", "desc" or "none") for a field the list sorts only one way: the direction shows beside the name and the direction group hides while it is chosen. 126da08

[0.25.101] — October 29, 2026

📦 npm

  • @vegastack/design → 0.8.49 (was 0.8.48).
  • The design-system registry (@vegastack/ui) bumps 0.25.100 → 0.25.101.

🐛 Fixed

  • InboxFilters: the Unread toggle no longer sits on top of the last tab. The tabs were an unshrinkable pill list inside a min-w-0 box, so with four tabs (For you · Updates · Snoozed · Archived) in the 384px Inbox sheet the list ran under the toggle and hid most of Archived. The row now wraps: the tabs keep their natural width and Unread drops under them, end-aligned, when both do not fit one line. The tabs use TabsList overflow="menu", so they fold into one dropdown only when they alone do not fit. 5cef805

[0.25.100] — October 29, 2026

🧩 New components

  • List toolbar (release 1 of the list and table toolbar redesign): one header row for every list and table. ListToolbar (new) — tabs left; search, a tight cluster of 32px borderless icon tools (Filter · Sort · Layout · Display · ⋯, 4px apart, tooltips, counts and dots, names that carry state) and the page's main button (action, a ListToolbarAction: filled, labelled on a wide toolbar, an icon + on a narrow one) right; the filter row under it. It measures its own width and collapses in a fixed order — search 240 → 144, tabs fold to a dropdown, Layout then Sort move into Display, search becomes an icon, undo/redo move into ⋯ — and never wraps; every toolbar popover is a bottom sheet on a phone (ListToolbarPopover); / focuses search and F opens the filter row. Also ListToolbarButton, ListToolbarMenu (⋯, renders nothing when empty) and useListToolbar. FilterRow (new): + Filter, pinned facets, chips, Clear in a fixed order, wrapping from @3xl and one scrolling line below it, with an AI note line (count · Undo · words it could not match); it reports its count to the Filter tool. AddFilterMenu + AiFilterInput (new): one input that narrows the fields or, when the page passes ai.onSubmit, sends a plain-language request (no AI code in the DS; no ✦ without ai); fields drill into their pickers in the same popover. SortMenu (new): the one-key sort as two radio groups, directions worded by field type, Reset away from the default; sortSummary(). DisplayMenu (new): Layout and Sort once folded in, row height, group by, a Columns/Fields drill-in with eye toggles and reorder, Show switches, Reset — one surface, no fills. useOverflowFold (new hook). Changed: Tabs TabsList overflow="menu" folds into one dropdown when it does not fit (LAY-14 widened); RouteTabs overflow="menu"; ViewTabs folds through useOverflowFold and takes dirty per view (a dot); SortBuilder rows carry no fill or hover wash, the grip shows only from two keys, "Add sort" sits on the field column and is replaced by "Up to N sorts" at max, a header with Clear (header, labels); GroupByPicker the same row grid; SortableList handle="auto"; FilterChip ai and unset, and unset facets are dashed; SearchInput collapsible and onExpandedChange; DataList fills a ListToolbar toolbar's Layout and Display row height, drops the column header when empty, noResults.onEditFilters; ActionBar docks full width with icon-only actions on a phone; PanelSearch icon; InboxFilters use the small pill tabs. Deprecated (removed in release 2): FilterBar, ViewToggle, DensityToggle. docs · 4825a99

📦 npm

  • @vegastack/design → 0.8.48 (was 0.8.47).
  • The design-system registry (@vegastack/ui) bumps 0.25.99 → 0.25.100.

[0.25.99] — October 29, 2026

📦 npm

  • @vegastack/design → 0.8.47 (was 0.8.46).
  • The design-system registry (@vegastack/ui) bumps 0.25.98 → 0.25.99.

🐛 Fixed

  • RecentStrip's ‹ › scroll buttons and the DocumentLayoutJumpCue pill no longer jump down when pressed. Both positioned their Button with a y-translate (-translate-y-1/2, -translate-y-full), and Button's press nudge (active:translate-y-px) sets the same translate property, so a press replaced the positioning and dropped the control by half (or all) of its height. The strip's buttons now centre with inset-y-0 my-auto (the idiom DatePicker, SearchableSelect and Carousel already use) and the jump cue's band aligns the pill with items-end; no other registry Button positions itself with a y-translate. 0561c7e

[0.25.98] — October 29, 2026

📦 npm

  • @vegastack/design → 0.8.46 (was 0.8.45).
  • The design-system registry (@vegastack/ui) bumps 0.25.97 → 0.25.98.

🐛 Fixed

  • StatGroup and RecentStrip cells are transparent with a border in both themes (they were bg-card: filled in dark, invisible on a white page in light; StatGroup now draws its dividers with borders instead of a bg-border grid). A RecentStrip card's hover is bg-muted and its icon tile turns bg-background on hover, so the tile no longer disappears into the hover fill (same pairing in Inbox item icons); the strip's scroll buttons keep a visible border and foreground chevron in dark. 007d163

[0.25.97] — October 28, 2026

🧩 New components

  • TextEdit highlights text: the bubble menu's Highlight, Mod Shift H or typing ==text==. VegaStack Markdown stores it as ==text==, with an optional colour right after it (==text=={green}: yellow, green, blue, purple, pink or red — every colour reads, renders and round-trips; the editor makes yellow). MarkdownView renders it as <mark data-color> on the colour's tag-*-subtle fill (the prose recipe's new mark rule, kept in print), and typedFirstLine and the new highlightText read it as plain text. text-edit-grammar adds readHighlight, highlightMarkdown, HIGHLIGHT_COLORS and HIGHLIGHT_SUFFIX_SQL. A literal == run that could open or close a highlight is now written \=\=. docs · ba95612

📦 npm

  • @vegastack/design → 0.8.45 (was 0.8.44).
  • The design-system registry (@vegastack/ui) bumps 0.25.96 → 0.25.97.

[0.25.96] — October 28, 2026

🧩 New components

  • RecordPicker browses folders: pass a browse loader (one folder's contents, null for the top) and mark folder rows folder: true, and the empty search box walks the app's folders — a folder opens instead of being picked, a breadcrumb (or Backspace in the empty box) climbs back, and typing still searches everything. With multiple, selectAll adds a "Select all" for the rows on screen; kindFilter={false} hides the kind filter. RecordPickerDialog takes a counting confirmLabel ("Add 3"), a cancelLabel, and an async onConfirm that keeps the dialog open and busy until it settles (false keeps it open); its confirm is off while nothing is picked. docs · 5725c61

🔧 Changed components

  • AgentHoverCard shows the agent's lastAction (its latest progress line, under the state), and its body ships on its own as AgentCard. PersonHoverCard takes a host's card through PersonHoverCardProvider (keyed by the new optional Person.id), so an agent's name in comments and activity opens the same agent card. docs · 0530a15

📦 npm

  • @vegastack/design → 0.8.44 (was 0.8.43).
  • The design-system registry (@vegastack/ui) bumps 0.25.95 → 0.25.96.

[0.25.95] — October 28, 2026

📦 npm

  • @vegastack/design → 0.8.43 (was 0.8.42).
  • The design-system registry (@vegastack/ui) bumps 0.25.94 → 0.25.95.

🐛 Fixed

  • DataGrid keeps a long primary value readable beside the row's Open button. The button and an editable value sat side by side as inline boxes, so a value long enough to fill the column overflowed the cell by the button's width, and the cell's ellipsis then replaced the whole value — its ref or leading content included — with a lone "…". The button and the value now share one flex row (data-grid-cell-lead), the value takes the width that is left, and a long value ends in its own ellipsis. docs · 025f625
  • A DataList numeric column (a date, a count) keeps its whole width on a narrow list's line meta line, like an end-aligned or mono one: the text values beside it truncate first, so "1w ago" never clips to "w ag". docs · 78b59e3

[0.25.94] — October 28, 2026

📦 npm

  • @vegastack/design → 0.8.42 (was 0.8.41).
  • The design-system registry (@vegastack/ui) bumps 0.25.93 → 0.25.94.

🐛 Fixed

  • A long text whose first line ends in a hard break (Shift+Enter) no longer shows a trailing backslash at rest in a grid cell or a FieldProperty row: typedFirstLine drops the break's marker (\ or two trailing spaces) when it takes the first line. docs · 61dc419

[0.25.93] — October 28, 2026

📦 npm

  • @vegastack/design → 0.8.41 (was 0.8.40).
  • The design-system registry (@vegastack/ui) bumps 0.25.92 → 0.25.93.

🐛 Fixed

  • FieldProperty values sit where a property's value starts. A read-only number, currency, percent or calculated value no longer slides to the far end of the row: FieldCellDisplay takes a new align prop ("start" | "end"; by default a number keeps the grid column's end alignment), and FieldProperty passes align="start". An open editor now fills the value column instead of shrinking to the value, and its box hangs over the line as the resting value's does, so the row keeps its height entering and leaving edit and the text stays put. A long text shows its first line as typed — the editor's Markdown escapes (\#, 1\.) undone and mentions by name — at rest, in a grid cell and in a property row alike; only the editor shows the source. The new typedFirstLine and mentionLabels helpers in text-edit-grammar do it. A date-time editor keeps its date and time on one line where they fit, and the time no longer clips "PM". docs · 3b7eea8
  • MarkdownView's find tint is a plain hoisted style instead of Tailwind ::highlight() variants, so a consuming app's Tailwind build no longer warns "'highlight' is not recognized as a valid pseudo-element" (Turbopack's "1 Issue" badge). The matches paint exactly as before. docs · 3b7eea8
  • An embedded DataGrid inside a TextEdit page (a live table block) keeps its pinned column pinned while it scrolls sideways. The editor's column-resize rule set position: relative on every td/th beneath it, which beat the frozen cells' sticky. It now reaches only the editor's own table cells, as does its table-fixed. The prose recipe's table rules (borders, padding, header ground) also leave alone anything under [data-prose="off"], which DataGrid's root now carries, so a grid in a page's prose draws exactly as it does on a table page: its pinned header stays opaque and its cells keep their own padding. The read view's pinned column already stayed put. docs · b75815b

[0.25.92] — October 27, 2026

🧩 New components

  • FieldProperty (in field-cell): one field as an editable property row inside a PropertyList, for a row peek, a row page, or a customer's or project's custom fields. The name sits on the left. The value sits on the right, drawn exactly as the grid draws it (FieldCellDisplay), with no box at rest and a quiet wash on hover. A click, Enter or a typed character opens the grid cell's own editor (FieldCellEditor) in place: text, numbers, URLs, emails and phones inline, long text growing in place, choices on the in-cell option list, dates on the calendar, and a link on the record picker. A checkbox toggles where it is. A value that is itself a link (URL, email, phone, record chips) stays followable, and an Edit pencil beside it opens the editor. Files show and never edit here. Read-only types, readOnly and disabled never edit and show no hover. focusMode="managed" with editing and onEditingChange lets a host's roving focus own reachability and open the editor. onSave returns a promise: the value shows at once, a spinner follows after 300ms, and a rejection rolls back with a Retry toast, as EditableCell does. conflict shows "Someone changed this field." under the value. An empty value reads "Empty" (emptyText). On a phone the value goes full width under its label. FieldCellDisplay takes a label that names a checkbox. docs · 1fc6977

🔧 Changed components

  • TextEdit takes inset="none" so it can sit flush with a ghost Input or Textarea title above it. Inside a Dialog, Sheet, Drawer, Popover or Card whose padding is narrower than the block grip needs, TextEdit adds start padding (about 10px in a Dialog) so the ⋮⋮ grip sits before the text. That pushed a New task dialog's "Add a description…" right of its title. With inset="none" the text, placeholder and caret start at the editor's own edge, and the block grip is not shown. The block menu still opens from ⌘/ or ⇧F10, and on touch from a long-press (implied, whatever touchBlockMenu says); ⌘⇧↑ and ⌘⇧↓ still move blocks, and tables keep their grips. The default, inset="auto", is unchanged. docs · 507578d

🛠 CLI & tooling

  • vegastack-design check-updates and the registry verification behind add retry transient registry failures before reporting the registry unreachable. A network error ("fetch failed"), a response cut off mid-read ("terminated"), a 5xx or a 429 gets up to three tries, with jittered exponential backoff, a 15-second timeout per try and Retry-After honoured (seconds or a date, up to 5 seconds). Item fetches still run at most eight at a time. A 404, a 401/403 or an integrity mismatch is still an answer and fails at once. This stops --fail-on-update from failing CI on a passing network flake. docs · 103f084

📦 npm

  • @vegastack/design → 0.8.40 (was 0.8.39).
  • The design-system registry (@vegastack/ui) bumps 0.25.91 → 0.25.92.

🐛 Fixed

  • ViewTabs stays as tabs while they fit. It used to switch to its narrow Select below a fixed @3xl container width, so in a FilterBar leading slot a table page's two short tabs ("Grid", "Board") collapsed to "Grid ▾", with the + floating far right, once the facet row opened. Now it measures its tab row against the room it has: it collapses only when the tabs don't fit, and goes back to tabs as soon as they do. The collapsed select keeps its + and ⋯ beside it. The root is no longer a size container. DataGrid shows a quiet trailing edge shade while more columns sit past the end of a sideways-scrolling viewport (an embedded block on a phone, for one), and the viewport uses overscroll-x-contain, so a sideways pan stops at the grid instead of moving the page. docs · c140a1d

[0.25.91] — October 27, 2026

🔧 Changed components

  • CommentThread, CommentItem: Resolve moves from the thread header to the first comment's action row — a ✓ hover action before add-reaction, shown on hover or focus and always on a touch screen — through CommentItem's new onResolve. The header now shows only when it has something to say: the quote, "Original text was removed", or "Resolved by …" with Reopen; a page-level thread (no quote) no longer opens with an empty band above the author line. docs · 4f98ad7
  • FieldCellDisplay, FieldCellEditor, FieldInput, DataGrid: a choice value is drawn as a Badge filled in its option's hue — no outlined pill — in a cell, a phone card, the editor's list and a form trigger, through the new exported FieldChoiceBadge (hue + Badge's props), still two then "+N" with every name on hover and keyboard focus. A choice cell's editor no longer puts a field with a pill and a chevron inside the cell: the cell keeps its badges where they were, and the option list opens under it at once with the search field first, each option a badge with a check on the chosen ones and "Add “…”" when onCreateOption is given. A single choice commits on the pick; a multiple choice now toggles with the list open (it used to commit and close on the first pick) and commits on Tab, on Enter with no option highlighted, or when the list closes; Escape cancels. DataGrid no longer shows the fill handle on a cell being edited. docs · 9d6ec9a
  • VersionCompare: the host can choose the side a phone shows — defaultPhoneView ("list" by default) or controlled phoneView with onPhoneViewChange, which reports "preview" on a pick and "list" from the back button. A "View changes" link to one checkpoint passes defaultPhoneView="preview" with selectedId and showChanges, so a phone opens on that version's changes instead of the list. Nothing changes when none is passed. docs · 5497cf6

📦 npm

  • @vegastack/design → 0.8.39 (was 0.8.38).
  • The design-system registry (@vegastack/ui) bumps 0.25.90 → 0.25.91.

🐛 Fixed

  • Row states, empty values, phone board lanes and inline chips (Regent polish audit).

    • DataList and DataGrid use one set of row states. Hover is a faint muted/30 wash with no start edge. A ticked row keeps the stronger muted/50 wash. The open row (activeRowId) gets the accent wash and the 2px start edge. The edge used to follow the mouse, because DataList's pointer-driven cursor drew it; now only the keyboard cursor and keyboard focus draw it.
    • DataGrid pinned (frozen) cells now take a ticked row's wash. They checked data-selected="true", but rows set data-selected="". The phone card view has the same fix.
    • DataGrid shows an empty primary value as a muted "Untitled" in the primary cell and in a card's title. The new untitledLabel prop overrides the word. Cards leave out fields that have no value, label included.
    • Board on a phone with lane tabs: each lane header drops the title and count that its tab already shows, and keeps only its actions.
    • InlineChip (mentions, file and record chips) never wraps. Its icon and label stay on one line, and a chip wider than its line ends in an ellipsis.
    • DataList and DataGrid can show a sort set somewhere else, such as a toolbar. Pass sort without onSortChange and the headers are no longer sort buttons. The sorted column still shows a read-only ↑/↓ (with its position number in a multi-sort) and keeps aria-sort. SortableHead also draws the read-only glyph when it gets a direction and no onSort.
    • Board card and lane menus and SortableList item menus use ⋯, the same as table rows. The app-shell header's actions menu keeps ⋮.
    • SortableList takes handle="fine": the drag grip ⠿ shows only on a mouse or pen. On a touch screen there is no grip, and a long-press still lifts the row or tile. The handle stays in the tab order and comes back into view while it has keyboard focus. The default, handle="always", is unchanged. 1d94395
  • FieldCellEditor: a choice cell's list puts focus in its search field as soon as the list is there, so the first keys typed after opening the editor search and choose instead of landing nowhere. docs · 0d6a462

  • FilterBuilder, GroupByPicker: "Add group" no longer means two things. FilterBuilder's nested-condition button reads "Add condition group" (new labels.addGroup), and GroupByPicker's add button reads "Group by" while no level is set and "Add level" once one is (new labels prop, GroupByPickerLabels: groupBy, addLevel). docs · 0d6a462

  • Enter that confirms an input-method (IME) candidate no longer submits or commits early. The Enter that ends an IME composition can arrive with isComposing already false and keyCode 229, and Enter-to-submit only checked isComposing. It now checks both, in Textarea submitOnEnter, ChipInput, PickerPanel search, TaskInline, EditableCell, useInlineEdit, SearchInput onValueCommitted, DocumentLayout find, Transcript search and speaker rename, the AgentPanel reply and instructions and the DelegatePopover ⌘Enter send, TextEdit (⌘Enter submit, block exits, the bookmark field, find and replace), the editor suggestion menus (suggestionMenuKeyDown, used by TextEdit and TemplateEditor), BlockView's ⌘Enter, and the search-palette block's ⌘Enter. DataGrid, FieldCell and InlineChip already checked both. 4f4da52

⚠️ Breaking

  • ViewTabs: the + menu offers just Grid view and Board view, each with its icon, instead of "Shared" and "Personal" groups of both. onCreate is now (kind) => void — it takes the kind alone; the host's own create step names the new view and decides who sees it (an "Only visible to me" switch). canCreateShared and the createShared / createPersonal labels are removed. Migrate onCreate={(kind, scope) => …} to onCreate={(kind) => …} and drop canCreateShared. The tab row's shared and personal grouping ("Mine") is unchanged. docs · 9d6ec9a

[0.25.90] — October 27, 2026

🧩 New components

  • RecordPicker and RecordPickerDialog: pick one or several records of the kinds the app passes in — a search over the app's loader (debounced, abortable, paged), a kind filter, recents while the box is empty, results grouped by kind, rows refused with a reason; value holds records so earlier picks survive any search, Enter never picks a row from a superseded search, and the dialog starts from value each time it opens. DestinationPicker: choose where something goes — spaces at the top of a folders-only FolderTree, folders loaded as they open, recent places, a folder search, and destinations refused with a reason that is shown and announced. FieldTypePicker: a searchable, grouped list of field types with their icons; a type with next opens a second step (renderStep(type, { back, setTitle })) under a back header. FolderTree picker mode: a node's disabledReason keeps the row visible and openable but aria-disabled with the reason as its description. SearchableSelect renderTrigger: your own trigger element (a RecordChip) over the same picker. SelectContent and ComboboxContent take loading, error and onRetry (API-38). ItemGroup variant="flush" for rows in a card (API-37). Input credential={false} keeps password managers off a name field (FRM-17). TabsTrigger, Checkbox, RadioGroupItem and Switch reach a 44px hit area on a coarse pointer (A11Y-24 widened). PersonAvatar: an agent with a photo keeps a purple bot badge. docs · f88c403
  • RecentStrip: one row of the records a person opened last — up to max (6) compact cards (icon tile · title · one meta line) in a sideways strip with edge fades, ◂ ▸ buttons on a mouse and one Tab stop with ←/→/Home/End; a single line of chips under 28rem of its width (a phone). collapsible (with open/defaultOpen/onOpenChange), loading (same-shape skeleton), emptyState (otherwise an empty strip renders nothing), linkRender for the router's link and headingRender for the heading level. SidebarMenuFlyout: a sidebar row whose entries open in a menu beside it on hover or press — Spaces in the collapsed icon rail; railOnly keeps the expanded sidebar's inline group, and badge/badgeLabel work as on SidebarMenuButton. AppShellHeader mobileActions: the phone bar's own tools (Search, the Inbox with its count) before actions, below 768px only, with a 40px sidebar trigger beside them; AppShellHeaderMobileAction is one such 40px button with a count pill (count, countMax 99, countLabel) or a dot. AppShellFold place="sidebar" | "menu" folds the drawer's footer entries into the profile menu on a phone. AppShellSidebar railSeparators rules a hairline between groups in the collapsed rail. All additive: without the new props the shell renders as before. docs · ec6227e
  • DocumentLayout aside: DocumentLayout aside="discussion" | "outline" | null gives a page one non-modal rail after the text, and DocumentLayoutAside is the rail — a heading with its count, a × (onClose), and tabs ({ value, label, count?, content }: Open · Resolved · Page, Review while a review is open), the panel scrolling inside a rail as tall as the window. Any aside, even null, keeps the end track from the L tier, so opening the rail never moves or narrows the text (in measure="wide" the text and an open aside share wide's min(100%, 1200px) block, the text keeping its start edge). Below the L tier the discussion is a bottom Sheet, the whole screen below 640px; kind="outline" pins the contents from the XL tier, where the contents rail's hover ticks step aside. An open discussion takes the margin's place. Without aside nothing changes. Type rename: the native aside props type of DocumentLayoutContents/DocumentLayoutMargin is now DocumentLayoutColumnProps (DocumentLayoutAsideProps is the new part's props). useDocumentLayoutTier also accepts a ref to a part inside the layout. CommentMargin quote={false} hides each card's quote beside its highlight (default unchanged). VersionList groupBy="day" groups the rows under a heading per day. VersionCompare (new): a document's history at full size — the selected version or its changes (DiffView) beside the day-grouped list on resizable panels, with actions (Restore this version, Copy) pinned; on a phone the list first and the preview pushed in. Dialog DialogContent size="fullscreen": the viewport less a 16px inset (none on a phone), header and footer pinned, body scrolling (OVL-16 widened). docs · 4c4d25c
  • FieldTypeLinkStep (new): the "Links to" step of FieldTypePicker, rendered from its renderStep for the link type. kinds (FieldLinkKind[] — { id, label, icon?, group?, description?, keywords?, disabledReason?, table? }) list in their groups (groupOrder), each with one line on what a link to it does; a kind marked table (Rows) asks which table (tables, FieldLinkTable[]; tablesLoading) in a third view and retitles the picker's step header through controls.setTitle (tableTitle, "Which table's rows?"). The choice comes back through onSelect as { kind, table? } (FieldLinkChoice); value checks the current one. Backspace in an empty search goes up one view. DataGrid: with phoneLayout="cards", a summaryRow shows above the cards as one read-only line — its label, then "Column · Function result" for each summarised column, wrapping on a narrow screen (no change for grids without one). docs · 264ec0e

🔧 Changed components

  • Board, DataList and SectionRow: a lane, section or hidden-lane count above 99 shows "99+" (the cap the notification bell and the Inbox already use), while its spoken label keeps the number the host passed (countLabel, sectionCountLabel); displayCount(n) in data-table-parts formats it. DataList nests rows one level deep with getRowParentId (a sub-task under its task, in its parent's section, stepped in), folded through collapsedParents / onCollapsedParentsChange, with the toggle left to the host through the cell context's nesting. docs · be76d4a
  • AppShellProgress: a 2px indeterminate bar along the top of AppShellContent, shown at once while a page loads. motion-reveal (design-tokens utility): a loading placeholder that waits --reveal-delay (150ms) before it fades in — a fast load never flashes a skeleton, and with AppShellProgress a slow one is never a blank page. docs · 774e258
  • FilterBar: leading puts content before the search on the tools' row — a table's ViewTabs — taking the room the tools leave and scrolling sideways when squeezed; on a phone-width bar it keeps the first row with the tools and a search field moves below them. searchMode="icon" (default "field") shows the search as a Search button that expands into the field when pressed; the field stays while it holds a query, and an empty one folds back when focus leaves it or on Escape, with focus back on the button. On a phone-width window (below the tablet layout tier) the View menu opens as a bottom Sheet instead of a popover. docs · c482b50
  • Textarea takes variant ("default" | "ghost") and size ("default" | "lg"), as Input does, and submitOnEnter (decision API-40). variant="ghost" is borderless and transparent, starts at one row and grows with its text; size="lg" is the heading size — <Textarea variant="ghost" size="lg" submitOnEnter /> is a big title that wraps. With submitOnEnter, Enter submits the form (requestSubmit) and Shift+Enter adds a line; IME composition is left alone. Existing textareas are unchanged. docs · 58a6452

📦 npm

  • @vegastack/design → 0.8.38 (was 0.8.37).
  • @vegastack/design-tokens → 0.8.38 (was 0.8.35).
  • The design-system registry (@vegastack/ui) bumps 0.25.89 → 0.25.90.

🐛 Fixed

  • Calendar makes today easy to see (decision A11Y-25). Today used to be bg-muted, the same grey as hover and the range middle, so it was almost invisible in the neutral theme, dark mode most of all. An unselected today now has no fill: a thin border-foreground/40 outline and medium weight. A selected today keeps the normal filled selection, and range start, end and middle look as before. Today's day button also carries aria-current="date". Every date picker built on Calendar picks this up. docs · 33b06d4
  • RecordAsideSection, SectionToggle, AgentPanel and ShareDialog no longer paint an expanded section's header grey. A ghost Button paints aria-expanded:bg-muted to say a menu or popover is open, but on a section that expands in place, expanded is the resting state, so every open section (the task page's "Subtasks 3" header, for example) looked pressed. The fix lives in a new lib, @vegastack/disclosure-trigger. Put its disclosureTriggerClasses on any ghost Button that toggles a section in place. It clears the fill while the pointer is elsewhere, and hover still shows. docs · 14fb0d4
  • MakeTaskPopover fits the window: it is never taller than the room the window leaves (--available-height), the lines scroll between the heading and the actions, and Create and Cancel stay in view however many lines there are (up to 50). Its title inputs stop at titleMaxLength characters (new, default 500), so an over-long title can't be typed. docs · ed5ecda
  • RecordPeek: the "2 of 3" position stays on one line and never shrinks, so it no longer wraps beside the arrows on a phone. e0b3bd2

[0.25.89] — October 27, 2026

🧩 New components

  • AgentPanel and DelegatePopover (new): agents as collaborators on a record. AgentPanel is the agent's rail card (variant="card") or a peek's collapsible section (variant="section", open unless done or stopped, its header a one-line summary): the agent's bot, name and "Agent" label, "Acting for Priya · Read & write", a ⋯ menu (View agent · Change agent · Stop · Remove, each only when its handler is passed), and a state of none, queued, working, question, approval, failed, blocked, done or stopped with that state's line and actions (Stop; one-tap answers and Reply…; Approve/Decline; Retry/Remove; Reconnect; Mark task done; Delegate again), the live progress line with elapsed time, the queued hint, and a muted "Agent since disconnected" that never turns a finished card back into Needs you. Below: Instructions with ✎ edit in place, Latest (up to three messages and changes, Show all), a Reply box (Cmd/Ctrl+Enter, a disabled reason) and History (runs with View and Undo, Undo disabled with its reason). State changes are announced politely. The same file adds AgentSlot (the one-line Delegate property, its "Delegate to an agent…" ghost and its muted can't-delegate reason), AgentChip (the pill for a peek or phone), AgentStateBadge (icon and word), AgentStateRing (amber while it needs you, red when it failed) and AgentHoverCard (acting for, access, state, View agent). DelegatePopover is the one Delegate popover for tasks and rows: search the viewer's agents (arrow keys and Enter; an agent that can't take it is disabled with its reason), optional instructions, a "When finished" choice for tasks, "Connect an agent ›" and Delegate — with a bulk title, the no-agents empty state and loading rows. Every string comes in through labels, with English defaults. docs · bd32fd1
  • BlockHeader (new): the one header row of a live block inside a page — a table view, a task list. BlockHeader lays out icon · title · its controls (children: BlockHeaderButtons, small quiet buttons to use as the render of the host's View ▾ and Filter triggers) · a ⋯ menu (menu items, menuLabel) · open (a BlockHeaderOpen: ↗ Open, a real link via href or a router link as render, with a label). Before the block has a source, picker takes the title's place ("Pick a table…") so a block inserts at once and is configured where it sits. On a narrow block the controls wrap under the title and Open folds to its icon. docs · bd32fd1
  • CommandSearch (new): the search palette, results first. CommandSearch (a CommandDialog + non-filtering Command) with pending and preview; CommandSearchInput runs a thin progress bar under the input while pending; CommandSearchScopes keeps the scope chips on one line — what does not fit (or past maxVisible) goes into a More ▾ menu, with "Include archived" (includeArchived, onIncludeArchivedChange); CommandSearchResults caps the results at min(60dvh, 520px), dims and marks them busy while pending, and shows preview beside them from 1280px; CommandSearchSections renders Recent · Create · Places in that order. Pair with useAsyncSearch, which drops out-of-order answers. The command-search-01 block now builds on it (its palette is renamed SearchPalette, and its search call gets { includeArchived }). docs · bd32fd1
  • TextEdit ergonomics and BlockToolbar (new). BlockToolbar (aria-label, reveal: "always" | "hover", placement: "corner" | "inline"), BlockToolbarButton (label + Button props, 28px, tooltip) and BlockToolbarMenu (label, items) are the one place a block's controls sit — top-right, four at most, the rest in ⋯; the code block (TextEdit and CodeBlock), the image toolbar (Replace, Caption, Alt text, Open; Download and Copy link now in ⋯) and the diagram header use it, all at 28px. TextEdit gains bubbleActions ((context: TextEditBubbleContext) => ReactNode), toolbar ("bubble" | "dock" — a 44px bottom bar on a coarse pointer) and touchBlockMenu ("handle" | "longPress" — no grip on touch, a long press opens the block menu sheet); the bubble also offers Make task(s) for selected checklist lines with onMakeTask, and stays inside the text column (wrapping onto a second row when narrower). onImageUpload / onFileUpload get onProgress (TextEditUploadOptions): a bar on the image placeholder, a ring on the file chip. ⌘↵ or ↓ on the last line leaves a code block or a diagram's source for the line below, with a footer hint; a new Diagram starts with one comment and draws "Nothing to draw yet" (MermaidBlock emptyMessage). The block grip keeps its 16×20 glyph with a 24×28 hit area, and the find bar floats over the text instead of pushing it down. Breaking: codeBlockActionsClassName is removed (use BlockToolbar). docs · bd32fd1
  • FileViewer carries a file's discussion: rail={{ details, comments, activity }} adds a column beside the stage with a tab per slot given (a bottom sheet below 768px), opened by a header Details toggle — railOpen / defaultRailOpen / onRailOpenChange, railTab / defaultRailTab / onRailTabChange, railLabels. header={{ share, watch, menu }} puts Share and Watch before Download and the ⋯ menu after it. Every slot is a node or a function of the open file. mode="page" renders the same viewer in place as a file's page (no dialog or Close, the page's theme, the name as its h1). onOpenChange is now optional (needed only for the overlay). Existing overlay usage is unchanged. docs · bd32fd1
  • FormulaInput (new): a calculated field's formula on one line. fields (FormulaInputField[] — { key, label, type? }) are offered when you type { and inserted as chips; * and / read as × and ÷ (operators); the value is text with {{key}} tokens. Under the input, preview shows the first row's result ("First row = 1,240", previewLabel) and error takes its place, marks the input invalid and is announced politely; invalidFields marks a chip invalid. Built on TemplateEditor, which gains triggers ("@" | "{{" | "{", default ["@", "{{"]) and pickerLabel (default "Insert placeholder"). docs · bd32fd1
  • HelpMenu (new): the sidebar-footer Help item — one menu with What's new, Docs, Keyboard shortcuts and Contact support, in that order, and the build version at its foot (version). Each entry in links is a link (href, rendered through linkRender, external for a new tab) or an action (onSelect); omit one to hide it. whatsNewUnread puts the unread dot on What's new and on the trigger and adds "(new updates)" to both names. In the collapsed rail it is the help icon with a tooltip, the dot on the icon. docs · bd32fd1
  • SuggestionMenu and TextEdit: roomy slash and @ menus, and phone sheets. SuggestionMenu gains size ("default" | "lg" — lg is 320px wide, up to min(60vh, 440px) tall, with the description as a second line under the label and a 28px icon box) and presentation ("popover" | "auto" — auto opens a bottom Sheet with its own search field on a coarse pointer or a window under 640px, driven by query, onQueryChange, onClose and searchPlaceholder); useSuggestionMenuSheet(presentation) says when it does, and an option's descriptionShown: "active" shows its description on the highlighted row only. TextEdit gains menuSize and menuPresentation for its slash and mention menus: on lg each built-in block says what it is ("Big section heading") and the full slash menu is grouped Basic · the host's items · Media · Advanced, with TextEditSlashAction.group naming a host item's heading ("Insert" by default). TextEditMentions.sections (MentionSection[] — "people" | "agents" | "records") fixes the @ menu's order: People, Agents (with the agent avatar), then each record kind, the nearest match first; a person's email shows only on the highlighted row unless two people share the name (MentionOption.descriptionShown overrides it). Every new prop is opt-in: without them the menus are unchanged. docs · bd32fd1
  • RouteTabs (new): tabs whose panels are pages — a named nav (label) of links drawn as the pill tabs, one URL per tab, the current one from currentHref (or a tab's also paths) with aria-current="page", counts, and overflow="scroll" keeping the current tab in view. A clicked tab is current at once with a pending bar until currentHref moves (or drive it with pendingHref / onPendingHrefChange); links render through linkRender. RouteTabsPanel dims and is aria-busy while a page loads (pending, or from a RouteTabsProvider shared with the tabs). docs · bd32fd1
  • ShortcutOverlay takes page-scoped groups={[{ id, label, active, items }]} as an alternative to shortcuts: groups with active: true (this page) come first, groups without active (General, Go to) follow, and active: false groups (other pages) appear only with the new All shortcuts switch (allShortcutsLabel), which resets at every opening. A filter query searches every group. The line under the title is now description (default unchanged). shortcuts is optional; existing callers are unchanged. docs · bd32fd1
  • StatGroup in stat: a hairline-divided row of inline stats (items={[{ label, value, href }]}), one row from 24rem of its own width and a 2×2 grid below. A cell with an href is a link through linkRender; value: undefined is loading (a skeleton, aria-busy), null a failed read ("—" and errorLabel); hideWhenAllZero renders nothing when every value is 0. docs · bd32fd1
  • TaskInline (new) and Bookmark cards: one box per task, Make task in place, and a link shown as a card. TaskInline (title, done, struck, status, assignee, due, overdue, checkbox, onToggle, onOpen, href, label props) draws a task in running text — one round box, the title that opens it (a peek), the assignee's avatar, a due chip and ↗ on hover — and MakeTaskPopover (open, onOpenChange, anchor, lines, onCreate, assignee, due, space slots, labels) makes tasks from checklist lines in a popover anchored to the line instead of a modal. TextEdit and MarkdownView gain onTaskOpen: a linked checklist line draws as a TaskInline (its own box, round, is the task's; the chip's task glyph — the "second box" — is gone), with mentionStatus (and InlineChipState through the provider) answering assignee, due and overdue; Alt+Enter on the line opens it. onMakeTask now also receives { anchor } (the first line's element), and an unlinked checklist line under the pointer shows → Task. TextEdit gains bookmarks: Bookmark in the slash menu and Link · Bookmark in the paste popover, a card with the page's title, blurb, icon and site from the InlineChipProvider's linkPreview (which gains optional siteName, description and image), stored as [url](url "bookmark") on its own line; MarkdownView draws it as MarkdownBookmark (also usable directly with preview or fetchPreview). New grammar helpers BOOKMARK_TITLE, bookmarkMarkdown and bookmarkOfParagraph. Every new behaviour is opt-in except that a bookmark line already in a document renders as a card. docs · bd32fd1

🔧 Changed components

  • DataGrid, Board, BoardCard and DataList: table blocks in pages and calmer lanes and rows — every change is opt-in except where noted. DataGrid embedded frames the grid as one block in a page (the toolbar — a BlockHeader — on top, footer under the rows): its height comes from its rows, at most maxRows (25 by default when embedded; the first rows in display order), it scrolls sideways in the fixed layout with no column cap, its column picker is off unless columnPicker asks, and virtualize/maxHeight do not apply. footer also takes a DataGridBlockFooter object — { onNewRow, newRowLabel, shown, total, countLabel } — for "+ New row" and "25 of 117". readOnly turns off every write path (cell editors, paste, clear, cut, fill, undo keys, headerTrailing, + New row) while rows still open, sort and select. Board emptyLane="compact" draws an empty lane as its header over a 48px drop target with "+"; emptyLane="collapse" moves empty lanes into the Hidden columns list (choose one to bring it back; drop a card on it there; a lane a move empties stays). noValueLane="last" moves the lane marked noValue (a new BoardColumn field) to the end and lets it collapse on any board. loading (with loadingLanes, 4) shows lane shells only — one skeleton per load. Always on: a lane loading its first cards shows no count (never "0" over skeletons), and the edge fades carry a soft scroll shadow. BoardCardChip empty keeps a no-value chip off the card at rest (shown on card hover and focus, while its menu is open, and on touch). DataList density="compact" is now one line per row: merged values join the primary value's line, a plain primary value clamps to one line and a thumbnail steps down to 24px. A column render's context carries density, useDataListDensity() reads it deeper down, and DataListSecondary wraps a custom cell's second line so compact leaves it out. docs · bd32fd1
  • Toggle (and every ToggleGroup item): a pressed toggle is now filled with bg-primary/10 instead of upstream's bg-muted — which was also its hover fill, so pressed read as hover — deepening to bg-primary/15 on hover, with the outline variant's pressed border at border-primary/30 (decision API-39). Unpressed toggles are unchanged. docs · bd32fd1

📦 npm

  • @vegastack/design → 0.8.37 (was 0.8.36).
  • The design-system registry (@vegastack/ui) bumps 0.25.88 → 0.25.89.

📚 Docs

  • Shorter roster summaries for RecordPeek, RecordHeader and useRecordPeekRow, and the llms.txt index tripwire raised to 128 KiB (MK, 27-10-2026); no package change. 9267e6c

[0.25.88] — October 27, 2026

🧩 New components

  • RecordPeek: a record opened beside its list — a non-modal side panel, resizable by its start edge (480px to 90% of the window, remembered with storageKey), whose header walks the list (↑/↓ and J/K, "n of N" from position, disabled at the ends), opens the full page (⌘↵ / Ctrl+↵ through fullLink, fullHref or onOpenFull), holds the record's actions and closes with Esc, returning focus to the row (returnFocus). Full-screen below the tablet tier. useRecordPeekRow: data-peek-active on the open row of a hand-rolled list, scrolled into view as the peek walks (DataList, DataGrid and Board take activeRowId). RecordHeader: one head for every record on its page and in a peek — eyebrow (where it lives) and key chip, icon and title renamed in place, status, meta line, chips, presence and actions — with RecordHeaderSkeleton. RecordAsideSection: collapsible (the title folds the section; an empty one is its header alone), open/defaultOpen/onOpenChange, notice and headingLevel; its count is read as a separate word. docs · caccebb

📦 npm

  • @vegastack/design → 0.8.36 (was 0.8.35).
  • The design-system registry (@vegastack/ui) bumps 0.25.87 → 0.25.88.

[0.25.87] — October 27, 2026

🧩 New components

  • MemberList (new): one list for every "people in X" — a team, a space, a share list, an invite list. MemberListAdd is the add row (a picker slot · an optional role from roles · Add, stacking below 28rem); MemberList rows are 36px — avatar · name over a muted description sub line ("2 people", "Creator") · the role as a PermissionMenu chip (or plain text without roles, or read-only without canManage) · a ⋯ menu with Remove (onRemove) and the host's menuItems. locked rows show the role with a lock and lockedReason and no ⋯, while the ⋯ column keeps its width so the chips line up; past maxRows (8) the list scrolls inside itself; loading, empty and every string (labels) are props. docs · 6b445b6
  • SettingsLayout (new): the two-pane Settings frame. SettingsLayout nav={…} puts a SettingsNav in a 220px column that sticks below the page's top edge and the page in one column of up to 960px; SettingsLayoutContent kind="form" caps a page at 720px and kind="list" fills the column, both on the same left edge. Below 42rem of its own width it shows one pane at a time: view="nav" makes the nav the screen (44px rows with a chevron), view="content" shows the page with a "‹ Settings" link to backHref (backLabel). SettingsNav sections={[{ label, items: [{ href, label, badge }] }]} marks the current page from activeHref (a nested path marks its section) or isActive, with aria-current="page"; links render through linkRender (your router's Link). docs · 6b445b6

🔧 Changed components

  • Layout tiers: one width vocabulary for every surface — phone below 40rem, tablet 40–64rem, laptop 64–90rem, wide from 90rem — as Tailwind container sizes and breakpoints (@max-tablet/app-shell-content:, max-tablet:) in utilities.css and as LAYOUT_TIERS, layoutTierOf, layoutTierQuery, RAIL_COLLAPSED_QUERY and pageGutterOf in page-layout. AppShell: the page gutter steps to 48px from 1920px; AppShellSection measure="default" holds a list at a default page's content width, centred, inside a full frame (width follows the content, not the route); AppShellHeaderActions moves a page's top-bar actions into one ⋮ menu by the bar's own width and drops labels to icons below 40rem; an open sidebar folds to its rail when the window narrows into the tablet span and reopens on the way out (collapseRail). DocumentLayout: the text stays centred when the comment margin shows (the start track mirrors the margin while there is room) its tiers start at the layout tiers (L from 64rem), and measure="wide" stops at 75rem (1200px). Sidebar: SidebarStateScript starts an icon rail collapsed on a tablet by default (LAY-13 amended; collapsedWhen={null} opts out), and a collapsed icon rail keeps a menu count in sight at the icon's corner — a pill up to two characters, a dot beyond (A11Y-17 widened). Sheet: below the tablet tier a side sheet drops its side border and pads the safe areas (OVL-22). docs · c7a1a33
  • Comments, Activity Feed: quieter record history. CommentThread takes density="compact": no reply row at rest — Reply is a hover action on the first comment (on focus too, always on a touch screen; CommentItem's new replyAction) that opens the row with the caret in it, and the row folds away once its reply posts or is left empty. A trailing lone "@" (a mention started and left) is dropped as a comment, reply or edit posts, in every density. ActivityFeed's heading count now says what it counts — countLabel, "2 comments" by default. ActivityEvent props take change (ActivityChange — { field, verb?, from?, to }), so an ActivityEventGroup run that changes one field reads as one chain ("moved it Todo → In progress → In review"); summarizeAt folds a longer run of different changes into "made 3 changes" (changesLabel, or the host's summarizeRun), a disclosure listing them. groupActivityRuns(items, { actorKey, date, isBreak?, breakBefore?, runWindowMs? }) splits a chronological list into those runs — one actor, within 10 minutes of the run's first event by default, broken by comments and the host's own boundaries. docs · e052ff2
  • SettingsRow controlSize (sm 160px · md 256px · lg 320px · auto): every row's control shares one measure and one right edge once the row is side by side; the slot's first child fills it and a trailing status icon keeps its size. Left unset, a row holding a field control (input, input group, textarea, select, combobox, date or people picker) takes md, and a switch, button or read-only value keeps its own width — so a width class on a control inside a row (w-64, @sm/settings-row:w-72) is no longer needed and is overridden; delete it. New SettingsMatrix (columns, rows: [{ label, description, values, onChange(column, checked), disabled }], label): settings × channels with the column names once in a sticky header and each switch named by its row and column; below 28rem the switches stack under their label with the column name beside each. docs · 6b445b6
  • ShareDialog is built on MemberList: the invite row is a MemberListAdd and People with access are 36px MemberList rows (avatar · name over its reason · level chip), with Remove access moved from the level menu into each row's ⋯ menu (More for {name}; new labels.removeAccess and labels.moreFor). The "Who can see this" summary (audience) is now the first thing on the Share tab, above the invite row. The props are unchanged. docs · 6b445b6

🛠 CLI & tooling

  • vegastack-design doctor now warns where a project works around a DS part instead of using it, each as file:line: a Tailwind ! modifier in the className of a part imported from the ui alias; a w-*, min-w-* or max-w-* class on PopoverContent, DropdownMenuContent, ContextMenuContent, MenubarContent, SelectContent, ComboboxContent or HoverCardContent; a hand-written header element inside SheetContent, DrawerContent, DialogContent or AlertDialogContent in the same file; and an AppShellPage in an App Router page or layout whose size differs from the route map (definePageWidths rows plus any widths: { … } object feeding it). They are warnings by default; --strict makes them failures. New vegastack-design owned-files lists the registry-owned files installed in a project (index targets resolved through components.json aliases, plus header-carrying files) as plain lines or --json, so lint ignores, knip and drift checks can read one generated list. docs · bd4f239

📦 npm

  • @vegastack/design-tokens → 0.8.35 (was 0.8.17).
  • @vegastack/design → 0.8.35 (was 0.8.34).
  • The design-system registry (@vegastack/ui) bumps 0.25.86 → 0.25.87.

🐛 Fixed

  • AutoSaveIndicator no longer shows "Saved" beside a field's own error: invalid replaces the tick (and the spinner) with a visible "Not saved" in the warning ink, in both variants (labels.invalid). New useInvalidRevert({ invalid, onRevert, delay = 1000 }) returns onBlur/onFocus for the field: leaving it while invalid puts the last saved value back a second later through onRevert, which shows the host's message; returning to the field or fixing the value cancels it. AutoSaveInput reads a value validate rejects as "Not saved" rather than "Save failed", and onInvalidRevert(savedValue) (with revertDelay) turns on the revert. docs · 6b445b6

⚠️ Breaking

  • Inbox: one row per record, tabs, and J / K triage. InboxItem gains actors (stacked 20px avatars, three then "+n", names read out), events (InboxItemEvent[] — { id?, actor?, text, time? } — listed under the row behind a disclosure), status ("pending" | "answered") with answeredLabel (the line that replaces Allow / Decline once answered), value (the row's identity for triage) and menuLabel; count now reads after the actors, on its own line under meta, through countLabel ("9 updates") and becomes the events disclosure, instead of a pill after the title. InboxFilters is now tabs — tabs?: InboxTab[], For you · Updates · Archived by default (INBOX_TABS) — plus a separate Unread toggle (unreadOnly, onUnreadOnlyChange, unreadCount, unreadLabel); value / onValueChange take the tab's value. Inbox gains activeId, onActiveChange and onOpen: J / K (and ↓ / ↑) walk the rows with focus, the active row gets a start bar and aria-current and scrolls into view, and Enter or a plain click calls onOpen without navigating (a modified click still opens href). Breaking: InboxFilters' extra prop and its built-in All / Unread chips are gone (pass tabs, and the Unread toggle's props), and the InboxFilter and InboxFilterOption types are removed (use string and InboxTab). The notifications-01 block follows. docs · e052ff2

[0.25.86] — October 27, 2026

🧩 New components

  • QrCode (new): a QR code for a value, drawn as one SVG path (shape-rendering="crispEdges") — dark modules on a light, rounded tile in both themes so it always scans, with the standard 4-module quiet zone inside the drawing (quietZone to change it). errorCorrection takes L, M (default), Q or H; size it with a size-* class (default size-40). The root is role="img" named by the required label. Server-safe: the matrix is encoded during render by uqr (zero dependencies, MIT), a new sanctioned encoder dependency imported only by qr-code.tsx. An empty or unencodable value draws the empty tile (data-state="invalid") instead of throwing. docs · 913f987

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.25.85 → 0.25.86.

[0.25.85] — October 27, 2026

🔧 Changed components

  • FilterBar: grid slots for a spreadsheet-like list with saved views. filterBuilder makes the Filters toggle open a FilterBuilder in a popover (the applied filters chips stay in the chip row whenever there are any); sort (with sortLabel, default "Sort", and sortCount) adds a Sort button that opens a SortBuilder; group, fields, density and viewActions share one View menu (viewMenuLabel, default "View") with labelled sections in the order Group, Fields, Row height, then the view's own actions (groupLabel, fieldsLabel, densityLabel). The three controls sit after the Filters toggle and go icon-only below @3xl, keeping their words as accessible names. A saved view's "View changed · Save · Reset" goes in trailing, in the chip row. docs · c0169da
  • DataGrid, DataList, Board and the shared table parts: one way to open a record from a list, and a grid that fills its box. DataGrid's fixed layout is never narrower than its viewport: a filler track takes what the columns leave, so the header band, row borders, washes, summary row and footer reach the end, and the "+" column stays beside the last real column. onRowOpen(rowId, { event, index }) adds a 24×24 Open button at the start of each row's primary cell (shown on hover and keyboard focus, always on touch), with Space on a cell and Enter on the row header opening it (F2 still edits). activeRowId marks and scrolls to the open row (data-peek-active), and actionsRef exposes focusCell(rowId, key) and getRowOrder(). The active cell is a 2px inset info outline for pointer and keyboard alike, and the rest of a range takes a blue tint that never reads as hover. Empty cells are blank: the placeholder shows only while editing. A header with a headerMenu is one button that opens the menu (sort lives there), columns take an icon, and a numeric column aligns its values and header to the end. The empty or error state is held to the viewport, loadingRows sizes the skeleton, selectionActions docks the same selection bar as DataList, and phoneLayout="cards" shows cards on a container under 40rem (cardFields, default 3). In cells mode, dragging a range never selects text. DataList adds onRowOpen for the whole row: a link row stays a real link for ⌘ and middle clicks, while a plain click opens the row in place. It also adds activeRowId, actionsRef.getRowOrder(), and hiddenSections/onShowSection/onHideSection with a section's hideable for its board. Rows follow one density scale (comfortable 40px, compact 32px, the grid's medium and short). Board adds activeItemId and a built-in "Hidden columns" list (hiddenColumns, onShowColumn, onHideColumn, a column's hideable). onCardOpen opens every card in place, so a link card's plain click (or Enter) opens it while modified and middle clicks still follow the link. onCardActivate keeps its link-free meaning and now receives the event. Row and card ⋯ menus mount on demand. Shared parts: a squeezed list clamps a truncate element to one line instead of wrapping it over its neighbours. New exports: SelectionActionBar, SelectionBarSpacer, RowOpenDetails, PEEK_ACTIVE_ROW_CLASS, LIST_ROW_HEIGHT_PX and LIST_ROW_HEIGHT_CLASS. EmptyRow takes viewportWidth, and EditableCell/EditableCellRest take emptyText. docs · fb85832

📦 npm

  • @vegastack/design → 0.8.34 (was 0.8.33).
  • The design-system registry (@vegastack/ui) bumps 0.25.84 → 0.25.85.

[0.25.84] — October 26, 2026

📦 npm

  • @vegastack/design → 0.8.33 (was 0.8.32).
  • The design-system registry (@vegastack/ui) bumps 0.25.83 → 0.25.84.

🐛 Fixed

  • FieldCellEditor: Enter, F2 or a click on a long-text cell that holds a value opens the editor with the caret at the end of the text, nothing selected, so writing goes on from it; typing on the cell still replaces the value with the typed key. TextEdit: with a query, the @ menu's kind groups sit by their best match on the name — the whole name, then its start, then a word's start, then anywhere — and only then in order, so "@Arc" lists the customer named "Arcadia Architects" ahead of tasks that only mention it; each group keeps its results in the order the search returns them. docs · 3805287

[0.25.83] — October 26, 2026

📦 npm

  • @vegastack/design → 0.8.32 (was 0.8.31).
  • The design-system registry (@vegastack/ui) bumps 0.25.82 → 0.25.83.

🐛 Fixed

  • DataGrid: a horizontal scroll tick at 150 columns now stays under 8 ms of main-thread time. A cell whose column has a custom editor rests as EditableCellRest — the same button, name, description, box and activation as EditableCell's display, in one element with no state or effects — and becomes the full EditableCell once it edits or the host reports a save status (and stays one while mounted, so a save in flight keeps its optimistic value). A header's menu mounts its machinery once the header is pointed at, focused or opened. The row window overscans two rows. The observer that keeps cell content out of the tab order looks only at what each change added instead of searching the whole table. docs · 9862b44

[0.25.82] — October 26, 2026

📦 npm

  • @vegastack/design → 0.8.31 (was 0.8.30).
  • The design-system registry (@vegastack/ui) bumps 0.25.81 → 0.25.82.

🐛 Fixed

  • DataGrid: a scroll tick at 150 columns costs about a quarter to a third less main-thread time. A body cell is no longer position: relative unless it holds the remote-change cue or the fill handle (each positioned cell was a paint layer of its own, so paint and layerize were most of a tick), and EditableCell's display is positioned only while it edits. The row window overscans three rows and the column window one column. The frozen edge's data-scrolled-x is set once a frame from the offset the column virtualiser already read, so no grid scroll handler forces a layout. Header and summary cells are memoised behind stable actions, so a column-window move renders the entering column alone, and a remote change's cue (and its timer) mounts only on the cell it marks. A managed EditableCell's text leaf that measures on contact keeps its element in a ref, so a newly mounted cell renders once instead of twice. docs · 5c5d4e0

[0.25.81] — October 26, 2026

🔧 Changed components

  • field-cell, version-list: a long-text field takes @ mentions. FieldConfig gains mentions, mentionHref and mentionImage, passed to the long-text TextEdit in both FieldInput (a row panel) and FieldCellEditor's popover (a grid cell), where choosing in the mention menu keeps the popover open. VersionList gains heading (default "Version history"), so a host can call the list "History". docs · e472b76

📦 npm

  • @vegastack/design → 0.8.30 (was 0.8.29).
  • The design-system registry (@vegastack/ui) bumps 0.25.80 → 0.25.81.

[0.25.80] — October 26, 2026

📦 npm

  • @vegastack/design → 0.8.29 (was 0.8.28).
  • The design-system registry (@vegastack/ui) bumps 0.25.79 → 0.25.80.

🐛 Fixed

  • data-grid, editable-cell, field-cell, inline-chip, filter-bar-managed, text-edit: the grid's final acceptance fixes. GroupByPicker and SortBuilder no longer crash when every field is already used (a compiled memo read free[0].key while rendering); Add stays disabled. Typing on a long-text cell opens its editor holding every typed key in place of the value (a # or - is the character, not Markdown), and typing on a date cell opens a field that takes a typed date ("12/3", "Mar 12", "today") seeded with the key. Links and chips inside grid cells leave the tab order, so Tab leaves the grid from any cell: Alt+Enter (or Enter on a read-only cell) moves into its first chip, arrows step, Escape returns; a key or click on a chip inside an editable value is the chip's (it follows the link), never an edit. TextEditHandle.focus takes an optional "start" or "end", and focuses the read view while the editor loads so early keys are kept. docs · 8de8175

[0.25.79] — October 26, 2026

📦 npm

  • @vegastack/design → 0.8.28 (was 0.8.27).
  • The design-system registry (@vegastack/ui) bumps 0.25.78 → 0.25.79.

🐛 Fixed

  • DataGrid: scrolling a wide grid costs much less per frame, and a fast fling through clipped values no longer stops the grid with "Maximum update depth exceeded". A cell the grid mounts no longer measures its own overflow: TruncatedText, IconText and a managed EditableCell under a grid or list host measure when the pointer reaches them (the full text still opens on hover), and every other useOverflow shares one ResizeObserver that reads sizes after layout instead of forcing a layout on mount (measureOverflow is the one-shot read). useMediaQuery keeps one matchMedia list and listener per query, which TruncatedText now uses for (hover: none). Inside DataGrid, an EditableCell speaks through the grid's one live region (AnnouncerScope, joined with useAnnouncer({ scoped: true })) instead of a role="status" region per cell, a row's selection checkbox no longer re-renders when the column window moves, and EditableCell runs only the mode half of the edit machine (useInlineEditMode). docs · 9fdae8d

[0.25.78] — October 26, 2026

📦 npm

  • @vegastack/design → 0.8.27 (was 0.8.26).
  • The design-system registry (@vegastack/ui) bumps 0.25.77 → 0.25.78.

🐛 Fixed

  • field-cell: a link cell's chips no longer break inside a name. A chip's first word may wrap in running text, and inside a host's prose (overflow-wrap: break-word, an editor's embedded table) a narrow column split its letters ("Merid / ian"). Each chip in FieldCellDisplay now stays on one line, cut with an ellipsis and its full name on hover; a host that lets the cell grow (className="flex-wrap") wraps whole chips onto new lines. docs · 4e13ad8

[0.25.77] — October 25, 2026

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.25.76 → 0.25.77.

🐛 Fixed

  • DataGrid: Tab back into the grid returns to the cell that was active, even after its row or column was windowed out by scrolling (it scrolls back in), instead of landing on whichever cell stood in as the tab stop. The grid is its own stacking context, so its sticky summary row no longer covers an ActionBar and its buttons can be pressed. FieldCellEditor: opened from the keyboard, a choice or link picker focuses a usable control (never the picker's hidden form input, where keys went nowhere), a choice, person, link or date picker opens its list or calendar with the editor, and long text's editor takes the caret when opened with Enter too. DataList: a press on a clickable grid card's text opens its row; only the card's own controls (its checkbox, a menu, an interactive column) keep a press to themselves. 67a3c27

[0.25.76] — October 25, 2026

📦 npm

  • @vegastack/design → 0.8.26 (was 0.8.25).
  • The design-system registry (@vegastack/ui) bumps 0.25.75 → 0.25.76.

🐛 Fixed

  • text-edit-grammar: escapeMentionLabel (and so mentionMarkdown and every mention TextEdit writes) now escapes a backtick in a mention's label too. A bare backtick in one label could pair with one in another label, or in the text, into a code span, which Markdown reads before links, so the mentions it crossed rendered as code. parseMentionLink reads the escaped label back unchanged. Image alt text keeps its own escaping. docs · 9c91680

[0.25.75] — October 25, 2026

📦 npm

  • @vegastack/design → 0.8.25 (was 0.8.24).
  • The design-system registry (@vegastack/ui) bumps 0.25.74 → 0.25.75.

🐛 Fixed

  • EditableCell, DataGrid, FieldCellEditor: a custom editor now receives the seed (the key that opened it by type-to-edit, which was lost) and commit(next, move?), where move (down, up, right, left — the new EditableCellMove type) steps the grid on through onNavigate. FieldCellEditor follows the spreadsheet keys — Enter commits and moves down, Shift+Enter up, Tab right, Shift+Tab left (after a date-time's own controls), Escape cancels, and IME composition never commits — through onCommit(value, move), and starts from seed: it replaces a text, URL, email, phone or number value and opens a choice, person or link picker searching for it (the new defaultSearch on SearchableSelect, PeoplePicker and FieldInput). ViewTabs: a cut view name shows in full in a tooltip on its tab and the narrow select's trigger, and the select's list wraps it. docs · 41328dd

[0.25.74] — October 24, 2026

📦 npm

  • @vegastack/design → 0.8.24 (was 0.8.23).
  • The design-system registry (@vegastack/ui) bumps 0.25.73 → 0.25.74.

⚠️ Breaking

  • MarkdownView, TextEdit, DocumentLayout: live blocks of any kind show their stored snapshot generically. liveBlockKinds now takes the host's kinds as LiveBlockKind objects ({ name, rowKind, label, empty? }, default DEFAULT_LIVE_BLOCK_KINDS, the live task list) instead of bare names, in MarkdownView, TextEdit, markdownToDoc and textEditExtensions; MarkdownTaskList becomes MarkdownLiveBlock (the kind's heading, its rows' chips, a box only on rows that have one) and parseTaskListRows becomes parseLiveBlockRows(snapshot, rowKind), whose rows carry checked: null when they have no box. A live block's rows may be plain list items (- [@Row](mention://row/<id>)). New DocumentLayoutBlockTable parts (Head, Body, Row, Column, Cell): a quiet read-only table for a block of a table's records, its name column first, keyboard-reachable while it scrolls sideways. docs · 0305052

[0.25.73] — October 24, 2026

🔧 Changed components

  • DataGrid: an editable column with a render keeps it as the cell's display while not editing (a FieldCellDisplay's tags, chips or avatars), instead of the value as plain text; and a custom editor's render now receives the row with value, commit and cancel (the new DataGridEditor<T> type). docs · 143cb17

📦 npm

  • @vegastack/design → 0.8.23 (was 0.8.22).
  • The design-system registry (@vegastack/ui) bumps 0.25.72 → 0.25.73.

[0.25.72] — October 24, 2026

🧩 New components

  • FieldCell (new, Regent R5): one display and one editor per field type — FieldCellDisplay (text, long text, number, currency, percent, date, date-time, single and multiple choice as coloured tags with "+N", checkbox that toggles in place, person, link as InlineChips in their four states, file as kind tiles, URL / email / phone as links out, calculated with its missing inputs, auto-number, created/updated; filling and recalculating states), FieldCellEditor (for EditableCell's custom slot; commits a typed value, Enter / Escape, pickers commit on choosing) and FieldInput (the same editors as form controls: NumberField for numbers so a comma-decimal locale reads "1,5" as 1.5, DatePicker and a time field, SearchableSelect with onCreateOption, PeoplePicker, an async record search, checked URL / email / phone fields, the TextEdit editor for long text). Locale and time zone come from the reader. docs · 17550e2

📦 npm

  • @vegastack/design → 0.8.22 (was 0.8.21).
  • The design-system registry (@vegastack/ui) bumps 0.25.71 → 0.25.72.

[0.25.71] — October 24, 2026

🧩 New components

  • Views and filters (Regent R4). ViewTabs (new): a table's saved views as a line tab row — shared views, a divider, then the reader's own under "Mine"; a lock on a locked view; onCreate(kind, scope) adds a + menu for a grid or board view, shared or personal; getViewActions(view) fills the open view's ⋯ menu; below @3xl of its own container the row is one Select with the same groups. filter-bar-managed: SortBuilder (up to three { key, dir } keys on SortableList, read-only chips) and GroupByPicker (up to two levels); value editors DateValueEditor (exact day, Today, This week, Last N days, and { from, to } for a range), PersonValueEditor (on PeoplePicker, with Me as FILTER_ME, several for a list operator), CheckboxValueEditor and LinkValueEditor (async record search through the field's search, stores { id, label }); FilterField takes people and search; an editors key <type>:range or <type>:list wins for that type alone; describeFilter and the chips read the new values. docs · 737a604

📦 npm

  • @vegastack/design → 0.8.21 (was 0.8.20).
  • The design-system registry (@vegastack/ui) bumps 0.25.70 → 0.25.71.

[0.25.70] — October 24, 2026

🧩 New components

  • DataGrid cells mode (Regent R3): selectionMode="cells" adds spreadsheet ranges kept by row id and column key — Shift+arrows, Shift+click and drag extend; Shift+Space, Ctrl+Space and ⌘/Ctrl+A take a row, a column and everything; copy, cut and paste through the clipboard events as TSV and HTML; Delete clears; a paste, clear or cut is one onCellsCommit(changes) batch answered per cell (CellChange, CellCommitResult) and announced ("Pasted 120 cells · 4 skipped"); rows past the last loaded row go to onPasteOverflow; the fill handle and ⌘/Ctrl+D call onFill({ source, target, series }); undo and redo keys reach onUndo / onRedo; remoteChanges (keyed by gridCellKey) pulses a cell with motion-flash, shows its author for 1.5s and announces it only on the focused cell. Columns gain parse(cell, row) and format(row). useGridSelection (new hook): the range model. grid-clipboard (new lib): parseTsv, serializeTsv, parseHtmlTable, serializeHtmlTable, parseClipboard (Google Sheets' compact-table typed values, Excel's x:num) and expandPaste, tested against the recorded G3 clipboards. docs · f7f9449

📦 npm

  • @vegastack/design → 0.8.20 (was 0.8.19).
  • The design-system registry (@vegastack/ui) bumps 0.25.69 → 0.25.70.

[0.25.69] — October 24, 2026

🧩 New components

  • DataGrid grid core (Regent R2): the grid now hosts a 50,000-row, 150-column, server-sorted grid. DataGrid: sortMode="manual" renders data in the host's order and only signals sorts; a FIXED layout (any column width, frozen or resizable, or onColumnResize / virtualizeColumns) sizes columns from a <colgroup>, keeps every cell to one line and scrolls sideways; a leading run of frozen columns sticks to the inline start with a scroll-edge shadow; onColumnResize adds the header resize handle (drag, double-click to fit, Alt+arrows); onColumnReorder makes the scrolling headers draggable; virtualizeColumns windows the columns; rowHeight (short / medium / tall) fixes row heights; client group columns and the new host sections (each with its own loadMore) are flattened into the one row virtualiser, so grouping and virtualize combine; summaryRow adds a sticky footer of per-column functions (summaryOptions) with host-computed values; cellMenu and column headerMenu open from a right click or Shift+F10; headerTrailing (a render function handed DataGridFocusProps) adds a control after the last column on the header row's roving path; pagedMode turns row windowing off and shows a CursorPager; ariaRowCount, ariaColCount and ariaRowIndexOffset keep the ARIA totals true. Focus is kept by row id and column key and roves over the header, group, body and summary rows (one tab stop); primary cells are rowheaders; PageUp/PageDown, ⌘/Ctrl+arrows, type-to-edit and Enter/Tab commit-and-move are new, and an IME composition never moves or commits. The grid renders its own <table> in one scroll viewport (data-slot="grid-viewport", formerly data-grid-scroll). CursorPager (new): Previous / Next for keyset paging, with no page numbers or total. data-table-parts: ColumnResizeHandle, columnResizeKeyWidth, GridSummaryCell (with leading and onOpenChange); useRowSelection memoises its all/some scan; SortHeaderButton and SectionToggle take tabIndex (and SectionToggle a ref); SelectAllHead and SelectionCell take controlTabIndex / controlRef. EditableCell: editSeed starts a host-opened text or number edit from the typed key, and onNavigate receives which key moved ("tab" | "enter"); useInlineEdit takes the matching seed. docs · 1273a5a

📦 npm

  • @vegastack/design → 0.8.19 (was 0.8.18).
  • The design-system registry (@vegastack/ui) bumps 0.25.68 → 0.25.69.

[0.25.68] — October 23, 2026

🔧 Changed components

  • Open references (Regent R1): the design system no longer lists an app's record kinds. text-edit-grammar: a mention kind is any [a-z][a-z0-9_]{1,31} (MENTION_KIND_PATTERN, isMentionKind); MentionKind is "user" | "agent" (BUILT_IN_MENTION_KINDS) or any host kind, and MENTION_LINK_SOURCE / MENTION_LINK_SQL are built from the pattern, both with groups 1 label, 2 kind, 3 id (MENTION_KINDS is gone). Live blocks are :::<name>{…} … ::: for the names a host reads: readLiveBlock(source, { names }) and liveBlockMarkdown({ kind, … }) replace readTasksDirective / tasksDirectiveMarkdown, and :::tasks reads and writes byte for byte as before; text-edit-markdown's markdownToDoc and normalizeMarkdown take liveBlockKinds. TextEdit: mentions.kinds is MentionKindConfig[] ({ id, noun, plural, icon?, order }: the group heading, its icon, its order); a MentionOption.group ("Recent") shows under its own heading, first; liveBlockKinds and renderLiveBlock(kind, props) read and draw any live block, stored as the taskQuery node with a new kind attribute (null for tasks, so a task list's Markdown, HTML and shared state are unchanged; renderTaskQuery still draws tasks). MarkdownView: the same liveBlockKinds and renderLiveBlock; a mention of any well-formed kind renders as a chip. InlineChip: InlineChipResolver.kinds names each host kind's icon and noun; an unnamed kind shows a link glyph and "Item"; done shows the check on any kind (INLINE_CHIP_KINDS and INLINE_CHIP_KIND_LABEL are gone). date-time: formatCalendarDate(day, { locale, format }), parseCalendarDay and toCalendarDay — a stored day never shifts with the reader's zone; EditableCell's date editor uses them. SearchableSelect: onCreateOption(query) adds an "Add “query”" row while no option matches exactly, and selects the option it resolves with (createLabel). docs · 453a08c

📦 npm

  • @vegastack/design → 0.8.18 (was 0.8.17).
  • The design-system registry (@vegastack/ui) bumps 0.25.67 → 0.25.68.

[0.25.67] — October 23, 2026

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.25.66 → 0.25.67.

🐛 Fixed

  • TextEdit: a table column's width is its own (Notion) — the first resize, and every column added (160px), stores each column's width, so widening or adding a column no longer squeezes the others and a wide table scrolls inside its box. A document ending in a table, audio, video or image keeps room below it: clicking there, ↓ from the table's last row, or ↓ / → on the selected block adds an empty paragraph to type in. A variant="composer" comment box no longer shows a scrollbar after a mention (the chip's hit area overflowed its last line). (Regent operator, 23-10-2026.) 70a2787

[0.25.66] — October 21, 2026

🧩 New components

  • DocumentLayout sections, pages and live blocks: DocumentLayoutSection (named by DocumentLayoutSectionTitle, with count), DocumentLayoutSectionHeader, DocumentLayoutSectionButton (more · add), DocumentLayoutSectionSkeleton; DocumentLayoutPageList, DocumentLayoutPageItem and DocumentLayoutPageLink (icon, name, excerpt, meta); DocumentLayoutBlock with its header, title, rows (DocumentLayoutBlockRow, …RowMain, …RowRef, …RowMeta), note, skeleton, footer and DocumentLayoutBlockAction. DocumentLayoutDecisionBar keeps 24px between itself and the text it leads. TableOfContents: a scroller that overflows by only 2px no longer marks its last heading at its top. DataList / DataGrid: a column's numeric sets its body values in tabular figures. db39dee

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.25.65 → 0.25.66.

📚 Docs

  • DocumentLayout's margin guidance: show the comment margin only while it holds threads or a draft, decided with the text, so a page without threads is centred (Regent operator, 21-10-2026). d896e40

[0.25.65] — October 21, 2026

🧩 New components

  • EntryList (new): a short, compact list of plain entries in a sheet, dialog or popover — EntryList, Entry (row · stack), EntryMeta, EntryActions. Sheet: SheetSection, SheetSectionHeader, SheetSectionTitle, SheetSectionLabel, SheetSectionDescription. Popover: PopoverContent size (default · md · lg · fit), PopoverBody (scrolls from 70% of the window), PopoverAnchor (an invisible trigger to point an opened-from-elsewhere popover), and PopoverDescription keeps line breaks and inks a strong. Skeleton: SkeletonText (text · rows). Attachment: AttachmentSkeletons, and AttachmentDescription sets numbers in tabular figures. VersionList: headingHidden. Comments: CommentThreadSkeleton. DocumentLayout: DocumentLayoutDecisionBar with its text and actions. Badge: a badge rendered as a button or link shows the pointer. 8044584

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.25.64 → 0.25.65.

[0.25.64] — October 21, 2026

🔧 Changed components

  • InlineChip: InlineChipResolver.state(kind, id) gives every chip without state of its own its record's state — a done task's check, a canceled or duplicate task struck through with its status — so one InlineChipProvider shows a record alike in an editor, a rendered body and a comment. A surface's own mentionStatus still wins. TextEdit and MarkdownView pass no state when theirs answers none. aeb74b1
  • DocumentLayout: page tools without app styling — DocumentLayoutFindBar (a docked find field with status, Previous, Next, Close and optional match highlights), DocumentLayoutJumpCue, DocumentLayoutFloatingButton, DocumentLayoutStarters / DocumentLayoutStarter, DocumentLayoutNote, and focusable on DocumentLayout. AppShell: AppShellHeaderSlot (a fixed 32px place in the top bar) and AppShellHeaderShow (narrow · wide, by the bar's own width). AutoSaveIndicator: compact="narrow" shows the icon alone in a narrow bar. CommentComposer: hint, a quiet line under the box. c959fa9

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.25.63 → 0.25.64.

[0.25.63] — October 21, 2026

🧩 New components

  • TextEdit turns a lone pasted link into what it names, as Notion and Google Docs do. The URL lands as text, then becomes a mention of the app's own record (the new mentions.fromUrl), a YouTube or Loom embed (where it can stand in for an empty paragraph), or a link. With InlineChip's new linkPreview resolver, a link's text is the page's title when it answers within 1.5 seconds (link and title land as one step), and the link card shows the page's icon and title. A small paste popover offers the other choice (Mention · Link, Embed · Link) until the next keystroke, click or Escape; Tab moves into it. Each step is its own undo step, so ⌘Z right after a paste leaves the URL as text, in shared documents too. Pasting over a selection, in code or with ⌘⇧V never converts. The "Embedded" toast and its Undo are gone: the popover and ⌘Z replace them. Escape with a chip's card open (a click on a person shows it) closes only the card, and the caret stays beside the chip, so typing goes on there instead of being dropped. InlineChip's card closes when a key is typed anywhere, so a person's card never stays over a comment box (or the text) being typed in. a6f4201

🔧 Changed components

  • ShareDialog: the publish hint ("Anyone with the link can view, without signing in", "Off — only people with access can open it") wraps instead of being cut off on a narrow sheet; checked at 320 and 375px, published or not, on both tabs. Empty: a long unbroken word in EmptyTitle or EmptyDescription (a quoted search) breaks inside the empty state instead of running past it (LAY-17). MermaidBlock: the error's Details toggle is a full 24px target. bfce471

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.25.62 → 0.25.63.

[0.25.62] — October 21, 2026

🔧 Changed components

  • Type ramp: the copy tier (text-xs, text-sm, text-base) declares its letter-spacing as 0, so copy-size text inside heading type — a character count under a title, a badge or a meta line in a heading — no longer inherits the heading's negative tracking. Outside heading type nothing changes (body text was already 0); across every preview fixture, no other element's tracking moves. 454506c
  • @vegastack/design: released beside @vegastack/design-tokens 0.8.17 (the copy tier's zero tracking), so the preset's tokens range names that release. 4e6a569
  • DocumentLayout: meta lines under the title. DocumentLayoutMeta (a dotted line whose wrapped lines never start with a ·; variant="status" reserves one line from sm and two on a phone and fits its chips — full words, then short forms longest first, then wrapping), with DocumentLayoutMetaItem (icon, yields), DocumentLayoutMetaGroup (unit · flow), DocumentLayoutMetaField, DocumentLayoutMetaBadge (label · short), DocumentLayoutMetaWords, DocumentLayoutMetaButton, DocumentLayoutMetaLink and DocumentLayoutMetaSkeleton. DocumentLayoutContentsButton is the floating "On this page" trigger below the M tier, and DocumentLayoutTitle takes editable (the text cursor over the row). An Add icon text button sits flush with the title. fd223e2
  • TextEdit: stepping through Find matches no longer pops the selection toolbar over the find bar; a regular expression that doesn't compile says "Invalid pattern" (the field marked invalid) instead of "No results"; a pasted Notion callout whose icon is a lone emoji keeps no stray emoji line, and the emoji picks the tone (💡 a tip, ⚠ a warning, ❗ important, 🚫 caution, any other a note); inserting an embed, image, video or audio from its panel puts the caret on a line after it, so typing never replaces the block. ShareDialog: at 320px its rows wrap inside the sheet instead of running past its edge (the panels' grid track may shrink below its widest line). a4dfb45
  • InlineChip: a person is the one coloured chip (the tag blue); every record — page, file, task, meeting, customer, project, product, family and agent — wears the same neutral page chip and is told apart by its icon alone. Done, closed and restricted states are unchanged. A new family kind (a product family, the layers icon) joins INLINE_CHIP_KINDS and the Markdown grammar's MENTION_KINDS. TextEdit's @ menu opens on placeholder rows in its own row geometry instead of "Searching…", at the height its answer usually takes, and holds that height while a search is in flight, so rows never shift under the caret; the opening query is asked at once, later keystrokes after 120ms, and answers are kept per query so a query asked before shows instantly. Its row icons are neutral, as the chips are, and it offers "Families". SuggestionMenu shows placeholder rows while loading with nothing to show yet. 9911375
  • ShareDialog: Reset link asks first ("Reset the public link?"), as Stop publishing does, since the current link stops working at once. publicLink.expiresAt (the instant the link lapses) shows under Expires as "Until Oct 22 · 2:30 PM" in the viewer's zone, so a link opened again later says when it ends, not only the preset that was chosen. 30a4535
  • Sidebar: SidebarStateScript takes collapsedWhen, a media query. With no sidebar_state cookie yet, a window it matches starts the panel collapsed before the first paint, and the cookie records it. A width-dependent default no longer has to collapse the panel after hydration, which slid it shut and shifted the page. A cookie (a person's own choice) always wins. 30391d7

📦 npm

  • @vegastack/design-tokens → 0.8.17 (was 0.8.12).
  • @vegastack/design → 0.8.17 (was 0.8.16).
  • The design-system registry (@vegastack/ui) bumps 0.25.61 → 0.25.62.

🐛 Fixed

  • TextEdit: a heading's # link stays on its own heading. While it shows it follows the heading wherever the page moves it (content arriving above the editor, a resize), instead of staying at the coordinates it was drawn at — where it could sit beside a meta line or a paragraph that had moved into the heading's place. It hides while the page scrolls and returns a beat after, and a collaborator's edit drops a hovered position until the pointer moves again. 0289f34
  • DocumentLayout: a DocumentLayoutMeta line prints (a public page's "Approved version · date" belongs on paper; hide a line with your own print rules where it doesn't), and DocumentLayoutMetaItem takes nowrap: a fact built around a DocumentLayoutMetaLink keeps its words on one line ("From meeting" and its date never wrap beside a long meeting name) while the link truncates. Plain facts still wrap. ad344d8
  • TextEdit: a key acts at the caret the reader sees. ProseMirror read a caret the browser had just moved (a click, an arrow key) only on selectionchange, which the browser queues behind input already waiting, so Enter, Backspace or a shortcut pressed right after a click acted at the selection from before. In a shared page opened a moment ago, that was the document's start: Enter split the first line and what followed was typed into it, and it was saved that way. Every key now reads a pending caret first. New TextEditHandle.clearHistory() for a shared document: the host calls it when it replaced the text under the reader (a version restored), so ⌘Z doesn't undo the reader's earlier changes onto the new text and splice the old text into it letter by letter. b5a4f3b

[0.25.61] — October 21, 2026

🔧 Changed components

  • DocumentLayout: a fluid text column on five tiers by the layout's own width — S < 40rem (full width), M 40–60rem (max(36rem, 75%)), L 60–90rem (clamp(40rem, 75%, 52rem)), XL 90–120rem (60rem), 2XL ≥ 120rem (64rem) — replacing the 47ch measure; Wide is the whole width less the gutters and the margin. The contents rail moves to the gutter before the text (TableOfContents side="start") and stacks above the text and the comment margin, so its open panel takes the pointer over both. A comment margin (20rem, 24rem from XL) keeps the text's width and shifts the centred block to the start; the text gives way only when text, margin and gutters can't all fit. DocumentLayoutTier adds 2xl. New DocumentLayoutTitleGroup, DocumentLayoutIcon (reveal for an Add icon control) and DocumentLayoutTitle (30/36, one step above the text's #). TableOfContents: the rail's panel sits in a frame that bridges the gap to the rail, with 300ms of grace before it closes, and marks the reached heading with a bar. CommentComposer: size="sm" pads the card 12/8 for a comment margin or popover, where the composer is the card. 82def75

📦 npm

  • @vegastack/design → 0.8.16 (was 0.8.15).
  • The design-system registry (@vegastack/ui) bumps 0.25.60 → 0.25.61.

[0.25.60] — October 21, 2026

🔧 Changed components

  • TextEdit: choosing a type in the block menu's Turn into closes the menu, and a menu whose block left the page closes with it, so the block grip never disappears for the rest of the page. ⌘/ (Ctrl+/) opens the block menu for the caret's block, as ⇧F10 does. ⌘K and the image, video, audio and file panels refuse text that isn't a link, inline: they take a web address, a bare domain, mailto:/tel: (links only) or a path, never saving "https://not a url". Replacing an image, by upload, import or link, keeps its alt text and caption. Choosing an insert panel's Link tab puts the caret in its field. FileViewer: a small image opens at its own size, never upscaled. MermaidBlock: a diagram that can't be drawn says "Couldn't draw this diagram — check line 2." with the parser's words behind Details. a32b576
  • TextEdit pads a table's columns only up to 200 characters, so one very long cell no longer pads every row and a table's Markdown grows only with its content (ordinary tables are written exactly as before); writing a code span's edge spaces never changes another character the document holds; repeated headings never share an id. MarkdownView turns heading ids on when only headingLinkHref is passed, and reads Markdown with the same bounded rules as the editor, so long runs of unmatched *, _, ~, unclosed links or unclosed toggles render in linear time; an empty checklist item (- [x]) is a checked task, as in the editor; a code block's final newline is kept when shown, copied and anchored; HTML read mode renders the editor's videos, toggles, mentions and callouts; a comment highlight over a mention or link never hides it from assistive technology or puts it inside a button; find keeps its matches when highlights reshape the text, folds the query exactly as the text (ΟΣ, İ) and covers a whole character a match ends inside; and the highlight layout is reported again when a highlight moves without the view resizing. 1d83aca

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.25.59 → 0.25.60.

[0.25.59] — October 21, 2026

🧩 New components

  • EditableCell: the text editor takes maxLength, the field's maxlength. Typing stops at the limit; a paste keeps the characters that fit, and when the limit cut it short the count says "Only the first N characters fit" and the cell announces it; from 90% of the limit a quiet n/max count shows under the field (its description), as wide as the field and laid over what follows, so nothing moves. What is typed or pasted in place can no longer run past what the store accepts and then be lost to a failed save. 1dee53c

🔧 Changed components

  • TextEdit: what you act on stays the thing you meant while others edit, and nothing you type, paste or upload is lost. The block menu, a block or table-row drag, the file picker and an image's Replace hold their block through a collaborator's edits — acting on it where it is now, never on the block that took its place (a table changed mid-drag moves nothing, with a toast). Replacing an image by Upload keeps the original until the upload lands, and a failed upload leaves it. Pasted images import each in its own place among the pasted blocks, every pasted data: image uploads (past the import limit too), and a file name that is not valid percent-encoding no longer stops an import. Typing, Enter, Backspace, a paste or a drop while the editor loads all reach it; flush() waits for files handed over before it loaded; the handle's focus() follows editability. A read-only document can open Find, folds toggles for its reader without changing the document, offers Comment on a selection with onCreateAnnotation, shows heading links with headingLinkHref, and renders an HTML body holding only a live task list. Find's Replace field shows what Replace all will insert, and Escape in the text closes Find. An upload whose placeholder is deleted stops at once, and an upload's signal.reason says why it aborted — TEXT_EDIT_UPLOAD_ABORT.removed (the placeholder was deleted) or .unmounted (the editor went away with it still in the text), so a host can keep the file as an attachment. ImageViewerScope: turned off, its images are plain pictures again. 3b2ae3d

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.25.58 → 0.25.59.

[0.25.58] — October 21, 2026

🔧 Changed components

  • MultiStepForm: a pinned (sticky) actions row keeps a gap above its buttons in its own surface colour, so content scrolling under it stops short of the buttons instead of running into them, and while it is stuck over content a hairline marks its top edge. Resting at the end of the step, the row sits exactly where it did. 81fc154
  • Comments: a comment, reply or edit holds still while it sends (read-only, attaching paused), so text typed meanwhile is never lost, and the caret comes back if sending fails. Send and Cmd/Ctrl+Enter wait until files uploading into the text have landed, so a comment never posts without them. Escape keeps an unsent comment or reply; only editing a posted comment reverts. ShareDialog shows the Share tab whenever public links are unavailable, instead of an empty dialog. SortableList drops a touch drag exactly where the finger lifts. DataList grid cards tell their renderers when they are selected, never wrap an interactive first column in their link, and the board keeps every row in its one lane when getRowSection comes without sections. MermaidBlock never prints an older diagram's drawing in place of the current one, and a diagram not yet drawn prints its source instead of a placeholder. BlockView prints the source when Code is chosen without a split preview, and the expanded view fits its canvas inside the screen in right-to-left layouts. InlineChip passes keys to onKeyDown on linked chips too. 53e2f3e

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.25.57 → 0.25.58.

[0.25.57] — October 20, 2026

🔧 Changed components

  • DataList: a reorder seam is the row's only cue — while data-drop-edge shows, the row drops its hover, keyboard-cursor and selection washes, and the 2px line paints above the next row's cells, so a place between rows never reads as the "into this row" wash. b390bb3

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.25.56 → 0.25.57.

[0.25.56] — October 20, 2026

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.25.55 → 0.25.56.

🐛 Fixed

  • TextEdit, MarkdownView: an image stores its size (![alt|320x180](src); parseImageAlt and imageMarkdown read and write the height), read from the file on upload and from the image on import, so the read view and the editor reserve its box before it loads; an image on a line of its own is a figure in MarkdownView, laid out as TextEdit's image block (16px apart at the document scale in both), a diagram's block view sits 16 from its neighbours in the read view as in the editor and its server fallback no longer takes the pre margins, and the content box keeps its blocks' margins (flow-root) — the swap from the read view into the editor moves nothing. A failed upload stays in the text as "Couldn't upload" with Retry and Remove (new onFailedUploadsChange counts them) instead of vanishing. The link card truncates a long link in the middle (the full URL on hover and in its accessible name) and never overflows its card. 05b58bc

[0.25.55] — October 20, 2026

🔧 Changed components

  • DataList, SortableList: a DataList list row that takes drops (canDropOnRow) takes them in its middle half only — its top and bottom quarters never nest, so a drag aimed between two rows can't land inside one (a grid card still takes a drop anywhere); SortableList columns="cards" lays a grid on DataList's dense card grid, column for column and 12px apart, and a grid's gap no longer shrinks when a tile holds a small (xs/sm) control. 03d02e5

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.25.54 → 0.25.55.

[0.25.54] — October 20, 2026

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.25.53 → 0.25.54.

🐛 Fixed

  • StatSkeleton: each bar sits in the line box of the text it stands in for (the label's 16px, the value's 20px or 32px), so a loading stat is exactly as tall as a loaded one — dashboard tiles no longer grow 8px when their counts arrive. 19da478

[0.25.53] — October 20, 2026

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.25.52 → 0.25.53.

🐛 Fixed

  • TextEdit, MarkdownView: an image's caption is its Markdown title (![alt](src "caption")) — the toolbar's Caption edits it, TextEdit shows it under the image and MarkdownView renders a figure with a figcaption (no tooltip); Notion paste maps its to-do lists to checklists and its callouts (figure.callout, aside, and Markdown's <aside>) to callouts; the editor hands Tiptap stable editorProps, so inserting a callout or a code block no longer logs React's "flushSync was called from inside a lifecycle method" (and the view is no longer re-propped on every render). ac4dd66

[0.25.52] — October 20, 2026

🔧 Changed components

  • DocumentLayout: with a comment margin, the text column moves as near the middle of the canvas as the room allows (fully centred from about 72rem, where the last release waited for 74rem), never squeezing its measure — at 1440 with a sidebar it now sits within 18px of the middle instead of a margin's width to the left. 8868fc1

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.25.51 → 0.25.52.

[0.25.51] — October 20, 2026

🔧 Changed components

  • DocumentLayout: with room for a comment margin on both sides (74rem), the text column sits in the middle of the canvas and the margin hangs to its right, instead of the pair being centred with the text off to the left. ShareDialog: the Publish tab lays the link's settings out as one grid (Expires and the host's publicLinkOptions rows, label beside control), and while published its own footer says what stays private and holds Stop publishing, in the same box as the Share tab's footer. TextEdit: behind a modal Dialog or Sheet (the editor inert) the bubble toolbar and every floating part hide, so nothing floats over the backdrop; an editor inside a modal keeps its own floating parts live. 3df6c23

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.25.50 → 0.25.51.

[0.25.50] — October 20, 2026

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.25.49 → 0.25.50.

🐛 Fixed

  • TextEdit and MarkdownView: section marks are a flat partition. A marked #–### heading's bar now stops at the next #–### heading of any level, instead of running on through its sub-headings; a #### and deeper stays a block of its section. sectionHeading(level) (exported from markdown-view) is the shared rule. 87d445b

[0.25.49] — October 20, 2026

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.25.48 → 0.25.49.

🐛 Fixed

  • TextEdit, CodeBlock, EmojiPicker: the image panel's Link imports another site's image through onExternalImage (it no longer inserts a hotlink the page can't show), and an image that can't load keeps a visible frame; the image toolbar is Replace, Alt text, Open, Download and Copy link (no ⋯ menu; Delete stays in the block menu and on Backspace), and the new imageDownloadHref gives Download (toolbar and viewer) a link that saves the file; link and media panels open under the caret instead of over the text above; the caret in a link shows a link card (destination, Edit, Remove); the slash menu shows group headings (Text, Lists, Blocks, Media, Insert) and Divider sits with the blocks; the bubble toolbar puts Comment last; a toggle's chevron no longer looks pressed while open, and the toggle's own ⋯ menu is gone ("Open by default" is in the block menu); ⌘A in a code block selects its code first; code block controls are not printed; Enter in the emoji search picks the first result. 9e9294e

[0.25.48] — October 20, 2026

🔧 Changed components

  • DataList, ActionBar, PageHeader: DataList onReorderRow / canReorderRow drag list rows to a place between others — a drop on a row's top or bottom edge lands before or after it under a 2px drop-indicator seam (data-drop-edge), while a folder row's middle still takes onDropInto (useDragInto gains reorder, onReorder and canReorder); ActionBar end closes the set after secondaryActions, so the selection bar's Clear no longer sits between the primary action and the secondary ones; PageHeader's actions wrap, end-aligned, instead of running off a phone screen. 59132c2

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.25.47 → 0.25.48.

[0.25.47] — October 20, 2026

🧩 New components

  • TableOfContents: the rail is a slim column of dashes (a button with aria-expanded) that opens a floating panel of the headings on hover, keyboard focus or a tap — side="end" anchors it to a page's inline-end edge — and Escape closes it with focus back on the rail. open / onOpenChange open the sheet from a menu item, and an item's blockId lets the scroll spy find a shared document's headings. MarkdownView puts the # heading link in the gutter before the heading (after it on touch, shown on a tap) and copies with a toast, and draws sectionMarks (with onSectionMark) as TextEdit does, inside a phone's gutter too. TextEdit's # heading link moves to the same gutter place, the block grip stepping out beside it. CommentComposer compact rests as one quiet "Add a comment…" line that opens the card on demand. f27cf47

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.25.46 → 0.25.47.

⚠️ Breaking

  • DocumentLayout: the contents take no column. DocumentLayoutContents floats at the layout's inline-end edge, in a 2.5rem gutter the grid keeps from the M tier whether or not a page has headings, so a contents list that arrives never moves the text. DocumentLayoutContentsRail, DocumentLayoutContentsList and the contents prop (on DocumentLayout and DocumentLayoutSkeleton) are gone: put a TableOfContents variant="rail" side="end" in DocumentLayoutContents, and offer "On this page" from a menu below M. f27cf47

[0.25.46] — October 20, 2026

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.25.45 → 0.25.46.

🐛 Fixed

  • InlineChip, MermaidBlock, SortableList: a task chip shows the task glyph and only a finished task its check (done, set by TextEdit and MarkdownView from mentionStatus's new done); a chip's icon and its label's first word never part on a wrap; a mermaid diagram scales down to fit its column (never above its drawn size) and scrolls sideways only below 0.6 of that size; SortableList's minTileWidth raises a tile grid's 8.5rem floor so wider cards sit one a row on a phone. bf6a775

[0.25.45] — October 20, 2026

🧩 New components

  • DataList: on a tight mergedLayout="line", a figure (an end-aligned or mono column: a size, a count, an id) keeps its whole width and the text values beside it truncate first — "Produ… · 1w ago · K Manoj… · 25 MB", never "25 M…". 9c5f045

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.25.44 → 0.25.45.

[0.25.44] — October 19, 2026

🧩 New components

  • DataList: a mergedLayout="line" meta line stays ONE line in a squeezed phone row. The squeeze released every descendant of the fill cell, so a folder row's space, time, person and size each wrapped inside its own box ("Produc" / "t", "25" / "MB"). The line is now kept out of the release and contains its inline size, so it truncates and never widens the table. f47cb07

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.25.43 → 0.25.44.

🐛 Fixed

  • TextEdit: node views follow the editor's editability. Checklist boxes, live task lists (renderTaskQuery's editable and setQuery), callouts, toggles and code blocks redraw when setEditable runs, so an editor that mounts read-only and turns editable (a collaborative page whose room becomes ready) no longer keeps disabled checkboxes and a read-only task list. ca55b16

[0.25.43] — October 19, 2026

🧩 New components

  • TextEdit, InlineChip, MarkdownView: linked task lines, Make task, closed-target chips, product mentions and section marks.

    • Linked task lines: linkedTaskId(node) returns the task id of a checklist line whose first inline is a task mention (- [x] [@Order glass](mention://task/t1)). onTaskItemToggle(taskId, checked) reports only this person's own ticks. It never reports a collaborator's tick, a value change or setTaskItemChecked.
    • Make task: with onMakeTask, the block menu of an unlinked checklist line offers Make task, or Make N tasks for the lines the selection reaches. onMakeTask gives each line a key (its blockId); handleRef.linkTaskItems([{ key, id, label }]) turns each still-unchanged line into a leading task chip. Text after a matching plain-text title stays after the chip; otherwise the whole line stays, mentions included. handleRef.setTaskItemChecked(taskId, checked) ticks or unticks every line linked to a task that changed elsewhere.
    • Closed targets: mentionStatus(kind, id) on TextEdit and MarkdownView strikes a chip's label through and adds a small status tag ("Canceled"). The props behind it are InlineChip's new struck and status.
    • Products: a new product mention kind with the package icon on a magenta tint. The @ menu has a Products group after Projects.
    • Section marks: sectionMarks ({ key, tone, label }, keyed by a heading's blockId) draws a gutter dot and a warning or info bar along each marked section. They are decorations only: nothing is written to the document and no text moves. Clicking the dot calls onSectionMark(key).
    • renderTaskQuery setQuery: in an editable editor the live task list gets setQuery(query), which replaces its filter as one local edit; read views get none. c1f2fd2
  • ShareDialog: publicLinkOptions — the host's own settings for a published link (what it shows, say), rendered in the Publish tab under the link and its expiry for a viewer who can publish. Additive; nothing changes without it. 9612b82

  • TextEdit and MarkdownView: live task lists.

    • taskQuery block: an atom { blockId, query, snapshot }, selectable and draggable, and in BLOCK_ID_TYPES. Its Markdown is the :::tasks{id="…" …} container directive: the task mention rows (- [ ] [@Title](mention://task/<id>) — <status>), then :::. It round-trips byte for byte. id is its blockId, the one block id Markdown carries. The other attributes are its query, unknown keys included. Lines that do not make a whole block stay text and a list.
    • renderTaskQuery: a new TextEdit prop. It draws the list with the host's own component, in the editor and in the read view. Without it the stored snapshot shows as a quiet card.
    • MarkdownView: renders :::tasks as that card (MarkdownTaskList: "Task list", task chips with their status labels, or "No matching tasks"). It is server-safe and takes the same optional renderTaskQuery.
    • text-edit-grammar: adds readTasksDirective, tasksDirectiveMarkdown, parseDirectiveAttrs and parseTaskListRows. docs · 5d1f4f3

🛠 CLI & tooling

  • Test lanes: geometry.css reads hover: as plain :hover (@custom-variant hover). Headless Chromium in the Linux runner image reports (hover: hover) as false, so every hover: style vanished in CI while desktop hosts applied it (ghost-chip-align failed in CI only). 3b00157

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.25.42 → 0.25.43.

🐛 Fixed

  • MermaidBlock and TextEdit find:

    • MermaidBlock prints from dark mode: each diagram is laid out in its own off-screen box with real layout, so a print stylesheet that hides the rest of the page no longer draws its labels at translate(NaN, NaN) (a blank diagram on paper), and a drawing that couldn't be measured is never cached. A diagram first drawn while printing takes its colours from the design tokens, not the print-time CSS.
    • No flushSync: the dark drawing's light copy is swapped in by CSS for print; React no longer warns "flushSync was called from inside a lifecycle method".
    • Wide diagrams: the diagram scrolls sideways in its own box with the table scroll cue (the overflowing edge fades) and is a named, focusable region ("Diagram, scrolls sideways") while it scrolls.
    • Find bar: the find field gives way first, so the bar stays on one row in a 27rem text column. 1813d14
  • DataList mergedLayout="line": every value sits on the line's centre. A person (a flex row of avatar and name) no longer drops to a second line under its dot, and a chip or a time with its 24px tooltip target lines up with the plain text and the dots. A "—" wrapped in a plain element (<span className="tabular-nums">—</span>) now drops with its dot, like a bare one. 7e846d1

[0.25.42] — October 14, 2026

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.25.41 → 0.25.42.

🐛 Fixed

  • TextEdit:

    • Find: ⌘F with words selected now searches for them and typing replaces the term. It used to append to it, and a whole selected block was taken as the term.
    • Heading links: the # copy link follows its heading when the text above it changes, instead of staying where the heading was.
    • MermaidBlock: printing from dark mode prints the light diagram (drawn while the page is idle), not dark shapes on white paper.
    • slashActions: onSelect also gets insertContent(json) to put a block at the caret.
    • Block views: a new Diagram (or any block view) holding its starter opens in Code, as documented, instead of Preview. docs · 09a3866

[0.25.41] — October 14, 2026

🔧 Changed components

  • ChipInput: a refused entry (a duplicate, or one past max) and an invalid entry now also show as one short line under the field, cleared by the next keystroke, so a sighted user sees why the typed text stayed put. The polite announcement is unchanged. The field now renders inside a data-slot="chip-input-root" column that takes className, so layout classes (flex-1, col-span-*, a min width) keep placing it; ref and the data-invalid/data-disabled state stay on the box (data-slot="chip-input"). docs · 9798291

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.25.40 → 0.25.41.

[0.25.40] — October 14, 2026

🔧 Changed components

  • AgentEditBanner shows the full "Undo this agent's changes" from a 24rem banner (was 28rem), so a banner in a desktop page column reads the long label; a phone-width banner keeps "Undo changes". 0c31e06

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.25.39 → 0.25.40.

[0.25.39] — October 14, 2026

🧩 New components

  • MermaidBlock (new): a mermaid fence drawn as a diagram in the browser — mermaid 12.1.0 loaded lazily the first time a diagram nears the viewport, every render pinned to strict security, SVG labels and the dagre layout inside one queue, token colours for light and dark (the other scheme pre-drawn on idle, so theme switches and print are instant), an LRU of 50 SVGs, the measured height reserved on the next visit, and the last diagram that worked kept, dimmed, with a role="status" line when the source stops parsing. Exports MermaidBlock, renderMermaid(source, { theme }), useMermaidSvg(source, { debounce }) and mermaidBlockView. TextEdit and MarkdownView: blockViews maps a fence's language to a renderer — blockViews={{ mermaid: mermaidBlockView }} makes those fences Diagram blocks with Preview / Code (split beside a live preview on a block at least 720px wide), Expand (full screen, pan and pinch) and a ⋯ menu (Copy source, plus the view's own actions); the mode is per viewer and never saved; ⌘/Ctrl+Enter and Escape return to the preview; the slash menu and Turn into offer Diagram while blockViews.mermaid is set. MarkdownView shows the source in the same frame on the server and before hydration. New shared internal: block-view. docs · 0f856e3

  • TextEdit editor tools (B.2):

    • Block menu: a click on the ⋮⋮ handle, or ⇧F10 / the context-menu key, opens Turn into ▸ (Callout ▸ by tone, Toggle), Duplicate (⌘D), Copy link, Copy as Markdown, Move up / down, Open by default and Delete. On touch it is a bottom sheet, the handle follows the caret, and a drag needs a 350ms hold. Drags auto-scroll at the window's edge, and in a shared document a drop re-resolves the block by its Yjs position.
    • Find and replace: findReplace adds a docked bar (⌘F, ⌘G, RE2 regex, Replace all as one undo step) and handleRef.openFind().
    • Emoji: emoji adds a : menu.
    • Heading links: headingLinkHref adds a # copy link on the hovered or caret heading. scrollToHeading now takes <slug>~<blockId>, opens toggles and returns whether it found the heading.
    • Word count: onStatsChange / getStats(), counted when idle.
    • Toggles: toggleState persists each viewer's toggles. Find results, links, comments and the caret open the toggles around them for the moment.
    • Host slash items: slashActions adds host / items after the built-in ones, and insertMention inserts a mention chip.
    • Outline: items carry blockId, and a shared document no longer rebuilds heading decorations on every change.

    SuggestionMenu: an option can be disabled. docs · 5358739

  • TextEdit: YouTube and Loom embeds, opt-in with embeds={EMBED_PROVIDERS} (without it an embed shows as a link and a pasted link stays one) — a new embed block ({ provider, url }, with a blockId) whose Markdown is the URL as written, alone on its line; a paragraph holding only a link to its own URL on an allowlisted host parses back to an embed, any other lone URL stays a link, and existing documents serialize unchanged. It draws a facade (16:9 card, provider glyph, "YouTube video" / "Loom video", Play) that requests nothing from the provider until Play, then a lazy, sandboxed frame whose src is rebuilt from the video id (youtube-nocookie.com, t= as ?start=) with its own referrer policy; printed, it is the URL. A lone link pasted into an empty paragraph embeds, with an "Embedded" toast and Undo back to a link; the slash menu's Video link tab embeds YouTube and Loom links. New onExternalImage(url, { signal }): images pasted or dropped from another site leave the paste, show an upload placeholder and land as your stored copy — or as a link with "Couldn't import that image. A link was added instead." — and pasted data: images upload through onImageUpload (20 a paste, 3 at a time). MarkdownView renders the same facade (MarkdownEmbed) with the same embeds prop. New lib embed-providers: matchEmbed, EMBED_PROVIDERS, EmbedProviders, EMBED_FRAME_ORIGINS (the frame-src allowlist) and EMBED_FRAME_ATTRIBUTES. Corpus cases extensions/embed-*, editor-json/embed-*. docs · 37b51ca

📦 npm

  • @vegastack/design → 0.8.15 (was 0.8.14).
  • The design-system registry (@vegastack/ui) bumps 0.25.38 → 0.25.39.

[0.25.38] — October 14, 2026

🧩 New components

  • AgentEditBanner is new, and VersionList writes an agent author as everywhere else.

    • AgentEditBanner (shadcn add @vegastack/agent-edit-banner) is the strip under a document's header after an agent changed it: the agent's purple bot, "Edited by Claude (via Manoj) · 3 changes · 2m ago", View changes, "Undo this agent's changes" and a ✕. status="undoing" spins Undo and disables both buttons; refused adds "Couldn't undo only Claude's changes because people edited the page since." (or your refusedReason) and swaps Undo for Open versions; kind="created" reads "Created by Claude (via Manoj) · 5m ago". The callbacks passed decide which buttons show, so a read-only viewer sees the words and the ✕. moreAgents ends the line with "+1 more agent", a popover listing the rest, each with its own Undo. Under a 42rem container the buttons take their own row. It is a polite status region, the buttons are named "Undo Claude (via Manoj)'s changes" and "View Claude (via Manoj)'s changes", and every string can be replaced through labels.
    • VersionList: an author with kind: "agent" reads "Claude via Manoj" with the purple "Agent" label after the bot, as in comments and activity. A row's name can be any title the host writes ("Before Claude (via Manoj)'s edits"), with summary as its second line ("Claude edited 3 blocks"). 9d1edc8

📦 npm

  • @vegastack/design → 0.8.14 (was 0.8.13).
  • The design-system registry (@vegastack/ui) bumps 0.25.37 → 0.25.38.

📚 Docs

  • The shipped vegastack-design-system skill's component roster lists AgentEditBanner. 9d1edc8

[0.25.37] — October 14, 2026

🧩 New components

  • MarkdownView: comment on a selection in read mode — onCreateAnnotation(anchor) shows a floating Comment button over a selection inside the view (TextEdit's read-only selection bubble; commentLabel renames it) and hands over { quote, prefix, suffix, start, end } computed over the same text annotations resolve against, so the anchor passed straight back highlights exactly the selected words. Mentions and images count whole; whitespace, atoms or code alone show nothing. It flips and slides into view, hides while the selection is scrolled out, opens below the selection on a coarse pointer, closes on Escape, and Tab reaches it from a keyboard selection; the view stays server-safe. TextEdit, MarkdownView and SuggestionMenu: floatingSafeArea ({ top, bottom } px) keeps every floating part — selection bubble, link panel, slash and mention menus — clear of a sticky header or a bottom bar; TextEdit's selection bubble opens below the selection on a coarse pointer. docs · 2eba8c1

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.25.36 → 0.25.37.

[0.25.36] — October 14, 2026

🔧 Changed components

  • VersionList: author may be null — a version the system kept with no person behind it (a page's text from before live editing) names no one: no avatar or placeholder person, the time alone. docs · de023eb

📦 npm

  • @vegastack/design → 0.8.13 (was 0.8.12).
  • The design-system registry (@vegastack/ui) bumps 0.25.35 → 0.25.36.

🐛 Fixed

  • text-edit-markdown / TextEdit: a link (or any other span) followed by an image on the same line — See [docs](/d) ![shot](/s.png) — no longer fails to parse ("Empty text nodes are not allowed"); the image is lifted to its own block as anywhere else. Corpus case regressions/link-then-image. docs · 61d4d55
  • VersionList: a system-kept row's time (no author before it) starts its line capitalized — "Today, 11:59 AM". docs · dfd4214

[0.25.35] — October 14, 2026

🧩 New components

  • DocumentLayout (new): a page read as a document — DocumentLayoutHeader, DocumentLayoutBody at a 47ch reading measure (about 67 characters a line), DocumentLayoutFoot, a DocumentLayoutContents rail or list and a DocumentLayoutMargin for comments — on one grid with four tiers by its own width (36 · 52 · 64rem), measure="wide" up to 60rem, DocumentLayoutSkeleton on the same grid and line pitch, and useDocumentLayoutTier reading the tier from the CSS; it prints as one column on a named page with paper margins. MarkdownView gains a read mode that needs no editor: annotations (comment highlights resolved from their quote, with activeAnnotationId, onAnnotationClick, annotationCounts and onAnnotationsLayout), onOutlineChange, headingAnchors (slug and block id) with headingLinkHref copy links, per-reader toggleState, and findQuery (CSS Custom Highlight API, a match bar, onFindClose); a table wider than the text fades at its scrolling edge and is a named, focusable region while it scrolls (the editor's tables fade too). BreadcrumbTrail: compact / compact="auto" — ‹ and the parent's name, a menu of the whole path. ShareDialog: publishExposure, onPublishRequest and a "Who can see this" audience section. Button: a 44×44 hit area on a coarse pointer at every size, nothing moving (A11Y-24). Sheet: a bottom sheet clears the home indicator (LAY-10). Tokens: printing from dark mode prints the light palette. docs · 8ab9e76

🔧 Changed components

  • TextEdit diff={{ doc, baseline }}: a version diff, formatted and read-only — added text and blocks underlined on the success tint, removed ones struck through (ins/del, announced "Added:"/"Removed:"; changed images outlined), under Compared with {baseline}. text-edit-schema adds TextEditDiff (the ychange mark and block attribute a Yjs snapshot diff produces, never in a live document — whose schema now refuses it), textEditDiffSchema() (textEditExtensions({ diff: true }), code blocks take the change mark) and ychangeType(). A diff view never fires onValueChange/onCommit and ignores value. docs · cb419b5

📦 npm

  • @vegastack/design-tokens → 0.8.12 (was 0.8.3).
  • @vegastack/design → 0.8.12 (was 0.8.11).
  • The design-system registry (@vegastack/ui) bumps 0.25.34 → 0.25.35.

⚠️ Breaking

  • MarkdownView / TextEdit: headingScale is replaced by typeScale — "compact" | "document", no alias — and proseDocumentHeadingsClassName is gone. typeScale="document" is now the whole document type scale (TYP-20, MK 2026-10-14), not just bigger headings: 14/24 text (16/26 on a coarse pointer or a narrow window, so reading and editing match and iOS never zooms), 12px between paragraphs, # 24 · ## 20 · ### 16 (18 over 16px text), inline code in the body's ink, and print rules; @vegastack/design exports it as proseDocument and proseDocumentClassName. Rename headingScale="document" to typeScale="document"; headings are smaller than the old document scale (24/20/16, was 30/24/20). TextEdit: onOutlineChange no longer mounts the editor — the read view reports the outline from the Markdown until the editor is in. docs · 8ab9e76
  • VersionList kinds are now auto · safety · restore · import · agent_checkpoint · named · approved, each with its badge (VERSION_KIND_LABELS: "Before a large deletion", "Before a restore", "Before an import", "Before an agent's edits", "Approved"); a row's own label replaces it ("Before Claude's edits") and a named row shows none, except Approved. conflict ("Unsaved copy") is removed, and restore now means the text just before a restore (it was "Restored"). docs · cb419b5

[0.25.34] — October 14, 2026

📦 npm

  • @vegastack/design → 0.8.11 (was 0.8.10).
  • The design-system registry (@vegastack/ui) bumps 0.25.33 → 0.25.34.

🐛 Fixed

  • date-time / RelativeTime / ApprovalCard: a future time rounds to the nearest unit instead of cutting down, so a request that expires 7 days from now reads "Expires in 7 days" the moment after it is made (it read "in 6 days"); formatRelative "in 6d" becomes "in 7d" the same way. Past times still count completed units ("6 days ago"). docs · 6ca36dd

[0.25.33] — October 14, 2026

📦 npm

  • @vegastack/design → 0.8.10 (was 0.8.9).
  • The design-system registry (@vegastack/ui) bumps 0.25.32 → 0.25.33.

🐛 Fixed

  • TextEdit: in collaboration, two people typing at the same spot no longer get one side's characters reversed (12345678 landing as 87654321). After a collaborator's change the caret now stays exactly where Yjs puts it whenever its text still exists, instead of the binding re-placing it by its old offset in a paragraph whose text changed. docs · 85d4bea

[0.25.32] — October 14, 2026

📦 npm

  • @vegastack/design → 0.8.9 (was 0.8.8).
  • The design-system registry (@vegastack/ui) bumps 0.25.31 → 0.25.32.

🐛 Fixed

  • text-edit-markdown / TextEdit: inline code inside a link, bold, italic or strikethrough ([`code`](/x), **`code`**) no longer throws on parse — the code mark now combines with other marks. Link titles are kept for inline and reference-style links ([text][ref] + [ref]: url "Title"), written back as inline links with their title; corpus cases for both. docs · 4b0d50d

[0.25.31] — October 14, 2026

🧩 New components

  • TextEdit: collaboration={{ document, provider, user, peer }} edits a shared Yjs document live — collaborators' carets and selections in their tag hue with their name (an agent in purple), the identity taken only from peer(clientId) (the server's word), never from what a client writes into awareness, ⌘Z undoing only this viewer's changes, nothing serialized per keystroke, and the editor mounted at once (read-only viewers watch it move; readCopy paints first). A toggle's chevron is each viewer's own ("Open by default" in its ⋯ menu sets it for everyone), a page, task or file mention is stored without its title (pasted and imported ones too) and labelled per reader by the new mentionLabel, a comment made here carries anchor.y Yjs positions (TextEditAnchor, kept by onAnnotationsLayout) that keep the highlight on its words and orphan it once they are deleted, uploads in flight survive collaborators' edits, an open slash or @ menu survives them too, and block ids are given only to blocks made here (a document's own are the server's). The slash and @ menus take spaces and digits (/heading 2, /head2, @Asha Rao); handleRef.pendingUploads() and onPendingUploadsChange report uploads still landing. AutoSaveIndicator adds connecting and offline; InlineChip and mentions add the agent kind ("Your agents"). docToMarkdown(doc, { labels }) fills label-less mentions. New dependencies yjs, y-protocols, @tiptap/y-tiptap and @tiptap/extension-collaboration(-caret), in the editor's lazy chunk. docs · e5a1833

📦 npm

  • @vegastack/design → 0.8.8 (was 0.8.7).
  • The design-system registry (@vegastack/ui) bumps 0.25.30 → 0.25.31.

⚠️ Breaking

  • TextEdit: Escape in a document editor now keeps the text, commits and blurs (it reverted unless autosave was set) — reverting a document erased text people had typed; boxed and composer fields still revert, and escapeBehavior still chooses either way. A readOnly editor is now a document (role="document") rather than a read-only text box: no aria-readonly or aria-multiline, so assistive tech announces content, not a field. docs · e5a1833

[0.25.30] — October 14, 2026

🧩 New components

  • TextEdit: its document lives in three server-safe registry libs, so a server reads and writes exactly what the editor does. text-edit-grammar holds VegaStack Markdown's constructs as plain strings (parseMentionLink, mentionMarkdown, MENTION_LINK_SQL, headingIds, parseImageAlt, imageMarkdown, parseCodeFenceInfo — moved here from MarkdownView and CodeBlock); text-edit-schema every node, mark and attribute with its Markdown rules (textEditExtensions(), textEditSchema()), with a stable blockId on every heading, paragraph, list item, toggle, callout, table and code block that Markdown never carries; text-edit-markdown the conversion (markdownToDoc, docToMarkdown, normalizeMarkdown, docToText) and the editor's VegaStackMarkdown extension. Each TextEdit parses with its own marked, and a megabyte of Markdown parses in well under a second (it took over twenty). A round-trip corpus checks every construct in Node and in Chromium against the same output. Tiptap moves to 3.31.4. docs · 46e19d5

📦 npm

  • @vegastack/design → 0.8.7 (was 0.8.6).
  • The design-system registry (@vegastack/ui) bumps 0.25.29 → 0.25.30.

⚠️ Breaking

  • TextEdit Markdown output changes once for existing documents (derived copies are rewritten on their next save): text is escaped only where Markdown would read it as syntax (snake_case, R&D and a * b stay as typed; 1. at the start of a paragraph is 1\. ); a hard break is \ and a newline, or <br> in a heading or a table cell; a table cell's leading and trailing <br> are dropped; a link with a space in its address is written <…>. Parsing no longer loses text: an indented code fence is a code block, Hi. there is a sentence, a less-indented ordered item stays, &amp; in a link is &, and raw HTML is text in the browser as it is on a server. MarkdownView reads <br> as a line break everywhere, not only in a table cell. docs · 46e19d5

[0.25.29] — October 14, 2026

🧩 New components

  • Agents get one identity across people surfaces, and ApprovalCard is new.

    • Person.kind adds "agent" (an AI assistant working for a member), with via naming the member. PersonAvatar draws an agent as the Bot icon on the purple tag hue. The new PersonName writes the name as "Claude Code via Manoj" with the purple "Agent" label (PersonAgentLabel), and a screen reader hears "Claude Code via Manoj, agent". personDisplayName ("Claude Code (via Manoj)") and personAccessibleName give the same name as plain text. PersonCard, PersonHoverCard, AvatarStack, Comments, ActivityEvent and PeoplePicker rows all use it.
    • CommentData adds tone: "question" (amber bar, "Needs your reply") and choices. Choices render as buttons under the body when the item, thread or list gets onChoose. ActivityKind adds delegated, undelegated, agent-queued, agent-working, agent-needs-you, agent-done, agent-failed and agent-step, each drawn in its status tone.
    • PeoplePickerOption adds disabledReason, which lists the option disabled with the reason as its second line. The list props add heading ("Your agents").
    • ApprovalCard (shadcn add @vegastack/approval-card) shows a risky action an agent asked to take: who asks, the action, the records it touches ("and N more"), the reason, a reassurance, "Requested 2m ago · Expires in 5 days", and one Approve / Decline for the whole request. It covers the pending, approving, approved, declined, expired, cancelled and already-answered states, plus a "Waiting for Manoj's approval" view for anyone who can't decide.
    • Deprecated: CommentData.agent and ActivityEvent's agent prop still work for one release; give the author or actor kind: "agent" instead. b82b754

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.25.28 → 0.25.29.

[0.25.28] — October 14, 2026

🧩 New components

  • Field: FieldCounter — a character counter for Input and Textarea at the end of the label row (FieldHeader), so it takes no room under the control. <FieldCounter value={bio} maxLength={100} threshold={0.8} /> shows 92/100 (12px, muted, tabular) only from threshold of maxLength (default 0.8), in the destructive ink at the limit. Inside a Field it describes the control (aria-describedby) while it shows, and a polite status speaks only at its thresholds ("8 characters left", "Character limit reached"), never per keystroke. docs · e142894

🔧 Changed components

  • DataList and DataGrid: fill and fit columns. The first column takes the width the others leave, wraps anywhere and (DataList) clamps a plain value at two lines with the full value in its tooltip; every other column is sized to its content on one line, and headers never break inside a word. One long unbroken value no longer squeezes every cell and header ("Prod" / "ucts"): the squeeze releases only the first column, then DataList merges another column, and only when nothing is left to merge does it strain (data-strained) the rest. columnCellClass, headerCellClass and SortableHead take a fill role, isNowrapColumn defaults non-first columns to one line (nowrap: false lets one wrap between words), and revealColumns takes extraOverflow. BreadcrumbTrail's current step truncates with its full name in a tooltip on hover and focus; PageHeader's title breaks an unbroken token anywhere, clamped or not. docs · e142894

📦 npm

  • @vegastack/design → 0.8.6 (was 0.8.5).
  • The design-system registry (@vegastack/ui) bumps 0.25.27 → 0.25.28.

[0.25.27] — October 13, 2026

🔧 Changed components

  • TemplateEditor and TextEdit: the @ / {{ placeholder picker and the slash and mention menus are now one shared SuggestionMenu (@vegastack/suggestion-menu, installed with either editor). Rows look exactly like ComboboxItem's — the accent highlight on hover and arrows (visible in dark mode), the same padding, radius, gap, icon sizing and muted trailing text — with group headings, a status for empty, loading and error, a flip above the caret, and the textbox's aria-activedescendant on the active row. Escape in TemplateEditor's picker no longer reaches a host (a dialog stays open). TemplateEditor adds singleLine (Enter inserts nothing, pasted line breaks become spaces) and replacements ([{ from: "*", to: "×" }], applied as you type) for formulas. docs · f5f16fe

📦 npm

  • @vegastack/design → 0.8.5 (was 0.8.4).
  • The design-system registry (@vegastack/ui) bumps 0.25.26 → 0.25.27.

[0.25.26] — October 13, 2026

🔧 Changed components

  • Searchable Select, Select: an opt-in wrap shows a long value whole. SearchableSelect wrap, and SelectTrigger wrap with SelectContent wrap: the trigger's fixed height becomes its minimum and the value runs onto as many lines as it needs, and long options wrap in the list instead of being cut off (API-35). Off by default; both write data-wrap. docs · 77051bc

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.25.25 → 0.25.26.

🐛 Fixed

  • Dialog, Alert Dialog, Sheet, Drawer, Popover: a long name stays inside the overlay. DialogContent and AlertDialogContent lay out on one minmax(0, 1fr) column (grid-cols-1), so a no-wrap child such as a select showing a long value no longer widens the popup past its cap and pushes the field and footer out (LAY-17). All five surfaces set wrap-break-word, so a long unbroken word breaks inside them. While a dialog or sheet shows its corner close ×, its header keeps clear of it, so a long title wraps before the × instead of running under it; the alert dialog header (with or without media) and a sheet header with an action wrap a long title instead of overflowing. DialogTitle is leading-snug instead of leading-none, so a wrapped title's lines no longer collide (TYP-19). docs · 77051bc

[0.25.25] — October 12, 2026

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.25.24 → 0.25.25.

🐛 Fixed

  • Multi-step form: a step body now stays mounted (hidden) after its first visit, so a move no longer replaces the node, resets its state or re-runs its effects. The entrance animation plays only on a move, never on first paint. docs · 15325c6

[0.25.24] — October 12, 2026

🔧 Changed components

  • Attachment: AttachmentGroup layout="cards" (so FileCard and UploadCard) now wear the same surface as MediaCard — card background, border, radius, p-2 pe-3 padding and the subtle hover — instead of a bare transparent row; MediaCard exports RECORD_CARD_SURFACE_CLASS, the one surface both share. Other layouts are unchanged. docs · eff079d

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.25.23 → 0.25.24.

[0.25.23] — October 12, 2026

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.25.22 → 0.25.23.

📚 Docs

  • File Card: fix the docs page, whose prose braces broke the static export. docs · f5091de

[0.25.22] — October 12, 2026

🧩 New components

  • FileCard and UploadCard: new — one file as a card (thumbnail or type icon, name, description lines, a ⋯ actions menu) that opens the file viewer, or with href (+ download, target) makes the whole card a link to a download or a page; UploadCard is the same row for a file uploading, with progress, Retry and Cancel. Install with @vegastack/file-card. docs · 5e37f27

🔧 Changed components

  • Attachment: AttachmentGroup takes layout="cards" — the list row in a responsive grid (three columns on a wide container, two on a tablet, one on a phone) with a 48px thumbnail, name, description lines and trailing actions. Existing layouts are unchanged. docs · 5e37f27

📦 npm

  • @vegastack/design → 0.8.4 (was 0.8.3).
  • The design-system registry (@vegastack/ui) bumps 0.25.21 → 0.25.22.

[0.25.21] — October 12, 2026

🔧 Changed components

  • FilterBarFacet, SearchableSelect: a FilterBarFacet with 7 or fewer options no longer shows a search box (server-searched remote lists always do). The new searchable prop overrides it on both components; SearchableSelect still defaults to showing search, so nothing changes unless you opt in. docs · 2f97a7a

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.25.20 → 0.25.21.

[0.25.20] — October 12, 2026

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.25.19 → 0.25.20.

🐛 Fixed

  • Stepper, Toast, Toggle: the current step now carries the same 1px transparent border as the clickable steps, so the node and label no longer shift by a pixel when the step changes. A bottom Toaster stack also rises by --toaster-offset-bottom (default 0px) — set it on :root while a sticky footer is mounted so toasts stop covering it. A pressed Toggle and ToggleGroupItem name their foreground ink instead of inheriting it, so a selected chip stays at AA contrast on the muted fill. docs · 5fb69c2

[0.25.19] — October 11, 2026

🧩 New components

  • ValueChange: new inline before → after diff of one value — the old value struck through in a <del> (destructive ink), a muted arrow, the new value in an <ins> (success ink, no underline). Only after renders an addition, only before a removal; screen readers hear "changed from A to B", "added B" or "removed A". Server-safe. docs · 0a40ba0

🔧 Changed components

  • Stepper: a navigable step is now one target — node and label together — so the circle is as clickable as the text and the hover fill covers both. Clearer states: the current step alone is the solid primary circle with a medium-weight label (its bold width reserved, so nothing shifts), a completed step is a check on a muted primary tint with its connector in the same tone, upcoming stays an outline. An overflowing horizontal rail scrolls the current step into view. The non-interactive step box is data-slot="stepper-item"; the stepper-node-rail wrapper is gone. No prop changes; MultiStepForm picks this up as is. docs · 0a40ba0

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.25.18 → 0.25.19.

[0.25.18] — October 11, 2026

🧩 New components

  • SortableList: align="start" keeps the handle, the × and the ⋯ on a row's first line instead of its middle, for tall rows such as a card whose title line sits over an expandable body. docs · 5523f81

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.25.17 → 0.25.18.

[0.25.17] — October 11, 2026

🔧 Changed components

  • LoadMore: keyset lists load on scroll — no Load more button anywhere. A sentinel at the list's end fetches the next cursor page as it nears its scroll container's edge, skeleton rows hold the new rows' place while it loads, and one page is requested at a time. onLoadMore/hasMore/loading/error keep their meaning; skeleton and rootMargin are new and label is deprecated. A failed page still shows its error with Try again. DataList and DataGrid append skeleton rows to the table body, board lanes show skeleton cards, SearchableSelect pages inside its option list with skeleton options, VersionList pages on scroll, RecordListMore loads the next batch on scroll instead of a Show more button, CommentList loads older comments on scroll instead of Load earlier, ActivityFeed shows skeleton rows instead of a busy button, and useAsyncSearch's loadMore.loading is now true only for a next page. docs · 02a8c1b

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.25.16 → 0.25.17.

[0.25.16] — October 11, 2026

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.25.15 → 0.25.16.

🐛 Fixed

  • ShareDialog: Space access's mode is a quiet pill like RecordChip's ghost form — no border, a tint and a ▾ on hover. generalAccess.level is optional: leave it out when members open the item with their own space role, and no level shows. While everyone in a named space can open the item, the footer reads "Members of Sales and people with access can open it" (new copyLinkHintSpace label) instead of "Only people with access can open it". docs · 474c6aa

[0.25.15] — October 11, 2026

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.25.14 → 0.25.15.

🐛 Fixed

  • ShareDialog: the invite picker's search field reads "Search people or teams", since it lists both. A new inviteSearchPlaceholder label sets it. docs · 4397871

[0.25.14] — October 11, 2026

🧩 New components

  • Add PeoplePicker, the one picker for people and teams; it replaces PeopleInput.

    • PeoplePicker (shadcn add @vegastack/people-picker) chooses one person or, with multiple, several people and teams. Rows are the avatar, the name (the viewer's reads "(you)" and leads), then the email, a description or "Team · N people". The trigger matches Button and Select heights at sm, default and lg, so it sits flush in an "Add people… | Can edit | Add" row. Footer actions ("Assign to me", "Unassign"), leadingOptions ("Unassigned"), exclude, viewerId and a fixed options list are built in; loading shows skeleton rows, never a spinner.
    • Only active people are listed: an option with active: false is hidden unless includeInactive, which badges it "Inactive".
    • PeoplePickerContent is the popover body for a trigger the host owns (a record pill); PeoplePickerMenu is the same list inside a DropdownMenu submenu ("Assign ›").
    • FilterBarPeopleFacet (in filter-bar) is a person filter chip over the same body.
    • ⚠️ Breaking: people-input is removed — use PeoplePicker multiple. ShareDialog's search is now a PeoplePickerSearch loader that resolves { items, nextCursor? } (it was Promise<Option[]>), and its invitees are PeoplePickerOptions. The invite field is a picker trigger that reads the chosen names, not a chips field. 2f9bce4

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.25.13 → 0.25.14.

[0.25.13] — October 11, 2026

🧩 New components

  • DataList: keyboardNavigation gives a page's main list a row cursor. J or ↓ and K or ↑ move it, Enter opens the cursor row, and Escape clears it. The cursor wears the keyboard-focus cue (the hover wash and the 2px start edge) and follows the mouse. cursorId and onCursorChange give a host with its own keys the same cue. A row link that gets focus back, for example when a drawer opened from it closes, no longer paints a rounded grey box behind the first cell. SortableList: a header row above the list, full width, for an add field whose new value goes first, and flush rows with no hover wash or side padding, for rows that hold their own bordered input. docs · c01155a

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.25.12 → 0.25.13.

[0.25.12] — October 11, 2026

🔧 Changed components

  • Re-publish @vegastack/design so its @vegastack/design-tokens range follows the token release that adds the gradient-spark utilities. 1005935
  • Add the gradient-spark utility (a subtle purple to blue gradient from the theme-aware tag hues) and stop-spark-from / stop-spark-to for SVG gradient stops, so a stroked icon such as What's new can carry the same gradient in light and dark. c0140d4

📦 npm

  • @vegastack/design → 0.8.3 (was 0.8.2).
  • @vegastack/design-tokens → 0.8.3 (was 0.8.0).
  • The design-system registry (@vegastack/ui) bumps 0.25.11 → 0.25.12.

[0.25.11] — October 10, 2026

🧩 New components

  • Add ShareDialog as an installable component, with canPublish and hints.

    • ShareDialog — the Share dialog from the share-01 block — is now its own component. Add it with shadcn add @vegastack/share-dialog (it installs to components/ui/share-dialog.tsx) and keep it current through the integrity flow instead of maintaining a copy. Every prop and type the block's dialog exported is unchanged.
    • canPublish gates the Publish tab's controls (the switch, Reset, Expires and Stop publishing) separately from canManage, and follows canManage when unset.
    • hints places an optional help node, such as an InfoHint, right after the People with access heading, the Space access heading and the Publish label.
    • The share-01 block now imports the dialog from @/components/ui/share-dialog and lists it as a registry dependency; its demo and page are unchanged. 461515f

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.25.10 → 0.25.11.

[0.25.10] — October 10, 2026

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.25.9 → 0.25.10.

🐛 Fixed

  • NumberField: the − and + steppers draw one divider each against the input, inside the field's single border. Before, each stepper painted its own full box, doubling the outer edge and the dividers in light and dark. The divider no longer nudges a pixel on press. Tabs: a click on a panel's empty space no longer tints the whole panel once a key is pressed (⌘ alone was enough in Chromium, turning a drawer body grey). A panel reached with the keyboard still shows the focus tint. SortableList: a new footer row, such as an "Add a value" field and its Add button, lines up with the rows' content, between the handle gutter and the × gutter. docs · d4abc18
  • BoardCard and Board: every band starts at the card's edge. That covers the eyebrow, the status icon (not its hit area), the context, the chips and the footnote. A wrapped title hangs under its own first line beside the status, and stops at two lines with an ellipsis. Bands keep fixed heights: the eyebrow and title line are 20px, and the chips and footnote are 24px with an even 8px gap. A card without chips or without an assignee keeps the same rhythm. On a Board, only the first band leaves room for the ⋯ menu (data-board-card-menu). The rows under it run to the card's end padding, so a footnote avatar sits flush and in line with the ⋯, which moves onto the end padding. The loading card mirrors these bands. docs · 1681ebe
  • TruncatedText: a single line is centred inside its 24px target floor instead of sitting at the top. The current breadcrumb crumb now shares a vertical centre with the links and chevrons without an app-side override. docs · 71082c6

[0.25.9] — October 10, 2026

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.25.8 → 0.25.9.

🐛 Fixed

  • Board and ActionListSkeleton — a loading lane's skeleton cards follow the card's bands (the eyebrow, the title, a chip, then the footnote with the assignee at its end) instead of a chip row with the avatar. ActionListSkeleton's title and meta bars no longer touch: each sits in its text line's height, so a loading row keeps the loaded row's height with a gap between the bars. docs · 96ce883
  • EditableCell, useInlineEdit, TextEdit and MarkdownView — clicking a value to edit it puts the caret where you clicked (a keyboard open puts it at the end) instead of selecting the whole value; useInlineEdit's start takes the opening pointer event for this. A TextEdit with citation shows its read view whenever it is not being edited: a preloaded editor waits for a click instead of swapping in on mount, and the read view comes back once focus leaves the editor and its menus and panels (after the blur commit, never while an upload is in flight), so [[n]] markers read as citations again. MarkdownView also recognises a marker the editor's Markdown escaped (\[\[n\]\]). docs · d32e54e
  • Share dialog (share-01) — switching between Share and Publish no longer leaves the other tab's controls painted for a beat (the Publish switch over the Invite button, or Invite and Copy link over the Publish row). The hidden panel and the hidden footer hide with visibility, which upstream's transition-all on Button, Switch and Select animated; descendants of a hidden box now carry no transition, so they hide in the same frame. docs · dcee40f

[0.25.8] — October 10, 2026

🔧 Changed components

  • BoardCard drops its leading gutter: the completion tick or status control now sits inline before the title (a wrapped title wraps under itself), and the eyebrow, context, chips, alert and footnote start at the card's own edge instead of being indented to the title. docs · 88ed5f6

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.25.7 → 0.25.8.

[0.25.7] — October 9, 2026

🧩 New components

  • BoardCard gains quieter Linear-style slots — eyebrow, aside, chips, alert, footnote and titleWeight (normal by default beside a status control) — and exports BoardCardChip, a thin bordered pill with no fill that renders as a menu trigger through render. Board takes a column icon, addPlacement="header" (the add button as an icon beside the lane's ⋯) and trailing content after the last lane; cards sit on a slightly raised surface. DataList passes them through: a section's icon and actions, addPlacement and boardTrailing. docs · 28f1696

🔧 Changed components

  • Drawer floats inset by default (OVL-21): --spacing(2) off every viewport edge, every corner rounded and a full border, in all four directions, as shadcn's inset styles ship it. Pass flush on Drawer for the previous edge-to-edge sheet. Width overrides are unchanged (w-*/max-w-* on DrawerContent), and a different inset is [--drawer-inset:--spacing(4)]. docs · 679d19b

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.25.6 → 0.25.7.

[0.25.6] — October 9, 2026

🔧 Changed components

  • StatusIcon keeps Linear's shapes but now colours each status with the system's standard semantic tokens instead of Linear's palette: todo → foreground, progress → warning-text, review → info-text, done → success-text (was purple); backlog, canceled and duplicate stay muted-foreground, triage stays tag-orange-text. docs · c751e87

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.25.5 → 0.25.6.

[0.25.5] — October 9, 2026

🔧 Changed components

  • StatusIcon — Linear's workflow status set, drawn as 16-unit glyphs instead of lucide icons: backlog (dashed ring), todo (ring), progress (half pie, yellow), review (three-quarter pie, green), done (filled check, purple), canceled (filled cross, grey), duplicate (ring with diagonal strokes, grey) and triage (filled left-right arrow, orange). blocked and cancelled keep working as deprecated aliases; the default todo label reads "Todo". docs · 478de35

📦 npm

  • @vegastack/design → 0.8.2 (was 0.8.1).
  • The design-system registry (@vegastack/ui) bumps 0.25.4 → 0.25.5.

[0.25.4] — October 9, 2026

🔧 Changed components

  • EmptyMedia variant="icon" is a larger, bordered muted plate: 48px (rounded-xl) with a 24px text-muted-foreground icon, and 40px / 20px (rounded-lg) in the compact size="sm" Empty. f1f73fd
  • ActivityJumpToLatest and the Transcript's "Back to current line" float only while the reader scrolls away from the latest (or current) line and fade once they settle; hovering or focusing holds them. Text Edit's upload placeholder spins the system Loader glyph instead of a CSS ring. dd0e424

🗑 Removed / renamed

  • EmptyIllustration is removed: empty states use Empty with EmptyMedia variant="icon" and a lucide icon only. Replace <EmptyIllustration name="tasks" /> with <EmptyMedia variant="icon"><ListTodo /></EmptyMedia> (or pass the icon to Empty's icon prop). f1f73fd

📦 npm

  • @vegastack/design → 0.8.1 (was 0.8.0).
  • The design-system registry (@vegastack/ui) bumps 0.25.3 → 0.25.4.

[0.25.3] — October 9, 2026

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.25.2 → 0.25.3.

🐛 Fixed

  • SkeletonRows (the DataList and DataGrid loading rows) sizes each placeholder bar from its column's minWidth budget and follows the column's alignment, with an icon-sized square for a narrow ⋯ column, so a loading table's columns settle where the loaded table's do instead of jumping when the rows arrive. 7b3e423

[0.25.2] — October 9, 2026

🧩 New components

  • email-kit gains EmailMarkdown: long-form Markdown (meeting notes) as email-safe blocks in the in-app notes' reading styles — no injected HTML, safe links only, dark-mode hooks. 9b22921

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.25.1 → 0.25.2.

🐛 Fixed

  • InlineEditTrigger (cell layout) is block-level, so it centres on a table row instead of sitting on the text baseline; the inline date and select editors drop the dark-mode resting border. 9b22921

[0.25.1] — October 9, 2026

🧩 New components

  • Whole-cell inline editing: InlineEditTrigger (in editable-cell) makes a list cell's whole value — icon and text — the button that opens an app-owned status, priority or assignee editor, with no border at rest and the properties-panel tint on hover, focus and while open. EditableCell's date and select editors use the same tint (no hover border, the select trigger is ghost) and fill a cell variant. BoardCard gains renderField to make its assignee, due and priority fields editable the same way. IconGlyph without a hue now takes the sidebar nav icon ink instead of full foreground. 4a2b49b

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.25.0 → 0.25.1.

[0.25.0] — October 8, 2026

🧩 New components

  • Motion and interaction primitives: motion-stagger utility (staggered reveal for content replacing a skeleton), useViewTransition/startViewTransition (View Transitions with reduced-motion and unsupported fallbacks), useOptimisticAction (apply now, Undo toast, commit after the window), LevelMeter (live recording input level), EmptyIllustration (shared monochrome empty-state set), EditableCell date editor, and DataList density with the new DensityToggle. 5760011

🔧 Changed components

  • Status alerts (info, success, warning, destructive) now tint their border (30%) and background (5%, 10% in dark) instead of keeping the neutral card; an outline button inside one wears the variant colour at rest. Padding is 12×10px, and with an action and no title a single-line description sits centred on the 32px button. The Alert docs gain a Status surfaces matrix. 59300fb

📦 npm

  • @vegastack/design → 0.8.0 (was 0.7.111).
  • @vegastack/design-tokens → 0.8.0 (was 0.7.80).
  • The design-system registry (@vegastack/ui) bumps 0.24.5 → 0.25.0.

[0.24.5] — October 8, 2026

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.24.4 → 0.24.5.

🐛 Fixed

  • Mention chips keep their avatar inside the chip, at every text size, and never spellcheck.

    • The photo no longer drops out of the chip in rendered text and the editor: a prose root's picture rule (block, margin, border) reached the avatar's <img>; the chip now pins it to the circle.
    • Initials scale with the text (55% of the chip's size, not a fixed 12px), so "MA" fits its circle in a comment, a paragraph or a heading. The avatar is 1.1em, an inline-block centred on the label, so it no longer drops by half its height when it shows initials.
    • A chip is spellcheck="false", and in TextEdit its node view is also contenteditable="false": no red underline under a name.
    • A long name still breaks inside a narrow column (the prose root's wrap-anywhere), so a chip never overflows it.
    • New InlineChipPerson.inactive: the name is followed by the person surfaces' small muted outline "Inactive" badge, and the chip carries data-inactive. The label text is unchanged, so comment anchors keep matching. 93f7d5d

[0.24.4] — October 8, 2026

🛠 CLI & tooling

  • Searchable pickers get a footer for their static actions, and the editor, space picker and pill calendar get review fixes (Regent new-task review, OVL-20).

    • CommandActions (new): the last child of CommandList, a sticky footer behind a hairline for a picker's static actions ("Assign to me", "Unassign", "Clear"). Its CommandItems are never filtered and never counted as results, so "No results" still shows with the actions under it; the arrow keys reach them after the last result; the list's scroll padding keeps the active row clear of the footer.
    • PanelList and PanelActions (new, in panel-search): the scrolling list under a PanelSearch row and the matching sticky footer for menus. A row action with both submenu and items now renders items as that footer below the submenu content (they used to sit above the search).
    • SpacePicker rows are one line (tile and name; a disabled reason muted at the end); SpacePickerItem.secondary is deprecated and no longer shown. placement="title" is an outlined, rounded chip, with no "› New task" after it; SpaceChip readOnly size="title" is the same outlined shape without the ▾, and a read-only chip now passes its other props (a tooltip trigger's) to its span.
    • TextEdit: the bubble menu no longer opens over a selection with no text (select all in an empty editor), and it follows its selection when a dialog or panel around the editor scrolls, hiding while the selection is out of view. In a narrow frame (a Dialog's 16px padding) the content takes just enough start padding that the block grip sits clear of the text and the caret.
    • RecordChipMenu: a Calendar inside it is transparent, so its square box no longer paints over the popup's rounded border. 164172b

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.24.3 → 0.24.4.

🐛 Fixed

  • IconPicker keeps focus in its own search when its trigger sits in an InputGroupAddon, and its footer and tabs line up.

    • Clicks inside the popup no longer bubble (through the portal) to an InputGroupAddon around the trigger, which used to move focus to the field's input while you typed a search. Opening by pointer or keyboard now focuses the search field; touch keeps the default.
    • The footer is one row that never wraps: "No colour" and the ten hues on the left, Remove as a compact trash icon button (aria-label "Remove icon", tooltip "Remove") on the right.
    • The checked swatch shows the selected cell's 1px primary border, rounded, instead of a ring.
    • Both tabs share one width sized to the grid (w-79, compact w-62.5, with 6px cell gaps), and both browse eight categories: emoji flags leave the category bar and sections but stay searchable (new PickerPanel searchOnly prop). EmojiPicker uses the same grid width. 00ff4bd
  • InputGroupAddon focuses its input only when the click started inside the addon itself, so choosing an item in a menu (or any portalled popup) opened from the addon no longer pulls focus into the field (INT-14). 48956d1

  • ActivityEvent, VersionList and CommentThread's "Resolved by" header show a person's badge after their name, like every other place a person is named. An actor, version author or resolver with badge: "Inactive" (someone deactivated, or whose invite is pending) now reads "Jamie Rao Inactive changed status…" with the small muted outline badge from PersonBadge, instead of dropping it. activity-feed and version-list now depend on searchable-select (comments already did). ac500f1

[0.24.3] — October 7, 2026

🛠 CLI & tooling

  • IconPicker picks colour from inline swatches in a sticky footer instead of a popover beside search.

    • In icon mode the footer row starts with a "No colour" swatch (clears the hue), then the hues, as one "Icon colour" radiogroup; Remove sits on the right when onRemove is passed. The emoji tab shows only Remove, and the footer is omitted when empty.
    • Line tabs sit over a full-width hairline, search runs full width, and section headers stay pinned while the grid scrolls.
    • PickerPanel's searchAction prop is deprecated. 5b0d8db

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.24.2 → 0.24.3.

[0.24.2] — October 7, 2026

🔧 Changed components

  • Add InfoHint, a footer slot on ShortcutOverlay, and a linkable email footer note.

    • InfoHint is a muted icon-only info button that opens one sentence in a popover, with an optional href link that opens in a new tab. label names the trigger ("About spaces"). Add it with shadcn add @vegastack/info-hint.
    • ShortcutOverlay takes an optional footer, rendered below the shortcut list.
    • The email kit's EmailLayout footer takes noteHref, which links "VegaStack" in the default "Sent with VegaStack" note, and note now accepts inline content. 95d592a

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.24.1 → 0.24.2.

[0.24.1] — October 7, 2026

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.24.0 → 0.24.1.

🐛 Fixed

  • Keep the full floating Jump to latest button inside its scrolling viewport, including bottom padding. 6959fb9

[0.24.0] — October 6, 2026

🔧 Changed components

  • Jump to latest follows an explicit content boundary and keeps held pointer clicks stable.

    The host can capture a batch at activation and acknowledge it after that same target is reached. e8d0004

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.23.128 → 0.24.0.

🐛 Fixed

  • Keep Jump to latest pointing in the selected feed direction when its boundary is already visible. 11defd8

[0.23.128] — October 6, 2026

🛠 CLI & tooling

  • Verify generated picker helper mirrors during version-package releases.

    • Allow only provenance-header changes for the four registered picker helper mirrors; runtime edits remain rejected. 2753a93

📦 npm

  • @vegastack/design → 0.7.111 (was 0.7.110).
  • The design-system registry (@vegastack/ui) bumps 0.23.127 → 0.23.128.

⚠️ Breaking

  • Choose consistent icon and emoji identities with shared search, recents and a nested hue palette.

    • Add IconPicker, IconGlyph and a 160-icon server-safe catalogue.
    • Share panel geometry and visual-row navigation with EmojiPicker.
    • Replace filled swatch checks with persistent cell borders and balanced colour grids.
    • Remove SpaceAvatar; compose generic glyphs with existing space controls and app-owned presentation adapters. ff3d9fb

[0.23.127] — October 6, 2026

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.23.126 → 0.23.127.

🐛 Fixed

  • Activity cursor loading preserves the current reading position in both orders, including when older pages regroup visible changes or the reader scrolls during the request. 812ebf4

[0.23.126] — October 6, 2026

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.23.125 → 0.23.126.

🐛 Fixed

  • Activity icons align with their first text line, and pre-grouped changes render as one visible sentence with a single actor and timestamp. Cursor pages can load automatically at the history edge while preserving the reader's position, with an explicit retry after a failed read. c402dbc

[0.23.125] — October 6, 2026

🔧 Changed components

  • Inbox rows say more at a glance: an unread row carries a small dot in its start gutter (beside the tint and the sr-only "Unread:"), and InboxItem takes a badge — a small glyph on the avatar's corner naming the kind of event (assigned, a mention, a comment), decorative so the title carries the meaning. SettingsSection keeps its actions (an autosave status, a button) on the title row beside a long description instead of wrapping them onto a row of their own. A person InlineChip's initials no longer break onto two lines in a narrow, break-anywhere column (a mention in a comment on a phone). 101ba5e

📦 npm

  • @vegastack/design → 0.7.110 (was 0.7.109).
  • The design-system registry (@vegastack/ui) bumps 0.23.124 → 0.23.125.

[0.23.124] — October 5, 2026

🧩 New components

  • Add the email-kit block: server-rendered HTML email that installs into components/email/ (shadcn add @vegastack/email-kit).

    • Three templates, every string a prop: NotificationEmail (one event — heading, quotes, a record card, one button), DigestEmail (grouped rows and "Open inbox") and ActionEmail (an invite or reset — button, raw-link fallback, expiry note).
    • EmailLayout and its parts — EmailHeading, EmailText, EmailLink, EmailButton, EmailCard, EmailQuote, EmailItems, EmailDivider — on React Email's tables: a text wordmark, a 600px fluid card, the system font stack, a 46px bulletproof button that goes full width on a phone, and a footer with the reason, preference links and "Sent with VegaStack".
    • Light colours inline; dark mode from prefers-color-scheme and, in its own style block, the Outlook.com [data-ogsc]/[data-ogsb] hooks; a 600px ghost table for classic Outlook. The colours are the design tokens converted to hex by tooling/generate-email-tokens.mjs, AA in both modes and never pure white or black; design:verify fails if email-tokens.ts drifts.
    • renderEmail(element) returns { html, text }; each template renders to about 10 KB.
    • The docs page shows every template light and dark and carries the "Writing notifications" rules for in-app, push and email copy. docs · 66439bc

📦 npm

  • @vegastack/design → 0.7.109 (was 0.7.108).
  • The design-system registry (@vegastack/ui) bumps 0.23.123 → 0.23.124.

[0.23.123] — October 4, 2026

🧩 New components

  • Add ActivityFeed, a record's history in Linear style: comment threads and change events in one list.

    • ActivityFeed is the section: an "Activity" heading with a count, an All / Comments / Activity filter and an Oldest / Newest first toggle, and pending to dim the list while a new filter loads. ActivityFeedList and ActivityFeedItem hold the rows: events 4px apart, a thread card or a divider 12px.
    • ActivityEvent is one event — a 20px icon (an ActivityKindIcon, a StatusIcon or PriorityIcon, or the actor's avatar), the actor's name opening their hover card ("System" without an actor), the sentence with ActivityValues, and the relative time. ActivityKindIcon covers twenty common kinds.
    • ActivityEventGroup folds a run of events to the first with "N more changes"; "Show less" folds them back.
    • ActivityUnreadDivider is the "New" line before the first unread item and calls onVisible once it is half on screen; ActivityJumpToLatest floats "Jump to latest" while the feed's end is off screen; ActivityFeedSkeleton is the loading shape.
    • useActivityFeedKeyboard: J / K move between items, X toggles the focused one, Esc clears; keys typed in a field, a menu or a dialog are ignored.
    • PersonHoverCard takes trigger="name": the person's name, inline, is the trigger.
    • Comments: the author's name opens their person hover card, and agent: true on a comment shows a violet bar, a bot avatar when there is no image, and an "Agent" label. adedbe1

📦 npm

  • @vegastack/design → 0.7.108 (was 0.7.107).
  • The design-system registry (@vegastack/ui) bumps 0.23.122 → 0.23.123.

[0.23.122] — October 4, 2026

🔧 Changed components

  • Comments match Linear's spacing: rows sit 16px in from the card's sides and 12px from top and bottom, avatars are 20px (a reply's body indents 28px, and the hairline between replies starts at the text), and the reply row is a 44px row — a 20px avatar, "Leave a reply…", attach and Send at its end. The composer card and the skeleton take the same 16px sides. 71219d1

📦 npm

  • @vegastack/design → 0.7.107 (was 0.7.106).
  • The design-system registry (@vegastack/ui) bumps 0.23.121 → 0.23.122.

[0.23.121] — October 4, 2026

🔧 Changed components

  • Comments take the Linear-style thread: one card per thread, indented replies, a flat reply row and a bordered composer with a toolbar row.

    • CommentThread is one rounded-lg card with no padding of its own; its comments are rows 12px in from its sides. The first comment's body runs the full width, and each reply sits over a hairline with its body indented to the name. hiddenReplies + onShowReplies add a full-width "Show N more replies" row between the first comment and the replies (with a spinner while a promise settles), and onHideReplies adds "Show less". The reply row sits flat at the card's foot: the viewer's avatar, a one-line box with no border of its own, and attach and Send. highlightedId tints a comment in the thread.
    • CommentComposer is a bordered card: the text on top (two lines at rest), the draft's files, and a toolbar row with attach and the round ↑ Send at its end. Files dragged over any comment box with onAttachFiles show a dashed "Drop files to attach" overlay. Send's tooltip names the shortcut.
    • CommentItem: the hover actions (add reaction, ⋯) sit at the end of a header row. Edit puts ✕ Cancel and ✓ Save in that header, with the body becoming a compact box. The ⋯ menu adds Copy text (onCopyText) and, on a touch screen inside a thread, Reply, which focuses the reply row. "(edited)" replaces "· edited". A deleted comment keeps its author and time over "This comment was deleted.". Reaction pills get an add button after them. A standalone comment card is rounded-lg, and variant="reply" indents the body.
    • CommentList puts the composer under the heading while newest first and names the day between comments posted on different days (dayDividers, on by default). New parts: CommentListHeader, CommentDayDivider and useCommentDayDividers, for hosts that lay out their own threads. CommentListSkeleton now draws two thread cards and the composer. 36baad3

📦 npm

  • @vegastack/design → 0.7.106 (was 0.7.105).
  • The design-system registry (@vegastack/ui) bumps 0.23.120 → 0.23.121.

[0.23.120] — October 4, 2026

🧩 New components

  • Add UploadPanel, UploadItem and UploadGroup, the upload-progress lib, and 25 file kinds in file-kind.

    • UploadPanel is the uploads card pinned bottom-end: a header with the batch, time left and bytes, collapse and close; file rows with a thumbnail or file-type icon, a middle-truncated name that keeps its extension, a destination link (onOpen) and a state icon (clock, percentage ring with × on hover, spinner while finishing, check, alert with Retry, Choose file when interrupted); folder rows with aggregate progress that expand. Closing while uploads move asks first. A clean finish shrinks it into a done card that dismisses itself after 8 s, paused on hover or focus; failures stay. Below 768px (or with compact) it is a bar that opens the list in a bottom sheet. It publishes --upload-panel-inset while on screen.
    • The upload-progress lib: createRateEstimator (EWMA of acknowledged bytes, alpha 0.15 over 1 s ticks, ignores the first 3 s, reports after 5 s and 2%, moves only on a 15% change), formatTimeLeft ("About 2 minutes left") and formatBytesProgress ("2.1 MB of 3.4 MB").
    • file-kind adds the data, json, config, script, cad, photometric, design, ebook, email, calendar, contact, key, encrypted and font kinds, each with an icon, tint and label. CSV and TSV are now data, JSON is json and YAML is config; rtf is a document by extension and by MIME type, and a .ts labelled video/mp2t is code. FileViewer still reads all of these as text.
    • The Toast stack's bottom positions rise by --upload-panel-inset, so a toast never covers the upload panel. cf43d17

📦 npm

  • @vegastack/design → 0.7.105 (was 0.7.104).
  • The design-system registry (@vegastack/ui) bumps 0.23.119 → 0.23.120.

[0.23.119] — October 4, 2026

🧩 New components

  • AppShellSection takes align: "start" | "center" (default start). center puts a measure="prose" section in the middle of the page, so a settings tab under a full-width header reads like a settings page; align is ignored at full. c48726d

📦 npm

  • @vegastack/design → 0.7.104 (was 0.7.103).
  • The design-system registry (@vegastack/ui) bumps 0.23.118 → 0.23.119.

[0.23.118] — October 4, 2026

📦 npm

  • @vegastack/design → 0.7.103 (was 0.7.102).
  • The design-system registry (@vegastack/ui) bumps 0.23.117 → 0.23.118.

🐛 Fixed

  • Align share-01's Share and Publish tabs with the dialog's content edge. The underline and the label of the first tab now start where the title and the rows below start, in the Dialog (they sat 4px and 10px in) and in the phone sheet (they sat 12px and 6px outside the 16px content inset). The tabs drop the line list's padding and the trigger's inline padding, sit 16px apart, and take the sheet's 16px inset. 28b06eb

[0.23.117] — October 3, 2026

📦 npm

  • @vegastack/design → 0.7.102 (was 0.7.101).
  • The design-system registry (@vegastack/ui) bumps 0.23.116 → 0.23.117.

🐛 Fixed

  • Keep the share-01 dialog the same height when switching between its Share and Publish tabs. Both panels stay mounted in one grid cell, with the inactive one invisible and inert, and the Share tab's footer keeps its space on the Publish tab. The dialog and the phone sheet are as tall as the taller panel, whether the people list is long or empty. f05e02b

[0.23.116] — October 3, 2026

🧩 New components

  • Give toasts a second action, the emoji picker a footer, and the space and share parts a way to name a space the viewer cannot see.

    • Toast: data.actions takes one or two { label, onClick } buttons after the text, with the type icon kept, each closing its toast once taken unless data.keepOpen. The toast manager's data is typed as ToastData (also exported, with ToastActionItem), so a second action needs no hand-rendered data.render body.
    • EmojiPicker: a footer slot renders a row at the bottom of the panel, such as "Remove icon".
    • New SpaceHint type, spaceHintLabel() and SpaceHintIcon in space-avatar name a hidden space by kind — "Priya's My space" or "Private space" — never by name. AccessChip gains { kind: "hidden", hint }, SpaceChip gains hint, and share-01's generalAccess.spaceHint turns the Space access row into a plain statement with no mode or level.
    • share-01 shows a one-line "Published to the web" notice on the Share tab while published, with Manage opening the Publish tab.
    • Removed unneeded API: AccessChip labels / AccessChipLabels (the hidden kind replaces the relabelling), SpacePicker labels / SpacePickerLabels, and share-01's unused share, publicSection, publicHeading, publicOff, stopSharing, done and unknownSpace labels. SpacePicker's empty trigger now reads "Space: none chosen", and the toast Action example no longer closes its own toast by hand. 84a7db2
  • Add the spaces and sharing parts Regent needs, restructure the Share block around an explicit Publish switch, and fix the audit's review findings.

    • New ResponsiveDialog (a Dialog from 768px up, a bottom sheet with a handle and a safe-area footer below), SpaceChip and SpacePicker (My space first, a check on the current space, disabled rows that say why, placement="title" for [General ▾] › New task), AccessChip (one focusable chip for a record's reach that opens Share) and ChoiceCard / ChoiceCardGroup.
    • share-01 has Share and Publish tabs at the new md dialog size. Copy never publishes: the Share tab's footer is "Copy link", and only the "Publish to the web" switch calls onCreatePublicLink; "Stop publishing" confirms. Built-in rows are locked with their reason, a personal space reads "My space", defaultTab opens on Publish, and onInvite may resolve false to keep the chips.
    • Dialog gains size="md" (512px). PermissionMenu gains variant (ghost, chip, outline), locked and lockedReason, and two-line descriptions. SidebarGroupAction gains showOnHover. SpaceAvatar gains the 16px 2xs size for sidebar rows, showLock, a person-with-lock tile for a personal space and the SpaceIcon (Layers) export. ColorPicker gains HUE_COLORS, variant="inline" and a None swatch through onClear. AppShellSection with measure="prose" holds a start-aligned 720px column. AvatarStack gains total and footer, overlaps by 4px and draws initials from letters only. The comment composer puts attach beside Send on the right.
    • The settings guidance and settings-01 move to autosave, with confirmation for consequential changes and explicit Save only for drafts that must apply together. DataList's own empty state is the only empty state in a list, and its empty row keeps an inset.
    • Board keyboard moves out of a droppable: false lane work again; date components render a clock-free absolute date without a request reference and re-render only when their label can change; refresh={false} freezes at mount; pickers mark the provider zone's today and presets take a timeZone; MultiStepForm never resolves a step index of -1; unsupported pastes show a toast; stale DataGrid focus requests expire; grid cards with their own controls are labelled groups; and stale focus-border and toggle-group docs are corrected. 1e016bc

📦 npm

  • @vegastack/design → 0.7.101 (was 0.7.100).
  • The design-system registry (@vegastack/ui) bumps 0.23.115 → 0.23.116.

[0.23.115] — October 3, 2026

📦 npm

  • @vegastack/design → 0.7.100 (was 0.7.99).
  • The design-system registry (@vegastack/ui) bumps 0.23.114 → 0.23.115.

🐛 Fixed

  • Preserve permissions, editor content and interaction state across asynchronous work, and make design verification report the behavior it actually checks.

    • Guard board drops, serialize pending moves, cancel lazy playback, retain wizard locks and support keyboard activation of grid cards and virtualized cells.
    • Normalize image URLs, bound thumbnail inflation, preserve unsupported paste selections and honor React callback-ref cleanup.
    • Render relative timestamps from a shared request reference and live clock; hide unavailable comment composers without hiding replies.
    • Preserve no-ring focus while raising the existing text-entry border tint to the first standard opacity that passes the unchanged contrast threshold.
    • Protect cleanup and idempotency checks, verify negative-test identity, load credential-only dotenv values and correct generated installation/API/example guidance and docs demos. 7152602
  • Keep calendar dates and due labels live across viewer midnight, with stable request-time hydration.

    • DateTime and DueLabel use the existing shared clock; explicit DateTime options.now stays controlled and formatting/tooltip choices remain intact.
    • Use the bundled full Chromium headless channel for component verification after repeated legacy headless renderer crashes. CI retains native failures and reports only process/signal metadata, never memory contents. 7152602
  • Keep date-picker Today highlighting and application date groups on the shared viewer clock.

    • DateRangeFilter forwards its explicit calendar-day reference to the custom Calendar.
    • Export useDateTimeNow from the relative-time registry item for groups, badges and picker presets, preserving serialized SSR references and one shared live timer. 7152602

[0.23.114] — October 2, 2026

🔧 Changed components

  • board and data-list: new canMoveItem prop locks single items — a view-only record in an editable view. On Board, canMoveItem(item) returning false means the card cannot be picked up by pointer, touch or Space, keeps the default cursor (no cursor-grab), carries data-move-locked and drops the "Draggable card" role description; it still focuses, opens, keeps its ⋯ actions, and other cards move around it. On DataList, canMoveItem(row) makes that row (and grid card) no drag source with data-move-locked, leaves it out of a dragged selection, keeps it openable and a drop target, and passes through to the board view. The docs gain a "Locked cards" board example and a locked row in the drag-into example. 992e9f3

📦 npm

  • @vegastack/design → 0.7.99 (was 0.7.98).
  • The design-system registry (@vegastack/ui) bumps 0.23.113 → 0.23.114.

[0.23.113] — October 2, 2026

🔧 Changed components

  • InboxFilters gains extra: app-defined chips after All and Unread, each { value, label, count? } (new InboxFilterOption type; InboxFilter widens to accept their values), so an inbox shows All · Unread 3 · Requests 2 without a second control. A chip's count is now separated by a space in its accessible name ("Unread 3"). notifications-01 adds a Requests chip for rows that ask for a decision. cce47ed
  • share-01: new generalLevelReadOnly prop on ShareDialog shows General access's level as text while a manager can still switch between "Everyone in ‹space›" and "Only people invited". defaultInvitees is now reactive — when it changes (compared by id) the invite chips follow it, so a "Share with Priya" prompt can open the mounted dialog with Priya chosen; chips the viewer edits stay until the prop changes again. The dialog body's section gap moves from the off-scale gap-5 to gap-4. b19f577

📦 npm

  • @vegastack/design → 0.7.98 (was 0.7.97).
  • The design-system registry (@vegastack/ui) bumps 0.23.112 → 0.23.113.

[0.23.112] — October 1, 2026

🧩 New components

  • Sharing parts. PersonAvatar gains kind: "team" (a rounded-square tile with the team icon on its hue), and AvatarStack stacks and lists teams beside people. New SpaceAvatar (a space's tile — icon or initial on its hue, a corner lock when private, the lock itself for a personal space) with SpaceOption, the space row for pickers and menus. New PeopleInput: add people or teams as removable chips, searched as you type through the host's search(query), with Backspace removing the last chip. New PermissionMenu: an access level with one-line descriptions, a check on the current one, an optional destructive Remove access, and a read-only mode for built-in rows. New block share-01: a data-agnostic Share dialog (a bottom Sheet on phones) with an invite mode that gives the whole batch one level, people with access and why, general access in the item's space, and a view-only public link with copy, reset, expiry and Stop sharing. 2df5784

📦 npm

  • @vegastack/design → 0.7.97 (was 0.7.96).
  • The design-system registry (@vegastack/ui) bumps 0.23.111 → 0.23.112.

[0.23.111] — October 1, 2026

📦 npm

  • @vegastack/design → 0.7.96 (was 0.7.95).
  • The design-system registry (@vegastack/ui) bumps 0.23.110 → 0.23.111.

🐛 Fixed

  • Composer controls never touch the border: the paperclip and Send are 24px and sit 4px in from the border on every side (still 32px tall at rest, pinned to the last line as it grows). A record's new-comment box (CommentComposer without replyingTo) reserves two lines at rest; reply boxes stay one line. 1dbc57e

[0.23.110] — October 1, 2026

🔧 Changed components

  • Comments match the shared web/mobile spec. A 24px avatar 8px from the name, 4px from the name row to the body (or files), 6px from the body to the files, 12px between comments; thread cards have a 1px border, a light fill and 4px padding (CommentThread too). New CommentMedia: a comment's (or a draft's) images as a wrapping row of rounded previews up to 200px tall that open the FileViewer. The composer follows the mobile layout: paperclip on the left, Send on the right, both centred on the line and 6px in, on a light fill (TextEdit's composer gains leading). Reaction pills always have a light border. InlineChipPerson takes hue (initials on the member's colour, as comment avatars) and cardImage; a person chip always leads with their avatar. 0df640e

📦 npm

  • @vegastack/design → 0.7.95 (was 0.7.94).
  • The design-system registry (@vegastack/ui) bumps 0.23.109 → 0.23.110.

[0.23.109] — October 1, 2026

📦 npm

  • @vegastack/design → 0.7.94 (was 0.7.93).
  • The design-system registry (@vegastack/ui) bumps 0.23.108 → 0.23.109.

🐛 Fixed

  • TextEdit composer is 32px at rest (the editor surface's 24px minimum no longer adds 4px) and an empty footer takes no room; a click on an image selects it. 04a041e

[0.23.108] — October 1, 2026

📦 npm

  • @vegastack/design → 0.7.93 (was 0.7.92).
  • The design-system registry (@vegastack/ui) bumps 0.23.107 → 0.23.108.

🐛 Fixed

  • TextEdit type-checks against Tiptap 3.31.4: the paragraph parser no longer calls Paragraph.config.parseMarkdown (whose this type changed), it builds the paragraph itself. c367765

[0.23.107] — October 1, 2026

📦 npm

  • @vegastack/design → 0.7.92 (was 0.7.91).
  • The design-system registry (@vegastack/ui) bumps 0.23.106 → 0.23.107.

🐛 Fixed

  • TextEdit read view and editor are pixel-identical. A callout lays out in one row with its icon at the same x in both (the read icon no longer spans two rows; the editor's tone button takes the icon's 16px column), and a read-only code block's language label sits where the editor's picker does. The swap browser test now compares every block of a rich document (headings, lists, tasks, quote, callout, code, table, toggle, file chip, rule) — position, height and first-character x — between the read view and the editor. cc31e7f

[0.23.106] — October 1, 2026

📦 npm

  • @vegastack/design → 0.7.91 (was 0.7.90).
  • The design-system registry (@vegastack/ui) bumps 0.23.105 → 0.23.106.

🐛 Fixed

  • TextEdit swap. A variant="composer" box mounts its editor at once (it is an input — no read view to click through first), and every editable TextEdit fetches the editor when the page goes idle, so the first click swaps it in immediately. A new browser test holds the read view and the editor to the same first-line position, height and caret start (within half a pixel) for documents and composers, empty and with text. a7fdd5a

[0.23.105] — October 1, 2026

🔧 Changed components

  • TextEdit polish. Inline code keeps a neutral muted chip with orange (tag-orange-text) text, Slack/Notion-style, in every markdown surface. The callout tone menu shows each icon in its tone colour; the callout's tone button is centred on the first text line, keeps its tone colour while pressed or open, and takes a light tone-tinted hover wash. Toggles use Notion's filled triangle (▶, turning to ▼ in 150ms) in a muted icon button with a hover wash, here and in MarkdownView. 3a257c9

📦 npm

  • @vegastack/design → 0.7.90 (was 0.7.89).
  • The design-system registry (@vegastack/ui) bumps 0.23.104 → 0.23.105.

[0.23.104] — October 1, 2026

🔧 Changed components

  • TextEdit: images are blocks and a composer variant. An image is a block of its own (still ![alt|width](src) in markdown; an image inside a paragraph is lifted into its own block on load, and MarkdownView shows it the same way). Dragging an image moves it between blocks with the block drop line (2px, info blue); pasted, dropped or inserted images, videos and audio land after the current block. A selected image has a 2px info-blue ring at its radius and handles in the same colour, with no ring on hover. New variant="composer" with actions and footer: a box that looks exactly like Input at rest, grows line by line to about ten lines then scrolls, actions pinned bottom-right, file cards under the text. CommentComposer, replies and in-place edits use it; the old composer styles are gone. c9140ce

📦 npm

  • @vegastack/design → 0.7.89 (was 0.7.88).
  • The design-system registry (@vegastack/ui) bumps 0.23.103 → 0.23.104.

[0.23.103] — October 1, 2026

📦 npm

  • @vegastack/design → 0.7.88 (was 0.7.87).
  • The design-system registry (@vegastack/ui) bumps 0.23.102 → 0.23.103.

🐛 Fixed

  • TextEdit fixes from a live check. Slash and mention menu items use DropdownMenuItem's look (accent highlight, radius, padding, shortcut text), and the active item is marked data-selected="true" so apps that map the variant to ="true" show it. Callouts render their text as the Alert's description, in the tone colour, with the tone icon in colour. Code blocks scroll unwrapped lines sideways in the editor too (Tiptap's content element no longer forces pre-wrap). A closed toggle hides only its body, not its title. Images: a thin outline at the image's own radius when selected, Notion-style side handles only, no extra spacing, and a blurred hover toolbar (Open, Download, Copy link, ⋯ Replace / Delete). A file chip opens in the FileViewer on a double-click or from the link panel's Open. e69fa24

[0.23.102] — October 1, 2026

🧩 New components

  • New InlineChip: the one inline reference — a person, page, file, task, meeting, customer or project — for TextEdit, MarkdownView and Comments. It is an inline box on the text's baseline (no more label riding above the line), sized by the surrounding text with a 1em icon, tinted per kind from the tag hues, and wraps cleanly. A person chip shows its photo and previews avatar, name and email on hover or focus; other chips open on click (onOpen, e.g. a file in FileViewer, else href), ⌘/Ctrl-click opens a new tab, and preview adds a small hover card (InlineChipPreview). InlineChipProvider resolves href, person, preview and onOpen for every chip below it. MentionChip and file links now render through it, and mentions gain the meeting, customer and project kinds. 610784a

📦 npm

  • @vegastack/design → 0.7.87 (was 0.7.86).
  • The design-system registry (@vegastack/ui) bumps 0.23.101 → 0.23.102.

[0.23.101] — October 1, 2026

🔧 Changed components

  • TextEdit blocks and media. Code blocks always show the language picker and get icon-only wrap and copy buttons (a check confirms the copy); wrap is saved after the language in the fence info string (ts wrap) and CodeBlock takes wrap; unwrapped lines scroll inside the block with a thin scrollbar. Editor tables use a fixed layout, so typing wraps inside the cell. Toggles get a clickable chevron that opens and closes them (saved as <details open>), with hints in an empty title or body; MarkdownView draws the same chevron. New video and audio blocks (<video src> / <audio src>, native players, also in MarkdownView); a dropped video or audio file lands as one. The slash menu's Image, Video, Audio and File open an insert panel with Upload and Link tabs. 35824b2

📦 npm

  • @vegastack/design → 0.7.86 (was 0.7.85).
  • The design-system registry (@vegastack/ui) bumps 0.23.100 → 0.23.101.

🐛 Fixed

  • TextEdit menus and callouts. The slash menu has one active item that the pointer and ↑/↓ share (pointer cursor on items), scrolls it into view inside the list only, and lists commands in one canonical order wherever a subset is passed (comments and full editors match). The selection, link and image menus open just above the caret or selection without page scroll jumps. Callouts render as Alerts in the editor and in MarkdownView and gain GitHub's [!IMPORTANT] and [!CAUTION]; the tone icon opens a menu. Inline code in prose is tinted orange (tag-orange-text on tag-orange-subtle). b2d18f3

[0.23.100] — October 1, 2026

🔧 Changed components

  • Comment boxes. One line of text is exactly the controls' height, so a comment or reply box stays the Input height (32px) whether focused or not — no extra space when the editor loads — with the Input type size (16px on a phone, 14px from md); the attach and send buttons stay on the last line as the text grows. The paperclip is muted, so Send carries the emphasis. 47bbd1c

📦 npm

  • @vegastack/design → 0.7.85 (was 0.7.84).
  • The design-system registry (@vegastack/ui) bumps 0.23.99 → 0.23.100.

[0.23.99] — October 1, 2026

🔧 Changed components

  • TextEdit images resize with Tiptap's ResizableNodeView (corner and side handles, aspect kept, snap guides at ¼ ½ ¾ and full width, touch included), keep their width in Markdown as ![alt|320](src) and in HTML as width, and carry a ⋯ menu — Open, Download, Copy link, Remove image. Open, a double-click, and a click on an image in a read-only document or a posted comment show it in the FileViewer, paging through the document's images; ImageViewerScope does the same around any MarkdownView, which now renders the stored width. ec194c9

📦 npm

  • @vegastack/design → 0.7.84 (was 0.7.83).
  • The design-system registry (@vegastack/ui) bumps 0.23.98 → 0.23.99.

[0.23.98] — October 1, 2026

🔧 Changed components

  • Comments. The comment and reply boxes take the Input radius (rounded-lg) instead of a near-pill rounded-xl. CommentList shows no "No comments yet" line by default — the composer alone; emptyText still adds one. Delete in a comment's ⋯ menu calls onDelete at once, without a confirmation dialog (hosts offer Undo). A box's files (the draft's attached files) now render inside the box at its bottom, under the text, as the same cards a posted comment shows — not above the box; with onAttachFiles, pasted or dropped files join them instead of going into the text. CommentComposer and CommentThread's composer take hasFiles: a draft with attached files can be sent without text. Reaction pills are lighter: a soft hairline border with no fill, and a light primary tint when reacted, in both themes. c8965a5

📦 npm

  • @vegastack/design → 0.7.83 (was 0.7.82).
  • The design-system registry (@vegastack/ui) bumps 0.23.97 → 0.23.98.

[0.23.97] — October 1, 2026

🔧 Changed components

  • Comment boxes and code blocks. A code block's language label and Copy button get their own room above the code (codeBlockControlsPadClassName) instead of covering the first line, in CodeBlock and in TextEdit. TextEdit takes slashHint (default on); comment boxes turn it off, so "Add a comment…" and "Write a reply…" stay while focused instead of "Type / for commands". A press on a TextEdit box's padding before its editor has loaded now focuses the read view, so text typed while the editor loads is kept and replayed with the caret at its end. ba00a25

📦 npm

  • @vegastack/design → 0.7.82 (was 0.7.81).
  • The design-system registry (@vegastack/ui) bumps 0.23.96 → 0.23.97.

[0.23.96] — October 1, 2026

🔧 Changed components

  • File chips, inline images and comment files. MarkdownView and TextEdit take fileContentType(href) so a file chip's icon follows the file's content type when the host knows it, not only the name's extension. TextEdit takes inlineImageTypes (default JPEG, PNG, WebP, GIF and AVIF): a pasted, dropped or picked file of another type — an SVG, a HEIC photo — goes to onFileUpload and lands as a file chip instead of an inline image. CommentComposer (and CommentThread's composer) take onAttachFiles, which receives the attach button's files instead of inserting them into the text (pasted and dropped images stay inline), and files, shown over the box for the draft's attached files and their upload progress; mentionImage reaches the comment boxes and bodies, and CommentItem, CommentThread and CommentList pass fileContentType to each body; CommentList takes renderAttachments. b15a39b

📦 npm

  • @vegastack/design → 0.7.81 (was 0.7.80).
  • The design-system registry (@vegastack/ui) bumps 0.23.95 → 0.23.96.

[0.23.95] — October 1, 2026

🔧 Changed components

  • Comments and editor polish. CommentComposer and the reply box are one line and 32px tall at rest, with a paperclip attach button (shown when onImageUpload/onFileUpload is set; picked files go through the editor's own upload flow) and Send inline on the bottom line as the text grows; replyingTo/onCancelReply show a "Replying to" line (× or Escape cancels and keeps the text) and the "Write a reply…" placeholder. CommentList with no comments shows a muted "No comments yet" line with the composer under it (no Empty illustration or "Add a comment" step). Replies are flush rows inside their comment's card (no ms-9 indent), avatars are 28px, long names truncate, and composer buttons get 40px touch targets. Mention chips are inline-flex on the info tint (bg-info/10, text-info-text, hover bg-info/15; restricted stays muted) with a centred icon, and a person chip shows a 16px avatar via the new mentionImage prop on MarkdownView and TextEdit. TextEditHandle gains uploadFiles and pickFiles. The block drag handle is a 16px grip inset from the frame edge, and the drop line (the shared dropIndicatorClasses, SortableList included) is info blue and inset. Paste parses Markdown from the plain-text flavour when the HTML only wraps code (VS Code, a lone pre), turns a single fenced block into a code block, and Mod-Shift-V pastes plain text. CodeBlock and the editor's code block are Notion-style: no border or header bar, bg-muted/60, 13px mono, the language (a searchable picker while editing) and Copy on hover, lowlight syntax highlighting with alias normalisation, Tab/Shift-Tab indent and any-language fence input rules. utilities.css adds token-mapped .hljs-* colours for both themes. AutoSaveIndicator's saved check is muted ink instead of green, and errors show in warning ink. ffdee11

📦 npm

  • @vegastack/design-tokens → 0.7.80 (was 0.7.79).
  • @vegastack/design → 0.7.80 (was 0.7.79).
  • The design-system registry (@vegastack/ui) bumps 0.23.94 → 0.23.95.

[0.23.94] — October 1, 2026

📦 npm

  • Ships beside @vegastack/design-tokens 0.7.79 (the INT-13 comment relabel in base.css), so its @vegastack/design-tokens range moves with it. No code change. 4db7d90
  • The design-system registry (@vegastack/ui) bumps 0.23.93 → 0.23.94.

📚 Docs

  • base.css: the no-iOS-focus-zoom rule now names its own register row, INT-13 (MK 2026-09-26), instead of INT-9 (the grab-cursor decision), and the file's header list includes it. Comment only; no CSS change. c01c929

[0.23.93] — September 30, 2026

🔧 Changed components

  • base.css stops the page bouncing under a mouse or trackpad (INT-12): on pointer: fine, html, body get overscroll-behavior-y: none, so the page no longer rubber-bands at its top and bottom edges and an inner scroller at its end no longer chains into the page. Vertical only, so a trackpad two-finger swipe still goes back/forward; touch devices keep pull-to-refresh and the native bounce. 8825076

📦 npm

  • @vegastack/design-tokens → 0.7.78 (was 0.7.56).
  • @vegastack/design → 0.7.78 (was 0.7.77).
  • The design-system registry (@vegastack/ui) bumps 0.23.92 → 0.23.93.

[0.23.92] — September 30, 2026

🔧 Changed components

  • DataList passes selectionSecondaryActions and selectionMoreLabel to its selection bar's ActionBar (as secondaryActions and moreLabel). BreadcrumbTrail takes renderCurrent to render the current step's content, and a step's itemProps spread onto its BreadcrumbItem (data-* attributes, onDragOver/onDrop). 4663fa5

📦 npm

  • @vegastack/design → 0.7.77 (was 0.7.76).
  • The design-system registry (@vegastack/ui) bumps 0.23.91 → 0.23.92.

[0.23.91] — September 30, 2026

🔧 Changed components

  • AutoSaveIndicator takes variant="icon": the status as one 16px icon for a field's trailing slot — a muted spinner while saving, a success check once saved that fades out when the status returns to idle, and a destructive warning triangle on error (warning ink on conflict). Each state pops in (fade plus a 0.9 → 1 scale), the box is always reserved, and the wording stays as screen-reader text in the live region. In a textarea InputGroup, an inline addon now sits top-aligned with the first line instead of mid-height (FRM-16), so a status icon lands in the top-right corner. f45d422

📦 npm

  • @vegastack/design → 0.7.76 (was 0.7.75).
  • The design-system registry (@vegastack/ui) bumps 0.23.90 → 0.23.91.

[0.23.90] — September 30, 2026

🧩 New components

  • MarkdownView takes citation: a [[n]] marker (n = 1–999) renders as a small superscript n — a muted pill button named by the source's label that opens its content on hover, keyboard focus or a long press and calls onSelect on a click or tap. A marker without a source, every marker without the prop, and markers in code or link text stay literal. TextEdit passes citation to its read view, where pressing a marker never starts editing. Transcript takes reveal={{ id, key }}: the row scrolls to centre, takes the accent wash for a moment, and following pauses. b688b9e

📦 npm

  • @vegastack/design → 0.7.75 (was 0.7.74).
  • The design-system registry (@vegastack/ui) bumps 0.23.89 → 0.23.90.

[0.23.89] — September 30, 2026

🔧 Changed components

  • Design review fixes. The TableOfContents rail opens as a solid, raised panel over the page (z-20, a shadow). DataList's mergedLayout="line" is one truncating meta line: avatars in it are hidden, and a value that renders nothing, "" or "—" is dropped with its dot. Keyboard focus in a DataList row now washes the whole row and paints a 2px start bar on its first cell (a background layer, FOC-13). ActionBar stays inside the viewport and takes secondaryActions, which fold into a ⋯ menu (moreLabel) below sm. VideoPlayer keeps its controls, and the play button, shown on a coarse pointer. FileTypeIcon takes tinted, and the new FileKindTile (with FILE_KIND_LABEL) is a no-preview card's picture. MarkdownView and TextEdit take headingScale="document". Menus size to their longest item, so items never wrap (OVL-19). The new BreadcrumbTrail folds its middle steps into a "…" menu before it shortens the current step. FileViewer reads Excel workbooks (sheet tabs, the first 500 rows) and Word documents itself, with SheetJS and mammoth imported only when such a file opens, up to 20 MB. 6886ed1

📦 npm

  • @vegastack/design → 0.7.74 (was 0.7.73).
  • The design-system registry (@vegastack/ui) bumps 0.23.88 → 0.23.89.

[0.23.88] — September 30, 2026

🔧 Changed components

  • AlertAction now lays its children out in a wrapping flex row with a gap-2 gap, so two buttons (a "Resend invite" beside a "Revoke invite", say) go straight inside it with no ButtonGroup or wrapper div, and wrap onto a second line when the alert is narrow. The Alert docs gain a Two actions example. 552698a

📦 npm

  • @vegastack/design → 0.7.73 (was 0.7.72).
  • The design-system registry (@vegastack/ui) bumps 0.23.87 → 0.23.88.

[0.23.87] — September 30, 2026

📦 npm

  • @vegastack/design → 0.7.72 (was 0.7.71).
  • The design-system registry (@vegastack/ui) bumps 0.23.86 → 0.23.87.

🐛 Fixed

  • AudioWaveform fits its bar count to its width (2px a bar, 1px apart), so many peaks or a large bars in a narrow card never thin to nothing. A comment reopened for editing from the ⋯ menu starts from the host's retained draft (editDefaultValue) instead of the stored body; CommentList and CommentThread pass it through the same way. f98f37c

[0.23.86] — September 30, 2026

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.23.85 → 0.23.86.

🐛 Fixed

  • RecordChip — the development-only "value is ignored with person" warning now fires from a module-level helper instead of reassigning a module variable during render, so the component passes React's react-hooks/globals lint rule in consuming apps. Behaviour is unchanged: one warning per page load, none in production. docs · d63eb17

[0.23.85] — September 30, 2026

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.23.84 → 0.23.85.

🐛 Fixed

  • SearchableSelect — a person option can no longer render two avatars and two emails. With the person contract (itemToStringLabel for the name, itemToSecondaryLabel for the email, itemToAvatar for the Person), the component draws each option as one PersonOption and the trigger as one avatar plus the name; renderItem and renderValue are ignored there, with a one-time development console.warn when they are passed. renderItem is now optional and defaults to itemToStringLabel. FilterBarFacet follows the same rule, and RecordChip warns in development when value is passed with person (the person already wins). docs · 6d5ec6f

[0.23.84] — September 30, 2026

📦 npm

  • @vegastack/design → 0.7.71 (was 0.7.70).
  • The design-system registry (@vegastack/ui) bumps 0.23.83 → 0.23.84.

🐛 Fixed

  • A sidebar item's showOnHover action no longer overlaps its count. SidebarMenuAction now writes the data-show-on-hover attribute the badge swap selects on — Base UI lower-cases state keys, so it had only ever written data-showonhover and the API-33 swap never engaged. At rest the count shows; on hover, keyboard focus or while the action's menu is open the action takes the count's place, with no layout shift or motion. Touch keeps both side by side: below md, and now also on a coarse pointer at any width, the action stays visible and the count sits just before it. ceb5c59

[0.23.83] — September 30, 2026

📦 npm

  • The audio player loads less. AudioPlayerProvider, useGlobalPlayer, useGlobalPlayerTime and GlobalAudioPlayer now live in audio-player-global (still re-exported from audio-player): import them from there in an app shell and the player UI loads only once a recording opens, so routes that never play one ship no player code. AudioPlayer takes deferControls: a light play button (and the still waveform with peaks) until the first play, then the full controls with focus on their play button, for lists of recordings. fae3c2e
  • @vegastack/design → 0.7.70 (was 0.7.69).
  • The design-system registry (@vegastack/ui) bumps 0.23.82 → 0.23.83.

[0.23.82] — September 30, 2026

📦 npm

  • @vegastack/design → 0.7.69 (was 0.7.68).
  • The design-system registry (@vegastack/ui) bumps 0.23.81 → 0.23.82.

🐛 Fixed

  • Dates no longer differ between the server and the browser. RelativeTime's locale defaults to DEFAULT_LOCALE (en-US, now exported from the date-time lib) instead of the runtime's, so a server under a non-US LANG renders the same text as the browser and the page hydrates cleanly. This covers every DS part that shows a RelativeTime (inbox, comments, version list, media card, item, data list, truncated text). DatePicker's display and a comment's "Edited" time use the same default. aed72d5

[0.23.81] — September 30, 2026

🧩 New components

  • Library redesign gaps. AppShellHeader is a width container (@container/app-shell-header), so header tools compact by the header's own width. FileViewer takes actions(item): your controls in the top bar before Download, kept at icon size below sm. One media plays at a time: starting an AudioPlayer, VideoPlayer, the global player or a FileViewer stage pauses any other that is playing, with exclusive={false} to opt out. While open, GlobalAudioPlayer sets --dock-inset-bottom on the AppShell root; ActionBar floats above it, and a selecting DataList ends with a spacer so its last row and Load more stay reachable. useDragInto takes onDropFiles and onFilesOver: its targets take files dragged in from the desktop (folders walked, with relativePath), and an outer useFileDrop surface leaves that drop alone. DataList passes them through as onDropFilesOnRow and onFilesOverRow, and FolderTree as onDropFiles and onFilesOver. The dense lg grid drops to 1 column below a 20rem container, and the lg grid skeleton is the MediaCard lg shape (16:9 and two lines). RecordLayout takes stack: below 64rem the rail flows under the main column, with one scroller. Comments: CommentItem takes editDefaultValue, and CommentList and CommentThread take editingId, onEditingIdChange and editDefaultValue(commentId), so an unsaved edit survives a remount. 375e445

📦 npm

  • @vegastack/design → 0.7.68 (was 0.7.67).
  • The design-system registry (@vegastack/ui) bumps 0.23.80 → 0.23.81.

🐛 Fixed

  • TextEdit keeps what you typed when it is hidden and shown again (React's <Activity>), and image uploads still insert afterwards. It no longer offers block commands (headings, lists, quote, callout, toggle, code block, table, divider) or "Turn into" while the cursor is in a table cell. MarkdownView renders <br>, <br/> and <br /> inside table cells as line breaks; other raw HTML still shows as text. A FolderTree folder with hasChildren: false is a leaf: a spacer in place of its disclosure, data-leaf on the row, and → does nothing on it. 375e445

[0.23.80] — September 30, 2026

🧩 New components

  • Library grid and sidebar tree. FolderTree takes dragScope: its folders and section headings accept rows and cards dragged from a DataList in the same scope, and its own rows drop onto that list's folder rows and onto BreadcrumbDropTargets; every drag now carries the host's own ids. canDropInto adds the host's rule after the tree's own (and is the only rule for ids the tree does not hold), FolderTreeSection.href makes a section heading a link (active when activeId is the section's id) with its open/close on a chevron beside it, and the labels gain toggleSection and itemCount. DataList takes gridDensity="dense": 2 columns, then 3, 4 and 5 as the container widens at gridSize="lg". MediaCard takes imageBadge, a chip over the image's bottom end ("▶ 1:24") that never takes the click. audio-player exports AudioWaveform, the player's waveform as still, aria-hidden bars for cards. bdf75c0

📦 npm

  • @vegastack/design → 0.7.67 (was 0.7.66).
  • The design-system registry (@vegastack/ui) bumps 0.23.79 → 0.23.80.

[0.23.79] — September 30, 2026

🔧 Changed components

  • Comments keep drafts. CommentComposer takes defaultValue, the editor's starting text (Send is enabled while it is non-empty; the box starts empty after a post). CommentThread's composer passes defaultValue (the reply box opens unfolded on it), onValueChange (every change, and "" after a reply posts) and posting (busy, OR'd with the reply's own state: Send and Cmd/Ctrl+Enter do nothing, e.g. while an upload runs). CommentItem, CommentList and CommentThread take onEditValueChange(commentId, value), called with the edit box's text on every change and with null when the edit is saved or cancelled, so a host can guard unsaved edits. c478509

📦 npm

  • @vegastack/design → 0.7.66 (was 0.7.65).
  • The design-system registry (@vegastack/ui) bumps 0.23.78 → 0.23.79.

[0.23.78] — September 30, 2026

📦 npm

  • @vegastack/design → 0.7.65 (was 0.7.64).
  • The design-system registry (@vegastack/ui) bumps 0.23.77 → 0.23.78.

🐛 Fixed

  • TextEdit no longer erases saved text on Escape in a document that saves as you type. A new escapeBehavior prop — "revert" (restore the document as focus found it, blur, onRevert) or "blur" (keep the text, commit, blur) — defaults to "blur" whenever autosave is set and to "revert" otherwise. handleRef gains markSaved(), which advances the baseline Escape reverts to (and a commit compares against) after a host's own acknowledged save. d43ea00

[0.23.77] — September 30, 2026

📦 npm

  • @vegastack/design → 0.7.64 (was 0.7.63).
  • The design-system registry (@vegastack/ui) bumps 0.23.76 → 0.23.77.

🐛 Fixed

  • CommentList takes mentionHref and passes it to every comment, so mention chips in top-level comments link as they already did in CommentThread. A top or bottom SheetContent now stops at 85dvh (API-21), so a long body — a comment thread on a phone — scrolls inside SheetBody with the footer in view instead of running past the screen. 8e6abe4

[0.23.76] — September 30, 2026

🔧 Changed components

  • FileViewer previews every file type an app stores: text and code files show their first 1 MB, a CSV or TSV is a table (a built-in parseCsv; the header row and at most 500 rows, "Showing first 500 rows"), Markdown and MDX render through MarkdownView (loaded lazily, JSX never run), and Office and other files are a card with the file's own picture (thumb), size, facts and a primary Download. A new loadPreview(item, { signal }) lets the app hand back parsed text, table, markdown, html or card content (FileViewerPreview) so parsers stay app dependencies. Items gain refreshSrc (an expired video or audio URL) and peaks (a stored waveform); video letterboxes instead of cropping; data-kind now names the stage on screen, and the plain file card is card (was other). VideoPlayer no longer shows a Quality menu unless qualityOptions are passed, never autoplays, documents poster, shows "Can’t play this video here" with a Download button (downloadHref, loadErrorLabel, downloadLabel), and renews an expired signed URL with onSourceExpired, resuming where it stopped. AudioPlayer takes precomputed peaks and decodes a file for its waveform only up to maxDecodeBytes (20 MB), showing the plain seek slider above it; a renewal that returns the same URL now reloads it in both players. fileKindOf treats text/plain as generic (the extension decides) and maps .mdx, .toml, .ini and .jsonc. New lib media-probe: probeVideo (duration, size and a JPEG poster), probeAudio (duration and up to 200 peaks, skipped over 50 MB) and extractPptxThumbnail (an Office file's embedded preview picture), browser-only and dependency-free. 3b005c7

📦 npm

  • @vegastack/design → 0.7.63 (was 0.7.62).
  • The design-system registry (@vegastack/ui) bumps 0.23.75 → 0.23.76.

[0.23.75] — September 30, 2026

🧩 New components

  • Outline, keyboard comments, list selection and drag into folders. New TableOfContents: an "On this page" outline with a scroll spy (the last heading above min(180px, 28%), the last one at the bottom), aria-current="location", a list or a rail of ticks that opens on hover or focus, sticky placement, one tab stop with arrow keys, and a left Sheet through trigger; useActiveHeading is exported and page-editor-01 uses it. TextEdit comment highlights are tab stops in the read view (Enter or Space opens the thread) and open with Alt+Enter while editing; annotationCounts adds a count pill after each highlight (TextEditAnnotation.count, annotationCountLabel, annotationLabel). DataList adds shift-click ranges, ⌘/Ctrl+A, Esc and Space on a row link, grid-card checkboxes, a selectionActions bar, and dragging rows onto folder rows and breadcrumbs (onDropInto, canDropInto, canDropOnRow, dragScope); useDragInto gains scope, getDragIds and getDragPreviewLabel, and ActionBar's actions scroll on a narrow screen. New breadcrumb-cascade: BreadcrumbDropTarget (a crumb that takes dropped rows) and BreadcrumbSiblings (a menu of a segment's siblings). 268d6bd

📦 npm

  • @vegastack/design → 0.7.62 (was 0.7.61).
  • The design-system registry (@vegastack/ui) bumps 0.23.74 → 0.23.75.

[0.23.74] — September 30, 2026

🧩 New components

  • Comments beside the text and version history. CommentThread (in comments): a quoted thread with replies, a one-line reply box, resolve/reopen, orphaned and collapsed states; CommentItem takes attachments, and the composers pass mentions, mentionHref and uploads to their editor. New CommentMargin lays thread cards beside their highlights (active card aligned, the rest stacked) with CommentPopover for narrow screens; VersionList is a version history listbox (Current, Restored, Unsaved copy, Named only, Load more); DiffView shows word-level changes between two texts, line-only above 200 KB, with the sanctioned diff engine in its own lazy chunk. New block: page-editor-01. 78e2a2c

📦 npm

  • @vegastack/design → 0.7.61 (was 0.7.60).
  • The design-system registry (@vegastack/ui) bumps 0.23.73 → 0.23.74.

[0.23.73] — September 29, 2026

🧩 New components

  • New FolderTree: a Notion-style navigation tree for a file library — sections of folders, pages and files; folders that load their children on first open (loadChildren, or a controlled childrenOf) with loading, error (Retry), empty and "Show all" rows past maxChildren; one tab stop with tree arrow keys, * and typeahead; a ⋯ menu per row through renderRowActions and FolderTreeRowAction; pointer drag into a folder or onto a section heading (onMove, which may be async); and mode="picker" — folders only, one selected — for Move dialogs. use-drag-reorder gains useDragInto, the drag-into-a-target engine the tree runs on. A new file-kind lib gives every file surface one formatBytes, fileKindOf and FileTypeIcon. AttachmentGroup takes layout="list" for dense full-width file rows; FileViewer plays video/* and audio/* items in the system players and pauses them when paging away; useFileDrop and Dropzone take directories — each accepted file's relativePath through onEntriesAccepted, maxFiles refusing a whole oversized folder, and openDirectory() (on the hook, and on Dropzone's new actionsRef) for a folder picker. New block: library-01. 655d595

🔧 Changed components

  • TextEdit gains what a page editor needs, every construct round-tripping exactly through Markdown: @ mentions (mentions, mentionHref) stored as [@Label](mention://kind/id); image and file uploads by paste, drop, the image panel and the slash menu (onImageUpload, onFileUpload, onUploadError, fileLinkPrefix) that commit even after blur; callouts (> [!NOTE], [!TIP], [!WARNING]) and toggles (details); an outline (onOutlineChange) and an imperative handleRef (scrollToHeading, flush, focus, getAnchorForSelection, pulseAnnotation); and comment highlights (annotations, activeAnnotationId, onAnnotationClick, onAnnotationHover, onCreateAnnotation, onAnnotationsLayout) drawn as decorations, read-only too. ⌘K inside the editor no longer reaches a page's own palette. MarkdownView renders mention and file chips, callouts, toggles and headingIds. New libs text-anchor and text-anchor-doc store and map comment anchors. 9799b19

📦 npm

  • @vegastack/design → 0.7.60 (was 0.7.59).
  • The design-system registry (@vegastack/ui) bumps 0.23.72 → 0.23.73.

[0.23.72] — September 29, 2026

🔧 Changed components

  • Dates & times — formatDuration takes style: "long" for sentences and emails ("1 hour 15 minutes", "7 days"); new formatLongDate for page headers ("Thursday, September 24", day-first with locale: "en-IN", the year only outside the current year) and hourOfDay for the hour on the viewer's clock (greetings, working hours). docs · 82ade2c
  • New TemplateEditor: a plain-paragraph editor for text with spec placeholders. Typing @ or {{ opens a searchable list of tokens; the picked one becomes an inline chip that deletes as one unit and is stored as {{id}}. The value is plain text (paragraphs joined by a blank line) and round-trips exactly; unknown ids and invalidTokenIds render in the destructive style. Sized and bordered like Textarea, with its focus border. 0e3bd65

🛠 CLI & tooling

  • vegastack-design check-updates also checks registry files outside the components dir — @lib/… targets under aliases.lib (date-time, page-layout, tile-overlay, drag-item, emoji-data, geo-data) and @hooks/… under aliases.hooks — so a locally edited lib file is drift and fails --fail-on-update like an edited component. --dir still scans only that directory. 82ade2c

📦 npm

  • @vegastack/design → 0.7.59 (was 0.7.58).
  • The design-system registry (@vegastack/ui) bumps 0.23.71 → 0.23.72.

[0.23.71] — September 28, 2026

🔧 Changed components

  • DataList and DataGrid sort headers follow the standard indicator pattern: the sorted column's ↑/↓ always shows at foreground strength; other sortable columns keep their ⇅ transparent (space reserved, so labels never shift) and fade it in muted on header hover (fine pointers only) or keyboard focus, so touch shows only the active arrow; unsortable columns show nothing. Each sort button is now named Sort by <column> alongside the header's aria-sort. 343da2b

📦 npm

  • @vegastack/design → 0.7.58 (was 0.7.57).
  • The design-system registry (@vegastack/ui) bumps 0.23.70 → 0.23.71.

[0.23.70] — September 28, 2026

🧩 New components

  • Field FieldHeader and FieldAction: a label row with a trailing field-level action (such as a ghost size="sm" "Add more" button) right-aligned on the label's baseline, wrapping on narrow widths, with the description staying under the label. ab64e86

📦 npm

  • @vegastack/design → 0.7.57 (was 0.7.56).
  • The design-system registry (@vegastack/ui) bumps 0.23.69 → 0.23.70.

[0.23.69] — September 28, 2026

🧩 New components

  • Attributes and SKU generator support. ComboboxChips showClear adds a clear-all × to the multiple-selection chips field (clearLabel, "Clear all"); both clear controls are muted until hovered and are a keyboard stop while the list is closed (API-34). FilterBuilder always renders the Minimum–Maximum pair for a "between" (range) operator, even when editors registers a single-value editor for the field's type. MultiStepFormActions has one footer everywhere: a ‹ icon button (Back, hidden on the first step) then Cancel at the start; the status, the optional outline save (new MultiStepFormSave) and a › icon button (Next) at the end; on the last step the finish takes the primary slot with its text label, and an outline save that repeats it steps aside — never two saves. ‹ and › carry "Back"/"Next" as their accessible name and tooltip; nextLabel now defaults to "Next". RecordList gains RecordListGroup (a heading over one list, for grouped conflicts) and RecordDiff (the compact "what differs" table that marks differing rows), and its titles take the foreground ink inside a status Alert. New documented patterns: the review list dialog (pill tabs with counts over a scrolling numbered list), the conflict message (single and grouped), and checkbox chips for picking allowed values (ToggleGroup multiple wrap). 23e7e5b

🔧 Changed components

  • @vegastack/design now depends on @vegastack/design-tokens with the --scrim / --scrim-foreground pair, so a consumer installing the preset gets the bg-scrim / text-scrim-foreground utilities the tile-overlay recipe paints. dd91b95
  • New theme-invariant --scrim / --scrim-foreground token pair (black and white in both themes, with bg-scrim / text-scrim-foreground utilities) for the wash a photo wears under a control. The tile-overlay recipe — Attachment tile corner actions, SortableList's grid handle and actions, and AvatarPicker's photo overlay — now paints bg-scrim/40 text-scrim-foreground (/60 on hover and open) instead of raw bg-black/40 text-white, so it follows the tokens-only rule and a theme can retune every tile overlay in one place. The rendered result is unchanged. f667c77

📦 npm

  • @vegastack/design → 0.7.56 (was 0.7.55).
  • @vegastack/design-tokens → 0.7.56 (was 0.7.49).
  • The design-system registry (@vegastack/ui) bumps 0.23.68 → 0.23.69.

[0.23.68] — September 28, 2026

🧩 New components

  • One page layout. AppShellPage has three widths — prose (720px of content, centred; narrow is its deprecated old name for one release), default (1280px, centred) and full (edge to edge) — and one gutter, --page-gutter (16px, 24px from 640px, 32px from 1024px), which AppShellHeader pads with too and RecordLayoutRail sticks below, so the header, page and rail edges line up. The new page-layout lib's definePageWidths declares every route's width once for the page, its loading skeleton (widthOfPath) and a route test, and vegastack-design doctor fails on an AppShellPage that sets its own max-w-*, mx-* or padding class and warns on size="narrow". New docs page: Foundations › Page layout. 3032c9d

📦 npm

  • @vegastack/design → 0.7.55 (was 0.7.54).
  • The design-system registry (@vegastack/ui) bumps 0.23.67 → 0.23.68.

[0.23.67] — September 28, 2026

🔧 Changed components

  • Image and file tiles overhaul. AttachmentGroup layout="tiles" shows at most columns square tiles a row (four by default), three and then two as its container narrows; layout="scroll" with columns sizes each tile so that many fit the row. On a vertical tile, AttachmentActions is an overlay slot — side="end" top-right (up to two icon actions), side="start" top-left (a drag handle) — inset from the corner, shown on hover or focus within the tile and always on touch, with AttachmentAction white on a blurred dark scrim. Attachment takes file, preview (default on) and onOpen: a tile with a file is one Open {name} button that opens the FileViewer (lazily loaded), paging through its group or an enclosing new AttachmentPreview. AttachmentTitle carries the full name as its title. SortableList's grid uses the same overlay slots and scrim for its ⠿ handle and ×/⋯ actions, and columns is now a responsive cap instead of a fixed count. Every reorder drop indicator — SortableList rows and tiles, and TextEdit's block and table drag line — is drawn in the standard border colour from one drag-item token (dropIndicatorClasses). A new tile-overlay lib holds the shared scrim, overlay-slot and tile-grid recipe; AvatarPicker's photo overlay reads its scrim from it. 866d5b1

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.23.66 → 0.23.67.

[0.23.66] — September 28, 2026

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.23.65 → 0.23.66.

🐛 Fixed

  • RecordLayoutRail keeps its gap when the page scrolls: it sticks --record-rail-gap (default --spacing(8), AppShellPage's top gutter) below the top of the scroll container instead of touching the header, and its own scroll height leaves that gap above and below (--record-rail-offset now defaults to --spacing(14), the AppShellHeader). useRecordLayoutWide() also returns rail and sheet mount flags — both true until the layout is measured — so a server-rendered record page paints its rail on first load instead of popping it in after hydration. 8321134

[0.23.65] — September 28, 2026

🔧 Changed components

  • TextEdit and MarkdownView tables are Notion's simple table: a rounded outer border, a rule between every cell, and a header row on a muted ground at medium weight — the same prose.table styles in the read view and the editor. The floating table toolbar is gone; hovering a table shows a row grip and a column grip (click for a menu — insert, move, duplicate, clear, delete; drag to reorder), a corner grip where every block's handle sits (select the table, clear, delete; drag to move it; header row/column toggles in HTML), and "+" bars that add a row or column at the end. Column borders drag to resize. Shift+F10 opens the menu from the keyboard, Tab in the last cell adds a row, and markdown round-trips unchanged. The stray block handle over a table is gone. fc926dc

📦 npm

  • @vegastack/design → 0.7.54 (was 0.7.53).
  • The design-system registry (@vegastack/ui) bumps 0.23.64 → 0.23.65.

🐛 Fixed

  • Inline-edit fields paint no fill: EditableCell values and the ghost Input (a dialog's title) no longer tint on hover — at rest, on hover and while editing they are transparent, and the text cursor is the affordance. Keyboard focus on a resting EditableCell keeps the system focus tint. Text alignment and hit areas are unchanged; picker pills and menus keep their button hover. fc926dc

[0.23.64] — September 27, 2026

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.23.63 → 0.23.64.

🐛 Fixed

  • FileViewer: a PDF fits to a reader's page width (920px max) centred on the dark stage instead of the full screen; the previous/next buttons no longer drop by half their height when pressed; opening focuses the viewer itself, so the Download button no longer looks hovered. 007aec0
  • SortableList (and every useDragReorder item): dragging a row by its handle no longer drags a picture of half the page. The browser sizes its drag image from the row plus every descendant's box, and a Checkbox or Switch without a name keeps its hidden input fixed at the viewport's top-left — so a row with a "Required" checkbox dragged everything between the page corner and the row. The drag image is now a copy of the row alone, held under the pointer where it was grabbed. Keyboard move mode, touch long-press, drop indicators and announcements are unchanged. d7e8139

[0.23.63] — September 27, 2026

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.23.62 → 0.23.63.

🐛 Fixed

  • FileViewer builds its file-type icon as an element, so consumers' react-hooks/static-components lint passes. d1a525f

[0.23.62] — September 27, 2026

🧩 New components

  • New UploadDialog (UploadDialog, UploadDialogFileRow) — the "Add files" modal: a drop zone with the staged files under it (thumbnail or file icon, name, size, ×), then Next to a per-file details view the host renders (renderDetails, detailsValid) and Add. Without renderDetails it adds straight from the staged list. validate refuses a file with a message and maxFiles caps the staged count. It stages files only — onSubmit hands them to the host's upload queue and the dialog closes. AttachmentGroup gains layout="tiles": compact square tiles for forms, two per row on a phone and three from sm up. 758ee45

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.23.61 → 0.23.62.

[0.23.61] — September 27, 2026

🧩 New components

  • New FileViewer — a full-screen dark overlay for stored files, controlled by items, index (null closed), onIndexChange and onOpenChange. Images open fit to the screen with the thumb's blur fading into the full-size srcSet variant, zoom by double-click/double-tap, pinch, ⌘/Ctrl+scroll or +/−/0, and pan by drag; neighbours preload. PDFs render with pdfjs-dist (^6.3.289, now a sanctioned renderer, installed by the item and loaded lazily only when a PDF is shown, worker served same-origin) over range requests, as windowed canvas pages at the device pixel ratio with fit-width, zoom and "Page 3 of 20". Anything else shows its type icon, name, size and Download. ←/→, side buttons (fine pointers) or a swipe page; Esc or a swipe down closes and focus returns to the opener; paging announces "Image 3 of 12". ab850e4

🔧 Changed components

  • PropertyList takes columns={2}: rows flow into two columns once the list's own container is @xl (36rem) wide and stay one column in a narrower pane, with both the stacked and inline variants. EditableCell gains a number editor ({ type: "number", unit, min, max, step, placeholder }): the string value displays as 1,234 mm (locale-formatted, unit muted), edits with a decimal keyboard and the unit trailing the caret in the same box, and a draft that is not a number, is outside min/max or off step stays open with aria-invalid and an announcement instead of saving; an empty draft saves "". Attachment takes size="lg": a 10rem vertical tile, and a larger media box when horizontal. A select EditableCell inside a PropertyValue lines its option text up with plain values. ab850e4

📦 npm

  • @vegastack/design → 0.7.53 (was 0.7.52).
  • The design-system registry (@vegastack/ui) bumps 0.23.60 → 0.23.61.

[0.23.60] — September 27, 2026

📦 npm

  • @vegastack/design → 0.7.52 (was 0.7.51).
  • The design-system registry (@vegastack/ui) bumps 0.23.59 → 0.23.60.

🐛 Fixed

  • TextEdit's block drag handle stays inside its container. The ⋮⋮ grip is now 10px wide and sits 3px before the text (was 18px, 4px out), so it fits in the 16px padding of a Dialog, Sheet, Popover or Card instead of touching the edge, and it is clamped just inside a boxed editor's border or the container when the padding is narrower. The text keeps its column: nothing indents while the handle is hidden. 84ae2d9

[0.23.59] — September 27, 2026

🔧 Changed components

  • A ghost Input (a title typed straight onto the page) keeps its hover tint only until it is focused: typing into a field under the pointer shows no grey, just the caret. An EditableCell already paints no fill while editing, in a property list or not. 3d7aa6b
  • Heavy UI stays out of a page's first bundle. TextEdit renders its saved value as light read HTML (server-rendered, same typography, still editable to the touch) and loads the Tiptap editor on hover, focus or tap, swapping it in place with the caret where you clicked; preloadTextEdit() loads it ahead of time. MarkdownView now parses with marked instead of react-markdown (about a quarter of the size), adds format="html" for rich-text HTML and allowedImageOrigins={["*"]}. DatePicker and the FilterBar date filter load the calendar engine only when the picker is about to open. 0ccadf1

📦 npm

  • @vegastack/design → 0.7.51 (was 0.7.50).
  • The design-system registry (@vegastack/ui) bumps 0.23.58 → 0.23.59.

[0.23.58] — September 27, 2026

📦 npm

  • @vegastack/design → 0.7.50 (was 0.7.49).
  • The design-system registry (@vegastack/ui) bumps 0.23.57 → 0.23.58.

🐛 Fixed

  • Pills sit one step above any surface, one empty pill, and a sidebar hover action. SplitChip (behind RecordChip) drops its bg-background fill for the outline Button's surface-aware one — none in light, a translucent input/30 in dark — so a pill in a Dialog, Sheet, Popover or Card no longer reads darker than its surface. An empty RecordChip looks the same in both variants: variant="ghost" now draws the dashed rounded-full border around its muted placeholder too (inside the same 24px box), so every unset property pill — status, priority, assignee, due date, customer, project — shares one empty state. SidebarMenuAction showOnHover swaps with the row's badge in the same spot (API-33): the count shows at rest and the action takes its place on hover, focus or while its menu is open, with no layout shift or motion; below md the action stays visible and the count moves beside it. 5d12da5

[0.23.57] — September 27, 2026

🔧 Changed components

  • Form flow: Stepper labels keep one padding and weight in every state (no shift, no press motion) and a crowded horizontal rail scrolls; MultiStepForm gets self-clearing validate checks, mobileNav="stepper" and an action row with start / status / secondary slots (Cancel is a ghost button before Back); new FieldGrid / FieldGridItem / FieldChoices (responsive 1–3 column fields, inline radios and checkboxes); useAutoSave + AutoSaveIndicator for forms that save as you go; AttachmentGroup layout="grid" tiles at listing size. 4cfe999

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.23.56 → 0.23.57.

[0.23.56] — September 27, 2026

🔧 Changed components

  • Editable values in a PropertyValue line up exactly with read-only ones: an inline EditableCell there keeps the plain values' left edge, baseline and row height at rest, on hover and while editing, with a compact tint (6px × 2px, hung outside by negative margins) instead of an offset 10px × 6px box. A ghost RecordChip (the property pickers) now draws the same compact 24px box, so every editable property value reads the same. baf9d99

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.23.55 → 0.23.56.

[0.23.55] — September 27, 2026

🔧 Changed components

  • SortableList list rows are as dense as a DataList's — compact padding, 2px between rows (ItemGroup's 10px gap no longer applies), a subtle wash on hover — and the row's ⋯ menu trigger now shows on row hover or focus, while its menu is open, and always on touch, like the ×. 1dba41b

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.23.54 → 0.23.55.

[0.23.54] — September 27, 2026

🧩 New components

  • New RecordList (RecordList, RecordListItem, RecordListMore) — the records a change affects, for confirmation dialogs: numbered rows (an <ol>, so the numbers continue when "Show more" appends the next batch), a muted record-type icon, the name with a small ↗ Open {name} in a new tab link right after it (shown on row hover or focus, always on touch), an optional badge and description line. 88c9150

🔧 Changed components

  • SortableList: touch long-press reordering (the Board's model — a 250ms hold that moves under 8px lifts the row or tile; the page doesn't scroll under it and the long-press menu is suppressed) for lists and grids, beside the handle's pointer drag and keyboard move mode. The Move up / down / to top / to bottom menu items are gone. New onRemove puts a small × (Remove {label}) in each unlocked row's corner, shown on hover or focus and always on touch; getItemActions still gives a ⋯ menu for more actions, and a row with no actions has no menu. Grid: columns (2–6) fixes the tiles per row, and tile="bare" drops the tile frame so an Attachment inside is the one frame. A locked row's reason is now the row's description. 88c9150

📦 npm

  • @vegastack/design → 0.7.49 (was 0.7.48).
  • @vegastack/design-tokens → 0.7.49 (was 0.7.46).
  • The design-system registry (@vegastack/ui) bumps 0.23.53 → 0.23.54.

🐛 Fixed

  • Text fields show focus as a subtle darker border that eases in (150ms), with no grey fill and no darker rectangle inside input groups. base.css again leaves text inputs and textareas out of the background tint and drops the field-group tint that stacked a square inner rectangle over the group. Input, Textarea, InputGroup (and so NumberField, ChipInput, Combobox, Command, SearchInput, PasswordInput, AutoSaveInput and the popup search rows), ComboboxChips, PanelSearch, the Questionnaire answer field and the InputOTP active slot take border-ring/40. An invalid field keeps its destructive border while focused. Buttons, rows, tabs, menu items and chips keep the tint. TextEdit gains variant="document" | "boxed" and children. document (the default) stays caret-only. boxed draws a bordered box whose border darkens on focus, and the comment composer now uses it. The ghost Input has no focus fill. design-lint's no-focus-border allows the text-entry border only in those components, and the geometry sweep checks the new contract. a0e3482

[0.23.53] — September 27, 2026

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.23.52 → 0.23.53.

🐛 Fixed

  • AudioPlayer no longer grows while it loads. The "Loading audio…" status row below the controls is gone: while loading is set, a lazy src resolves, or the media buffers after play, the play button's glyph becomes a spinner of the same size in the same button (aria-busy, same accessible name, still pauses). loadingLabel is still announced once to screen readers. MediaPlayerControls gains the loading prop that draws it. c2bcdc8
  • Board: an empty lane's "+ Add" action is now as wide as its label, centred under "Nothing here" like any Empty action, instead of stretching the full lane so its hover background ran far past the text. After the last card it stays a full-width row. 1e89391

[0.23.52] — September 27, 2026

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.23.51 → 0.23.52.

🐛 Fixed

  • MediaCard's badge now sits beside the title, where DataList's list view puts a pill, instead of in the meta line — a long title truncates before it and the badge never shrinks; the meta line is just meta and timestamp. The record title has one weight in the grid and the list: DataList's first column is now font-medium, the grid card's title weight, through a shared RECORD_TITLE_CLASS exported from media-card; merged values under it stay regular. Docs show the badge variants (with, without, long title) and the grid and list side by side. 747476d

[0.23.51] — September 27, 2026

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.23.50 → 0.23.51.

🐛 Fixed

  • Photos show instantly. PersonAvatar now renders the photo as a plain <img> in the server HTML, layered over the initials, instead of Base UI's AvatarImage (which mounts its <img> only after a JS loader reports it loaded) — so the browser fetches it from the markup and a cached photo is there on first paint, with no flash of initials; the initials come back if the photo fails. Image no longer hides a server-rendered image until hydration: it paints as soon as it decodes, and only a client-mounted image that has not loaded yet fades in; a load or error that fired before hydration is read from the element. New priority prop on Image (loading="eager" + fetchPriority="high") for above-the-fold images. 8d620f5

[0.23.50] — September 27, 2026

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.23.49 → 0.23.50.

🐛 Fixed

  • Board lanes no longer scroll sideways: the lane body was overflow-y-auto, which turns overflow-x to auto too, so a few px of card overflow made the cards jiggle sideways and swallowed horizontal trackpad, Shift+wheel and touch pans. The lane body is now overflow-x-hidden overflow-y-auto overscroll-y-contain, and the board scroller is touch-pan-x touch-pan-y, so a sideways pan anywhere on the board scrolls the board. Each lane now has a subtle top-to-bottom muted fade (drop-over still swaps to the solid accent wash), and "+ Add" moved from the lane's foot into the scrolling content: full width and centred right after the last card (after LoadMore), or as the empty state's action under "Nothing here". a214a5b

[0.23.49] — September 27, 2026

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.23.48 → 0.23.49.

🐛 Fixed

  • AvatarPicker's dialog no longer leaves extra space under its footer: the hidden file input sat in the dialog's grid as a zero-height row and added a 16px gap, so it now lives, not displayed, inside the drop circle, whose wrapper is flex so the dialog no longer grows 4px when a photo replaces the initials. A pending call's spinner now sits in the button that started it (Update or Remove, via loading), not over the photo; the other button and the circle are disabled until it settles. 5e94a40

[0.23.48] — September 27, 2026

🔧 Changed components

  • AvatarPicker is now just the avatar circle that opens a "Profile photo" dialog. In the dialog a 128px circle is the file button and a drop target (with a "JPEG, PNG or WebP · up to 10 MB" hint); a chosen file is only staged as a preview, Update awaits onUpload(file) and Remove clears a staged file or awaits onRemove(). While a call is pending the dialog shows a spinner and cannot be dismissed; it closes when the call resolves and shows the error's message when it rejects. API: onUpload(file): Promise<void> replaces onSelect; onRemove returns a promise; busy and error are gone (the component owns pending and error state); new title (default "Profile photo"), updateLabel ("Update"); size is xs · sm · md · lg · xl (32–80px) with sm (40px) the new default. 48ec31e

📦 npm

  • @vegastack/design → 0.7.48 (was 0.7.47).
  • The design-system registry (@vegastack/ui) bumps 0.23.47 → 0.23.48.

[0.23.47] — September 27, 2026

🔧 Changed components

  • AvatarPicker's hover, focus and busy scrim: the pencil and the spinner are white again (the ghost Button's icon ink no longer overrides them), and the scrim no longer blurs, so the photo or initials stay recognisable under it. With no photo, the initials fade out while the scrim shows (hover, keyboard focus, busy), so only the icon sits on the person's colour; a touch device keeps the initials beside its corner badge. 9af2c55

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.23.46 → 0.23.47.

[0.23.46] — September 27, 2026

🔧 Changed components

  • AvatarPicker is an avatar-edit control: the circle is the button (named "Upload photo" or "Change photo"), with a dark scrim and pencil fading in on hover and keyboard focus, and a small pencil badge on the edge on devices without hover. The separate Upload / Change photo button is gone. While busy the scrim holds a spinner over an instant local preview of the chosen file; person.image shows again when busy ends. size is now sm (40px) · md (48px, the new default) · lg (64px) · xl (80px), with initials scaled to match. "Remove" is a small ghost button beside the circle. 3c3b354

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.23.45 → 0.23.46.

[0.23.45] — September 27, 2026

🔧 Changed components

  • Blur placeholders reach record grids and lists. MediaCard takes imagePlaceholder (a data URL blurred over the image area until it loads), imageSrcSet and imageSizes, passed to its thumbnail. A DataList column's thumbnail may return { src, placeholder?, srcSet?, sizes? } (the DataListThumbnail type) as well as a URL string, forwarded to the row's Thumbnail and to the grid's MediaCard; string thumbnails work as before. AvatarPicker names a refused HEIC/HEIF photo (“<name> is a HEIC photo. Export it as JPEG and try again.”) and announces every refusal and every error exactly once through one polite live region. 772f4b7

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.23.44 → 0.23.45.

[0.23.44] — September 27, 2026

🔧 Changed components

  • People carry their own colour and photo everywhere. AvatarFallback takes a hue (AvatarHue, the ten tag hues: initials in the hue's ink on its subtle fill; API-32). A new shared person-avatar item owns PersonAvatar and the Person shape (now with hue) — person-hover-card still re-exports them — and it exports the one initials rule, personInitials(name, email?): first letters of the first and last words, two letters of a single word, else the email's first two characters. RecordChip, BoardCard and InboxItem draw PersonAvatar instead of their own initials, and take hue on person, assignee and avatar ({ name, src?, hue? }); PropertyPerson and comment authors take it too. SearchableSelect (and FilterBarFacet) gain itemToAvatar, leading each person option — and a single selection on the trigger — with the person's avatar. Image and Thumbnail take a placeholder data URL, a blurred cover until the image loads (Image still runs a caller's onLoad/onError), and Thumbnail passes srcSet/sizes, retrying when either changes. New AvatarPicker: a large PersonAvatar with a busy spinner, Upload / Change photo through the file picker, Remove, and a line for a refused file. app-shell-01's user menu draws PersonAvatar. The shipped vegastack-design-system skill says a person is always PersonAvatar. 74415be
  • personInitials(name, email?) now lives in person-avatar — import it from @/components/ui/person-avatar. The separate person lib item is gone, so person-avatar installs nothing into your lib/ alias. 74415be

📦 npm

  • @vegastack/design → 0.7.47 (was 0.7.46).
  • The design-system registry (@vegastack/ui) bumps 0.23.43 → 0.23.44.

[0.23.43] — September 26, 2026

🔧 Changed components

  • FilterBar's search field is ~320px (w-80) on a desktop- or tablet-width bar again, with the Filters toggle and the view at the far end; it takes the full width only on a phone-width bar (below @lg), where it sits on its own row. 98a6981

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.23.42 → 0.23.43.

[0.23.42] — September 26, 2026

🔧 Changed components

  • One list toolbar for every list page. FilterBar's filter row is hidden by default and never opens by itself when a filter becomes set — the Filters toggle shows it and carries the applied count meanwhile; autoOpenFilters opts back in, and filtersOpenStorageKey remembers the toggle per page for the browser session. Row 1 is the search taking the free space, then the Filters toggle, then the view switch at the far end. On a tablet-width bar (below @3xl) the Filters toggle and the view go icon-only, with the count as a badge; below @lg the search takes its own row and the icon-only toggle and view share the next one, start and end. PageHeader drops the view slot (and page-header-view) added in 0.7.42: the layout switch belongs only in the FilterBar's view, and the header's tabs hold the scope. list-page-01 moves its Grid | List switch into the toolbar and uses the bar's "Clear". Docs: FilterBar gains a Breakpoints example, and the PageHeader "View tabs", DataList "View toggle" and list-page-01 pages show the layout. 74adb14

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.23.41 → 0.23.42.

[0.23.41] — September 26, 2026

🔧 Changed components

  • RecordChip gains variant="ghost" for property values: no border and no fill, the value in plain body text (the same size, weight and colour as a plain PropertyValue), the standard tint on hover and keyboard focus, and a ▾ that fades in then and stays while the menu is open. A status or priority icon keeps its colour, and any other leading icon is dropped. A new person prop shows a 20px avatar (initials fallback) and an "Inactive"-style badge. A DueLabel value keeps its overdue colour, and an empty chip shows the muted placeholder. PropertyValue hangs a ghost chip's padding and 28px height outside the cell, so its text starts where plain values do and every row is the same height. SplitChip's name segment drops the button's transparent border, so its hover background sits the same 2px from the pill's border as the ↗ segment's on every side and around the separator. PropertyPerson's avatar is 20px, matching the ghost chip. Date labels lose tabular-nums (BoardCard's due badge, MediaCard's timestamp), so dates use Geist's proportional figures everywhere, and tabular figures stay only on live timers. Docs: the RecordChip and PropertyList pages and the issue-detail-01 block show both variants and every state. fe030ca

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.23.40 → 0.23.41.

[0.23.40] — September 26, 2026

🔧 Changed components

  • Comments: each posted comment sits in its own card (the composer's bg-muted/30 surface, border-border, rounded-xl), and so do the deleted placeholder and edit mode; the border never changes on hover or focus. The add-reaction and ⋯ buttons are identical ghost icon-sm buttons at the card's top-right, both shown on hover or focus-within (kept while their popup is open, always on touch). The ⋯ menu is sized to its content with muted Link, Pencil and destructive Trash2 icons. ReactionAdd gains a size prop (icon-xs default, icon-sm). 5674880
  • A border never changes colour on focus or while active (FOC-14). Every control keeps its resting border-border/border-input in every state, and the focus cue is base.css's background tint everywhere — text entry included. A bordered field group (InputGroup, NumberField, ChipInput, ComboboxChips, PanelSearch, now all data-field-group) wears the tint on the group; TextEdit stays caret-only, and the comment composer box no longer re-borders when active. Input, Textarea, NativeSelect, Select, Combobox, InputOTP (the active slot takes the tint), Questionnaire, DatePicker and SearchableSelect ghost triggers, and MediaPlayerControls' seek thumb lose their focus border; the ghost Select no longer re-borders while open. The invalid state keeps its destructive border in every state, focused or not (the not-focus: guards are gone from Button, Badge, Toggle, Checkbox, RadioGroup, Switch and the fields). design-lint gains no-focus-border, and the geometry focus sweep fails any control whose border colour moves on focus. 5674880

📦 npm

  • @vegastack/design → 0.7.46 (was 0.7.45).
  • @vegastack/design-tokens → 0.7.46 (was 0.7.45).
  • The design-system registry (@vegastack/ui) bumps 0.23.39 → 0.23.40.

[0.23.39] — September 26, 2026

📦 npm

  • @vegastack/design-tokens → 0.7.45 (was 0.7.43).
  • @vegastack/design → 0.7.45 (was 0.7.44).
  • The design-system registry (@vegastack/ui) bumps 0.23.38 → 0.23.39.

🐛 Fixed

  • Programmatic focus targets (tabindex="-1") no longer paint the :focus-visible background tint. Before this, clicking the empty part of AppShell's <main> and then pressing any key (Shift or ⌘ alone) tinted the whole page grey. Combobox chips keep the cue. 1100891

[0.23.38] — September 26, 2026

🔧 Changed components

  • Notion-grade TextEdit. The editor has no fill in any state (the caret is the focus cue, data-focus-cue="caret") and never widens the page — long words, URLs and inline code wrap, code blocks and tables scroll in their own box (prose gains min-w-0 wrap-anywhere). Every menu floats in a <body> portal and flips to stay in view, so no container clips it. The slash menu adds Heading 4, Table and Image (URL and alt text); the bubble menu adds Turn into, Clear formatting and Open / Remove on links; code blocks get a language selector. GFM tables get a table menu (insert, move, delete rows and columns), drag grips to reorder columns and rows, and Tab / Shift+Tab between cells. A ⋮⋮ block handle drags any block or list item (dragHandles, on by default), and ⌘⇧↑ / ⌘⇧↓ move the caret's block. New shortcuts: ⇧⌘X strikethrough. Image-only paragraphs now round-trip. RelativeTime defaults to the compact house form everywhere — "now", "19m ago", "3h ago", "2d ago", "3w ago", "5mo ago", "1y ago" — with format choosing suffix (default), minimal or long; the Intl short style with periods ("19 min. ago") is gone and unitStyle is deprecated. Comments: a lighter composer and edit box (bg-muted/30, hairline border), "Add a comment…" as the default placeholder, auto-grow to about twelve lines, a legible disabled send button, and no drag handles. 43e36b5

📦 npm

  • @vegastack/design → 0.7.44 (was 0.7.43).
  • The design-system registry (@vegastack/ui) bumps 0.23.37 → 0.23.38.

[0.23.37] — September 26, 2026

🔧 Changed components

  • Emoji reactions. New Reactions (pills with counts that toggle, a hover card naming who reacted, an add button with quick reactions 👍 ❤ 😄 🎉 👀 🙏) and toggleReaction for optimistic updates. CommentItem and CommentList take onReactionToggle and read each comment's reactions; the add button joins the hover actions. EmojiPicker gains a quick row, a category bar, a Recent section, size="sm", a skeleton and a DS empty state, and loads its data lazily from the new emoji-data lib (EMOJI moved there). a7e0e2b

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.23.36 → 0.23.37.

[0.23.36] — September 26, 2026

🔧 Changed components

  • The date-time lib's module doc now reaches consumers: shadcn add drops every comment before a file's first statement, so the doc moves below DateInput. The code is unchanged. ec9fb47

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.23.35 → 0.23.36.

[0.23.35] — September 26, 2026

🔧 Changed components

  • Every empty list, grid, table and board lane renders the design-system Empty with an icon: an Inbox icon and "Nothing here" by default. Change the copy with the new empty prop ({ icon, title, description, action }) on DataList, on a DataList section and on a Board column; emptyState still replaces it outright, and the new DataListEmptyState is the same state outside a DataList. NoResultsEmpty takes an icon. Board lanes keep their columnWidth however many lanes are collapsed, instead of stretching into the space a collapsed lane frees. base.css renders text-entry controls at 16px or more on touch pointers so iOS Safari never zooms into a focused field; the install docs and consume guide add the viewport's maximumScale: 1. 3c3b145
  • TextEdit's markdown output is clean: blank lines typed with Enter no longer serialize as &nbsp; between extra blank lines — empty paragraphs collapse, so paragraphs are separated by one blank line and the output reloads to itself. Blank lines inside fenced code are kept. Input no longer exports inputVariants (nothing used it, and its fixed heights broke the data-table squeeze census). aeb7442

📦 npm

  • @vegastack/design → 0.7.43 (was 0.7.42).
  • @vegastack/design-tokens → 0.7.43 (was 0.7.33).
  • The design-system registry (@vegastack/ui) bumps 0.23.34 → 0.23.35.

[0.23.34] — September 26, 2026

🔧 Changed components

  • Editing a comment uses the composer's compact box with icon buttons: a round ↑ Save (disabled while the text is empty or unchanged) and a ghost × Cancel, each with a tooltip; Cmd/Ctrl+Enter saves and Escape cancels. TextEdit's tests now cover every markdown element's round-trip (headings, strike, inline code, code blocks, quotes, dividers, task and nested lists, blank lines), commit on blur and unmount, Escape revert, autosave, slash-menu keys, undo/redo, paste and bubble-menu links. efb06cc

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.23.33 → 0.23.34.

[0.23.33] — September 26, 2026

🔧 Changed components

  • PageHeader gains tabs and view slots: a list page's view tabs sit on their own row directly under the title, with the layout switch (ViewToggle) pinned to that row's end. The list-page-01 block moves its Mine | Team scope (now default Tabs) and Grid | List toggle there, out of the FilterBar. 2bf27bf

📦 npm

  • @vegastack/design → 0.7.42 (was 0.7.41).
  • The design-system registry (@vegastack/ui) bumps 0.23.32 → 0.23.33.

[0.23.32] — September 26, 2026

🔧 Changed components

  • Comments go Linear style: CommentComposer is a soft filled box with a round ↑ send button (Cmd/Ctrl+Enter sends), an attachments slot and no avatar (author is gone); CommentList gets an Oldest / Newest first toggle (order, onOrderChange) and an empty state whose "Add a comment" reveals and focuses the composer; editing a comment uses a compact box with Cancel and Save. New block issue-detail-01: a Linear-style issue page composed of the real components. 41c25fe
  • TextEdit has no toolbar: type / for a slash menu (headings, lists, task list, quote, code block, divider, link) and select text for a bubble menu (bold, italic, strikethrough, inline code, link). slashCommands limits the menu — TEXT_EDIT_SLASH_COMMANDS is the full set, TEXT_EDIT_COMPACT_SLASH_COMMANDS the comment-sized one. Hover and focus tint the surface, and every change leaves through one onCommit path; the toolbar, variant, onSave, onCancel, saveLabel and cancelLabel props are removed. Blank lines round-trip in markdown. Input gains variant="ghost" (borderless, tinted on hover and focus) and size="lg" (heading type) for a title field, and InputGroupInput accepts Input's props. EditableCell variant="heading" has no box padding, so a title is edited exactly where it renders. PropertyRow top-aligns its label on the value's first line. Prose list markers inherit the item's colour and use proportional figures, so lists read exactly like body text. 9ae6bb2

📦 npm

  • @vegastack/design → 0.7.41 (was 0.7.40).
  • The design-system registry (@vegastack/ui) bumps 0.23.31 → 0.23.32.

[0.23.31] — September 26, 2026

🔧 Changed components

  • Dates and times (RelativeTime, DateTime, DueLabel, Duration) render in the body font with proportional figures, like the text around them, instead of tabular figures that read as monospace. Live timers keep tabular figures. 8e5f523
  • TextEdit is Notion-style: no border, ground or focus ring, and no view mode — it rests looking exactly like MarkdownView; click anywhere and type. The Save/Cancel buttons are gone: leaving the editor calls the new onCommit(value) when the document changed, Escape reverts and calls onRevert(), Cmd/Ctrl+Enter calls onSubmit (or commits), and autosave commits after an idle gap. The formatting toolbar is a compact row under the text, shown only while focused (minimal included). onSave/onCancel still work as aliases; variant, saveLabel and cancelLabel are ignored. The prose recipe is now the app's body text: text-sm, body family and foreground ink for paragraphs, lists, list markers, links, quotes and table cells (no relaxed leading, no muted markers), headings on the app scale (# text-lg, ## text-base, the rest body size, all font-heading), and only inline code and code blocks in mono — identical in MarkdownView and TextEdit. 3da5a55
  • Toasts are tighter: 12px padding (was 16px), 10px between the icon, text and actions (was 12px), and 2px between title and description (was 4px). Close and action buttons are unchanged. 384ff99

📦 npm

  • @vegastack/design → 0.7.40 (was 0.7.39).
  • The design-system registry (@vegastack/ui) bumps 0.23.30 → 0.23.31.

[0.23.30] — September 26, 2026

🔧 Changed components

  • Comments: CommentList (heading with a count, Load earlier, skeleton, "No comments yet"), CommentItem (avatar, name and badge, relative time, "edited", ⋯ Copy link / Edit / Delete, in-place Markdown editing, #comment-<id> highlight, a replies slot) and CommentComposer (avatar and a minimal Markdown editor; Cmd/Ctrl+Enter or Comment posts, with posting and error states). cc7d5ba

📦 npm

  • @vegastack/design → 0.7.39 (was 0.7.38).
  • The design-system registry (@vegastack/ui) bumps 0.23.29 → 0.23.30.

[0.23.29] — September 26, 2026

🔧 Changed components

  • Toast actions close their toast: clicking the action (e.g. "Undo") runs it and dismisses the toast; data: { keepOpen: true } opts out. Tooltips open after 250ms everywhere (TIMINGS.tooltipOpenDelayMs, was 300), and RelativeTime's date tooltips now use that shared delay instead of opening instantly. ffa5257

📦 npm

  • @vegastack/design → 0.7.38 (was 0.7.37).
  • The design-system registry (@vegastack/ui) bumps 0.23.28 → 0.23.29.

[0.23.28] — September 26, 2026

🔧 Changed components

  • Board lanes are transparent, like list view: no lane fill, only the column spacing; cards keep their surface. Each lane header shows a collapse icon button (tooltip "Collapse lane") left of its ⋯ menu, the ⋯ menu drops its "Collapse lane" item and hides when a lane has no other actions, and a collapsed lane is a dashed strip with an expand icon (tooltip "Expand lane"). 4781f95

📦 npm

  • @vegastack/design → 0.7.37 (was 0.7.36).
  • The design-system registry (@vegastack/ui) bumps 0.23.27 → 0.23.28.

[0.23.27] — September 26, 2026

🔧 Changed components

  • AudioPlayer: the floating pill always sits at the bottom of its flex column (mt-auto built in) and centres on the main column beside an end rail — --audio-player-inset-end narrows and shifts it, and inside AppShell a RecordLayout rail sets it automatically. 8334813

📦 npm

  • @vegastack/design → 0.7.36 (was 0.7.35).
  • The design-system registry (@vegastack/ui) bumps 0.23.26 → 0.23.27.

[0.23.26] — September 26, 2026

🔧 Changed components

  • BoardCard's assignee avatar opens the person hover card on hover (PersonCard: name, email and status badge); BoardCardAssignee takes email and badge ("Inactive"). 7fafdc5

📦 npm

  • @vegastack/design → 0.7.35 (was 0.7.34).
  • The design-system registry (@vegastack/ui) bumps 0.23.25 → 0.23.26.

[0.23.25] — September 25, 2026

🔧 Changed components

  • AudioPlayer: the floating pill is redesigned — no visible title, back 10s · filled play · forward 10s, tabular times around a thin seek bar with a buffered shade, hover thumb and hover time, a speed menu and a volume popover, even size-8 controls; Space plays or pauses and ←/→ move 5s. Full width and tighter on phones, with volume hidden. 4e40109
  • Person status badge: PersonOption takes badge, and Person (for PersonCard, PersonHoverCard and AvatarStack) and PropertyPerson take badge — a status right after the name on the same line; a string such as "Inactive" renders as a small muted outline Badge. SearchableSelect and FilterBarFacet pass it to person options with itemToBadge. PersonBadge is exported for person cells that compose their own avatar, name and email. 98045ec

📦 npm

  • @vegastack/design → 0.7.34 (was 0.7.33).
  • The design-system registry (@vegastack/ui) bumps 0.23.24 → 0.23.25.

[0.23.24] — September 25, 2026

🔧 Changed components

  • Board, BoardCard, row menus and PersonOption: BoardCard takes a status slot (your status circle, click to change and Alt-click to finish, as in list rows) in a fixed leading column, with the title, context and meta row sharing one text column; the meta row reads assignee avatar, due, then priority, and the source/sourceLabel icon is removed; a done title's strike no longer clips. The Board card ⋯ menu drops its Move up/down/top/bottom/"Move to" items (drag and Space + arrows stay) and shows only when a card has actions. RowActionItem gains checked (a tick for the current choice) and shortcut (a one-key pick such as O/P/B/D/C); row and card menus are at least 12rem wide, submenus 12rem, and person submenus 18rem. PersonOption (avatar, name over email) is the standard for every person list, and SearchableSelect with itemToSecondaryLabel widens its popup to 18rem. Card ⋯ menus (MediaCard, BoardCard) are top-aligned with the title's first line. 12744e2
  • No focus rings anywhere, Tabs included (FOC-13): tab triggers and panels take the same background tint as everything else, and clicking a tab shows nothing. Every clear and dismiss × (FilterBar facet and date clear, Chip remove, SearchableSelect, Combobox and DatePicker clear, Dialog and Sheet close) opts out of Button's press nudge and centres with inset and auto margins, so it never jumps when pressed. Transcript: the search field is capped at max-w-sm, the speaker name in each turn is an inline EditableCell when onSpeakerRename is set (a rename applies to every turn with that speaker), and TranscriptSpeakers is deprecated. Empty's icon tile is 40px with a 20px icon (32px / 16px at size="sm"), and the tile sizes any icon passed in. 7d9d485

📦 npm

  • @vegastack/design → 0.7.33 (was 0.7.32).
  • @vegastack/design-tokens → 0.7.33 (was 0.7.18).
  • The design-system registry (@vegastack/ui) bumps 0.23.23 → 0.23.24.

[0.23.23] — September 25, 2026

🔧 Changed components

  • Markdown view and edit share one typography: TextEdit gains toolbar levels (minimal/standard/full or an action array) that set the schema, a selection bubble menu, a link popover (⌘K), task lists, tables, code blocks and images, markdown paste, onSave/onCancel (⌘Enter/Esc) with toolbar Save/Cancel, optional autosave, a borderless ghost variant and a zero-shift placeholder; MarkdownView task items take the same DOM through the new prose.taskList rules. New Markdown guide shows every element in view and edit. c6920d0
  • Combobox's clear and chip-remove × no longer move when pressed (API-31). RecordLayout gains RecordLayoutPanels, RecordTabCount and RecordLayoutMainSkeleton; RecordChip gains RecordChipMenu, the flush picker popover a pill opens. RecordLayout picks the rail from its own width (container query, useRecordLayoutWide) and gains RecordTabsRow / RecordTabsActions. e706cd9
  • Toolbar switches use the default Tabs. ViewToggle now renders the default (pill) Tabs (at the bar's h-8 tier, which FilterBar also lifts a TabsList size="sm" scope to) instead of an outline ToggleGroup, keeping its icons (List, Columns3 for Board, LayoutGrid) with labels hidden on a phone. The FilterBar scope and view slot guidance, the FilterBar, ViewToggle and DataList docs, and the skill now say every scope or view switch in a table or list toolbar is a default Tabs, never a ToggleGroup or line tabs. 78d1263

📦 npm

  • @vegastack/design → 0.7.32 (was 0.7.31).
  • The design-system registry (@vegastack/ui) bumps 0.23.22 → 0.23.23.

[0.23.22] — September 25, 2026

🔧 Changed components

  • EditableCell keyboard focus shows only the system focus tint (FOC-13), the same as hover: no focus ring or outline. The 0.7.30 changelog entry now describes the shared box correctly (the text sizes the box and the field is laid over it). f2d4304
  • SettingsSection takes actions, right-aligned on the title's row. Row ⋯ menus (RowActionsMenu) are at least 192px and as wide as their longest item, so labels never wrap, and a disabled item's disabledReason is a tooltip on the item (still its accessible description) instead of a wrapped second line. fae1529

📦 npm

  • @vegastack/design → 0.7.31 (was 0.7.30).
  • The design-system registry (@vegastack/ui) bumps 0.23.21 → 0.23.22.

[0.23.21] — September 25, 2026

🔧 Changed components

  • Inline edit rework. EditableCell's edit mode now looks like its view: no border, ring or background, and the field inherits font, size, weight, line height, tracking and colour, so only the caret and the selection show it is editing. The display and the field share one box (the text sizes the box and the field is laid over it), so the field grows with its content, wraps when the value wraps and nothing shifts entering or leaving edit. New props: onSave (the promise API; onCommit still works), variant (inline · cell fills a table cell and lets clicks beside the text reach the row link · heading keeps the title's size and weight and wraps), multiline (⌘/Ctrl+Enter saves), required + requiredMessage, onNavigate (Enter or Tab saves and moves on), tooltip and saveErrorToast. Saves are optimistic, the trailing spinner shows only after 300ms, and a failure rolls back, is announced and raises a toast with Retry. Read mode is a button labelled Edit {label}; F2 starts editing. useInlineEdit gains multiline and ignores keys during IME composition. 50f37dd

📦 npm

  • @vegastack/design → 0.7.30 (was 0.7.29).
  • The design-system registry (@vegastack/ui) bumps 0.23.20 → 0.23.21.

[0.23.20] — September 25, 2026

🔧 Changed components

  • AvatarStack: truly stacked 24px avatars (overlapping, background ring, "+N") as one button that lists everyone, with a hover preview per avatar; PersonCard never truncates. RecordChip: placeholder={null} shows the icon alone, and the chevron sits as far from the divider as the ↗ does. Clear and remove × controls (DatePicker, SearchableSelect, FilterBar, Chip, SearchInput, AnnouncementBanner, RecordAsideAction) no longer move when pressed: the absolutely centred ones used -translate-y-1/2, which Button's press nudge replaced. 1ccbcee

📦 npm

  • @vegastack/design → 0.7.29 (was 0.7.28).
  • The design-system registry (@vegastack/ui) bumps 0.23.19 → 0.23.20.

[0.23.19] — September 25, 2026

🔧 Changed components

  • PersonHoverCard: AvatarStack (stacked avatars, max 5, the rest behind "+N" as rows), PersonHoverCard (hover, focus or tap: a compact 240px card, a 32px avatar with the name and muted email stacked beside it), PersonCard and PersonAvatar; people without an account show initials and the name. Card gains variant="outline" (a border, no fill; API-30). RecordAside cards are outline, the inline PropertyList has a fixed 112px label column with values left-aligned, and the rail has layout-matching skeletons. Stat gains StatSkeleton. e2ef1fc
  • RecordAside: the record rail's cards — RecordAsideCard, RecordAsideSection (title, count, a tooltip icon action), full-width ActionList rows on Item (edge to edge, hover tint, no underline) with a one-line ActionListEmpty, and PropertyPerson, PropertyPeople (+N) and PropertyClamp (Show more). PropertyList gains variant="inline", PropertyEmpty and PropertySection. People icons are the round lucide set (UserRound, UsersRound, CircleUserRound). e2ef1fc
  • Row actions: RowActionItem gains an optional submenu slot — custom content (a searched, paged list such as Assign › members) rendered inside the submenu after items. 2c9582f
  • NotificationBell: the count badge is a solid destructive fill (was a faded tint) and the unread dot is solid info blue (was primary ink); both sit inside the button's top inline-end corner with a background ring, so they never spill out when pressed. Counts cap at "99+" with a matching "99+ unread" accessible name. c6ea931

📦 npm

  • @vegastack/design → 0.7.28 (was 0.7.27).
  • The design-system registry (@vegastack/ui) bumps 0.23.18 → 0.23.19.

[0.23.18] — September 25, 2026

📦 npm

  • @vegastack/design → 0.7.27 (was 0.7.26).
  • The design-system registry (@vegastack/ui) bumps 0.23.17 → 0.23.18.

📚 Docs

  • Fix the docs build: an unescaped addLabel expression on the DataList page, and a ⓘ the OG font lacks in the RecordLayout description. cccb347

[0.23.17] — September 25, 2026

🔧 Changed components

  • Board redesign and the new BoardCard. Board lanes now fill the viewport below the board (height="fill", cards scroll inside each 320px lane, the page never scrolls), with sticky headers, a ⋯ menu that collapses a lane to a slim strip (collapsible, collapsedColumns, onCollapsedChange, getColumnActions), "+ Add" at each lane's foot (onAdd, addLabel, per-lane addable), dashed "Nothing here" / "Drop here" empty zones, per-lane skeletons and load-on-scroll paging with LoadMore as the fallback, a hidden-scrollbar board with edge fades, and one lane at a time with a lane strip on a phone. Dragging is live (usePointerDrag in use-drag-reorder): the card lifts with a shadow and tilt, other cards make room and it settles; touch picks up on a 250ms long-press; lanes and the board auto-scroll; the keyboard picks up with Space, moves with the arrows, drops with Space and cancels with Escape. Moves are optimistic and a rejected onMove snaps back with a toast (moveErrorToast); readOnly turns moves off. New BoardCard: a round done tick, a two-line title, a context line, due and priority chips and the assignee's avatar. DataList's board view renders BoardCards (boardCard) and passes onAddToSection, addLabel, collapsedSections, onCollapsedSectionsChange and boardHeight through, with section defaultCollapsed, droppable, lockedReason and addable. RowAction items take items for a submenu. The list-page-01 and board-01 blocks now use a FilterBar toolbar over DataList views. 3f83fa2

📦 npm

  • @vegastack/design → 0.7.26 (was 0.7.25).
  • The design-system registry (@vegastack/ui) bumps 0.23.16 → 0.23.17.

[0.23.16] — September 25, 2026

🔧 Changed components

  • StatusIcon and PriorityIcon keep their semantic colour on hover, focus and highlight inside dropdown, context-menu, menubar, select, combobox, command, sidebar, navigation-menu, toggle, button and item rows: both icons carry data-icon-tone, and the rows' muted-icon default and state recolours skip it. Add data-icon-tone to any other semantic-coloured icon to get the same behaviour. 8d8eb5b

📦 npm

  • @vegastack/design → 0.7.25 (was 0.7.24).
  • The design-system registry (@vegastack/ui) bumps 0.23.15 → 0.23.16.

[0.23.15] — September 25, 2026

🔧 Changed components

  • RecordChip: the picker and the ↗ link now sit inside one shared p-0.5 inset, both 24px tall and rounded-full, so their hover and open backgrounds keep the same gap from the pill's border on every side (the pill grows from 28px to 30px). The shell is exported as SplitChip with SplitChipButton, SplitChipSeparator and splitChipIconActionClassName for any main-action-plus-icon-action pill; documented under Record Chip. 12ba8d9
  • RecordLayout: a record page's main column beside a sticky 320px right rail of cards (from lg up, scrolling on its own), with RecordDetailsSheet — a ghost ⓘ button that opens the details in a Sheet from the right — in its place on small screens. 6ae4f3a

📦 npm

  • @vegastack/design → 0.7.24 (was 0.7.23).
  • The design-system registry (@vegastack/ui) bumps 0.23.14 → 0.23.15.

[0.23.14] — September 25, 2026

🔧 Changed components

  • FieldDescription variant="footnote": a centred 12px muted line with a small gap above, the documented footer style for login-01 and the sign-up, forgot password and set password variants. 49ce48f

📦 npm

  • @vegastack/design → 0.7.23 (was 0.7.22).
  • The design-system registry (@vegastack/ui) bumps 0.23.13 → 0.23.14.

[0.23.13] — September 25, 2026

🔧 Changed components

  • Inbox: infinite scroll — onLoadMore / hasMore / loadingMore (and loadMoreError, endLabel) page through an IntersectionObserver sentinel, show three skeleton rows while a page loads (no spinner), a ghost "Try again" after a failure and "You’re all caught up" at the end (15 rows a page). Day-group labels drop their dividers and stick within the scroll area with 16px above (not on the first group) and 6px below; rows keep a 1px divider between them only. Titles are regular weight — foreground when unread, muted once read — with the actor and record in medium. The row ⋯ menu sizes to its content (224–320px). notifications-01 uses the new API in place of LoadMore, names its row item "Mute this type" (tooltip "Stop notifications like this") and demonstrates the loading, error and end states. e42fe8c
  • StatusIcon gains cancelled (CircleX, muted) and a static progress (CircleDashed; animated keeps the spinning Loader), blocked becomes CircleSlash in the warning ink and done a filled CircleCheck; new PriorityIcon (urgent / high / medium / low / none flags in semantic colours); docs recipes for a Status menu and Priority menu (DropdownMenu + icons + shortcuts O/P/B/D/C and 1/2/3/4/0, Alt-click for quick Done) and icons leading Select/Combobox options. 248dd90

📦 npm

  • @vegastack/design → 0.7.22 (was 0.7.21).
  • The design-system registry (@vegastack/ui) bumps 0.23.12 → 0.23.13.

[0.23.12] — September 25, 2026

🔧 Changed components

  • SearchableSelect (and so FilterBarFacet): a remote search that is loading with no rows yet shows five Skeleton rows the height of real options (two lines with itemToSecondaryLabel) instead of a blank panel; with rows already shown they stay and a small spinner marks the fetch. adce170

📦 npm

  • @vegastack/design → 0.7.21 (was 0.7.20).
  • The design-system registry (@vegastack/ui) bumps 0.23.11 → 0.23.12.

[0.23.11] — September 25, 2026

🔧 Changed components

  • DataList views: view is "list", "grid" or "board" over the same items, sections, sort, filters, paging, loading, empty and no-results states. The grid renders a MediaCard per row (sections as "Label · n" headings over a 1/2/3-column container grid, gridSize="lg" for a 16:9 image, renderCard to override); the board renders sections as Board lanes with onMove(row, from, to) and a per-section loading/loadMore/emptyState. onViewChange mounts the new ViewToggle (Grid | List | Board, labels hidden on a phone) in the FilterBar view slot and remembers the view for the session; views picks the offered views. Columns take a thumbnail (32px Thumbnail with thumbnailFallback). New MediaCard (whole-card link, 48px thumbnail, meta, badge, timestamp, ⋯ on hover/focus/touch, lg size), Thumbnail (32/48px, cover-fit, fallback) and ViewToggle; RowAction entries can be { type: "separator" } between groups (and an item can take separatorBefore), in row, grid-card and board-card menus; the Badge page documents the warning pill. 08196ae

📦 npm

  • @vegastack/design → 0.7.20 (was 0.7.19).
  • The design-system registry (@vegastack/ui) bumps 0.23.10 → 0.23.11.

[0.23.10] — September 25, 2026

🔧 Changed components

  • Meeting page parts: AudioPlayer gains variant="floating" (a centred pill) and a global player — AudioPlayerProvider, GlobalAudioPlayer, useGlobalPlayer and useGlobalPlayerTime — that keeps one recording playing across routes with a title link back; Transcript turns show time, a coloured speaker dot and the name above full-width text, TranscriptSpeakers adds speaker chips with rename (onSpeakerRename), and long transcripts mount progressively (batchSize); new MetaLine, RecordChip and StatusLine; new useTabsSwipe hook for touch swipe between Tabs, documented with the full-width default variant; review-split-01 shows the speaker chips. ffbf133

📦 npm

  • @vegastack/design → 0.7.19 (was 0.7.18).
  • The design-system registry (@vegastack/ui) bumps 0.23.9 → 0.23.10.

[0.23.9] — September 25, 2026

🔧 Changed components

  • List toolbars, tables and focus: FilterBar puts search (~320px) left and a Filters (n) toggle, scope Tabs and the view switch right, with compact rounded-md filter chips (tinted when set, a fixed 24px ×, "Status: 2") on a toggled row 12px below that scrolls sideways on a phone (the bottom sheet is gone); new DateRangeFilter with presets; SearchableSelect ticks async options and gains a person option (name plus muted email, both searched); DataList adds sortable columns with an indicator, compare/sortFirst/sortMode, a standard rowActions ⋯ column (32px, always last), un-underlined row links, and noResults; Empty always renders an icon; TabsList gains size="sm"; buttons inside a status Alert hover in the family's own tint; Dialog and Sheet open onto the first field, never the ×; and no focus rings anywhere except Tabs — keyboard focus is a subtle background tint (a border tint on text entry). 283057d

📦 npm

  • @vegastack/design → 0.7.18 (was 0.7.17).
  • @vegastack/design-tokens → 0.7.18 (was 0.7.1).
  • The design-system registry (@vegastack/ui) bumps 0.23.8 → 0.23.9.

[0.23.8] — September 25, 2026

🔧 Changed components

  • notifications-01 is rebuilt on the Inbox component to match the Regent app (Mark all read and ⋯, All | Unread chips, day groups, avatar or icon rows with the unread tint, action chips, "You’re all caught up"); command-search-01 uses CommandFilters, the built-in CommandFooter hints and the wide default size; the alert, checkbox (circle preview) and sidebar docs pages describe the current behaviour. 6353561

📦 npm

  • @vegastack/design → 0.7.17 (was 0.7.16).
  • The design-system registry (@vegastack/ui) bumps 0.23.7 → 0.23.8.

[0.23.7] — September 25, 2026

🔧 Changed components

  • InboxItem: unread rows drop the dot for a soft full-bleed tint and a medium title (regular when read); a fixed right column keeps the time on top with the read toggle and ⋯ menu below it on hover or focus. ca2bd35

📦 npm

  • @vegastack/design → 0.7.16 (was 0.7.15).
  • The design-system registry (@vegastack/ui) bumps 0.23.6 → 0.23.7.

[0.23.6] — September 25, 2026

🔧 Changed components

  • Leading icons are muted and follow their row — menu, select, combobox, command, sidebar, navigation-menu, toggle, ghost/outline button, breadcrumb and item-media icons rest at text-muted-foreground and go full colour on hover, focus, highlight, selection, active, open and pressed. Destructive rows keep text-destructive. 164e490

📦 npm

  • @vegastack/design → 0.7.15 (was 0.7.14).
  • The design-system registry (@vegastack/ui) bumps 0.23.5 → 0.23.6.

[0.23.5] — September 25, 2026

🔧 Changed components

  • Dates & times: one Intl-based module (@/lib/date-time) — formatRelative, formatDuration, formatDate, formatDateTime, formatDateRange, formatTimeOfDay, formatDueLabel, groupByDay, getTimeZone and the tz cookie script — plus DateTime, Duration, DueLabel, TimeZoneProvider/useTimeZone/TimeZoneScript beside RelativeTime (new format prop). The Relative Time docs page becomes "Dates & times" with a where-to-use table, and the design-system skill says which to pick. 323e8c8
  • Inbox: Inbox, InboxFilters, InboxGroup, InboxItem (avatar or muted icon, rich titles, action chips with loading and done states, hover/focus read toggle and menu, grouped counts), InboxEmpty, InboxSkeleton and InboxError for notification panels. af1194d

📦 npm

  • @vegastack/design → 0.7.14 (was 0.7.13).
  • The design-system registry (@vegastack/ui) bumps 0.23.4 → 0.23.5.

[0.23.4] — September 25, 2026

🔧 Changed components

  • Sidebar rows breathe and the user menu is one standard — SidebarMenu puts a 2px gap between rows so a hovered row never touches the active one, and SidebarGroup sits tighter so groups stack closer. The app-shell-01 user menu is now the standard account menu: avatar, name and role on the trigger; the same row with the email as the menu header; Profile, Settings, a Theme submenu (Light · Dark · System with icons and a check), Keyboard shortcuts, and Sign out — every item with an icon. See App shell 01. 5789ff6

📦 npm

  • @vegastack/design → 0.7.13 (was 0.7.12).
  • The design-system registry (@vegastack/ui) bumps 0.23.3 → 0.23.4.

[0.23.3] — September 25, 2026

🔧 Changed components

  • Command palette: CommandFilters row for type chips and selects, built-in Kbd key hints in CommandFooter, animated search icon in CommandLoading, the wide 820px palette as CommandDialog's default size, and no blank space below the footer. e5a31c1

📦 npm

  • @vegastack/design → 0.7.12 (was 0.7.11).
  • The design-system registry (@vegastack/ui) bumps 0.23.2 → 0.23.3.

[0.23.2] — September 25, 2026

🔧 Changed components

  • FilterBar is a two-row table toolbar — search, scope, view and actions on the first row; facets, "More" and onClear's "Clear" on the second, folding into a "Filters (n)" sheet on a narrow bar. An unset facet reads just its label and a set one is a filled pill with a "Clear Status" ×; searchPlacement and anyLabel are deprecated and ignored. a3ddf24
  • Overlay footers are as plain as their headers — DialogFooter and AlertDialogFooter drop the muted, top-bordered band and sit in the popup's own padding. SheetFooter right-aligns its actions at every width instead of stacking them full width; give a Cancel data-slot="sheet-cancel" to seat it at the start edge. Secondary actions use variant="secondary": AlertDialogCancel, DialogFooter showCloseButton, and MultiStepForm's Back, Skip and Exit now default to it, and the MultiStepFormActions row is right-aligned with no divider. See Sheet and Dialog. f22ce1f

📦 npm

  • @vegastack/design → 0.7.11 (was 0.7.10).
  • The design-system registry (@vegastack/ui) bumps 0.23.1 → 0.23.2.

[0.23.1] — September 25, 2026

🔧 Changed components

  • Checkbox takes shape="circle" — a round check for marking a task or to-do done; square stays the default for selection and form fields. 499e2dd

📦 npm

  • @vegastack/design → 0.7.10 (was 0.7.9).
  • The design-system registry (@vegastack/ui) bumps 0.23.0 → 0.23.1.

🐛 Fixed

  • Alert has no focus ring — an app can move focus to an error Alert so screen readers announce it; the alert no longer shows the focus ring, since it is not an interactive control. SheetContent takes showOverlay={false} to drop the backdrop for a non-modal panel docked beside the page. c126f9f
  • No focus ring on programmatic focus targets — AppShellContent's <main> (the skip-link and route-change focus target) no longer draws a focus outline, which showed as a line under the app header after navigation. The same applies to SheetContent's popup, the MultiStepForm step heading and section list, and the Stepper summary; interactive controls keep their focus rings. 008bea6

[0.23.0] — September 25, 2026

🔧 Changed components

  • Last facelift gaps — DataList (and DataGrid) reserve the first column and every mobile: "visible" column before fitting the rest, so a trailing Status or actions column no longer leaves a narrow table squeezed while an earlier column that should fold stays. tabsListVariants and tabsTriggerVariants draw horizontal tabs — the list height and the line indicator — whenever the group/tabs ancestor is not vertical, so route tabs need no data-orientation at all, and a scrolling route list keeps its indicator inside the scroll box. SearchableSelect's in-panel search keeps its own name inside a Field instead of taking the Field label, and searchLabel is now optional (default "Search"). docs · ac4341c

📦 npm

  • @vegastack/design → 0.7.9 (was 0.7.8).
  • The design-system registry (@vegastack/ui) bumps 0.22.0 → 0.23.0.

[0.22.0] — September 25, 2026

🔧 Changed components

  • Facelift gaps, all opt-in and backward compatible — EditableCell takes wrap (a page title wraps instead of truncating) and flush (its text lines up with the line below). DataList columns take mergedRender, so a value folded into the first column on a phone keeps its context ("4 to review", not "4") and mergedLayout="line" joins folded values into one compact meta line ("Today · High · Arjun Mehta"), and the list takes rowProps for per-row data-* attributes, a class and a highlighted state that flashes a new row. AudioPlayer takes onSourceExpired: a media error renews an expired signed URL once and resumes at the same position. MarkdownView takes headingOffset, moving every heading down that many levels (capped at h6). tabsListVariants scrolls a list that carries no data-orientation (route tabs), and only a vertical list turns the scroll box off. Board's card href now wins over one on the itemLinkRender template, as DataList's row link already did (the template's own ref still lands), and a card with a menu reserves end padding so a long title wraps before the ⋯ trigger. The Alert page documents a caller role (role="note") as the way to keep a page-load alert out of every live region, and the agent skill shipped in @vegastack/design names the new props. docs · f15f6da

📦 npm

  • @vegastack/design → 0.7.8 (was 0.7.7).
  • The design-system registry (@vegastack/ui) bumps 0.21.2 → 0.22.0.

[0.21.2] — September 25, 2026

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.21.1 → 0.21.2.

🐛 Fixed

  • useAsyncSearch aborts the in-flight request as soon as the query changes, so a slow response for the previous text can no longer fill the list while the new query waits out its debounce. 638d754

[0.21.1] — September 25, 2026

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.21.0 → 0.21.1.

🐛 Fixed

  • A pinned MultiStepFormActions row paints the surface it sits on — a card, a dialog, a sheet or a drawer, not always the page — so it no longer shows as a black band in dark mode. The Tabs page gains the DS-62 recipes Regent's list pages use: vertical tabs that turn into a horizontal line list below an @md container, and vertical route tabs with counts that become a NativeSelect jump in a narrow column. The docs' code blocks keep their copy button clear of a long first line. docs · c911726

[0.21.0] — September 25, 2026

🧩 New components

  • Three new blocks: a list page, the notification inbox and the search palette.

    • list-page-01: search first, a Status FilterBarFacet, and Mine | Team and Grid | List switches that never deselect. It shows the same records as a DataList with row links and row actions, or as industry-grouped grids of linked tiles, pages both views with Load more, and has three empty tiers: nothing yet, no matches and couldn't load (DS-52). docs
    • notifications-01: InboxSheet. The rail row and the bell carry the unread count in their names. The sheet has All | Unread, Today and Earlier groups of linked rows with the neutral unread dot, "Mark all read" announced once, Load older, and loading, empty, caught-up and error states (DS-56). docs
    • command-search-01: CommandSearch has scope chips (Alt+←/→ from the input), recents, results grouped by type, and aborted stale searches. It has loading, no-results and error states, footer hints from formatShortcut, and ⌘↵ / Ctrl+↵ to open in a new tab (DS-57). docs · 1f8da35

🔧 Changed components

  • The board-01 block pages its Backlog and gives each card its own menu (DS-80).

    • board-01: the Backlog lane shows its full count and loads the rest with Load more (loadMore), and each card has Edit and Archive above Move in its ⋯ menu (getItemActions). docs · 1f8da35

📦 npm

  • @vegastack/design → 0.7.7 (was 0.7.6).
  • The design-system registry (@vegastack/ui) bumps 0.20.0 → 0.21.0.

📚 Docs

  • The vegastack-design-system agent skill now lists the facelift components and blocks: SearchableSelect multiple and server search, FilterBarFacet, DataList paging, row links, sections and row actions, LoadMore, useAsyncSearch, SortableList tiles, Transcript with a docked AudioPlayer, and a starter-blocks roster from app-shell-01 to status-pages-01. 4178c7f

  • The multi-step form guide gains a "Full-page flow" example with the step in the route.

    • MultiStepForm: the example keeps the step in the route with a controlled step, onStepChange(id, { replace }), a vertical nav, sticky actions on a phone, and a Review step with Change links and a checklist (DS-55). docs · 1f8da35

🐛 Fixed

  • Fixes from the consolidated review of the facelift design-system work (Regent #136–#140).

    • Two-line rows (Command, Select, Combobox and the three menus): the second line is read once, as the row's description, and no longer repeated inside its name. A caller's own aria-describedby still wins, and the second line then stays in the name. docs
    • CommandLoading reads "Searching…" by default. With a progress value it stays a labelled progressbar, so the value reaches assistive technology. docs
    • LoadMore: when the last batch arrives the footer keeps the focus its button had, instead of dropping it to the page, and reads its endLabel (or "End of list"). It is now a client component.
    • SortableList and Board: a row or card action that removes its row moves focus to the row that took its place (its handle, or its menu when the row is locked). SortableList takes the accessor as getItemActions, Board's name; menuItems is deprecated. Board takes actionsLabel, a lane's loadMore takes the footer's labels, and a read-only lane keeps a card's own actions (only the Move items go). docs
    • RowActionsMenu: a disabled single icon action is described by its disabledReason, not only in its tooltip.
    • FilterBar: facets have their own seat (facets), after the search and before the chips; an "Add filter" option with an editor opens it on the new chip; FilterChip takes defaultEditorOpen. FilterBarFacet: the pinned "Selected" group comes first and is taken when the list opens, so toggling a row no longer moves it; the facet is the bar's height (h-8); removeLabel, selectedGroupLabel and moreGroupLabel override its strings. docs
    • FilterBuilder (filter-bar-managed): fieldPicker="searchable" (with fieldPickerProps) searches the fields, an option value with more than seven options is a search picker and a several-values editor is SearchableSelect multiple, and a row error is tied to the field picker whenever the operator takes no value. docs
    • SearchInput: a value reset from outside (a host's "Clear filters") drops the typed value's pending onValueCommitted.
    • SearchableSelect: a server search with no rows yet shows its loading line; groupOrder puts named groups first; its fallback name no longer overrides a <label for>; it takes aria-invalid and aria-describedby.
    • TextEdit: a disabled Base UI Field disables the editor.
    • RelativeTime: formatOptions with dateStyle or timeStyle no longer throws for another year's date or with withTime.
    • useAsyncSearch: Load more waits out a typed query's debounce instead of pairing the new query with the old cursor.
    • Transcript: matches after characters whose lower case is longer are highlighted exactly; segments that only add matches keep the reader's position without a new announcement; a press on the scrollbar pauses follow; the loading line is a status.
    • AudioPlayer: a rejected lazy src or a failed media load shows the player's own error line (loadErrorLabel); the loading glyph is Spinner. AttachmentProgress speaks a clamped percent.
    • VegaStackProvider: a custom toaster={<Toaster limit timeout />} sets the provider's queue, and a Toaster on another manager brings its own provider.
    • AppShell takes keyboardShortcut (or false), forwarded to the sidebar. SidebarStateScript takes a nonce. The sidebar docs render a collapsible menu row as the SidebarMenuItem, show the unavailable item's TBD badge and reason, and format the shortcut per platform.
    • DataList section rows use the group-label face; SettingsSection titles use the heading face; Board keeps one stable ref per card.
    • Blocks: command-search-01 keeps Enter on its scope chips and Try again, follows only http(s) links, debounces its search, announces a scope change and a settled result count once, and has all ten scopes; the Mod+K shortcuts in command-search-01 and app-shell-01 ignore text fields; list-page-01 searches on the settled query, adds an Industry facet, sets mobile on every column, takes readOnly, and moves focus to the search after "Clear filters", as board-01 does; both use one control height per row; notifications-01 and list-page-01 take loading / error (and notifications-01 loadMore) instead of status / loadOlder; review-split-01 keeps its player docked in its column and names its tab count; counts in tabs are muted tabular numbers with a spoken suffix.
    • Docs: Stat shows linked tiles; Dropzone's upload queue has per-file fields and a save retry; Pagination describes its <a> links; list-page-01 keeps view state in guarded sessionStorage.
    • vegastack-design doctor points retired text-h1/text-h2 at the page and section heading faces.
    • Migration: the data-slot values notification-bell-dot, row-action and row-actions-trigger are now notification-dot, row-actions-menu-action and row-actions-menu-trigger. Copied blocks are yours: re-add list-page-01 or notifications-01 to take the prop renames. From 0.19.0 (not in its notes): a mixed checkbox shows a minus, disabled checkboxes and radios dim outside a Field too, and Card and Empty are client modules. 5f33be1

[0.20.0] — September 25, 2026

🧩 New components

  • Three new blocks: a settings hub, the status pages and a two-pane review page.

    • settings-02: the settings landing page. It has one section per area, and each area is a container-query grid of whole-tile links, each named by its title and described by one fact. An area that isn't built yet is a plain "TBD" tile, not a tab stop (DS-53). docs
    • status-pages-01: NotFoundPage, ForbiddenPage and ErrorPage, each usable in the shell or standalone. Each is an Empty whose title is the page's h1 and gives one way out. The error page adds "Try again" and, when there is a digest, the reference with a copy button (DS-60). docs
    • review-split-01: ReviewSplit puts the summary and action items beside a sticky transcript, with the docked AudioPlayer at the end of the column. The block measures its own container: the layout and the switch to tabs change together at 56rem, and each panel mounts once (DS-58). docs · a109931
  • useAsyncSearch — a new hook for server search over a cursor-paged list: the query updates as you type and the request waits for TIMINGS.searchDebounceMs (a new 300 ms timing in @vegastack/design), a newer request aborts the last and late responses are dropped, loadMore pages with the returned cursor, and a failure keeps the loaded items with a retry. Documented in the components guide. ce43c9b

🔧 Changed components

  • The app-shell-01 and board-01 blocks are modernised as the shell and board references (DS-80).

    • app-shell-01: the rail has a workspace menu (a real menu trigger) and a Search row that opens a palette on ⌘K / Ctrl+K, with its hint from formatShortcut. It also has an Inbox row whose unread count is part of its name, grouped links with aria-current="page", a collapsible "Coming soon" group, and a user menu with the theme choice. The page is AppShellPage › PageHeader h1 › linked stat tiles, which replace the hand-rolled stat cards. The docs page shows SidebarStateScript. docs
    • board-01: AppShellPage › PageHeader h1 › a FilterBar (search plus an Assignee facet) over Board. Lanes are named with their count ("Backlog, 3 tasks"), cards link to their task, and filters that match nothing show "No matches" with "Clear filters". docs
    • Migration: the board's view switch that nothing read and its fixed toolbar widths are gone, and every href="#" in both blocks is a real route. A copy you already own is unaffected until you copy the block again. a109931
  • Board lanes page with column.loadMore (the shared LoadMore footer inside the lane's scroll), and cards take their own actions through getItemActions — listed first in the card's ⋯ menu above the Move items, which is then named Actions for {card}. SortableList rows do the same through menuItems, on locked rows too. docs · docs · ce43c9b

  • Combobox exports ComboboxStatus, Base UI's own polite status region for a list that loads asynchronously. Render it as a sibling of ComboboxList and change its children ("Searching…", then the result count) rather than mounting it conditionally. It is screen-reader-only by default; visible shows the message as a muted row above the list. It carries data-slot="combobox-status" (decision API-27). c087ef9

  • settings-01 is now the reference settings page, and ToggleGroup moves with the up and down arrows when vertical.

    • settings-01: an AppShellPage size="narrow" with a PageHeader, full-width controls, an ActionBar save bar that appears only once something changed (Discard restores the defaults), and "Delete workspace" confirmed in an AlertDialog. docs
    • ToggleGroup: orientation="vertical" now reaches the primitive, so the arrow keys follow the column. docs
    • AppShell: the docs show a static or cached shell kept collapsed on first paint with SidebarStateScript and useSidebarCookieOpen, and the preview's counts use badgeLabel. docs · a08665c
  • FilterBar gains FilterBarFacet — a "Status: Open" facet on SearchableSelect, single or multiple, local or server-searched, pinnable and removable — and chips that edit in place: a filter's editor opens from its label and value (FilterChipTrigger), Escape returns focus to the chip, and onEditorOpenChange reports it. DataList rows become real links with getRowHref (and rowLinkRender for a router link): modifier and middle clicks work anywhere on the row, and getRowLabel names each selection checkbox. docs · docs · c4c7dff

  • SearchInput and FilterBar send a settled query. SearchInput gains onValueCommitted, which fires after debounceMs of quiet (default TIMINGS.searchDebounceMs, 300 ms) and at once on Enter and on clear, while onValueChange keeps the field instant; FilterBar's search passes both through. FilterBar gains searchPlacement="start" for search-first lists, its chips now read "Label: value" with the colon the docs always promised, and children is gone from its type (it was silently dropped). docs · docs · ce43c9b

  • FilterBuilder edits flat condition rules: allowGroups={false}, a prefix that names the rule ("Required when"), labels for its own words, conditionError for a row's own message, and summary="sentence" for a read-only sentence. describeFilter and formatRange print a tree or a range as text, fields take options and a unit, and five value editors ship with it — TextValueEditor, NumberValueEditor, NumberRangeEditor, OptionValueEditor, OptionsValueEditor — picked by default from the operator's valueShape. docs · ce43c9b

  • DataList gains collapsible sections (with getRowSection and groupState/defaultGroupState/onGroupStateChange) and a rowActionsColumn helper: one ⋯ menu per row, named Actions for {row}, with link actions, destructive ink, disabled actions that stay reachable and read their reason, and a single-icon shortcut. The parts behind them — SectionRow, SectionToggle, GroupState, RowAction, RowActionsMenu, RowActionMenuItems — are exported from data-table-parts. DataGrid's group header now uses the same toggle: its count reads in muted tabular numerals without parentheses ("Open 2", heard as "Open, 2 rows"). docs · docs · ce43c9b

  • The login-01 block is rebuilt as a real sign-in page to the block rules (DS-79).

    • login-01: one h1 "Sign in" (CardTitle render={<h1 />}), Email (autoComplete="email") and Password through PasswordInput (autoComplete="current-password"), each labelled and described by its Field, a FieldError per field with focus on the first invalid one, a live destructive Alert shown only after a rejected sign-in, and a submit Button that loads in place without changing width. docs
    • LoginForm takes your signIn call; "Forgot password?" and "Sign up" are real links (forgotPasswordHref, signUpHref). The docs page adds sign-up and forgot-password recipes on the same frame.
    • Migration: the "Login with Google" button and every href="#" are gone; a copy you already own is unaffected until you copy the block again. f3dbf16
  • MultiStepForm tells a route-driven host whether to push or replace: onStepChange(id, { replace }) passes replace: false for Next, Back and rail jumps, and true when the form clamps a step it will not admit or follows the address bar's hash. One-argument handlers keep working. docs · c087ef9

  • SearchableSelect searches on the server and picks several values: remote stops local filtering, onSearchChange/loading/error/onRetry/loadMore take useAsyncSearch as-is (with "Searching…" announced through the panel's own status region and the Load more footer under the rows), leadingItems stay first and unfiltered, multiple makes the value an array ({a}, {b} or {n} selected), itemToDescription and itemToDisabledReason add a described second line, groupBy adds headings and renderTriggerValue rewrites the trigger text. docs · c4c7dff

🛠 CLI & tooling

  • changelog-lint refuses a bare {…} outside inline code: the docs changelog is MDX, so an unescaped brace evaluates as JavaScript and breaks the public docs build. cd9b28d
  • The real shadcn add consume gate retries a shadcn add that fails on a transient network error (EAI_AGAIN and friends from ui.shadcn.com) twice before failing, so a runner's flaky resolver no longer blocks a deploy; every other failure still fails on the first attempt. dc8608a
  • The deploy workflow trusts its container checkout before it checks that the release commit is on main, so the public distribution proof no longer stops on git's dubious-ownership guard. a62eb9f

📦 npm

  • @vegastack/design → 0.7.6 (was 0.7.5).
  • The design-system registry (@vegastack/ui) bumps 0.19.0 → 0.20.0.

📚 Docs

  • The doctrine now describes the form, toast and overlay behaviour that shipped in the facelift fixes.

    • Forms: the skill and design.md say Field wires its control through Base UI Field — label, description and error ids, and aria-invalid from data-invalid — so ids are passed only to override, and an explicit aria-* prop merges with the Field's. docs
    • Toast: one action, one toast — a repeat rewrites the live toast with toast.update. docs
    • Elevation: one overlay width table for Dialog, Sheet and CommandDialog size. docs · 1786767
  • The Stat page gains a "Linked stat tiles" example: each count is a whole-tile link to the list it counts.

    • Stat: tiles are Item variant="outline" rendered as a link, in a container-query grid. The name reads label first ("Overdue tasks 3"), the value is tabular-nums in the regular font, and a Skeleton holds each tile's box while loading (DS-59). docs
    • Empty: the docs example's "404 - Not Found" now reads "Page not found". docs · a109931

🐛 Fixed

  • Board type-checks in apps without @types/node: its dev-only lane-name warning declares the process.env.NODE_ENV it reads. The sortable-list page quotes Actions for {label} as code, and scroll-area documents its props in a table, so the public docs build and export pass again. ad7294f

[0.19.0] — September 24, 2026

🧩 New components

  • LoadMore — the one "Load more" footer for keyset lists: an outline button that keeps its width and focus while the next batch loads, an error line with Try again, and an optional endLabel once the list has ended. LoadMoreState (hasMore, onLoadMore, loading, error) is the shape lists, board lanes and data hooks pass around. docs · d7e9562

  • Transcript is a new component for the text of a recording: timestamped, speaker-labelled lines on MessageScroller's engine.

    • The line playing at currentTime gets aria-current="true" and stays centred while following. Scrolling the list pauses following, and "Back to current line" brings it back.
    • Each timestamp is a "Play from 0:15" button that calls onSeek. Without onSeek, timestamps are plain text.
    • TranscriptSearch highlights matches with <mark>. Enter and Shift+Enter move between them, and each move announces "2 of 5" or "No matches".
    • loading and emptyState cover the states before there is any text.
    • Add it with shadcn add @vegastack/transcript. b38e9a7

🔧 Changed components

  • Attachment now covers file and media tiles on any surface, not only chat. AttachmentGroup takes layout="grid" to wrap tiles into equal columns (the scrolling row stays the default), a new AttachmentProgress part shows a determinate upload bar built on Progress that announces its percentage, and muted dims the media of a file no longer in use. Image media now styles a nested <img>, so the system Image and its fallback fill the slot. The item now depends on @vegastack/progress (decision API-28). d5e9404

  • AudioPlayer docks, closes, loads its source lazily and seeks from outside. docked pins it to the bottom of its scroll column (sticky, bordered, on the popover surface, clear of the bottom safe area) as a region named by label. open and onOpenChange hide it with a close button (closeLabel, "Close player") that pauses, reports false and returns focus to the control that opened it; a hidden player stays mounted and inert, and Escape does not close it. src also takes a function, resolved once on the first play. loading shows and announces "Loading audio…" (loadingLabel) once, and error renders an alert with "Try again" (retryLabel, onRetry). actionsRef exposes seek(seconds, { play }), play() and pause(); a seek before the metadata loads is applied when it does. ref is still the root element. docs · 10f8d06

  • NotificationBell exports NotificationDot, the one unread dot for rows, inbox items and nav items (intent default or destructive, decorative), and its dot mode now draws it: the dot's default intent changes from destructive to primary, and its data-slot is notification-bell-dot (the count pill keeps notification-bell-badge). A new countLabel words the count in the accessible name ("Notifications, 3 unread" by default). usePlatform now exports formatShortcutKey and formatShortcut, which turn "mod" into ⌘ on macOS and Ctrl elsewhere; ShortcutOverlay uses them and renders the same key labels as before. docs · docs · 10f8d06

  • Board lanes show their total as a muted count, lanes and cards have names, and cards can be links. The lane count is now muted tabular-nums text instead of a Badge, and it shows count (the lane's total) when the lane has loaded only some of its cards. docs

    • Names: a lane takes a plain-text label (required when its title is not a string; development warns without one), and getItemLabel names each card. Each lane is a region named "Open, 14 tasks" (countLabel supplies the noun, default "cards"), the Move menu says "Move to In progress", each card's menu control is "Move Write spec", and move announcements name the card and the lane instead of a column id.
    • Lane states: loading shows skeleton cards and marks the lane aria-busy; emptyState replaces the default "No cards" drop target; defaultCollapsed starts a lane collapsed (collapsed stays as its alias). "Drag a card here" shows only where a pointer drag can start.
    • Cards as links: getItemHref renders a card as a real link (itemLinkRender swaps in your router's link). Clicks and modifier clicks are the browser's own, Enter follows the link, and Space still lifts the card into move mode. Card content no longer adds a second tab stop through TruncatedText. 88b7a1e
  • DataList and DataGrid page keyset lists with the shared LoadMore footer. DataList gains loadMore (the footer sits above footer, the loaded rows stay while the next batch loads, and the table reports aria-rowcount="-1" while more rows exist). DataGrid's footer is now LoadMore too: an outline button instead of a ghost one, Try again after loadMore.error, and no "All rows loaded" caption unless you pass loadMore.endLabel. DataGridLoadMore is a deprecated alias of LoadMoreState. docs · docs · d7e9562

  • Page chrome and pickers gain the props the facelift pages need: a framework back link and a page container, zone-correct relative dates, one inline picker tier, Field-wired chips and pickers, and one toast queue.

    • PageHeader: backRender takes your router's link element for the back affordance, titleLines clamps or frees the title ("none" wraps), meta is a <div> row that can hold controls, and the title renders the page-heading face font-heading text-2xl font-semibold. docs
    • AppShellPage (new part): the one page container — page gutters, a gap-6 rhythm and a size measure of narrow (768px), default (1280px) or full; AppShellSkeleton uses the same gutters. docs
    • RelativeTime: timeZone decides the calendar day in the label, the server render and the tooltip; capitalize, formatOptions and withTime shape the day label; a label with no tooltip is plain inline text. docs
    • SearchableSelect and DatePicker: size="sm" and variant="ghost" form one inline tier with Select; clearing returns focus to the trigger. SearchableSelect is named by its FieldLabel inside a Field and takes name, required and contentClassName; DatePicker takes clearable, clearLabel and renderValue. docs
    • ChipInput: inside a Field the label, description and error reach the input; name posts every chip, and max/maxLabel cap the entries. docs
    • EditableCell: renderValue shows a label for an id in display mode. docs
    • MultiStepForm: MultiStepFormActions sticky (or "narrow") keeps the action row in view on a long step and reports data-stuck. docs
    • FilterBuilder: maxDepth={1} shows no group control, operators declare a valueShape and a shape change clears the value, "Value required" waits for a touch or a submit, and the cap copy is singular for one. docs
    • Toast and VegaStackProvider: the provider carries the module toast manager and a Toaster below it reuses that provider, so toast() and useToastManager() feed one queue with one viewport (OVL-17). docs
    • AnimatedNumber: the default face is the regular font with tabular digits, not mono. docs
    • Migration: a test pinning font-mono on AnimatedNumber, the PageHeader title classes, a "Value required" message on an untouched FilterBuilder row, or a 24px box around a RelativeTime with title={false} sees the new output. 70ff12f
  • sortable-list keeps a locked row in the list: a row with disabled: true now shows a spacer the size of the handle (data-slot="sortable-list-handle-spacer") and KEEPS its row menu with the Move items disabled, where it used to lose both. New props: lockedReason (the accessible description of a locked row's disabled Move items), renderActions (inline actions before the row menu), actionsLabel (the menu trigger's name) and layout="grid" (auto-fill image tiles with the handle and actions over the tile's top corners). The row menu trigger is now named Actions for {label} by default, where it was Move {label}. use-drag-reorder gains columns (a number or "auto", measured from where items wrap) so ↑/↓ in keyboard move mode step a whole row when a horizontal axis wraps into a grid, and drag-item draws the left and right drop-edge hairlines a horizontal axis reports. The component roster in the shipped vegastack-design-system skill describes the new surface. 5500590

  • TextEdit edits Markdown: format="markdown" makes value, onValueChange and onSubmit carry Markdown instead of HTML (through Tiptap's own @tiptap/markdown, a new dependency of the item), and loading a document never fires onValueChange. New readOnly (no toolbar, aria-readonly) and disabled (no toolbar, dimmed, aria-disabled) props; editable is deprecated — editable={false} means readOnly. docs · 6fda658

📦 npm

  • @vegastack/design → 0.7.5 (was 0.7.4).
  • The design-system registry (@vegastack/ui) bumps 0.18.0 → 0.19.0.

📚 Docs

  • The Dropzone page gains an "Upload queue" example: accepted files upload through a host-owned uploadFile(file, { onProgress, signal }) call and render as Attachment tiles in an AttachmentGroup layout="grid", each with AttachmentProgress, cancel, retry and discard, while refused files join the queue as error tiles that say why. d5e9404

🐛 Fixed

  • Tables and pagers read and tab the way they should: headers in the sans face, no tab stop per timestamp, pagination links that are links, and pager controls that are buttons.

    • DataList and DataGrid: a mono column's header is in the sans face — mono styles body cells only, for codes and IDs (DS-01) — and clipped text or a RelativeTime in a cell is no longer a tab stop (DS-68). headerCellClass is exported from the shared table parts. docs
    • Pagination: PaginationLink is a real <a> wearing the button recipe, so it keeps the link role (A11Y-23); the landmark is named "Pagination" (label) and the ellipsis copy is morePagesLabel (VOI-1). docs
    • DataListPager: page controls are buttons with aria-current="page" on the current page; onPageSizeChange is optional, and without it there is no rows-per-page chooser (DS-27, DS-71). docs
    • TruncatedText: IconText's label slot is icon-text-label, and TableCellText mono is text-sm. docs
    • useDragReorder (and SortableList, Board): a handle that mounts after its row still owns the pointer drag (DS-70). docs
    • Migration: a test that asserts role="button" on a pagination link, font-mono on a mono column's header, a timestamp in a table as a tab stop, text-xs on TableCellText mono, or the icon-text-sm font-medium slot now sees the new output; a DataListPager page control is a <button>, not an <a>. 4b9f0d0
  • Sixteen upstream-backed components gain the facelift fixes: counts and current page read correctly in the sidebar, two-line rows link their description, overlays get sizes and scrolling bodies, and built-in copy is sentence case.

    • Sidebar: SidebarMenuButton takes badge/badgeLabel, so the count is part of the item's name and the visual badge is aria-hidden. An active link carries aria-current="page". The trigger reads "Toggle sidebar" (triggerLabel). Mod+B ignores text fields and takes keyboardShortcut (false turns it off). Skeleton widths are deterministic. SidebarStateScript and useSidebarCookieOpen keep a collapsed sidebar collapsed on a static shell's first paint, and a controlled provider writes no cookie. docs
    • Item: a link or button row inside ItemGroup keeps its own role, and a listitem wrapper takes the list role. New ItemGroupLabel names the group. docs
    • Command: CommandDialog takes size; new CommandLoading and CommandFooter; two-line results; resultsLabel. Default copy is "Command palette" and "Search for a command…". docs
    • Select, Combobox, DropdownMenu, ContextMenu and Menubar: a two-line row (ItemTitle + ItemDescription) reads its second line as the row's description, which is where a disabled row gives its reason. docs
    • Checkbox: the mixed state shows a minus. Checkbox and RadioGroup dim when disabled outside a Field too. docs
    • Tabs: orientation="vertical" now reaches the primitive, so arrow keys and aria-orientation follow it. A line tab list scrolls inside itself. tabsTriggerVariants is exported for route tabs. docs
    • Sheet and Dialog: side sheets take size (sm, default, lg, xl). New SheetBody and DialogBody scroll between a fixed header and footer, new SheetAction sits beside the close button, and closeLabel renames the close control. docs
    • CardTitle and EmptyTitle take render, so a title can be a real heading. docs
    • Alert: the role is status unless live is set on a destructive or warning alert. Pass live for an alert that appears after a user action. AlertAction now takes its own column beside the text and drops below it on a narrow alert, so a long label never overlaps the title. docs
    • ToggleGroup: takes deselectable={false} to always keep one item pressed, and wrap. docs
    • Badge: exports BadgeVariant. docs
    • ScrollArea: aria-label names the scrolling viewport, which becomes a region, and viewportRef reaches it. docs
    • Migration: the default copy changed ("Toggle sidebar", "Command palette"). Link rows in an ItemGroup gain a wrapper div. An Alert that was assertive at load is now status. 1ae9f5b
  • Inside a Field, a Combobox's "Show suggestions" toggle keeps its own name instead of taking the Field's label, so the input is the one element the Field names.

    • Since 0.18.0 the toggle answered to the field's name too: a screen reader heard a second button with the label, and a getByLabel query found two elements. docs
    • The geometry lane gets back the Select width and ButtonGroup checks and the textEditInsideField dynamic-DOM selector that a later merge dropped. a6362db
  • Inside a Field, a RadioGroup is the control the Field names and each radio item keeps its own name, instead of every item answering to the Field's label.

    • An item in a Field of its own (the per-item pattern) still takes that Field's label. docs · 70ff12f

[0.18.0] — September 24, 2026

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.17.1 → 0.18.0.

🐛 Fixed

  • Field now wires the control inside it — label, description, error and invalid state — and a Select trigger fills its parent by default, with a new ghost inline variant.

    • Field renders Base UI's Field underneath (API-26): a control inside a Field gets its label, the ids of the FieldDescription and FieldError that are rendered, and aria-invalid from data-invalid, with no id, htmlFor or aria-* props. An explicit id still wins, and an explicit aria-describedby keeps its ids first with the Field's ids after them. FieldTitle's slot is now field-title. docs
    • Input, Select, Combobox, NumberField, PasswordInput, Checkbox, RadioGroup, Switch and Slider read the Field through Base UI; Textarea (now a client component), InputGroup, DatePicker, DateRangePicker and TextEdit render their focusable element through Base UI Field.Control.
    • NumberField puts aria-describedby, aria-labelledby and aria-invalid on its <input>, never the group div, and an explicit id labels it inside a Field (DS-67). docs
    • Select: the default trigger is w-full instead of w-fit (inside a ButtonGroup it still sizes to its content), and variant="ghost" is the inline, content-width trigger with no border at rest (API-24). docs
    • Migration: a Select trigger that should size to its content outside a ButtonGroup now needs variant="ghost" or a width class. A test asserting that a control inside a Field has no aria-describedby or aria-invalid now sees the Field's ids and state; a selector for [data-slot="field-label"] no longer matches FieldTitle; and a standalone Textarea, DatePicker trigger or TextEdit now carries a generated id, as Base UI's Input already did. b5daf25

[0.17.1] — September 24, 2026

📦 npm

  • @vegastack/design → 0.7.4 (was 0.7.3).
  • The design-system registry (@vegastack/ui) bumps 0.17.0 → 0.17.1.

📚 Docs

  • The docs and the agent skills now state one convention per concern: numbers, page headings, link buttons, theme choice, empty states, view switches, page spacing and the conventions for components we own.

    • Numbers (counts, dates, amounts) are the regular font with tabular-nums; font-mono is for code and identifiers only. docs
    • One page heading: PageHeader, font-heading text-2xl font-semibold; a section is font-heading text-base font-medium.
    • One link-button recipe: <Link className={buttonVariants({ variant, size })}>, never Button render={<Link/>}. docs
    • Theme choice is a Light / Dark / System radio group in the user menu. docs
    • Two new empty-state tiers, "No matches" and "Couldn't load", plus default copy rules. docs
    • One view-switch rule: RadioGroup for a form value, ToggleGroup for a view or scope switch, Tabs for page regions, links for URLs.
    • A page-rhythm recipe for gutters and gaps. docs
    • design.md gains the conventions for components we own and a component / part / block / example decision tree; the public skill gains "Names hide abilities" and "Which component for X".
    • Registry metadata for button, label, chip, filter-bar-managed, select, command, emoji-picker, chip-input, message-scroller, badge, toggle-group and radio-group now says only what the components do.
    • The shadcn-reset decision register is tracked in git, with true counts (180 rows: 108 shadcn, 72 ours). 1f11fbc

[0.17.0] — September 24, 2026

🗑 Removed / renamed

  • Demo blocks — the registry no longer serves 28 demo blocks that did not meet the rulebook (27 upstream compositions and our onboarding-01), and the @dnd-kit drag engine they needed is gone with them.

    • Removed: dashboard-01, login-02…login-05, signup-01…signup-05, sidebar-01…sidebar-16, preview-03 and onboarding-01.
    • Kept: login-01, app-shell-01, board-01, settings-01 and the 68 chart blocks.
    • Where each recipe went: shells and the sidebar variants → app-shell-01 and the Sidebar page; sign-in → login-01; sign-up → compose it from login-01's frame with Field and PasswordInput; the dashboard and preview-03 showcase → app-shell-01 plus the Chart and Stat pages; the getting-started checklist → the Item page's Checklist example.
    • @dnd-kit/* and thesvg leave @vegastack/ui; @atlaskit/pragmatic-drag-and-drop stays the one drag engine, and Icon/BrandIcon in @vegastack/design keep their own thesvg.
    • An installed copy of a removed block is yours and keeps working; shadcn add of a removed name now fails. docs · 27433fe

📦 npm

  • @vegastack/design → 0.7.3 (was 0.7.2).
  • The design-system registry (@vegastack/ui) bumps 0.16.1 → 0.17.0.

📚 Docs

  • Item — a new Checklist example composes a getting-started checklist from Progress, ItemGroup, Item and StatusIcon. docs · 27433fe

[0.16.1] — September 24, 2026

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.16.0 → 0.16.1.

🐛 Fixed

  • stepper and multi-step-form keep the current-step marker (aria-current="step", the step count and focus-follow) when the current step fails validation. StepperStep gains an optional current flag that marks the step current alongside an error or warning state; MultiStepForm sets it, so a refused step now reads as both current and in error. cefa8f5

[0.16.0] — September 24, 2026

🔧 Changed components

  • DatePicker / DateRangePicker bind to a FieldLabel, and a long EditableCell value truncates with its full text on hover. Both pickers now accept id, aria-describedby and aria-invalid and forward them to the trigger button, so <FieldLabel htmlFor> names the picker (clicking the label opens it) and a FieldDescription/FieldError and the invalid border work as they do for Input. EditableCell's display stays on one line and ellipsizes to its container — a long page title at 390px no longer overflows — and when the value is actually clipped the display carries it as title and data-truncated (measured with use-overflow, now a registry dependency); the editor still opens with the whole value. ee0cbfd

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.15.0 → 0.16.0.

[0.15.0] — September 23, 2026

🔧 Changed components

  • DialogContent takes a size prop — sm, default, lg or xl, published as data-size — so a bulk-edit table or an attribute editor gets a wider dialog without a className override. default keeps upstream's sm:max-w-sm; sm, lg and xl step the cap to max-w-xs, max-w-2xl and max-w-5xl. The axis mirrors AlertDialogContent's (decision OVL-16). f93704e
  • EditableCell inherits the surrounding type instead of fixing its display and editor at text-sm, so it can edit a page title at the heading's size and weight (<h1 className="text-3xl font-semibold"><EditableCell … /></h1>) without descendant-selector overrides. At the 14px body default it looks as before; below md the editor still never drops under 16px. e4c7208

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.14.0 → 0.15.0.

[0.14.0] — September 23, 2026

🧩 New components

  • PasswordInput is back as a thin InputGroup composition with a show/hide toggle.

    • The toggle is a type="button" icon button named "Show password" / "Hide password" with aria-pressed, so it never submits a form.
    • Every native input prop (id, name, autoComplete, aria-invalid) and the React 19 ref land on the inner input, so it works inside Field.
    • Add it with shadcn add @vegastack/password-input. c2a246e

🛠 CLI & tooling

  • vegastack-design doctor no longer reports a password-input import as retired, and the shipped skills stop listing PasswordInput among the retired components — it is a registry component again. c2a246e

📦 npm

  • @vegastack/design → 0.7.2 (was 0.7.1).
  • The design-system registry (@vegastack/ui) bumps 0.13.0 → 0.14.0.

[0.13.0] — September 23, 2026

🧩 New components

  • DataList never forces a horizontal scroll, and the new DataListPager pages it.

    • DataList columns take the same responsive posture as DataGrid — minWidth (default 120) and mobile: "visible" | "hidden" | "merge" (default "merge") — now declared once on the shared column layout. In a narrow container, overflow columns stack into the first cell, hidden columns are dropped and counted in a line the table is described by, and visible columns never hide (if they still do not fit, the table is squeezed). See Fitting the width.
    • A merged value always wraps and wears its own column's face, so a mono or end-aligned first column no longer pins the stack to one line. If the table still overflows after that — a long unbroken value, a one-line mono id, several visible columns — it is squeezed (data-squeezed): every cell, and the text a custom render puts in it, may break rather than scroll. A truncate span or a whitespace-nowrap row wraps instead (an auto-layout table cannot truncate), and text-like parts grow taller to hold a wrapped label: a Badge (or badgeVariants() on your own element, or ToolCallChip), a Chip or Tag, a link-variant Button (or buttonVariants({ variant: "link" })) and EditableCell's value. Controls and fixed-size content are never wrapped, so their label never spills their box — recognised by what reaches the DOM: every other Button and any element styled with buttonVariants(), toggleVariants(), navigationMenuTriggerStyle(), tabsListVariants() or stepperNodeVariants(), a native button/input/select/textarea, the control roles, an Avatar and a Kbd (held on one line at full width). The row grows taller instead; only a kept part wider than the container itself still scrolls the table (see Fitting the width). A Checkbox, Switch or Radio in the last column keeps 12px of end padding, so its 24px pointer target no longer scrolls the table.
    • When the sorted column is merged or hidden, a "Sorted by …" line under the table states the order its header can no longer show. DataGrid does the same in its toolbar, describes the grid by both lines, and now gives merged values their header as a screen-reader prefix.
    • DataList always renders one data-list-root stack, so its status lines never land in your own grid or flex container.
    • Sortable headers use the plain header's text-sm font-medium and foreground ink in DataList and DataGrid, and their label now lines up with the column's values (it sat about 5px in): the start of a start column, the end of an end column, where the sort arrow leads instead of trailing. SortHeaderButton takes the column's align. A selected row is bg-muted/50, so a secondary Badge on it stays visible.
    • New DataListPager for DataList's footer slot: a controlled range summary ("1–15 of 40"), a rows-per-page Select (15 / 30 / 50 by default), and windowed Pagination that hides on a single page. The page list narrows with the pager's width (data-layout: full, compact, minimal); a page count that outgrows its layout (three digits at exactly 240px, four or five digits wider) steps down one more (re-checked on any width change, however small, and when the page list's own content changes size, as when a web font swaps in), number slots grow to hold their number, and at the last step the position reads "N / M" (data-short) and may truncate while screen readers still hear "Page N of M" — so the page list never scrolls sideways. The range and the rows-per-page chooser each stay on one line, which puts the pager's floor at about 200px. A NaN, undefined or negative total reads as 0, and a pageSize of 0 or less shows the first pageSizes entry instead of adding a bogus option.
    • Chip's height and EditableCell's display height are now floors (min-h-7 / min-h-8), not fixed boxes: identical on one line, and a wrapped label grows the box instead of spilling it. stepperNodeVariants output carries a group/stepper-node hook. 72741b7

🔧 Changed components

  • Cards and floating surfaces draw a real border instead of a ring outline, destructive menu rows clear AA, three components mirror under RTL, and FilterBar and DatePicker fit their space.

    • Border, not ring (BRD-1). card, dialog, alert-dialog, popover, hover-card, select, combobox, dropdown-menu, context-menu, menubar (content and sub-content) and navigation-menu swap upstream's ring-1 ring-foreground/10 box-shadow for a 1px border border-border, and the floating sidebar swaps its ring-sidebar-border outline for border border-sidebar-border, so SettingsCard and Board columns follow. The ring read as a stray outline and disappeared in forced-colours mode. A border takes 1px of layout on each side: under Tailwind's border-box sizing a surface with a fixed width or height keeps that size and its content area shrinks by 2px, while a content-sized surface grows by 2px. Board's drop-over highlight now recolours that border (data-drop-over:border-primary/50). See Elevation.
    • Destructive menu rows (A11Y-13). A focused variant="destructive" item in dropdown-menu, context-menu and menubar reads text-destructive-text on its /10 wash (it read 3.99:1 in light). QuestionnaireError takes the same -text ink as FieldError.
    • Logical direction. PageHeader's actions and FilterBar's search and trailing slot push with ms-auto, and DatePicker's preset rail divides with border-e, so all three mirror under a DirectionProvider.
    • FilterBar never overflows. SearchInput's clear button sat in an addon whose box ended about 4px outside the input (upstream's inline-end -0.3rem margin), so a search filling a narrow FilterBar pushed the bar 4px past its container at 320px. The addon now stays inside; the clear button keeps its position.
    • DatePicker's popup is one surface. The calendar inside DatePicker / DateRangePicker is transparent, so it no longer paints bg-background over the popup's bg-popover (a visible second tone in dark). With presets, the rail now sits beside the calendar from sm up, as documented — it had stacked above it at every width — and its one divider sits between rail and calendar instead of doubling the popup's edge. 72741b7

🛠 CLI & tooling

  • vegastack-design doctor now fails on retired design-system vocabulary in your own source, with file:line and the replacement for each.

    A utility the shadcn reset retired compiles to nothing — no build error, no type error — so a text-h2 heading silently renders as body text. The new check scans the project's code and stylesheets (skipping node_modules, .next, dist and the components.json ui directory) for text-h1…text-h4, text-label, text-label-sm, text-mono-label, text-code, text-strong, text-display-*, the -subtle steps of the four status families (destructive, success, warning, info), every --alpha-* name the system ever shipped (the reset's nineteen plus five retired before it, such as --alpha-fill-hover), the retired --opacity-* and --z-raised/--z-overlay/--z-toast names, shadow-overlay, backdrop-blur-glass, and imports of the retired icon-button, segmented, password-input, progress-indicator, field-inline, floating-surface, section-header and sonner components. Only real usage is reported: an import counts only when it resolves into your components.json ui directory (or @/components/ui/…), so your own @/components/layout/section-header is left alone; // and /* */ comments and prose strings are skipped, while JSX text — an apostrophe, a URL, a glob — never hides the real usage after it on the same line, and neither does Tailwind's ! modifier (hidden!); a class you define yourself (@utility text-h2 { … } or a .text-strong rule) or a custom property you declare (--z-header: 30) is yours; another library's --z-index or --alpha-channel is not ours to call retired; and the kept --tag-*-subtle tints are not reported. Every replacement is in Migrating to the shadcn reset. 72741b7

📦 npm

  • @vegastack/design → 0.7.1 (was 0.7.0).
  • @vegastack/design-tokens → 0.7.1 (was 0.7.0).
  • The design-system registry (@vegastack/ui) bumps 0.12.2 → 0.13.0.

📚 Docs

  • The shipped vegastack-design-system and vegastack-design-audit agent skills now name the retired vocabulary and the rules lint enforces.

    The design-system skill's Don't list adds no arbitrary text sizes (text-[13px]), no ring as a surface edge, and a table of every retired utility, token and component with what to write instead, pointing at vegastack-design doctor to find them. The audit skill runs doctor in its first pass and states one rule for copied-in components: don't edit one; if you must, it becomes yours and check-updates reports it as drifted. 72741b7

  • The reduced-motion comment in base.css now states the !important rule precisely: the reset is the only hand-written !important in the token CSS, and Tailwind's ! modifier in component class strings is a separate, counted allowance in design-lint.

    No token value, selector or rule changed — this is a documentation correction inside the shipped stylesheet. 72741b7

🐛 Fixed

  • AppShellSkeleton no longer causes a hydration mismatch. Its nav rows used upstream's SidebarMenuSkeleton, which picks a random width per mount, so the server HTML and the client's hydration never agreed in a loading.tsx. The rows now take their widths from a fixed cycle — same shape, same 50–90% band, identical on server and client. 72741b7

[0.12.2] — September 23, 2026

🛠 CLI & tooling

  • The Version PR head is read from REST and the exact-SHA comparison retries.

    gh pr view --json headRefOid served a stale head for minutes on release run 35763347504 — the branch ref was already at 49486dab0 while the PR API still reported 6a359a602. The comparison against the branch ref had no retry of its own (only the PR lookup above it did), so a Version PR that had been generated correctly was left behind a red run.

    The head now comes from repos/{owner}/{repo}/pulls/{n} (REST answered correctly and immediately throughout that incident) and the comparison retries on the same bounded schedule. The check itself is unchanged: two genuinely different shas still fail, because that comparison is the exact-SHA protection boundary.

    The negative harness gained the mutation that boundary never had — replacing the branch-ref lookup with the PR's own head, making the comparison vacuous. 36/36 rejected.

    Both reads now tolerate a failing call, because under set -e a transient gh api error aborted the step at the command substitution before the retry could do anything, with no message at all. An empty read never compares equal, so it falls through to the same bounded retry. 9ad0966

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.12.1 → 0.12.2.

[0.12.1] — September 23, 2026

🛠 CLI & tooling

  • A release whose changesets all bump nothing no longer fails the coordinator.

    release-detect.mjs decided the Version PR path by counting files in .changeset/. The empty-frontmatter changeset is the documented form for a change that bumps no published package, and changeset version consumes it without writing a version — so Changesets opens no PR, while the workflow had been told to expect one. It retried, hard-errored, and skipped the deploy; and because the changeset stays pending, every later push to main failed identically.

    The output is now has_version_bump and means what the workflow actually asks: at least one pending changeset declares a bump for a package Changesets will version. Frontmatter is read at the same ref as the file list, and packages in the Changesets ignore list are excluded, since naming one bumps nothing and would dead-end the same way.

    This removes the failure. It does not change deploy policy: a docs-only push still does not deploy on its own — it never has — and now simply succeeds and waits for the next release. dd9b308

  • Browser assertions test whether something is painted, not how Chromium spells "nothing".

    Chromium serialises a fully transparent colour as rgba(0, 0, 0, 0) or as oklab(0 0 0 / 0) depending on the colour space the value came through, and this token system's colours routinely come through OKLCH. Fourteen sites compared against the first spelling as a literal string.

    Two of them asserted equality and broke loudly when the spelling changed — one of them flipped red and then green again across three CI runs on identical source. The other ten asserted not.toBe("rgba(0, 0, 0, 0)") to mean "something is painted here", which passes on an element that lost its fill entirely whenever Chromium spells the nothing differently. A Badge, Button, Switch track, Toast action or Bubble surface could have gone completely unpainted with its test green.

    packages/ui/test/color.ts now answers the question by meaning rather than by spelling, and its own contract is pinned in color.browser.test.tsx — including the original defect as an executable claim, and a real element read in the engine without naming which spelling it produced. No call site changed meaning: every file was green before and after, so this removes the possibility rather than uncovering an instance. 229add9

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.12.0 → 0.12.1.

📚 Docs

  • The registry-auth banner no longer paints over the site header on a phone.

    Fumadocs' Banner is a fixed-height sticky box — it writes the same height into its own inline style and into --fd-banner-height, which every sticky offset below it is measured from. The notice was long enough to need six lines at 320px inside that 48px box, so 72px of it overflowed and, at z-40 over the header's z-30, rendered on top of the VegaStack logo row. The layout boxes never overlapped and there was no horizontal scroll, which is why nothing caught it: the defect was content overflowing its own container, not a broken grid.

    The trailing enumeration — the Base UI shadcn project, the @vegastack namespace, the Cloudflare Access service token — is now shown from lg up, which is measured rather than guessed: the full sentence occupies six lines at 320px, three at 480px, two at 768px and one from 1024px. Below that the lead sentence and its link to the registry setup are unconditional, so the notice itself is intact at every width and the detail is deferred to the page that performs it, one tap away. The banner stays one line and 48px everywhere, and the wide layout is unchanged. 823b3bc

[0.12.0] — September 22, 2026

🧩 New components

  • MultiStepForm — a guarded, branching flow around Stepper, with no opinion about your fields.

    • It owns the flow and refuses to own your form. steps declares labels, branches and guards; each MultiStepFormStep declares one body, and only the current one renders. A body is whatever you put in it — a Field form, a review table, an upload surface, nothing. That boundary is what keeps it at zero new dependencies: a guard is an ordinary function returning a promise, so React Hook Form, Zod or a hand-written check all plug in from the app side, and none of them becomes a dependency of the design system.
    • Guards, forwards and backwards. beforeNext and beforeBack return true to pass or a sentence to refuse, and may be async — while one runs the step is loading in the rail and the action is a loading Button. The flow never advances optimistically. canGoNext is the cheap synchronous gate for the case with no reason worth showing.
    • A refusal sits beside the control it blocks, and its weight follows its cause. A check that ran and failed renders an assertive Alert and marks the step; a gate not yet satisfied renders a quiet polite line and leaves the rail alone. Either is tied to the forward button through aria-describedby, so it reads out with the control and persists — which a toast cannot do (WCAG 3.3.1). The one failure a toast belongs to is the check that could not run at all, and that arrives as onTransportError.
    • Conditional steps. A step whose when is false leaves the rail, the count and the sequence, so "step 3 of 4" stays true when a branch drops one — and a branch closing under the current step rewinds rather than stranding it.
    • One predicate decides four behaviours. Mark a step satisfied when its data already exists, and reachability follows: which steps a #step=… deep link may open, whether the rail starts complete, whether jumping is offered, and which layout a phone gets. There is no mode="create" | "edit" to keep in sync.
    • lock seals what has been committed. Once a locking step is passed, Back is disabled for good and neither a jump nor a forged hash can reopen what came before it.
    • Optional steps, resume and deep links. optional adds the affix and a Skip that records skipped rather than complete. urlSync writes a namespaced #step=<id> so the browser's Back moves a step, and no router adapter is needed. persistKey restores the position and the steps passed for the life of the tab — opt-in, sessionStorage, and never for payment data.
    • layout="panel" is the shape for a dialog. Inside a Dialog, Sheet or Drawer the nav and the action row hold their place while the step body becomes the one scrolling region, so the frame cannot grow past the viewport and take its own footer with it.
    • MultiStepFormExit guards unsaved work. dirty is yours to define; while it is true, leaving raises an AlertDialog first and the browser warns on a refresh or a closed tab — the half no component can fake. With nothing dirty it simply calls onExit, because a confirmation nobody needs is the fastest way to teach people to dismiss confirmations unread.
    • Phones get the layout their flow can honour. Reachable steps become a tappable section list that drills into a step; a strictly linear flow keeps the rail, which collapses itself to a line and a bar, because rows would promise navigation the flow does not offer.
    • The multi-step form guide is rewritten from a recipe you assemble into a map of which component to reach for, and Stepper's page now leads with the case that needs no buttons at all. 76fa8d3

🔧 Changed components

  • Typography: a global Geist-spec ramp replaces per-component type decisions

    The system had no typography contract at all — the shadcn reset resolved TYP-1…TYP-9 and TYP-11 as shadcn, which left every size, weight, line-height and letter-spacing to Tailwind's stock values plus 229 local decisions across 112 component files. Nothing was globally declared, and nothing carried letter-spacing at any size.

    Four new decisions (MK, 2026-09-22), all declared once and inherited everywhere:

    • TYP-15 — heading-tier optical metrics. At text-lg and above, line-height and letter-spacing follow Geist's heading spec, declared as per-size --text-*--line-height and --text-*--letter-spacing in the @theme inline bridge. Tracking runs −0.012em at 18px to −0.06em at 72px. Geist's copy tier carries zero letter-spacing, and this system's body sizes are its copy tier, so text-xs/text-sm/text-base are untouched and render byte-identically. SIZES do not move, so TYP-1 stays shadcn and a pasted shadcn snippet still renders at upstream's size.
    • TYP-16 — Geist rendering. -webkit-font-smoothing: antialiased on body. Geist is drawn for it; without it the same weight renders heavier and softer than the identical weight elsewhere.
    • TYP-17 — a declared 14px default body size, on body and never on html. rem resolves against the root, so an html size would rescale every token and override the reader's browser font-size preference. Previously unclassed text fell back to 16px while components were 14px. The docs shell keeps its 16px reading size.
    • TYP-18 — no arbitrary font size. Upstream's text-[0.8rem] (button sm, toggle sm, calendar) and text-[0.625rem] (questionnaire) now sit on the ramp. Upstream's ladder does scale type with control size and is KEPT — the sm half-step resolves down to text-xs rather than flattening up to 14px.

    Also enforced: TYP-10 ("tabular figures on code and data") had been ours since the reset with no gate at all, and number-field shipped proportional digits whose value jittered on every stepper press. It now carries tabular-nums, and three new design-lint rules — raw-tracking, arbitrary-text-size and tabular-figures — hold all of the above, each with negative-specimen coverage in verify-design-lint-structural.

    Block heading weight is normalised to font-semibold; chart figure labels keep font-bold.

    Markdown surfaces. prose.root never declared a font family, and neither of its two consumers (MarkdownView, TextEdit) sets one — so prose inherited whatever surrounded it, and inside any mono container the whole tree rendered in Geist Mono: headings, paragraphs, table cells, and the 1. / 2. markers of an ordered list, since ::marker inherits font properties from its element. The recipe now declares font-sans, making mono the exception it names explicitly (code, pre, pre code) rather than something prose falls into by accident.

    No uppercase, anywhere. design.md § Voice & content has always said sentence case for everything and TYP-7 resolves as shadcn ("No uppercase"), but twelve font-mono text-xs uppercase tracking-wide eyebrows had survived across the docs shell, plus the terminal and code-block header labels and the OG card. Two were a correctness bug rather than a style one: the home page rendered real CSS custom-property names through the transform, so --text-lg displayed as --TEXT-LG. All of it is removed and gated by a new uppercase-transform rule, which bans the CSS transform rather than uppercase text — if a string is uppercase, write it that way. That also removed the only justification for positive tracking-*, which existed to make uppercase legible, so raw-tracking now allows tracking-widest alone (the menu shortcut hint). code-block now shows tsx as given instead of TSX, and terminal shows Terminal.

    The principle applied throughout: mono is for code, uppercase is for nothing — content that is code keeps font-mono, content that is language is font-sans in sentence case.

    The docs site. Fumadocs' .prose writes font-size directly rather than through a utility, so its headings tracked the ramp's sizes but never its letter-spacing, h1 rendered at weight 800 (h1 strong at 900) and h3 at 1.6 leading. All four now reference the ramp variables at weight 600. Two arbitrary sizes baked into Fumadocs' own class strings — 15px on the sidebar, tabs and accordion, 13px on code blocks — are pulled onto --text-sm, putting every piece of docs chrome at the same 14px as the product layer. Prose body stays 16px; it is a reading surface. 76fa8d3

🛠 CLI & tooling

  • The package-exports gate resolves its sibling from the tarball under test, not from npm.

    verify-package-exports packs both public packages and installs them into a throwaway consumer under npm and again under pnpm. @vegastack/design depends on @vegastack/design-tokens by semver range, and version-sync rewrites that range on the Version Packages PR — so on a release that moves design-tokens, the range names a version npm does not have yet, because that release is what publishes it. npm hoisted the local tarball and passed; pnpm resolved independently, went to the registry and failed the whole gate.

    The consumer now overrides that dependency to the tarball just built — the top-level overrides key for npm, pnpm-workspace.yaml for pnpm, which in pnpm 11 no longer reads pnpm.overrides from package.json. Because an override could equally hide a range pointing at the wrong sibling, it is paired with an assertion that the declared range equals ^<the design-tokens version being shipped beside it>, which is the form version-sync writes. The gate keeps failing on a genuinely wrong range; it stops failing on a version that does not exist yet. dbc1190

  • The Version Packages PR refreshes pnpm-lock.yaml after rewriting internal dependency ranges.

    version-sync rewrites the semver range @vegastack/design declares on @vegastack/design-tokens whenever a bump moves that package out of the pinned range, but nothing regenerated the lockfile afterwards — and pnpm-lock.yaml records the range as a specifier. The generated branch therefore carried a manifest and a lockfile that disagreed, and its own PR quality run died at pnpm install --frozen-lockfile before reaching a single gate.

    version-packages now runs pnpm install --lockfile-only --ignore-scripts between version-sync and sync-changelog. linkWorkspacePackages is on, so the pair resolves locally: the refresh needs no network and does not need the new design-tokens to exist on npm, which it does not at the moment this runs.

    The failure was latent from the moment the two packages' versions diverged and only fires when a bump leaves the ^ range — which is why every previous release passed. No new gate: the check that should have caught this is the Version PR's own required run, and it did. a71cd3a

  • The release scope guard knows about the lockfile, and is specific about it.

    verify-release-output-scope is an allowlist, and pnpm-lock.yaml was not on it — because until this release version-packages never produced a lockfile change. It does now, so the guard rejected a legitimate release output.

    The lockfile gets its own class with its own predicate rather than being waved through beside the changelogs: it is the one release output where a new third-party dependency could hide, which is the threat the allowlist exists for. The specifier:/version: lines under a @vegastack/* key are neutralised — the same move already made for the package manifests — and everything else must be byte-identical. Verified against this release's real lockfile diff (exactly one line) and against a synthetic third-party addition and re-resolve, both rejected. 2ef219d

📦 npm

  • @vegastack/design → 0.7.0 (was 0.6.1).
  • @vegastack/design-tokens → 0.7.0 (was 0.5.0).
  • The design-system registry (@vegastack/ui) bumps 0.11.3 → 0.12.0.

⚠️ Breaking

  • Stepper is rebuilt: the rail now carries the progress, and the refusal message moves out of it.

    • Removed: blockedReason and blockedReasonId. They rendered under the current step's label — a one-column-wide ribbon in a horizontal rail, and nothing at all once the rail collapses — so the single placement they had was the one layout they did not fit. A reason the flow cannot advance now belongs beside the control it blocks: an Alert for a check that failed, a quiet line for a gate not yet satisfied, wired to your own Next button's aria-describedby.
    • Removed: the root is a <div>, not the <ol>. The list is now nested inside it as data-slot="stepper-list", so a ref or a selector aimed at the old root resolves to the wrapper. Every other data-slot survives.
    • Changed: the navigable step is a plain row, not a link-styled Button. data-slot="stepper-trigger" is the target and data-slot="stepper-label" stays on the label itself; anything selecting the old inner Button classes will not match.
    • New states. loading is what an async advance gate occupies while it runs — the state the previous four could not express without a checking step pretending to be idle. warning is a step that is passable but carries something to know, and skipped is one passed over rather than failed. optional on a step renders an affix beside its label.
    • New: numbered nodes and a rail that fills in. Connectors behind the flow take bg-primary and those ahead bg-border — read from each step's own state, never from its index — so the component is its own progress bar. A completed step's ordinal gives way to a check; colour appears only for warning and error, whose labels take the family's -text ink per A11Y-13 while the node fill carries its -foreground.
    • New: orientation="auto" (the default) flips to vertical at verticalFrom steps (6), because a rail long enough to crush its own labels reads better down the page. collapse="auto" (the default) replaces the rail with the current step's name, its position and a Progress bar below a width derived from the step count — a container query, so a rail inside a narrow dialog collapses on a wide screen too. Focus follows the process into whichever of the two is laid out.
    • New: size (default, sm), labelPosition (below, inline) and showCount. inline sets each label beside its node with the connector running on from it, from the same DOM and reading order as the default. 76fa8d3
  • Toast is the one notification engine — Sonner is retired, and Toast gains position, anchored toasts, custom bodies and a band of its own above the modal scrim (OVL-10, OVL-15, COL-23).

    • Removed: the sonner registry item, its docs page and the sonner dependency. Upstream ships both toast (Base UI) and sonner, and the shadcn reset shipped both; two engines for one job meant two live regions and two fixed viewports competing for the same corner. Toast was already the default — provider has always mounted it — so the migration is the import and the call shape:

      - import { Toaster } from "@/components/ui/sonner";
      - import { toast } from "sonner";
      + import { Toaster, toast } from "@/components/ui/toast";
      
      - toast.success("Event created", { description: "Sunday at 9:00 AM" });
      + toast.add({ type: "success", title: "Event created", description: "Sunday at 9:00 AM" });

      toast() / toast.success() / toast.error() / toast.info() / toast.warning() become toast.add({ type, title, description }); duration becomes timeout (and 0, not Infinity, disables auto-dismiss); action: { label, onClick } becomes actionProps: { children, onClick }; toast.dismiss(id) becomes toast.close(id) — with one real difference: close() with no id closes the FRONTMOST toast, where sonner's dismiss() closed every one. toast.promise keeps its { loading, success, error } shape. A copy you already installed keeps working; it just stops receiving updates. Upstream's dashboard-01 block now fires our manager.

    • New: position on Toaster — six logical corners (top/bottom × start/center/end, default bottom-end). Inline names are logical, so bottom-end is bottom-right in an LTR document and bottom-left in an RTL one. The stack's growth direction and its swipe direction follow the corner, so a toast always enters and leaves through the edge it is pinned to.

    • New: anchored toasts. ToastPositioner and ToastArrow wrap the two Base UI parts upstream wraps neither of, so a confirmation can position against the control that caused it instead of stacking in the corner.

    • New: custom bodies. A toast carrying data.render draws its own content and keeps the surface, the stack, swipe-to-dismiss, Escape and the viewport's live region — an avatar or a thumbnail no longer means opting out of the toast contract.

    • Fixed: a toast fired while a modal is open is no longer hidden behind the scrim. The viewport moves to a z-60 band, one above the single z-50 overlay band every other surface shares. It has to be a band rather than DOM order, because the viewport mounts with the provider before any dialog exists. Nothing else leaves z-50.

    • Fixed: a toast with only a description read in the secondary ink. Base UI renders Toast.Title as null when there is no title, so toast.add({ description }) — the shape upstream's own examples use — painted the toast's only line, its primary message, in muted grey. The description now takes the default ink exactly when it leads, and stays muted under a title. 76fa8d3

[0.11.3] — September 22, 2026

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.11.2 → 0.11.3.

📚 Docs

  • The reference-consumer starter is no longer part of the project workflow. The ship skill's post-release step, the Quickstart, Troubleshooting and Production checklist guides, the repo map and the internal operations guide no longer reference it, and the guides state what is actually true of themselves rather than deriving their authority from a repo nobody can run. Release verification is now the npm versions plus the production-boundary probe. Historical records in docs/ledger/ and docs/plans/ are untouched: they record what was decided at the time. 80e127a

[0.11.2] — September 22, 2026

🔧 Changed components

  • The indeterminate loading mark is now lucide Loader everywhere, in place of LoaderCircle.

    • Spinner, Toast's loading icon and Sonner's loading icon move from Loader2Icon — an alias of lucide's LoaderCircle — to LoaderIcon, so they match StatusIcon's progress and upstream's own dashboard-01 block. One loader shape across the system.
    • Every component that composes Spinner inherits the new mark, including Button and Toggle in their loading state.
    • Recorded as decision ICO-8 and enforced by design-lint's new loader-mark rule, so the upstream mark cannot return one file at a time. 38ad929

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.11.1 → 0.11.2.

🐛 Fixed

  • A loading Toggle no longer paints its label underneath the spinner.

    • The label wrapper was display: contents, which generates no box and therefore accepts no opacity, so opacity-0 had nothing to apply to and the label stayed fully visible. It is now a real inline-flex box inheriting the control's own gap, matching Button.
    • This is the same defect that was fixed on Button; Toggle was the only other component carrying the pattern. 38ad929

[0.11.1] — September 22, 2026

📦 npm

  • @vegastack/design → 0.6.1 (was 0.6.0).
  • The design-system registry (@vegastack/ui) bumps 0.11.0 → 0.11.1.

🐛 Fixed

  • Publish concrete dependency ranges so fresh npm and pnpm consumers can install the package outside the VegaStack workspace. 997dfb8

[0.11.0] — September 22, 2026

🧩 New components

  • SearchInput now provides a token-safe clear action with controlled, uncontrolled, keyboard, and native form behavior across supported browsers.

    • Use the new reusable SearchInput for consistent search semantics and styling.
    • FilterBar now composes SearchInput while preserving its existing controlled search API. 6ed3e37

🔧 Changed components

  • Avatar fallback initials now use the smaller text-xs size across all three avatar diameters.

    • Preserve the existing sm, default, and lg diameters and public API.
    • Keep explicit consumer typography overrides and standalone group-count typography unchanged. cfe049d

📦 npm

  • @vegastack/design → 0.6.0 (was 0.5.0).
  • The design-system registry (@vegastack/ui) bumps 0.10.0 → 0.11.0.

📚 Docs

  • Teach consumers to use SearchInput for token-safe, cross-browser search clearing and FilterBar-compatible search behavior. 14c88ce

🐛 Fixed

  • Loading Buttons now hide their label through a real layout box, preventing the centered spinner from overlapping visible text while preserving the button’s size and accessible name. e7097d9
  • Command dialogs now stay centered inside short viewports while their results scroll and composed footers remain visible. 1689201

[0.10.0] — September 19, 2026

🛠 CLI & tooling

  • upstream:parity now compares the canonical file and the patched upstream file with the generated provenance header excluded, so a version bump no longer breaks byte parity for every patched component. a918731
  • version-sync now walks the whole component-contracts document when it rewrites public dependency ranges instead of iterating three named categories, so a category added later — chart blocks, libs, the animated-icon shared contract — cannot be left at the previous range and fail verify-component-contracts on the Version Packages PR. c3053c3

📦 npm

  • @vegastack/design → 0.5.0 (was 0.4.1).
  • @vegastack/design-tokens → 0.5.0 (was 0.4.0).
  • The design-system registry (@vegastack/ui) bumps 0.9.1 → 0.10.0.

⚠️ Breaking

  • The shadcn base-nova reset — the runtime and token bridge are rebuilt on shadcn base-nova, with no compatibility layer.

    The optional lucide-react peer range moves to ^1.47.0, matching the version the system is built and tested against; a consumer on an older lucide should upgrade alongside this release.

    cn is plain twMerge again, because the custom font-size class group it extended no longer exists, and seven shared class-string recipes are deleted with no alias: surfaceInteractive, surfaceInteractiveGroup, fillInteractive, FillTone, fieldControl, fieldControlGroup and selectedChipVariants. Each described a system — a three-rung surface ladder, a per-tone fill map, a shared text-entry chrome, a raised-chip recipe — that the reset deleted; replace each with the literal it expanded to, or compose the upstream component it was re-deriving. cn, TIMINGS, FLOATING, mergeRefs, prose/proseClassName, the icon runtime, the Tailwind preset and the vegastack-design CLI are unchanged. The shipped agent skills are rewritten for the new vocabulary.

    Who this affects: every consumer. Nothing is deprecated first — an import of a removed export fails to resolve. The complete break, with a replacement for each removed export, is the migration guide § 6. 72ae827

  • The shadcn base-nova reset — the token contract is shadcn base-nova's neutral base plus our recorded additions, and every deleted family is gone with no alias.

    94 resolved tokens per theme, down from 185. Deleted: the surface ladder (--surface-1/2/3), the 19-entry --alpha-* ladder, the --opacity-* ladder, --size-*, --icon-*, --panel-width-*, --layout-*, --z-*, --shadow-overlay, --radius-xs, --radius-sharp, --overlay, --muted-foreground-faint, --font-family-pixel, the whole role and display type scale (text-h1…text-h4, text-label*, text-code*, text-mono-label, text-display-*), and every -subtle/-hover/-active step on the four STATUS families (the ten --tag-* trios keep theirs). Each is now a plain Tailwind utility. Kept and ours: the four status families in upstream's own destructive shape — each with a -foreground ink for the solid fill and a -text ink for the page and the family's own tint — the 8-hue chart palette plus --chart-single, the 10-hue tag palette, the brand pair, the media trio, the Geist families, and the --duration-*/--motion-ease-* pairs behind the motion-* utilities.

    Who this affects: every consumer, including ones that never touched a component. A deleted token compiles to nothing rather than failing, so a page keeps rendering and quietly looks wrong. The searches that find every holdover, and the write-instead table for each family, are the migration guide §§ 3–5 and § 11. 72ae827

  • The shadcn base-nova reset — every component shadcn ships is now upstream's own file plus a recorded patch, and there is no compatibility layer.

    62 upstream components were reset onto pinned shadcn@4.21.0 base-nova, so their props, variants and behaviour are upstream's; 13 are new here (aspect-ratio, button-group, calendar, carousel, direction, drawer, input-group, input-otp, menubar, native-select, questionnaire, sonner, panel-search); 10 are retired onto an upstream replacement (icon-button, otp-input, password-input, checkbox-group, field-inline, segmented, split-button, progress-indicator, onboarding-checklist, floating-surface); and the 10 marketing components are removed outright with their scope mechanism and tokens. Button's variant × tone axis becomes upstream's flat variant, IconButton becomes Button size="icon*", Field composes its label, description and error as children, the toast manager is toast.add/toast.close/toast.promise, Sheet moves off Base UI Drawer onto Dialog, and a long list of size axes is gone. Visible without touching any code: one 2px focus outline instead of the ring glow, a hand cursor on every control, shadcn's own neutral, no surface ladder, and Tailwind's stock radius, shadow and type scales. The registry ships 689 items, including 100 blocks.

    What is ours is 61 recorded exceptions, every one traceable: a component's docs page closes with a ## Deviations section naming the decision IDs behind its patch. One of them is new to this release: TooltipContent and DropdownMenuContent (and so DropdownMenuSubContent) take an optional container, forwarded to their portal, so chrome drawn over a fullscreen surface renders inside it instead of behind it — which is what makes a fullscreen player show its control labels and its settings menu again. Leaving container unset is upstream's default.

    Who this affects: every consumer. Re-pull every copied-in component; a retired import fails to resolve, and each retirement's prop map — including what did not survive it — is in the migration guide § 7. 72ae827

[0.9.1] — September 15, 2026

🧩 New components

  • Selection controls — Give ToggleGroup, Segmented, and pill Tabs a persistent semantic boundary, and use Segmented for the documentation preview's device modes. 6ec9d54

🛠 CLI & tooling

  • Verification and release — Run deterministic affected component tests once on pull requests and automate the authorized publish-and-deploy chain. 6ec9d54
  • Version PR checks — Use GitHub's native approval gate for the bot-created pull-request run so its PR quality result satisfies protected-main status checks without a duplicate dispatch. 22cc795
  • Version PR verification — Route generated version metadata through static and positive output checks without misclassifying provenance-only registry rewrites as component edits. ed2aa8f
  • Release automation — Restore a generated Version Packages PR so protected main needs no automated direct-push bypass. 3dd6a85

📦 npm

  • @vegastack/design → 0.4.1 (was 0.4.0).
  • The design-system registry (@vegastack/ui) bumps 0.9.0 → 0.9.1.

[0.9.0] — September 15, 2026

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.8.2 → 0.9.0.

🐛 Fixed

  • Toast — center the copy and controls vertically in single- and two-line notifications, give the labelled action the same quiet ghost treatment as dismissal, and complete the docs Scope and part API sections. docs · 31e915d

[0.8.2] — September 12, 2026

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.8.1 → 0.8.2.

🐛 Fixed

  • Dropzone docs — keep the three-state drag preview within 320px by giving its narrow layout an explicit zero-minimum grid track before the three-column breakpoint. docs · c371d09

[0.8.1] — September 12, 2026

🛠 CLI & tooling

  • Geometry diagnostics — name the elements and computed paint geometry that own a horizontal overflow, so a cross-engine failure identifies its cause instead of reporting only the page width. 9390ce4

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.8.0 → 0.8.1.

[0.8.0] — September 12, 2026

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.7.5 → 0.8.0.

🐛 Fixed

  • Keep Dropzone drag feedback inside narrow WebKit viewports. The drag-state stroke now sits one stroke-width inside the surface and uses explicit border-box sizing. This avoids WebKit counting the stroke's two edges as horizontal scroll overflow at 320px while preserving the design system's independent focus-visible outline. docs · f4fec28

[0.7.5] — September 11, 2026

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.7.4 → 0.7.5.

🐛 Fixed

  • Dropzone — paint drag feedback with an inset overlay that cannot expand WebKit scrollable overflow. e5ba405

[0.7.4] — September 11, 2026

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.7.3 → 0.7.4.

🐛 Fixed

  • VideoPlayer WebKit contract — separate pointer-leave hiding from the intentional focus-retention behavior in the browser test. e45defd

[0.7.3] — September 11, 2026

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.7.2 → 0.7.3.

🐛 Fixed

  • WebKit release contracts — keep Dropzone drag outlines inside the viewport and make AudioPlayer media-time tests use an engine-independent writable clock. 7c5a9e8

[0.7.2] — September 11, 2026

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.7.1 → 0.7.2.

🐛 Fixed

  • Docs-shell verification — assert native modal isolation directly instead of requiring a platform-dependent focus-guard escape after the injected defect. 9a1c6ec

[0.7.1] — September 11, 2026

📦 npm

  • The design-system registry (@vegastack/ui) bumps 0.7.0 → 0.7.1.

🐛 Fixed

  • Docs-shell verification — make the modal native-inert negative proof remove every outside descendant and keep the injected defect active for the full focus walk. ae881cc

[0.7.0] — September 11, 2026

🧩 New components

  • --duration-indeterminate (1200ms) and the motion-indeterminate utility — the one sanctioned looping animation, the sweeping segment of an indeterminate Progress. Its keyframes open and close on the same resting frame, so the global prefers-reduced-motion reset leaves a static 35% segment rather than a bar that reads as complete. docs · 7915a71

  • surfaceInteractive and fillInteractive — the two hover/pressed recipes, exported from @vegastack/design so no component writes a hover:bg-* literal again. surfaceInteractive (hover:bg-surface-2 active:bg-surface-3) is for a control on a known ladder surface; fillInteractive.<tone> (hover:bg-<tone>/(--alpha-hover) active:bg-<tone>/(--alpha-pressed)) is for one on an unknown backdrop or hovering in its own hue. The FillTone type ships with them. docs · b2c2e96

  • A layout scale, and --chart-single. --layout-header-height, --sidebar-width-mobile, --layout-overlay-max-height and --panel-width-sm|md|lg give the shell and the overlay family named dimensions instead of per-component literals, and --chart-single names the one-series chart colour. docs · b2c2e96

  • CheckboxGroup — shared state for a set of checkboxes, with first-class "select all". Base UI ships the parent/child arithmetic (allValues plus a parent child gives the mixed state and the whole-set toggle) and the system had no wrapper for it, so DataGrid, DataList and every permissions block computed it by hand. There is no CheckboxGroupItem — a child is a plain Checkbox with a value.

    useInlineEdit — the click-to-edit machine: draft, commit, cancel, focus restoration and the double-commit guard (Enter closes the edit, which unmounts the input, which fires blur, which would commit a second time). FieldInline and EditableCell had each written it, and the copies had already drifted — only one re-armed the guard when a controlled host flipped editing on, and only one restored focus after a keyboard commit. It owns no DOM and no persistence, so it also serves a cell editor that is a Select popup with no text input at all. docs · 9fbeb65

  • 28 new lucide-animated icons adopted — the mirror moves 439 → 467. Upstream had drifted 28 items ahead of the pinned manifest, and because tooling/mirror-animated-icons.mjs fails closed on an unexpected upstream item count, --refresh could not be run at all. The new items are binary, cigarette, cigarette-off, circle-gauge, cloud-backup, cloud-sync, external-link, hat-glasses, leaf, leafy-green, link-2, monitor-cog, palette, plane-landing, plane-takeoff, server, server-cog, server-crash, ship-wheel, shredder, soup, spray-can, ticket, tree-deciduous, tree-pine, user-plus, waves-arrow-down and waves-arrow-up — each shadcn add @vegastack/icon-<name>, each generated by the mirror rather than authored, and each inheriting the single createAnimatedIcon controller with its reduced-motion gate. None replaces an existing icon: the refresh removed nothing, the 439 pinned upstream and generated-module hashes are byte-identical, and every new item maps to its own lucide-react icon module rather than to an alias of one already mirrored. Registry items go 568 → 596. tooling/verify-animated-icons.mjs gains two sanctioned Motion easings ([0.25, 1, 0.5, 1], [0.65, 0, 0.35, 1]), two durations (0.12s, 0.32s) and a narrow staggered-duration form whose base and per-index step are each held to a sanctioned vocabulary; its --self-test grows to seventeen mutations, two of them new — an off-vocabulary stagger step, and a one-digit glyph edit inside one of the newly adopted modules. docs · 2035023

  • media-player-controls — the shared media transport, lifted out of audio-player (which owned the video player's controls too). It carries the control surface and useMediaShortcuts: ONE keyboard map for both players (Space/K play, J/L and arrows skip, M mute, F fullscreen), scoped surface vs controls so a shortcut can never steal a key from the focused control. The assignRef / getMediaDuration / clampTime helpers live here as well, instead of in a copy per player. audio-player drops from 1,431 lines to 305. docs · aa5fa0d

  • use-media-query — the system's one matchMedia subscription, on useSyncExternalStore with a caller-declared serverFallback. Five files each hand-rolled the same useState(false) + useEffect shape, and every one of them reported false on the server, so a phone rendered the DESKTOP branch of every JS-driven layout until an effect ran. Ships usePrefersReducedMotion as its named reduced-motion reader; useIsMobile and usePlatform's touch half are now one-liners over it. docs · 3663f8f

  • floating-surface — the shared floating-overlay module every anchored overlay now composes: one Portal → Positioner → Popup (→ Viewport) composer, one theme-scope hand-off across the portal boundary, one arrow, and four painted surfaces (panel at the 16px tier, menu at list density, tooltip as the inverted ink chip, navigation for the morphing mega-menu). It also owns menuItemVariants — the one list-item recipe behind menu items, select options, combobox options and command rows — and PanelSearchFrame, the in-panel search row. 7915a71

  • toast — the toast surface on Base UI's Toast primitive, replacing the sonner wrapper. It ships the whole vocabulary rather than a configured library root: ToastProvider, Toaster, Toast and every part (ToastRoot, ToastContent, ToastTitle, ToastDescription, ToastAction, ToastClose, ToastViewport, ToastPortal, ToastPositioner, ToastArrow), plus useToast and the imperative toast(). Stacking with expand-on-hover, swipe-to-dismiss, F6 into the viewport landmark and Escape on the focused toast come from the primitive; promise toasts and a custom render come with it. The surface is the floating-family recipe at 16px padding and a typed toast wears Alert's exact tint recipe, so the two status surfaces read as one design. Six types (default · success · error · warning · info · loading) — the strings follow the engine, which writes loading/success/error in promise() itself, while the tokens follow the house families. The live-region policy is enforced in code: error and warning announce urgently, everything else politely, derived from the type. docs · d5e2de2

  • geo-data and drag-item — the first two registry:lib items: plain .ts modules that install under a consumer's lib alias and are pulled in automatically as dependencies. geo-data holds the ISO 3166-1 country list, the states/provinces map and their two lookups, so a consumer installing both geography selects copies the data once (region-select.json 67 KB → 7.4 KB). drag-item holds the one visual recipe for a use-drag-reorder item — drop-edge hairlines, lift dim, pending shimmer — which Board and SortableList had each copied. docs · 45cde26

  • searchable-select — the one Select-shaped Combobox preset: a full-width trigger, the shared in-panel search row, a check on the selected row, a --anchor-width panel and an optional clear control. CountrySelect and RegionSelect are now thin data-fed wrappers over it. Two rules it exists to hold: selection runs through Base UI's value/onValueChange and nothing else (the old RegionSelect computed the value inside each row's click handler with the root deliberately unwired, so keyboard Enter and a pointer click reached it by two different paths), and the clear control is a SIBLING of the trigger, never a child, because an interactive control may not contain another. docs · 45cde26

  • data-table-parts — the chrome DataList and DataGrid had each grown separately: the sort header, the select-all / per-row selection arithmetic, the skeleton rows, the empty row and the column class rules, twice each. They now come from one registry item, installed automatically with either renderer through registryDependencies. SortableHead and SortHeaderButton compose the system Button instead of a hand-rolled <button>, and emit aria-sort on every sortable column ("none" included). The doctrinal split is unchanged — DataList stays presentational, DataGrid keeps its engines. 6d25185

  • use-overflow — the system's one overflow measurement, live across resizes AND content growth: it observes the element and its children, so a table widening inside a fixed viewport is not missed. It backs TruncatedText's clipping check, the Table scroll region's focusability, ComparisonMatrix and Terminal's command pane. docs · 6d25185

  • chip — THE labelled pill, and the only one. hue (10 decorative tag hues, or neutral) × size (sm 28px inline · md 32px control-scale) × active (the neutral chip's promotion to the surface-2 selection rung), with onRemove mounting a round ghost IconButton size="xs" whose real border box is 24×24. Tag, FilterChip, ComboboxChip and ChipInput's chips are all this one primitive composed through Base UI render. docs · 7915a71

  • use-announcer — the one polite live region. Destructure announce and Announcer from useAnnouncer() and render the Announcer element once per component. It keeps the region mounted and observed from first paint, re-keys it per call so an identical consecutive announcement is still spoken, and holds its state outside the host so announcing no longer re-renders a whole DataGrid. docs · 7915a71

🔧 Changed components

  • Animated icons — the host element is now an inline-flex <span> rather than a block-level <div>, so an icon placed in a line of text no longer breaks the line box, and AnimatedIconComponent types its host as HTMLSpanElement. Reduced motion is now a live subscription to (prefers-reduced-motion: reduce), so turning the preference on settles every mounted icon immediately instead of only affecting icons mounted afterwards. Motion's own hooks cannot do this: in 12.42.2 useReducedMotion() is useState(prefersReducedMotion.current) — a one-shot read of a module singleton captured at first import, with a standing TODO about not updating — and useReducedMotionConfig() layers <MotionConfig> on that same one-shot value. Worse, the OS preference was never consulted at all unless the application happened to mount a <MotionConfig>: useReducedMotionConfig() returns false outright when the context says reducedMotion: "never", and "never" is precisely Motion's default context value. The factory now treats the preference as the base value and lets <MotionConfig reducedMotion="always"> add reduction on top; the override is one-way, because an explicit reducedMotion="never" is byte-identical to no provider at all and honouring it would switch reduced motion off for everyone who configured nothing. Public icon names, the size prop and the startAnimation/stopAnimation handle are unchanged. docs · b2c2e96

  • A list row is 14/500 over 12, disclosures hover with the row wash, and the checklist composes the progress primitive. ItemTitle moves from 12px to text-label — the same type Sidebar menu rows, DataList cells, menu items and Message rows already use, because a 12px Item title beside a 14px sidebar row read as two systems; size="sm" keeps the denser 12/12 pair. Accordion and Collapsible triggers dropped hover:underline — underlining on hover is the link affordance — and took surfaceInteractive together with the padding, inner radius and ≥4px hairline inset that the wash requires, with the accordion panel taking the same horizontal padding so the body stays aligned under its label and row heights unchanged. OnboardingChecklist's segmented bar was a second hand-rolled role="progressbar" next to the primitive that already draws one, so it now composes ProgressIndicator segments; the primitive gains segmentsFill (segments share the container width instead of a fixed bar width) and accepts a single segment. A navigable Stepper label is now a link-variant Button instead of a ghost Button with its height and padding stripped to imitate inline text. docs · 8c18d2b

  • Base UI 1.6.0 → 1.8.0, @shadcn/react 0.2.1 → 0.3.1. Fifteen upgrade deltas were executed and observed in a browser rather than assumed from a green suite; only MessageScroller needed a source change. Its viewport now answers the primitive's new data-pending-scroll attribute with invisible, so a server-rendered transcript no longer paints the top of the thread for one frame before jumping to the bottom — visibility: hidden rather than display: none on purpose, because the primitive measures clientHeight/scrollHeight to decide where to scroll and a display-none viewport measures zero. A regression test asserts the attribute never sticks. message-scroller's declared @shadcn/react range moves to ^0.3.1; it and date-picker carry new integrity hashes. Two user-visible upstream changes are kept as shipped: a readOnly Select/Combobox now opens and browses (reached through editable-cell), and start/end-aligned popups take their pop-in --transform-origin from the aligned edge. docs · 5b03e3b

  • One key chip, and the display leaves move onto role tokens. TooltipKbd renders Kbd size="xs" instead of restyling a second <kbd>, so a shortcut hint reads identically wherever it appears — and inherits the OS rewrite. Kbd's three sizes now use one type role (text-code-sm); md reached the same 12px through text-sm, the same pixel size named twice, and the meaningless pointer-events-none on a <kbd> is gone. StatusIcon sizes become the --icon-inline / default / action / feature role tokens (14 / 16 / 20 / 24px), the ladder Spinner already uses, instead of raw size-N steps spelling the same four values. A Skeleton line moves to the text radius (rounded-sm): 8px on a 16px bar reads as a pill, not as text. docs · 7915a71

  • Reduced motion is stated once, globally. All seventeen motion-reduce: copies across eleven components were deleted — the registry now carries zero. The base.css reset owns the rule with the one sanctioned !important, so a per-component restatement adds nothing and is a second copy that can drift. One copy looked load-bearing and exposed a hole in the reset instead: it zeroed animation duration but not delay, so StaggeredTextReveal still played its words out one by one over the full stagger window. docs · 7915a71

  • Twelve components are server-safe again. Avatar, Button, Collapsible, Field, Progress, Resizable, ScrollArea, Separator, Slider, Switch, Tabs and Toggle carried "use client" without touching a hook or a handler. A client module poisons every RSC importer downstream — buttonVariants could not be read from a server component. 84 client leaves in the registry became 72. docs · 7915a71

  • text-xs is mono-only. Seven sites across Attachment, AudioPlayer, Chart and ProgressIndicator were reaching 11px in Geist Sans for density; sans copy now floors at text-sm (12px). docs · 7915a71

  • TruncatedText gains focusable, with a TruncationFocusProvider that sets it for a whole region. Clipped text becomes a Tooltip trigger and takes a tab stop — in a 50-row table that is 50 extra tab stops layered on a grid's own roving focus, and CSS truncation never hides anything from a screen reader, so the tooltip only ever served sighted keyboard users. IconText, TableCellText and RelativeTime take the same prop. docs · 7915a71

  • Button, Select, Sidebar, Toggle, Tabs, Table, DataGrid, DataList, Board, Item, Pagination, NavigationMenu, Combobox, DatePicker, Dialog, Sheet, Popover, HoverCard, Segmented, TagGroup, Bubble, Card, AppShell, EmojiPicker, FieldInline, MessageScroller, NumberField, OnboardingChecklist, ShortcutOverlay, Sonner, Switch, ToolCallChip and the dashboard-01 block — every hover now climbs one rung and every control has a pressed step. Previously only the solid primary Button darkened on :active; a state probe found 268 elements where pressing changed nothing. Select's trigger hovered only in dark mode; it now hovers in both. The current sidebar row rests on surface-3 so hovering it still moves. ComparisonMatrix and PricingSection stop using info (blue) for the promoted column and the highlighted plan — info is links and informational UI only; promotion is a neutral ladder rung. docs · b2c2e96

  • A loading Button no longer changes width. The spinner is taken out of flow and stacked over the label, which keeps its box behind opacity-0 — not visibility: hidden, which would drop the label out of the accessibility tree and leave a pending button with no name; previously a "Save changes" button jumped about 20px the moment a request started. docs · 7915a71

  • IconButton everywhere. The dismiss, pager and toggle controls that were hand-rolled <button> elements in Alert, AnnouncementBanner, Dialog, Sheet, Pagination, OnboardingChecklist and FilterBar are now IconButton, and CopyButton, MessageScrollerButton, ColorPicker, EmojiPicker and SplitButton's chevron half compose it too — so they all inherit the matrix, the focus ring, the loading contract and the required accessible name. New iconButtonGeometry(size, shape) styles an icon-only link: navigation stays a real <a> (PageHeader's back affordance) instead of acquiring role="button". 7915a71

  • One field chrome. The border/hover/focus/invalid/disabled grammar was copy-pasted in Input, Textarea and NumberField, restated a fourth time as slot overrides in Field, and again in Combobox and ChipInput. It is now fieldControl / fieldControlGroup in @vegastack/design, which Input, Textarea, NumberField, OTPInput, the Select trigger, the Combobox input and its input-group, and ChipInput all spread — so retuning the field is one edit. docs

    Checkbox, RadioGroup, Switch and the Select trigger hover in both themes, checked included. A state probe found no hover treatment at all on 31 checkbox/radio/switch fixtures, and a ticked control read dead under the cursor while an unticked one moved. They now step through the same neutral border rung every field wears, and a filled control steps through the solid's own darker rungs. docs

    NumberField's ± steppers no longer run their hover fill into the field border. The wash was full-bleed, so it met the field's hairline on three sides with a square inner corner against the rounded outer one. It is now an inset chip with its own radius; the button keeps the full pointer target. docs

    Label is inline-flex by default, so it composes into a sentence instead of breaking the line around itself; layout="block" is the explicit opt-in for the stacked form row. docs

    PasswordInput's eye toggle is an IconButton in the ghost recipe, and the eye swap has no motion. It replayed motion-pop-in behind a guard whose only job was to stop the animation firing on first paint — a tell that the animation did not belong there.

    FieldInline and EditableCell run on useInlineEdit, and FieldInline's rest hover is the shared interactive-surface recipe, so it is visible on a card rather than only on the page ground.

    AutoSaveInput drops a cn(className) no-op, sizes its status slot with --icon-default instead of a raw size-4, and marks its spinner decorative. docs · 9fbeb65

  • AttachmentTrigger shows a real focus indicator. It dropped outline-none and a border tint as its only keyboard cue; the standard outline is drawn inset so the card's radius does not clip it. A border tint is the text-entry treatment, not a button's. docs · aa5fa0d

  • AudioPlayer gains mute and a volume rail in both layouts. Audio previously had no visible volume control at all and mute was reachable only from the M key. The rail is a vertical Slider opened from the mute button, rendered inline rather than portaled — the video frame is the fullscreen element, so a portal to <body> would put the rail outside it. The seek thumb is now hidden at rest only where a pointer can hover; on touch it stays visible, because otherwise there is no scrub affordance at all. docs · aa5fa0d

  • Image lazy-loads and decodes off-thread by default. loading="lazy" decoding="async" are now the defaults, matching what MarkdownView already did for its images. Pass loading="eager" for an above-the-fold hero, where deferring the fetch delays LCP rather than saving it. docs · aa5fa0d

  • Media chrome is theme-invariant. The video overlay was built on primary, which flips with the theme — in dark it rendered a near-white scrim behind near-black icons. Scrim, pills and ink now come from --media-scrim, --media-scrim-strong and --media-foreground, which are the same values in both themes, and a compiled-CSS test pins scrim lightness under 0.3 with overlay ink over 0.85. Overlay controls are IconButton variant="ghost" shape="round" on the scrim. docs · aa5fa0d

  • Media controls keep the system's focus outline. The ring-2 ring-ring/50 glow that media invented for itself, and the forced-colours carve-out beside it, are gone; the standard 2px :focus-visible outline applies, inset with -outline-offset-2 so an overflow-hidden frame cannot clip it. tabIndex={0} now appears only on genuinely scrollable regions. docs · aa5fa0d

  • Slider grows the props the players were faking with descendant selectors. variant (default · media · overlay · bare), orientation (vertical is now supported and is how the volume rail is built), thumb (always · hover · none), marks and showValue. Every [&_[data-slot=slider-*]] override in the players is deleted. docs · aa5fa0d

  • VideoPlayer gains controlsVisible. true pins the overlay open and false keeps it closed, for kiosk players — and for static fixtures, since the auto-reveal never fires without a pointer and the docs page therefore never showed the video controls. docs · aa5fa0d

  • Rich text is one recipe, and the bars that promise arrow-key traversal now implement it.

    • MarkdownView and TextEdit share the prose recipe. Both restated the same heading, paragraph, list, quote and code recipe in their own grammar and had already drifted (h4–h6, tables and images existed on one side only). They now wear the same class from @vegastack/design, so rendered markdown and edited rich text are one typography — asserted structurally: a unit test requires every rule of the recipe on both roots, and no typography of their own. TextEdit's fenced-code block stops being a third copy of CodeBlock's surface, and the editor's links no longer all light up when the editor itself is hovered.
    • TextEdit's formatting row is a Base UI Toolbar — one tab stop, arrow keys across three labelled groups, Shift+Tab out. It was a role="toolbar" of eight independent tab stops.
    • ActionBar is a Base UI Toolbar with new ActionBarButton and ActionBarSeparator parts. Compose the actions from them: a toolbar builds its single tab stop from the items that register with it, so a bare <Button> renders but keeps its own tab stop.
    • ActionBar and MessageScrollerButton use the shared motion-dock-in / motion-dock-out pair instead of two copies of a recipe that exited more slowly than it entered, with a scale on a bar that slides off its own edge.
    • MessageScrollerButton defaults to variant="outline" with no inline colour override — after the Button matrix, outline already is a page-coloured face with the one hairline and the surface-ladder hover. 2a3fc24
  • dashboard-01's KPI labels no longer truncate. Every stat label was cut at the 2-column width ("Active agen…", "Tasks compl…", "API calls (24…"). The label now wraps to two lines across the full header and the trend badge sits on the value row, beside a short mono figure; the header breadcrumb collapses at maxItems={2} so it stays one line at 320px. 3663f8f

  • LogoRow shows marks, not links. The underline is gone — wordmarks rest in text-muted-foreground and lift to text-foreground on hover, because a wall of underlined text reads as a paragraph of links. Cell seams are logical (-ms-px border-s), so RTL keeps its inner rules instead of doubling the outer edge, and wallColumns is now a MAXIMUM over an auto-fill track with an 8rem cell floor: a 4-column wall at 320px gave 80px cells and clipped every mark. 3663f8f

  • PlanCard's promoted plan is a ladder rung, not a border colour. It now lifts onto surface-3 with its hairline tinted primary at --alpha-outline-border. A full-strength border-primary read as the active/invalid state of a form control — the wrong signal on a card someone is being invited to choose. 3663f8f

  • SettingsSection gained titleAs (h2…h6, default h3). A settings page nests sections at different depths, and a hard-coded <h3> everywhere breaks heading navigation. The visual size is unchanged; only the document structure moves. docs · 3663f8f

  • StaggeredTextReveal waits for the scroll (whenVisible, on by default). A reveal below the fold used to finish before anyone scrolled to it. The gate only ever REMOVES the reveal — the server-rendered markup animates and the client pulls off-screen words back before the first paint — so a page whose JavaScript never runs still shows its text. 3663f8f

  • Testimonial takes its quotation marks from CSS. The quote renders inside a <q>, so the browser inserts the pair the ACTIVE language uses („…“, « … », 「…」) instead of the hard-coded English “…” — which also kept the marks out of the copied text. 3663f8f

  • Navigation and layout take one selection recipe and one hover geometry. Tabs, Segmented, Toggle and ToggleGroup move onto the shared selectedChipVariants recipe from @vegastack/design, and a selected chip now hovers and presses again — it used to be excluded from both by not-data-pressed:*/not-data-[active], so the one chip a user is most likely to click was the one that answered nothing. The Tabs line trigger's hover wash used to end exactly on the rule the underline indicator rides along, in both orientations; it is held one 4px step off it with a logical margin, so the vertical variant mirrors onto the inline-start rail and RTL follows for free. SidebarProvider gains persist (default true) around the cookie write: persist={false} keeps the component out of document.cookie entirely and onOpenChange fires either way, so a host under a consent regime persists the state itself and loses nothing — the docs section is renamed from "SSR persistence" to "Persistence". ScrollArea's viewport is a tab stop only once its content actually overflows, measured on mount and on resize; Board's column viewports inherit it. AppShellContent and SidebarInset gain landmark="region", which renders a <div role="region"> instead of a <main> for a shell embedded in a page that already owns one. SidebarTrigger is an IconButton rather than a hand-rolled useRender button, so it inherits the one box, ink and hover/pressed grammar, and PageHeader's back affordance swaps its physical -ml-2 for a logical -ms-2. PageHeader's active favourite star fills with foreground instead of warning ink, which read as caution on a control that means "I marked this". docs · fdaed05

  • Popover and Select document their modality. Both are modal by default so the page cannot scroll out from under an open panel; pass modal={false} for a lightweight one. Combobox stays non-modal and says why in its source. docs · 7915a71

  • Overlay motion follows one measured scale. Every floating surface enters and leaves at duration-fast (150ms); NavigationMenu takes duration-base (200ms) because it resizes between items rather than simply appearing; the modal family — Dialog, AlertDialog, Sheet — is duration-base. Timings were measured against Vercel and Linear rather than chosen. docs · 7915a71

  • Overlay padding has two tiers, not per-surface literals. 24px (p-6) for Dialog, AlertDialog and Sheet; 16px (p-4) for Popover and HoverCard; menus keep list density. Panel widths come from --panel-width-*, and a viewport-capped popup uses Base UI's --available-height instead of a hand-written 100dvh calc. DialogContent and Command size through size. docs · 7915a71

  • Popover, HoverCard, Tooltip, DropdownMenu, ContextMenu, Select, Combobox, NavigationMenu — eight lookalike overlays became one module. Each now composes floating-surface instead of restating its own portal, positioner, popup surface, arrow and theme-scope plumbing. ContextMenu is bound to the same item parts as DropdownMenu through createMenuParts (Base UI's ContextMenu namespace re-exports Menu's parts verbatim), so the two menus can no longer drift. 7915a71

  • Command, Combobox, EmojiPicker, ShortcutOverlay — the in-panel search row is one recipe. A bordered Input inside a bordered popup drew two borders; PanelSearchFrame is a sticky, full-bleed header row with a leading glyph, no box of its own and a hairline below. docs · 7915a71

  • DatePicker, Board and Dropzone. DatePicker and DateRangePicker triggers are w-full like every other form control: the fixed w-56 and w-72 were the only fixed-width controls in the system and overflowed a 320px content area. The calendar caption is now a real auto 1fr auto grid (navLayout="around") instead of an absolutely positioned nav over a px-7 clearance, and the selected day carries a pressed rung (hover:bg-primary-hover active:bg-primary-active) instead of pinning its rest fill. Board's grab cursor appears only where a pointer drag can actually start and its column body height is the new columnMaxHeight prop. Dropzone paints the drag-over state on its own surface, so a dropzone wrapping anything other than an Empty finally shows one, and a new dragState prop paints either state for documentation and automated checks. docs · 45cde26

  • Table, DataList, DataGrid, ComparisonMatrix, Terminal — a scroll viewport is now a named, keyboard-reachable region, and it is measured rather than guessed. A wide table could previously only be scrolled with a pointer (axe scrollable-region-focusable on the Table page and the dashboard block), while ComparisonMatrix and Terminal carried an unconditional tab stop that was dead on every instance that fits. Each viewport now measures itself through useOverflow and takes a tab stop only while it can actually scroll; name it with scrollLabel (falling back to the table's aria-label) and it is exposed as role="region", unnamed it stays a plain focusable container. Its focus outline is inset, because the viewport clips its own overflow. Names and roles do not move with the measurement — Terminal's name and group role stay unconditional. Table and Terminal both stay server-safe: the measurement lives in a 'use client' leaf (table-scroll-region.tsx, terminal-body.tsx). docs · 6d25185

  • DataGrid, DataList, Table, PropertyList, Chart — the rest of the table-family pass. DataGrid gains columnPicker (default true): the "Columns" picker used to render whenever the grid had any columns — i.e. always, even for a three-column read-only grid — and it now sits in the toolbar's trailing slot beside the hidden-columns hint. DataList and DataGrid gain a mono column flag (mono numeral face plus tabular-nums, nowrap by default) and a nowrap flag; together with the shared chrome that is 249 fewer lines across the two files. TableRow no longer tints the header row on hover — it styles every row it renders, including the header row the two renderers build with it, so hovering a header washed it as if it were actionable. PropertyList becomes a container query: the label track was a fixed 112px regardless of the pane, and is now content-sized above an 80px floor at @xs and stacked below it, with values wrapping instead of truncating. Chart moves axis labels from 11px to 12px (11px is reserved for mono) with the numerals on the mono text-code-sm tier, and the tooltip follows. docs · 6d25185

  • EditableCell, ChipInput, DataGrid, CopyButton, SortableList, Board, Dropzone — all announce through use-announcer, one live region each, replacing five identical hand-rolled { text, seq } regions plus CopyButton's. EditableCell's visible status slot is no longer itself a live region, so it stops announcing its own icon swaps. Pagination's PaginationLink takes its hover and pressed steps from surfaceInteractive instead of restated hover:bg-surface-2 / active:bg-surface-3 literals. docs · 7915a71

  • Tag, FilterChip, ComboboxChip, ChipInput, TagGroup — all now compose the chip primitive. Three consequences are visible: a Tag is 28px rather than 20px and a FilterChip is a pill rather than a rounded rectangle (chips are rounded-full by doctrine); the neutral chip rests on surface-1 and an applied filter sits on surface-2 instead of the accent alias; and every remove control is the same 24×24 target. That last one fixes ComboboxChipRemove, a bare 16px box with no hit-area expansion at all (a WCAG 2.5.8 failure), and retires Tag's ::before hit area, which a nested native <button> clipped and so never actually expanded anything. TagGroup's +N overflow control is itself a chip, so the whole 28px pill is the pointer target and its hover/pressed steps come from the shared surfaceInteractive recipe rather than a hand-written descendant selector. docs · 7915a71

🗑 Removed / renamed

  • track is removed. The slider rail, progress track, skeleton and every well are surface-1; the switch off-track is surface-3, the pressed rung. Three alpha roles are removed with it, because the ladder is now the one hover mechanism and nothing references them: --alpha-fill-hover (the secondary button's /80 opacity dim), --alpha-input-hover (the dark-only input hover wash) and --alpha-surface-subtle (the outline button's hover tint, now --alpha-hover in the family's own hue). docs · b2c2e96

  • Button glass, and the seven colour-in-the-name variants. The frosted glass variant had no product consumer; media chrome uses the theme-invariant --media-* tokens instead. success, warning, info, destructive-outline, success-outline, warning-outline and info-outline baked a colour into a name and are replaced by the tone axis. docs · 7915a71

  • Button finish and the --shadow-lit token. The "lit" action finish is retired, so flat-by-default now has no exception at all and the system has exactly one shadow role, shadow-overlay. docs · 7915a71

  • selection:* in form controls — Input, Textarea, OTPInput, NumberField and the Combobox input repainted selected text near-black on near-white. Native selection is what users expect and it respects the OS and accessibility settings. docs

    shakeSignal on Input, Checkbox, RadioGroupItem, OTPInput, NumberField and ChipInput — the prop lives on Field, which owns the shake. docs

    Spinner label="" as the way to say "decorative" — decorative is now the sanctioned spelling. label="" still means the same thing; it just says it by passing a value that reads as a mistake at the call site. docs · 9fbeb65

  • mergeRefs no longer ships from the use-animation-replay registry item, and media-player-controls no longer exports assignRef. Both were spellings of the same merge; the one implementation now lives in @vegastack/design and every registry file imports it there alongside cn. grep -rn 'typeof ref === "function"' packages/ui/registry/ui is 0. docs · 3663f8f

  • AlertDialogContent intent — it wrote a data-intent hint and nothing else, leaving two props named for one concept with one of them inert. AlertDialogAction intent is the single owner of a confirmation's tone. docs · 7915a71

  • Hand-written 100dvh overlay height calcs — replaced by --layout-overlay-max-height and Base UI's --available-height. docs · 7915a71

  • sonner — the registry item and the dependency. Removing it deletes a rendering engine, the CSS override layer that fought the library's internal greys, its z-index exemption and a :focus-visible box-shadow glow the design system bans everywhere else. Toasts are now @vegastack/toast. The toaster-mirror gate stays but points at toast.tsx — the private @vegastack/ui package still needs a byte-identical Toaster — and the sonner-human-facing-alias contract exemption is gone, because it existed only while the registry item was named sonner and its page was /docs/components/toast; the two now match. docs · d5e2de2

  • TagHue — the hue vocabulary moved to the Chip primitive and is exported as ChipHue from @vegastack/chip. Tag's hue prop is unchanged; only the type's name and home moved. docs · 7915a71

  • The attestation stack is deleted — the gate receipt and its guards, the change classifier, route scoping, the Playwright-over-the-docs-export contract and pixel lanes, and the cross-engine smoke selection. CI executes every browser lane instead of inspecting evidence about it. The five docs-shell contracts those lanes carried survive as tooling/verify-docs-shell.mjs, a release-stage script with a --self-test that proves each assertion still fails on its own defect. @vegastack/ui is private and neither published package changes. 09b2107

🛠 CLI & tooling

  • Animated icons are one factory plus 439 data modules. Every mirrored lucide-animated icon used to carry its own copy of the controller — the animation controls, the reduced-motion gate, five pointer/focus handlers, the imperative handle and a block-level host — so a change to any of that meant regenerating 439 files and trusting that all 439 agreed. The controller now lives once in createAnimatedIcon, exported from the new @vegastack/design/create-animated-icon subpath, and each icon is a createAnimatedIcon({ … }) call describing only its geometry, its Motion variants, and (for 49 icons) its non-default start/stop steps. motion becomes an OPTIONAL peer dependency — only an animated icon pulls it in, so Icon/BrandIcon consumers are unaffected. The corpus went from 79,078 lines to 12,951 (-84%) and from 2.06 MiB to 0.57 MiB of source; the served registry fell from 4.48 MiB to 2.92 MiB. tooling/mirror-animated-icons.mjs emits the data modules and fails closed on any upstream archetype it cannot model; tooling/verify-animated-icons.mjs asserts the controller contract once against the factory, holds every module to a schema whose central clause is that a data module contains no controller at all, pins each generated module by SHA-256 in packages/ui/animated-icon-sources.json so a hand-edited path or timing value is rejected outright, and carries a --self-test that proves seventeen distinct regressions are rejected. docs · 2035023

  • Patch/minor alignment across the workspace, and an explicit supply-chain floor. Seventeen packages moved to their current patch or minor, including the three Tailwind siblings brought to a matching 4.3.3. The release-age floor is now explicit (minimumReleaseAge: 1440 + minimumReleaseAgeStrict: true) rather than inherited. Measured on pnpm 11.7.0: inherited, pnpm appends a minimumReleaseAgeExclude entry to pnpm-workspace.yaml and installs the too-new version anyway; explicit, the install fails with ERR_PNPM_NO_MATURE_MATCHING_VERSION and writes nothing. The two stale fumadocs-*@16.10.5 excludes were written by pnpm that way, not by a human, and are deleted.

    @hookform/resolvers moves 5.4.0 → 5.9.1 in the same pass: zodResolver's overloads did not accept a schema built by zod 4.5, so field-form.test.tsx — which exists to fail the typecheck when the RHF/Zod surface drifts — stopped compiling. It did its job.

    deploy.yml's wranglerVersion pin follows apps/docs to 4.129.0. verify-workflow-security (#88) asserts the two agree, because the deploy action downloads the version named in the workflow and production would otherwise ship through a wrangler this repository never installs or tests against. 5b03e3b

  • Next 16.2.11 → 16.3.4 for the docs app. The 16.2 line stopped receiving patches in July and two criticals were never backported. enablePrerenderSourceMaps and experimental.turbopackFileSystemCacheForBuild are defaults in 16.3 and were deleted. next dev in 16.3 writes a managed agent-rules block into an AGENTS.md/CLAUDE.md in the Next app directory when it detects a coding agent; agentRules: false turns that off — this repo's agent instructions are authored and reviewed, and a tool-managed block inside a hand-authored file has no owner. 5b03e3b

  • Security patches across the toolchain. @vitest/browser 4.1.9 → 4.1.11 (critical: provider commands bypassed allowWrite), @tiptap/* 3.27.4 → 3.31.3 (prototype pollution + paste XSS), postcss 8.5.19 → 8.5.28 (source-map file read), style-dictionary 5.5.0 → 5.5.2 (prototype pollution), plus the shadcn 4.21 bump that clears postcss under shadcn/tsup. Measured on the same advisory database on 2026-09-09, pnpm audit goes from 59 findings (3 critical · 29 high · 25 moderate · 2 low) before to 47 (0 critical · 24 high · 21 moderate · 2 low) after. Every remaining finding is a transitive dev-tool dependency with no path into published output. style-dictionary is held at 5.5.2 deliberately: 5.5.3 touches color/css alpha precision, which is token CSS output, and 5.5.2 already carries the fix. 5b03e3b

  • shadcn CLI 4.13.0 → 4.21.0. 4.13.1 fixed three unadvised security bugs — custom registry headers leaking across cross-origin redirects, path traversal for registry items without an explicit target, and flag injection from registry-supplied dependency strings. No GHSA or CVE was ever filed, so no audit tool would have flagged the gap. shadcn build output is byte-identical and verify-shadcn-consume still passes against the real CLI. 4.21.0 makes the cn package the upstream default for generated lib/utils; we did not adopt it — cn() keeps coming from @vegastack/design, which is a locked decision, and the change is registry content, not CLI behaviour. 5b03e3b

  • Playwright 1.61.0 → 1.63.0, and the Linux CI container tag follows it. The playwright devDependency moves in the root workspace and in @vegastack/ui; 1.63 ships Chromium 153.0.8010.12, Firefox 155.0 and WebKit 26.6, and drops Ubuntu 20.04 support. The audit's one API-removal concern, Locator.ariaRef(), is a non-issue: it is absent from the type surface of the 1.61.0 we came from as well as 1.63.0, and nothing in this repo names it. tooling/verify-workflow-security.mjs derives the required Linux job image (mcr.microsoft.com/playwright:v<version>-noble) from the single playwright version the lockfile resolves, so ci.yml, release.yml and deploy.yml now pin v1.63.0-noble.

    auto-install-peers=true also makes pnpm resolve Next's optional @playwright/test peer even though no manifest declares it, and left alone it stayed on 1.61.0 — two playwright versions in one lockfile, which that gate refuses. A pnpm-workspace.yaml override pins the phantom peer to the same version, so the container tag keeps exactly one authority. 3b37dde

  • verify-component-contracts --write-data-attributes records each registry part's data-* attributes and CSS variables in component-contracts.json, extracted from the canonical source through the TypeScript AST, so the docs API tables and the agent markdown export list them. The default mode fails when the contract drifts from the source, and a --self-test drifts a dataAttributes record in memory and requires the reconciliation to reject it — so the gate cannot pass by never having run. docs · 7915a71

  • Gate correctness (G1-b) — four fail-opens closed, and focus indication covered again. packages/ui/test/{contrast,stacking}.css imported only part of the layer set production ships, so every custom @utility a fixture wore compiled to nothing and both lanes measured a page no user sees; verify-test-css-layers now derives the required set from the shipped preset and fails closed. pnpm lint gains a repo-wide prettier --check. A changeset may no longer link a commit — the only sha it can name is a pre-merge one the squash orphans — and changelog-lint's own probe became reachability rather than mere object existence. Eleven cases in the workflow-security negative harness matched literals that can drift; one of them had never exercised its own rule. The geometry lane gains a focus-indicator assertion over all 541 fixtures that rejects the browser's own ring by name, packages/ui/test finally type-checks, verify-component-contracts derives the inventory counts from the registry instead of hard-coding them, six token-vocabulary design-lint rules land with negative fixtures, and verify-token-references fails closed on a --token that does not exist. b2c2e96

  • CI: the macOS runner class is ONE mac mini running two runner agents that share one home directory, and pnpm/action-setup defaulted its bootstrap directory to ~/setup-pnpm — which it deletes on every job, and which PNPM_HOME also made the package store. Concurrent jobs raced that deletion into ENOTEMPTY (or a half-linked node_modules that turbo reported as unable to spawn child process) before any repository code ran, and the store never survived a job, so every macOS install re-downloaded the whole dependency graph. Each mac-mini job now bootstraps into ${{ runner.temp }}/setup-pnpm and installs with --store-dir "$RUNNER_WORKSPACE/pnpm-store" — per-agent, and persistent — and the three jobs that enabled setup-node's package-manager cache no longer do, since it cached the directory the next job deleted. verify-workflow-security.mjs asserts all three, with negative-harness cases for each; two of its existing cases that matched the install by literal command line were rewritten to match by shape. c352ba9

  • release-detect --check-npm fails closed instead of fail-open, and docs/RELEASING.md matches reality. The registry probe spawned npm view with cwd = the repo root, where package.json declares devEngines.runtime node 24.20.0; npm enforces that field, does not honour pnpm's onFail: download, and exits EBADDEVENGINES on any other Node before it reaches the network. The old status !== 0 branch read that as "not published", so every push to main reported both live public packages as unpublished, forced publish=true, ran quality-gate, and armed the OIDC-capable publish job on changeset-free pushes (release run 34323665258 shows unpublished: @vegastack/design (none) → 0.3.2 with 0.3.2 live). The query now runs in an empty temporary directory carrying a copy of the repo .npmrc, out of devEngines' reach but still on the repo's own registry mapping, and npm view --json is parsed into three outcomes: published, absent (a genuine E404 — the only answer that may mean unpublished), and unknown (engine refusal, transport, auth, missing npm, unparseable output). An unknown never contributes to publish, is retried once, and exits non-zero when it was the only thing that could have set publish — fail-closed and loud rather than a false that was never established. Nine cases in tooling/test/release-detect.test.mjs pin it against a stubbed npm; six of them fail against the previous script. docs/RELEASING.md no longer calls this public repo private in four places, states the real reason provenance is off (npm rejects a self-hosted bundle with E422, not repository visibility), describes the merged build-sign-deploy job rather than the deleted three-job artifact split, and records that releases deliberately create no git tag or GitHub release. AGENTS.md gains that tag decision and reconciles two deviations from the verification-rebuild plan. af61a44

  • design-lint --docs-shell --emitted-css reads the BUILT stylesheet, because the shell's off-system values are compiled in by dependencies and never appear in this repo's source. It ships a negative self-test. 7915a71

  • CI gates — pnpm verify can no longer be neutered in a workflow. The workflow-security gate now matches the command as the ENTIRE run: body (a block scalar that merely contained the line satisfied the presence check while escaping every effectiveness rule), rejects any run body that discards an exit code (set +e, || true, a bare exit 0), rejects a WEBKIT_LANE / SMOKE_WEBKIT / cleared-CI env: at workflow, job or step level (each silently dropped verify:release from three browser engines to two), pins --ignore-scripts per job in both directions — the npm-OIDC publish job now installs without lifecycle scripts, like version-pr — and checks --frozen-lockfile line by line. Eleven new negative cases, all observed rejecting. The hover/pressed recipes are now measured for real: the @source glob that compiles them was a no-op in both browser lanes, and the one assertion over it was reading its own file. Also: the secret scan's scope is stated honestly (it reads the working tree, never history), release-detect no longer reads an unanswerable git diff as "nothing changed", workspace-clean refuses a path when git cannot say whether it is tracked instead of assuming it is not, and every compiled-CSS lane now imports the same set production ships, tw-animate-css included. 47ddb18

  • verify-docs-base-mirror diffs the rule blocks apps/docs/app/global.css hand-copies from base.css, which had no gate. It counts @apply as a declaration: the focus ring is expressed only that way on both sides, so filtering @-prefixed lines compared that block as empty against empty and could never fail. It ships a negative self-test. 7915a71

  • verify-docs-export fails the docs build on any JSX tag surviving outside a code fence, any unresolved export placeholder, or any empty API table — the regression guard for the markdown export — and enforces that a page carries either a curated playground or the Story explorer, never both. It ships a negative self-test, so it cannot pass by never having run. 7915a71

  • verify-mdx-manifest (docs lint) proves the agent export fails closed on the three failures that leave no artefact behind for verify-docs-export to find: an MDX component no manifest entry classifies, a placeholder whose runtime renderer is missing (nested ones included), and a component registered in the MDX map but absent from the manifest. Before it, the first rendered to a single space and the second to its bare children. 7915a71

  • pnpm verify:release — discards the docs build cache before the first export so a stale Turbopack cache can no longer report the docs shell as off-system, and the registry idempotency check now compares before/after instead of demanding an empty tree. cb99022

  • Cross-engine lane — CI now defaults to WEBKIT_LANE=require instead of auto, so a WebKit that stops launching fails the release lane instead of silently reducing it to two engines. e5d7a7f

  • CI/CD: the workflow-security gate now asserts that the workflows DO something, not only that they are shaped correctly. An adversarial review built twelve mutations that the gate passed; three made a workflow report success while executing nothing — continue-on-error on the deploy's verify job (a failed sweep still deployed to production), the same key on ci.yml's pnpm verify step (a green check on a red suite), and if: false on pnpm verify:release (the step stayed in the file and left the run). The gate now rejects continue-on-error anywhere without a recorded allowlist entry, forbids an if: on any verification step and on the deploy's sweep job, asserts the Cloudflare command exactly (deploy --dry-run deployed nothing and reported success), pins wranglerVersion against apps/docs/package.json, requires the publish-time lifecycle guard and the standalone npm publish line (a false && prefix published nothing), requires SITE_VISIBILITY in all three workflows, ref: ${{ github.sha }} on every outward checkout, --frozen-lockfile on every install, a concurrency group and a bounded timeout-minutes on every job, and verify-macos's required steps including the changeset status presence check. publish loses the contents: write and pull-requests: write it never used, every job declares a timeout instead of inheriting the 360-minute default, ci.yml declares the same SITE_VISIBILITY as the other two so the turbo cache key genuinely matches, and the OIDC scope check is structural rather than a text count. The negative harness grows from 37 to 60 mutations, each of the new ones reproduced against the pre-fix gate first. 087015e

  • WP1 of the verification rebuild — the 320px reflow, RTL containment, and effective 24px pointer-target contracts now run as a vitest browser lane over the preview fixtures (packages/ui/test/geometry.browser.test.tsx) instead of @playwright/test over the docs export. No published package changes: @vegastack/ui is private, and the only files touched are its test lane and vitest config. 7915a71

  • WP2 of the verification rebuild — test-config and tooling only, no published package change. 7915a71

  • WP4 of the verification rebuild — the contract-derived docs files become build outputs rather than committed artefacts; no published package change. 7915a71

  • The root CHANGELOG.md release entry is assembled from the changesets. A PR's only changelog artefact is its changeset, whose body opens with one of the eight section emoji; tooling/changelog-assemble.mjs groups those bodies into the ## [x.y.z] entry at version time, inside pnpm run version-packages, and sync-changelog regenerates the docs page as before. tooling/changeset-lint.mjs rejects a body with no marker, two markers, or no text. Nobody hand-edits /CHANGELOG.md between releases — it was one list at the top of one file, and every branch collided on it. 7915a71

📦 npm

  • Dependency batches 5–6: the Fumadocs 16.15.8 family, lucide-react 1.42, axe-core 4.13, Playwright 1.63 and recharts 3.10.

    Fumadocs 16.11.5 → 16.15.8 (fumadocs-ui, fumadocs-core, fumadocs-mdx 15.4.0, fumadocs-typescript 5.4.0, @fumadocs/story 1.3.0; fumadocs-twoslash takes the TS 6-safe 3.3.1 patch rather than 4.0, which needs TypeScript 7) is two migrations rather than a bump. fumadocs-core 16.14 replaced Orama with ZBSearch behind the same module path — oramaStaticClient is now a deprecated alias for staticClient, the client builds its own database, and the initOrama factory plus the direct @orama/orama dependency are gone. fumadocs-typescript 5.4 swapped ts-morph for the native TypeScript 7 API, so the docs' own-props filter reads a property symbol's declarations[].path instead of getDeclarations()[].getSourceFile().getFilePath(); the generator cache is TypeScript-version bound and was cleared.

    Two Fumadocs behaviour changes were audited and deliberately left alone. 16.13's global d theme hotkey is inert here — RootProvider mounts its window-level keydown listener inside the theme.enabled !== false branch and this site disables fumadocs' theme provider outright, so nothing is registered; the reasoning is now recorded at the call site so re-enabling that provider cannot silently reintroduce a hotkey that swallows a letter on every interactive page. 16.12 stopped force-mounting inactive Tabs panels, so a ComponentPreview's hidden Code panel is no longer in the prerendered HTML; nothing depends on it, because the visual-surface contracts read the always- mounted Preview panel and the markdown export reads fixture source from disk, so no forceMount was added.

    Nothing in the component sources changed for the icon or chart bumps. The lucide 1.25 → 1.42 rename sweep is a no-op: all 126 distinct lucide names imported across the registry, the design package and the docs app — 125 icons plus the LucideIcon type — resolve against the installed 1.42.0 module, and lucide keeps every historical rename as a named alias, so there is nothing to sweep. The 439 animated-icon data modules regenerate byte-identical through the factory, with the 28 new upstream icons left unadopted. axe-core 4.12.1 → 4.13.0 expands aria-prohibited-attr and role=image, and the browser axe lane reports no new violation. recharts 3.9.2 → 3.10.1 deprecates Legend's align/verticalAlign in favour of position/offset; no ChartLegend call site passes either and Legend still injects verticalAlign into custom content, so ChartLegendContent keeps reading it. Only chart's registry item changes, and only because its documentation comment records that migration. 3b37dde

  • @tanstack/react-table 8.21.3 → 9.2.4, in data-grid alone. v9 replaces useReactTable with useTable and stops bundling every feature into every table: a table now declares the features it uses, and row models are feature slots rather than table options. data-grid declares exactly one — rowSortingFeature with createSortedRowModel() — which turns the sanctioned-exception boundary from a claim into something the module reads back: columnVisibilityFeature, columnOrderingFeature and rowSelectionFeature all exist in v9 and none is adopted, because column visibility, column order and row selection are data-grid's own state, and the APG grid keyboard layer (roving gridcell tabindex, Enter/F2 edit mode, Escape restore) is unchanged this-file-only code. getCoreRowModel() is gone (the core model is automatic) and manualPagination went with rowPaginationFeature, where it was already inert. The four built-in comparators v8 kept permanently in its registry — alphanumeric, basic, datetime, text — are registered explicitly in the sortFns slot so getAutoSortFn resolves the same comparator per column as it did under v8. No public prop, type or behaviour of DataGrid changes; @tanstack/react-virtual is untouched. docs · 36ebf9d

  • Vitest 5. vitest 4.1.11 → 5.0.0, @vitest/browser-playwright 4.1.11 → 5.0.0 and vitest-browser-react 2.2.0 → 2.3.0 across the workspace — the runner under every test in the repository, including the browser suite pnpm verify gates on. Vitest 5 matches locator text and accessible names whole-string by default and makes toHaveTextContent whole-string equality (toMatchTextContent is the partial/RegExp matcher), which turned ten of 2363 browser tests red. Every one was fixed at the call site rather than by restoring the old substring behaviour: eight now assert the FULL accessible name — a tab's trailing count badge, a link's sr-only "(opens in new tab)", a requirement row's sr-only "Met: " prefix — so a query proves what a screen-reader user hears. getByRole('tab', { name: 'A' }) had been resolving a tab named Activity3. Vitest 5 also consolidates every run artifact under one .vitest directory, so workspace-clean.mjs's tree walk for the old .vitest-attachments directory is deleted rather than kept. No component source, no published output and no registry item changes; item count stays at 568. 2625f2b

  • Five mechanical dependency majors. motion 12.42.2 → 13.2.0 (its only import site is the animated-icon factory; the 13.0 removal of the optional @emotion/is-prop-valid dependency does not apply — no CSS-in-JS library wraps a motion component here — and useReducedMotion() is still the one-shot useState read the factory deliberately replaces with a live useSyncExternalStore subscription). react-dropzone 19.1.1 → 20.1.1, whose only breaking change is a Node 22 floor (this repo pins Node 24.20.0). @atlaskit/pragmatic-drag-and-drop 2.0.1 → 3.1.0 plus -hitbox 2.0.0 → 2.2.0, whose 3.0.0 renamed every entry point: use-drag-reorder now imports from /adapter/element-adapter, /utils/combine, /closest-edge/attach-closest-edge, /closest-edge/extract-closest-edge and /types rather than the deprecated compatibility shims. @testing-library/jest-dom 6.9.1 → 7.0.1, which makes @testing-library/dom a required peer — now declared explicitly at 10.4.1. globals 16.5.0 → 17.12.0, whose 17.0.0 split the audioWorklet environment out of browser; the shared ESLint config uses browser + node only. Behaviour of the drag keyboard layer, the live-region announcements, the "Move to…" menu equivalents and the paste-acquisition path is unchanged — all of it is ours, not the engines'. docs · 452df99

  • @vegastack/design exports the field-chrome recipes. fieldControl and fieldControlGroup are the one border/hover/focus/invalid/disabled grammar every text-entry control wears, so Input, Textarea, NumberField, OTP slots, the Select trigger, the Combobox input and ChipInput can stop keeping private copies of it. Three border rungs and no more: border-input at rest, the neutral --alpha-border-subtle ink on hover, the ring tint on focus. Hover is guarded by not-disabled: because a disabled control keeps its pointer events so a Tooltip can explain it. Every element wearing the wrapper recipe must also carry a bare data-field-group attribute — that is what @vegastack/design-tokens' base.css hooks to paint the forced-colours focus outline on the group, whose overflow-hidden would otherwise clip the inner input's own.

    Also surfaceInteractiveGroup — the group-scoped twin of surfaceInteractive, for the one geometry where the two ladder rungs cannot sit on the interactive element itself: a wash painted by an inner chip inset from a container hairline (NumberField's ± steppers). The rungs stay written once. docs · 9fbeb65

  • @vegastack/design-tokens — adds the motion-dock-in / motion-dock-out utilities — the docked-control enter/exit pair for a control that stays mounted at a viewport edge and flips data-active (a bottom action bar, a floating scroll-to-edge button). 150ms in on ease-emphasized, 100ms out on ease-exit, translate and fade, no scale: an exit is never slower than its enter.

    The pair owns the timing, the fade and the parked pointer-events: none; the travel distance stays at the call site as ordinary translate-* utilities, because it is per-dock geometry and a translate declaration inside the utility would clobber a horizontally-centred bar's composed transform. 2a3fc24

  • @vegastack/design — exports the prose recipe — one token vocabulary for rendered rich text, so a surface the system did not author element by element (markdown, a contenteditable, CMS copy) is styled by one class on its root. proseClassName is the whole recipe; prose is the per-element record it composes from, keyed by the ProseElement type.

    It is expressed as descendant variants ([&_h1]:…) because neither consumer can put a class on the elements — ProseMirror owns the editor's DOM, and react-markdown's output is reachable only through an override map — and because an element-level class silently LOSES the cascade to a root-level descendant rule (specificity (0,1,0) against (0,1,1)). Restyle prose by composing prose, never by setting a class on the rendered element. 2a3fc24

  • @vegastack/design exports mergeRefs — fan one DOM node out to several refs (a forwarded ref prop plus one or more internal refs) as a single ref callback, handling both shapes React 19 accepts and skipping null/undefined entries. It was exported from the use-animation-replay registry item, an odd home for it, while nine registry files hand-inlined the same typeof ref === "function" merge; ref-as-prop makes "the component needs the node AND has to forward it" the normal case, so it belongs in the package. Typed against React's ref shapes with a type-only import, so the entry stays server-safe. 3663f8f

  • Exports selectedChipVariants — the one "raised chip on a muted track" recipe, shared by Tabs pill/chip, Segmented, and pressed Toggle/ToggleGroup, which had drifted into four different selected looks (bg-background, bg-secondary plus a hairline, bg-foreground/10). The track is surface-1; the chip is the pressed/selected rung in its alpha form (bg-foreground/(--alpha-ink-tint)), which is what lets a SELECTED chip keep stepping — it strengthens on hover and drops back to the resting tint on press. Ships as track, item, and two state literals: pressed (Base UI data-pressed) and active (Base UI data-active). docs · fdaed05

  • The design-system registry (@vegastack/ui) bumps 0.6.0 → 0.7.0.

📚 Docs

  • The input and textarea summaries no longer promise a focus ring they never had. Both text-entry controls signal focus with a border tint, not an outline — that is the rule in Accessibility and it is what the shared field recipe implements — but component-contracts.json, the machine authority that feeds the shipped design-system skill, still described "a focus-visible ring" for each. Both summaries are corrected, so an agent reading the packaged skill roster is told what the components actually do. The other 114 component summaries were audited for the same class of claim and hold.

    Doctrine, in the same pass: react-markdown and remark-gfm are now sanctioned renderer engines rather than an undocumented exception, and two version decisions are written down with their evidence — TypeScript stays at 6.0.3 while no shipped typescript-eslint supports TypeScript 7, and tw-animate-css stays bundled in preset.css because it is consumer-facing API that Quickstart and Troubleshooting both document. 78ed487

  • Alert, Empty, Item, Timeline, Collapsible, Accordion, Announcement Banner, Onboarding Checklist, Progress Indicator, Board and Dropzone — the pages now describe the polite live-region policy, the one Empty container axis, the group-licensed listitem role and the disclosure hover wash. Alert's "Strip variant" and Empty's "Illustration & value tiers" move above API Reference per the page canon, Alert and Announcement Banner gain a "Live announcements" section, and new fixtures cover Card, Announcement Banner, Onboarding Checklist and Timeline. docs · 8c18d2b

  • react-day-picker and next-themes are now named sanctioned engines in AGENTS.md § Sanctioned dependency exceptions and in design.md, with their one-file isolation written down. Both were already shipping and neither was on the list. react-day-picker v10 is the calendar state machine behind date-picker — Base UI ships no Calendar, and shadcn's Calendar is this package under every base; next-themes is the theme engine mounted only by provider, which sonner reads through useTheme() rather than mounting a second time. docs · 5b03e3b

  • Every component page now follows the page canon, and a gate says so. All 116 pages carry the canon's frontmatter (registry, status, since, a11y), the ## Installation heading is now ## Install, and the five generated sections — install steps, anatomy, API tables, states tested and the per-item changelog — are rendered from registry.json, component-contracts.json and CHANGELOG.md on every page instead of being hand-typed on three. tooling/content-lint.mjs enforces the section vocabulary, the section order, "nothing after Do / Don't except the Changelog", and generated-not-typed, with a --self-test that observes each rule failing; tooling/verify-docs-export.mjs additionally requires a playground or Story explorer to render under the page's ## Playground heading. The registry frontmatter field is required and is no longer inferred from the page slug, so a wrong or missing item name fails the build rather than composing the wrong shadcn add target. The AutoTypeTable alias for ApiTable is gone. On the Command page the live dialog demo moved to ⌘J, because the docs site itself owns ⌘K and both dialogs were opening at once. docs · 7153311

  • A component page's status and since are now machine authorities, not hand-typed prose. Both are recorded per component in packages/ui/component-contracts.json, written onto the page by pnpm design:derived, and compared against the contract by tooling/content-lint.mjs, so a page cannot claim a status or an origin version the contract does not hold. status was 116 identical stable strings with nothing behind them; every component is genuinely stable — each has a registry item, a docs page, a preview and required test coverage, and no ledger, changelog or source records a deprecation or a preview-quality component. since was derived once from git log --follow and is now pinned data, never computed: the values were verified against /CHANGELOG.md's enumerated release lists (0.2.0 1/1, 0.3.0 12/12, 0.4.0 12/12, 0.5.0 2/2) and against packages/ui/registry.json as it stood at each release commit (0.1.0 82/82). That check corrected one page — MediaPlayerControls now reads since: 0.7.0, not 0.5.0: --follow had walked into the audio-player source it was extracted from, while the item itself has never shipped. A component authored between releases carries the next version, and tooling/version-sync.mjs re-stamps it at version time with the version actually being released, so a different bump than the author guessed cannot publish a wrong since. docs · c352ba9

  • API tables are flat and expanded, on every page at once. One table per exported part — name, the literal union ("default" | "secondary" | …, not union), the @default value, the description — instead of collapsed accordion rows. This one lands everywhere immediately: the renderer is registered under the legacy AutoTypeTable name the 107 unmigrated pages author, so no page body had to change for it. Own props only, and a part with no own props of its own gets one sentence instead of the 138 "(no own props)" placeholder rows that filled 18 pages. A second table lists the data-* attributes and CSS variables the part exposes. example · 7915a71

  • Badge — a "Minimal — the dense-table treatment" section, real pixel sizes on the Sizes section, and a playground that gained the bordered switch and emits dot={false} when the switch disagrees with the variant's own default. docs · 7915a71

  • Button — the page is rebuilt around the matrix: a variant row, a tone row, and the full variant × tone grid with the forbidden solid + destructive cell called out. The "Lit finish" section is gone. IconButton gains Tones and Shape sections; SplitButton gains Tones. docs · 7915a71

  • Colors — a new surface-ladder specimen renders both themes side by side with the rungs and their alpha twins; the sidebar section now says the rail is aliases, not a second palette. docs · b2c2e96

  • Doctrine corrections in design.md — the hairline is described as the derived foreground alpha rather than a solid border; Button secondary as the rung-1 fill over a transparent base border rather than a card fill plus border; Input/Select/Textarea as transparent with a dark-only wash rather than a secondary fill; and the switch off-track as surface-3 rather than the removed track token. The Sidebar page's active-row description matches the shipped surface-3 rest / surface-2 hover behaviour. docs · b2c2e96

  • Progress, RelativeTime, TruncatedText, Kbd, StatusIcon, Skeleton — each page now explains the mechanism rather than the class name: why an indeterminate bar is a segment, why the first paint is an absolute date, why a grid turns tab stops off, and where reduced motion is actually handled. A duplicate empty heading on the Progress page is gone. docs · 7915a71

  • Elevation — "the two roles" is now "the one role"; every foundations page that named shadow-lit was corrected. docs · 7915a71

  • Fullscreen preview is the system Dialog. The old overlay declared role="dialog" aria-modal="true" and had no focus trap — Tab walked straight out into the hidden chrome behind it. Copy Prompt moved once into the page header (it was repeated six times on the Button page), the hero preview renders through the same frame as every other example, a skip link is now the first tab stop on every page, and the icon-gallery tile is a real labelled button instead of 439 nameless focusable divs. accessibility · 7915a71

  • llms.txt carries the registry roster and the skill roster — every installable item with its page and its shadcn add target, and the public agent skills — so an agent can go from "I need a data grid" to the page and the install command without scraping. guide · 7915a71

  • The markdown export is real markdown. The per-page .md route and llms-full.txt previously emitted <AutoTypeTable …/> and <ComponentPreview …/> verbatim — 107 of 110 component pages and 260 occurrences in llms-full.txt — so an agent reading the docs saw no props and no example code at all. Every MDX component now renders to markdown: the exact fixture source the Code tab shows, the flat prop tables, the install steps, the do/don't pairs. Browser-only surfaces are replaced by a one-line note rather than dropped silently. guide · 7915a71

  • One page canon, for humans and agents alike — the infrastructure, and three reference pages. The canon defines a fixed page shape whose machine-readable half is generated from the two authorities rather than typed: Install from registry.json (the shadcn add command, the registry dependencies and the sanctioned engines), Anatomy from the contract's new dataAttributes, the states-tested table from the contract's states, and a per-item Changelog filtered out of /CHANGELOG.md. This release ships those generated sections as MDX components and places them on three reference pages (button, dialog, data-grid); the remaining 107 pages keep their current bodies and are migrated to the canon in the next release. The canon table is design.md §Docs canon — the target shape, not a description of every page today. canon · 7915a71

  • Per-family variants are documented as eight tokens, not six — subtle-hover and subtle-active (the soft fill's hover and pressed steps, precomposed at build time and AA-gated against -text) were missing from the Colors page, and from design.md, which called the ramp seven tokens. docs · b2c2e96

  • The docs shell obeys the design system it documents. Fumadocs' chrome and the typography plugin are compiled against Tailwind's stock theme, so headings, sidebar titles and prose <strong> rendered at weight 600–900 in a system whose ladder is 400/500, cards used rounded-xl, and popovers used the stock shadow ladder. All of it is remapped to system values once. Demos also sat on the 15px/28px prose base because the product type scope re-bound the --type-* vars but not the inherited font-size. foundations · 7915a71

  • Theming and Colors no longer teach a removed token — both pages used bg-primary/(--alpha-surface-subtle) as the worked example of an override flowing through the @theme inline bridge. That alpha role was deleted with the ladder, so the example compiled to nothing; both now show the fillInteractive recipe's real washes. docs · b2c2e96

  • The shipped design-system skill is refreshed for Button's variant × tone matrix, the single xs · sm · md · lg size vocabulary, and IconButton as the only icon-only path. No runtime code changed. guide · 7915a71

  • Field, Checkbox, FieldInline, Textarea, Motion — every page that described the error as role="alert", the shake as per-control, or the textarea focus treatment as a removed outline now says what the code does. Textarea gains the size example it never had, NumberField gains its invalid fixture and folds its "Scope" table into Usage, and Label documents layout. docs · 9fbeb65

  • Docs — design.md gains a §Prose section (one recipe, why it is expressed as descendant rules, and the cascade trap that makes an element-level typography class a silent no-op inside a prose root) and its §Motion vocabulary gains the docked pair with the rule the pair encodes: an exit is never slower than its enter. The component skill's motion-mechanism matrix gains a fourth row for a control that stays mounted at a viewport edge and flips data-active. text-edit's "Scope" moves into its canon position and its keyboard table documents the toolbar's arrow-key traversal. docs · 2a3fc24

  • design.md §Charts and §Brand & marketing. A single series is drawn in foreground ink (--chart-single), not a categorical hue: the ramp encodes which series is this, a distinction that does not exist when there is only one — hue starts at two series. Two new §Brand & marketing subsections: Promotion is a ladder rung plus a tinted hairline, never info and never a full-strength chromatic border; Marketing leaves records the three conventions the leaf components encode — quotation marks from CSS rather than characters, a logo wall of marks rather than links, and a scroll reveal that fails open. The component skill §4 states the responsive ladder explicitly: container query → viewport breakpoint → useMediaQuery, and a JS media branch must declare its server answer. 3663f8f

  • Plan and ledger — withdrew the verification rebuild plan tooling line-count target, dropped WP3b with the measured reasoning, and recorded the post-rebuild audit including the decision to leave main unprotected while the audit epic merges. 5d2c705

  • Ship skill — corrected a false claim about CI on the Version PR: runs on the changesets bot branch sit at action_required and never execute, so the publish is gated by quality-gate on main before and after the merge, not by checks on that PR. 5aba886

  • Docs

    • Corrected the shipped 0.1.0 changelog entry: the initial release carried 83 components, not 75 (and the docs showcase had 99 pages, not 91). Recomputed against the release commit with the repository's own path-derived classifier, which counts icon-button as the component it is. The animated-icon and hook figures were already right.
    • Replaced the hand-maintained runner roster in the Linux CI runbook — and its two copies elsewhere — with the one gh api command that returns live truth, keeping only the per-host facts that command cannot return.
    • Reconciled the hover/pressed rule in AGENTS.md and design.md with what tooling/design-lint.mjs actually enforces (hover-without-pressed), and added the sr-only accessible-name separator to design.md § Accessibility as doctrine.
    • verify-component-contracts no longer tells the reader to run a command that cannot fix a drifted expectedCounts key.
    • Versioned the audit epic's orchestration briefs into the repo, and recorded the epic's review rounds — three Codex, the rest independent Opus reviewers — in the ledgers. c68a35b
  • Docs — AGENTS.md is a rulebook again (150 lines): locked decisions as one-liners with dates, the three verification loops, and the four artefacts a PR carries. The CI history it used to carry moved to docs/ledger/operator-review.md, and the component, review, and ship skills now describe only machinery that exists. d65088b

🐛 Fixed

  • Animated icons — the reduced-motion effect ran after every render in all 439 icons, because it was written without a dependency array. It now runs when the preference changes, once, in the factory. docs · b2c2e96

  • AppShell's skip link now targets its own content region. The skip link and AppShellContent both hard-coded the id main-content, so a page holding more than one shell published that id twice and EVERY skip link resolved to the first region — measured on the app-shell docs page, where four embedded previews each carried it and the documented "Tab once, press Enter" flow landed the reader in the wrong preview from every frame but the first. AppShell now mints the id once with React.useId() and shares it to AppShellContent, with a new contentId prop for when the id has to be known (a deep link, an external aria-controls, a test harness). Setting id on AppShellContent moves the element but does not rewire the link; that is what contentId is for. Sharing a generated id is why app-shell.tsx now carries a client boundary at the shell root — createContext/useContext are unavailable under the react-server condition — which is where SidebarProvider's own client context already lives; page content passed as children still renders on the server. docs · 64ba720

  • Release readiness — close the modal focus leak across Dialog, AlertDialog and Sheet; isolate the animated-icon gallery to its own route; make the docs-shell and export checks fail closed over both fixes; and stabilize the complete cross-engine suite with trusted interactions, settled animation baselines, and sequential browser processes. 515da26

  • The notification badge pops for real activity only. NotificationBell read a mount ref during render and flipped it in an effect that scheduled no re-render, so the pop-in class first landed on whatever unrelated re-render happened next: a parent state change animated the badge with no new notification behind it. The previous count is now held in state and the cue is replayed through useAnimationReplay when the count rises after mount and the badge visibly changes — so it never fires on mount, never on a re-render, and never for 100 → 101 (both read "99+"). The Timeline hero fixture, which sat under the 24px pointer-target floor, is lifted off it. docs · 8c18d2b

  • tiptap 3.31.3 loaded prosemirror-model twice, and TextEdit's suite died on it. @tiptap/pm@3.31.3 declares prosemirror-model directly and resolved to 1.25.11, while its siblings stayed on the 1.25.9 the pre-bump lockfile already carried — pnpm install only re-resolves what it must. ProseMirror compares node types by object identity, so a fragment built by one copy is unconvertible by the other (RangeError: Can not convert <> to a Fragment). pnpm dedupe collapses the tree to a single 1.25.11 and removes 34 packages. packages/ui/vitest.config.ts also stops pre-bundling clsx and tailwind-merge, which are @vegastack/design's dependencies rather than @vegastack/ui's and printed a resolve failure on every run. 5b03e3b

  • The provider unit test no longer races sonner's toast timer. Sonner's toast store is a module singleton, so a toast fired by one test kept rendering — close button and all — into the next test in the same file for its full 4 s lifetime, and an unscoped getByRole("button") there became a strict-mode violation under load. The toast is now dismissed and drained where it is fired, and the theme probe is queried by accessible name. Test-only; no component changed. 5b03e3b

  • An indeterminate Progress no longer reads as 100% complete. Base UI writes no inline width when value is null, so an indicator styled only for the determinate case inherited the track's full width — an upload in progress looked finished. It is now a 35% segment sweeping the track on the one sanctioned looping utility, motion-indeterminate, whose keyframes rest on the same frame at both ends so reduced motion leaves a static segment rather than a full bar, and aria-valuenow is omitted. docs · 7915a71

  • The prefers-reduced-motion: reduce reset zeroes delays too. It gains animation-delay: 0s !important and transition-delay: 0s !important. Zeroing duration alone left a staggered entrance sequencing over its real-time delay window, which is motion; with the delay zeroed the whole sequence lands at once, and no component needs a motion-reduce: restatement of its own. docs · 7915a71

  • RelativeTime no longer renders an empty first frame. A relative label needs Date.now(), which the server cannot reproduce, so it used to render "" until hydration — a visible pop and a layout shift on every row of a list. Server and hydration render now agree on the absolute date ("Mar 15, 2025"), derived from the target instant alone, and the swap to the relative label is a text change inside a box that is already the right size. docs · 7915a71

  • Docs — the icon gallery was unusable by keyboard and inert on touch. Each tile was a focusable <div> with no role, so all 439 were reachable and announced as nothing; each is now a real <button>. A tile also drives its icon through a ref, and holding the ref suppresses every trigger the icon provides for itself, including its tap-to-play pointerdown — the tile replaced hover and focus but not that one, so on a phone nothing in the gallery ever moved. Tiles now carry the tap driver too, under the same pointer-type rules. docs · b2c2e96

  • The surface-ladder specimen showed the wrong swatches — the "alpha twins" panel labelled two swatches --alpha-hover / --alpha-pressed while painting the opaque surface-2 / surface-3 rungs, so it demonstrated the opposite of the twins' claim. It now paints the real foreground composites over three hosts (page, card, well) in both themes, with the opaque rung beside each wash for comparison. docs · b2c2e96

  • Text-entry focus under forced colours — Input, Textarea, NumberField, OTPInput and TextEdit signal focus with a border tint and outline-none. Windows High Contrast replaces border-color outright, so a focused field showed no indicator at all. The token layer now paints a real 2px outline under forced-colors: active, once, for every text-entry control. docs · b2c2e96

  • Text on the soft media scrim is gated at AA — media-foreground on media-scrim was checked only against the 3:1 non-text floor while the token contract permitted labels on it, so the contract was wider than its enforcement. The pair is now gated at 4.5:1; it measures 5.22:1 over the white worst case, so nothing moves today and a future scrim retune that thins it under AA fails the build instead of silently demoting its labels. The media-scrim, media-scrim-strong and media-foreground descriptions state where text is allowed and which floor enforces it. No token value changes. docs · b2c2e96

  • Media chrome no longer inverts in dark — the video scrim and its controls were built from primary, which flips with the theme, so in dark the scrim rendered near-white with near-black icons. New theme-invariant --media-scrim, --media-scrim-strong and --media-foreground tokens keep overlay chrome dark-scrim + light-ink in both themes. docs · b2c2e96

  • Four controls shipped with utilities silently destroyed, and a Field > Textarea was unlabelled. In five places two adjacent class-string literals were concatenated with no separating space, so JavaScript welded them into one word and the utility on both sides of the seam vanished. The Switch had no track colour in either state — measured background-color: rgba(0, 0, 0, 0) and padding: 0px unchecked and checked, with on/off conveyed only by thumb position on a background-coloured thumb; only a hovered checked switch painted, so the control appeared under the cursor and nowhere else. The switch thumb ran on Chromium's default curve instead of --motion-ease-standard; a focused OTP slot wore the global 2px focus ring that text entry exists to suppress; and the NumberField steppers rendered at full --foreground with no hover step. All four are repaired, and design-lint gained a structural class-glue rule that rejects the seam at the AST — the existing rules read one literal at a time and could not see it, which is why transition-pairing passed on an element with no ease token.

    <Field label="…"><Textarea /></Field> produced a textarea with no id, no aria-labelledby, no aria-describedby and no aria-invalid: the <label for> pointed at nothing, the error message was not linked, and axe reported label at critical. Textarea now renders through Base UI's Field.Control, like every sibling control, so the wiring and the destructive border tint arrive automatically. Standalone use is unchanged.

    Also fixed: aria-invalid was accepted and inert on a standalone OTPInput (it landed on the root, never on the slots) and on a standalone NumberField (the group's :has() selector cannot match the group's own attribute); a read-only EditableCell with a select editor rendered the raw stored value where the editable cell rendered the option label; and a borderless Field showed no resting border when invalid. docs · 02ba364

  • Text-entry focus under forced colours, on a field with addons, where the outline was being clipped. A bordered field group — Input's prefix/suffix wrapper, NumberField's stepper group, ChipInput, the Combobox input-group — clips with overflow-hidden so its addons follow the rounded corner, and the inner input's outline is offset outward into that clip. It was drawn and then cut, so an addon field still had no visible focus under the forced palette. The group carries the outline now (via a bare data-field-group attribute that base.css hooks) and the control inside stands down, so the two never double-ring. docs · 9fbeb65

  • Text-entry focus — a text field's border is its only focus channel, and two other states were taking it. An aria-invalid field kept its destructive border when focused, and a Field borderless control kept its transparent one, so both showed no focus indicator at all (WCAG 2.2 §2.4.7). The invalid tint in fieldControl / fieldControlGroup and on TextEdit's container now stands down on focus/focus-within, borderless flattens only while unfocused, and Input's outline-hidden — lost to a missing space in a string concatenation — applies again. The focus tint is now contrast-gated as the composite users actually see: 4.04–4.51:1 light, 6.31–7.72:1 dark. docs · cff5ccb

  • Two shipped WCAG 1.4.3 failures in brand and status colour, and the three gates that could not see them. The destructive Bubble was the only place in the registry that used a solid FILL token as body text — text-destructive over bg-destructive/(--alpha-soft-surface) measured 5.24 / 4.31 / 4.44:1 in light and 2.56 / 2.37 / 1.78:1 in dark across rest/hover/pressed, and its light ladder inverted (hover L 0.874, pressed L 0.883) because the pressed step jumped to a precomposed token sitting on a different ground. It is now the same soft recipe the four soft Buttons use — bg-destructive-subtle / -hover / -active with destructive-text ink — measuring 5.80 / 5.13 / 4.69 light and 6.14 / 5.02 / 4.61 dark, monotone in both. The tinted variant's pressed step, which composited to L 0.921 against a surface-3 hover at 0.922, moves to --alpha-ink-tint-strong so a press is visible. The cta Button painted its 0.75rem/400 mono label in text-brand, a 3.5:1 MARKER value, measuring 3.41 / 3.33 / 3.21:1 in light on the public docs playground; the family now ships brand-text, the page-readable half every chromatic family already has, and the label re-measures 5.93 / 5.80 / 5.59 light and 11.41 / 10.90 / 10.13 dark. The hovered-link dim (--alpha-link-hover, on the link Button, every rendered rich-text link through prose, and PropertyList) composited success/info/warning ink to 4.03–4.11:1 in light at 80% and is now 88%, re-measured 4.74–4.83:1. --alpha-soft-hover and --alpha-soft-surface are removed: their only consumer was that Bubble line, and --alpha-soft-hover was a second, 13pp different answer to the role sd-hooks.mjs's SUBTLE_HOVER_ALPHA already owns. --font-display / --font-pixel are now bridged into @theme inline, so D17's sanctioned Geist Pixel flourish is reachable. Gates: contrast-check.mjs measures brand-text and the link-hover composite (both observed failing on the pre-fix theme); design-lint gains fill-token-as-text and field-group-pairing, both with negative fixtures; verify-token-references and design-lint now cover packages/design/src, where every shared recipe lives and where a bogus token previously exited 0; and verify-docs-base-mirror now mirrors the ::view-transition-* reduced-motion companion rule the docs copy had silently lost. docs · 18e2208

  • A Timeline's last row lost the bottom of its pointer target. TimelineItem's <li> uses content-visibility: auto for render skipping, which brings paint containment with it, so anything a child paints outside the box is clipped and stops being hit-testable. timeline-content dropped its bottom padding on the last item, and a trailing RelativeTime — whose 24px pointer target comes from a 4px before:-inset-y-1 overhang — had that overhang fall outside the clip: the effective target collapsed to the row's own 23px, under the WCAG 2.2 SC 2.5.8 floor. The last row now keeps 4px of bottom padding, exactly the depth of that hit area. Trailing whitespace under the final row grows by 4px; nothing else moves. docs · 6a5a49e

  • Pointer targets — nine WCAG 2.2 §2.5.8 misses recorded by the geometry lane are closed at the root. IconText's row and a Marker rendered as a link or a button each carry an invisible hit area, so a clipped row and an action marker measure 24px to the pointer and not one pixel differently to the eye. In the docs, the attachment demo composed its actions above the card trigger — the reverse of the documented order, which made the remove button unclickable — the scroll-fade demos now reflow at 320px instead of scrolling the page sideways, and the message-scroller outline entries sit on a target-sized pitch. The lane itself learned two facts it was missing: an inert control accepts no pointer action, and a role="tabpanel" is not a target. docs · 1b11aa1

  • The video player's tooltips and settings menu are visible in fullscreen. They portaled to <body>, which the browser does not paint inside a fullscreen element — the playback-speed and quality menus were unusable in fullscreen. MediaPlayerControls takes a portalContainer and VideoPlayer passes its frame; the volume panel already avoided this by rendering inline. docs · aa5fa0d

  • The video overlay's seek rail rests at its own thickness and thickens on engagement again. Its h-1 tied on specificity with the shared track's h-1.5, so Tailwind's sort order picked the default 6px rail and the hover/focus thickening had nothing to thicken from. docs · aa5fa0d

  • A viewer's chosen playback speed no longer resets while the media plays. The media-element effect listed the consumer's onTimeChange among its dependencies; timeupdate fires ~4×/s and re-renders the controls, so a player given an inline callback re-applied defaultPlaybackRate several times a second and 2× snapped back to 1×. docs · aa5fa0d

  • Fixed — the geometry contract lane (packages/ui/test/geometry.css) imported the token theme and base layers but never utilities.css, so every @utility in the system — scroll-fade-*, scrollbar-thin, motion-pop-in, the new motion-dock-* pair — compiled to nothing and all 523 fixtures were measured against a layout no user ever sees. A custom utility with no definition is an empty rule, not an error, so the gap only ever made the contracts weaker. The lane now imports the same layer production does. 2a3fc24

  • useIsMobile reported false on the server, so SSR rendered the desktop layout on a phone until the effect ran — Board enabled pointer drag and then disabled it. It now renders the serverFallback the caller declares. usePlatform's isTouch was frozen at the post-hydration value; the primary pointer can change mid-session — a 2-in-1 detaching its keyboard — and a drag affordance gated on isTouch has to follow it, so that half is now the live (pointer: coarse) query. docs · 3663f8f

  • ParticleField froze the brand colour of the theme it mounted in. The ink was read once, into a const, inside an effect keyed on nothing the theme touches — so a light-mounted field kept the light --brand after a toggle to dark until something forced a remount. It now reads the canvas's own resolved color per frame, and the single static reduced-motion frame repaints on a theme change too. 3663f8f

  • The collapsed-breadcrumb trigger was a 20×20 pointer target. Its visible box was the 20px BreadcrumbEllipsis glyph and nothing expanded it, so the one control that reveals a trail's hidden segments sat under the WCAG 2.5.8 24×24 CSS px floor — measured 20.00×20.00 on all three breadcrumb fixtures. BreadcrumbCollapsed's trigger now carries a transparent ::before expansion (relative before:absolute before:-inset-0.5) that brings the EFFECTIVE target to exactly 24×24 with no change to the visible glyph and no change to the trail's line height; 2px per side stays inside BreadcrumbList's 6px gap, so it never reaches into a neighbouring segment. BreadcrumbEllipsis is decorative and stays 20px — its doc comment now says the wrapping trigger owns the target, and the manual-composition example demonstrates it. docs · fdaed05

  • Four navigation and layout accessibility defects. BreadcrumbPage announced the current page as a disabled link — it rendered <span role="link" aria-disabled="true">, so screen readers described a non-interactive segment as a dimmed link; it is a plain <span aria-current="page">, and the ARIA misuse was inherited from shadcn. Focus rings were clipped on scroll viewports — ScrollArea's and MessageScroller's viewports and the sidebar rail offset their focus outline OUTWARD under a clipping ancestor, so the ring was cut in half or lost; all three inset it now. Every shell and sidebar docs fixture rendered a duplicate <main> inside the docs page's own, failing axe's landmark-no-duplicate-main; they render landmark="region". And TabsContent, BreadcrumbCollapsed's trigger and AppShell's skip link each restated the global :focus-visible rule — two copies of one rule can only drift, so the copies are gone. docs · fdaed05

  • The DatePicker caption dropdown was a 21px-tall pointer target. captionLayout="dropdown" renders a transparent <select> stretched over the caption root (absolute inset-0), and that root — an items-center child of a 32px row — collapsed to its 21px line box, so the real control measured 50.36×21.00 against the system's 24px effective-target floor (WCAG 2.2 §2.5.8). The root now takes self-stretch, handing the select the row's full height. Nothing in that root paints, so the month/year label and its chevron are pixel-identical. Its entry in the geometry lane's EXCLUDED map is deleted. docs · 45cde26

  • registry:lib files are now modeled as shadcn-transformed in the shipped consume verifier. shadcn add removes a JS/TS file's entire leading comment prologue as it writes it, for every file type it touches — but verify-registry-item.mjs listed only registry:ui, registry:hook, registry:page and registry:component, so the first registry:lib items (geo-data, drag-item) compared the copy-in against unstripped source and failed post-write verification with a line-count mismatch — i.e. the gate reported a TOCTOU signal for a transform the CLI is sanctioned to perform. check-updates reads the same set, so its diffs were affected identically. 45cde26

  • Toasts appear again, and three appearance-probe defects close. The docs site mounted the registry copy-in Toaster under the package provider's ToastProvider, and each toast module owns a module-scope manager — so the viewport listened to one store while every preview's toast() wrote to the other, and the Toast page had been silently dead since toasts moved to Base UI. The copy-in now brings its own provider, a gate refuses a Toaster whose provider comes from a different module, and a browser test pins both halves of the rule. Tabs' count badge stacks its ink wash on whatever the trigger paints, which put muted ink at 3.43:1 on a selected pill in dark; it takes body ink now, the token gate learned to check a wash painted on a ladder rung, and the rendered-contrast lane covers every variant in both themes. DatePicker's data-day hook is a stable YYYY-MM-DD instead of a locale-formatted string, which is what made a prerendered calendar throw a hydration error in every browser whose locale was not the build host's, and the page now says prerendering a formatted date needs an explicit locale. NumberField's addon slots hold an interactive control off the field's hairlines and keep its focus ring out of the clip, so the money recipe's currency Select stops painting into the rule. 1e439d9

  • A table's selection checkbox did not own its own 24px hit area. TableHead and TableCell collapsed the trailing padding of a checkbox column to pe-0, so the checkbox's 6px ::before overhang fell outside its own cell — the neighbouring header's sort control owned part of the centred 24×24 square. Both cells now use pe-2, the least that contains the target, and the sort control no longer carries a negative inline margin (SortableHead narrows its own cell padding instead, so the label alignment is unchanged). docs · 6d25185

  • Two focus rings that never painted. Charts were focusable with no visible ring — recharts' accessibilityLayer makes the plot <svg> a tab stop, and ChartContainer's own .recharts-surface outline reset then poisoned --tw-outline-style on the very element that takes focus, so even the global :focus-visible rule resolved to outline-style: none. The reset is now scoped to :not(:focus-visible). And the DataGrid roving cell's ring was clipped, because the cell lives inside the table's scroll viewport, which clips its overflow — it is now inset. PropertyList values stop truncating for the same reason: the overflow: hidden that truncate implies was clipping the focus ring of any link inside a value. docs · 6d25185

  • The 320px contract check no longer races a re-rendering fixture — a fixture that re-renders on its own timer (relative-time reschedules a setTimeout) could detach between the visibility assertion and scrollIntoViewIfNeeded, failing the sweep with "Element is not attached to the DOM" on a different subset of Chromium projects each run — on unmodified main as well. The scroll is now a bounded retry that re-resolves the locator; the assertions, the RTL and 24px target-floor checks, and the fixture selection are unchanged. b2c2e96

⚠️ Breaking

  • Seven animated icons drop a deprecated handle alias; an eighth renames its handle type. BotMessageSquareHandle, ConciergeBellHandle, KeyIconHandle (on both key-circle and key-square), RefreshCCWIconWIcon (on refresh-cw), ActivityIconHandle (on square-activity) and ZapHandle were @deprecated aliases left behind by upstream naming quirks; each of those icons still exports its <Name>IconHandle and only the alias is gone. chevron-first is the different case and is a rename, not an alias removal: upstream had copy-pasted a displayName from another icon, so the primary interface was called ChevronsDownUpIconHandle and ChevronFirstIconHandle was the @deprecated alias of it. The exported component symbol is authoritative, so the name that survives is the one that matches it — ChevronsDownUpIconHandle → ChevronFirstIconHandle. Consumers of the old name must rename; no compatibility alias is kept. docs · b2c2e96

  • Alert is role="status", not role="alert", and Item takes role="listitem" only inside an ItemGroup. Every alert used to be an assertive live region, so a page rendered with three static alerts interrupted a screen reader three times before the user had read anything. The banner is now polite for every intent; pass the new live prop for a banner raised by a user action, and a destructive or warning intent then escalates to the assertive alert role. AnnouncementBanner drops its role="status" at load and takes the same live prop. Tests asserting getByRole("alert") on a static banner should read getByRole("status"). Separately, Item applied role="listitem" to every non-render row, so a standalone Item was an axe aria-required-parent critical and Timeline had to document role="none" as a workaround; ItemGroup now provides the context that licenses the role, and outside one a row carries no role at all. Remove any role="none" passed to work around the old default. docs · 8c18d2b

  • Empty has one container axis. variant: plain | card | dashed replaces the bordered flag crossed with surface, a pair that could ask for a dashed card and resolved it only by letting tw-merge pick a winner. Rename map: bordered → variant="dashed", surface="card" → variant="card", surface="transparent" (the default) → variant="plain". The data-bordered and data-surface attributes are replaced by data-variant. EmptyTitle also takes an as prop, so the hard-coded <h3> no longer guesses at the host page's heading outline — pass as="h2" when the empty state replaces a page body, or as="p" when the surrounding card already carries the heading. docs · 8c18d2b

  • Badge speaks the same variant vocabulary as Button. variant is now solid · soft · outline · minimal: subtle is renamed soft, with no alias — a stale variant="subtle" is a type error. The three sizes become three REAL heights, sm 16px · md 20px · lg 24px; sm used to be md with 2px less horizontal padding, which is a padding value, not a size. minimal becomes ink only — no fill, no border, no horizontal padding — so it sits flush in a table cell instead of faking a pill, and it now carries a leading dot by default, because a badge with no container has nothing but colour left to signal status with (WCAG 1.4.1). Pass dot={false} to opt out, or the new icon prop to take the dot's place. docs · 7915a71

  • Kbd defaults to os="other". It reads no navigator — that is what keeps it server-safe — so the platform is the caller's to resolve: run usePlatform() and pass the answer down. The old default shipped mac glyphs to a Windows majority. TooltipKbd takes the same os prop. docs · 7915a71

  • border and input are translucent. border is derived as foreground at --alpha-border (8% light / 14% dark) so one hairline reads on the page, on a card, inside a well and on a dark band. Anything that assumed an opaque border value should read the variable instead. docs · b2c2e96

  • Light cards are page-coloured. card was oklch(0.985) against a 0.994 page — a grey slab no reference system draws. It is now the page colour, separated by the hairline alone; dark keeps its one-step lift. popover is card in both themes. docs · b2c2e96

  • Surface tokens are now one ladder. surface-1 / surface-2 / surface-3 — the rest-fill/well, hover and pressed/selected rungs — arrive with the theme-invariant alpha twins --alpha-hover (7%) and --alpha-pressed (10%). secondary, muted and accent were a single OKLCH value under three names, so no hover or pressed state could be seen on a card. They are now aliases of ladder rungs and have no independent values: secondary = muted = surface-1, accent = sidebar-accent = surface-2, sidebar = card, sidebar-border = border, sidebar-ring = ring. Existing bg-muted / bg-accent / bg-sidebar-* utilities keep compiling and keep their rest appearance; only accent moves (one rung darker, because it is the hover rung). Name the rung in new code. docs · b2c2e96

  • Button has no icon size tier. size="icon" / icon-xs / icon-sm / icon-lg are gone; every icon-only action is IconButton, which makes the missing aria-label a type error and now owns shape="square" | "round". 7915a71

  • Button is two axes, not fifteen variants. variant is now the SHAPE — solid · soft · outline · ghost · link · cta — and the new tone prop is the HUE — neutral (default) · destructive · success · warning · info. Every recipe is written once as ten class strings and reads the hue from --btn-* custom properties, so all thirty cells share one hover/pressed grammar. Rename map: default → solid, secondary → soft, destructive → soft

    • tone="destructive", success/warning/info → soft + the matching tone, {family}-outline → outline + the matching tone; outline, ghost, link and cta keep their names. A destructive action is never a solid red button — tone="destructive" with variant="solid" does not type-check. docs · 7915a71
  • disabled is aria-disabled, not the native attribute. Button, IconButton and SplitButton keep their pointer events and stay focusable when disabled, so a Tooltip can explain why the action is unavailable. Base UI still suppresses activation. Code asserting element.disabled should read aria-disabled instead. docs · 7915a71

  • One size vocabulary: xs · sm · md · lg. The tier every component called default is now md, matching the --size-* tokens it was always built from. This is a rename across Button, IconButton, SplitButton, Badge, Input, Textarea, Select, Combobox, Avatar, Card, Item, Empty, Kbd, Dialog, Switch, Checkbox, RadioGroup, Toggle, ToggleGroup, Segmented, Stat, Spinner, StatusIcon, Progress, ProgressIndicator, OTPInput, NumberField, Attachment, ChipInput, Pagination and Sidebar. There is no alias — size="default" is a type error. docs · 7915a71

  • Helper text moved below the control, and Field owns validation feedback. A field's description now renders under the control with the error under that — above the control it pushed the input away from its own label, and a wrapped description put two lines of prose between the two things the eye pairs. The invalid shake moved with it: Input, Checkbox, RadioGroupItem, OTPInput, NumberField and ChipInput no longer take shakeSignal and no longer shake on their own — Field does, for every control it wraps, so Textarea gains the behaviour it never had. A bare <Input aria-invalid> outside a Field still tints its border; wrap it in a Field for the motion, or move shakeSignal onto the Field. docs

    Inline validation announces as role="status", not role="alert". FieldError and FieldInline's error are polite live regions: the person just typed or submitted and is looking at the field, and alert interrupts whatever the screen reader was saying. alert stays reserved for something that arrives without being asked for. Tests asserting getByRole("alert") on a field error should read getByRole("status"). docs

    Form controls keep their pointer events when disabled. disabled:pointer-events-none is gone from Input, Textarea, NumberField, OTPInput, Select, Combobox, ChipInput, Checkbox and RadioGroup, matching the Button contract — an unavailable control must stay hoverable so a Tooltip can say why. cursor-not-allowed and the dim stay; Base UI suppresses activation either way. docs · 9fbeb65

  • MediaPlayerControls moved out of audio-player into its own registry item. It was exported from audio-player even though the video player was its main consumer. Run shadcn add @vegastack/media-player-controls and import from @/components/ui/media-player-controls; video-player declares it as a registry dependency, so a fresh shadcn add @vegastack/video-player pulls it in automatically. docs · aa5fa0d

  • Slider's internals are no longer restyled from outside. Anything reaching into [&_[data-slot=slider-track]] / -indicator / -thumb to build a media rail should pass variant="media" or variant="overlay" with thumb="hover" instead. The internal slots keep their data-slot names, but their rest appearance is now the variant's to decide. docs · aa5fa0d

  • Menu items take tone, not variant. DropdownMenuItem, ContextMenuItem and their checkbox/radio siblings use tone="destructive", matching Button's tone axis. The state attribute moves with the prop: items expose data-tone, not data-variant. docs · 7915a71

  • In-panel search fields are a searchbox. Every panel-search row renders type="search", so ShortcutOverlay's filter (and any other field inside the shared row) exposes the searchbox role rather than a generic textbox. Selecting one by role in a test or script must change with it. docs · 7915a71

  • Sheet runs on Base UI's Drawer. It gains swipe-to-dismiss, snap points (snapPoints / snapPoint / onSnapPointChange pass straight through) and SheetVirtualKeyboardProvider for bottom sheets containing fields. side moves from SheetContent to the Sheet root, because it selects the dismiss gesture as well as the pinned edge, and a side on the content could disagree with the gesture. SheetContent now sizes through size — sm · md · lg · full from the shared --panel-width-* vocabulary replaces className width overrides, and one tier means a width on a left/right sheet and a height on a top/bottom one. Swipe is always an addition: Esc, the close button and a backdrop press still close the panel. docs · 7915a71

  • Toasts run on Base UI, and the toast() API changed with them. sonner is removed from the system and the registry item is renamed sonner → toast. toast() now takes a title plus Base UI's options: action: { label, onClick } becomes actionProps: { children, onClick }, duration becomes timeout (and 0, not Infinity, disables auto-dismiss), and ids are strings. toast.message is gone — it was toast(). toast.custom now renders the toast BODY inside a real toast, so a custom notification keeps stacking, swipe-to-dismiss, Escape and the live region instead of opting out of them. Resolving a loading toast is toast.update(id, …) rather than re-firing with the same id. Toaster loses sonner's props: position values are logical (bottom-end, not bottom-right), expand is gone because the stack expands on hover by design, and offset / mobileOffset / theme are gone — the viewport carries the safe-area insets itself and reads the theme from the cascade. VegaStackProvider always mounts the toast context now: toaster={false} still suppresses the visible viewport — the part that must not mount twice — but the provider toast() writes into is unconditional, so a host rendering its own <Toaster /> shares one queue. @vegastack/design gains TIMINGS.tooltipOpenDelayMs / TIMINGS.tooltipCloseDelayMs, which the provider applies to Tooltip.Provider so every tooltip in an app shares one rhythm. @vegastack/design-tokens gains a third z band, --z-toast (60): the toast viewport mounts with the app provider, before any dialog exists, so DOM order alone would put every later-opened dialog on top of it — and a toast fired from inside a modal must stay visible. Sonner supplied that from its own private z-index, which is why elevation doctrine carried a library-shaped exception; it is now a token with exactly one caller. docs · d5e2de2

  • CountrySelect / RegionSelect internals. COUNTRIES, Country, getCountryByCode, REGIONS_BY_COUNTRY, Region, getRegionsByCountry and hasRegions are no longer exported from country-select / region-select, and region-select-data.ts is gone — import COUNTRIES, REGIONS, getCountryByCode and getRegions from @/lib/geo-data (getRegionsByCountry → getRegions; hasRegions(c) → getRegions(c).length > 0). RegionSelect no longer clears by re-selecting the current state; clearing is the explicit clearable control on the trigger, on by default. Both selects now render a wrapper, so data-slot="country-select" / "region-select" is on the wrapper and the trigger carries the -trigger suffix. docs · 45cde26

  • Table cells wrap by default. Every head and cell carried whitespace-nowrap, so one long value forced the whole table to scroll instead of wrapping at a word boundary. Body cells now use overflow-wrap: anywhere over a --table-cell-min-width floor, and scrolling is reserved for tables that are genuinely wide. Two column shapes opt back out automatically — align="end" figures and the new mono columns — and DataListColumn.nowrap / DataGridColumn.nowrap override the inference either way. A layout that relied on single-line cells should set nowrap: true (or whitespace-nowrap on a raw TableCell). Two further breaks land with it: DataGrid's mobile posture now defaults to "merge" rather than "hidden", so overflow columns stack into the primary cell instead of disappearing — mobile: "hidden" is still available, and when it drops anything the toolbar states "N columns hidden"; and Table's containerClassName is removed, because it did exactly what containerProps.className does. docs · 6d25185

  • useDragReorder and useFileDrop return Announcer, not getLiveRegionProps(). Render <reorder.Announcer /> / <drop.Announcer /> in place of <span {...reorder.getLiveRegionProps()} />. The props-getter shape could not keep the region mounted across an announcement, which is the property that makes it audible. docs · 7915a71

  • Pagination no longer hard-codes its accessible name. It renders a plain <nav> (no role="navigation") and aria-label defaults to "Pagination". A page with more than one pager must name each one — two identically named landmarks are an axe landmark-unique failure. docs · 7915a71

[0.6.0] — August 31, 2026

🔧 Changed components

  • AudioPlayer — rebuilt transport with a responsive, two-line mobile layout. On a wide player it is a single line: play/pause, rewind and forward (±15s), an elapsed / duration readout, a flexible seek, and a fixed-width tappable speed control that cycles the playback rates (1x → 1.25x → 1.5x → 2x → 0.5x). Audio carries no volume control — mute stays on the M key. On a narrow (mobile-width) player it reflows to two lines: the seek bar with elapsed and duration pinned to either edge in a smaller font on top, and a centred play/pause flanked by rewind and forward on the bottom, with an optional transcript control (new onTranscriptClick, lucide audio-lines) on the leading edge and the speed control on the trailing edge. The chrome and progress fill move from the brand-bold primary to a subdued secondary emphasis, and the waveform seek now fills continuously instead of one bar at a time. VideoPlayer's overlay controls are unchanged. docs · 43eb359

📦 npm

  • @vegastack/design → 0.3.2 — refreshes the shipped Audio Player skill reference for the reworked transport and its two-line mobile layout. No runtime code changed.
  • @vegastack/design-tokens is unchanged. The design-system registry (@vegastack/ui) bumps 0.5.0 → 0.6.0.

[0.5.0] — August 28, 2026

🧩 New components

  • AudioPlayer, VideoPlayer — media players sharing one custom transport: grouped play / seek / mute / settings controls, a 128-bar waveform seek on audio, a smoothly expanding video progress rail, a contained volume rocker, larger overlay actions, state-aware fullscreen, and keyboard shortcuts. docs · 334cb4c

🔧 Changed components

  • ProgressIndicator — new value-display variants render the percentage inline or contained within the glyph. 334cb4c
  • Sidebar — the desktop sidebar and its footer now stay pinned to the viewport while page and navigation content scrolls. docs · 334cb4c
  • Switch — the invalid state no longer applies a destructive track border; aria-invalid remains the semantic cue and the wrapping Field owns the error message. Consumer-visible restyle. docs · 334cb4c

🐛 Fixed

  • DatePicker, ComparisonMatrix, PricingSection, Tabs, FieldInline — manual-QA remediation across range selection, compact navigation, overflow handling, and responsive pricing and comparison layouts. docs · 334cb4c

🛠 CLI & tooling

  • Cross-engine smoke and the full three-engine suite now treat WebKit as host-conditional — it runs where it can launch and is skipped with an auditable banner where it cannot (macOS 26.6.2 cannot launch Playwright's WebKit). Chromium + Firefox coverage is never blocked, and a Mac on macOS 26.2–26.5 still enforces WebKit. Control with WEBKIT_LANE=auto|off|require. b6b1bd0

📚 Docs

  • Improved the published examples for dropzones, menus, charts, mobile shells, and the dashboard starter. 334cb4c

📦 npm

  • @vegastack/design → 0.3.1 — ships the regenerated component-roster skill reference (the two new players, the ProgressIndicator variant note, updated counts). No runtime code changed.
  • @vegastack/design-tokens is unchanged. The design-system registry (@vegastack/ui) bumps 0.4.1 → 0.5.0.

[0.4.1] — July 28, 2026

🐛 Fixed

  • Stepper — horizontal step content is now width-constrained so long labels truncate instead of overlapping adjacent steps at the 320px responsive contract width. docs

[0.4.0] — July 27, 2026

🧩 New components

  • ActionBar, ChipInput, EditableCell, FilterBuilder (filter-bar-managed), NumberField, ShortcutOverlay, Stepper, and Timeline — eight additions from the CRM commission (plan 2026-07-26), each with complete docs, state coverage, accessibility tests, and registry integrity metadata. components
  • SortableList, Board, Dropzone, and DataGrid — the four dependency-gated commissions, unblocked by MK's 2026-07-27 sanction of four engines (Pragmatic drag and drop, react-dropzone, TanStack Table, TanStack Virtual). Reorderable rows with a lossless keyboard Move menu; a kanban board with cross-column card movement and per-move lock reasons; a paste-capable file drop surface; and the full-parity grid — multi-key sort, column picker with responsive revelation, collapsible grouping, keyboard-continuous load-more, opt-in virtualization, and APG cell navigation with inline editing. components
  • useListNav, usePlatform, useDragReorder, and useFileDrop — four new registry hooks: roving-tabindex keyboard navigation for lists and grids (RTL-aware arrows, homeEndScope, overlay suppression); SSR-safe platform detection ({ os, isTouch }) for Kbd's modifier rewriting and touch gating; the reorder seam over Pragmatic drag and drop (pointer drags, commit-per-step keyboard move mode, server-refusable moves); and the file-drop seam over react-dropzone (drop + paste + browse, typed rejection reasons). guide

🔧 Changed components

  • Kbd — mac modifier glyphs now pair the visual glyph with visually hidden spoken names ("Command", "Option", …), so screen readers no longer hear "place of interest sign" or nothing. docs
  • Table — new containerClassName / containerProps forwarded to the data-slot="table-container" scroll wrapper, the attachment point for sticky headers, fixed-height viewports, and virtualizers. docs
  • DataList — DataListProps now extends TableProps (the spreadsheet voice and container hooks type-check), columns gain a per-cell cellClassName hook, and render receives an optional third DataListCellContext argument. All additive. docs
  • FieldInline — additive controlled edit mode (editing / onEditingChange) and a tabIndex override for the display element, so EditableCell and grid hosts compose it instead of forking it. No behaviour change for existing consumers.
  • ColorPicker and EmojiPicker — internal refactor onto the shared useListNav hook; the API and visuals are unchanged, with one behavioural correction riding along: EmojiPicker's horizontal arrow keys become RTL-aware, matching ColorPicker. Safe to take or skip. docs
  • Button — the active:translate-y-px press nudge is removed from buttonVariants, so every component composing Button (IconButton, SplitButton, toolbars, pickers) loses it in one place. Pressed feedback is now colour-only via the existing active:bg-* states, and the motion foundations doctrine records press-motion as a deliberate exclusion. docs

🛠 CLI & tooling

  • verify-registry-deps gains a fail-closed npm-range check: a registry item pin the installed version cannot satisfy now fails registry:build instead of passing silently.

📚 Docs

  • The npm dependency pins the installed versions could not satisfy are reconciled to packages/ui/package.json: lucide-react was declared at both ^1.20.0 and ^0.525.0 across a major boundary (41 items), and @shadcn/react at ^0.1.0 against an installed ^0.2.1.
  • table.mdx no longer claims the Table parts add no props; data-list.mdx documents that a column render is invoked as a plain function (hooks belong in a returned component element).
  • The docs homepage and 404 page now pass nativeButton={false} to every Button rendered as a link, matching the Base UI contract for non-button render targets; button.mdx and the bundled design-system skill document the pattern, and motion.mdx records the press-nudge removal.

[0.3.0] — July 24, 2026

🧩 New components

  • AnnouncementBanner, CodeBlock, ComparisonMatrix, NavigationMenu, OnboardingChecklist, PricingSection, PropertyList, RuledBand, Segmented, Stat, TagGroup, and ToolCallChip — twelve production-ready additions with complete docs, state coverage, accessibility tests, responsive previews, and registry integrity metadata. components · 6c60d53

🔧 Changed components

  • MarkdownView — remote image sources are now same-origin by default; explicitly allow trusted remote origins with allowedImageOrigins. The renderer and copied-file verifier also enforce contained paths and digest-pinned post-write checks. docs · 6c60d53
  • Provider and all portal-owning components now share an explicit theme-scope contract, while server-safe modules are verified under React's real react-server condition. docs · 6c60d53

🛠 CLI & tooling

  • vegastack-design skills install now bundles and safely installs the four public VegaStack agent skills for Claude Code and Codex, with atomic conflict handling, symlink containment, dry-run, and single-surface options. guide · 6c60d53
  • vegastack-design check-updates now verifies installed file bodies and the complete target set; matching provenance headers can no longer hide edited content, and removed or renamed targets fail --fail-on-update. guide · 6c60d53
  • Registry builds now prune stale artifacts, reconcile exact authority/index/manifest sets, and prove real shadcn consumption against locally packed npm artifacts before release. integrity · 6c60d53

📦 npm

  • @vegastack/design 0.2.0 — adds bundled public agent skills, the skills installer, CommonJS-compatible exports, and the explicit ./theme-scope client subpath while preserving a server-safe root. 6c60d53
  • @vegastack/design-tokens 0.2.0 — publishes the unified DTCG-backed doctrine, strong type and effect roles, exact dark/marketing parity, and verified ESM/CommonJS/package exports. 6c60d53
  • Private @vegastack/ui advances to 0.3.0 to stamp the 538-item registry; it remains private and is never published to npm. 6c60d53

📚 Docs

  • The public design doctrine, component contracts, homepage catalog, API reference, agent guidance, internal SSO corpus, metadata, and release/cutover runbooks now derive from current machine authorities and build successfully in both private and public visibility modes. design doctrine · 6c60d53

🐛 Fixed

  • State-specific accessibility coverage now exercises open, expanded, selected, disabled, highlighted, loading, empty, error, success, and removable states across the applicable component contracts; all token contrast pairs pass WCAG 2.2 AA gates in both themes. accessibility · 6c60d53
  • Release and deploy workflows now use the reviewed-merge/manual-dispatch approval model supported by the private GitHub Team repository, retain isolated OIDC and repository secrets, and probe the Cloudflare Access boundary after deployment. 6c60d53

⚠️ Breaking

  • vegastack-design verify --post-write now requires --expected-integrity; use the exact integrity-pinned command printed by the pre-write verification. MarkdownView also requires an explicit allowlist for cross-origin images. verification · MarkdownView · 6c60d53

[0.2.0] — July 19, 2026

🧩 New components

  • Provider — the app-root wrapper (theme, toasts, tooltip coordination, text direction); install once via shadcn add @vegastack/provider, composes the sonner Toaster item. docs · c7de692

🔧 Changed components

  • Checkbox, Switch — Story explorer controls narrowed to human-usable props (the raw Base UI prop graph serialized to ~24 MB per page, over Cloudflare's 25 MiB asset limit). No API change. checkbox · switch · 45c7cf8
  • dashboard-01 (block) — component files now target app/dashboard/components/ (the page's relative imports were broken on clean installs) and page.tsx ships the default export Next requires for route files. ac6288a
  • use-animation-replay (hook) — stray duplicate header line removed (caused a false drift flag in check-updates). ac6288a

🛠 CLI & tooling

  • vegastack-design check-updates is header-optional: the shadcn CLI strips provenance headers on copy-in, so copies are now identified by filename against the registry index and compared by alias-normalized content. New ≈ drift status (differs — upstream update or local edits); --fail-on-update fails on update + drift. guide · ac6288a
  • Release workflow is path-routed: the container pixel gate runs only when component-visual code changes; the release job only when something is publishable. e8a8450

📦 npm

  • @vegastack/design 0.1.1 — tw-animate-css is a real dependency (was an optional peer that pnpm never installed, breaking every fresh consumer build at preset.css). Published via npm OIDC trusted publishing — the first fully-automated release. 9532d42
  • @vegastack/design-tokens stays 0.1.0 (no changes).

📚 Docs

  • New Guides section — Quickstart, Registry access & auth, Working with components, Provider setup, Theming, Production checklist, Troubleshooting. Every command executed for real against the reference starter before being written down; 29 adversarial-review findings applied. 7307231 · 11836b6
  • Sidebar sections labeled; doc pages carry a server-rendered "Last updated" stamp. a323cba
  • Lifecycle guides for both audiences: Internal projects (the two-speed update model: npm = global on dep bump, registry = frozen until reviewed pull; the token-ordering trap) and Client projects (delivery model, handover checklist, zero-lock-in dependency table). Repo also gains a maintainer README. The internal guide is now SSO-only and intentionally absent from the public corpus; the client-project guide remains public.

[0.1.0] — July 18, 2026

🧩 New components

  • Initial release: 83 components, 439 animated-icon items, 2 hooks (use-mobile, use-animation-replay), and the dashboard-01 starter block — all Base UI + Tailwind v4, semantic-token-only, WCAG 2.1 AA, distributed as copy-in via the private registry at design.vegastack.com/r/* (Cloudflare Access service-token auth, Sigstore-signed manifest). components · 8a5bb2a

📦 npm

  • @vegastack/design 0.1.0 — cn(), the icon runtime (./icons), the Tailwind v4 preset (./preset.css), token CSS re-exports, and the vegastack-design CLI.
  • @vegastack/design-tokens 0.1.0 — the zero-dependency DTCG token contract (theme.css, base.css, utilities.css, tokens.json).

📚 Docs

  • At the 0.1.0 release, the Fumadocs showcase had 99 pages, live previews rendering the real shipped source, and complete desktop/mobile VRT coverage; it was deployed behind Cloudflare Access SSO. Current route and baseline counts are verified dynamically rather than inferred from this historical release entry. 8a5bb2a

How you receive updates

Two delivery paths, because the system is hybrid — the full operating model per audience: Internal projects · Client projects.

Packages (@vegastack/design + @vegastack/design-tokens) are normal npm dependencies — bump them with your package manager when a new version publishes. They share a Changesets linked group: releases that touch both give them the same version; otherwise versions may differ (e.g. design 0.1.1 alongside design-tokens 0.1.0 — nothing changed in the tokens).

Components are pulled, never pushed. They are copied into your repo by the shadcn CLI, so an upstream change does not silently rewrite your files. You decide when to re-pull, review the diff, then overwrite.

# 1. See what changed upstream (content comparison against the live registry)
vegastack-design check-updates

# 2. Review the diff for a component before taking it
shadcn add @vegastack/button --diff

# 3. Pull the update in, overwriting your copy
shadcn add @vegastack/button --overwrite

Status is by content, not version number

check-updates identifies your copies by filename against the registry index and compares alias-normalized content (a provenance header, when present, is only a fast-path pin). A component reads up to date when the global version bumped but that component's content didn't change — you only re-pull what actually moved. ≈ differs means the bytes diverged — an upstream update or your local edits; add --diff disambiguates.

Every registry item carries a meta.integrity SHA-256 plus a provenance header in its registry content, and the consume flow supports a fail-closed verify (vegastack-design verify). If a hash doesn't match, the add is rejected rather than silently trusted.

Do
Run check-updates, then shadcn add --diff before --overwrite so you review every incoming change.
Don't
Assume a global version bump means a component changed — status is by content, not the version number.

On this page

[0.26.6] — October 30, 2026📦 npm🐛 Fixed[0.26.5] — October 30, 2026🛠 CLI & tooling📦 npm[0.26.4] — October 30, 2026📦 npm🐛 Fixed[0.26.3] — October 30, 2026📦 npm📚 Docs[0.26.2] — October 30, 2026🛠 CLI & tooling📦 npm🐛 Fixed[0.26.1] — October 30, 2026🧩 New components🔧 Changed components📦 npm⚠️ Breaking[0.26.0] — October 30, 2026🧩 New components🔧 Changed components📦 npm[0.25.105] — October 29, 2026📦 npm🐛 Fixed[0.25.104] — October 29, 2026🔧 Changed components📦 npm🐛 Fixed⚠️ Breaking[0.25.103] — October 29, 2026🗑 Removed / renamed📦 npm[0.25.102] — October 29, 2026📦 npm🐛 Fixed[0.25.101] — October 29, 2026📦 npm🐛 Fixed[0.25.100] — October 29, 2026🧩 New components📦 npm[0.25.99] — October 29, 2026📦 npm🐛 Fixed[0.25.98] — October 29, 2026📦 npm🐛 Fixed[0.25.97] — October 28, 2026🧩 New components📦 npm[0.25.96] — October 28, 2026🧩 New components🔧 Changed components📦 npm[0.25.95] — October 28, 2026📦 npm🐛 Fixed[0.25.94] — October 28, 2026📦 npm🐛 Fixed[0.25.93] — October 28, 2026📦 npm🐛 Fixed[0.25.92] — October 27, 2026🧩 New components🔧 Changed components🛠 CLI & tooling📦 npm🐛 Fixed[0.25.91] — October 27, 2026🔧 Changed components📦 npm🐛 Fixed⚠️ Breaking[0.25.90] — October 27, 2026🧩 New components🔧 Changed components📦 npm🐛 Fixed[0.25.89] — October 27, 2026🧩 New components🔧 Changed components📦 npm📚 Docs[0.25.88] — October 27, 2026🧩 New components📦 npm[0.25.87] — October 27, 2026🧩 New components🔧 Changed components🛠 CLI & tooling📦 npm🐛 Fixed⚠️ Breaking[0.25.86] — October 27, 2026🧩 New components📦 npm[0.25.85] — October 27, 2026🔧 Changed components📦 npm[0.25.84] — October 26, 2026📦 npm🐛 Fixed[0.25.83] — October 26, 2026📦 npm🐛 Fixed[0.25.82] — October 26, 2026📦 npm🐛 Fixed[0.25.81] — October 26, 2026🔧 Changed components📦 npm[0.25.80] — October 26, 2026📦 npm🐛 Fixed[0.25.79] — October 26, 2026📦 npm🐛 Fixed[0.25.78] — October 26, 2026📦 npm🐛 Fixed[0.25.77] — October 25, 2026📦 npm🐛 Fixed[0.25.76] — October 25, 2026📦 npm🐛 Fixed[0.25.75] — October 25, 2026📦 npm🐛 Fixed[0.25.74] — October 24, 2026📦 npm⚠️ Breaking[0.25.73] — October 24, 2026🔧 Changed components📦 npm[0.25.72] — October 24, 2026🧩 New components📦 npm[0.25.71] — October 24, 2026🧩 New components📦 npm[0.25.70] — October 24, 2026🧩 New components📦 npm[0.25.69] — October 24, 2026🧩 New components📦 npm[0.25.68] — October 23, 2026🔧 Changed components📦 npm[0.25.67] — October 23, 2026📦 npm🐛 Fixed[0.25.66] — October 21, 2026🧩 New components📦 npm📚 Docs[0.25.65] — October 21, 2026🧩 New components📦 npm[0.25.64] — October 21, 2026🔧 Changed components📦 npm[0.25.63] — October 21, 2026🧩 New components🔧 Changed components📦 npm[0.25.62] — October 21, 2026🔧 Changed components📦 npm🐛 Fixed[0.25.61] — October 21, 2026🔧 Changed components📦 npm[0.25.60] — October 21, 2026🔧 Changed components📦 npm[0.25.59] — October 21, 2026🧩 New components🔧 Changed components📦 npm[0.25.58] — October 21, 2026🔧 Changed components📦 npm[0.25.57] — October 20, 2026🔧 Changed components📦 npm[0.25.56] — October 20, 2026📦 npm🐛 Fixed[0.25.55] — October 20, 2026🔧 Changed components📦 npm[0.25.54] — October 20, 2026📦 npm🐛 Fixed[0.25.53] — October 20, 2026📦 npm🐛 Fixed[0.25.52] — October 20, 2026🔧 Changed components📦 npm[0.25.51] — October 20, 2026🔧 Changed components📦 npm[0.25.50] — October 20, 2026📦 npm🐛 Fixed[0.25.49] — October 20, 2026📦 npm🐛 Fixed[0.25.48] — October 20, 2026🔧 Changed components📦 npm[0.25.47] — October 20, 2026🧩 New components📦 npm⚠️ Breaking[0.25.46] — October 20, 2026📦 npm🐛 Fixed[0.25.45] — October 20, 2026🧩 New components📦 npm[0.25.44] — October 19, 2026🧩 New components📦 npm🐛 Fixed[0.25.43] — October 19, 2026🧩 New components🛠 CLI & tooling📦 npm🐛 Fixed[0.25.42] — October 14, 2026📦 npm🐛 Fixed[0.25.41] — October 14, 2026🔧 Changed components📦 npm[0.25.40] — October 14, 2026🔧 Changed components📦 npm[0.25.39] — October 14, 2026🧩 New components📦 npm[0.25.38] — October 14, 2026🧩 New components📦 npm📚 Docs[0.25.37] — October 14, 2026🧩 New components📦 npm[0.25.36] — October 14, 2026🔧 Changed components📦 npm🐛 Fixed[0.25.35] — October 14, 2026🧩 New components🔧 Changed components📦 npm⚠️ Breaking[0.25.34] — October 14, 2026📦 npm🐛 Fixed[0.25.33] — October 14, 2026📦 npm🐛 Fixed[0.25.32] — October 14, 2026📦 npm🐛 Fixed[0.25.31] — October 14, 2026🧩 New components📦 npm⚠️ Breaking[0.25.30] — October 14, 2026🧩 New components📦 npm⚠️ Breaking[0.25.29] — October 14, 2026🧩 New components📦 npm[0.25.28] — October 14, 2026🧩 New components🔧 Changed components📦 npm[0.25.27] — October 13, 2026🔧 Changed components📦 npm[0.25.26] — October 13, 2026🔧 Changed components📦 npm🐛 Fixed[0.25.25] — October 12, 2026📦 npm🐛 Fixed[0.25.24] — October 12, 2026🔧 Changed components📦 npm[0.25.23] — October 12, 2026📦 npm📚 Docs[0.25.22] — October 12, 2026🧩 New components🔧 Changed components📦 npm[0.25.21] — October 12, 2026🔧 Changed components📦 npm[0.25.20] — October 12, 2026📦 npm🐛 Fixed[0.25.19] — October 11, 2026🧩 New components🔧 Changed components📦 npm[0.25.18] — October 11, 2026🧩 New components📦 npm[0.25.17] — October 11, 2026🔧 Changed components📦 npm[0.25.16] — October 11, 2026📦 npm🐛 Fixed[0.25.15] — October 11, 2026📦 npm🐛 Fixed[0.25.14] — October 11, 2026🧩 New components📦 npm[0.25.13] — October 11, 2026🧩 New components📦 npm[0.25.12] — October 11, 2026🔧 Changed components📦 npm[0.25.11] — October 10, 2026🧩 New components📦 npm[0.25.10] — October 10, 2026📦 npm🐛 Fixed[0.25.9] — October 10, 2026📦 npm🐛 Fixed[0.25.8] — October 10, 2026🔧 Changed components📦 npm[0.25.7] — October 9, 2026🧩 New components🔧 Changed components📦 npm[0.25.6] — October 9, 2026🔧 Changed components📦 npm[0.25.5] — October 9, 2026🔧 Changed components📦 npm[0.25.4] — October 9, 2026🔧 Changed components🗑 Removed / renamed📦 npm[0.25.3] — October 9, 2026📦 npm🐛 Fixed[0.25.2] — October 9, 2026🧩 New components📦 npm🐛 Fixed[0.25.1] — October 9, 2026🧩 New components📦 npm[0.25.0] — October 8, 2026🧩 New components🔧 Changed components📦 npm[0.24.5] — October 8, 2026📦 npm🐛 Fixed[0.24.4] — October 8, 2026🛠 CLI & tooling📦 npm🐛 Fixed[0.24.3] — October 7, 2026🛠 CLI & tooling📦 npm[0.24.2] — October 7, 2026🔧 Changed components📦 npm[0.24.1] — October 7, 2026📦 npm🐛 Fixed[0.24.0] — October 6, 2026🔧 Changed components📦 npm🐛 Fixed[0.23.128] — October 6, 2026🛠 CLI & tooling📦 npm⚠️ Breaking[0.23.127] — October 6, 2026📦 npm🐛 Fixed[0.23.126] — October 6, 2026📦 npm🐛 Fixed[0.23.125] — October 6, 2026🔧 Changed components📦 npm[0.23.124] — October 5, 2026🧩 New components📦 npm[0.23.123] — October 4, 2026🧩 New components📦 npm[0.23.122] — October 4, 2026🔧 Changed components📦 npm[0.23.121] — October 4, 2026🔧 Changed components📦 npm[0.23.120] — October 4, 2026🧩 New components📦 npm[0.23.119] — October 4, 2026🧩 New components📦 npm[0.23.118] — October 4, 2026📦 npm🐛 Fixed[0.23.117] — October 3, 2026📦 npm🐛 Fixed[0.23.116] — October 3, 2026🧩 New components📦 npm[0.23.115] — October 3, 2026📦 npm🐛 Fixed[0.23.114] — October 2, 2026🔧 Changed components📦 npm[0.23.113] — October 2, 2026🔧 Changed components📦 npm[0.23.112] — October 1, 2026🧩 New components📦 npm[0.23.111] — October 1, 2026📦 npm🐛 Fixed[0.23.110] — October 1, 2026🔧 Changed components📦 npm[0.23.109] — October 1, 2026📦 npm🐛 Fixed[0.23.108] — October 1, 2026📦 npm🐛 Fixed[0.23.107] — October 1, 2026📦 npm🐛 Fixed[0.23.106] — October 1, 2026📦 npm🐛 Fixed[0.23.105] — October 1, 2026🔧 Changed components📦 npm[0.23.104] — October 1, 2026🔧 Changed components📦 npm[0.23.103] — October 1, 2026📦 npm🐛 Fixed[0.23.102] — October 1, 2026🧩 New components📦 npm[0.23.101] — October 1, 2026🔧 Changed components📦 npm🐛 Fixed[0.23.100] — October 1, 2026🔧 Changed components📦 npm[0.23.99] — October 1, 2026🔧 Changed components📦 npm[0.23.98] — October 1, 2026🔧 Changed components📦 npm[0.23.97] — October 1, 2026🔧 Changed components📦 npm[0.23.96] — October 1, 2026🔧 Changed components📦 npm[0.23.95] — October 1, 2026🔧 Changed components📦 npm[0.23.94] — October 1, 2026📦 npm📚 Docs[0.23.93] — September 30, 2026🔧 Changed components📦 npm[0.23.92] — September 30, 2026🔧 Changed components📦 npm[0.23.91] — September 30, 2026🔧 Changed components📦 npm[0.23.90] — September 30, 2026🧩 New components📦 npm[0.23.89] — September 30, 2026🔧 Changed components📦 npm[0.23.88] — September 30, 2026🔧 Changed components📦 npm[0.23.87] — September 30, 2026📦 npm🐛 Fixed[0.23.86] — September 30, 2026📦 npm🐛 Fixed[0.23.85] — September 30, 2026📦 npm🐛 Fixed[0.23.84] — September 30, 2026📦 npm🐛 Fixed[0.23.83] — September 30, 2026📦 npm[0.23.82] — September 30, 2026📦 npm🐛 Fixed[0.23.81] — September 30, 2026🧩 New components📦 npm🐛 Fixed[0.23.80] — September 30, 2026🧩 New components📦 npm[0.23.79] — September 30, 2026🔧 Changed components📦 npm[0.23.78] — September 30, 2026📦 npm🐛 Fixed[0.23.77] — September 30, 2026📦 npm🐛 Fixed[0.23.76] — September 30, 2026🔧 Changed components📦 npm[0.23.75] — September 30, 2026🧩 New components📦 npm[0.23.74] — September 30, 2026🧩 New components📦 npm[0.23.73] — September 29, 2026🧩 New components🔧 Changed components📦 npm[0.23.72] — September 29, 2026🔧 Changed components🛠 CLI & tooling📦 npm[0.23.71] — September 28, 2026🔧 Changed components📦 npm[0.23.70] — September 28, 2026🧩 New components📦 npm[0.23.69] — September 28, 2026🧩 New components🔧 Changed components📦 npm[0.23.68] — September 28, 2026🧩 New components📦 npm[0.23.67] — September 28, 2026🔧 Changed components📦 npm[0.23.66] — September 28, 2026📦 npm🐛 Fixed[0.23.65] — September 28, 2026🔧 Changed components📦 npm🐛 Fixed[0.23.64] — September 27, 2026📦 npm🐛 Fixed[0.23.63] — September 27, 2026📦 npm🐛 Fixed[0.23.62] — September 27, 2026🧩 New components📦 npm[0.23.61] — September 27, 2026🧩 New components🔧 Changed components📦 npm[0.23.60] — September 27, 2026📦 npm🐛 Fixed[0.23.59] — September 27, 2026🔧 Changed components📦 npm[0.23.58] — September 27, 2026📦 npm🐛 Fixed[0.23.57] — September 27, 2026🔧 Changed components📦 npm[0.23.56] — September 27, 2026🔧 Changed components📦 npm[0.23.55] — September 27, 2026🔧 Changed components📦 npm[0.23.54] — September 27, 2026🧩 New components🔧 Changed components📦 npm🐛 Fixed[0.23.53] — September 27, 2026📦 npm🐛 Fixed[0.23.52] — September 27, 2026📦 npm🐛 Fixed[0.23.51] — September 27, 2026📦 npm🐛 Fixed[0.23.50] — September 27, 2026📦 npm🐛 Fixed[0.23.49] — September 27, 2026📦 npm🐛 Fixed[0.23.48] — September 27, 2026🔧 Changed components📦 npm[0.23.47] — September 27, 2026🔧 Changed components📦 npm[0.23.46] — September 27, 2026🔧 Changed components📦 npm[0.23.45] — September 27, 2026🔧 Changed components📦 npm[0.23.44] — September 27, 2026🔧 Changed components📦 npm[0.23.43] — September 26, 2026🔧 Changed components📦 npm[0.23.42] — September 26, 2026🔧 Changed components📦 npm[0.23.41] — September 26, 2026🔧 Changed components📦 npm[0.23.40] — September 26, 2026🔧 Changed components📦 npm[0.23.39] — September 26, 2026📦 npm🐛 Fixed[0.23.38] — September 26, 2026🔧 Changed components📦 npm[0.23.37] — September 26, 2026🔧 Changed components📦 npm[0.23.36] — September 26, 2026🔧 Changed components📦 npm[0.23.35] — September 26, 2026🔧 Changed components📦 npm[0.23.34] — September 26, 2026🔧 Changed components📦 npm[0.23.33] — September 26, 2026🔧 Changed components📦 npm[0.23.32] — September 26, 2026🔧 Changed components📦 npm[0.23.31] — September 26, 2026🔧 Changed components📦 npm[0.23.30] — September 26, 2026🔧 Changed components📦 npm[0.23.29] — September 26, 2026🔧 Changed components📦 npm[0.23.28] — September 26, 2026🔧 Changed components📦 npm[0.23.27] — September 26, 2026🔧 Changed components📦 npm[0.23.26] — September 26, 2026🔧 Changed components📦 npm[0.23.25] — September 25, 2026🔧 Changed components📦 npm[0.23.24] — September 25, 2026🔧 Changed components📦 npm[0.23.23] — September 25, 2026🔧 Changed components📦 npm[0.23.22] — September 25, 2026🔧 Changed components📦 npm[0.23.21] — September 25, 2026🔧 Changed components📦 npm[0.23.20] — September 25, 2026🔧 Changed components📦 npm[0.23.19] — September 25, 2026🔧 Changed components📦 npm[0.23.18] — September 25, 2026📦 npm📚 Docs[0.23.17] — September 25, 2026🔧 Changed components📦 npm[0.23.16] — September 25, 2026🔧 Changed components📦 npm[0.23.15] — September 25, 2026🔧 Changed components📦 npm[0.23.14] — September 25, 2026🔧 Changed components📦 npm[0.23.13] — September 25, 2026🔧 Changed components📦 npm[0.23.12] — September 25, 2026🔧 Changed components📦 npm[0.23.11] — September 25, 2026🔧 Changed components📦 npm[0.23.10] — September 25, 2026🔧 Changed components📦 npm[0.23.9] — September 25, 2026🔧 Changed components📦 npm[0.23.8] — September 25, 2026🔧 Changed components📦 npm[0.23.7] — September 25, 2026🔧 Changed components📦 npm[0.23.6] — September 25, 2026🔧 Changed components📦 npm[0.23.5] — September 25, 2026🔧 Changed components📦 npm[0.23.4] — September 25, 2026🔧 Changed components📦 npm[0.23.3] — September 25, 2026🔧 Changed components📦 npm[0.23.2] — September 25, 2026🔧 Changed components📦 npm[0.23.1] — September 25, 2026🔧 Changed components📦 npm🐛 Fixed[0.23.0] — September 25, 2026🔧 Changed components📦 npm[0.22.0] — September 25, 2026🔧 Changed components📦 npm[0.21.2] — September 25, 2026📦 npm🐛 Fixed[0.21.1] — September 25, 2026📦 npm🐛 Fixed[0.21.0] — September 25, 2026🧩 New components🔧 Changed components📦 npm📚 Docs🐛 Fixed[0.20.0] — September 25, 2026🧩 New components🔧 Changed components🛠 CLI & tooling📦 npm📚 Docs🐛 Fixed[0.19.0] — September 24, 2026🧩 New components🔧 Changed components📦 npm📚 Docs🐛 Fixed[0.18.0] — September 24, 2026📦 npm🐛 Fixed[0.17.1] — September 24, 2026📦 npm📚 Docs[0.17.0] — September 24, 2026🗑 Removed / renamed📦 npm📚 Docs[0.16.1] — September 24, 2026📦 npm🐛 Fixed[0.16.0] — September 24, 2026🔧 Changed components📦 npm[0.15.0] — September 23, 2026🔧 Changed components📦 npm[0.14.0] — September 23, 2026🧩 New components🛠 CLI & tooling📦 npm[0.13.0] — September 23, 2026🧩 New components🔧 Changed components🛠 CLI & tooling📦 npm📚 Docs🐛 Fixed[0.12.2] — September 23, 2026🛠 CLI & tooling📦 npm[0.12.1] — September 23, 2026🛠 CLI & tooling📦 npm📚 Docs[0.12.0] — September 22, 2026🧩 New components🔧 Changed components🛠 CLI & tooling📦 npm⚠️ Breaking[0.11.3] — September 22, 2026📦 npm📚 Docs[0.11.2] — September 22, 2026🔧 Changed components📦 npm🐛 Fixed[0.11.1] — September 22, 2026📦 npm🐛 Fixed[0.11.0] — September 22, 2026🧩 New components🔧 Changed components📦 npm📚 Docs🐛 Fixed[0.10.0] — September 19, 2026🛠 CLI & tooling📦 npm⚠️ Breaking[0.9.1] — September 15, 2026🧩 New components🛠 CLI & tooling📦 npm[0.9.0] — September 15, 2026📦 npm🐛 Fixed[0.8.2] — September 12, 2026📦 npm🐛 Fixed[0.8.1] — September 12, 2026🛠 CLI & tooling📦 npm[0.8.0] — September 12, 2026📦 npm🐛 Fixed[0.7.5] — September 11, 2026📦 npm🐛 Fixed[0.7.4] — September 11, 2026📦 npm🐛 Fixed[0.7.3] — September 11, 2026📦 npm🐛 Fixed[0.7.2] — September 11, 2026📦 npm🐛 Fixed[0.7.1] — September 11, 2026📦 npm🐛 Fixed[0.7.0] — September 11, 2026🧩 New components🔧 Changed components🗑 Removed / renamed🛠 CLI & tooling📦 npm📚 Docs🐛 Fixed⚠️ Breaking[0.6.0] — August 31, 2026🔧 Changed components📦 npm[0.5.0] — August 28, 2026🧩 New components🔧 Changed components🐛 Fixed🛠 CLI & tooling📚 Docs📦 npm[0.4.1] — July 28, 2026🐛 Fixed[0.4.0] — July 27, 2026🧩 New components🔧 Changed components🛠 CLI & tooling📚 Docs[0.3.0] — July 24, 2026🧩 New components🔧 Changed components🛠 CLI & tooling📦 npm📚 Docs🐛 Fixed⚠️ Breaking[0.2.0] — July 19, 2026🧩 New components🔧 Changed components🛠 CLI & tooling📦 npm📚 Docs[0.1.0] — July 18, 2026🧩 New components📦 npm📚 DocsHow you receive updatesStatus is by content, not version number