3 hours ago
astryx

v0.7.2

Astryx 0.7.2 updates the stable @astryxdesign/* package set to this version.

OLD_VERSION=0.7.1  # replace with the Core version your project uses today
npm install @astryxdesign/core@0.7.2 @astryxdesign/cli@0.7.2  # plus every other stable @astryxdesign/* package you use, at 0.7.2
npx @astryxdesign/cli upgrade --from "$OLD_VERSION" --apply --path .

@astryxdesign/core

New Components

  • Promote Drawer and DrawerHeader from the canary-only Lab package to Core, imported from @astryxdesign/core/Drawer or the package root. The stable package now ships the full-height side panel with modal and non-modal presentation, the shared dismissal stack, Dialog's purpose dismissal policy, a DrawerHeader whose close button appears when given onOpenChange, and Dialog-style container padding (--spacing-4 by default, padding prop, or a theme's drawer padding), plus Core documentation and runnable examples. Canary users importing from @astryxdesign/lab switch to @astryxdesign/core/Drawer.

    Also released in @astryxdesign/cli.

New Features

  • An integration can set the command its users run the CLI with: a new cliCommand field in astryx.integration.*, such as cliCommand: 'acme astryx'. In a project that loads that integration, every command the CLI prints starts with it instead of the package-manager prefix (npx astryx, pnpm exec astryx): next-step hints, errors, suggestions, Doctor fixes, --help, the setup reminder, the agent-docs block, and doc text. If two loaded integrations set different commands, neither is used, and the new cli-command check in astryx doctor warns. An invalid cliCommand is ignored with an invalid_cli_command warning; the rest of the manifest still loads. Docs, including an integration's, are now written with bare astryx ... commands, and astryx docs prefixes them for the project when it prints them. JSON output keeps every command bare, as before. The install-time setup reminder from @astryxdesign/core and @astryxdesign/cli names the command the project's package manager runs, instead of always npx @astryxdesign/cli.

    Also released in @astryxdesign/cli.

  • Adds the @astryx.diffViewer.* keys to the en catalog so the lab DiffViewer reads its file-status badges, line counts, line-number button labels, per-line "Added"/"Removed" labels, and empty-state messages from the translation runtime. No existing string changes; locales without a translation fall back to English.

  • Item accepts align="first-line", which centers each start and end slot on the label's first line. A small status dot, radio, checkbox, or timestamp beside a wrapping label now stays level with the first line, where center drops it to the middle of the row and start leaves it above the line. A slot taller than the first line moves the text down rather than leaving the row, and a one-line row with no description looks the same as center. ListItem, RadioListItem, and CheckboxListItem accept the same align and pass it to their row. Themes can style these rows through the item target's align axis. The default stays center, so existing rows don't change.

  • i18n catalog: add the English messages used by the Lab CommentComposer for its input, image upload, inline quote, emoji action, cancel action, and retry affordance.

  • i18n catalog: add the English messages used by the Lab CommentThread for replies, reactions, edit history, resolution, errors, comment actions, and the unsaved-draft warning it announces when a dismissal would discard a reply or edit.

  • i18n catalog: add the English messages used by the Lab Comments panel for its region, empty/loading states, pagination, and posting errors.

  • i18n catalog: add the English messages used by the Lab EmojiPicker so locale catalogs and provider overrides can replace its labels, search status, loading state, and group names.

  • List: add edge to choose where each row's text, dividers, and hover and selection backgrounds sit. padded (default) keeps today's look. flush drops the rows' side padding, so text and dividers start on the same line as the list header and a heading above, with no container needed. outset keeps backgrounds' room around the text while reaching into the padding of a Card, Section, or Dialog, so text lands on the heading's line. bleed reaches rows to the container's edges with square corners, like Table rows in a Card. edgeCompensation="inline" is deprecated in favor of edge="outset" and keeps working; its rows' outer room is no longer cut off by the list's swipe clip. Run astryx upgrade to rename it.

  • Pagination: variant="compact" shows first/last buttons around previous/next when hasFirstLast is true and the page count is known. Compact still omits them by default, and the input variant still shows them unless hasFirstLast is false.

  • reset.css: in Chromium, the dropdown marker of an <input> with a list sits in the middle of the field. Chromium sets the marker on the field's line-height, so above a line-height of 1 it sits low, and the reset makes every field inherit the page's 1.5: the marker sat 3.5px below the middle of a 40px field, and further down on taller lines. Date and time fields, whose calendar icon uses the same part, keep their icon where it was. To keep the old placement, add input[list]::-webkit-calendar-picker-indicator { line-height: inherit; } after the reset.

  • SideNavItem: nested items now span the full width of the nav list, like top-level items, so their hover and selected fill lines up with the rows above them. Before, a nested item started 24px in from the list edge. The indent now sits inside the row, one --spacing-6 step per nesting level, so icons and labels stay exactly where they were. The indent area is now part of the nested row, so it shows hover feedback and clicking it activates the item. Content placed directly inside a SideNavItem that is not itself a SideNavItem is no longer inset by the children group. There is no supported way to keep the old inset look.

  • TabMenu passes host attributes and DOM event handlers to its trigger button. The trigger keeps its button semantics, tab stop, and menu disclosure attributes; a caller's onClick runs after the menu toggle.

  • Tables draw a line on every cell edge, not only between rows, on both Markdown and the RichText surfaces, and the grid closes at the table's rim. A reader following one row across never needed vertical lines, but someone editing a table has to aim at a cell and judge where a selection stops, and the empty 3×3 a toolbar insert produces was three horizontal rules with no sign it had three columns. The line is the same --color-border already used between rows, the rim merges with it into one rule rather than doubling at the edge, and both surfaces move together — the read and edit renderings match block for block, more closely than before.

    Also released in @astryxdesign/richtext.

  • TreeList: nested rows now span the full width of the list, like root rows, so their hover and selected fill lines up with the rows above them. Before, a nested row started at its indent. The indent now sits inside the row, so chevrons, icons and labels stay exactly where they were. The indent area is now part of the row, so it shows hover feedback, and clicking it activates the row: clicking a folder's indent toggles the folder, and clicking a row whose only action is its link anywhere outside its label follows the link, as Enter already did: modifier keys are kept, so Cmd/Ctrl-click opens a new tab, and a middle-click opens one too. Controls in a row's start or end content keep their own clicks, including role-based ones such as a role="switch" element, which no longer also toggle the row or run its onClick. Guide lines draw above the rows, so a hover or selected fill doesn't break them, and they let clicks pass through to the row underneath. The one exception is a row with keyboard focus: it draws above the guides so they never cut its focus ring, which means that if it is also filled (selected or hovered), its fill covers the guides at that row. Padding set on a row through the tree-list-item theme target or an item's className, style or xstyle still composes with the indent. --tree-list-indent still retunes the indent step. There is no supported way to keep the old inset rows.

Fixes

  • Allow MetadataList label width without an explicit position while preserving single- and multi-column label defaults (#6310)

  • AppShell: defer hiding the mobile nav's Activity boundary until its close animation finishes (#5701) AppShell mounts the mobile nav drawer inside a React.Activity boundary that flipped to mode="hidden" in the same commit as the drawer closing. React's Activity hides its subtree with display: none !important synchronously and re-runs the cleanup of every effect inside it, which cut off MobileNav's own slide-out CSS transition and cancelled its delayed dialog.close() before either could run. The drawer still closed correctly (a safety-net effect force-closed the native dialog on that same cleanup), just with the close animation skipped entirely.

    AppShell now holds the Activity boundary visible for a beat after the drawer closes, strictly longer than MobileNav's own worst-case close delay, so MobileNav's normal close effect gets a real chance to run its transition and its own timed dialog.close() before Activity ever hides the subtree.

    MobileNav also keeps its dialog rendered from the commit that closes it until its delayed close() has run, instead of relying on the dialog's discrete display transition to do so. A browser that does not hold that transition (WebKit) dropped the dialog to display: none at once while it was still an open modal, so for about 200ms nothing was visible and the page stayed inert. The slide-out itself is unchanged.

  • AspectRatio's fit="cover" and fit="contain" now fill the box on the documented Tailwind setups. The fit rules lived in reset.css, in the reset layer, and the setup guides put Tailwind's preflight in a layer above it, so preflight's img, video { height: auto } won and a fitted image kept its own aspect ratio. The fit sizing now ships with the component's own styles in astryx-base: above preflight, and below Tailwind's utilities, so a utility class on the child still wins. reset.css no longer carries AspectRatio rules. With fit="cover" or fit="contain", a direct child's own Astryx styles (an Astryx component's own size or object-fit) no longer override the fill. To keep a child's own size, set it with an inline style, unlayered CSS or a rule in a later layer, or leave fit unset.

  • The static AvatarGroupOverflow chip (no onClick) is exposed to assistive technology as an image named by its count label ("5 more"). Before, it was a span with an aria-label but no role, a name the generic role does not allow, so screen readers could skip the count. This meets WCAG 2.2 SC 4.1.2 Name, Role, Value. Nothing changes visually; the clickable chip stays a button, and a role passed by the caller still wins.

  • Button (and IconButton) no longer use the native disabled attribute while a busy state (isLoading, or a non-interruptible clickAction in flight) is the only reason the button can't be clicked. A natively disabled element can't hold focus, so the browser moved focus to <body> the instant an action started and never restored it — the classic reason busy state should be visual-only. Busy now uses aria-disabled (button stays focusable) with aria-busy and the spinner, matching the documented "Disabled vs Busy" API convention and the behavior input components already follow. isDisabled (and isDisabled together with a busy state) is unchanged — that still uses native disabled. isInterruptible is also unchanged. aria-disabled, unlike native disabled, still lets the button dispatch a click — the busy guard already called preventDefault() to block the button's own action, but a click on a busy button could still bubble to a wrapping ancestor's own click handler (a clickable row, card, or backdrop), which the previous native-disabled behavior never allowed. handleClick now also calls stopPropagation() in that guard, containing an unavailable click at the Button boundary the way a natively disabled control always did.

    The href-rendered anchor path had the same gap for keyboard activation: it only wired onClick to the busy guard, not onKeyDown, so a consumer's own onKeyDown still received Enter/Space on a busy-only anchor Button while it was announced aria-disabled. The anchor path now wires the same handleKeyDown suppression the button path already had, so Enter/Space are swallowed and every other key still reaches the consumer, on both render paths.

  • ChatLayoutScrollButton: the scroll-to-bottom pill now gives up keyboard focus, and refuses new focus, as soon as it starts to hide. Previously focus stayed on the fading pill for about half a second, until visibility: hidden applied, so it could sit on a control that was fading out or already transparent (WCAG 2.2 SC 2.4.7 Focus Visible). (#7738)

  • Preserve caller-provided IDs on CheckboxInput and keep labels associated with the native input.

  • CheckboxList keeps a caller's aria-labelledby. The group always replaced it with its own label id, so the caller's ids never reached the accessible name. The group is now named by its label and then the caller's ids, the same order RadioList and TimeInput use.

  • The setup reminder, the swizzle StyleX note, and one doctor line say only what is true

    Also released in @astryxdesign/cli.

  • CommandPaletteInput: the query field now draws the shared keyboard focus ring. Previously it suppressed the browser outline without replacing it, so keyboard users could not see which field held focus.

  • Source builds through Next.js's Babel no longer fail with Unknown property: L (or Zs, Letter) when the app sets no browserslist or targets a browser without Unicode property escapes. Avatar's initials and the Markdown parser's emphasis and heading-link rules now build their \p{…} regular expressions at runtime, as NumberInput already does, so there is no literal for Babel to rewrite. Matching is unchanged.

  • DrawerHeader now renders numeric zero startContent inside its start slot and preserves numeric zero endContent when no close button is present.

  • Drawer now slides from the correct logical edge when dir="rtl" is set directly on the drawer, matching inherited right-to-left regions.

  • DropdownMenuDivider now forwards host attributes and DOM handlers (id, data-*, aria-*, event handlers) to its separator, as other DOM-owning components do. Before, it accepted only xstyle, className, style and ref and dropped everything else, so a separator could not carry a test id or an accessible label. Its role="separator" and orientation stay component-owned.

  • DropdownMenu: load the touch bottom sheet only when a menu shows it DropdownMenu imported its touch sheet (BottomSheet, its gestures, and the touch action list) up front, so every page with a dropdown shipped it, even when the menu only ever opens as an anchored popover. The sheet now loads on demand, the way ContextMenu already loads it. A popover-only dropdown's own code drops by about a third (about 23 KB gzipped). Bottom-sheet and adaptive menus behave as before. The sheet starts loading when the trigger mounts, and a menu opened from the keyboard before it arrives still focuses its first action once it loads.

  • EmptyState no longer leaves blank icon or action spacing when those optional slots receive a boolean or an empty string. An empty description also omits its theme target. The docs now describe configurable heading levels and map the container anatomy to the public theme target.

  • Keep Field's horizontal-label layout marker authoritative when callers pass conflicting data attributes, and keep hidden descriptions visually hidden in that layout. Correct its custom label-icon type, all three status placements, complete anatomy, and guidance for controls that cannot safely use attached status.

  • FieldStatus (and any element using useEntryAnimation) no longer causes a hydration mismatch on server-rendered pages. An element that arrives in the server's HTML now hydrates without the entry style and never plays its entry animation, even when hydration runs after the page has painted; elements mounted after hydration still animate. useEntryAnimation no longer documents server rendering as unsupported.

  • Keep FormLayout's direction metadata authoritative when callers pass conflicting data attributes.

  • GridSpan keeps its content inside the span. A child whose min-content width is wider than the span, such as a Tokenizer with a long token, used to stretch the span's column past the grid cell; it now gets the span's width and truncates the way it does outside a Grid (#7438)

  • Keep Grid's theming metadata authoritative when callers pass conflicting data attributes.

  • useHoverCard: closing a hover card with hide() or Escape while the pointer rests on the card no longer leaves the next open stuck. The hook kept believing the pointer was still over the card, because a card closed that way unmounts without a mouseleave, so the trigger's next hover-open never closed on leave or after hideDelay. Both paths now clear that state, as the touch dismissal already did.

  • Compute ISO week numbers (Calendar hasWeekNumbers, getWeekNumber) on UTC anchors instead of local-time Date arithmetic. In timezones whose DST ends after New Year (Sydney, Auckland, Santiago), the local-millisecond difference between the week's Thursday and January 1 carried the DST offset, and in years whose January 1 is a Friday that extra hour rounded a whole week up: the entire ISO week 14 of April 2021 displayed as week 15. Week numbers are now identical in every timezone.

  • Anchored layers (Popover, DropdownMenu, and every component built on them) now keep their safe-area gutter and dynamic-viewport size cap in the shipped stylesheet. The compiled CSS referenced an undefined variable for the preferred value, so every browser used the fallback: on a phone with a dynamic toolbar or a notch, a tall layer could run past the visible screen or under a declared bottom bar. Desktop sizes are unchanged.

  • createMarkdownFrontmatter now hands its parse function a plain object, so a parser that returns the decoded fields unchanged (parse: fields => fields) is accepted as metadata. Before, the record had no prototype, failed the JSON-like metadata check, and the document fell back to readable text with its frontmatter still showing.

  • MultiSelector: the row action cell the keyboard reaches with the inline-end arrow draws the standard focus ring. Before, its outline read four CSS variables that nothing declared, so no ring showed on the active action cell.

  • PowerSearch: clear every filter with one press of the clear-all button instead of removing only the last filter.

  • Prevent pressing a built-in PowerSearch filter token with the mouse from opening search suggestions before the filter editor. Token editing, removal, and keyboard navigation retain their existing behavior.

  • Keep built-in PowerSearch value controls disabled in read-only filter editors, matching custom editors.

  • RadioList keeps a caller's aria-labelledby. The radiogroup always replaced it with its own label id, so the caller's ids never reached the accessible name. The group is now named by its label and then the caller's ids, the same order TimeInput uses.

  • Restore focus to the element that opened a Dialog when its content mounts in the open commit. The opener was captured in the Dialog's passive effect, which runs after a freshly mounted DialogHeader's autofocus effect had already moved focus to the dialog's own title — closing then restored focus to that detached title and the user's focus landed on <body>. The capture now runs in a layout effect, before any descendant's passive effect. (#5637)

  • astryx search finds more of what a reader means when the words differ from the catalog's.

    Also released in @astryxdesign/cli.

  • astryx search ranks a match on more of your words above a shorter named entry.

    Also released in @astryxdesign/cli.

  • SideNavItem keeps a caller's className on its row element (the link or button, and the collapsed rail item) next to its own classes. Before, the row's generated class replaced it, so a class passed to style the row (for example, a cursor while a row is dragged) never reached the DOM, even though style, ARIA, data attributes and handlers did.

  • Shrink Token's invisible remove-button target from 44px to 24px without changing the visible icon. Clicking adjacent text or values in PowerSearch now edits the filter instead of accidentally removing it.

  • DropdownMenu submenu flyouts close with their menu when it is light-dismissed: previously a submenu opened by tap or a fast click stayed open (and re-anchored to the viewport corner) after the menu was dismissed and reopened. A hover-open scheduled on a submenu trigger before the menu closed from elsewhere is also cancelled, so it can no longer reopen a flyout behind the dismissed menu

  • Switch now adds a caller's aria-describedby to the switch control, ahead of its own description, status, and disabled-reason ids. Before, the value landed on the field wrapper, so a hint rendered elsewhere on the page could not describe the switch to assistive technology.

  • Switch now puts a caller's aria-labelledby on the switch control. Before, the value landed on the field wrapper, so a visible title elsewhere in the row could not name the switch for assistive technology.

  • A truncated Text or Heading (maxLines) now re-measures when its content changes, not only when its box resizes. A fixed-width single-line status whose message changed kept showing the first message in its truncation tooltip, and a new short message could keep a tooltip while a new long one lacked one. useTruncation now also observes the element's text, so the tooltip text and the truncated state follow the current content.

  • ToggleButton keeps toggling when it also receives an onClick, for example from a wrapper that renders it as its trigger. That onClick used to replace the toggle's own click handling, so onPressedChange never ran and a ToggleButtonGroup member never asked its group to toggle (pressedChangeAction still ran). The caller's onClick now runs first and the toggle request follows. On a standalone ToggleButton, calling preventDefault() in that onClick cancels the request: neither onPressedChange nor pressedChangeAction runs.

  • useTooltip (and every component built on it, such as Button's tooltip prop) now drops a show that is still waiting out its delay when isEnabled turns false. Before, hovering a trigger and then disabling its tooltip within the delay let the timer fire anyway: the tooltip opened after its triggers were turned off, with no pointer-leave listener left to close it. A pending hide still runs, and an enabled tooltip is unchanged.

  • Tooltip: an icon-only trigger now opens instead of throwing. The layer passed its trigger to showPopover as the invoker source, which the Popover API declares as an HTMLElement; a caller-rendered icon is an SVGElement, whose dictionary conversion throws before the popover opens, so the hint never appeared. An icon trigger now reaches showPopover without a source — positioning is unchanged, since the layer is anchored by its anchor-name pair, and only the invoker's focus order is given up.

  • A collapsed TopNavMenu or TopNavMegaMenu section in the mobile drawer is now inert and hidden from assistive technology, so Tab no longer moves focus onto links that are not shown and screen readers no longer list them under a section announced as collapsed.

  • An expanded TopNavMenu or TopNavMegaMenu section in the mobile drawer no longer clips the focus ring of its items. The section still clips while collapsed and while it animates open or closed.

  • Typeahead, Tokenizer, and PowerSearch: clear loading state and stop busy indicator when search source is replaced (#7457)

Performance

  • Text and Heading with maxLines and hasTruncateTooltip={false} no longer measure their text or observe it for resizes and content changes. Only the tooltip uses that measurement, so with the tooltip off the text is cut by CSS alone, and a long table of truncated cells no longer pays a layout read per cell. Rendering is unchanged; with the tooltip enabled (the default) nothing changes.

Documentation

  • Avatar and AvatarGroup docs list the numeric sizes size accepts (16, 20, 24, 32, 36, 40, 48, 60, 64, 72, 96, 128, 144, 180) instead of implying any pixel value, and say that an AvatarGroup's size always overrides a child Avatar's size, including the group's default.

  • Docs and CLI examples import each Core export from the path astryx component <Name> prints.

    Also released in @astryxdesign/cli.

  • ContextMenu docs now state that data-driven items and compound menuContent are mutually exclusive choices instead of marking items as unconditionally required.

  • Document DropdownMenuGroup's complete wrapper props, group semantics, and non-interactive heading guidance.

  • Document FieldLabel's complete public prop surface, group-label semantics, and supporting descriptions.

  • Make FormLayout documentation match logical fractional grid tracks and viewport collapse, and make the public layout target discoverable from component anatomy.

  • Make Grid and GridSpan documentation match fractional track sizing, responsive repeat behavior, implicit rows, and explicit-column spans.

  • useCombobox, the single-select combobox hook exported from @astryxdesign/core/Selector that powers Selector and CommandPalette, now has hook docs, so astryx hook useCombobox and astryx search combobox return it with its options, its return fields, and how to wire aria-activedescendant while focus stays on the control.

  • The useMultiCombobox hook docs now say that ArrowDown or ArrowUp opening the list lands on the first or last option even with hasSearch or a disabled end option, and that the hook still handles Home and End with hasSearch, so a search input that keeps those keys for the caret stops them first and relies on PageUp and PageDown.

  • useMultiCombobox, the multi-select combobox hook exported from @astryxdesign/core/MultiSelector that powers MultiSelector, now has hook docs, so astryx hook useMultiCombobox and astryx search combobox return it with its options, including the grid rows with an action cell, its return fields, and how to wire aria-activedescendant while focus stays on the control.

  • InternationalizationProvider documentation now names its nonvisual provider boundary so the new component contract can map its anatomy without implying rendered DOM. (#6048)

  • ListItem's docs now list label as a ReactNode, matching its props type: a string is truncated to one line, and any other node renders as given. They used to say string, which told readers a rich label had to be restructured away.

Other Changes

  • VStack and HStack come from @astryxdesign/core/VStack and @astryxdesign/core/HStack, Heading from @astryxdesign/core/Heading, Text from @astryxdesign/core/Text, and Theme and defineTheme from @astryxdesign/core/theme.

    Also released in @astryxdesign/cli.

  • This covers the guides, the Card docs, the next steps astryx init prints, the install example astryx theme build prints, the wiring example theme add prints, the build rules, the theme-module fix astryx doctor prints, and the annotated theme template.

    Also released in @astryxdesign/cli.

  • Every path was already exported. Nothing else changes.

    Also released in @astryxdesign/cli.

  • The "run init to finish setup" reminder no longer prints where agents already have the Astryx prompt. In a git repository it looks for init's block in the agent docs from the project up to the repository root, because coding agents read every agent doc in between, so a package in a monorepo that ran init at its root stays quiet. This holds for every command and for the reminder printed when @astryxdesign/core or @astryxdesign/cli is installed. Running the CLI from its own source checkout, inside that checkout, prints no reminder. A project with no block in reach still gets it, and --json never prints it.

    Also released in @astryxdesign/cli.

  • astryx swizzle no longer says StyleX components "render unstyled (no error)" without a StyleX compiler. Importing them throws StyleX's "Unexpected 'stylex.create' call at runtime" error and the page does not render, and the note now says so.

    Also released in @astryxdesign/cli.

  • In an integration package, where the package's own manifest loads, astryx doctor's implicit-integrations line no longer says that "every loaded integration is named in astryx.config". It names the package's own manifest: as the only loaded integration, or beside the integrations astryx.config names.

    Classification: contract-restoring. Only message text changes: JSON keys, check ids, statuses, and exit codes are unchanged. The doctor line's new text also appears in its --json message.

    Also released in @astryxdesign/cli.

  • Internal groundwork for a sub-menu's header and footer: DropdownMenu's Tab walk through its header, menu and footer moves, unchanged, into a module of its own, so another menu level with regions can share it. DropdownMenu's behavior does not change.

  • Adjacent query words that join into a component, hook, or template name, or into a one-word keyword, match it: table header cell with sort arrow finds TableHeaderCell, tree view folders the treeview keyword, and a hook answers to its words without use. Most of a name, a name's leading words, or a run of its words also count for components and hooks, a step below the full name.

    Also released in @astryxdesign/cli.

  • A query that is one camelCase identifier and matches nothing as typed is read as the words it spells, so an unknown API name such as useTablePaginationState reaches useTablePagination. In a query of several words each identifier stays one term.

    Also released in @astryxdesign/cli.

  • When the whole query is an entry's name, these readings add no near siblings beside it. A match on a joined keyword never ranks above an entry the query names, and a match on part of another name does only when it spans more query words than that entry's name.

    Also released in @astryxdesign/cli.

  • A gerund matches its verb (spacing and space, dragging and drag), and a plural can start a longer word through its singular (tokens and Tokenizer).

    Also released in @astryxdesign/cli.

  • Core component docs declare more keywords: other libraries' names (scrollarea, togglegroup, pageheader, splitpane, circularprogress, linearprogress, commandmenu, relativetime, sidesheet, descriptionlist, inlinecode) and common glyph names on Icon, since any icon library's glyph renders through it.

    Also released in @astryxdesign/cli.

  • The JSON shape is unchanged.

    Also released in @astryxdesign/cli.

  • A match on part of a longer name that reads more of the query than the entry the query names is no longer held below it: dropdown menu radio options leads with DropdownMenuRadioItem, not DropdownMenu.

    Also released in @astryxdesign/cli.

  • useResizable drops the splitview keyword, which put it ahead of the side-panels guide.

    Also released in @astryxdesign/cli.

@astryxdesign/richtext

Documentation

  • The RichTextEditor docs now list isOptional, isRequired, labelTooltip and namespace, four props the editor already accepted, so astryx component RichTextEditor and the docsite props table show the Required/Optional indicator, aria-required, the label tooltip and the Lexical namespace.

@astryxdesign/cli

New Features

  • An integration component doc can name a differently named export of the file beside it: when Slides.tsx exports AcmeCarousel and nothing named Slides, Slides.doc.mjs can set name: 'AcmeCarousel', and a kebab-case acme-widget.doc.mjs can name AcmeWidget. component --list, component <Name>, search, swizzle, the doctor integration checks and integration verify know the component by that name, and its file name still opens it. A name that Core or another doc's file already has stays theirs on every surface unless --package names the integration. Docs and sources are read without running them; a doc named after its file, or beside a file that exports its own name, reads exactly as before.

  • Each full entry an integration's family doc lists in components, such as AcmeSlide in an AcmeCarousel doc, is a component under its own name when the module the doc's import names exports it. Its source is the .tsx file in the family's folder that declares it; a member that only built output or a file outside that folder declares has no source to show or copy. component --list, component <Name>, search, swizzle, the doctor integration checks, integration verify and integration add know these members. A member's detail is scoped to its own entry, as a Core sub-component's is, and it never carries the family's replaces. A member named like a Core component or another doc's file opens with --package.

  • The CLI installed for the computer keeps one short Astryx skill for every agent on the computer: ~/.agents/skills/astryx/SKILL.md, which most coding agents read, linked into the skill folder of each agent that reads only its own (~/.claude/skills always; Kiro, Windsurf, Letta, Xcode's coding assistant, and ~/.config/agents/skills when that folder exists) with a folder link and a relative target, a junction on Windows, or a copy where no link can be made. Continue, whose scanner skips links, gets a managed copy. The text tells agents that Astryx is on the computer, when it is relevant (building or changing UI), to start with astryx search, and to read docs with astryx docs; it names no version and only commands every release keeps. It is written on the global copy's first run and after each upgrade, in the background; package install scripts never write it. astryx init --user writes it now, in any folder, and astryx init --user --remove-agents removes the file, every link Astryx made, and the copy, and turns it off. The setting and the links Astryx made are kept in astryx/settings.json in the per-user configuration folder. The file records a hash of its text, so a file a person edited is never overwritten or removed, and nothing Astryx did not create at a link folder is replaced. init --user warns, while still writing, when no astryx command on the computer runs the Astryx CLI, and its init.user receipt lists each path and what happened to it. Outside a project, astryx doctor adds a computer-agent-text check (info only) that says whether the text is present, current, edited, or turned off, where it is linked, and any link folder it skipped or whose skill folder is missing. A cache or configuration folder set through XDG_CACHE_HOME, XDG_CONFIG_HOME, LOCALAPPDATA, or APPDATA is used only when it is an absolute path.

  • Integrations installed for the computer. An integration package installed in the same place as the CLI installed for the computer (npm i -g, pnpm add -g, yarn global add, or bun add -g beside @astryxdesign/cli), or installed with the CLI in one package set by a tool that runs it, is a computer-level integration. Outside a project, it loads the way a project's integrations do: its components, templates, docs, themes, discover source, and cliCommand apply, with the same validation, and one whose @astryxdesign/core or @astryxdesign/cli peer range excludes the CLI's release is skipped. pnpm 11 and later, which give each global package a folder of its own, count every one of those folders. Inside a project, only the project's own integrations load: a computer-level one adds nothing there, and astryx discover lists it under meta.available with source installed for this computer and the project's install command in install, when its peer range admits the project's release and it loads. A project that installs the same integration uses only its own copy. astryx doctor adds a computer-integrations check: outside a project, each one it uses, each the folder replaces with its own copy, and a warning for each it skips or cannot load; inside one, the ones the project could add.

  • astryx gap-report --to <package> sends a report to the issues URL of the loaded integration the person chose, under the same --confirm-public rule, instead of the default handlers. When loaded integrations name their own issues URLs, --list-categories and the receipt list every destination and tell the agent to ask the person where to file. The default route is unchanged.

  • Install the CLI once for your computer (npm i -g @astryxdesign/cli) and run astryx in any folder. In a project, the project's own CLI answers, whatever its version, as before: a copy started anywhere else (the global install, npx, a typed version) hands the run to it. A project with no CLI of its own (only @astryxdesign/core, or a fresh clone) is answered by the copy you started, as before; when that copy is not the Core's release, one hint after the command, at most once a day per project, names the command that adds the CLI at Core's version, and JSON reports meta.answeredBy.copy: "starting". Only a project whose own CLI cannot start is answered by the CLI release equal to its Core, installed once into a per-user cache (from the installed Core, else the lockfile, else the declared range). astryx doctor warns when a project's CLI and Core are different releases, or when a project has no CLI and the copy that answered differs from its Core, and prints the command that aligns them. astryx --help and the manifest's new globalMode field point to astryx docs global. The hand-off keeps the arguments, folder, standard streams, and exit status. Outside a project, the copy installed for your computer answers with its own release; commands that read Core use the Core of that release there: the global copy downloads it into the cache in a background step it starts as a run ends (never in CI, and not on Windows, where a background process would hold the caller's pipes and the first component command downloads it instead), so component reads work offline after one online run. Nothing that worked before breaks: when that release cannot be obtained (offline, a registry without it, an unwritable cache), the copy you started answers, as before global mode, with one warning line on standard error (astryx: could not get Astryx <version> to match this project's Core (<reason>); answering with CLI <version> instead) and meta.answeredBy.copy: "fallback" in JSON. A copy counts as installed for the computer only in npm's global folder or in a global folder a package manager's own settings name (Bun's BUN_INSTALL or globalDir, Yarn's global-folder, pnpm's PNPM_HOME or global-dir, or their defaults), never by a folder's layout alone, so an app made with yarn add is never mistaken for one; a moved Bun or Yarn global folder counts, and its notice reinstalls there. A failed install is remembered in the cache, so offline runs do not start npm again each time: it is retried after 2 minutes, then after 10 once it fails again, and a success forgets it. A stale install lock ends the process it records only when that process is exactly this CLI's install of that release (npm, or node running npm-cli.js, installing @astryxdesign/cli@<version>, started after the lock); otherwise the lock is simply taken over. The new ERR_VERSIONS_UNAVAILABLE is kept for runs nothing can answer, such as a command that needs Core outside a project that cannot install it. The CLI starts npm from npm's own entry file with the running Node, never through a shell, and reads a project's declared Core range only when it is a plain version range. Every successful --json result now carries meta.answeredBy (cli, core, copy: project, starting, matched, carried, or fallback), typed in CLIResponse and described in the output envelope docs. In text mode, a run answered by anything other than the project's own CLI prints one astryx: answered by … line on standard error. Printed commands start with a name that reaches the same release: the project's runner when its own CLI answers, astryx outside a project for the copy installed for the computer, and otherwise the CLI package pinned to the answering version (npx @astryxdesign/cli@0.7.2). The new ERR_PROJECT_REQUIRED code names a command that needs a project. See astryx docs global/install-once.

  • astryx doctor integration and astryx integration check catch more doc mistakes, and stop flagging one pattern Core itself uses.

  • The integration manifest accepts an optional docsiteUrl, a link to the integration's docs site, as a known field, so setting it no longer raises an unknown-key warning.

  • Update notices, the way gh and the Vercel CLI do them. Once a day, never in the first day after its first run, the CLI installed for the computer checks the registry's latest release in the background without delaying the command; it never installs a release by itself. When a newer release exists, a command that finishes successfully prints two lines on standard error, at most once a day: A new release of Astryx is available: <current> → <newest> and To upgrade, run: <command>, with the command of the package manager that installed the CLI (npm i -g, pnpm add -g, yarn global add, bun add -g, or volta install), after the Node the release needs when the computer runs an older one. Inside a project the notice names the project's install and upgrade commands instead, and an astryx first on the search path from before the hand-off gets a notice naming its upgrade. No notice prints with --json, after a failed command, or when CI or NO_UPDATE_NOTIFIER is set, and neither variable lets the check run; unlike gh, a notice prints when standard error is not a terminal, because agents run the CLI without one. What the check found lives in astryx/state.json in the per-user configuration folder. astryx doctor reports the same: the newest-release check names the upgrade commands, and a new cli-on-path check lists every astryx command on the search path.

  • A {@link <target>} in a component doc's text now reads as the command that opens its doc, in astryx component <Name>, its --props view, and component lists, in text and JSON: the description, usage, best practices, anatomy and accessibility notes, and each prop's and example's description. A topic's description reads the same way in astryx docs, its list, and the docs tree. Code fields and code ticks show the link as written. A Core component's links resolve against the CLI's docs; an integration's against its own. astryx doctor integration components warns on a link in an integration's component docs that names no doc, and doctor integration docs does the same for a topic's description. The written-pointer warning also reads component docs and topic descriptions, and each finding now names its kind and field.

  • Component prop docs accept stability: 'experimental', the machine-readable marker for a prop outside the stable compatibility promise, such as a component's experimental prop object.

  • doctor integration docs (and so integration check) lists each pair of topics that repeat 10 or more words of prose word for word: two of the package's own topics, or one of them and a Core topic other than the one it replaces. Each entry names the topic that keeps the text (a Core topic, or of two package topics the one the passage fits), the line to change in the other, and the exact link to put there, and is in the new copiedPassages field. A run with fewer than 4 distinct content words (words other than function words such as "the", "in" or "it"), or one that three or more topics share, is not listed. Each pair is also a copied_passage warning, so integration check --strict (new in this release) fails until it is fixed; doctor integration docs still exits 0. The CLI's own guides no longer repeat 18 passages that another guide owns; each copy is now a link to the guide that owns the fact.

  • astryx discover lists the official Astryx packages, first, from a list each CLI release carries, so a project with no discover source sees the official themes, even offline. Each entry in meta.available carries the command that adds it (install), so a project that loads no integration and has no discover source of its own, where every query still answers with the list, can add a package straight from it. An official entry's latest is the newest release the CLI knows (its own version on a release CLI, or a newer Core release the project has), and its versions also list the project's Core version, so both can be looked up. Its add command pins the project's Core version (installed, or declared exactly), else the Core range the project declares (quoted), else the latest release; on a canary CLI with no Core it pins none for packages released with Core, and canary-only packages are listed only beside a canary Core or no Core. Official entries name Astryx as their source, and meta.sources reports the list last. A project's or an integration's source keeps its entry for an official package, and for any package of the same integration. A theme package that the project's own package.json declares as a dependency, under an npm alias too, shows as installed from any folder of the project, and a theme's add text ends with astryx theme add <slug>. A version or item that the official list does not show still answers ERR_UNKNOWN_PACKAGE, as before.

  • doctor integration docs (and so integration check) lists fluff in a package's prose, in the new fluff field: each padding phrase such as "in order to", "note that", or "it is worth noting", with its file line and what to write instead, and each topic of 80 or more prose words in which fewer than half the words carry content. Each is also a doc_fluff warning, so integration check --strict (new in this release) fails until it is cut; doctor integration docs still exits 0. The phrase list and the floor are in the check-your-docs guide (astryx docs cli/integrations/building-blocks/docs/check-your-docs). Core's browser-support guide loses its one padding phrase, and three of Core's guides (debug-and-gap-reports, sections-and-placement, publishing) are tightened to meet the density floor.

  • astryx doctor integration validate now checks every doc an integration ships under its roots (components, templates, docs topics, themes, and their translations, served or not) against the published authoring type for its kind, with the project's own TypeScript, the way tsc would. Each unknown field, missing required field, or value outside its field's type is a warning that names the doc, the line, the field, and a fix; the exit code is unchanged. A doc it cannot fully check, or a themes root that does not load, is listed with the reason; test fixtures under __tests__ in the components or docs root are counted, not checked. JSON adds data.docTypes. Without TypeScript the check prints one [skip] line instead of passing silently; with a TypeScript it cannot read types with (one with no JavaScript compiler API, such as TypeScript 7), that line names the installed version. With no doc files, it says there is nothing to check.

  • astryx doctor integration components compares each component doc's props with the component's TypeScript props, read with the project's own typescript. It warns about a documented prop the code does not have, a prop the package declares that the doc leaves out, and a wrong required. Without TypeScript it prints one line saying the comparison was skipped; with a TypeScript it cannot read types with (one with no JavaScript compiler API, such as TypeScript 7), that line names the installed version. The findings are warnings, so the exit code does not change; the JSON response gains data.propDrift, and nothing else in it changes.

  • astryx doctor integration components also judges each documented prop's type against the code, for props the package declares, a documented field such as status.type and a name the package's own index signature takes included, and warns when they differ (type_mismatch). A type is judged only where both sides read exactly, with the project's own typescript: the same text once whitespace, quotes and union order are set aside, or primitives, literals, arrays and unions of those, and names for them. Every other type (a callback, an object, a generic with arguments, an intersection, keyof, a type that does not resolve, a name two index signatures take) is listed as not compared, never reported. The comparison needs TypeScript 4.8 or later: with an older one, no type is judged and the text says so in one [skip] line. JSON adds typesCompared, typesNotCompared and, when the types were not judged, typesSkipped to propDrift. The findings are warnings, so the exit code does not change.

  • New astryx integration check, the CI check for an integration package's own build. It takes no package argument and checks the integration package in the current folder. It runs the four doctor integration checks (validate, components, templates, docs), prints each result, then a summary, and exits 1 when any of them would. --strict also exits 1 on any warning and on any check that did not run, such as a package with no astryx.integration.*: add "typecheck": "tsc --noEmit && astryx integration check --strict" to package.json so every build keeps the package's docs in sync. Its exit codes and JSON are a contract for the builds that run it: from this first release on, a new check, or a new failure code, joins only in a minor release named in the release notes. JSON: a new integration.check envelope carries each check's own envelope unchanged. Its docs say to run it in CI, so astryx search ci finds it. The doctor integration subcommands keep their exit codes; their JSON gains only additions.

  • Three new published types describe an integration package's templates: IntegrationPageTemplateDoc and IntegrationBlockTemplateDoc, each with every field of its TemplateDoc member but category as the package's own string instead of a TemplateCategory value, and their union, IntegrationTemplateDoc. @astryxdesign/cli/authoring exports IntegrationTemplateDoc; @astryxdesign/cli/doc and @astryxdesign/cli/template export all three. TemplateDoc is unchanged.

  • astryx upgrade renames a static edgeCompensation="inline" on List to edge="outset", and drops edgeCompensation beside an edge that is a fixed string. It keeps edgeCompensation with a TODO(astryx upgrade) comment where it can't prove the result: where edge is computed (it may be undefined, and then edgeCompensation still applies), on a dynamic edgeCompensation value, and where the List also spreads props, which may set edge. The deprecated prop keeps working there.

  • astryx doctor integration docs lists each code span in an integration's prose that is exactly astryx docs <name> for a doc that exists, and says which {@link} to write instead: {@link generic:<name>} for the package's own topic, {@link @astryxdesign/cli:generic:<name>} for Core's. The link prints the command that opens the doc, and doctor's link check fails it if the doc goes away. A command with more words, a route, or a code block is not listed. Each is a stray_doc_pointer warning, so astryx integration check --strict fails on it, and is in the new strayPointers field; doctor integration docs keeps exit 0. Core's own guides point with links, and a test keeps them that way.

  • astryx doctor integration validate and astryx integration check now warn source_without_template_doc when a .tsx directly in an integration's templates root has no same-stem template doc, so astryx template would never list it. The warning names the file and the doc to add. Files in subfolders, such as helpers a template imports, and test, spec or story files such as stray.test.tsx are not checked; a dotted template id such as landing.page.tsx is, and integration check --strict fails on the warning.

  • The doc-type and prop checks in integration check and doctor integration validate|components read types with @typescript/typescript6, the TypeScript 6 API package TypeScript publishes for tools, when the project installs it and its own typescript is missing or has no JavaScript compiler API, as TypeScript 7 does. When no usable TypeScript is found, the skip reason now ends with the remedy: add @typescript/typescript6 on TypeScript 7, or install TypeScript 5.0 through 6 (npm install -D typescript@6).

Fixes

  • extractComponents reported phantom components from locally-declared helper functions in template files — e.g. "Timeline", derived from a function TimelineSection() {...} helper declared directly in the detail-page template, which isn't a real component. It now excludes any name bound by a top-level function, class, const, let or var declaration in the same file (read from the parsed source, so comments, strings and type annotations don't count), before suffix-stripping runs, so a locally-declared helper is never mistaken for an imported component. A component whose whole name is a stripped suffix, such as a bare Section, now stays in the list instead of emptying out.

  • Global and project copies of the CLI meet more predictably. A repository that builds the CLI itself, with packages/cli beside packages/core, answers with that CLI instead of failing with ERR_VERSIONS_UNAVAILABLE. On a cache folder shared between computers, a release install renews its lock every 10 seconds, and a lock from another computer that goes a minute without renewal is taken over, instead of blocking runs for 20 minutes. Folder names are compared without letter case on macOS and Windows, a Yarn 1 global install on Windows is recognized as a global install, and the shims npm, pnpm (including pnpm 12's), and PowerShell write are read when doctor and the reminders look for the astryx commands on the search path.

  • A CLI installed with npm under a prefix of its own (npm i -g --prefix ~/.local, NPM_CONFIG_PREFIX, or prefix= in an .npmrc) is now upgraded into that same prefix: the update notice and astryx doctor print npm i -g --prefix <prefix> @astryxdesign/cli@latest, with ~ for the home folder. npm's default prefixes (Node's own, /usr/local, /usr, and %APPDATA%\npm on Windows) keep the plain command.

  • The CLI now uses postcss-selector-parser 7.1.6, which parses flat selectors in linear time and closes a CPU-exhaustion vulnerability (#6985).

  • integration verify reads a package's exports with the same reader discovery uses, so the two agree: a component re-exported through a folder's index file passes, and a type-only re-export no longer counts as the component.

  • astryx component --list and astryx search find VStack, HStack and Heading Each has its own import path, but its doc lives in its family's folder: Stack's doc documents HStack and VStack, and Heading's doc sits next to Text's. The list only counted a doc in the component's own folder, so the three were missing from the list and from search, and astryx component VStack found its doc only through fuzzy matching. A component whose doc is elsewhere is now listed and searchable with its own import path, and astryx component VStack opens its doc directly.

  • A doc link to a component, page, or block ({@link component:AcmeButton}, {@link @astryxdesign/core:component:Button}, {@link block:acme-card}) now resolves, and reads as the command that opens it: astryx component <Name> or astryx template <id> --type <page|block>, with --package for anything not from Core. Before, every such link printed as written, and astryx doctor and doctor integration docs warned that it named no doc, for Core's components as for a package's own. A link that names no component or template still warns, and its hint now searches components or templates instead of topics. A resolved link's route is null for these kinds, which no astryx docs route opens.

  • astryx doctor no longer fails "Theme owners and built imports", and astryx component no longer stops with "Could not load the recorded theme", when an added theme's built module imports a stylesheet, as in import './ocean.css'. Node cannot load a stylesheet, so the CLI checks that each imported .css file exists, then treats it as empty. It looks for each stylesheet from the theme module's folder, also when a module that the theme imports from another folder names it. Doctor still fails when an imported stylesheet is missing, and when the module has any other problem, such as a missing dependency, a syntax error, or an error thrown while it loads.

  • astryx search keeps a guide first when the query is a phrase of two or more words that the guide declares as a keyword. font size and font weight now open the font setup guide before the Text component, and dark mode opens the theme guides before the theme hooks. A component whose name is the whole query still comes first, one-word queries are unchanged, and the code terms and table cells in a reference doc don't count as its keywords.

  • astryx integration check --strict prints each finding once. The checks' own output shows its message and fix, and the closing "Failing under --strict" list names each failure by check, code and place only, so a long report is no longer doubled.

  • The table-filter page template's phone-width view-options panel is now labelled by its selected tab (aria-labelledby) instead of a fixed aria-label, so screen readers get the panel's name from the tab that controls it and the tab–panel relationship resolves in both directions.

  • astryx theme build --check and themeBuild({check: true}) keep comparing outputs outside the working directory, as the latest release does, rather than refusing them with ERR_PATH_TRAVERSAL the way a build does. Check mode writes nothing, so the write confinement of architecture:cli-surface/INV10 and INV10a does not apply to it: an absolute --out path outside the working directory, a theme file outside it whose outputs sit beside it, and --family with its root theme file outside it are each checked read-only. A relative --out path that escapes the working directory is still refused, as before. Builds keep refusing every one of these writes, so when such outputs are stale, the report no longer ends with a Rebuild with: command the build refuses. It names the nearest directory that contains the theme files and the outputs, and the build and check commands to run from there: each output's Source: header records the directory its build ran from, so the check passes only from that same directory. Stale outputs inside the working directory keep the Rebuild with: line.

  • astryx theme build and themeBuild() now write only inside the working directory (cwd for the API), as architecture:cli-surface/INV10 requires; under architecture:cli-surface/INV10a the refusal is a compatible fix. Each of these builds now fails with ERR_PATH_TRAVERSAL before anything is written: an absolute --out path outside the working directory, a theme file outside it whose outputs would go beside it, and --family with its root theme file outside it. Paths inside the working directory still build, including through a symlink. To migrate, run the build from a folder that holds the theme file and its outputs, or pass an --out path inside that folder; API callers pass a cwd that holds them.

  • astryx upgrade no longer reports your code as up to date when the CLI is older than the installed @astryxdesign/core. Core codemods ship inside the CLI, so an older CLI has none for the releases after its own version. It used to print "No changes needed" and "No codemods available for this version range" and exit 0 while the code still needed those releases' migrations. It now warns once that it cannot finish, prints the same upgrade pinned to the CLI release that matches your Core (such as cd <project> && npx @astryxdesign/cli@<core-version> upgrade --from <old-version> --path <source-dir> --apply), and ends with "Upgrade incomplete". That command changes to the folder the run used and names the directory it scanned as an absolute path, both with symbolic links resolved, so it upgrades the same code wherever you paste it. It is written for a POSIX shell, or for PowerShell on Windows. upgrade.status (no_codemods and config_fixable) and upgrade.run gain an optional cliBehindCore field with cliVersion and command, present only in that case. The codemods the CLI has still run, and the exit status and every existing field are unchanged.

  • astryx integration verify accepts a theme module that loads the theme's own styles. Such a module imports the theme's exported stylesheet and font stylesheet for their side effects, and re-exports from the module astryx theme build writes either everything or only its theme, under the theme's export name. Verify also finds that built module by the name theme build gives it, the theme's own name, so a theme whose name differs from its slug no longer fails to rebuild. Any other difference from the built module still fails with theme_module_stale, and for a module that re-exports the theme, the message now says what is wrong.

Performance

  • The CLI loads Core's runtime (@astryxdesign/core and @astryxdesign/core/theme) only when it builds or checks a theme (theme build, and the doctor and integration pack theme checks), not on every start. In a project with Core installed, every other command, and an import of @astryxdesign/cli/api, skips loading Core's components and React: astryx --version drops from about 1.3 s to 0.5 s. Output is unchanged.

Documentation

  • Global mode docs: astryx docs global is a top-level docs section (and one docsite page, under Guide, which Getting Started introduces and links) explaining what global mode is, the one install command, what you get, and which version runs where. Its pages, each answering one question: project, global, or both (both is recommended; global/project-global-or-both), install (global/install-once, with what to do about EACCES), which version runs and why (global/who-answers), updates and notices (global/updates), the agent guide on your computer (global/computer-agent-guide), integrations installed globally (global/computer-integrations), monorepos and nested folders (global/monorepos), CI and containers (global/ci-and-containers), troubleshooting every line global mode prints (global/problems), uninstall and reset (global/uninstall), and a reference of paths, environment variables, JSON fields, and error codes (global/reference). astryx docs global and its pages are answered by the copy you start, even inside a project whose version predates them. Integration authors get a checklist with the commands to try a package globally (cli/integrations/global-integrations). Every command that does something else outside a project says exactly what in its "In global mode" note, checked against the command run in an empty folder, and every terminal line the global-mode pages show is checked against the CLI's own strings. A project's update notice now installs the CLI beside Core, so a project that installed only Core gets its own CLI too.

  • The integration guides make integration check --strict a required step in every integration's build: the quick start installs typescript@6 (the checks need its JavaScript compiler API, which TypeScript 7 does not ship) and adds "Check it in every build (required)", the Build an integration overview says so, and the checks guide's section reads "Run every check in every build (required)".

  • The astryx docs cli/integrations guides and astryx docs authoring now match the current CLI, and the guides point to the matching astryx docs authoring <section> for every field instead of repeating its tables.

  • Fix the MCP setup snippet in working-with-ai — "type": "url" is not a transport type, and Claude Desktop adds remote servers through Connectors

Other Changes

  • A docsZh or docsDense overlay may set name and displayName, as Core's own docs do; both are ignored when the doc renders, so --strict no longer fails on them.

  • doctor integration components reports a documented default that differs from the literal default the component writes in its props destructuring (size = 'md') as default_mismatch. Quotes and spacing are ignored; where either side's default is not a literal, the prop is listed as not compared.

  • doctor integration validate warns, each with a fix, on a prop documented twice (duplicate_prop), a propDescriptions key that matches no prop entry's name as written (unknown_translated_prop), a section with no content (empty_section), two sections of one topic with the same key (duplicate_section_key, whose fix names the key the second one is already served at), and a .doc.zh.mjs or .doc.dense.mjs overlay beside no topic file (orphan_doc_overlay).

  • A topic that takes the route of one of Astryx's own docs is named by its topic name, not an internal id, with the rename to make. A link written as a bare name ({@link deploying}) gives the exact link to write, or says no topic has that name.

  • When a doc is missing several required fields, each finding's message names only its own field.

  • doctor integration validate prints each issue's fix and file in text output, as JSON already carries them.

  • Internal groundwork for astryx doctor integration components: a comparison of the props a component doc documents with the props its TypeScript source declares, read with the project's own typescript. Nothing reaches it yet, so the CLI's behavior and output do not change; the command starts running it in a later change, which describes what users see.

  • Internal groundwork for astryx doctor integration components: reading two written TypeScript types as one type when they differ only in how they are written (whitespace, comments, quotes, member order, Array<T> for T[], parameter names, React. qualifiers), and judging a documented prop type against the type the code declares where both read exactly. Nothing reaches either yet, so the CLI's behavior and output do not change.

  • Internal groundwork for astryx doctor integration components: finding the project's own typescript, and reading a package's TypeScript project the way tsc reads it (its own tsconfig.json or a monorepo root's, a solution-style config's references, and the declaration files the config includes), and finding the references in that project the compiler cannot resolve. Nothing reaches it yet, so the CLI's behavior and output do not change.

  • Old-CLI notes name the published releases that behave that way: template replaces, links, docs sections, section ids and typed theme descriptors work from 0.6.4, and template keywords from 0.6.6. integration verify still names the CLI range to declare.

  • The quick start makes the package an ES module, so a theme it builds can be imported.

  • Samples and output that had drifted are fixed, among them the subcomponent imports, a theme that extends another, and the upgrade output.

  • The Authoring Reference corrects how an invalid astryx.config is handled, the issuesUrl format, where codemod files go, and which fields nothing reads yet. Its examples use the export default {type: ...} form that integration add writes.

  • astryx docs cli/integrations links to the Authoring Reference, and the reference links back to the guides.

  • astryx docs styling-overview and tokens-and-setup give the real symptom of a missing StyleX compiler: importing a swizzled component throws an error that starts Unexpected 'stylex.create' call at runtime.

@astryxdesign/build

Fixes

  • withAstryx() builds again under pnpm's default isolated layout. It no longer turns off webpack's symlink resolution, which hid each Astryx package's own dependencies (Can't resolve 'intl-messageformat', 'lucide-react'). Astryx modules are still matched by their real path, so a workspace-linked package keeps resolving its source entries.

  • The PostCSS plugin now scans what each installed Astryx package publishes (its files field, or the whole package minus nested node_modules when it has none) instead of everything under node_modules/@astryxdesign. A package linked from a workspace no longer drags in its own node_modules, whose links can cycle back to it and made the build run out of memory, or its unpublished test fixtures. Registry installs scan the same files as before.

Performance

  • The PostCSS plugin no longer loads the app's root Babel config when it compiles Astryx source. In the documented Next.js setup that config (next/babel plus Astryx's StyleX plugin) ran over every installed Astryx module on each build only for the plugin to read StyleX metadata, roughly doubling the CSS pass. The emitted CSS is unchanged, and the app's own files still compile with its config.

Contributors

Thanks to everyone who contributed to this release:

@abu-abdullah22 @azure-553 @cixzhang @dependabot @ernestt @Geervan @harjothkhara @HelloOjasMutreja @imdreamrunner @josephfarina @kentonquatman @korkt-kim @kyu-rong @Kyujenius @latxh @ManoharPaturi @nynexman4464 @potatowagon @Pushpak731 @rapsealk @vjeux @WeAreResilience

Full Changelog: https://github.com/facebook/astryx/compare/v0.7.1...v0.7.2

5 hours ago
astro

@astrojs/svelte@9.1.1

Patch Changes

5 hours ago
astro

@astrojs/telemetry@3.4.1

Patch Changes

5 hours ago
astro

@astrojs/prism@4.1.1

Patch Changes

5 hours ago
astro

create-astro@5.3.1

Patch Changes

5 hours ago
astro

@astrojs/react@7.1.1

Patch Changes

5 hours ago
astro

astro@7.4.2

Patch Changes

  • #18430 dda9ec3 Thanks @Abhirup-99! - Speeds up content sync with experimental.collectionStorage: "chunked" by computing each character's UTF-8 width from its code point instead of encoding every character through TextEncoder. On a store of ~44k entries this cuts the chunking step from about 60 seconds to well under a second, with byte-identical output.

  • #18438 5e0eaa4 Thanks @astro-factory! - Fixes astro dev with @astrojs/cloudflare failing with Calling `require` for "node-fetch" when a server dependency such as cross-fetch requires node-fetch

  • #18376 a4e201d Thanks @okxint! - Fixes astro dev treating a stale lock file containing a Linux thread ID as a running Astro server. A successful process lookup with no matching PID now marks the lock as stale, preventing astro dev stop and astro dev --force from killing the unrelated process that owns the thread.

  • #18467 d1fb7d3 Thanks @astro-factory! - Fixes the defineConfig() types for i18n.defaultLocale, i18n.fallback, and i18n.domains so they accept a custom locale's path instead of its codes, matching how Astro validates and uses these values

  • #18475 cf27e70 Thanks @ematipico! - Bumps the minimum supported Node.js version to 22.23.4, which includes the fixes from the October 2026 Node.js security releases

    This is a patch release because the Node.js support and upgrade policy allows the Core team to raise the minimum Maintenance LTS version in a patch release when a disclosed and fixed Node.js security flaw affects Astro.

  • #18451 7a0ba6f Thanks @DenisCDev! - Fixes astro sync, astro check and astro build replacing the optimized dependencies of a dev server running in the same project, which broke framework islands in that dev server until it was restarted with --force

  • #18336 e943003 Thanks @astro-factory! - Fixes inline styles in dev so that framework-transformed CSS is used, such as scoped styles and v-bind() in Vue single-file components

  • #18399 829fc00 Thanks @astro-factory! - Fixes a dev server error (Could not find server component name) for a server:defer island that imports astro:actions or astro:config/server, when its page was not the first island page loaded after starting the server

  • #18330 4db9e0d Thanks @Samuteg! - Fixes the astro:env/server virtual module being generated with invalid JS in dev when any environment variable contains $ replacement patterns ($`, $&, $', $1, …). The inlined env JSON was passed as a string to String.replace, so e.g. the KITTY_PUBLIC_KEY value set by default in Kitty terminals corrupted the module and broke dev with Failed to parse source for import analysis

  • Updated dependencies [cf27e70]:

    • @astrojs/telemetry@3.4.1
    • @astrojs/markdown-satteri@0.4.5
5 hours ago
astro

@astrojs/upgrade@0.8.1

Patch Changes

5 hours ago
astro

@astrojs/vue@7.1.2

Patch Changes

5 hours ago
astro

@astrojs/solid-js@7.1.1

Patch Changes