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 .
-
DropdownMenuSubMenutakesheaderandfooter, as the rootDropdownMenudoes, 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 saysaria-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. AContextMenudoes not show a sub-menu'sheaderorfooter. 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>
-
TimeInput's arrow keys ignore a fractional increment instead of writing a malformed time such as "14:30.5":adjustTimereturns the time unchanged for a fractional minute delta (#7823). -
Screen readers now announce a
Buttonthat is first rendered withisLoading(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 arole="alert"orrole="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 aChatMessageListnow 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 forcesrole="status", in either variant, so date and divider rows are no longer presented as status messages, and arolepassed to the component reaches its root. Tests that found a system message by thestatusrole should query it by its text instead. -
Keyboard and screen-reader users can now reach disabled
CheckboxInput,CheckboxListitems,Switch,SliderandFileInput, and hear that they are disabled. Before, they rendered the nativedisabledattribute unless adisabledMessagewas set, so Tab skipped them. Now every disabled control exposesaria-disabled="true"and keeps the Tab stop it has when enabled, while toggling, selecting, dragging, opening the file picker and anyonClickstay 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/coreand@astryxdesign/clito a new project or one without Core, or, in a project that already has@astryxdesign/core, the CLI at the same version; runastryx initthere; 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
ComplexSelectorand hear that it is unavailable (WCAG 2.2 SC 2.1.1 and 4.1.2). Before, its trigger rendered the nativedisabledattribute, so Tab skipped it and a focused trigger lost focus when disabled; now it exposesaria-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'sonClickoronKeyDown, and not an ancestor's click handler. -
Keyboard and screen-reader users can now reach a disabled
Calendarday orPaginationdot and hear that it is disabled. DisabledTreeListrows' expand toggle and action button, disabledPaginationdots and disabledCalendardays exposearia-disabled="true"instead of the nativedisabledattribute, 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 nativedisabled: its input and calendar button keep their place in the focus order and exposearia-disabled, while typing and opening stay blocked, and a focused disabled field shows its focus ring. Tests that query a disabled DateInput withtoBeDisabled()or:disabledshould checkaria-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 nativedisabled: the trigger, calendar button and presets keep their place in the focus order and exposearia-disabled, while opening and selecting stay blocked, and a focused disabled field shows its focus ring. Tests that query them withtoBeDisabled()or:disabledshould checkaria-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 nativedisabledon its text, native-picker or touch fields, or on its calendar and clock buttons: they keep their place in the focus order and exposearia-disabled, while typing, picking, opening and a caller'sonClickstay 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 fromdisabledtoaria-disabledand stays inert. Tests that query a disabled DateTimeInput withtoBeDisabled()or:disabledshould checkaria-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
dataVarsdefaults, so compiled imports and raw CSS references paint correctly.Also released in
@astryxdesign/cli. -
Dialog,BottomSheetandBottomSheetSwitcherstay open when a file picker inside them is dismissed. A file input fires a bubblingcancelevent 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 acancelaimed at their own<dialog>, so Escape and platform close requests (the Android back gesture) behave as before. No API changes. -
DropdownMenuRadioGroupnow forwardsref, and its docs explain controlled selection, how to name the group, when the menu closes, and how it composes inside aDropdownMenu. -
astryx initwrites 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.mdholds the block, created when missing. Before, a project with onlyCLAUDE.mdgot the block only there, so Codex and Gemini CLI never saw it.- Claude Code skips
AGENTS.mdin a folder that has aCLAUDE.mdor.claude/CLAUDE.md, so the Claude Code file the project already has keeps a full copy: rootCLAUDE.md, else.claude/CLAUDE.md. It keeps the copy even when it importsAGENTS.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 rootCLAUDE.md, which projects commit even when they ignore.claude/, and a block in.claude/CLAUDE.mdis removed. Init creates no Claude file.astryx upgraderefreshes 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, creatingGEMINI.mdwhen it is missing. It adds none whenGEMINI.mdalready importsAGENTS.md, inline or on its own line. It adds the import even when the project's.gemini/settings.jsonhas Gemini readAGENTS.mdthroughcontext.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.jsondeclares an@astryxdesignpackage, else the nearest with anypackage.json. The search stops at a repository root and never counts the home folder. With nopackage.jsonfound, init writes where it runs, as before.docsWrittenstill lists each file relative to where init ran, for example../../AGENTS.md. - A
GEMINI.mdthat resolves outside the project, for example a link to a shared file, is skipped and reported indocsErrorwith 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 upgradewrite, 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 (rootCLAUDE.md, or.claude/CLAUDE.mdwhen there is no root one) that resolves outside the project and is a block-less import of an existingAGENTS.md, which 0.7.1 left alone, is skipped and reported indocsErrorwith 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 indocsErrorwith one note, the rest is written, and init exits 0. - While root
CLAUDE.mdis 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.mdthe project commits gets the copy too, so a fresh clone still gives Claude Code a copy;--agent claudeand--agent allwrite 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 claudeadds, or a block-less import of an existingAGENTS.md) is skipped and reported indocsErrorwith one note, and init exits 0. So is a.claude/CLAUDE.mdwhose block--agent claudewould remove beside aCLAUDE.md, since 0.7.1'sclaudepreset wrote onlyCLAUDE.mdthere. Beside a committed rootCLAUDE.md, with.claudelinked outside the project, three cases that 0.7.1 refused now exit 0 with nothing written: the default with a.claude/CLAUDE.mdof plain text, and--agent allwith one of plain text or a block-less import ofAGENTS.md; rootCLAUDE.mdholds the copy, so no write leaves the project. In a project that keeps its own layout, aGEMINI.mdthat imports no agent file now gets a copy, as the other agent files do. - The copy in
.claude/CLAUDE.mdis removed only once rootCLAUDE.mdholds 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-agentsdeletes 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-agentsremoves the block at the project root, whereinitwrites it, and in the folder it runs in. Run from a subfolder, it removes whatinitwrote 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 claudeand--agent allnever leave a block in bothCLAUDE.mdand.claude/CLAUDE.md, unless the project ignoresCLAUDE.md: whenCLAUDE.mdgets the block, a block in.claude/CLAUDE.mdis removed.--agent allkeeps theGEMINI.mdimport. The--agenthelp and the working-with-AI guide say so.--agent-docs-pathwrites where init runs, as before.astryx doctorfails when theGEMINI.mdimport points at anAGENTS.mdwith no block. The setup check in the CLI and in@astryxdesign/coreno longer counts that import as set up, and looks atGEMINI.md. The reminder to runinitthat commands print looks at the project root too, so it stops onceinithas run in a nested package.
Also released in
@astryxdesign/cli. -
astryx initandastryx upgradeno longer write agent docs into your home folder, or into a settings folder in it such as~/.geminior~/.claudeoutside 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 apackage.json. A folder below it with its ownpackage.jsonis a project and is set up normally, such as an agent worktree in~/.codex/worktrees/or~/.cursor/worktrees/, andastryx upgraderefreshes it. With--agent-docs-path, each file's own folder is checked: from the home folder,notes/AI.mdis written as before, whileAI.mdor.claude/CLAUDE.mdis refused. - What
initdoes instead: it writes no agent docs, reportsdocsErrorasinstall-failedwith 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~/.dotfileswith apackage.json, with or without--apply. It leaves the block as it is, setsagentDocs.actiontoerror(neverwould-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 suggestingastryx init.astryx doctor's agent-docs fix does the same, and the setup reminder after each command stays quiet. - Migration: run
astryx initfrom 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, runastryx init --remove-agentsin the home folder.
Also released in
@astryxdesign/cli. - Refused by
-
Keyboard and screen-reader users can now reach a disabled
MultiSelectorand hear that it is unavailable (WCAG 2.2 SC 2.1.1 and 4.1.2). Before, its trigger rendered the nativedisabledattribute andtabIndex={-1}, so Tab skipped it and a focused trigger lost focus when disabled; now it exposesaria-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
LinkorClickableCard, and the buttons of a disabledToken. Before, they rendered the nativedisabledattribute ortabIndex={-1}, so Tab skipped them. Now each disabled control exposesaria-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. APopoverno longer opens from a wrapped oranchorReftrigger markedaria-disabled="true"(for example a disabledButtonwith atooltip), 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
Selectorand hear that it is unavailable (WCAG 2.2 SC 2.1.1 and 4.1.2). Before, its trigger rendered the nativedisabledattribute andtabIndex={-1}, so Tab skipped it and a focused trigger lost focus when disabled; now it exposesaria-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). -
Selectoroptions 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 nativedisabledon its typed, native-picker or touch input or its picker button: they keep their place in the focus order and exposearia-disabled, while typing, arrow-key adjustment, opening, submitting a surrounding form with Enter, and a caller'sonClickstay 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 withtoBeDisabled()or:disabledshould checkaria-disabled="true"instead. -
Pages that use
Timestampwith an absolute format (date,time,date_timeand the other fixed formats) now prerender in Next.js withcacheComponentsturned on, where they used to fail the build:Timestampno longer reads the current time while rendering on the server, and absolute formats never read the clock.relative,relative_shortandautoread 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
SideNavItemin a collapsedSideNavnow shows its tooltip on hover and getsaria-describedby, even when the item first appears inside an already-collapsedSideNav, the common case for saved collapse state. Before, only an item that collapsed after it appeared got a tooltip. Fixes #7692.A
Tooltipwith ananchorRefthat renders inside the very element the ref points at, asSideNavItemdoes, now attaches however it mounted. The effect now re-checks the anchor once the commit has finished attaching refs; event listeners and thearia-describedbymerge and restore are unchanged. -
TopNavon narrow screens: a longTopNavHeadingnow 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 reportsonChangeQuery(''), instead of leaving the old label in a field with no value. Fixes #7038.
-
Pages with many code blocks, such as a long Markdown document, paint sooner on first load:
CodeBlockinserts 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
Markdowndocument 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.
- The
DropdownMenuItemdocs now explain when an item is an action or a destination, disabled items, and supporting content.
-
astryx buildnames 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 searchputs 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 validateandastryx integration checkcheck 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 andintegration check --strictdoes not fail on them; they become failing warnings in the next minor release:doc_name_mismatchlists a component doc whosenameis not the name the component is listed under when the module its printed import names (the doc'simport, the published subpath, or the package root) provably does not export that name, soastryx componentwould print a broken import. TheFix: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 newdocNamesreport.unknown_block_componentlists a name a template'sexampleFororcomponentsUsedgives that neither the package nor Core has (every docastryx componentresolves in Core counts) and the template does not import it. Imports the template's local helpers make count too, including through a barrel such asparts/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 itsexportslike a helper). Anything else is listed as not checked in validate's newblockComponentsreport: an import from another package, which may render other components; anydefaultbinding 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 dynamicimport(); 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 --strictdoes not fail on anything listed as not checked. -
New
@astryxdesign/cli/hostentry for tools that carry the CLI:runHostedCliruns 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 validatenow notes, from source and without a build, what an integration package tells apps to import that they cannot import, the way it lists an ignoredcliCommand. Each note is indata.notesand names its fix, such as the exactexportsentry to add. Notes are listed only: neitherdoctor integration validatenorastryx integration check --strictcounts 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.jsonexportsdoes 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 asrequireortypes), anexportsthat is a single string, a wildcard that does not match, or an emptyexportsmap; - 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
exportsdeclaration alone, never by its target file, soexportsmay 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 withimportordefaultpass. An import of a subpath whoseexportsentry has onlytypestargets, or that an ES module import reaches only throughtypes(such astypesbesiderequire), is never judged as a run-time import: it passes when it isimport type, when every binding is an inlinetype, or when every name it imports is one thetypesfile declares as a type, and is listed underuncheckedotherwise. Atypestarget underrequiredoes not count, and a package with noexportskeeps its deep paths to files it ships. What it cannot judge (in a package with noexports, a deep path into build output not built yet, such asdist/; a template import of Core when@astryxdesign/coreis not installed beside the package; every import of the package's own name when package.json has noname) is listed underuncheckedin a newpublicImportsreport on the result ({status: 'checked', findings: [], unchecked: [{file, specifier, reason}]}), whichintegration checkcounts as not compared. - a component's import (the doc's
-
astryx doctor integration validatenow also notes, from source and without a build, an import that names something its module does not export (indata.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 theexportsentry 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 templatestill copies it, but its public import and anything that renders it get nothing), suggesting an exported PascalCase function, class orconst, never a hook such asuseAccount, a helper such asformatBalance, an ALL-CAPS constant or a type; with none, the fix stays generic.
A name is judged only when a file the
exportsentry names is there to read in full.export *is followed, cycles included; a built module's declaration file (x.d.tsbesidex.js, and the.d.tsfiles itsexport *names) is read for the types it exports;export import Alias = Inner.Valuecounts as an export; TypeScript source resolves a./x.jsspecifier 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 underuncheckedin thepublicImportsreport instead of as a note: a file theexportsentry 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 (outsidenode_modules) has a parseddeclare moduleblock 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 adeclare modulein a comment do not count, and a file withdeclare modulein its text that cannot be parsed counts.astryx docs authoring/ship/checksexplains each check. - a component's import (the doc's
-
A failed
astryx component,templateordocslookup 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 asMUI 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_commandnote fromdoctor integration validate(and sointegration check) indata.notes, with its file, line and the bareastryx <command>to write instead. A note fails nothing, sointegration check --strictexits as before. The unscoped name after a download runner (npx astryx) always counts, because it fetches an unrelated package; the scopednpx @astryxdesign/cli, for install steps, does not.
-
astryx buildstarts 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/corefrom any folder inside the project, socomponent,hook,build,search,swizzle,theme targetsanddoctornow 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 --fromrun 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 itsnode_modules, or for a member its workspace lists, the one the workspace installs), scans the project'ssrcby default, reads a--pathyou type from the folder you ran in, names the files in its--jsonresult 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,upgradereads only the current folder, as before.- An
upgraderun 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/corefolder that holds another package is no longer read as Core, and an integration named inastryx.confignow 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.jsonthat 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, andastryx discover's install command for an integration installed for the computer. The project is the nearest folder whosepackage.jsondeclares 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.jsonis 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 andastryx doctor's version-alignment fix used to name a command that added the CLI to that folder'spackage.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 discovergives no install command from such a folder, andastryx 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 discoverlists and the theme packagesastryx doctorsuggests still use the folder the command runs in; a member its workspace lists keeps its own advice; in a folder whosepackage.jsondeclares an Astryx package, the advice is the same. No JSON shape or exit code changes.
- From a folder inside such a project whose own
-
astryx doctor integration validate,astryx integration checkandastryx integration verifycatch more package mistakes, and each new finding ends in aFix: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.mjsandAcmeWidget.doc.js) are listed asduplicate_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_docwhere 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
nameorversionis listed with a fix, instead of a crash;verifyfails on it. A package whosenameand manifestproviderIdgive no provider ID is listed asno_provider_id. - A contribution file Astryx cannot read is listed as
unreadable_file, before npm pack fails on it.
astryx integration check --strictdoes not fail on these pairing, root and package.json notes either; they become failures in the next minor.astryx integration verifystill fails on a package.json npm cannot pack. - Two docs for one component (
-
astryx doctor integration validate,astryx integration checkandastryx integration verifysay 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.mjsbeside a builtocean.jsthat lacks theoceanexport) says it must share its stem with thedefineThemesource, naming that source when the built module'sSource:line or the source'sdefineThemename 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
.jsxget aFix:. - A theme source named for its theme (
brand.ts) beside the module the build writes (brand.js) is told to take thebrandThemestem. verifynames 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 afilesentry; and, when a theme'sdefineThemename 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/clipeer range:astryx doctor integration validateandastryx integration checklist acli_peer_versionnote naming the package, the range and the running version. Below the range the note gives the project's commands to install a newer@astryxdesign/coreand@astryxdesign/clitogether (as dev dependencies in the package's own folder) and then runastryx 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/corenow 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
astryxis not found: run the command that installed the CLI for the computer first, with the same package manager and folder, such asnpm i -g @astryxdesign/cliorpnpm 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 namesastryxand noastryxcommand is on the search path, apart from a package runner's own copy,astryx doctornow 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 searchreuses 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/apianswers faster when one process searches again: it reuses the components, docs, templates and themes it gathered for the same folder andtypewhile 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 wrapsnode:fs's read functions for that process; outside a search they behave exactly as before. Results are unchanged. Short-lived runs are unaffected: eachastryx searchcommand gathers once, as before (#7830). -
astryx searchandastryx buildfind 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.
-
astryx docs cli/commands/postinstallnow documents the hiddenpostinstallhook,astryx docs cli/commandslists it, andastryx postinstall --helpshows its exit code and an example. The summary ofastryx 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 fromcli/integrations/…toauthoring/…(for example,cli/integrations/quick-startis nowauthoring/quick-start), beside the field reference atastryx docs authoring/field-reference.astryx docs authoring <name>still opens one field reference section, and an oldcli/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-startedstill opens, and its Up isastryx 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 withastryx 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.
-
The butter, stone, and y2k themes no longer show the secondary hover fill on a disabled
ComplexSelectortrigger, whether the trigger is natively disabled or exposesaria-disabled="true", the focusable form every disabled control now uses. Before, the guard only recognized a natively disabled trigger. The themesastryx theme ejectcopies carry the same guard.Also released in
@astryxdesign/cli(templates).
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
v3.3.7
- fix(export): keep WebP exports within the format's 16383px side limit (#536) https://github.com/zumerlab/snapdom/pull/537
- fix(prepare): resolve percentage min/max sizes on the capture root https://github.com/zumerlab/snapdom/pull/533
- fix(toCanvas): return the canvas with an identity transform https://github.com/zumerlab/snapdom/pull/534
- fix(safari): keep text-shadow on stroked text https://github.com/zumerlab/snapdom/pull/531
- fix(firefox): paint background-clip: text gradients https://github.com/zumerlab/snapdom/pull/530
- fix(lineClamp): keep every clamped line with a fractional line height https://github.com/zumerlab/snapdom/pull/529
- fix(clone): keep a textarea's live size in the clone https://github.com/zumerlab/snapdom/pull/528
- fix(clone): copy SVG geometry set by CSS rules (#535) f320c1a
- docs(site): align the rest of the site with the new pitch ad2c112
- fix(diff): detect filtered nth and imported sibling rules (#539) 9713933
- test(export): cover dpr and type alias in the WebP limit, skip without a WebP encoder (#536) fead291
- @MFA-G made their first contribution in https://github.com/zumerlab/snapdom/pull/537
Full Changelog: https://github.com/zumerlab/snapdom/compare/v3.3.6...v3.3.7
v4.6.0
- #12354 chore: version bump to 4.6.0 to prepare for release
- #12328 test: stabilize overlapping polar-area label fixture
- #12238 Corrected spacing in pie/doughnut/polarArea (Fixes #10059)
- #12130 Enable scale title stroke (12092)
- #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
- #12321 fix(types): allow scriptable and indexed grid tick widths
- #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
- #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]
3.1.0
Please refer to CHANGELOG.md for details.