@astryxdesign/cli 0.7.1-canary.ff40b78 → 0.7.2-canary.c68f5e8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/api/build/_adapter.d.mts +1 -0
  3. package/api/build/_adapter.mjs +7 -0
  4. package/api/build/build.mjs +4 -1
  5. package/api/search/search.mjs +12 -0
  6. package/api/template/table-filter-view-tabs.test.mjs +45 -0
  7. package/api/theme/build/build.core-on-demand.test.mjs +113 -0
  8. package/api/theme/build/build.d.mts +13 -13
  9. package/api/theme/build/build.mjs +72 -37
  10. package/assets/docs/getting-started.doc.mjs +0 -10
  11. package/assets/docs/styling-overview.doc.mjs +54 -15
  12. package/assets/docs/tree/migration-setup.doc.mjs +31 -6
  13. package/assets/docs/tree/responsive.doc.mjs +37 -0
  14. package/assets/docs/tree/tailwind.doc.mjs +12 -0
  15. package/assets/docs/tree/tokens-and-setup.doc.mjs +3 -2
  16. package/assets/docs/tree/tokens-syntax.doc.mjs +20 -0
  17. package/assets/docs/tree/tokens.doc.mjs +1 -1
  18. package/assets/templates/blocks/components/CommandPalette/CommandPaletteAsyncSearch.tsx +6 -1
  19. package/assets/templates/pages/table-filter/page.tsx +3 -2
  20. package/assets/templates/themes/butter/butterTheme.ts +53 -11
  21. package/assets/templates/themes/chocolate/chocolateTheme.ts +29 -5
  22. package/assets/templates/themes/gothic/gothicTheme.ts +44 -9
  23. package/assets/templates/themes/stone/stoneTheme.ts +58 -11
  24. package/assets/templates/themes/y2k/y2kTheme.ts +60 -10
  25. package/clients/cli/commands/build-theme.mjs +32 -4
  26. package/clients/cli/commands/build-theme.watch.test.mjs +63 -2
  27. package/foundation/discovery/component-discovery.mjs +27 -26
  28. package/foundation/discovery/hook-discovery.mjs +13 -12
  29. package/foundation/fs/listing-cache.d.mts +36 -0
  30. package/foundation/fs/listing-cache.mjs +95 -0
  31. package/foundation/fs/listing-cache.test.mjs +88 -0
  32. package/foundation/fs/paths.mjs +3 -2
  33. package/package.json +9 -9
package/CHANGELOG.md CHANGED
@@ -1,5 +1,40 @@
1
1
  # @xds/cli
2
2
 
3
+ # 0.7.1
4
+
5
+ #### Fixes
6
+
7
+ - `CommandPaletteInput` now exposes the semantics and name that its visible state promises. (#7321)
8
+ - A standalone field with no popup attributes was `role="combobox"` with `aria-expanded="true"` and `aria-autocomplete="list"`; it is now `role="searchbox"` with no popup state. Pass `role="combobox"`, or your popup's own ARIA attributes, to keep the old semantics. A field that already passes popup attributes (`aria-expanded`, `aria-controls`, `aria-owns`, `aria-haspopup`, and the like) stays a `combobox` and keeps them, and a field inside `CommandPalette` is unchanged.
9
+
10
+ - The field's default accessible name was its placeholder (`Search…`, or the caller's `placeholder`); it is now `Search commands`, translated. Pass `label` to keep a specific name, for example `label="Search…"` for the old default. A caller whose custom `placeholder` served as the name keeps working code and hears the generic name until it passes `label`.
11
+
12
+ - The CLI's async-search CommandPalette block names its field `Search files`.
13
+
14
+ - `endContent={true}` no longer renders an empty trailing slot, and `endContent={0}` renders inside the trailing slot instead of after it.
15
+ - Stone, Y2K, and Gothic set `CodeBlock`'s diff row slots in their own syntax palettes, so a reader of a diff on those themes gets added and removed rows whose code, line numbers, and `+`/`-` glyphs stay readable: every syntax token keeps at least 4.5:1 on the tinted rows in Stone (light and dark) and Y2K (light and dark), and in Gothic the rows take a deep green and a deep red at the background's own luminance, so every token keeps the contrast it has on the plain background. Line numbers and glyphs on changed rows use each theme's own green and red. The CLI's bundled copies of the three themes (`astryx theme eject`) carry the same palettes. (#7428)
16
+ - A `secondary` Selector, MultiSelector, or ComplexSelector now matches the theme's secondary Button: under butter, chocolate, gothic, stone, and y2k it takes that Button's border, fill, text color, and hover instead of the default borderless neutral fill, so a toolbar mixing secondary buttons and selectors reads as one set of controls. Disabled and read-only triggers keep a still fill on hover, as a disabled Button does, and a Selector or MultiSelector inside an InputGroup keeps the group's neutral fill and edges. The default theme is unchanged, the selector docs now say `secondary` follows the theme's secondary Button, and themes added with the CLI get the same parity. (#7548)
17
+ - `astryx theme build --watch` stops as soon as it is interrupted (Ctrl-C or SIGTERM). A change saved while a rebuild was running no longer starts another rebuild after "Stopped watching.", and a rebuild in progress stops with the watcher instead of keeping the process alive. (#7508)
18
+
19
+ #### Performance
20
+
21
+ - `astryx search` and `astryx build`, and the `search` and `build` API functions, read each source directory once per call instead of once per candidate. Results are unchanged. A warm `search` drops from about 1.9 s to 0.4 s on macOS. (#7421)
22
+
23
+ #### Documentation
24
+
25
+ - The generated token docs list the five diff row slots of the syntax vocabulary — `--color-syntax-added`, `--color-syntax-removed`, `--color-syntax-diff-header`, `--color-syntax-added-marker`, `--color-syntax-removed-marker` — with their defaults, so `astryx docs tokens` describes nineteen syntax slots. (#7389)
26
+ - The Tailwind and migration setup docs now say that `globals.css` (or a `layers.css` holding only the `@layer` line) must be the first import in the root layout or app entry, before the `astryx-themes` module or any component that imports theme CSS; otherwise Tailwind preflight overrides Astryx base styles and theme overrides lose. The Tailwind v3 coexistence snippet moves its `@layer` line into a `layers.css` imported first, because Next.js (webpack or Turbopack) hoists CSS imports above an inline `@layer` line. (#7534)
27
+ - Grid's `columns` docs now lead with the shape that reflows: a numeric `columns` is exactly N columns at every width and never reflows, and `columns={{minWidth, max}}` is the form for columns that must survive a phone. The `astryx docs layout/responsive` topic gains a "Columns that reflow" section with a reflowing stat row beside a fixed week row. No behavior changes. (#7340)
28
+ - The styling setup docs now load the prebuilt stylesheets on every path. The "StyleX from source" path becomes "StyleX for your own code": it keeps `astryx.css` and points the StyleX compiler at your own and swizzled source only. A new "Other Astryx packages" section lists each package's stylesheet (`richtext.css`, `lab.css`, `charts.css`) and where to import it. Getting started no longer lists the two example setups that compile Astryx source; the remaining examples all load `astryx.css`. (#7587)
29
+
30
+ #### Contributors
31
+
32
+ Thanks to everyone who contributed to this release:
33
+
34
+ - @cixzhang
35
+
36
+ ---
37
+
3
38
  # 0.7.0
4
39
 
5
40
  #### Breaking Changes
@@ -66,6 +66,7 @@ export function isWeightsFile(file: any): file is import("./kit/weights.mjs").We
66
66
  * @returns {string[]} Setup notes (empty when the template needs nothing).
67
67
  */
68
68
  export function templateSetupNotes(template: PageTemplate, cwd: string): string[];
69
+ export { withListingCache } from "../../foundation/fs/listing-cache.mjs";
69
70
  /**
70
71
  * A page template the kit can recommend starting from.
71
72
  */
@@ -23,6 +23,13 @@ import {componentKeywords} from '../search/search.mjs';
23
23
  import {findCoreDir} from '../../foundation/fs/paths.mjs';
24
24
  import {analyzeTemplateNeeds} from '../../foundation/discovery/template-needs.mjs';
25
25
 
26
+ /**
27
+ * Runs one build with a single directory listing per path: the kit resolves
28
+ * every candidate's source and docs by walking the same tree, which does not
29
+ * change during the call.
30
+ */
31
+ export {withListingCache} from '../../foundation/fs/listing-cache.mjs';
32
+
26
33
  /**
27
34
  * A page template the kit can recommend starting from.
28
35
  * @typedef {object} PageTemplate
@@ -17,6 +17,7 @@ import {AstryxError} from '../error.mjs';
17
17
  import {ERROR_CODES} from '../../foundation/response/error-codes.mjs';
18
18
  import {buildHelp} from './help/help.mjs';
19
19
  import {buildKit} from './kit/kit.mjs';
20
+ import {withListingCache} from './_adapter.mjs';
20
21
 
21
22
  // Barrel: surface the leaves alongside the dispatcher for the fractal shape.
22
23
  export {buildHelp, buildKit};
@@ -40,5 +41,7 @@ export async function build(query, options = {}) {
40
41
  ERROR_CODES.ERR_INVALID_ARGUMENT,
41
42
  );
42
43
  }
43
- return buildKit(query, options);
44
+ // The kit resolves every candidate's source and docs by walking the same
45
+ // tree; read each directory once for this call.
46
+ return withListingCache(() => buildKit(query, options));
44
47
  }
@@ -79,6 +79,7 @@
79
79
  * queries they have nothing to do with.
80
80
  */
81
81
 
82
+ import {withListingCache} from '../../foundation/fs/listing-cache.mjs';
82
83
  import {readDocView} from '../../foundation/doc-compiler/read.mjs';
83
84
  import {findCoreDir} from '../../foundation/fs/paths.mjs';
84
85
  import {
@@ -1769,6 +1770,17 @@ function toResult(c, score, reason, matchedTerms, queryTerms) {
1769
1770
  * @returns {Promise<import('./search.type.mjs').SearchResponse>}
1770
1771
  */
1771
1772
  export async function search(query, options = {}) {
1773
+ // Every candidate resolves its source and docs by walking the same tree;
1774
+ // read each directory once for this call.
1775
+ return withListingCache(() => searchOnce(query, options));
1776
+ }
1777
+
1778
+ /**
1779
+ * @param {string} query
1780
+ * @param {NonNullable<Parameters<typeof search>[1]>} options
1781
+ * @returns {ReturnType<typeof search>}
1782
+ */
1783
+ async function searchOnce(query, options) {
1772
1784
  const {cwd = process.cwd(), type, limit = 20} = options;
1773
1785
 
1774
1786
  if (!query || !String(query).trim()) {
@@ -0,0 +1,45 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file Regression coverage for the table-filter page's compact view-options tabs.
5
+ * @input The shipped table-filter page template source.
6
+ * @output Assertions that the shared tabpanel is labelled by its selected tab.
7
+ * @position Template contract test for the tabs relationship in the narrow-view sheet.
8
+ */
9
+
10
+ import fs from 'node:fs';
11
+ import path from 'node:path';
12
+ import {fileURLToPath} from 'node:url';
13
+ import {describe, expect, it} from 'vitest';
14
+
15
+ const sourcePath = path.resolve(
16
+ path.dirname(fileURLToPath(import.meta.url)),
17
+ '../../assets/templates/pages/table-filter/page.tsx',
18
+ );
19
+ const source = fs.readFileSync(sourcePath, 'utf8');
20
+ const sheet = source.match(
21
+ /const viewOptionsSheet = \(([\s\S]*?)\n {2}\);/,
22
+ )?.[1];
23
+ const tab = sheet?.match(/<Tab\b([\s\S]*?)\/>/)?.[1];
24
+ const panel = sheet?.match(/<VStack\b([^>]*?role="tabpanel"[^>]*?)>/)?.[1];
25
+
26
+ describe('table-filter view-options tabs', () => {
27
+ it('finds the compact sheet, its tab and its panel', () => {
28
+ expect(sheet).toBeDefined();
29
+ expect(tab).toBeDefined();
30
+ expect(panel).toBeDefined();
31
+ });
32
+
33
+ it('gives every tab an id derived from the panel id and its section', () => {
34
+ expect(tab).toContain('id={`${viewPanelId}-tab-${section.key}`}');
35
+ expect(tab).toContain('panelId={viewPanelId}');
36
+ });
37
+
38
+ it('labels the shared panel by the selected tab, not a fixed string', () => {
39
+ expect(panel).toContain('id={viewPanelId}');
40
+ expect(panel).toContain(
41
+ 'aria-labelledby={`${viewPanelId}-tab-${viewSection}`}',
42
+ );
43
+ expect(panel).not.toContain('aria-label=');
44
+ });
45
+ });
@@ -0,0 +1,113 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file The theme build imports Core when a build runs, not when the CLI starts.
5
+ *
6
+ * The CLI loads the theme build module for every command, and Core's root
7
+ * entry pulls in all of Core and React. So a command that builds no theme must
8
+ * not ask for either, or `astryx --version` pays for them whenever a Core is
9
+ * installed. A resolve hook in a real subprocess records every request for
10
+ * Core or React and the module that made it, whether or not the request
11
+ * resolves.
12
+ */
13
+
14
+ import {afterAll, beforeAll, describe, expect, it} from 'vitest';
15
+ import {spawnSync} from 'node:child_process';
16
+ import * as fs from 'node:fs';
17
+ import * as os from 'node:os';
18
+ import * as path from 'node:path';
19
+ import {fileURLToPath, pathToFileURL} from 'node:url';
20
+
21
+ const BIN = fileURLToPath(
22
+ new URL('../../../clients/cli/bin/astryx.mjs', import.meta.url),
23
+ );
24
+
25
+ /** @type {string} */
26
+ let dir;
27
+ /** @type {string} */
28
+ let register;
29
+ /** @type {string} */
30
+ let log;
31
+
32
+ beforeAll(() => {
33
+ dir = fs.mkdtempSync(path.join(os.tmpdir(), 'astryx-core-on-demand-'));
34
+ log = path.join(dir, 'requests.jsonl');
35
+ const hooks = path.join(dir, 'hooks.mjs');
36
+ fs.writeFileSync(
37
+ hooks,
38
+ [
39
+ "import {appendFileSync} from 'node:fs';",
40
+ `const LOG = ${JSON.stringify(log)};`,
41
+ 'const WATCHED = /^(?:@astryxdesign\\/core|react|react-dom)(?:\\/|$)/;',
42
+ 'export async function resolve(specifier, context, nextResolve) {',
43
+ ' if (WATCHED.test(specifier)) {',
44
+ ' const parent = context.parentURL ?? null;',
45
+ " appendFileSync(LOG, JSON.stringify({specifier, parent}) + '\\n');",
46
+ ' }',
47
+ ' return nextResolve(specifier, context);',
48
+ '}',
49
+ '',
50
+ ].join('\n'),
51
+ );
52
+ register = path.join(dir, 'register.mjs');
53
+ fs.writeFileSync(
54
+ register,
55
+ `import {register} from 'node:module';\nregister(${JSON.stringify(pathToFileURL(hooks).href)});\n`,
56
+ );
57
+ // A plain theme object: the file itself never imports Core, so a request for
58
+ // Core can only come from the build.
59
+ fs.writeFileSync(
60
+ path.join(dir, 'ocean.mjs'),
61
+ "export default {name: 'ocean', tokens: {'--color-bg': '#0a0a0a'}};\n",
62
+ );
63
+ });
64
+
65
+ afterAll(() => {
66
+ fs.rmSync(dir, {recursive: true, force: true});
67
+ });
68
+
69
+ /**
70
+ * Run the CLI and return each Core or React specifier it asked for, mapped to
71
+ * the first module that asked.
72
+ * @param {string[]} args
73
+ * @returns {{status: number | null, requested: Record<string, string | null>}}
74
+ */
75
+ function run(args) {
76
+ fs.rmSync(log, {force: true});
77
+ const res = spawnSync(
78
+ process.execPath,
79
+ ['--import', pathToFileURL(register).href, BIN, ...args],
80
+ {cwd: dir, encoding: 'utf-8', timeout: 20_000},
81
+ );
82
+ /** @type {Record<string, string | null>} */
83
+ const requested = {};
84
+ const lines = fs.existsSync(log)
85
+ ? fs.readFileSync(log, 'utf-8').split('\n').filter(Boolean)
86
+ : [];
87
+ for (const line of lines) {
88
+ const {specifier, parent} = JSON.parse(line);
89
+ if (!(specifier in requested)) requested[specifier] = parent;
90
+ }
91
+ return {status: res.status, requested};
92
+ }
93
+
94
+ describe('theme build imports Core on demand', () => {
95
+ it('astryx --version asks for neither Core nor React', () => {
96
+ const {status, requested} = run(['--version']);
97
+ expect(status).toBe(0);
98
+ expect(requested).toEqual({});
99
+ });
100
+
101
+ it('theme build imports Core from the theme build module', () => {
102
+ const {requested} = run([
103
+ 'theme',
104
+ 'build',
105
+ 'ocean.mjs',
106
+ '--check',
107
+ '--json',
108
+ ]);
109
+ expect(requested['@astryxdesign/core/theme']).toMatch(
110
+ /\/api\/theme\/build\/build\.mjs$/,
111
+ );
112
+ });
113
+ });
@@ -163,6 +163,19 @@ export function themeBuildFamily(files: string[], options: {
163
163
  checked?: undefined;
164
164
  };
165
165
  }>;
166
+ /**
167
+ * The bindings one Core provides: the namespaces interception spreads, the
168
+ * functions the build calls, and why the import failed, if it did.
169
+ */
170
+ export type CoreBindings = {
171
+ themeModule: any;
172
+ rootModule: any;
173
+ importError: any;
174
+ defineTheme: any;
175
+ generateThemeRulesSplit: any;
176
+ generateOnMediaCSS: any;
177
+ generateAdaptationCSS: any;
178
+ };
166
179
  /**
167
180
  * How one `prop:value` pair on a component target is classified — the single
168
181
  * vocabulary both the root and adaptation paths reason in.
@@ -210,16 +223,3 @@ export type ThemeBuildTrailer = {
210
223
  */
211
224
  fontNotices: string[];
212
225
  };
213
- /**
214
- * The bindings one Core provides: the namespaces interception spreads, the
215
- * functions the build calls, and why the import failed, if it did.
216
- */
217
- export type CoreBindings = {
218
- themeModule: any;
219
- rootModule: any;
220
- importError: any;
221
- defineTheme: any;
222
- generateThemeRulesSplit: any;
223
- generateOnMediaCSS: any;
224
- generateAdaptationCSS: any;
225
- };
@@ -37,6 +37,10 @@
37
37
  * general compatibility scheme for every export an arbitrary older core might
38
38
  * lack.
39
39
  *
40
+ * Core is imported on first use, never when this module loads: the CLI loads
41
+ * this module for every command, and Core's root entry pulls in all of Core
42
+ * and React.
43
+ *
40
44
  * With `{check: true}`, it compiles the same outputs in memory but writes
41
45
  * nothing: it compares each generated file against what is on disk (ignoring
42
46
  * only the volatile `@generated` `Command:` line) and returns a
@@ -91,6 +95,8 @@ import {resolveIconImports} from './icon-imports.mjs';
91
95
  // action). A built, resolvable `@astryxdesign/core` is a hard requirement.
92
96
  // A built, resolvable `@astryxdesign/core` is a hard requirement. These are
93
97
  // populated from a dynamic import (a runtime boundary), so `any` is intentional.
98
+ // That import runs the first time a build needs Core (see `loadCliCore`), not
99
+ // when this module loads.
94
100
  //
95
101
  // The CLI and core are independently versioned optional peers, so this import
96
102
  // can succeed against a core OLDER than the CLI. Only the exports every theme
@@ -120,27 +126,6 @@ import {resolveIconImports} from './icon-imports.mjs';
120
126
  */
121
127
  /** @type {any} */ let _coreRootModule = null;
122
128
  /** @type {any} */ let _coreImportError = null;
123
- try {
124
- const coreTheme = await import('@astryxdesign/core/theme');
125
- _coreThemeModule = coreTheme;
126
- _defineTheme = coreTheme.defineTheme;
127
- _generateThemeRulesSplit = coreTheme.generateThemeRulesSplit;
128
- _generateOnMediaCSS = coreTheme.generateOnMediaCSS;
129
- _generateAdaptationCSS = coreTheme.generateAdaptationCSS;
130
- try {
131
- _coreRootModule = await import('@astryxdesign/core');
132
- } catch {
133
- // A core without a usable root entry still builds themes; interception
134
- // falls back to the theme namespace for the root specifier.
135
- }
136
- } catch (e) {
137
- // Capture the reason so the theme action can surface a precise, actionable
138
- // error. We don't throw here: this module is imported eagerly by the CLI
139
- // entrypoint for every command, and a throw at load time would break
140
- // unrelated commands (the entry wraps loads in try/catch and degrades the
141
- // command to a stub). The hard failure happens when `theme build` runs.
142
- _coreImportError = e;
143
- }
144
129
 
145
130
  /**
146
131
  * The bindings one Core provides: the namespaces interception spreads, the
@@ -157,19 +142,65 @@ try {
157
142
  */
158
143
 
159
144
  /**
160
- * The bindings exactly as the CLI's own import left them, kept so each build
161
- * can choose between them and the project's installed Core.
162
- * @type {CoreBindings}
145
+ * Import the CLI's own Core: the `@astryxdesign/core` this module resolves.
146
+ * Never throws. A failed import is kept as `importError`, so the build can
147
+ * surface a precise, actionable error when it runs.
148
+ * @returns {Promise<CoreBindings>}
163
149
  */
164
- const _cliCore = {
165
- themeModule: _coreThemeModule,
166
- rootModule: _coreRootModule,
167
- importError: _coreImportError,
168
- defineTheme: _defineTheme,
169
- generateThemeRulesSplit: _generateThemeRulesSplit,
170
- generateOnMediaCSS: _generateOnMediaCSS,
171
- generateAdaptationCSS: _generateAdaptationCSS,
172
- };
150
+ async function importCliCore() {
151
+ /** @type {CoreBindings} */
152
+ const core = {
153
+ themeModule: null,
154
+ rootModule: null,
155
+ importError: null,
156
+ defineTheme: null,
157
+ generateThemeRulesSplit: null,
158
+ generateOnMediaCSS: null,
159
+ generateAdaptationCSS: null,
160
+ };
161
+ try {
162
+ const coreTheme = await import('@astryxdesign/core/theme');
163
+ core.themeModule = coreTheme;
164
+ core.defineTheme = coreTheme.defineTheme;
165
+ core.generateThemeRulesSplit = coreTheme.generateThemeRulesSplit;
166
+ core.generateOnMediaCSS = coreTheme.generateOnMediaCSS;
167
+ core.generateAdaptationCSS = coreTheme.generateAdaptationCSS;
168
+ try {
169
+ core.rootModule = await import('@astryxdesign/core');
170
+ } catch {
171
+ // A core without a usable root entry still builds themes; interception
172
+ // falls back to the theme namespace for the root specifier.
173
+ }
174
+ } catch (e) {
175
+ // Capture the reason so the theme action can surface a precise, actionable
176
+ // error. The hard failure happens when `theme build` runs.
177
+ core.importError = e;
178
+ }
179
+ return core;
180
+ }
181
+
182
+ /**
183
+ * The CLI's own Core, imported once, the first time a build needs it. The CLI
184
+ * loads this module for every command, and Core's root entry loads all of Core
185
+ * and React, so an import at module load would make every command
186
+ * (`--version` included) pay for that whenever a Core resolves.
187
+ * @type {Promise<CoreBindings> | null}
188
+ */
189
+ let _cliCorePromise = null;
190
+
191
+ /**
192
+ * The CLI's own Core, kept so each build can choose between it and the
193
+ * project's installed Core. The first call also points the build at it, so a
194
+ * step that runs before any build has chosen a Core reads the CLI's own.
195
+ * @returns {Promise<CoreBindings>}
196
+ */
197
+ function loadCliCore() {
198
+ _cliCorePromise ??= importCliCore().then(core => {
199
+ setCore(core);
200
+ return core;
201
+ });
202
+ return _cliCorePromise;
203
+ }
173
204
 
174
205
  /**
175
206
  * Point the build at one Core: the module-level bindings every step reads.
@@ -248,13 +279,14 @@ function realOrSelf(file) {
248
279
  * repository, a bare directory), the same Core, or one that does not load.
249
280
  *
250
281
  * @param {string} cwd
282
+ * @param {CoreBindings} cliCore - The CLI's own Core, from {@link loadCliCore}.
251
283
  * @returns {Promise<CoreBindings | null>}
252
284
  */
253
- async function loadProjectCore(cwd) {
285
+ async function loadProjectCore(cwd, cliCore) {
254
286
  const dir = findInstalledPackage(cwd, '@astryxdesign/core');
255
287
  const themeFile = dir ? importTarget(dir, './theme') : null;
256
288
  if (!dir || !themeFile) return null;
257
- if (_cliCore.themeModule) {
289
+ if (cliCore.themeModule) {
258
290
  try {
259
291
  const own = fileURLToPath(
260
292
  import.meta.resolve('@astryxdesign/core/theme'),
@@ -301,9 +333,10 @@ const _projectCores = new Map();
301
333
  * @param {string} cwd
302
334
  */
303
335
  async function selectCore(cwd) {
336
+ const cliCore = await loadCliCore();
304
337
  if (!_projectCores.has(cwd))
305
- _projectCores.set(cwd, await loadProjectCore(cwd));
306
- setCore(_projectCores.get(cwd) ?? _cliCore);
338
+ _projectCores.set(cwd, await loadProjectCore(cwd, cliCore));
339
+ setCore(_projectCores.get(cwd) ?? cliCore);
307
340
  }
308
341
 
309
342
  /**
@@ -1694,6 +1727,8 @@ export async function validateThemePrivateInputs(
1694
1727
  );
1695
1728
  }
1696
1729
 
1730
+ // The CLI's own Core, unless a build in this process already chose one.
1731
+ await loadCliCore();
1697
1732
  const interception = interceptCore(_coreThemeModule, _coreRootModule);
1698
1733
  let theme;
1699
1734
  try {
@@ -185,16 +185,6 @@ const overrides = stylex.create({
185
185
  'Next.js + Tailwind bridge',
186
186
  '[apps/example-nextjs-tailwind](https://github.com/facebook/astryx/tree/main/apps/example-nextjs-tailwind)',
187
187
  ],
188
- [
189
- 'Next.js Source',
190
- 'Next.js importing from source',
191
- '[apps/example-nextjs-source](https://github.com/facebook/astryx/tree/main/apps/example-nextjs-source)',
192
- ],
193
- [
194
- 'Vite',
195
- 'Vite',
196
- '[apps/example-vite](https://github.com/facebook/astryx/tree/main/apps/example-vite)',
197
- ],
198
188
  [
199
189
  'Vite + Tailwind',
200
190
  'Vite + Tailwind bridge',
@@ -4,7 +4,8 @@
4
4
  * @file `astryx docs styling-overview`
5
5
  *
6
6
  * One question: "How do I set up CSS?"
7
- * Three paths, each with its exact imports.
7
+ * Three paths, each with its exact imports. Every path loads the prebuilt
8
+ * stylesheets (spec:AST-080 FR5); none compiles Astryx package source.
8
9
  */
9
10
 
10
11
  /** @type {import('@astryxdesign/cli/authoring').ReferenceDoc} */
@@ -14,7 +15,7 @@ export const docs = {
14
15
  title: 'Styling overview',
15
16
  category: 'guide',
16
17
  description:
17
- 'How to set up CSS — exact imports for three paths: plain, Tailwind, and StyleX from source.',
18
+ 'How to set up CSS — exact imports for three paths: plain, Tailwind, and StyleX for your own code — plus the stylesheet each Astryx package ships.',
18
19
  keywords: [
19
20
  'styling',
20
21
  'CSS',
@@ -46,15 +47,15 @@ export const docs = {
46
47
  "None beyond Tailwind's own.",
47
48
  ],
48
49
  [
49
- 'StyleX from source',
50
- 'Swizzled (ejected) components. Raw StyleX source needs a compiler plugin.',
51
- 'Yes \u2014 a StyleX bundler plugin.',
50
+ 'StyleX for your own code',
51
+ 'Your own `stylex.create()` styles and swizzled (ejected) components. Astryx itself stays prebuilt.',
52
+ 'Yes \u2014 a StyleX plugin for your own code.',
52
53
  ],
53
54
  ],
54
55
  },
55
56
  {
56
57
  type: 'prose',
57
- text: 'All three resolve to the same design tokens and support theming and dark mode. You can mix them in one app.',
58
+ text: 'All three load the same prebuilt Astryx stylesheets, resolve to the same design tokens, and support theming and dark mode. None of them compiles Astryx package source. You can mix them in one app.',
58
59
  },
59
60
  ],
60
61
  },
@@ -117,12 +118,12 @@ export const docs = {
117
118
  ],
118
119
  },
119
120
  {
120
- id: 'stylex-from-source',
121
- title: 'StyleX from source',
121
+ id: 'stylex-own-code',
122
+ title: 'StyleX for your own code',
122
123
  content: [
123
124
  {
124
125
  type: 'prose',
125
- text: 'Only needed when you swizzle a component (`astryx swizzle <Component>`) or write your own StyleX. The plain path already supports `xstyle` overrides \u2014 this path is for editing component internals. Some built-in page templates also use StyleX for layout; if you scaffold one and see "Unexpected stylex.create call at runtime", add the compiler plugin for your bundler below.',
126
+ text: 'Only needed when you write your own StyleX (`stylex.create()`) or swizzle a component (`astryx swizzle <Component>`), which copies its StyleX source into your app. The plain path already supports `xstyle` overrides \u2014 this path is for your own StyleX code. Some built-in page templates also use StyleX for layout; if you scaffold one and see "Unexpected stylex.create call at runtime", add the compiler plugin for your bundler below.',
126
127
  },
127
128
  {
128
129
  type: 'code',
@@ -134,14 +135,13 @@ export const docs = {
134
135
  type: 'code',
135
136
  lang: 'css',
136
137
  label: 'globals.css',
137
- code: `@layer reset, astryx-base, astryx-theme;
138
-
139
- @import "@astryxdesign/core/reset.css";
138
+ code: `@import "@astryxdesign/core/reset.css";
139
+ @import "@astryxdesign/core/astryx.css";
140
140
  @import "@astryxdesign/theme-neutral/theme.css";`,
141
141
  },
142
142
  {
143
143
  type: 'prose',
144
- text: 'No `astryx.css` \u2014 the StyleX compiler produces component styles from source. Add the plugin for your bundler:',
144
+ text: 'Keep `astryx.css`: Astryx component styles always come from the prebuilt stylesheets. Point the StyleX compiler at your own source only, never at `node_modules/@astryxdesign`. Add the plugin for your bundler:',
145
145
  },
146
146
  {
147
147
  type: 'table',
@@ -149,7 +149,10 @@ export const docs = {
149
149
  rows: [
150
150
  ['Webpack', '@stylexjs/webpack-plugin'],
151
151
  ['Vite / Rollup', '@stylexjs/rollup-plugin'],
152
- ['Babel (any)', '@stylexjs/babel-plugin + @stylexjs/postcss-plugin'],
152
+ [
153
+ 'Babel (any)',
154
+ '@stylexjs/babel-plugin + @stylexjs/postcss-plugin',
155
+ ],
153
156
  ['Next.js (keep next/font)', '@stylexswc/nextjs-plugin'],
154
157
  ],
155
158
  },
@@ -159,6 +162,37 @@ export const docs = {
159
162
  },
160
163
  ],
161
164
  },
165
+ {
166
+ id: 'other-packages',
167
+ title: 'Other Astryx packages',
168
+ content: [
169
+ {
170
+ type: 'prose',
171
+ text: 'Every Astryx package whose components carry styles ships its own prebuilt stylesheet. Import it after `astryx.css` for each package you use. All of them place their rules in the same `astryx-base` layer, so your theme overrides win over every package.',
172
+ },
173
+ {
174
+ type: 'code',
175
+ lang: 'css',
176
+ label: 'globals.css',
177
+ code: `@import "@astryxdesign/core/reset.css";
178
+ @import "@astryxdesign/core/astryx.css";
179
+ @import "@astryxdesign/richtext/richtext.css"; /* Rich Text */
180
+ @import "@astryxdesign/lab/lab.css"; /* Lab */
181
+ @import "@astryxdesign/charts/charts.css"; /* Charts */
182
+ @import "@astryxdesign/theme-neutral/theme.css";`,
183
+ },
184
+ {
185
+ type: 'table',
186
+ headers: ['Package', 'Stylesheet'],
187
+ rows: [
188
+ ['@astryxdesign/core', '@astryxdesign/core/astryx.css'],
189
+ ['@astryxdesign/richtext', '@astryxdesign/richtext/richtext.css'],
190
+ ['@astryxdesign/lab', '@astryxdesign/lab/lab.css'],
191
+ ['@astryxdesign/charts', '@astryxdesign/charts/charts.css'],
192
+ ],
193
+ },
194
+ ],
195
+ },
162
196
  {
163
197
  id: 'what-each-file-does',
164
198
  title: 'What each import does',
@@ -175,7 +209,12 @@ export const docs = {
175
209
  [
176
210
  'astryx.css',
177
211
  'astryx-base',
178
- 'Pre-compiled component styles. Required for plain and Tailwind. Omit when compiling from source.',
212
+ 'Prebuilt component styles. Required on every path.',
213
+ ],
214
+ [
215
+ 'richtext.css, lab.css, charts.css',
216
+ 'astryx-base',
217
+ 'Prebuilt styles for each other Astryx package you use. Load after astryx.css.',
179
218
  ],
180
219
  [
181
220
  'theme.css',