1 hours ago
astryx

Astryx v0.7.3

Astryx 0.7.3 — every published @astryxdesign/* package ships at this version.

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

@astryxdesign/core

New Features

  • DropdownMenuSubMenu takes header and footer, as the root DropdownMenu does, so a sub-menu can have a filter field above its rows, or an action such as "New folder" below them, outside the sub-menu's menu itself. With either, the sub-menu's surface (the flyout, or the drilled-in view on a phone) is a modal dialog named by its row: the row says aria-haspopup="dialog", Tab and Shift+Tab walk the header, the rows and the footer and wrap, Escape returns to the row, and a pointer leaving the flyout closes it only once focus leaves too. A ContextMenu does not show a sub-menu's header or footer. Without them nothing changes.

     <DropdownMenuSubMenu
       label="Move to"
    +  header={<TextInput label="Filter folders" isLabelHidden value={query} onChange={setQuery} />}
    +  footer={<Button label="New folder" onClick={createFolder} />}
     >
       <DropdownMenuItem label="Folder A" onClick={() => move('a')} />
     </DropdownMenuSubMenu>

Fixes

  • TimeInput's arrow keys ignore a fractional increment instead of writing a malformed time such as "14:30.5": adjustTime returns the time unchanged for a fractional minute delta (#7823).

  • Screen readers now announce a Button that is first rendered with isLoading (for example after a remount). Its visually hidden loading live region mounts empty and gets "Loading" on the next frame, because screen readers skip a live region that already holds its text when it appears. A Button that becomes loading after it mounts is unchanged, and a Button mounted idle does no extra render.

  • ChatComposer: screen readers now announce a status message, errors assertively and warnings politely, once per occasion: a rerender does not repeat it, and a status that clears and then returns is announced again. Before, the status bar mounted as a role="alert" or role="status" region already holding its text, which screen readers often skip. The message now goes through the shared announcement channel, and the visible bar no longer carries a live role. No API changes.

  • ChatSystemMessage: a new chat notice inside a ChatMessageList now has one live region announcing it, the list's polite log, instead of also being a second live region nested in it (WCAG 2.2 SC 4.1.3). Its root no longer forces role="status", in either variant, so date and divider rows are no longer presented as status messages, and a role passed to the component reaches its root. Tests that found a system message by the status role should query it by its text instead.

  • Keyboard and screen-reader users can now reach disabled CheckboxInput, CheckboxList items, Switch, Slider and FileInput, and hear that they are disabled. Before, they rendered the native disabled attribute unless a disabledMessage was set, so Tab skipped them. Now every disabled control exposes aria-disabled="true" and keeps the Tab stop it has when enabled, while toggling, selecting, dragging, opening the file picker and any onClick stay blocked, and its value is still left out of form submission. A focused disabled checkbox, switch, slider thumb or file input shows its keyboard focus indicator. No API changes.

  • The agent guide that global mode writes to the home folder now tells agents to deliver a page built with Astryx, not only how to find things. Before, agents that found the right template could still build the page with a tool that cannot install packages and deliver a page with no Astryx in it. The guide now tells agents to work in a project folder (creating one if there is none); add @astryxdesign/core and @astryxdesign/cli to a new project or one without Core, or, in a project that already has @astryxdesign/core, the CLI at the same version; run astryx init there; follow the same template-first workflow and self-check the project agent docs give; and end by building the project and delivering that build. The guide stays version-free and names only commands every release keeps. The project agent docs are unchanged, and so is what @astryxdesign/core's install script prints: its copy of the shared agent-docs file only gains the same workflow lines.

    Also released in @astryxdesign/cli.

  • Keyboard and screen-reader users can now reach a disabled ComplexSelector and hear that it is unavailable (WCAG 2.2 SC 2.1.1 and 4.1.2). Before, its trigger rendered the native disabled attribute, so Tab skipped it and a focused trigger lost focus when disabled; now it exposes aria-disabled="true", keeps focus, shows its keyboard focus outline, and still opens nothing. A press or key on it reaches no handler: not the toggle, not a caller's onClick or onKeyDown, and not an ancestor's click handler.

  • Keyboard and screen-reader users can now reach a disabled Calendar day or Pagination dot and hear that it is disabled. Disabled TreeList rows' expand toggle and action button, disabled Pagination dots and disabled Calendar days expose aria-disabled="true" instead of the native disabled attribute, while clicks, Enter and Space still do nothing and arrow keys still skip disabled days as before. No API changes.

  • DateInput: keyboard and screen-reader users can now reach a disabled date field and hear that it is disabled. A disabled or busy field no longer renders native disabled: its input and calendar button keep their place in the focus order and expose aria-disabled, while typing and opening stay blocked, and a focused disabled field shows its focus ring. Tests that query a disabled DateInput with toBeDisabled() or :disabled should check aria-disabled="true" instead.

  • DateRangeInput: keyboard and screen-reader users can now reach a disabled field or preset and hear that it is disabled. A disabled or busy field and a disabled preset no longer render native disabled: the trigger, calendar button and presets keep their place in the focus order and expose aria-disabled, while opening and selecting stay blocked, and a focused disabled field shows its focus ring. Tests that query them with toBeDisabled() or :disabled should check aria-disabled="true" instead (#7866).

  • DateTimeInput: keyboard and screen-reader users can now reach a disabled or busy field and hear that it is disabled. It no longer renders native disabled on its text, native-picker or touch fields, or on its calendar and clock buttons: they keep their place in the focus order and expose aria-disabled, while typing, picking, opening and a caller's onClick stay blocked, and a focused disabled field shows its focus ring. TimeInput's native picker button, which shares the time segment and stays out of the tab order, also moves from disabled to aria-disabled and stays inert. Tests that query a disabled DateTimeInput with toBeDisabled() or :disabled should check aria-disabled="true" instead.

  • Apps that use Core's prebuilt CSS without a StyleX build step now get the default data colors: the prebuilt CSS defines the canonical dataVars defaults, so compiled imports and raw CSS references paint correctly.

    Also released in @astryxdesign/cli.

  • Dialog, BottomSheet and BottomSheetSwitcher stay open when a file picker inside them is dismissed. A file input fires a bubbling cancel event when its picker closes without a choice, and all three took that event for a close request of their own and closed (a switcher closed its whole flow). They now act only on a cancel aimed at their own <dialog>, so Escape and platform close requests (the Android back gesture) behave as before. No API changes.

  • DropdownMenuRadioGroup now forwards ref, and its docs explain controlled selection, how to name the group, when the menu closes, and how it composes inside a DropdownMenu.

  • astryx init writes the agent docs at the project root, in files the project commits, so Codex, Gemini CLI, Copilot CLI and Claude Code all read the block from the project root or any folder below it (#7497). It no longer writes agent docs into a project's subfolder, the home folder, or a settings folder in it such as ~/.gemini.

    • AGENTS.md holds the block, created when missing. Before, a project with only CLAUDE.md got the block only there, so Codex and Gemini CLI never saw it.
    • Claude Code skips AGENTS.md in a folder that has a CLAUDE.md or .claude/CLAUDE.md, so the Claude Code file the project already has keeps a full copy: root CLAUDE.md, else .claude/CLAUDE.md. It keeps the copy even when it imports AGENTS.md, because Claude Code does not follow that import in a session started in a subfolder; started at the root, Claude Code then reads two. When both exist, the copy goes in root CLAUDE.md, which projects commit even when they ignore .claude/, and a block in .claude/CLAUDE.md is removed. Init creates no Claude file. astryx upgrade refreshes every copy, except in a project at the top of a home settings folder (see the next entry).
    • Gemini CLI reads only GEMINI.md: init adds a one-line import, @./AGENTS.md, creating GEMINI.md when it is missing. It adds none when GEMINI.md already imports AGENTS.md, inline or on its own line. It adds the import even when the project's .gemini/settings.json has Gemini read AGENTS.md through context.fileName: Gemini loads those settings only when it starts at the project root, so a session started in a subfolder would otherwise get no copy. Started at the root, Gemini then reads two.
    • The project root is the nearest folder at or above where init runs whose package.json declares an @astryxdesign package, else the nearest with any package.json. The search stops at a repository root and never counts the home folder. With no package.json found, init writes where it runs, as before. docsWritten still lists each file relative to where init ran, for example ../../AGENTS.md.
    • A GEMINI.md that resolves outside the project, for example a link to a shared file, is skipped and reported in docsError with one note (not an error line), in the default layout and in a project's own layout; the rest is written and init exits 0.
    • The managed block belongs to Astryx: init and astryx upgrade write, refresh, and remove only the block, whatever ignore rules or generated markers say, as 0.7.1 did, and never change the text around it. Every write stays inside the project: run in the folder 0.7.1 wrote, an agent file 0.7.1 wrote there whose path leaves the project still refuses the run with exit 1. The project's Claude Code file (root CLAUDE.md, or .claude/CLAUDE.md when there is no root one) that resolves outside the project and is a block-less import of an existing AGENTS.md, which 0.7.1 left alone, is skipped and reported in docsError with one note, and init exits 0. Run from a folder below the project root, where 0.7.1 wrote in that folder instead, a project-root file whose path leaves the project is skipped and reported in docsError with one note, the rest is written, and init exits 0.
    • While root CLAUDE.md is untracked and ignored by the repository's ignore rules (any ignore file from the repository root down, such as a monorepo's root .gitignore, the local exclude file, or configured excludes; init asks git, Sapling or Mercurial), a .claude/CLAUDE.md the project commits gets the copy too, so a fresh clone still gives Claude Code a copy; --agent claude and --agent all write it there as well. A file the repository tracks counts as committed whatever the ignore rules say, as git and Mercurial ignore only untracked files, and when the repository cannot answer, the copy is kept, on the first run as on later ones. Such a copy that resolves outside the project refuses the run with exit 1 where 0.7.1 wrote it too; one 0.7.1 did not write (the one --agent claude adds, or a block-less import of an existing AGENTS.md) is skipped and reported in docsError with one note, and init exits 0. So is a .claude/CLAUDE.md whose block --agent claude would remove beside a CLAUDE.md, since 0.7.1's claude preset wrote only CLAUDE.md there. Beside a committed root CLAUDE.md, with .claude linked outside the project, three cases that 0.7.1 refused now exit 0 with nothing written: the default with a .claude/CLAUDE.md of plain text, and --agent all with one of plain text or a block-less import of AGENTS.md; root CLAUDE.md holds the copy, so no write leaves the project. In a project that keeps its own layout, a GEMINI.md that imports no agent file now gets a copy, as the other agent files do.
    • The copy in .claude/CLAUDE.md is removed only once root CLAUDE.md holds the same block, and only the managed block between its markers: the rest of the file stays, and the file is deleted only when nothing but whitespace is left. astryx init --remove-agents deletes a file it created only when nothing but whitespace or init's own heading is left; 0.7.0 also deleted a file whose remaining text had a heading and any line with the word "guidance".
    • astryx init --remove-agents removes the block at the project root, where init writes it, and in the folder it runs in. Run from a subfolder, it removes what init wrote at the root, and a block an earlier version left in that subfolder; a root agent file with a block that resolves outside the project is left as it is with a note, and the run exits 0, as 0.7.1 did when it touched only the subfolder. Exit codes and the JSON shape are unchanged.
    • --agent claude and --agent all never leave a block in both CLAUDE.md and .claude/CLAUDE.md, unless the project ignores CLAUDE.md: when CLAUDE.md gets the block, a block in .claude/CLAUDE.md is removed. --agent all keeps the GEMINI.md import. The --agent help and the working-with-AI guide say so. --agent-docs-path writes where init runs, as before.
    • astryx doctor fails when the GEMINI.md import points at an AGENTS.md with no block. The setup check in the CLI and in @astryxdesign/core no longer counts that import as set up, and looks at GEMINI.md. The reminder to run init that commands print looks at the project root too, so it stops once init has run in a nested package.

    Also released in @astryxdesign/cli.

  • astryx init and astryx upgrade no longer write agent docs into your home folder, or into a settings folder in it such as ~/.gemini or ~/.claude outside a project. Agents read the files in those folders in every project, so a block there reached unrelated work. This follows the CLI's existing rules that no command writes agent docs into those folders and that a write outside a command's own folder is refused before anything is written.

    • Refused by init: the home folder itself, and the top of any folder in it whose name starts with a dot (every such folder is a settings folder), even when it holds a package.json. A folder below it with its own package.json is a project and is set up normally, such as an agent worktree in ~/.codex/worktrees/ or ~/.cursor/worktrees/, and astryx upgrade refreshes it. With --agent-docs-path, each file's own folder is checked: from the home folder, notes/AI.md is written as before, while AI.md or .claude/CLAUDE.md is refused.
    • What init does instead: it writes no agent docs, reports docsError as install-failed with the reason, and exits 0, as before.
    • Refused by upgrade: refreshing a block in a project whose root is the home folder or the top of a settings folder, such as ~/.dotfiles with a package.json, with or without --apply. It leaves the block as it is, sets agentDocs.action to error (never would-refresh), and warns with the folder and why; its other steps and its exit status are as before. With no block there, it gives the same reason instead of suggesting astryx init. astryx doctor's agent-docs fix does the same, and the setup reminder after each command stays quiet.
    • Migration: run astryx init from the project's own folder, or add the block to the file by hand. Move a project rooted at the home folder or a settings folder into a folder of its own, or update or remove its block by hand. To remove a block an earlier version wrote into ~/.claude/CLAUDE.md, run astryx init --remove-agents in the home folder.

    Also released in @astryxdesign/cli.

  • Keyboard and screen-reader users can now reach a disabled MultiSelector and hear that it is unavailable (WCAG 2.2 SC 2.1.1 and 4.1.2). Before, its trigger rendered the native disabled attribute and tabIndex={-1}, so Tab skipped it and a focused trigger lost focus when disabled; now it exposes aria-disabled="true", keeps focus, still opens nothing, and submits no form value. Like a natively disabled button, it runs no caller event handler other than focus and blur, and a press on it reaches no handler. A focused disabled trigger shows a keyboard focus ring, dimmed with the rest of the control (WCAG 2.2 SC 2.4.7).

  • Keyboard and screen-reader users can now reach a disabled Link or ClickableCard, and the buttons of a disabled Token. Before, they rendered the native disabled attribute or tabIndex={-1}, so Tab skipped them. Now each disabled control exposes aria-disabled="true" and keeps the Tab stop it has when enabled, while activation by click, Enter or Space, onClick, removal and navigation stay blocked; a disabled Link keeps no live destination and is exposed with the link role. A Popover no longer opens from a wrapped or anchorRef trigger marked aria-disabled="true" (for example a disabled Button with a tooltip), including for a click dispatched straight on the trigger; before, such a click opened it. No API changes.

  • Keyboard and screen-reader users can now reach a disabled Selector and hear that it is unavailable (WCAG 2.2 SC 2.1.1 and 4.1.2). Before, its trigger rendered the native disabled attribute and tabIndex={-1}, so Tab skipped it and a focused trigger lost focus when disabled; now it exposes aria-disabled="true", keeps focus, still opens nothing, and submits no form value. Like a natively disabled button, it runs no caller event handler other than focus and blur, and a press on it reaches no handler. A focused disabled trigger shows a keyboard focus ring, dimmed with the rest of the control (WCAG 2.2 SC 2.4.7).

  • Selector options in a narrow list now shrink and truncate a long label with an ellipsis instead of being clipped past the list edge.

  • Switch: a busy switch announces "Loading" once per busy start. Under React StrictMode the announcement used to be made twice.

  • TimeInput: keyboard and screen-reader users can now reach a disabled field, or a busy bottom-sheet field, and hear that it is disabled. It no longer renders native disabled on its typed, native-picker or touch input or its picker button: they keep their place in the focus order and expose aria-disabled, while typing, arrow-key adjustment, opening, submitting a surrounding form with Enter, and a caller's onClick stay blocked, a draft typed before the field became disabled is dropped, and a focused disabled field shows its focus ring. Tests that query a disabled or busy TimeInput with toBeDisabled() or :disabled should check aria-disabled="true" instead.

  • Pages that use Timestamp with an absolute format (date, time, date_time and the other fixed formats) now prerender in Next.js with cacheComponents turned on, where they used to fail the build: Timestamp no longer reads the current time while rendering on the server, and absolute formats never read the clock. relative, relative_short and auto read the clock on the client only: a server render or static prerender shows the full date and time in UTC, labelled, and the client switches to the format's normal output right after hydration, so server and client markup now always match. Client-only rendering is unchanged. No API changes (#7883).

  • Tooltip: moving the pointer straight from the trigger onto the tooltip in one step no longer hides it, so it stays open while hovered (WCAG 2.2 SC 1.4.13).

  • A SideNavItem in a collapsed SideNav now shows its tooltip on hover and gets aria-describedby, even when the item first appears inside an already-collapsed SideNav, the common case for saved collapse state. Before, only an item that collapsed after it appeared got a tooltip. Fixes #7692.

    A Tooltip with an anchorRef that renders inside the very element the ref points at, as SideNavItem does, now attaches however it mounted. The effect now re-checks the anchor once the commit has finished attaching refs; event listeners and the aria-describedby merge and restore are unchanged.

  • TopNav on narrow screens: a long TopNavHeading now gives way (it truncates with an ellipsis, or wraps when the heading is a link) instead of pushing the end content and the menu toggle past the edge of the bar, and the mobile drawer's close button is back on screen.

  • Typeahead: clearing the value while editing it now empties the input and reports onChangeQuery(''), instead of leaving the old label in a field with no value. Fixes #7038.

Performance

  • Pages with many code blocks, such as a long Markdown document, paint sooner on first load: CodeBlock inserts its syntax-highlight rules before the first layout read instead of after it, which cut first paint by 16–19% in 100–500 KB test documents (#7875). The rules and the highlighted output are unchanged.

  • Pages that update around a long Markdown document stay responsive: Markdown skips re-rendering its document when a parent re-renders without changing the text or any rendering prop, and a link handler passed as a new function on each render no longer forces that work. The rendered output is unchanged.

Documentation

  • The DropdownMenuItem docs now explain when an item is an action or a destination, disabled items, and supporting content.

@astryxdesign/cli

New Features

  • astryx build names the right block first far more often. It ranks every block in the catalog by what the request means, read from each block's own name, description, and component, so a block that word matching missed can still come first, with its own score and reason; and the block picker's fixed values are refitted on a larger, more varied set of written example requests, which helps most with requests worded in a product's own terms. A block the request names by its exact name, and a block from an integration, keep their place. The kit never lists more blocks than before, and its fields, page call, start template, and alternatives are unchanged.

  • astryx search puts the entry you are after first more often: it orders the matches inside each score band, every result keeps its match score and reason, and a query that is an entry's exact name still answers with that entry first (#7752).

  • astryx doctor integration validate and astryx integration check check the component names a package's docs and blocks give, and list what they find as notes beside validate's issues, each with its fix. Nothing counts a note and integration check --strict does not fail on them; they become failing warnings in the next minor release:

    • doc_name_mismatch lists a component doc whose name is not the name the component is listed under when the module its printed import names (the doc's import, the published subpath, or the package root) provably does not export that name, so astryx component would print a broken import. The Fix: proposes the file name when that module exports it, otherwise the component it exports. A name that module's exports cannot prove or rule out (a re-export from another package, export =, a destructured binding, a namespace, an import that resolves to no file, or, when no import can be resolved, a source that only exports a default) is listed as not checked, with the reason, in validate's new docNames report.
    • unknown_block_component lists a name a template's exampleFor or componentsUsed gives that neither the package nor Core has (every doc astryx component resolves in Core counts) and the template does not import it. Imports the template's local helpers make count too, including through a barrel such as parts/index.ts. A name is judged only where the template reaches components solely through named imports and re-exports from Core, React, StyleX, local helpers read in full, or the package's own name (followed through its exports like a helper). Anything else is listed as not checked in validate's new blockComponents report: an import from another package, which may render other components; any default binding other than React's or StyleX's; a namespace import; an alias of or member read off an imported binding; a component held in a variable; require(); a dynamic import(); a file that does not parse cleanly; a local module this check cannot read; and, without Core installed, any name only Core could resolve.

    integration check --strict does not fail on anything listed as not checked.

  • New @astryxdesign/cli/host entry for tools that carry the CLI: runHostedCli runs the CLI once per process, with the command name that tool's users type (printed commands start with it) and, when the tool skipped the project's own CLI, the reason, so that CLI is never run.

    import {runHostedCli} from '@astryxdesign/cli/host';
    
    const result = await runHostedCli({
      argv: ['search', 'button'],
      cliCommand: 'acme astryx',
    });
  • astryx doctor integration validate now notes, from source and without a build, what an integration package tells apps to import that they cannot import, the way it lists an ignored cliCommand. Each note is in data.notes and names its fix, such as the exact exports entry to add. Notes are listed only: neither doctor integration validate nor astryx integration check --strict counts or fails on them in 0.7.3, and they become failing warnings in the next minor release, so fix them now. It notes:

    • a component's import (the doc's import, or the one the CLI derives when the doc has none) or a template's public import whose subpath package.json exports does not declare, or that names another package. A subpath is not declared by a missing key, a key whose only conditions an ES module import does not load (such as require or types), an exports that is a single string, a wildcard that does not match, or an empty exports map;
    • a component example that imports a subpath the package does not declare;
    • a template that imports a relative path outside the templates root, or a path its own package or Core does not export.

    A subpath is judged from its exports declaration alone, never by its target file, so exports may point at build output that does not exist yet; whether that module exports the names an import asks for is the next entry's check. Wildcards and condition objects with import or default pass. An import of a subpath whose exports entry has only types targets, or that an ES module import reaches only through types (such as types beside require), is never judged as a run-time import: it passes when it is import type, when every binding is an inline type, or when every name it imports is one the types file declares as a type, and is listed under unchecked otherwise. A types target under require does not count, and a package with no exports keeps its deep paths to files it ships. What it cannot judge (in a package with no exports, a deep path into build output not built yet, such as dist/; a template import of Core when @astryxdesign/core is not installed beside the package; every import of the package's own name when package.json has no name) is listed under unchecked in a new publicImports report on the result ({status: 'checked', findings: [], unchecked: [{file, specifier, reason}]}), which integration check counts as not compared.

  • astryx doctor integration validate now also notes, from source and without a build, an import that names something its module does not export (in data.notes, each with its fix). These notes are listed only in 0.7.3 too, and become failing warnings in the next minor release. It notes:

    • a component's import (the doc's import, or the one the CLI derives) that leads to a module that does not export the component, naming the subpath that does or the exports entry to add;
    • a component example or a template that imports a name the package does not export;
    • a template source with no default export (astryx template still copies it, but its public import and anything that renders it get nothing), suggesting an exported PascalCase function, class or const, never a hook such as useAccount, a helper such as formatBalance, an ALL-CAPS constant or a type; with none, the fix stays generic.

    A name is judged only when a file the exports entry names is there to read in full. export * is followed, cycles included; a built module's declaration file (x.d.ts beside x.js, and the .d.ts files its export * names) is read for the types it exports; export import Alias = Inner.Value counts as an export; TypeScript source resolves a ./x.js specifier to ./x.ts; and a declaration file that is a module exports every top-level declaration unless it has an export list, export *, export = or a default export of an expression, as TypeScript does. Whatever the reader does not fully model is listed under unchecked in the publicImports report instead of as a note: a file the exports entry names that does not exist yet; a module whose list cannot be read in full (CommonJS, export * from another package); a declaration file with no import or export (a global script); a name missing from a JavaScript module with no declaration file, which may be a type; and every name it would report, other than a component doc's own component, while a TypeScript file in the package (outside node_modules) has a parsed declare module block for a relative path, the package's own name or a subpath of it, since that can add names to the module. The reason names that file; declare global, a wildcard spec such as '*.css', another package's name and a declare module in a comment do not count, and a file with declare module in its text that cannot be parsed counts. astryx docs authoring/ship/checks explains each check.

  • A failed astryx component, template or docs lookup lists the entry you most likely meant first. Components that tie on match score are ordered by how early the name you typed appears in their keywords and by how close their names are, instead of alphabetically; among the suggestions offered, a component one of whose other names was typed (including another library's name for it, such as MUI TextField, with or without the library's name) comes first; a compound name that does not exist (a family part named with other words, or without one of its family's words) suggests the real part or its family. Templates are ordered by how closely their id or title matches the name you typed, and unknown docs routes by how closely the route matches. Suggestion lists never get longer, every suggestion is a real entry, and a name that resolved before still resolves to the same entry.

  • Integration docs that tell readers to run an Astryx command through a package runner now get a runner_command note from doctor integration validate (and so integration check) in data.notes, with its file, line and the bare astryx <command> to write instead. A note fails nothing, so integration check --strict exits as before. The unscoped name after a download runner (npx astryx) always counts, because it fetches an unrelated package; the scoped npx @astryxdesign/cli, for install steps, does not.

Fixes

  • astryx build starts from a page template its word weights do not list (an integration's template, or one added since the weights were fit) only when the request names as many words of that template's own display name, category and keywords (search's stopwords aside) as of the best listed template, or names that template's family (the first part of its category) before the listed template's family and at most two fewer of its words; otherwise it starts from that listed template and offers the other as the first alternative.

  • The CLI finds the project's @astryxdesign/core from any folder inside the project, so component, hook, build, search, swizzle, theme targets and doctor now work five or more folders deep, in workspace members whose Core the workspace installs at its root, and in Yarn Plug'n'Play projects. They still search the folders they searched before, and past them continue up to the workspace root of the nearest folder whose package.json declares an Astryx package.

    • upgrade --from run from any folder in a project upgrades that project: the nearest folder whose package.json declares an Astryx package or that an Astryx workspace lists, found without passing a repository root or counting the home folder. It targets only the project's own Core (the one in its node_modules, or for a member its workspace lists, the one the workspace installs), scans the project's src by default, reads a --path you type from the folder you ran in, names the files in its --json result relative to that folder, refreshes the project's agent docs, and names the project root and the source folder it scans before it changes anything. With no such project, upgrade reads only the current folder, as before.
    • An upgrade run in a folder that installs Core but declares no Astryx package now upgrades the Astryx project above it instead of that folder alone, since such a folder was never a separate upgrade root. To keep a sub-folder that installs Core its own project, declare an Astryx package in that folder's package.json.
    • A packages/core folder that holds another package is no longer read as Core, and an integration named in astryx.config now loads when the workspace installs it at its root, as an autolinked one already did.
  • The CLI's install advice no longer adds a package to a package.json that is not an Astryx project's. This covers the daily hint, the setup reminder, the update notice, astryx doctor's version-alignment and integration fixes, and astryx discover's install command for an integration installed for the computer. The project is the nearest folder whose package.json declares an Astryx package, or a member its workspace lists, never the home folder and never past a repository root.

    • From a folder inside such a project whose own package.json is not the project's, such as a tool folder that installs its own @astryxdesign/core, the daily hint that matches a project's CLI to its Core, the setup reminder and astryx doctor's version-alignment fix used to name a command that added the CLI to that folder's package.json, making it a separate Astryx project. They still compare the CLI with the Core the run reads, and when the two differ they say to run from the project's folder, or to declare an Astryx package in that folder; the update notice upgrades the project from its folder, with the project's release.
    • For an integration installed for the computer, astryx discover gives no install command from such a folder, and astryx doctor's fix says to run from the project's folder, where the command adds it for the project; in a folder that belongs to no project, neither names a command.
    • In a folder that belongs to no Astryx project, the hint, the setup reminder and the update notice no longer print, and astryx doctor's fix says how to reach a project.
    • Unchanged: the official packages astryx discover lists and the theme packages astryx doctor suggests still use the folder the command runs in; a member its workspace lists keeps its own advice; in a folder whose package.json declares an Astryx package, the advice is the same. No JSON shape or exit code changes.
  • astryx doctor integration validate, astryx integration check and astryx integration verify catch more package mistakes, and each new finding ends in a Fix: that clears it. Findings that 0.7.2 did not fail on are listed as notes beside validate's issues, which nothing counts, until the next minor:

    • Two docs for one component (AcmeWidget.doc.mjs and AcmeWidget.doc.js) are listed as duplicate_component_doc, naming the one read; discovery now always reads the same one.
    • A component doc whose file name differs from its source only in letter case is one finding that says which file to rename: an error where the pair fails (Linux), a listed source_without_component_doc where it still pairs (a case-insensitive disk); a hidden doc that does not pair keeps the source warning it had.
    • A root reached through a symlinked folder is listed as symlinked_root: npm pack does not follow it.
    • An absolute root says roots are relative and gives the relative path; a root that is a file is reported by every check instead of crashing it.
    • A package.json without name or version is listed with a fix, instead of a crash; verify fails on it. A package whose name and manifest providerId give no provider ID is listed as no_provider_id.
    • A contribution file Astryx cannot read is listed as unreadable_file, before npm pack fails on it.

    astryx integration check --strict does not fail on these pairing, root and package.json notes either; they become failures in the next minor. astryx integration verify still fails on a package.json npm cannot pack.

  • astryx doctor integration validate, astryx integration check and astryx integration verify say more plainly what is wrong in findings they already reported, and how to fix it:

    • An empty doc or manifest, or one with no default export, says so and names what it does export, instead of "(root): Invalid input".
    • A manifest key one or two letters off a known key asks "did you mean components?" before suggesting a newer CLI.
    • A theme descriptor named for the built file (ocean.doc.mjs beside a built ocean.js that lacks the ocean export) says it must share its stem with the defineTheme source, naming that source when the built module's Source: line or the source's defineTheme name confirms it.
    • A template doc with no source, a theme descriptor with no source, a theme source with no descriptor, and a component whose source is .jsx get a Fix:.
    • A theme source named for its theme (brand.ts) beside the module the build writes (brand.js) is told to take the brandTheme stem.
    • verify names packed files by their path in the package, not the scratch copy's; says npm pack does not follow a symlinked root instead of asking for a files entry; and, when a theme's defineTheme name differs from the files its package exports, says a rebuild writes files under the new name.
  • Integration authors now learn when the running CLI is outside their package's @astryxdesign/cli peer range: astryx doctor integration validate and astryx integration check list a cli_peer_version note naming the package, the range and the running version. Below the range the note gives the project's commands to install a newer @astryxdesign/core and @astryxdesign/cli together (as dev dependencies in the package's own folder) and then run astryx upgrade --from <version> --apply. Above the range it asks for a release of an installed package that supports this CLI, or asks the author of the package in the current folder to test with this CLI and widen the range. A note fails nothing: --strict, integration verify, doctor, and exit codes are unchanged.

  • astryx component <Member> --package @astryxdesign/core now answers with that member, as the unscoped lookup does. A Core component documented in its family's doc (HStack and VStack in the Stack doc) used to come back as the family doc, named after the family and with no props.

  • Agents that follow the guide global mode writes to the home folder now know what to do when astryx is not found: run the command that installed the CLI for the computer first, with the same package manager and folder, such as npm i -g @astryxdesign/cli or pnpm add -g @astryxdesign/cli. On machines that keep the home folder but reset system folders on restart, the guide outlived the CLI installed for the computer and told agents to run a command that was gone. Outside a project, when the guide names astryx and no astryx command is on the search path, apart from a package runner's own copy, astryx doctor now says so and names the install step the guide records, or npm's when it records none. The global-mode guide for CI and containers explains how to keep the CLI across restarts. A guide written for a host's own command is unchanged. No JSON shape or exit code changes.

  • Repeated searches in one process answer faster: astryx search reuses its entry index while the catalog is unchanged, and ties that reuse to one list of the fields the index reads, so the index is never reused after any of them changes. Every answer is unchanged.

  • Tools that bundle the CLI into a server function no longer pack their whole project folder into it: one path in integration check's import checks was built in a way file tracers cannot follow. The path and its results are unchanged.

  • search() from @astryxdesign/cli/api answers faster when one process searches again: it reuses the components, docs, templates and themes it gathered for the same folder and type while no file it read, and no folder it listed, has changed since (a linked workspace package included). To see those reads, the first such search in a process wraps node:fs's read functions for that process; outside a search they behave exactly as before. Results are unchanged. Short-lived runs are unaffected: each astryx search command gathers once, as before (#7830).

  • astryx search and astryx build find a block by its authored display name when that name has two words or more and holds every word of the component the block shows: a query naming it, such as "rich text editor" for an integration's Rich Text Editor block, ranks it first. A display name that labels only the variant, such as "Inside a card", is not read.

Documentation

  • astryx docs cli/commands/postinstall now documents the hidden postinstall hook, astryx docs cli/commands lists it, and astryx postinstall --help shows its exit code and an example. The summary of astryx docs cli/commands/manifest (text and --json) drops its final period, so it matches the description the manifest has always printed. What each command prints when it runs, its JSON and its exit codes are unchanged.

  • Everything an integration author reads now lives under one home, astryx docs authoring: the integration guides' routes moved from cli/integrations/… to authoring/… (for example, cli/integrations/quick-start is now authoring/quick-start), beside the field reference at astryx docs authoring/field-reference. astryx docs authoring <name> still opens one field reference section, and an old cli/integrations/… route's error names its new route. The loose topics now sit in Guides or Foundations instead of Unorganized, under the same names: astryx docs getting-started still opens, and its Up is astryx docs guides.

  • A new guide, astryx docs is-my-code-good, checks component use, icons, custom styles, layout, assets, and code health from source and rendered evidence, in one page you can share. It includes an agent prompt and requires missing evidence to stay visible. The template grading rubric now links each category to these shared criteria instead of repeating them, and keeps its own weights, counting rules, and version 1.4.

  • Questions like "add an integration" or "install a package" now find a guide for app builders first. Added a "Use integrations" guide (use-integrations.doc.mjs) that covers discovering, installing, and using integration packages in an app; open it with astryx docs use-integrations. Search ranks it above authoring and API-function docs for those questions, while queries like "build an integration" still rank authoring content first.

Themes (@astryxdesign/theme-*)

Fixes

  • The butter, stone, and y2k themes no longer show the secondary hover fill on a disabled ComplexSelector trigger, whether the trigger is natively disabled or exposes aria-disabled="true", the focusable form every disabled control now uses. Before, the guard only recognized a natively disabled trigger. The themes astryx theme eject copies carry the same guard.

    Also released in @astryxdesign/cli (templates).

Contributors

Thanks to everyone who contributed to this release:

@aashish254 @cixzhang @ejhammond @HelloOjasMutreja @josephfarina @kyu-rong @Pushpak731 @vjeux

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

2 hours ago
Chart.js

v4.6.1

Essential Links

  • #12355 fix: trusted publishing fix + version 4.6.1 bump

Thanks to @etimberg

2 hours ago
snapdom

v3.3.7

What's Changed

New Contributors

Full Changelog: https://github.com/zumerlab/snapdom/compare/v3.3.6...v3.3.7

2 hours ago
Chart.js

v4.6.0

Essential Links

  • #12354 chore: version bump to 4.6.0 to prepare for release
  • #12328 test: stabilize overlapping polar-area label fixture

Enhancements

  • #12238 Corrected spacing in pie/doughnut/polarArea (Fixes #10059)
  • #12130 Enable scale title stroke (12092)

Bugs Fixed

  • #12283 filler plugin fill extends past line when offset: true + index scale min/max
  • #12155 Fix early exit from clearStacks
  • #12142 Use integer canvas dimensions

Types

  • #12321 fix(types): allow scriptable and indexed grid tick widths

Documentation

  • #12258 Add a note that axis labels can span lines
  • #12134 docs: Add note about flexbox/grid layout responsivity issue
  • #12139 Fix amount of category scales count in docs

Development

  • #12306 Bump actions/setup-node from 6 to 7
  • #12303 Bump actions/checkout from 6 to 7
  • #12307 Bump pnpm/action-setup from 4.2.0 to 6.1.0
  • #12310 Specify build script for compressed size
  • #12309 Use node 24 for CI
  • #12206 Bump rollup from 3.20.2 to 3.30.0
  • #12207 Bump minimatch from 3.1.2 to 3.1.5
  • #12163 Bump preactjs/compressed-size-action from 2 to 3
  • #12160 Bump actions/checkout from 4 to 6
  • #12159 Bump js-yaml from 3.14.1 to 3.14.2
  • #12138 Bump actions/setup-node from 5 to 6
  • #12133 Bump pnpm/action-setup from 4.1.0 to 4.2.0

Thanks to @DrWarpMan, @LeeLenaleee, @asmenezes, @danielgindi, @dependabot[bot], @etimberg, @jakezwang, @joshkel, @markw65, @sarathkumarsasi and dependabot[bot]

5 hours ago
paint-board

3.1.0

Please refer to CHANGELOG.md for details.

7 hours ago
virtual

@tanstack/svelte-virtual@3.13.48

Patch Changes

  • Updated dependencies [887e971, 5d51f1d]:
    • @tanstack/virtual-core@3.19.7
7 hours ago
virtual

@tanstack/marko-virtual@3.16.8

Patch Changes

  • Updated dependencies [887e971, 5d51f1d]:
    • @tanstack/virtual-core@3.19.7
7 hours ago
virtual

@tanstack/solid-virtual@3.13.49

Patch Changes

  • Updated dependencies [887e971, 5d51f1d]:
    • @tanstack/virtual-core@3.19.7
7 hours ago
virtual

@tanstack/react-virtual@3.14.22

Patch Changes

  • Updated dependencies [887e971, 5d51f1d]:
    • @tanstack/virtual-core@3.19.7
7 hours ago
virtual

@tanstack/lit-virtual@3.14.11

Patch Changes

  • Updated dependencies [887e971, 5d51f1d]:
    • @tanstack/virtual-core@3.19.7