@astryxdesign/cli 0.4.6 → 0.4.7-canary.2979219

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 (58) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/README.md +1 -0
  3. package/api/index.d.mts +1 -1
  4. package/api/index.mjs +1 -1
  5. package/api/theme/build/build.mjs +8 -38
  6. package/api/theme/targets/targets.d.mts +18 -0
  7. package/api/theme/targets/targets.mjs +87 -0
  8. package/api/theme/targets/targets.test.mjs +65 -0
  9. package/api/theme/theme.d.mts +1 -0
  10. package/api/theme/theme.mjs +3 -1
  11. package/api/theme/theme.type.d.mts +23 -0
  12. package/api/theme/theme.type.mjs +21 -1
  13. package/api/theme/themeTargets.doc.d.mts +11 -0
  14. package/api/theme/themeTargets.doc.mjs +58 -0
  15. package/assets/docs/README.md +50 -0
  16. package/assets/docs/cli-integrations.doc.mjs +4 -4
  17. package/assets/docs/theme.doc.dense.mjs +1 -1
  18. package/assets/docs/theme.doc.mjs +1 -1
  19. package/assets/templates/blocks/components/Dialog/DialogAdaptivePresentation.doc.mjs +25 -0
  20. package/assets/templates/blocks/components/Dialog/DialogAdaptivePresentation.tsx +167 -0
  21. package/assets/templates/blocks/components/Dialog/DialogScrollingContent.tsx +1 -1
  22. package/assets/templates/blocks/components/Step/StepContent.doc.mjs +14 -0
  23. package/assets/templates/blocks/components/Step/StepContent.tsx +32 -0
  24. package/assets/templates/blocks/components/Step/StepIndicator.doc.mjs +14 -0
  25. package/assets/templates/blocks/components/Step/StepIndicator.tsx +60 -0
  26. package/assets/templates/blocks/components/Step/StepShowcase.doc.mjs +15 -0
  27. package/assets/templates/blocks/components/Step/StepShowcase.tsx +26 -0
  28. package/assets/templates/blocks/components/Step/StepStates.doc.mjs +14 -0
  29. package/assets/templates/blocks/components/Step/StepStates.tsx +46 -0
  30. package/assets/templates/blocks/components/Stepper/StepperCustomContent.doc.mjs +22 -0
  31. package/assets/templates/blocks/components/Stepper/StepperCustomContent.tsx +126 -0
  32. package/assets/templates/blocks/components/Stepper/StepperIndicatorModes.doc.mjs +1 -1
  33. package/assets/templates/blocks/components/Stepper/StepperIndicatorModes.tsx +17 -5
  34. package/assets/templates/blocks/components/Stepper/StepperOnTrackHorizontal.doc.mjs +14 -0
  35. package/assets/templates/blocks/components/Stepper/StepperOnTrackHorizontal.tsx +25 -0
  36. package/assets/templates/blocks/components/Stepper/StepperOnTrackVertical.doc.mjs +2 -2
  37. package/assets/templates/blocks/components/Stepper/StepperOnTrackVertical.tsx +1 -1
  38. package/assets/templates/blocks/components/Stepper/StepperShowcase.doc.mjs +1 -1
  39. package/assets/templates/blocks/components/Stepper/StepperShowcase.tsx +6 -7
  40. package/assets/templates/blocks/components/Stepper/StepperStatus.tsx +1 -1
  41. package/clients/cli/commands/build-theme.mjs +85 -0
  42. package/clients/cli/commands/dialog-adaptive-template.test.mjs +24 -0
  43. package/clients/cli/commands/theme-targets.behavior.test.mjs +64 -0
  44. package/clients/cli/commands/theme-targets.doc.mjs +38 -0
  45. package/clients/cli/commands/theme.doc.mjs +4 -2
  46. package/clients/cli/index.mjs +1 -0
  47. package/clients/cli/lib/manifest.mjs +2 -0
  48. package/foundation/discovery/theming-targets.d.mts +48 -0
  49. package/foundation/discovery/theming-targets.mjs +135 -0
  50. package/foundation/discovery/theming-targets.test.mjs +127 -0
  51. package/foundation/response/response-types.doc.mjs +5 -0
  52. package/package.json +9 -9
  53. package/assets/templates/blocks/components/Stepper/StepperHorizontal.doc.mjs +0 -14
  54. package/assets/templates/blocks/components/Stepper/StepperHorizontal.tsx +0 -24
  55. package/assets/templates/blocks/components/Stepper/StepperMultiStepForm.doc.mjs +0 -14
  56. package/assets/templates/blocks/components/Stepper/StepperMultiStepForm.tsx +0 -92
  57. package/assets/templates/blocks/components/Stepper/StepperVerticalOnboarding.doc.mjs +0 -14
  58. package/assets/templates/blocks/components/Stepper/StepperVerticalOnboarding.tsx +0 -40
package/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # @xds/cli
2
2
 
3
+ # 0.4.7
4
+
5
+ ---
6
+
3
7
  # 0.4.6
4
8
 
5
9
  #### New Features
package/README.md CHANGED
@@ -419,6 +419,7 @@ Every response has a `type` discriminant. The full set is below (generated from
419
419
  | `theme.list` | Every bundled theme as a ThemeListEntry[]: each with slug, displayName, description, and a maintained flag. |
420
420
  | `theme.add` | A scaffold receipt: resolved slug, displayName, maintained flag, outputDir (relative to cwd), the theme entry file, its exportName, and the files written. |
421
421
  | `theme.template` | A write receipt for the annotated theme template: the path (relative to cwd), whether it was written, and the reason it was not — `exists` when a file was already there, which is a success. |
422
+ | `theme.targets` | The whole themeable surface: the echoed filter, the component count, and one entry per theming target — {key, className, component, props, states}, where props and states are its legal override keys. |
422
423
  | `upgrade.list` | Every available codemod, oldest→newest, as {name, title, version, optional}; returned for --list without running anything. |
423
424
  | `upgrade.status` | A short-circuit outcome with no codemods run (up_to_date, no_codemods, or config_fixable), each carrying the agent-docs summary. |
424
425
  | `upgrade.run` | The run receipt: from/to versions, codemod count, integrations processed, the agent-docs summary, and (apply mode) filesChanged, transformsApplied, and per-codemod errors. |
package/api/index.d.mts CHANGED
@@ -31,6 +31,6 @@ export * from "./doctor/doctor.type.mjs";
31
31
  export * from "./layout/layout.type.mjs";
32
32
  export * from "./integration/validate-integration.type.mjs";
33
33
  export type Logger = import("./logger.mjs").Logger;
34
- export { themeBuild, themeAdd, themeList, listThemes } from "./theme/theme.mjs";
34
+ export { themeBuild, themeAdd, themeList, themeTargets, listThemes } from "./theme/theme.mjs";
35
35
  export { layoutExpand, layoutCheck, layoutGrammar } from "./layout/layout.mjs";
36
36
  export { validateIntegration, summarizeIssues } from "./integration/validate-integration.mjs";
package/api/index.mjs CHANGED
@@ -25,7 +25,7 @@ export {docs} from './docs/docs.mjs';
25
25
  export {blog} from './blog/blog.mjs';
26
26
  export {discover} from './discover/discover.mjs';
27
27
  export {template} from './template/template.mjs';
28
- export {themeBuild, themeAdd, themeList, listThemes} from './theme/theme.mjs';
28
+ export {themeBuild, themeAdd, themeList, themeTargets, listThemes} from './theme/theme.mjs';
29
29
  export {hook} from './hook/hook.mjs';
30
30
  export {search} from './search/search.mjs';
31
31
  export {build} from './build/build.mjs';
@@ -43,6 +43,10 @@ import {ERROR_CODES} from '../../../foundation/response/error-codes.mjs';
43
43
  import {AstryxError} from '../../error.mjs';
44
44
  import {logger} from '../../logger.mjs';
45
45
  import {loadComponentDoc} from '../../../foundation/discovery/component-loader.mjs';
46
+ import {
47
+ collectThemingTargets,
48
+ targetsByKey,
49
+ } from '../../../foundation/discovery/theming-targets.mjs';
46
50
  import {
47
51
  collectUnloadedFonts,
48
52
  formatFontLoadingHelp,
@@ -882,6 +886,9 @@ ${iconType}export declare const ${toIdentifier(themeDef.name)}Theme: DefinedThem
882
886
  * Returns null when docs are unavailable so validation can skip unknown-key
883
887
  * warnings rather than guessing from a second registry.
884
888
  *
889
+ * Shares its enumeration with `theme targets`, so what a theme author can list
890
+ * is exactly what this validator accepts.
891
+ *
885
892
  * @returns {Promise<Record<string, string[]> | null>}
886
893
  */
887
894
  async function loadKnownComponents() {
@@ -889,44 +896,7 @@ async function loadKnownComponents() {
889
896
  const coreSrc = coreRoot ? path.join(coreRoot, 'src') : null;
890
897
  if (!coreSrc || !fs.existsSync(coreSrc)) return null;
891
898
 
892
- /** @type {Record<string, string[]>} */
893
- const targets = {};
894
-
895
- /** @param {string} dir */
896
- async function scan(dir) {
897
- const entries = fs.readdirSync(dir, {withFileTypes: true});
898
- for (const entry of entries) {
899
- const full = path.join(dir, entry.name);
900
- if (entry.isDirectory()) {
901
- if (entry.name === 'node_modules' || entry.name === '__tests__') continue;
902
- await scan(full);
903
- continue;
904
- }
905
- if (!entry.name.endsWith('.doc.mjs')) continue;
906
-
907
- /** @type {any} */
908
- let doc;
909
- try {
910
- doc = await loadComponentDoc(full);
911
- } catch {
912
- continue;
913
- }
914
-
915
- for (const target of doc?.theming?.targets || []) {
916
- const className = target?.className;
917
- if (typeof className !== 'string') continue;
918
- const key = className.replace(/^astryx-/, '');
919
- if (!key) continue;
920
- const props = [target.visualProps, target.states]
921
- .filter(list => Array.isArray(list))
922
- .flat()
923
- .filter((/** @type {unknown} */ p) => typeof p === 'string');
924
- targets[key] = [...new Set([...(targets[key] || []), ...props])];
925
- }
926
- }
927
- }
928
-
929
- await scan(coreSrc);
899
+ const targets = targetsByKey(await collectThemingTargets(coreSrc));
930
900
  return Object.keys(targets).length > 0 ? targets : null;
931
901
  }
932
902
 
@@ -0,0 +1,18 @@
1
+ // @generated by scripts/sync-api-types.mjs from the JSDoc in api/**/*.mjs.
2
+ // DO NOT EDIT — run `pnpm sync:api-types` to regenerate.
3
+
4
+ /**
5
+ * List every component theming target — the `defineTheme` `components` keys,
6
+ * with the props and states each one accepts.
7
+ *
8
+ * A filter naming a component exactly wins over a substring search, so
9
+ * `theme targets Button` is Button's own set (what `astryx component Button`
10
+ * prints) rather than every key that happens to contain "button".
11
+ *
12
+ * @param {string} [filter] - component name, or a substring of a target key
13
+ * @param {{cwd?: string}} [ctx]
14
+ * @returns {Promise<import('../theme.type.mjs').ThemeTargetsResponse>}
15
+ */
16
+ export function themeTargets(filter?: string, { cwd }?: {
17
+ cwd?: string;
18
+ }): Promise<import("../theme.type.mjs").ThemeTargetsResponse>;
@@ -0,0 +1,87 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file `astryx theme targets` leaf — the whole themeable surface, enumerated.
5
+ *
6
+ * @input a cwd (to resolve the project's `@astryxdesign/core`) and an optional
7
+ * component/key filter
8
+ * @output the `theme.targets` envelope: one row per theming target
9
+ * @position api/theme/targets — projection over
10
+ * foundation/discovery/theming-targets.mjs, the same component docs
11
+ * `astryx component <Name>` prints its Theming table from.
12
+ */
13
+
14
+ import * as path from 'node:path';
15
+ import {findCoreDir} from '../../../foundation/fs/paths.mjs';
16
+ import {collectThemingTargets} from '../../../foundation/discovery/theming-targets.mjs';
17
+ import {ERROR_CODES} from '../../../foundation/response/error-codes.mjs';
18
+ import {AstryxError} from '../../error.mjs';
19
+
20
+ /**
21
+ * Whether a target matches the caller's filter loosely: any target whose key,
22
+ * class, or component contains it — so `theme targets thumb` finds the switch
23
+ * thumb without knowing which component owns it.
24
+ * @param {import('../../../foundation/discovery/theming-targets.mjs').ThemingTarget} target
25
+ * @param {string} filter - already lowercased
26
+ * @returns {boolean}
27
+ */
28
+ function matchesLoosely(target, filter) {
29
+ return (
30
+ target.key.toLowerCase().includes(filter) ||
31
+ target.className.toLowerCase().includes(filter) ||
32
+ target.component.toLowerCase().includes(filter)
33
+ );
34
+ }
35
+
36
+ /**
37
+ * List every component theming target — the `defineTheme` `components` keys,
38
+ * with the props and states each one accepts.
39
+ *
40
+ * A filter naming a component exactly wins over a substring search, so
41
+ * `theme targets Button` is Button's own set (what `astryx component Button`
42
+ * prints) rather than every key that happens to contain "button".
43
+ *
44
+ * @param {string} [filter] - component name, or a substring of a target key
45
+ * @param {{cwd?: string}} [ctx]
46
+ * @returns {Promise<import('../theme.type.mjs').ThemeTargetsResponse>}
47
+ */
48
+ export async function themeTargets(filter, {cwd = process.cwd()} = {}) {
49
+ const coreDir = findCoreDir(cwd);
50
+ if (!coreDir) {
51
+ throw new AstryxError(
52
+ 'Could not find @astryxdesign/core package',
53
+ undefined,
54
+ ERROR_CODES.ERR_CORE_NOT_FOUND,
55
+ );
56
+ }
57
+
58
+ const all = await collectThemingTargets(path.join(coreDir, 'src'));
59
+ const needle = filter ? String(filter).toLowerCase() : null;
60
+ let targets = all;
61
+ if (needle) {
62
+ const named = all.filter(t => t.component.toLowerCase() === needle);
63
+ targets = named.length > 0 ? named : all.filter(t => matchesLoosely(t, needle));
64
+ }
65
+
66
+ if (needle && targets.length === 0) {
67
+ const near = [...new Set(all.map(t => t.component))]
68
+ .filter(name => name.toLowerCase().startsWith(needle.slice(0, 3)))
69
+ .sort()
70
+ .slice(0, 5)
71
+ .map(name => ({name, reason: 'has theming targets'}));
72
+ throw new AstryxError(
73
+ `No theming target matches "${filter}". Run \`theme targets\` with no filter for the whole list.`,
74
+ near.length > 0 ? near : undefined,
75
+ ERROR_CODES.ERR_UNKNOWN_COMPONENT,
76
+ );
77
+ }
78
+
79
+ return {
80
+ type: 'theme.targets',
81
+ data: {
82
+ filter: filter ?? null,
83
+ componentCount: new Set(targets.map(t => t.component)).size,
84
+ targets,
85
+ },
86
+ };
87
+ }
@@ -0,0 +1,65 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file Direct-API tests for the `theme targets` leaf. Runs against the real
5
+ * core docs, so it doubles as a guard that the themeable surface stays
6
+ * readable and shaped as `theme.targets` promises.
7
+ */
8
+
9
+ import {describe, it, expect} from 'vitest';
10
+ import {themeTargets} from './targets.mjs';
11
+
12
+ describe('themeTargets (api/theme/targets)', () => {
13
+ it('returns a theme.targets envelope covering the whole surface', async () => {
14
+ const result = await themeTargets();
15
+ expect(result.type).toBe('theme.targets');
16
+ expect(result.data.filter).toBeNull();
17
+ expect(result.data.targets.length).toBeGreaterThan(100);
18
+ expect(result.data.componentCount).toBeGreaterThan(50);
19
+ for (const t of result.data.targets) {
20
+ expect(Object.keys(t).sort()).toEqual([
21
+ 'className',
22
+ 'component',
23
+ 'key',
24
+ 'props',
25
+ 'states',
26
+ ]);
27
+ }
28
+ }, 60_000);
29
+
30
+ it('scopes to one component by name', async () => {
31
+ const {data} = await themeTargets('Switch');
32
+ expect(data.filter).toBe('Switch');
33
+ expect(data.componentCount).toBe(1);
34
+ expect(data.targets.map(t => t.key)).toEqual([
35
+ 'switch',
36
+ 'switch-field',
37
+ 'switch-thumb',
38
+ ]);
39
+ }, 60_000);
40
+
41
+ // Half the system's keys contain "button" (chat-send-button, toggle-button,
42
+ // …). A component name has to mean the component, or `theme targets Button`
43
+ // answers a different question than `component Button` and the two views
44
+ // look like they disagree.
45
+ it('prefers an exact component name over a substring match', async () => {
46
+ const {data} = await themeTargets('Button');
47
+ expect(data.targets.map(t => t.key)).toEqual(['button']);
48
+ }, 60_000);
49
+
50
+ // This command answers "which theme slot paints the switch thumb?" — a
51
+ // question you can only ask by the part, not the component, until you
52
+ // already know which component owns it.
53
+ it('searches keys by substring, across components', async () => {
54
+ const {data} = await themeTargets('thumb');
55
+ expect(data.componentCount).toBeGreaterThan(1);
56
+ expect(data.targets.map(t => t.key)).toContain('switch-thumb');
57
+ for (const t of data.targets) expect(t.key).toContain('thumb');
58
+ }, 60_000);
59
+
60
+ it('rejects a filter that matches nothing, with components to try', async () => {
61
+ await expect(themeTargets('nosuchthing')).rejects.toMatchObject({
62
+ code: 'ERR_UNKNOWN_COMPONENT',
63
+ });
64
+ }, 60_000);
65
+ });
@@ -3,6 +3,7 @@
3
3
 
4
4
  export { themeAdd } from "./add/add.mjs";
5
5
  export { themeTemplate } from "./template/template.mjs";
6
+ export { themeTargets } from "./targets/targets.mjs";
6
7
  export { themeList } from "./list/list.mjs";
7
8
  export { listThemes } from "./_adapter.mjs";
8
9
  export { themeBuild, importSpecifier } from "./build/build.mjs";
@@ -1,7 +1,8 @@
1
1
  // Copyright (c) Meta Platforms, Inc. and affiliates.
2
2
 
3
3
  /**
4
- * @file `theme` command barrel — re-exports the build/add/list leaves so the CLI
4
+ * @file `theme` command barrel — re-exports the build/add/list/template/targets
5
+ * leaves so the CLI
5
6
  * (cli/commands/build-theme.mjs) and scripted callers import from one place.
6
7
  * Each leaf is also importable directly (e.g. api/theme/add/add.mjs). `theme`
7
8
  * has real subcommands, so there is no flag-dispatch here — the CLI calls the
@@ -11,5 +12,6 @@
11
12
  export {themeBuild, importSpecifier} from './build/build.mjs';
12
13
  export {themeAdd} from './add/add.mjs';
13
14
  export {themeTemplate} from './template/template.mjs';
15
+ export {themeTargets} from './targets/targets.mjs';
14
16
  export {themeList} from './list/list.mjs';
15
17
  export {listThemes} from './_adapter.mjs';
@@ -101,3 +101,26 @@ export type ThemeTemplateResponse = {
101
101
  reason: "exists" | null;
102
102
  };
103
103
  };
104
+ /**
105
+ * One themeable target: the `defineTheme` `components` key, the class it
106
+ * renders as, the component whose doc declares it, and the props and states
107
+ * that are legal override keys under it.
108
+ */
109
+ export type ThemeTargetEntry = {
110
+ key: string;
111
+ className: string;
112
+ component: string;
113
+ props: string[];
114
+ states: string[];
115
+ };
116
+ /**
117
+ * xds --json theme targets [filter]
118
+ */
119
+ export type ThemeTargetsResponse = {
120
+ type: "theme.targets";
121
+ data: {
122
+ filter: string | null;
123
+ componentCount: number;
124
+ targets: ThemeTargetEntry[];
125
+ };
126
+ };
@@ -14,9 +14,10 @@
14
14
  * xds --json theme list -> theme.list
15
15
  * xds --json theme add <slug> -> theme.add
16
16
  * xds --json theme template -> theme.template
17
+ * xds --json theme targets [filter] -> theme.targets
17
18
  * (file not found / parse error) -> CLIError
18
19
  *
19
- * @position api — colocated typedefs for api/theme/{theme,build,add,list,template,_adapter}
20
+ * @position api — colocated typedefs for api/theme/{theme,build,add,list,template,targets,_adapter}
20
21
  */
21
22
 
22
23
  /**
@@ -78,6 +79,25 @@
78
79
  * @property {{path: string, written: boolean, reason: 'exists' | null}} data
79
80
  */
80
81
 
82
+ /**
83
+ * One themeable target: the `defineTheme` `components` key, the class it
84
+ * renders as, the component whose doc declares it, and the props and states
85
+ * that are legal override keys under it.
86
+ * @typedef {object} ThemeTargetEntry
87
+ * @property {string} key
88
+ * @property {string} className
89
+ * @property {string} component
90
+ * @property {string[]} props
91
+ * @property {string[]} states
92
+ */
93
+
94
+ /**
95
+ * xds --json theme targets [filter]
96
+ * @typedef {object} ThemeTargetsResponse
97
+ * @property {'theme.targets'} type
98
+ * @property {{filter: string | null, componentCount: number, targets: ThemeTargetEntry[]}} data
99
+ */
100
+
81
101
  // Make this a module so the @typedefs above are importable as types via
82
102
  // `import('./theme.type.mjs').ThemeBuildResponse` (and re-exportable from a .d.ts).
83
103
  export {};
@@ -0,0 +1,11 @@
1
+ // @generated by scripts/sync-api-types.mjs from the JSDoc in api/**/*.mjs.
2
+ // DO NOT EDIT — run `pnpm sync:api-types` to regenerate.
3
+
4
+ /**
5
+ * @file FunctionDoc for `themeTargets()` / `astryx theme targets`. Colocated
6
+ * with the API function it documents; the response-shape source of truth stays
7
+ * in `theme.type.mjs`.
8
+ * @position packages/cli/api/theme — function documentation
9
+ */
10
+ /** @type {import('@astryxdesign/cli/authoring').FunctionDoc} */
11
+ export const doc: import("@astryxdesign/cli/authoring").FunctionDoc;
@@ -0,0 +1,58 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file FunctionDoc for `themeTargets()` / `astryx theme targets`. Colocated
5
+ * with the API function it documents; the response-shape source of truth stays
6
+ * in `theme.type.mjs`.
7
+ * @position packages/cli/api/theme — function documentation
8
+ */
9
+
10
+ /** @type {import('@astryxdesign/cli/authoring').FunctionDoc} */
11
+ export const doc = {
12
+ type: 'function',
13
+ kind: 'api',
14
+ name: 'themeTargets',
15
+ displayName: 'themeTargets()',
16
+ summary: 'List every component theming target a theme can override.',
17
+ description:
18
+ 'Enumerates the whole themeable surface: each `defineTheme` components key, the stable ' +
19
+ 'class it paints, the component that declares it, and the props and states that are legal ' +
20
+ 'override keys under it. Same source as the Theming table `astryx component <Name>` prints ' +
21
+ '— the component docs — so the list cannot drift from the components, and `theme build` ' +
22
+ 'validates overrides against this exact set. A filter naming a component gives that ' +
23
+ 'component\u2019s set; anything else is a substring search over the keys.',
24
+ importPath: '@astryxdesign/cli/api',
25
+ signature:
26
+ 'themeTargets(filter?: string, ctx?: {cwd?: string}): Promise<ThemeTargetsResponse>',
27
+ keywords: ['theme', 'targets', 'defineTheme', 'components', 'override', 'class', 'states', 'audit'],
28
+ params: [
29
+ {
30
+ name: 'filter',
31
+ type: 'string',
32
+ description:
33
+ 'A component name (exact, case-insensitive) or a substring of a target key. Omit for the whole surface.',
34
+ },
35
+ {
36
+ name: 'ctx.cwd',
37
+ type: 'string',
38
+ description: 'Directory the project’s @astryxdesign/core is resolved from.',
39
+ },
40
+ ],
41
+ returns: [
42
+ {
43
+ type: 'theme.targets',
44
+ description:
45
+ 'The echoed filter, how many components are represented, and the targets: each {key, className, component, props, states}.',
46
+ },
47
+ ],
48
+ throws: [
49
+ {code: 'ERR_CORE_NOT_FOUND', when: '@astryxdesign/core cannot be resolved from cwd'},
50
+ {code: 'ERR_UNKNOWN_COMPONENT', when: 'a filter matches no target'},
51
+ ],
52
+ examples: [
53
+ {label: 'The whole themeable surface', code: 'await themeTargets();'},
54
+ {label: "One component's targets", code: "await themeTargets('Switch');"},
55
+ ],
56
+ command: 'theme targets',
57
+ related: ['themeBuild', 'themeTemplate', 'component'],
58
+ };
@@ -0,0 +1,50 @@
1
+ # /packages/cli/assets/docs
2
+
3
+ Reference topics for people **building with** Astryx. Not docs about building Astryx itself.
4
+
5
+ One `{topic}.doc.mjs` per topic, plus optional `{topic}.doc.dense.mjs` / `{topic}.doc.zh.mjs` prose overlays. `foundation/discovery/docs-discovery.mjs` picks up any `{topic}.doc.mjs` here with no registration; `api/docs/_adapter.mjs` merges the overlays.
6
+
7
+ What you add reaches `astryx docs <topic>`, `astryx search`, the `--json` API, the agent-docs block and the doc site — and ships on npm.
8
+
9
+ ## Who you are writing for
10
+
11
+ Someone building a product with Astryx. Their questions:
12
+
13
+ - what a component is for, and when to reach for something else
14
+ - the props, their defaults, and what each does to what they see
15
+ - how to compose it, and the pattern to copy
16
+ - what it costs — bundle size, the a11y obligations they inherit
17
+ - how to theme it, and which targets are stable
18
+
19
+ **The test for anything you add: does a caller act on it?** They are not reviewing a PR, promoting a component out of lab, or attaching evidence to a checklist.
20
+
21
+ ## Tells that you are writing for us instead
22
+
23
+ - second person aimed at the wrong reader — "reviewers should…", "before promoting a component…", "attach evidence for…"
24
+ - **rubric, readiness, gate, audit, checklist, sign-off, promotion, evidence** as things the reader must produce
25
+ - a table of things to verify rather than things to use
26
+ - anything about lab → core, which is our lifecycle, not theirs
27
+ - Storybook, Playwright, CI or the Simulator named as tools the reader runs
28
+
29
+ One subtlety: a statement about the **system's behavior** is caller-facing even when it sounds like process. "A component's theme targets are stable once published" tells a caller what they can rely on; "reviewers must check that theme targets are stable" is ours. Same fact, different reader — **rewrite it rather than move it**.
30
+
31
+ ## Where the rest goes
32
+
33
+ The material is usually good; the finding is placement, not quality. It goes in the [wiki](https://github.com/facebook/astryx/wiki) — **as a section on the page that already covers it, not a new page.** The wiki is at nearly 60 pages, several of them overlapping, because every stray section got its own.
34
+
35
+ | what you wrote | where it goes |
36
+ | --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
37
+ | how a component is graded — checks, scoring | [Component-Audit-Rubric](https://github.com/facebook/astryx/wiki/Component-Audit-Rubric) |
38
+ | lab → core promotion, what a component must satisfy | [Component-Lifecycle](https://github.com/facebook/astryx/wiki/Component-Lifecycle) |
39
+ | how to build a new component | [Component-Authoring-Guide](https://github.com/facebook/astryx/wiki/Component-Authoring-Guide), [Creating-New-Components](https://github.com/facebook/astryx/wiki/Creating-New-Components) |
40
+ | what a component must be hardened against | [Component-Hardening-Protocol](https://github.com/facebook/astryx/wiki/Component-Hardening-Protocol), [Hardening-Audit-Guide](https://github.com/facebook/astryx/wiki/Hardening-Audit-Guide) |
41
+ | a11y requirements as checks we run | [Accessibility-Checklist](https://github.com/facebook/astryx/wiki/Accessibility-Checklist) |
42
+ | how the system is put together | [System-Architecture](https://github.com/facebook/astryx/wiki/System-Architecture), [Theming-Infrastructure](https://github.com/facebook/astryx/wiki/Theming-Infrastructure) |
43
+ | API naming and shape decisions | [API-Conventions](https://github.com/facebook/astryx/wiki/API-Conventions), [API-Arbitration](https://github.com/facebook/astryx/wiki/API-Arbitration) |
44
+ | contributor workflow, PR process | [Contributing](https://github.com/facebook/astryx/wiki/Contributing), [Contributing-with-AI-Assistants](https://github.com/facebook/astryx/wiki/Contributing-with-AI-Assistants) |
45
+ | release mechanics | [Release-Process](https://github.com/facebook/astryx/wiki/Release-Process) |
46
+ | what a nightly agent role does | the Night-Watch pages, from [Night-Watch-Overview](https://github.com/facebook/astryx/wiki/Night-Watch-Overview) |
47
+
48
+ **Fits no row?** It is still not caller-facing. Default it to [Contributing](https://github.com/facebook/astryx/wiki/Contributing), or `CONTRIBUTING.md` when it is a step someone follows with the repo cloned. Never default it back to this directory.
49
+
50
+ Worked example: a responsive-and-interaction readiness rubric is grading criteria → **Component-Audit-Rubric**, or **Component-Lifecycle** if it is a promotion gate.
@@ -115,7 +115,7 @@ export const docs = {
115
115
  content: [
116
116
  {
117
117
  type: 'prose',
118
- text: "Point the integration file's `docs` field at a directory of reference docs and every `{topic}.doc.{ts,mjs,js}` under it becomes a topic the CLI serves `astryx docs` lists it, `astryx docs <topic>` prints it, `astryx search` indexes it, and `astryx init` names it in the agent block. A topic is a plain object stamped `type: 'generic'`, the same shape core's own topics use.",
118
+ text: "Point the integration file's `docs` field at a directory of reference docs and every `{topic}.doc.{ts,mjs,js}` under it becomes a topic the CLI serves: `astryx docs` lists it, `astryx docs <topic>` prints it, `astryx search` indexes it, and `astryx init` names it in the agent block. A topic is a plain object stamped `type: 'generic'`, the same shape core's own topics use.",
119
119
  },
120
120
  {
121
121
  type: 'code',
@@ -124,7 +124,7 @@ export const docs = {
124
124
  },
125
125
  {
126
126
  type: 'prose',
127
- text: "A topic can also speak about one that already exists. `replaces: 'x'` takes over topic x core's, or another integration's so a package whose consumers install it differently can serve its own Getting Started instead of the built-in one. Give the replacement a different `name` and the old name keeps resolving to it, so a link or an agent that learned the old topic still lands in the right place.",
127
+ text: "A topic can also speak about one that already exists. `replaces: 'x'` takes over topic x (core's, or another integration's) so a package whose consumers install it differently can serve its own Getting Started instead of the built-in one. Give the replacement a different `name` and the old name keeps resolving to it, so a link or an agent that learned the old topic still lands in the right place.",
128
128
  },
129
129
  {
130
130
  type: 'code',
@@ -133,14 +133,14 @@ export const docs = {
133
133
  },
134
134
  {
135
135
  type: 'prose',
136
- text: "`extends: 'x'` merges onto a topic instead of owning it: a section whose title matches one in the base replaces that section, and a section the base does not have is appended. Reach for it to correct or add to a topic you do not want to fork a fork of someone else's guide stops receiving their fixes the day you write it.",
136
+ text: "`extends: 'x'` merges onto a topic instead of owning it: a section whose title matches one in the base replaces that section, and a section the base does not have is appended. Reach for it to correct or add to a topic you do not want to fork: a fork of someone else's guide stops receiving their fixes the day you write it.",
137
137
  },
138
138
  {
139
139
  type: 'list',
140
140
  style: 'unordered',
141
141
  items: [
142
142
  'A topic name is a CLI argument and a docsite path, so it may hold only letters, digits, `_` and `-`.',
143
- "A name that collides with an existing topic and declares neither `replaces` nor `extends` is an error, not a silent override the CLI will not guess which one you meant.",
143
+ "A name that collides with an existing topic and declares neither `replaces` nor `extends` is an error, not a silent override; the CLI will not guess which one you meant.",
144
144
  '`replaces` and `extends` are exclusive: a topic either takes another\'s place or merges onto it.',
145
145
  'Two integrations replacing one topic is a warning, and the one configured later in `astryx.config` wins.',
146
146
  ],
@@ -10,7 +10,7 @@ export const docsDense = {
10
10
  { section: 'Theme Props', title: 'Props', content: [null] },
11
11
  { section: 'Creating a Custom Theme', title: 'Custom Theme', content: [{ type: 'prose', text: '`theme list` + `theme add <slug>` to start from a shipped theme, or defineTheme from scratch. only override tokens that differ.' }, null, { type: 'prose', text: '`astryx theme template` writes theme.template.ts: every defineTheme field + token families + override syntax, annotated, with the CLI command that prints each reference.' }] },
12
12
  { section: 'defineTheme', title: 'defineTheme', content: [{ type: 'prose', text: 'scale configs (color, typography, radius, motion) + explicit token overrides + component overrides. color derives full palette from accent via HCT; accent = hex or [light, dark] tuple (per-scheme palettes). tokens overrides win token-by-token; --color-on-accent stays baked from color.accent, so prefer a tuple accent over overriding --color-accent.' }, null, null] },
13
- { section: 'Component Style Overrides', title: 'Component Overrides', content: [{ type: 'prose', text: 'components field uses semantic component keys + style keys (base, variant:value, stateName), not raw selectors. for external CSS, prefer data-* selectors from `astryx docs styling`. write standard CSS (borderRadius, padding) — pipeline expands to internal vars. public vars (--button-focus-offset etc) set directly. private vars (--_*) cannot be set — use CSS properties. run `astryx component <Name>` for details.' }, null, null, null, null] },
13
+ { section: 'Component Style Overrides', title: 'Component Overrides', content: [{ type: 'prose', text: 'components field uses semantic component keys + style keys (base, variant:value, stateName), not raw selectors. for external CSS, prefer data-* selectors from `astryx docs styling`. write standard CSS (borderRadius, padding) — pipeline expands to internal vars. public vars (--button-focus-offset etc) set directly. private vars (--_*) cannot be set — use CSS properties. run `astryx theme targets [Name]` to enumerate every themeable key (--json for lint), `astryx component <Name>` for one component.' }, null, null, null, null] },
14
14
  { section: 'Custom Variants', title: 'Custom Variants', content: [{ type: 'prose', text: 'any unknown prop:value in components becomes a new variant. astryx theme build generates TS augmentations. works on any extensible prop axis (variant, status, etc).' }, null, null, null, null] },
15
15
  { section: 'Building Themes for Production', title: 'Build for Production', content: [{ type: 'prose', text: 'astryx theme build compiles defineTheme to static CSS. outputs .css + .js (__built:true) + .d.ts.' }, null, null, null, null] },
16
16
  { section: 'Runtime vs Built Themes', title: 'Runtime vs Built', content: [{ type: 'prose', text: 'runtime: useInsertionEffect injects styles client-side. built: static CSS on first paint. USE /built + theme.css FOR SSR.' }, null, null, null] },
@@ -300,7 +300,7 @@ const brandTheme = defineTheme({
300
300
  },
301
301
  {
302
302
  type: 'prose',
303
- text: 'Run `astryx component <Name>` to see a component\'s theming targets, public CSS variables, and which standard CSS properties are supported.',
303
+ text: 'Run `astryx theme targets` for every themeable key in the system (`astryx theme targets <Name>` to scope it, `--json` to lint a theme against it), and `astryx component <Name>` for one component\'s theming targets, public CSS variables, and which standard CSS properties are supported.',
304
304
  },
305
305
  {
306
306
  type: 'list',
@@ -0,0 +1,25 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /** @type {import('@astryxdesign/cli/authoring').TemplateDoc} */
4
+ export const doc = {
5
+ type: 'block',
6
+ exampleFor: 'Dialog',
7
+ alsoExampleFor: ['BottomSheet', 'useMediaQuery'],
8
+ name: 'Dialog — Adaptive presentation',
9
+ displayName: 'Dialog — Adaptive presentation',
10
+ description:
11
+ 'Opt-in recipe for an AdaptiveDialog wrapper: Dialog remains the default everywhere, while touchPresentation="bottom-sheet" switches only at lg and below when pointer is coarse and hover is unavailable. Includes a deterministic presentation override for tests/unusual environments and notes that BottomSheet purpose controls swipe and scrim dismissal. Usage examples: touchPresentation="dialog" keeps Dialog, "fullscreen" chooses fullscreen Dialog, and "bottom-sheet" chooses BottomSheet only for the touch-oriented range. Keep presentation as the deterministic override. Do not use this by default for AlertDialog or destructive confirmations.',
12
+ isReady: true,
13
+ aspectRatio: 3 / 4,
14
+ componentsUsed: [
15
+ 'Dialog',
16
+ 'DialogHeader',
17
+ 'BottomSheet',
18
+ 'Layout',
19
+ 'Button',
20
+ 'Text',
21
+ 'TextInput',
22
+ 'TextArea',
23
+ 'useMediaQuery',
24
+ ],
25
+ };