What's New
Release notes and changelog for all packages.0.3.0
Breaking Changes
- CLI — authoring is consolidated into a single entrypoint,
@astryxdesign/cli/authoring, that exposes only TYPES (the plain objects authors write) and PARSERS (the CLI's load-boundary validators). Zod is sealed inside each parser and never exported.
New Features
defineTheme: makecolor.accentoptional (#2279) A theme can now restyle the neutral ramp (neutralStyle,contrast) without adopting an accent. An accent-less config seeds the neutral palettes from the default accent's hue but leaves--color-accent,--color-accent-mutedand--color-on-accentungenerated, so they fall through to the token defaults — the same fall-throughexpandColorScalealready applies to status, categorical and on-dark tokens. Configs that pass an accent are unchanged, token for token.
Fixes
- theme build: generated custom Button variants now type-check through the public
@astryxdesign/core/Buttonsubpath. - cli — confine user-controlled file paths, close DoS vectors, and repair paths broken by the authoring reorg (#4637)
- cli hardening pass — validate inputs at the API layer, close path-safety gaps, and prevent agent-docs content loss. The API is a public surface (
@astryxdesign/cli/api), so guards that lived only in the CLI wrapper are pushed into the API. Path safety (the guard the write commands all depend on): - cli — rename the
search/buildverbose flag to--verbose, resync the bundled themes, and fixunwrap-authoring-factoriesedge cases (#4639) astryx doctor's peer-dependency check is now version-aware and names scoped packages correctly. Two problems are fixed: (1) the install hint was built withname.split('@')[0], which for a scoped peer like@stylexjs/stylexreturned an empty string, printing a barenpm installwith no package; and (2) the check only verified a peer was present, not that its installed version satisfied the declared range — so an out-of-range version (e.g.@stylexjs/stylex@0.10.1against a^0.19.0peer) was reported as satisfied. The check now flags out-of-range peers and its fix pins the required range, e.g.npm install @stylexjs/stylex@^0.19.0.- theme build: validate component override keys from documented theming targets so subtargets like Chat bubbles and SideNav items no longer warn as unknown.
astryx theme build: hyphenated component-override keys now resolve their built-in visual-prop values, and theKNOWN_COMPONENTSprop lists match what each component renders (#4109)loadKnownValuesmapped a theme key to its core component directory by stripping non-letters from only the directory name, so a hyphenated key (text-input,dropdown-menu,app-shell, ...) never matched itsTextInput/DropdownMenu/AppShelldir and the built-in prop values were silently dropped. It now strips non-letters from both sides before comparing, so hyphenated keys resolve. TheKNOWN_COMPONENTSvisual-prop lists are also synced to each component'stheming.targets[].visualProps(e.g.text-input/date-input/number-input/time-input:size,status;side-nav:mode;aspect-ratio:shape), correcting stale/empty entries.
Documentation
- Document the core codemod staging workflow and add release-time automation that promotes
transforms/nextcodemods into the resolved release version folder. - Document the
@astryxdesign/coreStyleX peer dependency — add@stylexjs/stylexto the Getting Started / Quick Start install commands in both READMEs, and add anastryx initnext-steps reminder to ensure the@stylexjs/stylexpeer dependency is met, with a pointer toastryx doctor. StyleX is the styling runtime every component calls, and not all package managers auto-install peers. - Surface the React 19 peer-dependency requirement everywhere a user would look for it (root README, core README, docsite hero, and the CLI getting-started guide), and add a sync test that keeps those surfaces naming the same React major as the core peer range.
Other Changes
- The
create*factories are removed (createConfig,createIntegration,createComponentDoc,createFunctionDoc,createDoc,createPageTemplate,createBlockTemplate,createCodemod,createConfigCodemod). Author a plain object and stamp itstypedirectly ({type: 'component', ...},{type: 'page', ...},{type: 'code', ...}); config and integration manifests are plain objects with no discriminant. - Import authoring types from
@astryxdesign/cli/authoring— the doc typesComponentDoc,HookDoc,ReferenceDoc,TemplateDoc, and the project-file typesAstryxConfig,AstryxIntegration,AstryxCodemod. The old split surfaces (@astryxdesign/cli/{config,doc,integration,template,codemod}and the authoring exports of@astryxdesign/core) are superseded. - Doc field types are renamed to explicit, domain-prefixed names so the surface reads clearly:
PropDoc → ComponentPropDoc,ThemingTarget → ComponentThemingTarget,ComponentVar → ComponentThemingVar,DerivedVar → ComponentThemingDerivedVar,ElementDescriptor → ComponentSlotElement,GroupDoc → ComponentGroupDoc,TranslationDoc → ComponentTranslationDoc,ExampleDoc/AnatomyElement/BestPractice/PlaygroundConfig → Component*, andContentBlock/TokenPreviewType → Reference*. The authorable entry types (ComponentDoc/HookDoc/ReferenceDoc/TemplateDoc) are unchanged. astryx upgrademigrates you automatically. Three codemods ship in this release:unwrap-authoring-factoriesrewrites everycreate*call to the plain stamped object,migrate-authoring-importsrepoints the import specifiers to@astryxdesign/cli/authoring, andrename-authoring-doctypesapplies the doc field-type renames (imports, type references, and JSDoc@typerefs).- CLI — the public
@astryxdesign/cli/apitype surface is now generated from the runtime JSDoc, and the injectable logger is consolidated into oneLogger. Consumer-visible changes to@astryxdesign/cli/api(types only — runtime imports are unchanged): - Precise return types.
component,docs,blog,discover,build,swizzle,upgrade,init, andthemeBuildpreviously resolved toPromise<any>; they now return their precise{ type, data }response unions. Code that leaned onanymay surface new (correct) type errors. - Response types are now exported by name — e.g.
ComponentDetailResponse,SearchResponse,UpgradeRunResponse— alongsidethemeAdd/themeList/listThemesand a new sharedloggervalue +Loggertype. - Breaking: the per-command return-union aliases
ComponentResult,DiscoverResult,DocsResult,HookResult, andTemplateResultare no longer exported. UseAwaited<ReturnType<typeof component>>(still works), or import the member response types directly. theme build --out/<file>, thevalidate-integrationmanifest roots (components/templates/codemods), andlayout --fileare now confined withassertWithin. An escaping integration root reports a validation issue instead of importing and executing files outside the package;layout --fileis also size-capped (5 MB) and rejects non-files, so a stream like/dev/zerocan't exhaust memory.- Fuzzy-match (Levenshtein), the layout value parser, and the layout expander gained bounds — a very long search query, a deeply nested attribute value, and a huge repeat count (
Box*999999999) can no longer spin the CPU, blow the stack, or exhaust the heap. - Docs topic lookup uses a null-prototype map so
__proto__/constructoras a topic name can't bypass the unknown-topic guard. The shipped getting-started docs and the sandbox registry generator point at the current CLI source path again (both broke in the authoring reorg). assertWithinnow canonicalizes symlinks (realpath of the deepest existing ancestor) — a symlink inside the project root pointing outside no longer lets a write escape. Also rejects a NUL byte in the path. This closes the escape for every command that writes through the guard (swizzle/template/upgrade/theme/layout/agent-docs).search(): non-positive/non-integerlimit, empty query, unknown--type→ERR_INVALID_ARGUMENT(previouslylimit: 0returned the full unclamped set).swizzle(): the component name is sanitized so../separators can't escape the--outputbase.swizzle()import rewriting: dynamicimport('../Sibling/…')is now rewritten (was left pointing at a non-existent sibling in the output dir); a two-levels-up asset import (../../locales/x.json) maps to the exported subpath instead of the invalid<pkg>/..; and../theme/tokens.stylexkeeps its full subpath (the StyleX compiler needs the dedicated./theme/tokens.stylexexport — collapsing it to<pkg>/themebroke StyleX resolution). Component-local.stylexfiles that aren't subpath exports keep the working barrel collapse.template()copy: refuses to clobber withoutoverwrite: true(ERR_FILE_EXISTS); adds anoverwriteoption.upgrade(): the--pathscan dir is confined to cwd (--applyrewrites files in place).init(): template scaffold refuses to clobber an existingpage.tsx(ERR_FILE_EXISTS); an unknown--agentnow throwsERR_UNKNOWN_AGENT(was silently ignored).layout: rejects an unknown--form(ERR_INVALID_OPTION) and empty expression (ERR_INVALID_ARGUMENT).layout expand: text payloads containing<,>,{, or}(e.g.Text"5 < 3") are emitted as JSX string-expression children so the generated TSX is valid — previously they produced syntactically-broken output.layout expand: a top-level repeat or group that expands to multiple sibling elements (B"x"*3,(B"a" + B"b"), an outlinerepeatblock) is now wrapped in a fragment — previously the generated TSX had adjacent root elements with no parent and failed to compile (the wrapper decision counted AST roots instead of expanded elements).layout(expand/check): an empty expression now surfacesERR_MISSING_ARGUMENTand a missing--filesurfacesERR_FILE_NOT_FOUND(was a genericERR_UNKNOWN/ a rawENOENTerrno, with a stack leak in human mode).layoutparser: a pathologically deep compact expression (V > …nested past 512 levels) is rejected with a locatedERR_LAYOUT_PARSEinstead of blowing the call stack and surfacing a rawRangeError(→ERR_UNKNOWN).layout check --form …printers: a string containing a quote (e.g. a Buttonlabel="Don't panic") now round-trips — the printer picks a delimiter the string doesn't contain instead of always single-quoting, so the emitted compact/outline surface re-parses (was producing an unparseable token).resolveTheme: a non-stringastryx.themein package.json (number/array/object/boolean) degrades to null instead of crashingastryx componentwith a rawTypeError(parity with the empty-string / unknown-slug paths).jsonOut: serializes the envelope BEFORE marking the emission handled, so if a command returns unserializabledata(circular ref / BigInt — an author bug) the bin error boundary still emits a JSON error envelope instead of leaving a--jsonconsumer with empty stdout.- package scanner: a dependency's
astryx.docsthat is a non-string (number/array) is skipped instead of crashing the whole scan with a rawTypeError, and adocspath that escapes its own package dir is skipped rather than surfacing foreign docs; a non-string packagenameis coerced to a string. component --package <pkg> --showcase/--blocks: route to the right leaf instead of falling back tocomponent.detail.discover/docsleaves: empty query/section errors instead of matching everything via.includes('').docs()/discover(): a non-stringtopic/section/querynow throws a stable coded error (ERR_UNKNOWN_TOPIC/ERR_UNKNOWN_SECTION/ERR_INVALID_ARGUMENT) instead of a rawTypeErrorthe CLI downgraded toERR_UNKNOWN(parity with thecomponent/hooknon-string guards).blog()detail: a non-string slug throwsERR_INVALID_ARGUMENT(was a rawTypeErrorthe CLI downgraded toERR_UNKNOWN), and fails fast before any network fetch.hook()/component()dispatchers: a non-stringnameorcategorythrows a coded error (ERR_UNKNOWN_HOOK/ERR_UNKNOWN_COMPONENT/ERR_UNKNOWN_CATEGORY) instead of a rawTypeErrorwith no.codefrom the leaf's.toLowerCase()/.replace(...).theme add: a write failure where an ancestor of the target dir is a file now surfacesERR_WRITE_FAILED(themkdirmoved inside the write try/catch) instead of leaking a raw fs errno (EEXIST/ENOTDIR) + absolute path.validate-integration: a path-unsafe[package]spec (../absolute) is reported as aninvalid_package_specdiagnostic instead of crashing with a raw stack (human) / genericERR_UNKNOWN(--json).doctor: no longer crashes (raw stack in human mode /ERR_UNKNOWNin--json) when multipleastryx.config.*files coexist — it reports aconfigFAIL. Version-alignment skips (info) instead of a spurious drift WARN with aNaN.undefined.xfix when either version isn't comparable semver (e.g.workspace:*).manifest: subcommands are sorted by name (same stability guarantee the top-level command list makes), so reordering.command()calls can't silently change the agent-facing manifest.build: the CLI wrapper now propagates the API's errorcodeinto the--jsonenvelope (bogus--type/ non-positive / non-integer--limit→ERR_INVALID_ARGUMENTinstead of a genericERR_UNKNOWN), and delegates--limitvalidation to the API (parity withsearch).layout check: exits1in BOTH--jsonand human mode for an invalid (but parseable) layout — the exit code no longer depends on the output mode, so it works as a CI gate / agent check without parsing stdout.upgradeconfig codemods: afindConfigPaththrow (multipleastryx.config.*files) is surfaced as a structured per-codemod error instead of crashing the whole upgrade run — config codemods run before the strict loader, so this restores the per-codemod isolation every other failure path honors.- CLI dispatch: the belt-and-suspenders postAction "completed without emitting an envelope" error carries a
code(ERR_UNKNOWN) so every error envelope is branchable oncode. toErrorEnvelope/AstryxError: attachsuggestionsonly when it's a real array.injectXdsBlock/removeXdsBlockno longer drop, duplicate, or orphan user content on malformed managed blocks (END-before-START, duplicate/nested blocks, or a start marker with no end). They locate a single well-formed block (END searched after START) and refuse to touch an ambiguous/half-written file instead of corrupting it.- The codemod source scan no longer follows symlinks (a symlinked file under the scanned path could rewrite its target OUTSIDE the project) and skips generated-output dirs (dist/build/out/.next/coverage) — codemods rewrite source, not artifacts or dependencies.
resolvePackageDirrejects an integration spec that isn't a bare package name (no.., no absolute, must stay in node_modules) — a config spec can no longer point the loader at an arbitrary module.- A broken integration manifest (throws on import or fails schema validation) no longer crashes
Project.load(and thus every command). It's recorded and surfaced viaissues(), restoring the documented skip+warn policy; other integrations still load. - The
--radius-*,--shadow-*/--elevation-*, and--color-*token-migration codemods no longer rewrite a longer consumer-defined token that merely shares a prefix (e.g.--radius-container-custom→--radius-3-custom,--radius-innermost→--radius-0most,var(--shadow-10)→--shadow-base0,--color-positive-custom→--color-success-custom). The boundary lookahead was binding only to the last alternative in the pattern (and two codemods had no boundary at all); it now wraps the whole alternation, so only exact token names migrate. migrate-badge-children-to-labelno longer emits a duplicatelabelprop when the badge already has one (<[XDSBadge](/components/Badge) label="x">Active</[XDSBadge](/components/Badge)>produced an invalidlabel="x" label="Active"); it now skips a badge that already declareslabel.readDocMetano longer reads agroup:/hidden:field nested inside apropDescriptionsblock (a docsZh/docsDense translation export) as the component's group — that leaked a translated prop description as a group key in the default Englishcomponent --list(e.g. a Chinese string appeared as a group). The field regexes now match top-level fields only (<=2 spaces).astryx search/buildverbose output was unreachable: the boolean--detailflag collided with the root program's value-taking--detail <level>, sosearch button --detailerroredargument missing. The boolean is now--verbose(the global--detail <level>is unchanged).- The themes bundled for
astryx theme addhad drifted from source — theneutralbundle was missing a WCAG AA light-modetext-secondarycontrast fix and a StatusDot color block, soastryx theme add neutralscaffolded a theme below AA. All bundles are regenerated to match source, guarded by a new drift test. - The
unwrap-authoring-factoriesupgrade codemod produced broken output for a shorthandtypeproperty (emitted{'component'}) and for no-argument factory calls (left a call referencing the just-removed import). Both now emit the correct plain object.
Contributors
Thanks to everyone who contributed to this release:
0.2.0
Breaking Changes
- cli/json: remove the central
CLIAnyResponse,CLIResponseType, andCLIResponseDataMaptypes.jsonOutis now a structural serializer andparseResponse/assertResponsereturn the structuralCLIResponse({type, data, meta?}) instead of the discriminated union, soresult.dataisunknownuntil you narrow it yourself. Runtime output is unchanged (every--jsonenvelope is byte-identical). This only affects consumers importing those types or relying onparseResponse/assertResponseto auto-narrow.data. - component/hook
--jsonlist responses collapsed.--detail compact/fullpreviously emitted distinctcomponent.brief/component.full(andhook.*) envelopes; they now all emitcomponent.list(resp.hook.list) with adata.detail: 'names' | 'compact' | 'full'field. Migrate: switch ondata.detail, not the.brief/.fulldiscriminator. Removed types: ComponentBriefResponse, ComponentFullResponse, HookBriefResponse, HookFullResponse.
New Features
- CLI:
blogis now a normal, agent-facing command — it appears in--helpand the capability manifest and supports--json(emittingblog.list/blog.detailenvelopes), instead of being hidden. Human output is unchanged; the reader still consumes the public RSS feed. Also scriptable through the./apibarrel asblog(slug?). - CLI:
initis now fully scriptable through the./apibarrel — the non-interactive installer (agent-docs cheat sheet, starter template,--remove-agents) lives inapi/initand returns a typed receipt (init.run|init.remove), with the CLI reduced to a thin parse → API call → render wrapper. Human output is emitted through an injectable logger, so a scriptedinit()stays silent while the CLI output is byte-identical for existing usage. - CLI:
theme buildis now fully scriptable through the./apibarrel — the ~1,000-line theme compiler (defineTheme extraction, CSS generation via@astryxdesign/core/theme, variant/type-declaration + icon-module generation, override validation) lives inapi/theme/buildand returns a typedtheme.buildreceipt, with the CLI reduced to a thin parse → API call → render wrapper. Human progress is emitted through an injectable logger, so a scriptedthemeBuild()stays silent while the generated CSS/JS/.d.ts, the--jsonenvelope, and human output stay byte-identical for existing usage. Watch mode remains a thin CLI loop. - CLI:
upgradeis now fully scriptable through the./apibarrel — the version-to-version pipeline (codemods + agent-docs refresh) lives inapi/upgradeand returns a typed receipt (upgrade.list|upgrade.status|upgrade.run), with the CLI reduced to a thin parse → API call → render wrapper. Human progress is emitted through an injectable logger, so a scriptedupgrade()stays silent while the CLI output and--jsonenvelopes are unchanged for existing usage. - Timestamp: new
tooltipEntriesprop renders the hover tooltip across several time zones and/or formats at once — one line per entry, each with an optionaltimezoneID(IANA id; omit it or pass'local'for the viewer's zone),format(every non-relativeTimestampFormatplus'full'), andlabel. The default is unchanged: with no entries the tooltip stays the single full absolute line in the viewer's zone. Configuring entries also attaches the tooltip to absolute formats, which previously had none — note that this gives those timestamps a tab stop and focus ring, as relative timestamps already have, so a column of them gains one tab stop per row.hasTooltip={false}still suppresses the tooltip, and an empty array counts as no configuration. Also correctsisTimezoneShown's documentation, which claimed it applied to thesystem_date_timeandsystem_timeformats; it never has, and those formats stay machine-readable. (#4188)
Fixes
astryx theme build: component-override keys for multi-word components (TextInput, DateInput, NumberInput, DropdownMenu, SideNav, TopNav, etc.) now match the hyphenated class the component actually renders. The known-component registry used de-hyphenated keys, so overrides authored against them emitted dead selectors (.astryx-textinputinstead of.astryx-text-input) that silently never applied (#4109).
Other Changes
- CLI: blog reorganized into api/blog leaf shape — list/detail leaves projecting a shared RSS adapter (
_adapter.mjsowns all network fetch + feed parsing), withblog.mjskept as a dispatcher+barrel so the sameblogexport, the CLI wrapper, api/index.mjs, and the --json/human output stay byte-identical. - CLI:
buildreorganized into theapi/buildleaf shape —build.mjsis now a dispatcher + barrel that routes no-query →build.help(api/build/help/help.mjs) and a query →build.kit(api/build/kit/kit.mjs), with each leaf projecting its single{type, data}envelope. Pure reorganization: thebuildexport, the./apibarrel, and the CLI consumer are unchanged, and the--jsonand human output stay byte-identical for existing usage. - CLI:
componentreorganized into theapi/componentleaf shape over a shared_adapterresolver —component.mjsis now a dispatcher + barrel that routes to per-type leaves (list,detail,detail/props,detail/source,detail/showcase,detail/blocks), each a thin projection of a subject the adapter resolves once (core/external/scoped/integration ownership, ambiguity handling, and fuzzy search, deduped). Pure reorg: every--jsonenvelope and human output stays byte-identical across all modes. - CLI: discover reorganized into api/discover leaf shape (list, detail, detail/doc, search) behind a shared _adapter that owns external-package discovery and doc loading; discover.mjs is now a dispatcher+barrel keeping the same exports. Pure reorg —
--jsonand human output are byte-identical and api/index.mjs + the CLI consumer are untouched. Adds colocated leaf tests. - CLI: docs reorganized into api/docs leaf shape —
docs()inapi/docs/docs.mjsis now a dispatcher + barrel that routes by argument shape into three leaves (api/docs/list,api/docs/detail,api/docs/detail/section), each projecting into a single{ type, data }envelope. The discovery, overlay loading, and topic resolution shared by ≥2 leaves live inapi/docs/_adapter.mjs. Pure reorganization: thedocsexport,api/index.mjs, the CLI consumer, and all--jsonand human output are unchanged (byte-identical). - CLI: hook reorganized into api/hook leaf shape —
hook.mjsis now a dispatcher+barrel routing to colocated leaves (list/list.mjs→ hook.list,detail/detail.mjs→ hook.detail,detail/params/params.mjs→ hook.detail.params) over a shared_adapter.mjsresolver. Pure reorg:--jsonand human output are byte-identical across all modes, and thehookexport surface (api/index.mjs + CLI) is unchanged. - CLI: init reorganized into api/init leaf shape —
init.mjsis now a dispatcher + barrel that routes toapi/init/run/run.mjs(the default /--features/--allinstall path) andapi/init/remove/remove.mjs(the--remove-agentspath), with the shared plain-logger contract inapi/init/_adapter.mjs. Pure reorg:getNextSteps,noopInitLogger, and theInitOptions/InitLoggertypes stay re-exported from the barrel, so api/index.mjs, the CLI command, and the programmatic API are unchanged. Human and--jsonoutput are byte-identical. - CLI: layout reorganized into the api/layout leaf shape — a shared
_adapter.mjs(analyze/loadBlocks/formatIssueoverlib/xle) with thinexpand/,check/, andgrammar/leaves, plus alayout.mjsbarrel.api/index.mjsand the CLI are unchanged (they import via the barrel). Pure reorg:layout expand/check/grammar--jsonenvelopes and human output are byte-identical. - CLI: swizzle reorganized into api/swizzle leaf shape — the flat command splits into
api/swizzle/list(swizzle.list) andapi/swizzle/copy(swizzle.copyreceipt, incl.rewriteImports), with shared @astryxdesign/core discovery + component listing deduped inapi/swizzle/_adapter.mjs, andswizzle.mjsreduced to a dispatcher + barrel that keeps its existing exports (swizzle,rewriteImports). Pure reorganization with no behavior change: human output and every--jsonenvelope stay byte-identical, and the CLI command, the./apibarrel, and the centraltypes/swizzledeclarations are untouched. - CLI: template reorganized into api/template leaf shape (shared helpers preserved on the barrel). Pure reorg —
--jsonand human output stay byte-identical: shared discovery/IO moved toapi/template/_adapter.mjs, the command modes split intolist/show/skeleton/copyleaves, andtemplate.mjsbecomes a dispatcher + barrel that re-exports every previously-exported symbol (template, discoverTemplates, discoverAll, discoverAllWithErrors, discoverIntegrationTemplatesForOne, findShowcase, findRelatedBlocks, stripTemplateAssetRefs, listTemplates, extractComponents, and the DiscoveredTemplate/TemplateDiscoveryError types) so component/layout/search/init/discover/validate-integration and lib/project keep resolvingapi/template/template.mjsunchanged. - CLI:
theme add/listare reorganized into the fractalapi/theme/leaf shape — a shared_adapter.mjs(bundled-theme manifest reader + slug resolver) with thinadd/(copy →theme.addreceipt) andlist/(theme.list) leaves over it, plus atheme.mjsbarrel, mirroring thetheme buildextraction (#4462).themeList()is now exported from@astryxdesign/cli/apialongsidethemeAdd. Pure reorg:theme list/add--jsonenvelopes and human output are byte-identical, with new direct-API tests for both leaves. - CLI: upgrade reorganized into api/upgrade leaf shape — the flat pipeline is split into a dispatcher+barrel (
upgrade.mjs), a shared_adapter.mjs(version detection + agent-docs refresh + codemod selection/execution machinery), andlist/status/runleaves (upgrade.list|upgrade.status|upgrade.run). Pure reorg: the./apibarrel + CLI consumer are unchanged, and both the human output and--jsonenvelopes are byte-identical.
Contributors
Thanks to everyone who contributed to this release:
0.1.9
New Features
- CLI: full API coverage for the
build,swizzle,layout, andvalidatecommands — each is now scriptable through the./apibarrel with the CLI as a thin parse → API call → render wrapper.buildgains--jsonoutput. Behavior is unchanged for existing command usage. (#4302)
Fixes
- Align two
--jsoncontract shapes with what the CLI actually emits - Register all emitted response types in the
--jsonenvelope union Three response types were defined, exported, and emitted by commands but never added toCLIAnyResponse— the union thatjsonOut()type-checks payloads against:component.full,component.detail.blocks, andupgrade.status. Because their discriminators were missing from the map,jsonOut('upgrade.status', …)(and the two component variants) were rejected by the type-checker, and their payload shapes weren't actually being validated.build.helphad no response type at all. Added aBuildHelpResponsetype and wired all four into the union so every--jsonenvelope the CLI can emit is now type-checked against a declared shape. - Type
detectPackageManagerhonestly soastryx doctor's "no lockfile" branch is reachabledetectPackageManagerreturns'npx'as the sentinel for "nothing detected", but its return type only listed'yarn' | 'pnpm' | 'bun' | 'npm'. Type-checkers therefore treateddoctor'spm !== 'npx'guard as a dead comparison — the "No lockfile detected — defaulting to npm/npx" message looked unreachable and was at risk of being "cleaned up". The return type is nowPackageManager | 'npx'and detection narrows via a shared type predicate, so the guard is honest and the branch is preserved. - Make the CLI's
.mjssources fully strict-typecheckable (checkJs + JSDoc) Annotated the entire CLI package sotsconfig.strict.json(fullstrictcheckJsoversrc,bin,scripts,docs, and the emittedtemplates) reports zero errors — down from 1717. Fixes are JSDoc-only: no runtime logic changed,.mjsstays.mjs. Strict checking also surfaced and corrected several type-contract drifts: theupgrade.runresponse type (declared adepsUpdatedfield the command never emits, and omitted the realintegrations/filesChanged/transformsApplied/errors), registered the emittedtheme.list/theme.add/layout.*response types in the--jsonenvelope union, and addedcategory?toReferenceSectionin core's docs types (reference docs already emit it). - Drop the dead
cwdparameter fromgetLatestVersioncheckForUpdatecalledgetLatestVersion(cwd)and the JSDoc advertised acwdparameter, but the function takes no arguments — it only reads the$ASTRYX_LATEST_VERSIONenv var, so the passedcwdwas silently ignored. Removed the phantom parameter and its doc so the signature matches the behavior. No functional change to the update-nudge output.
Other Changes
swizzle.copypayloads always includepackageandusesStyleX(both covered by tests), butSwizzleCopyResponse.datadidn't declare them — the call site cast the payload toRecord<string, unknown>to sidestep the mismatch. Added both fields to the type and dropped the loose cast so the payload is type-checked.- The error
suggestionsshape was declared as{name, reason}(reason required) in the JSON envelope / API error contract, but some call sites emit bare{name}(e.g. candidate component names on swizzle). Introduced a single canonicalSuggestiontype (reason?optional) and referenced it everywhere so the contract matches the emitted data.
Contributors
Thanks to everyone who contributed to this release:
0.1.8
Breaking Changes
- Avatar and AvatarGroup adopt Icon's abbreviated size scale —
sizenow takesxsm/sm/md/lg/xlinstead oftiny/xsmall/small/medium/large. Pixel values are unchanged (20/24/36/48/128px) and the default is nowmd(still 36px, formerlysmall). Avatar's tiers stay larger than Icon's because avatars align with media rather than glyphs. Runastryx upgradeto migrate call sites. (#2672)
New Features
astryx init --features agentsnow defaults to creating rootAGENTS.md— the tool-agnostic standard that Codex/Copilot, Cursor, and most agents read — instead of the Claude-specific.claude/CLAUDE.md. Claude output is now opt-in via--agent claude(→.claude/CLAUDE.md), and--agent allstill writes both. Projects with existing agent-doc files are unaffected: init still discovers and updates every file already present, so this only changes the from-scratch default. (#4216)- "Foolproof init": both
@astryxdesign/coreand@astryxdesign/clinow print a postinstall nudge pointing you tonpx @astryxdesign/cli init,astryxcommands nudge you to finish setup until init has run, andastryx initruns non-interactively (no TTY required) so it works in CI and agent environments. (#4147, #4153, #4154, #4155)
Fixes
- Stop suggesting bare
npx astryxbefore the CLI is installed — it resolves to an unrelated package on the npm registry. The CLI now emits an install-aware invocation everywhere it prints a command: - Extend the v0.1.0 upgrade codemods to cover test files that mock
@xds/coremodules, which were previously left half-migrated and broke after upgrade: astryx upgradenow keeps the managed agent-docs block (<!-- ASTRYX:START --> … <!-- ASTRYX:END -->) in sync with the installed version on every path — including the up-to-date and no-codemods short-circuits that previously returned before any refresh, leaving AI agents reading a stale component index and superseded rules. The block documents the installed library, so it's now refreshed up front (independent of codemods) and reported in the--jsonreceipt asagentDocs. One detection pass covers three cases: a stale block is rewritten (--apply) or reported as a pending change (dry-run, which no longer writes); a project with core installed but no managed block is nudged to runastryx init --features agents; an already-current block stays silent. (#4168, #4169)
Documentation
- Add a
cli-integrationsCLI docs topic (astryx docs cli-integrations) so the integration-authoring guide (originally written by @ejhammond) is discoverable through the CLI and docsite instead of an unreferenced markdown file. Rewrite the CLI README's Configuration section to match the current strict config schema (integrations,issuesUrl,hooks.postCodemod,experimental.xle) and reframe the Integrations section around the two-file API.
Other Changes
- Installed / global / dev runs suggest
<pm> astryx <cmd>(e.g.pnpm exec astryx …), unchanged. - One-off runs (launched via
npx/pnpm dlx/yarn dlx/bunx) suggest the scoped package<dlx> @astryxdesign/cli <cmd>, which always resolves to us. - migrate-xds-module-specifiers: rewrite the mocked-module path in
vi.mock/vi.doMock/jest.mock/jest.doMock(and baremock) calls, plusimport(...)specifiers used in TS type positions (typeof import('@xds/core/[Text](/components/Text)')), so the mock still intercepts the renamed@astryxdesign/*import. - drop-xds-prefix-imports: un-prefix partial-mock override keys inside an
@xds/coremock factory (e.g.useXDSTruncation→useTruncation) so the override matches the renamed export instead of silently overriding nothing. Scoped to recognized@xds/coremock factories only; unrelated object keys are untouched.
Contributors
Thanks to everyone who contributed to this release:
0.1.7
New Features
- Export the authoring factories from
@astryxdesign/core:createConfigat@astryxdesign/core/configandcreateIntegration/createPageTemplate/createBlockTemplate/createComponentDoc/createFunctionDoc/createDocat@astryxdesign/core/authoring. Authoring a config or integration no longer requires depending on the CLI. Existing@astryxdesign/cli/*imports keep working via re-export. - Add the finalized doc-authoring API to
@astryxdesign/cli/doc:createComponentDoc,createFunctionDoc(any function, including hooks), andcreateDoc(generic reference/topic docs). Each factory stamps atypediscriminant and is validated at the load boundary against a matching per-kind schema. The legacy looseexport const docs = {...}format keeps loading unchanged, and.ts-authored hook/function sources now derive their import path to a tree-shakeable subpath instead of the bare package root. - New codemod for the Table
tablePropsdeprecation: lifts object-literaltablePropskeys into direct props on<[Table](/components/Table)>, keeps colliding or dynamic values in place with a TODO note. Codemod:npx astryx upgrade --codemod migrate-table-tableprops-to-direct-props(#3679) - New docs topic
internationalizationcovering how to localize astryx components, provide translation catalogs, override default strings, coexist with existing i18n libraries (react-intl, i18next, next-intl), swap languages at runtime, and validate coverage with the shipped pseudo locale. Runnpx astryx docs internationalizationor read it at https://astryx.atmeta.com/docs/internationalization. - template: accept
.template.{ts,mjs,js}as the canonical suffix for template-spec files, alongside the legacy.doc.*suffix. Template specs exportcreateBlockTemplate/createPageTemplate— a scaffoldable template, not documentation — so they now get a descriptive name. Core, external-package, and integration discovery (findShowcase,--blocks,astryx template <id>scaffolding) all treatFoo.template.tsidentically to a legacyFoo.doc.mjs; same-stem.tsxsource resolves for either suffix, and.template.tsauthoring is loaded via jiti. Additive only — no existing files are renamed.
Fixes
- Translated component docs no longer drop props
A
docsZh/docsDenseblock that carried its ownpropsarray replaced the English component doc wholesale rather than overlaying it, so any prop the translation had not caught up with simply ceased to exist.astryx component [Button](/components/Button) --zhsilently omittedisInterruptibleandisIconOnly; ten components were affected, includingMobileNav,PopoverandStackthrough the multi-componentcomponents[]shape. - Anchor --dense / --zh doc overlays to their base sections (#2182) The compressed and translated reference docs were merged into the base doc by array position, so an overlay whose sections were ordered differently — or which omitted one — grafted every title onto the wrong body.
- template: inline full demo-image URLs in the Avatar blocks and theme-showcase page so scaffolding strips them to a clean placeholder. Templates that stored only the CDN base in a
constand appended the filename via interpolation (`${CDN}/File.png`) previously scaffolded a malformedsrc— the placeholder data URI with the filename glued onto the end — plus a deadconst CDN = 'data:…'. (#4027)
Documentation
- Document the minimal
package.json#exportsrecipe an integration needs so its block templates are importable by a bundler-resolution consumer and type-check undermoduleResolution: bundler:"./templates/*.tsx": "./templates/*.tsx"plus an extensionfulimport('@acme/widgets/templates/…/…Showcase.tsx'). Addspackages/cli/docs/integration-authoring.mdand a fixture test proving the recipe against the repo's owntscandesbuild.
Contributors
Thanks to everyone who contributed to this release:
0.1.6
0.1.5
New Features
- Add a v0.1.5 upgrade codemod that renames
labelSpacing="default"tolabelSpacing="hug"on Switch. (#2889) - New
incident-consolepage template: an on-call incident response console demonstrating the frame-first tracker archetype — grouped dense incident rows (StatusDot severity, Token state), PowerSearch filtering, status segmented control, and a resizable inspector panel with metadata and timeline. Adds theTools - Incident Consoletemplate category. - New
messaging-shellpage template: Slack-style column frame (rail | sidebar | stream | thread panel) built on the Chat component family — dense rows, zero cards. Adds theShell - Messagingtemplate category.
Fixes
- Fill viewport height across CLI page templates so the background covers the full page (#3762)
astryx init --features agentsnow supports--agent hermes. The preset injects the component index into an existing.hermes.md/HERMES.md(Hermes Agent's top-priority project-context files) and otherwise creates rootAGENTS.md, which Hermes loads from the project root — unlike the.claude/CLAUDE.mddefault. Additive only: existingclaude/cursor/codex/all/auto-detect behavior is unchanged. (#2187)- cli:
astryx doctornow detects@astryxdesign/theme-*packages in pnpm projects. pnpm installs packages as symlinks intonode_modules/.pnpm, and the theme scan only accepted real directories, so every symlinked theme package was skipped and doctor warned that none were installed (#3530). - Make
astryx theme build's color-scheme declaration mode-aware, so built themes withlight-dark()tokens no longer defeat<[Theme](/components/Theme) mode="light|dark">forcing (#3660) runCodemodsnow returnswrittenFiles, soastryx upgrade's post-codemod hooks (prettier/eslint formatting) actually run on core-codemod changes. The runner built thewrittenFileslist internally but omitted it from its return object, soupgrade.mjsreadcodemodResult.writtenFiles ?? []as always-empty and the configuredhooks.postCodemod(e.g.prettier --write,eslint --fix) received no files and silently skipped. As a result, jscodeshift's default double-quote output ("@astryxdesign/core/[Button](/components/Button)") was never reformatted to the project's style, failingprettier-formatlint on migrated apps. The siblingintegration-runneralready returnedwrittenFilescorrectly, so integration-codemod changes were formatted while core-codemod changes were not.astryx swizzle: swizzled components ship raw StyleX source that needs a build-time StyleX compiler, and without one they render unstyled with no error. The command now prints a StyleX build-setup note after copying (including the Next.js caveat that the StyleX Babel plugin disables SWC and breaksnext/font, so an SWC-based transform is required), andastryx docs stylinggains a "StyleX Build Setup" section covering per-bundler setup. (#3373)astryx theme build: custom component variants declared in a theme (e.g.button['variant:accentOutline']) now generate a type augmentation against the component's real interface (ButtonVariantMap) instead of a non-existentXDS-prefixed one, sovariant="accentOutline"type-checks. Props with no augmentation point (closed unions like Buttonsizeor Headingtype) are skipped instead of emitting dead augmentations, and the generated.variants.d.tsis now referenced from the theme's.d.tsso the augmentation actually loads. (#3371)
Documentation
- CodeBlock: terminal-style dark block template (syntaxTheme preset)
- Add cascade-layer safety guidance to the migration guide (
astryx docs migration): a Cascade Layer Safety audit checklist (unlayered styles and later layers both beatastryx-baseregardless of specificity, classify every stylesheet into a layer deliberately, layer Tailwind preflight on both v3 and v4) and a Foundation Smoke Test section (one page with Button/TextInput/Card/Table plus a non-zero-padding assertion) so a broken layer order fails before feature work instead of after N migrated screens. The getting-started guide now points to it from the theme CSS step. - NavHeadingMenu: add a playground config and showcase block so the Overview tab has a working preview (#2698)
- NavHeadingMenu: constrain the showcase SideNav to a shorter height so the heading no longer appears to float at the top of the Overview preview (#2698)
Contributors
Thanks to everyone who contributed to this release:
0.1.4
Fixes
astryx component <Name>now prints the correctdefineThemecomponent-override key. The theming example stripped a stalexds-prefix (left over from the astryx rename) instead ofastryx-, so it advertised keys likeastryx-base-table/astryx-button. Those double-prefix to.astryx-astryx-*selectors at runtime and silently match nothing. Keys are now the stable class name minusastryx-(e.g.base-table,button), which is whatgenerateThemeRulesexpects (#3458).- Harden the v0.1.0 upgrade codemods against three cases surfaced while migrating consumer apps:
Documentation
- Add a browser-support guide (
astryx docs browser-support) documenting the support tiers, the modern platform features Astryx depends on (Popover API, CSS anchor positioning,light-dark()), which components are affected, and how consumers can support older browsers for their own audience.
Other Changes
- drop-xds-prefix-imports: when un-prefixing an
@xds/coreimport (e.g.XDSCodeBlock→CodeBlock) would collide with a same-named local binding in the file (such as a localexport function CodeBlockwrapper), alias the import toAstryx<Name>and rewrite its usages instead of producing a duplicate declaration that breaks the build. - migrate-xds-css-surfaces: rewrite CSS
@importof@xds/*package stylesheets (both'…'/"…"andurl(…)forms), including the@xds/core/xds.css→@astryxdesign/core/astryx.cssfile rename and thetheme-default/theme-daily→theme-neutralcollapse. - migrate-xds-module-specifiers: when collapsing
@xds/theme-default/@xds/theme-dailyto@astryxdesign/theme-neutral, remap thedefaultThemeexport toneutralTheme, aliasing back to the original local name (neutralTheme as defaultTheme) so downstream usages keep working.
Contributors
Thanks to everyone who contributed to this release:
0.1.3
New Features
- Add a hidden
astryx blogcommand that reads the blog over the site's RSS feed and prints a post's plaintext (.txt) variant. The command is not shown in--helpor the manifest and always reads from the canonical site origin. - Component discovery is now package-ownership aware: --package scoping, source resolution for integration components, and package-qualified JSON listings.
- Strict config + integration v1 schema (integrations, issuesUrl, hooks.postCodemod) and new @astryxdesign/cli/integration export.
- File-based codemod API (createCodemod/createConfigCodemod) with the @astryxdesign/cli/codemod export and integration codemod discovery in upgrade.
- component, template, and upgrade now print a one-line non-blocking warning when a configured integration has validation issues, pointing to validate-integration.
- Add a Kanban Board page template: color-coded status columns, draggable task cards with priority tags, and board toolbar. Based on a design by @cg-hub18.
- Add frame-first layout guidance: new
astryx docs layouttopic (shell choice, region budgets, app archetypes, cards-vs-rows policy, responsive contracts), layout rules in the generated agent cheat sheet, and layout anti-patterns indocs principles. - Add a v0.1.3 config codemod that migrates astryx.config layout.components to experimental.xle.components.
- Add v0.1.0 codemods for migrating
declare module "@xds/core/..."type augmentations and.xds-*/[data-xds-theme]/@layer xds-themeCSS surfaces to their@astryxdesign/astryx-*equivalents. - Introduce the Project configuration API as the single entry point for reading resolved project config, components, templates, codemods, and issue routing, replacing loadConfig. Misconfigured integrations are now skipped with a warning during upgrade instead of hard-failing, and a new --skip-codemod flag lets you re-run past a failed codemod.
- Add a Shell page-template category to the CLI: Top Nav, Side Nav, and Shell Nav app-shell scaffolds (#3245, #3246, #3247)
- Static template authoring API (createPageTemplate/createBlockTemplate) with the @astryxdesign/cli/template export and type-driven, package-scoped template discovery.
- Swizzle can now copy integration-owned components, rewrites escaping imports to the owning package, and routes maintainer feedback through config and integration issue URLs.
astryx theme build --watch: rebuild a theme automatically whenever the source file changes, until interrupted with Ctrl-C. Removes the manual re-run step (and the stale-CSS confusion that comes with forgetting it) from the theme-authoring loop. Each rebuild runs in a child process so a build error is contained and the watcher keeps running. Not supported with--json. (#3375)- Add the validate-integration command and integration issue model for checking an Astryx integration package's manifest and contributions.
- XLE app-component registration moved into validated config under experimental.xle.components (object form), replacing the unvalidated layout.components read.
Fixes
- Align the CLI error-code type declarations with the runtime error codes (add the missing ERR_AMBIGUOUS_TEMPLATE declaration).
- Correct the
doctortheme-wiring hint to reference the realastryx.themeconfig field (wasxds.theme) and update the agent-docs check wording to say "Astryx". - Update the API/CLI parity harness for the package-qualified
component --listshape, and make the component API reject a non-string name with a clean error instead of throwing. - The XDS-prefix drop codemod now runs as a mandatory v0.1.0 upgrade step, so upgrading from 0.0.x rewrites prefixed imports (useXDSTheme, XDSButton, XDSIconRegistry, ...) to their bare names alongside the @xds/ → @astryxdesign/ scope rename.
- upgrade now runs core codemods before loading config, so a config codemod can repair an otherwise-invalid config; dry-run reports a fixable config and suggests the command to apply it.
Documentation
- Blockquote: add "With Attribution" and "Testimonials" examples (#3385)
- DateTimeInput and DateRangeInput: add example blocks so their docs pages have populated Examples sections and playground links (#2724)
- Add copyable example blocks to 46 component docs pages that previously showed only a hero visual and an empty Examples section (#3481)
- HoverCard: give the "Link Preview" example an interactive
Linktrigger so there is something to hover over (#2728) - Lightbox: add Gallery, Video, and Zoom examples and fix the playground preview (#3301)
- Remove lingering references to the removed gap-report feature and swizzle gap flags; docs now reflect swizzle's maintainer feedback link.
- Tab: add an interactive example showing
iconandselectedIconon the Tab docs page (#2765) - ToggleButtonGroup: add a vertical example block showing orientation="vertical" with single- and multi-select groups (#2707)
Other Changes
- Integration codemod and template-doc loading now use the shared module-loader util instead of duplicating the jiti/import logic.
- Extract the shared module-loading + conventional-file-discovery helpers used by config and integration loading into one internal util (no behavior change).
- Remove the standalone gap-report command. Swizzle now prints a short maintainer feedback link instead of filing issues.
- Load and validate user-authored config, integration, codemod, and template modules through one shared module loader; create* factories are now type-only and validation happens at load.
- Remove the obsolete xds config-surface migration codemod and unify config codemod execution on the shared (file, api) runner used by integration codemods.
Contributors
Thanks to everyone who contributed to this release:
0.1.2
Breaking Changes
Text,Heading,Link, andTimestamprename thecolor="active"value tocolor="accent", now mapping to the dedicated--color-text-accenttoken (legible accent text ink) instead of--color-accent. Runastryx upgradeto migrate call sites automatically. (#2863)
New Features
- Let
astryx.config.mjsintegrations contribute package docs, gap-report hooks, template fetching hooks, upgrade codemods, and post-codemod hooks. - Add
astryx theme add <slug> [path](andastryx theme list) to scaffold a theme's source into your project as editable files you own, with theme sources bundled into the CLI
Fixes
- align
astryx inittheme instructions with the runtime built-theme recommendation (#3080)astryx initnow points users at the pre-built theme path (@astryxdesign/theme-neutral/built+theme.css) and the base CSS imports, matching the runtime<[Theme](/components/Theme)>console guidance, instead of the slower runtime style-injection import that left apps unstyled. astryx theme buildnow derives every output file (.css/.js/.d.ts) from the theme name so they share one naming scheme, shows import paths as bare./<name>specifiers (instead of a cwd-rooted./src/...path that was wrong when your file already lives under src/), and no longer warns about thevariantprop oncard
Documentation
- Rename the ClickableCard and SelectableCard examples to follow the "Component — Variant" title convention (
Clickable [Card](/components/Card) — Nested Button,Selectable [Card](/components/Card) — Multi-select), and add playground defaults to both card docs so their docsite previews show realistic card content (#2877) - Declare playground scaffolds for the Chat sub-components so they preview at a realistic width (ChatComposer and ChatComposerDrawer wrap in a sized container, and the drawer seeds default content), and drop the redundant visible value label from the ChatComposerDrawer "With Progress" example while keeping the accessible label (#2877)
- Rename the DateInput "Date Range" example to "Min/Max Constraints" — it demos a single input constrained to a min/max window, not a date-range picker (#2692)
- Wire local state into more showcase examples that were frozen (static value + no-op onChange): TextInput, TextArea, NumberInput, SegmentedControl, RadioList, Tab, TabList, and TabMenu. Follows the same fix as the Slider/Selector/MultiSelector showcases so the docsite previews are actually interactive
- Wire local state into the Typeahead, Tokenizer, and FileInput showcase examples (static value + no-op onChange → frozen previews). Completes the interactive-showcase fixes started for Slider/Selector/MultiSelector (#3187-#3189) and the input/tab batch
- Wire local state into the Slider, Selector, and MultiSelector showcase examples so they are interactive — they were controlled components with a static value and a no-op/missing onChange, so the docsite previews appeared frozen (#3187, #3188, #3189)
- Add a LinkProvider example block showing how to swap in a framework router link (e.g. Next.js Link) for client-side routing (#2733)
- Add a showcase block for Outline so its docs page has a hero preview, alongside the existing example blocks (#2871)
- Remove the "MoreMenu — In Toolbar" example block — it rendered incorrectly and was redundant with the other MoreMenu examples (#2870)
- Add rendered example blocks for the two column-axis Table plugin hooks, shown on their own subcomponent pages:
- Move the "ToggleButton — Group" example to the ToggleButtonGroup page, where it belongs (it demonstrates grouped toggle behavior) (#2842)
- Make the Toolbar "Table Filter" example use real Selector controls for its Status and Priority filters instead of buttons styled to look like dropdowns, and add meaningful playground defaults plus richer slot options (buttons, icon buttons, tabs, segmented controls, selectors) to the Toolbar docs (#2877).
Other Changes
useTableStickyColumns — Pinned Columns(on /components/useTableStickyColumns)useTableColumnResize — Draggable Columns(on /components/useTableColumnResize)
Contributors
Thanks to everyone who contributed to this release:
0.1.1
New Features
- Add
astryx buildcommand for page composition, with natural-language search ranking.build "<idea>"returns a composition kit — the closest page template, the blocks that cover parts, and components to fill gaps, plus a Compose suggestion.buildwith no args prints the how-to-build playbook. The shared search ranking now handles oblique natural-language queries via tokenization + stopwords, a synonym/intent map, light stemming, and page-template keyword enrichment. - Make generated agent docs build-first and restructure
initoutput. The generatedCLAUDE.mdnow leads with thebuildworkflow (search reframed as a neutral universal find), and includes a required-CSS setup note (reset.css+astryx.css) so components never render unstyled.initnow points agents atastryx build/astryx searchinstead of dumping page-template names. - Improve
astryx buildoutput into a complete composition kit.build "<idea>"now returns an agent-ready kit grouped by role: a START line (scaffold vs compose), the closest PAGE template, an always-on FRAME (page shell) and FOUNDATION (layout/typography/action primitives), idea-specific BLOCKS and DOMAIN COMPONENTS (with a relevance floor to cut noise), and a SETUP reminder. The always-on FRAME/FOUNDATION groups fix low recall of the structural primitives every page needs but that never keyword-match an idea (measured: component recall 15% to 71% on an agent-grounded eval). - Densify agent docs + tailor styling guidance to the project's configured system
Tightened the generated
CLAUDE.md/AGENTS.mdblock from ~48 lines to ~26 (the per-topicdocsdump collapsed to one line,build/search/componentno longer duplicated between workflow and reference, run-prefix stated once, filler prose removed) — same information, far denser.
Fixes
npx astryxnow works when the CLI is installed as a real npm package. The bin imported its../src/*modules relative to the invoked path, so running through thenode_modules/.bin/astryxsymlink made them resolve outside the package (ERR_MODULE_NOT_FOUND: .../node_modules/src/...) on Node versions that don't realpath the bin entry. It now resolves siblings via the bin's real path (realpath ofimport.meta.url), working whether invoked via symlink, copy, or Windows shim. Also fixes the non-interactiveinit/themeerror to sayastryx <command>instead of the stalexds <command>.- Add a v0.1.0 upgrade codemod that migrates legacy
@xds/*module specifiers and config surfaces to the Astryx v0.1.0 names. [breaking] Remove legacyastryx.versionFileupdate-hint support from package.json.
Documentation
- Add npm install step to the Theme System guide
The Quick Start section jumped straight to
import {neutralTheme} from '@astryxdesign/theme-neutral', which fails withCannot find modulefor anyone who hasn't already installed the theme package. Prepend a one-line preamble +npm installcode block, and add a short prose note above the Available Themes table pointing at the install command pattern. Reported in #3082.
Other Changes
- StyleX compiler wired →
xstyle/ StyleX token imports - Tailwind → utility classes backed by
@astryxdesign/core/tailwind-theme.css - neither →
style/classNamewithvar(--token)design tokens, plus an explicit note NOT to usexstyle/utilities (they would not compile)
Contributors
Thanks to everyone who contributed to this release:
0.1.0
Breaking Changes
- Read project config from
astryx.config.mjs(wasxds.config.mjs) The CLI now resolves its optional project config fromastryx.config.mjsinstead ofxds.config.mjs— a hard cut, no fallback. Consumers with anxds.config.mjsmust rename it toastryx.config.mjs(the config shape and all fields are unchanged). Part of removingxdsnaming from the public API. - Rename the CLI command/bin from
xdstoastryxThe CLI binary is nowastryx(wasxds);bin/xds.mjsis renamed tobin/astryx.mjs, the dualxds+astryxbin entries collapse to a singleastryx, and the program/manifest name isastryx. Invoke the CLI asnpx astryx <command>(e.g.npx astryx component Button). The swizzle default output dir moves from./components/xdsto./components/astryx. Consumers usingnpx xds, anxdsnpm-script alias, or thexdsMCP server name should switch toastryx. Part of removingxdsnaming from the public API. - Rename the exported
XDSErrorclass toAstryxErrorThe CLI's programmatic API error class is renamedXDSError->AstryxError(exported from@xds/cli+ declared in its types). Consumers that catch or referenceXDSErrorfrom the CLI's API should switch toAstryxError. Part of removingxdsnaming from the public API. - Remove the XDS-prefix compatibility layer — astryx is now the only public surface
This release erases all
xdsnaming from the public API; there is no compatibility window. Consumers must migrate (we own all consumers pre-OSS): - Remove the daily, brutalist, and default themes; neutral is the new baseline Three theme packages are removed from the repo and will no longer be published:
Fixes
theme buildgenerates valid bare type imports (IconRegistry/DefinedTheme)astryx theme buildemitted.d.tsfiles importingXDSIconRegistry/XDSDefinedThemefrom@xds/core, but those aliases were removed — the generated types failed to resolve. GenerateIconRegistry/DefinedTheme(the bare names@xds/corenow exports) instead.
Documentation
- Update CLI theme docs to the current theme set
Refreshes the
astryx docs theme,getting-started,styling,styling-libraries, andmigrationreference docs to reflect the published themes:neutral,butter,chocolate,gothic,matcha,stone, andy2k. The removedtheme-default,theme-brutalist, andtheme-dailypackages are dropped from the docs, and install/import examples now use@astryxdesign/theme-neutralas the recommended starting theme.
Other Changes
- Component names: the
XDS*aliases are gone — use bare names (ButtonnotXDSButton,useThemenotuseXDSTheme,ButtonPropsnotXDSButtonProps). Thedrop-xds-prefix-importscodemod automates this. - CSS classes: components emit only
.astryx-*(the dual.xds-*class is gone). Update custom CSS selectors.xds-button->.astryx-button(prop/state value classes like.primary/.smare unchanged). - data attributes: only
data-astryx-theme/data-astryx-mediaare written; update custom selectors and SSR root attributes offdata-xds-*. - CSS layers:
@layer xds-base/xds-themeare renamed toastryx-base/astryx-theme; update your@layerorder line and any PostCSSlayersBeforeconfig.@astryxdesign/build's default library layer is nowastryx-base. - Pre-compiled stylesheet: the
@astryxdesign/core/xds.cssexport is removed — import@astryxdesign/core/astryx.css. - CSS custom properties: the
--xds-*padding fallback is gone; set--astryx-*. - CLI config key:
@astryxdesign/clireads the package.json"astryx"field (was"xds"). Rename the block; a stale"xds"key silently drops the package from discovery. @astryxdesign/theme-daily@astryxdesign/theme-brutalist@astryxdesign/theme-default- import {defaultTheme} from '@astryxdesign/theme-default/built';
- import {neutralTheme} from '@astryxdesign/theme-neutral/built';
```
```
- Remove the internal
drop-xds-meta-prefixcodemod from the OSS repo (#2970) This codemod has been moved to its own package's tooling, where it belongs. It was registered as an optional, version-independent transform and is not part of any standard upgrade path, so removing it does not affect the public0.0.13 → 0.0.15migration. - Rename the npm package scope from
@xds/*to@astryxdesign/*All published packages move to the new@astryxdesignscope (e.g.@xds/core→@astryxdesign/core), along with the workspace lockfile, build/runtime scope-directory scans, and docsite slug derivation. Consumers must update their imports and dependency names. The internal ESLint plugin namespace (@xds/*rules) is intentionally untouched and tracked separately. Existing@xds/*codemods continue to target the old scope so projects still on@xds/*can migrate.
Contributors
Thanks to everyone who contributed to this release:
0.0.15
Breaking Changes
- New
astryx upgradecodemods — This release ships codemods for the DatePicker→Input rename (rename-date-picker-to-input), Stackelement→as(rename-stack-element-to-as), ChatisStreaming→isStopShown(rename-isStreaming-to-isStopShown), imperativeref→handleRef(rename-imperative-ref-to-handleRef), the menu/selectorchildren→endContentmove (migrate-item-children-to-endcontent), and the selector function-children→renderOptionmove (migrate-selector-children-to-render-option). The bare-name migration (drop-xds-prefix-imports,drop-xds-meta-prefix) and the thememigrate-theme-selectors-to-data-attrscodemod ship as optional, run them explicitly. (#2879, #2957)
Upgrade
bashnpx astryx upgrade --apply
New Features
astryxbinary — The CLI is now also available asastryx(same launcher asxds), part of the un-prefix migration. Component discovery, the doc gate, and CI checks are prefix-agnostic — bothXDS{Name}.tsxand bare{Name}.tsxsource files are recognized. (#2867, #2878)astryx doctor— New health-check command for diagnosing project/setup issues. (#2565)- Unified search —
astryx searchsearches across components, hooks, docs, and templates in one query. (#2564) - Capability manifest — Full machine-readable capability manifest for agent discovery, plus stable machine-readable error codes on every error. (#2562, #2563)
@xds/cli/apihook export — Thehookis exposed via@xds/cli/apiwith types and parity coverage. (#2558)- CLI exit-code policy — Every user-visible error now exits with code 1 in both human and
--jsonmodes (previously several command-layer errors printed a message but exited 0, invisible to CI scripts and AI agents).xds bogus-cmd,astryx theme bogus-subcommand, the barethemegroup with an unknown subcommand, and "command not found"/"did you mean…" paths all exit 1. Help, version, and bare-list invocations still exit 0. Introduceslib/cli-error.mjsas the canonical exit-code helper. - Migration guide — Added an explicit guide for moving existing Tailwind, shadcn, and Radix applications to XDS incrementally.
- Data-attribute selector docs — Documented the data-attribute selector surface in CLI docs alongside the core dual-emit change.
Fixes
--jsonon Commander short-circuits —--jsonnow honored on parse errors and--help. A new shim wiresexitOverride()and a JSON-awareconfigureOutputonto every command and patchesoutputHelpto emit a{apiVersion, type:'help', data}envelope under--json. Parse errors produce{apiVersion, error}on stdout with exit 1; unknown subcommands now error instead of silently emitting help with exit 0;--detailis choice-validated. Non---jsoninvocations are unchanged.--jsoncontract enforcement — Commands that don't support--jsonreject the flag in apreActionhook before running side effects, soastryx init --jsonno longer creates files and then errors, leaving partial state behind.--jsonenvelope documented — Success responses are{ type, data }; error responses are{ error, suggestions? }. The--jsonhelp text describes both.xds --version --json— Emits{ type: 'version', data: { version } }instead of plain text.xds --json(no subcommand) — Emits{ type: 'help', data: { commands, jsonSupported, ... } }instead of human help text.astryx upgrade --json— "Already up to date" and "no codemods in version range" paths emit structured{ type: 'upgrade.status', ... }envelopes. The codemod runner is silent under--jsonso prompts and progress lines no longer corrupt stdout.astryx discover --json— Includesmeta: { configured: false }when no packages are configured, distinguishing "configured but empty" from "not configured".xds gap-report --json— Returns a structured error instead of starting an interactive prompt when required flags are missing; the "gh CLI missing" path also emits a JSON error.astryx theme --json— Thethemeparent command (without a subcommand) rejects--jsoncleanly;theme build --jsoncontinues to work.- Theme CSS prose regression —
astryx theme buildnow uses a single CSS generation path (@xds/core's generator) and treats a failed@xds/core/themeimport as a hard build error instead of a silent fallback, fixing the docsite Markdown typography regression after the XDS-prefix migration. (#2964)
Contributors
Thanks to everyone who contributed to this release:
0.0.14
Codemods
New Features
- New component showcases — XDSAvatarGroup, XDSInputGroup, XDSStepper, XDSButtonGroup, XDSContextMenu, XDSFileInput, XDSDateRangePicker, XDSDateTimePicker, XDSBlockquote
- Hook documentation system —
xds hooksCLI command for hook docs (#1849) - Playground defaults — Added to 19 more components (#2047)
- Theme/MediaTheme/SyntaxTheme showcases — Utility showcase support (#2040, #2028)
- Slot elements — Wired through playground UI for ReactNode props (#2012, #2005)
exampleForfield — Added to all block templates (#1966)scaffoldflag — Template metadata scaffold support (#1939)- Table page templates — Heatmap Status, Matcha Store, Chart Shoe Store (#2172, #2149, #2154)
Fixes
- Group useXDSToast and useXDSCollapsible with their parent components in docs (#2049)
- DropdownMenu inline data types — Inline into items prop docs (#2027)
- Parent hook docs to their component in docsite (#2022)
0.0.13
Codemods
New Features
--skip-installand--force-installflags forastryx upgrade(#1547)npx astryx docs iconsreference + updated icon prop descriptions (#1500)- Theme nudge in generated agent docs (#1456)
- Theme
expandColorScale— derive color tokens from accent hex inastryx theme build(#1452) - Component groups read from doc files instead of hardcoded map (#1650)
- Page and block template system (#1393)
Fixes
- Handle prerelease suffixes in
semverCompare(#1512) - Handle ternary/logical expressions in
icon-name-deprecationscodemod (#1513) - Don't inject XDS block into files without markers during upgrade (#1495)
findShowcasematches by directory name andcomponentsUsed(#1728)- Include
onMediaCSS in built theme output (#1450) - Register codemods for v0.0.13 (moved from v0.0.14) (#1508)
Upgrade
shnpx astryx upgrade --apply --to 0.0.13
0.0.12
Codemods
add-is-icon-only— AddisIconOnlyto icon-only Button and ToggleButton usages (#1257)
Upgrade
shnpx astryx upgrade --apply --to 0.0.12
0.0.10
Codemods
remove-size-props— Removesizeprop from StatusDot and ProgressBar (#966)
Upgrade
shnpx astryx upgrade --apply --to 0.0.10
0.0.8
New Features
- CLI: tsx parser for .ts files
- Update hints in postAction hook
Codemods
Upgrade
shnpx astryx upgrade --apply --to 0.0.8
0.0.7
Codemods
Upgrade
shnpx astryx upgrade --apply --to 0.0.7
0.0.6
Codemods
migrate-token-names— Design token renames per naming auditmigrate-shadow-tokens— Elevation → shadow semantic namingmigrate-collapse-to-collapsible— XDSCollapse → XDSCollapsiblemigrate-radius-tokens— Semantic radius → numeric scalemigrate-skeleton-radius— Skeleton radius prop → numeric scalemigrate-badge-children-to-label— Badge children → label prop
Upgrade
shnpx astryx upgrade --apply --to 0.0.6
0.0.5
New Features
Note: Codemods for v0.0.5 breaking changes are registered under v0.0.6. Use--to 0.0.6.
0.0.4
Features
Refactors
- Split
component.mjsintolib/modules with lazy command registry (#613)
0.0.3
Patch Changes
- Sync package.json exports map
- Add verify-exports CI check (#537)
0.0.2
New Features
astryx upgradecommand with codemod supportastryx theme build(formerlybuild-theme)
Codemods
12 codemods for the v0.0.2 breaking changes:
rename-selector-items-to-options— Selectoritems→optionsunify-visibility-to-onOpenChange— Visibility callbacks →onOpenChangeunify-uncontrolled-to-defaultX— Uncontrolled state → defaultX patternrename-banner-endButton-to-endContent— BannerendButton→endContentrename-form-tooltip-startIcon— Formtooltip→labelTooltip,startIcon→labelIconrename-isShown-to-isOpen— Dialog/PopoverisShown→isOpenrename-topnav-title-to-heading— TopNav title → headingrename-sidenav-header-to-heading— SideNav header → headingmigrate-useXDSIcon-to-getIcon—useXDSIcon()→getIcon()migrate-gap-to-numeric— String gap tokens → numericmigrate-isFullBleed-to-padding—isFullBleed→padding={0}migrate-badge-dot-to-statusdot— Badge dot → StatusDot
Upgrade
shnpx astryx upgrade --apply --to 0.0.2
0.0.1
- Initial release