@astryxdesign/cli 0.0.15 → 0.1.0-canary.3047a7c

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,93 @@
1
1
  # @xds/cli
2
2
 
3
+ # 0.1.0
4
+
5
+ #### Breaking Changes
6
+
7
+ - Read project config from `astryx.config.mjs` (was `xds.config.mjs`)
8
+ The CLI now resolves its optional project config from `astryx.config.mjs`
9
+ instead of `xds.config.mjs` — a hard cut, no fallback. Consumers with an
10
+ `xds.config.mjs` must rename it to `astryx.config.mjs` (the config shape and
11
+ all fields are unchanged). Part of removing `xds` naming from the public API.
12
+ - Rename the CLI command/bin from `xds` to `astryx`
13
+ The CLI binary is now `astryx` (was `xds`); `bin/xds.mjs` is renamed to
14
+ `bin/astryx.mjs`, the dual `xds`+`astryx` bin entries collapse to a single
15
+ `astryx`, and the program/manifest name is `astryx`. Invoke the CLI as
16
+ `npx astryx <command>` (e.g. `npx astryx component Button`). The swizzle
17
+ default output dir moves from `./components/xds` to `./components/astryx`.
18
+ Consumers using `npx xds`, an `xds` npm-script alias, or the `xds` MCP server
19
+ name should switch to `astryx`. Part of removing `xds` naming from the public API.
20
+ - Rename the exported `XDSError` class to `AstryxError`
21
+ The CLI's programmatic API error class is renamed `XDSError` -> `AstryxError`
22
+ (exported from `@xds/cli` + declared in its types). Consumers that catch or
23
+ reference `XDSError` from the CLI's API should switch to `AstryxError`. Part of
24
+ removing `xds` naming from the public API.
25
+ - Remove the XDS-prefix compatibility layer — astryx is now the only public surface
26
+ This release erases all `xds` naming from the public API; there is no compatibility
27
+ window. Consumers must migrate (we own all consumers pre-OSS):
28
+ - Remove the daily, brutalist, and default themes; neutral is the new baseline
29
+ Three theme packages are removed from the repo and will no longer be published:
30
+
31
+ #### Fixes
32
+
33
+ - `theme build` generates valid bare type imports (IconRegistry/DefinedTheme)
34
+ `astryx theme build` emitted `.d.ts` files importing `XDSIconRegistry` /
35
+ `XDSDefinedTheme` from `@xds/core`, but those aliases were removed — the
36
+ generated types failed to resolve. Generate `IconRegistry` / `DefinedTheme`
37
+ (the bare names `@xds/core` now exports) instead.
38
+
39
+ #### Documentation
40
+
41
+ - Update CLI theme docs to the current theme set
42
+ Refreshes the `astryx docs theme`, `getting-started`, `styling`,
43
+ `styling-libraries`, and `migration` reference docs to reflect the published
44
+ themes: `neutral`, `butter`, `chocolate`, `gothic`, `matcha`, `stone`, and
45
+ `y2k`. The removed `theme-default`, `theme-brutalist`, and `theme-daily`
46
+ packages are dropped from the docs, and install/import examples now use
47
+ `@astryxdesign/theme-neutral` as the recommended starting theme.
48
+
49
+ #### Other Changes
50
+
51
+ - **Component names:** the `XDS*` aliases are gone — use bare names (`Button` not
52
+ `XDSButton`, `useTheme` not `useXDSTheme`, `ButtonProps` not `XDSButtonProps`). The
53
+ `drop-xds-prefix-imports` codemod automates this.
54
+ - **CSS classes:** components emit only `.astryx-*` (the dual `.xds-*` class is gone).
55
+ Update custom CSS selectors `.xds-button` -> `.astryx-button` (prop/state value classes
56
+ like `.primary`/`.sm` are unchanged).
57
+ - **data attributes:** only `data-astryx-theme` / `data-astryx-media` are written; update
58
+ custom selectors and SSR root attributes off `data-xds-*`.
59
+ - **CSS layers:** `@layer xds-base` / `xds-theme` are renamed to `astryx-base` /
60
+ `astryx-theme`; update your `@layer` order line and any PostCSS `layersBefore` config.
61
+ `@astryxdesign/build`'s default library layer is now `astryx-base`.
62
+ - **Pre-compiled stylesheet:** the `@astryxdesign/core/xds.css` export is removed — import
63
+ `@astryxdesign/core/astryx.css`.
64
+ - **CSS custom properties:** the `--xds-*` padding fallback is gone; set `--astryx-*`.
65
+ - **CLI config key:** `@astryxdesign/cli` reads the package.json `"astryx"` field (was `"xds"`).
66
+ Rename the block; a stale `"xds"` key silently drops the package from discovery.
67
+ - `@astryxdesign/theme-daily`
68
+ - `@astryxdesign/theme-brutalist`
69
+ - `@astryxdesign/theme-default`
70
+ - import {defaultTheme} from '@astryxdesign/theme-default/built';
71
+ - import {neutralTheme} from '@astryxdesign/theme-neutral/built';
72
+ - <Theme theme={defaultTheme}>...</Theme>
73
+ - <Theme theme={neutralTheme}>...</Theme>
74
+ ```
75
+
76
+ ```
77
+ - Remove the internal `drop-xds-meta-prefix` codemod from the OSS repo (#2970)
78
+ 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 public `0.0.13 → 0.0.15` migration.
79
+ - Rename the npm package scope from `@xds/*` to `@astryxdesign/*`
80
+ All published packages move to the new `@astryxdesign` scope (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.
81
+
82
+ #### Contributors
83
+
84
+ Thanks to everyone who contributed to this release:
85
+
86
+ - @cixzhang
87
+ - @ejhammond
88
+
89
+ ---
90
+
3
91
  # 0.0.15
4
92
 
5
93
  #### Breaking Changes
package/README.md CHANGED
@@ -341,7 +341,7 @@ astryx doctor — diagnosing your setup
341
341
  @astryxdesign/core v0.0.14 is in step with @astryxdesign/cli v0.0.14.
342
342
  ⚠ Theme packages
343
343
  No @astryxdesign/theme-* packages are installed.
344
- → fix: Install a theme, e.g. `npm install @astryxdesign/theme-default`, then import its CSS or set xds.theme.
344
+ → fix: Install a theme, e.g. `npm install @astryxdesign/theme-neutral`, then import its CSS or set xds.theme.
345
345
  ℹ astryx.config.mjs
346
346
  No astryx.config.mjs found — using defaults.
347
347
  ℹ AI agent docs
@@ -21,7 +21,7 @@ export const docs = {
21
21
  type: 'code',
22
22
  lang: 'text',
23
23
  label: 'Paste this into your AI',
24
- code: 'Install @astryxdesign/core, @astryxdesign/theme-default, and @astryxdesign/cli in this project. Run `npx astryx init` to set up agent docs. Read the generated files to learn the conventions.',
24
+ code: 'Install @astryxdesign/core, @astryxdesign/theme-neutral, and @astryxdesign/cli in this project. Run `npx astryx init` to set up agent docs. Read the generated files to learn the conventions.',
25
25
  },
26
26
  ],
27
27
  },
@@ -36,7 +36,7 @@ export const docs = {
36
36
  type: 'code',
37
37
  lang: 'bash',
38
38
  label: 'Terminal',
39
- code: `npm install @astryxdesign/core @astryxdesign/theme-default @astryxdesign/cli`,
39
+ code: `npm install @astryxdesign/core @astryxdesign/theme-neutral @astryxdesign/cli`,
40
40
  },
41
41
  {
42
42
  type: 'prose',
@@ -63,11 +63,11 @@ export const docs = {
63
63
  label: 'globals.css',
64
64
  code: `@import '@astryxdesign/core/reset.css';
65
65
  @import '@astryxdesign/core/astryx.css';
66
- @import '@astryxdesign/theme-default/theme.css';`,
66
+ @import '@astryxdesign/theme-neutral/theme.css';`,
67
67
  },
68
68
  {
69
69
  type: 'prose',
70
- text: 'Available themes: @astryxdesign/theme-default (blue accent), @astryxdesign/theme-neutral (grayscale), @astryxdesign/theme-brutalist (zero radius, monospace). See `npx astryx docs theme` for the full theming guide.',
70
+ text: 'Available themes: @astryxdesign/theme-neutral (muted minimal, a good starting point), @astryxdesign/theme-butter, @astryxdesign/theme-chocolate, @astryxdesign/theme-gothic (dark-only), @astryxdesign/theme-matcha, @astryxdesign/theme-stone, and @astryxdesign/theme-y2k. See `npx astryx docs theme` for the full theming guide.',
71
71
  },
72
72
  ],
73
73
  },
@@ -91,15 +91,15 @@ npx astryx component Button --json`,
91
91
  lang: 'tsx',
92
92
  label: 'Root provider with explicit mode',
93
93
  code: `import {Theme} from '@astryxdesign/core/theme';
94
- import {defaultTheme} from '@astryxdesign/theme-default/built';
94
+ import {neutralTheme} from '@astryxdesign/theme-neutral/built';
95
95
  import {useState} from 'react';
96
- import '@astryxdesign/theme-default/theme.css';
96
+ import '@astryxdesign/theme-neutral/theme.css';
97
97
 
98
98
  export function AppRoot({children}: {children: React.ReactNode}) {
99
99
  const [mode, setMode] = useState<'system' | 'light' | 'dark'>('system');
100
100
 
101
101
  return (
102
- <Theme theme={defaultTheme} mode={mode}>
102
+ <Theme theme={neutralTheme} mode={mode}>
103
103
  <SettingsContext.Provider value={{mode, setMode}}>
104
104
  {children}
105
105
  </SettingsContext.Provider>
@@ -121,7 +121,7 @@ export function AppRoot({children}: {children: React.ReactNode}) {
121
121
  @import "tailwindcss/preflight.css" layer(base);
122
122
  @import "@astryxdesign/core/reset.css";
123
123
  @import "@astryxdesign/core/astryx.css";
124
- @import "@astryxdesign/theme-default/theme.css";
124
+ @import "@astryxdesign/theme-neutral/theme.css";
125
125
  @import "@astryxdesign/core/tailwind-theme.css";
126
126
  @import "tailwindcss/utilities.css" layer(utilities);`,
127
127
  },
@@ -162,7 +162,7 @@ const styles = stylex.create({
162
162
  @import "tailwindcss/preflight.css" layer(base);
163
163
  @import "@astryxdesign/core/reset.css";
164
164
  @import "@astryxdesign/core/astryx.css";
165
- @import "@astryxdesign/theme-default/theme.css";
165
+ @import "@astryxdesign/theme-neutral/theme.css";
166
166
  @import "@astryxdesign/core/tailwind-theme.css";
167
167
  @import "tailwindcss/utilities.css" layer(utilities);`,
168
168
  },
@@ -374,9 +374,9 @@ tokens: {
374
374
  lang: 'ts',
375
375
  label: 'Resolve tokens without React context',
376
376
  code: `import {resolveThemeTokens} from '@astryxdesign/core/theme/tokens';
377
- import {defaultTheme} from '@astryxdesign/theme-default';
377
+ import {neutralTheme} from '@astryxdesign/theme-neutral';
378
378
 
379
- const tokens = resolveThemeTokens(defaultTheme, {mode: 'light'});
379
+ const tokens = resolveThemeTokens(neutralTheme, {mode: 'light'});
380
380
 
381
381
  const chartOptions = {
382
382
  textColor: tokens['--color-text-primary'],
@@ -104,7 +104,7 @@ const overrides = stylex.create({
104
104
  @import "tailwindcss/preflight.css" layer(base);
105
105
  @import "@astryxdesign/core/reset.css";
106
106
  @import "@astryxdesign/core/astryx.css";
107
- @import "@astryxdesign/theme-default/theme.css";
107
+ @import "@astryxdesign/theme-neutral/theme.css";
108
108
  @import "@astryxdesign/core/tailwind-theme.css";
109
109
  @import "tailwindcss/utilities.css" layer(utilities);`,
110
110
  },
@@ -6,7 +6,7 @@ export const docsDense = {
6
6
  description: 'Theme provider, custom themes, light/dark, component overrides',
7
7
  sections: [
8
8
  { title: 'Quick Start', content: [null, null, { type: 'prose', text: 'default import = runtime injection. /built import = pre-compiled CSS (pair with theme.css).' }] },
9
- { title: 'Themes', content: [null, { type: 'prose', text: '@astryxdesign/theme-{name} = source (runtime). @astryxdesign/theme-{name}/built = optimized (+ theme.css).' }] },
9
+ { title: 'Themes', content: [null, { type: 'prose', text: 'published: neutral (start here), butter, chocolate, gothic (dark-only), matcha, stone, y2k. @astryxdesign/theme-{name} = source (runtime). @astryxdesign/theme-{name}/built = optimized (+ theme.css).' }] },
10
10
  { title: 'Props', content: [null] },
11
11
  { title: 'Custom Theme', content: [{ type: 'prose', text: 'CLI wizard or manual defineTheme. only override tokens that differ.' }, null] },
12
12
  { title: 'defineTheme', content: [{ type: 'prose', text: 'scale configs (color, typography, radius, motion) + explicit token overrides + component overrides. color derives full palette from accent hex via HCT.' }, null, null] },
@@ -19,11 +19,11 @@ export const docs = {
19
19
  lang: 'tsx',
20
20
  label: 'Basic theme setup (runtime injection)',
21
21
  code: `import {Theme} from '@astryxdesign/core';
22
- import {defaultTheme} from '@astryxdesign/theme-default';
22
+ import {neutralTheme} from '@astryxdesign/theme-neutral';
23
23
 
24
24
  function App() {
25
25
  return (
26
- <Theme theme={defaultTheme}>
26
+ <Theme theme={neutralTheme}>
27
27
  <YourApp />
28
28
  </Theme>
29
29
  );
@@ -34,12 +34,12 @@ function App() {
34
34
  lang: 'tsx',
35
35
  label: 'Optimized setup (pre-built CSS)',
36
36
  code: `import {Theme} from '@astryxdesign/core';
37
- import {defaultTheme} from '@astryxdesign/theme-default/built';
38
- import '@astryxdesign/theme-default/theme.css';
37
+ import {neutralTheme} from '@astryxdesign/theme-neutral/built';
38
+ import '@astryxdesign/theme-neutral/theme.css';
39
39
 
40
40
  function App() {
41
41
  return (
42
- <Theme theme={defaultTheme}>
42
+ <Theme theme={neutralTheme}>
43
43
  <YourApp />
44
44
  </Theme>
45
45
  );
@@ -59,20 +59,40 @@ function App() {
59
59
  type: 'table',
60
60
  headers: ['Theme', 'Import', 'Description'],
61
61
  rows: [
62
- [
63
- 'Default',
64
- "import {defaultTheme} from '@astryxdesign/theme-default'",
65
- 'Blue accent, system fonts, light/dark',
66
- ],
67
62
  [
68
63
  'Neutral',
69
64
  "import {neutralTheme} from '@astryxdesign/theme-neutral'",
70
- 'Grayscale, shadcn-inspired',
65
+ 'Muted, minimal aesthetic with system fonts. A good starting point.',
66
+ ],
67
+ [
68
+ 'Butter',
69
+ "import {butterTheme} from '@astryxdesign/theme-butter'",
70
+ 'Golden, buttery surfaces with blue accents; Sarina + Outfit type.',
71
+ ],
72
+ [
73
+ 'Chocolate',
74
+ "import {chocolateTheme} from '@astryxdesign/theme-chocolate'",
75
+ 'Warm brown tones and cozy beige; Fraunces + Albert Sans type.',
76
+ ],
77
+ [
78
+ 'Gothic',
79
+ "import {gothicTheme} from '@astryxdesign/theme-gothic'",
80
+ 'Dark-only atmospheric theme; deep blue-gray surfaces, distressed display type.',
81
+ ],
82
+ [
83
+ 'Matcha',
84
+ "import {matchaTheme} from '@astryxdesign/theme-matcha'",
85
+ 'Earthy green theme with Figtree typography.',
86
+ ],
87
+ [
88
+ 'Stone',
89
+ "import {stoneTheme} from '@astryxdesign/theme-stone'",
90
+ 'Warm stone and slate tones; Montserrat + Figtree type.',
71
91
  ],
72
92
  [
73
- 'Brutalist',
74
- "import {brutalistTheme} from '@astryxdesign/theme-brutalist'",
75
- 'Zero radius, monospace, heavy borders',
93
+ 'Y2K',
94
+ "import {y2kTheme} from '@astryxdesign/theme-y2k'",
95
+ 'Playful Y2K pop; periwinkle body, holographic accents, Poppins + Crimson Text.',
76
96
  ],
77
97
  ],
78
98
  },
@@ -194,14 +214,14 @@ const myTheme = defineTheme({
194
214
  {
195
215
  type: 'code',
196
216
  lang: 'tsx',
197
- label: 'Extending the default theme',
217
+ label: 'Extending the neutral theme',
198
218
  code: `import {defineTheme} from '@astryxdesign/core/theme';
199
- import {defaultTheme} from '@astryxdesign/theme-default';
219
+ import {neutralTheme} from '@astryxdesign/theme-neutral';
200
220
  import {myIcons} from './icons';
201
221
 
202
222
  const brandTheme = defineTheme({
203
223
  name: 'brand',
204
- extends: defaultTheme,
224
+ extends: neutralTheme,
205
225
  icons: myIcons,
206
226
  tokens: {
207
227
  '--color-accent': ['#7B61FF', '#9B85FF'],
@@ -525,9 +545,9 @@ const pandaOrEmotionTheme = {
525
545
  lang: 'ts',
526
546
  label: 'Resolve token values without a hook',
527
547
  code: `import {resolveThemeTokens} from '@astryxdesign/core/theme/tokens';
528
- import {defaultTheme} from '@astryxdesign/theme-default';
548
+ import {neutralTheme} from '@astryxdesign/theme-neutral';
529
549
 
530
- const lightTokens = resolveThemeTokens(defaultTheme, {mode: 'light'});
550
+ const lightTokens = resolveThemeTokens(neutralTheme, {mode: 'light'});
531
551
  const chartTheme = {
532
552
  textColor: lightTokens['--color-text-primary'],
533
553
  seriesColor: lightTokens['--color-data-categorical-blue'],
@@ -6,7 +6,7 @@ export const docsZh = {
6
6
  description: 'Theme 提供者、自定义主题、亮/暗模式和组件样式覆盖。',
7
7
  sections: [
8
8
  { title: '快速开始', content: [null, null, { type: 'prose', text: '默认导入使用运行时样式注入。/built 导入使用预编译 CSS(需配合 theme.css)。' }] },
9
- { title: '可用主题', content: [null, { type: 'prose', text: '@astryxdesign/theme-{name} = 源码版(运行时注入)。@astryxdesign/theme-{name}/built = 优化版(配合 theme.css)。' }] },
9
+ { title: '可用主题', content: [null, { type: 'prose', text: '已发布主题:neutral(推荐起点)、butter、chocolate、gothic(仅暗色)、matcha、stone、y2k。@astryxdesign/theme-{name} = 源码版(运行时注入)。@astryxdesign/theme-{name}/built = 优化版(配合 theme.css)。' }] },
10
10
  { title: 'Theme 属性', content: [null] },
11
11
  { title: '创建自定义主题', content: [{ type: 'prose', text: '使用 CLI 向导(推荐)或手动 defineTheme。只覆盖与默认值不同的令牌。' }, null] },
12
12
  { title: 'defineTheme', content: [{ type: 'prose', text: '支持比例配置(typography、radius、motion)+ 显式令牌覆盖 + 组件覆盖。' }, null, null] },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astryxdesign/cli",
3
- "version": "0.0.15",
3
+ "version": "0.1.0-canary.3047a7c",
4
4
  "displayName": "CLI",
5
5
  "description": "Scaffold projects, browse templates, generate themes, and get agent-ready docs from the command line.",
6
6
  "author": "Meta Open Source",
@@ -50,13 +50,13 @@
50
50
  "dependencies": {
51
51
  "@clack/prompts": "^1.5.1",
52
52
  "commander": "^12.1.0",
53
- "jiti": "^2.7.0"
53
+ "jiti": "^2.7.0",
54
+ "jscodeshift": "^17.3.0"
54
55
  },
55
56
  "peerDependencies": {
56
- "@astryxdesign/core": "*",
57
- "@astryxdesign/lab": "*",
58
- "@astryxdesign/theme-default": "*",
59
- "@astryxdesign/theme-neutral": "*"
57
+ "@astryxdesign/core": "0.1.0-canary.3047a7c",
58
+ "@astryxdesign/lab": "0.1.0-canary.3047a7c",
59
+ "@astryxdesign/theme-neutral": "0.1.0-canary.3047a7c"
60
60
  },
61
61
  "peerDependenciesMeta": {
62
62
  "@astryxdesign/core": {
@@ -65,18 +65,14 @@
65
65
  "@astryxdesign/lab": {
66
66
  "optional": true
67
67
  },
68
- "@astryxdesign/theme-default": {
69
- "optional": true
70
- },
71
68
  "@astryxdesign/theme-neutral": {
72
69
  "optional": true
73
70
  }
74
71
  },
75
72
  "devDependencies": {
76
- "@astryxdesign/core": "*",
77
- "@astryxdesign/lab": "*",
78
- "@astryxdesign/theme-default": "*",
79
- "@astryxdesign/theme-neutral": "*"
73
+ "@astryxdesign/core": "0.1.0-canary.3047a7c",
74
+ "@astryxdesign/lab": "0.1.0-canary.3047a7c",
75
+ "@astryxdesign/theme-neutral": "0.1.0-canary.3047a7c"
80
76
  },
81
77
  "scripts": {
82
78
  "astryx": "node bin/astryx.mjs",
@@ -240,7 +240,7 @@ export function checkThemes(ctx) {
240
240
  label: 'Theme packages',
241
241
  status: 'warn',
242
242
  message: 'No @astryxdesign/theme-* packages are installed.',
243
- fix: 'Install a theme, e.g. `npm install @astryxdesign/theme-default`, then import its CSS or set xds.theme.',
243
+ fix: 'Install a theme, e.g. `npm install @astryxdesign/theme-neutral`, then import its CSS or set xds.theme.',
244
244
  };
245
245
  }
246
246
 
@@ -355,8 +355,8 @@ export function checkAgentDocs(ctx) {
355
355
  try {
356
356
  const content = fs.readFileSync(path.join(ctx.cwd, rel), 'utf-8');
357
357
  return (
358
- content.includes('<!-- XDS:START -->') &&
359
- content.includes('<!-- XDS:END -->')
358
+ (content.includes('<!-- ASTRYX:START -->') || content.includes('<!-- XDS:START -->')) &&
359
+ (content.includes('<!-- ASTRYX:END -->') || content.includes('<!-- XDS:END -->'))
360
360
  );
361
361
  } catch {
362
362
  return false;
@@ -368,7 +368,7 @@ export function checkAgentDocs(ctx) {
368
368
  id: 'agent-docs',
369
369
  label: 'AI agent docs',
370
370
  status: 'warn',
371
- message: `Agent docs present (${present.join(', ')}) but no XDS section markers found.`,
371
+ message: `Agent docs present (${present.join(', ')}) but no Astryx section markers found.`,
372
372
  fix: 'Add the XDS section to your agent docs with `astryx init --features agents`.',
373
373
  };
374
374
  }
@@ -31,8 +31,11 @@ const AGENTS_MD = 'AGENTS.md';
31
31
  const CLAUDE_MD = 'CLAUDE.md';
32
32
  const CLAUDE_DIR_MD = path.join('.claude', 'CLAUDE.md');
33
33
 
34
- const XDS_MARKER_START = '<!-- XDS:START -->';
35
- const XDS_MARKER_END = '<!-- XDS:END -->';
34
+ const MARKER_START = '<!-- ASTRYX:START -->';
35
+ const MARKER_END = '<!-- ASTRYX:END -->';
36
+ // Legacy markers — read during migration so the script finds existing XDS blocks
37
+ const LEGACY_MARKER_START = '<!-- XDS:START -->';
38
+ const LEGACY_MARKER_END = '<!-- XDS:END -->';
36
39
 
37
40
  /**
38
41
  * Agent tool presets — maps tool names to their file search paths.
@@ -99,7 +102,7 @@ export function resolveAgentPaths(targetDir, agent) {
99
102
  */
100
103
  export function generateCompressedIndex(version, {coreDir, runPrefix = getRunPrefix()} = {}) {
101
104
  const run = `${runPrefix} astryx`;
102
- const lines = [XDS_MARKER_START];
105
+ const lines = [MARKER_START];
103
106
 
104
107
  // Component count from live discovery
105
108
  let componentCount = '90+';
@@ -177,7 +180,7 @@ export function generateCompressedIndex(version, {coreDir, runPrefix = getRunPre
177
180
  lines.push(`${run} swizzle <Name> eject source (--gap to report why)`);
178
181
  lines.push(`${run} upgrade --apply codemods after version bump`);
179
182
  lines.push(`after @astryxdesign/core bump, always run ${run} upgrade --apply`);
180
- lines.push(XDS_MARKER_END);
183
+ lines.push(MARKER_END);
181
184
 
182
185
  return lines.join('\n');
183
186
  }
@@ -218,14 +221,21 @@ export function injectXdsBlock(filePath, compressedIndex, {createIfMissing = fal
218
221
  if (fs.existsSync(filePath)) {
219
222
  content = fs.readFileSync(filePath, 'utf-8');
220
223
 
221
- const startIdx = content.indexOf(XDS_MARKER_START);
222
- const endIdx = content.indexOf(XDS_MARKER_END);
224
+ // Find existing section — try new markers first, fall back to legacy XDS markers
225
+ let startIdx = content.indexOf(MARKER_START);
226
+ let endIdx = content.indexOf(MARKER_END);
227
+ let markerEndLength = MARKER_END.length;
228
+ if (startIdx === -1) {
229
+ startIdx = content.indexOf(LEGACY_MARKER_START);
230
+ endIdx = content.indexOf(LEGACY_MARKER_END);
231
+ markerEndLength = LEGACY_MARKER_END.length;
232
+ }
223
233
 
224
234
  if (startIdx !== -1 && endIdx !== -1) {
225
235
  content =
226
236
  content.slice(0, startIdx) +
227
237
  compressedIndex +
228
- content.slice(endIdx + XDS_MARKER_END.length);
238
+ content.slice(endIdx + markerEndLength);
229
239
  } else if (onlyReplace) {
230
240
  // File exists but has no Astryx markers — skip it
231
241
  return false;
@@ -277,13 +287,20 @@ export function removeXdsBlock(filePath, {deleteIfEmpty = false} = {}) {
277
287
  if (!fs.existsSync(filePath)) return false;
278
288
 
279
289
  let content = fs.readFileSync(filePath, 'utf-8');
280
- const startIdx = content.indexOf(XDS_MARKER_START);
281
- const endIdx = content.indexOf(XDS_MARKER_END);
290
+ // Find existing section — try new markers first, fall back to legacy
291
+ let startIdx = content.indexOf(MARKER_START);
292
+ let endIdx = content.indexOf(MARKER_END);
293
+ let markerEndLen = MARKER_END.length;
294
+ if (startIdx === -1) {
295
+ startIdx = content.indexOf(LEGACY_MARKER_START);
296
+ endIdx = content.indexOf(LEGACY_MARKER_END);
297
+ markerEndLen = LEGACY_MARKER_END.length;
298
+ }
282
299
 
283
300
  if (startIdx === -1 || endIdx === -1) return false;
284
301
 
285
302
  const before = content.slice(0, startIdx).trimEnd();
286
- const after = content.slice(endIdx + XDS_MARKER_END.length).trimStart();
303
+ const after = content.slice(endIdx + markerEndLen).trimStart();
287
304
  content = before + (after ? '\n\n' + after : '') + '\n';
288
305
 
289
306
  if (deleteIfEmpty) {
@@ -32,8 +32,8 @@ describe('generateCompressedIndex', () => {
32
32
  it('includes the version number', () => {
33
33
  const result = generateCompressedIndex('1.2.3');
34
34
  expect(result).toContain('Astryx v1.2.3');
35
- expect(result).toContain('<!-- XDS:START -->');
36
- expect(result).toContain('<!-- XDS:END -->');
35
+ expect(result).toContain('<!-- ASTRYX:START -->');
36
+ expect(result).toContain('<!-- ASTRYX:END -->');
37
37
  });
38
38
 
39
39
  it('includes theme nudge rule', () => {
@@ -82,19 +82,19 @@ describe('injectXdsBlock', () => {
82
82
  const filePath = path.join(tmpDir, 'test.md');
83
83
  fs.writeFileSync(filePath, '# Existing content\n');
84
84
 
85
- const result = injectXdsBlock(filePath, '<!-- XDS:START -->\nnew\n<!-- XDS:END -->');
85
+ const result = injectXdsBlock(filePath, '<!-- ASTRYX:START -->\nnew\n<!-- ASTRYX:END -->');
86
86
 
87
87
  expect(result).toBe(true);
88
88
  const content = fs.readFileSync(filePath, 'utf-8');
89
89
  expect(content).toContain('# Existing content');
90
- expect(content).toContain('<!-- XDS:START -->');
90
+ expect(content).toContain('<!-- ASTRYX:START -->');
91
91
  });
92
92
 
93
93
  it('replaces existing markers', () => {
94
94
  const filePath = path.join(tmpDir, 'test.md');
95
95
  fs.writeFileSync(filePath, 'before\n<!-- XDS:START -->\nold\n<!-- XDS:END -->\nafter\n');
96
96
 
97
- injectXdsBlock(filePath, '<!-- XDS:START -->\nnew\n<!-- XDS:END -->');
97
+ injectXdsBlock(filePath, '<!-- ASTRYX:START -->\nnew\n<!-- ASTRYX:END -->');
98
98
 
99
99
  const content = fs.readFileSync(filePath, 'utf-8');
100
100
  expect(content).toContain('new');
@@ -106,7 +106,7 @@ describe('injectXdsBlock', () => {
106
106
  it('returns false and does not create file when createIfMissing is false', () => {
107
107
  const filePath = path.join(tmpDir, 'nonexistent.md');
108
108
 
109
- const result = injectXdsBlock(filePath, '<!-- XDS:START -->\ncontent\n<!-- XDS:END -->');
109
+ const result = injectXdsBlock(filePath, '<!-- ASTRYX:START -->\ncontent\n<!-- ASTRYX:END -->');
110
110
 
111
111
  expect(result).toBe(false);
112
112
  expect(fs.existsSync(filePath)).toBe(false);
@@ -116,11 +116,11 @@ describe('injectXdsBlock', () => {
116
116
  const filePath = path.join(tmpDir, 'test.md');
117
117
  fs.writeFileSync(filePath, '# Existing content\n\nNo XDS markers here.\n');
118
118
 
119
- const result = injectXdsBlock(filePath, '<!-- XDS:START -->\nnew\n<!-- XDS:END -->', {onlyReplace: true});
119
+ const result = injectXdsBlock(filePath, '<!-- ASTRYX:START -->\nnew\n<!-- ASTRYX:END -->', {onlyReplace: true});
120
120
 
121
121
  expect(result).toBe(false);
122
122
  const content = fs.readFileSync(filePath, 'utf-8');
123
- expect(content).not.toContain('<!-- XDS:START -->');
123
+ expect(content).not.toContain('<!-- ASTRYX:START -->');
124
124
  expect(content).toBe('# Existing content\n\nNo XDS markers here.\n');
125
125
  });
126
126
 
@@ -128,7 +128,7 @@ describe('injectXdsBlock', () => {
128
128
  const filePath = path.join(tmpDir, 'test.md');
129
129
  fs.writeFileSync(filePath, 'before\n<!-- XDS:START -->\nold\n<!-- XDS:END -->\nafter\n');
130
130
 
131
- const result = injectXdsBlock(filePath, '<!-- XDS:START -->\nnew\n<!-- XDS:END -->', {onlyReplace: true});
131
+ const result = injectXdsBlock(filePath, '<!-- ASTRYX:START -->\nnew\n<!-- ASTRYX:END -->', {onlyReplace: true});
132
132
 
133
133
  expect(result).toBe(true);
134
134
  const content = fs.readFileSync(filePath, 'utf-8');
@@ -139,7 +139,7 @@ describe('injectXdsBlock', () => {
139
139
  it('creates file when createIfMissing is true', () => {
140
140
  const filePath = path.join(tmpDir, 'new.md');
141
141
 
142
- const result = injectXdsBlock(filePath, '<!-- XDS:START -->\ncontent\n<!-- XDS:END -->', {
142
+ const result = injectXdsBlock(filePath, '<!-- ASTRYX:START -->\ncontent\n<!-- ASTRYX:END -->', {
143
143
  createIfMissing: true,
144
144
  header: '# Header',
145
145
  });
@@ -147,7 +147,7 @@ describe('injectXdsBlock', () => {
147
147
  expect(result).toBe(true);
148
148
  const content = fs.readFileSync(filePath, 'utf-8');
149
149
  expect(content).toContain('# Header');
150
- expect(content).toContain('<!-- XDS:START -->');
150
+ expect(content).toContain('<!-- ASTRYX:START -->');
151
151
  });
152
152
  });
153
153
 
@@ -157,9 +157,9 @@ describe('injectAgentsMd', () => {
157
157
 
158
158
  const content = fs.readFileSync(path.join(tmpDir, 'AGENTS.md'), 'utf-8');
159
159
  expect(content).toContain('# AGENTS.md');
160
- expect(content).toContain('<!-- XDS:START -->');
160
+ expect(content).toContain('<!-- ASTRYX:START -->');
161
161
  expect(content).toContain('Astryx v1.0.0');
162
- expect(content).toContain('<!-- XDS:END -->');
162
+ expect(content).toContain('<!-- ASTRYX:END -->');
163
163
  });
164
164
 
165
165
  it('updates existing AGENTS.md by replacing XDS markers', () => {
@@ -195,7 +195,7 @@ Existing agent docs.
195
195
 
196
196
  const content = fs.readFileSync(path.join(tmpDir, 'AGENTS.md'), 'utf-8');
197
197
  expect(content).toContain('Existing agent docs.');
198
- expect(content).toContain('<!-- XDS:START -->');
198
+ expect(content).toContain('<!-- ASTRYX:START -->');
199
199
  expect(content).toContain('Astryx v1.0.0');
200
200
  });
201
201
  });
@@ -210,7 +210,7 @@ describe('injectClaudeMd', () => {
210
210
  const content = fs.readFileSync(path.join(tmpDir, 'CLAUDE.md'), 'utf-8');
211
211
  expect(content).toContain('# Claude Config');
212
212
  expect(content).toContain('Existing rules.');
213
- expect(content).toContain('<!-- XDS:START -->');
213
+ expect(content).toContain('<!-- ASTRYX:START -->');
214
214
  expect(content).toContain('Astryx v1.0.0');
215
215
  });
216
216
 
@@ -322,7 +322,7 @@ describe('installAgentDocs', () => {
322
322
  expect(fs.existsSync(path.join(tmpDir, '.claude', 'CLAUDE.md'))).toBe(true);
323
323
  expect(fs.existsSync(path.join(tmpDir, 'AGENTS.md'))).toBe(false);
324
324
  const content = fs.readFileSync(path.join(tmpDir, '.claude', 'CLAUDE.md'), 'utf-8');
325
- expect(content).toContain('<!-- XDS:START -->');
325
+ expect(content).toContain('<!-- ASTRYX:START -->');
326
326
  });
327
327
 
328
328
  it('injects into CLAUDE.md at root when it exists', () => {
@@ -334,7 +334,7 @@ describe('installAgentDocs', () => {
334
334
  expect(written).toEqual(['CLAUDE.md']);
335
335
  expect(fs.existsSync(path.join(tmpDir, 'AGENTS.md'))).toBe(false);
336
336
  const claudeContent = fs.readFileSync(path.join(tmpDir, 'CLAUDE.md'), 'utf-8');
337
- expect(claudeContent).toContain('<!-- XDS:START -->');
337
+ expect(claudeContent).toContain('<!-- ASTRYX:START -->');
338
338
  expect(claudeContent).toContain('Project rules.');
339
339
  });
340
340
 
@@ -349,8 +349,8 @@ describe('installAgentDocs', () => {
349
349
  expect(written).toContain('CLAUDE.md');
350
350
  const agentsContent = fs.readFileSync(path.join(tmpDir, 'AGENTS.md'), 'utf-8');
351
351
  const claudeContent = fs.readFileSync(path.join(tmpDir, 'CLAUDE.md'), 'utf-8');
352
- expect(agentsContent).toContain('<!-- XDS:START -->');
353
- expect(claudeContent).toContain('<!-- XDS:START -->');
352
+ expect(agentsContent).toContain('<!-- ASTRYX:START -->');
353
+ expect(claudeContent).toContain('<!-- ASTRYX:START -->');
354
354
  });
355
355
 
356
356
  it('updates existing .claude/CLAUDE.md', () => {
@@ -363,7 +363,7 @@ describe('installAgentDocs', () => {
363
363
  expect(written).toEqual(['.claude/CLAUDE.md']);
364
364
  const content = fs.readFileSync(path.join(tmpDir, '.claude', 'CLAUDE.md'), 'utf-8');
365
365
  expect(content).toContain('Existing content.');
366
- expect(content).toContain('<!-- XDS:START -->');
366
+ expect(content).toContain('<!-- ASTRYX:START -->');
367
367
  });
368
368
 
369
369
  it('respects --agent claude preset: finds existing CLAUDE.md', () => {
@@ -410,7 +410,7 @@ describe('installAgentDocs', () => {
410
410
 
411
411
  expect(written).toEqual([]);
412
412
  const content = fs.readFileSync(path.join(tmpDir, 'CLAUDE.md'), 'utf-8');
413
- expect(content).not.toContain('<!-- XDS:START -->');
413
+ expect(content).not.toContain('<!-- ASTRYX:START -->');
414
414
  expect(content).toBe('# Claude\n\nProject rules only.\n');
415
415
  });
416
416
 
@@ -106,14 +106,14 @@ describe('doctor — individual checks', () => {
106
106
  });
107
107
 
108
108
  it('themes: WARN when theme installed but not wired', () => {
109
- installPkg('@astryxdesign/theme-default', '0.0.14');
109
+ installPkg('@astryxdesign/theme-neutral', '0.0.14');
110
110
  const res = checkThemes({cwd: tmpDir, configTheme: null});
111
111
  expect(res.status).toBe('warn');
112
- expect(res.message).toContain('@astryxdesign/theme-default');
112
+ expect(res.message).toContain('@astryxdesign/theme-neutral');
113
113
  });
114
114
 
115
115
  it('themes: PASS when theme installed and wired via config', () => {
116
- installPkg('@astryxdesign/theme-default', '0.0.14');
116
+ installPkg('@astryxdesign/theme-neutral', '0.0.14');
117
117
  const res = checkThemes({cwd: tmpDir, configTheme: 'default'});
118
118
  expect(res.status).toBe('pass');
119
119
  });
@@ -246,8 +246,8 @@ export function registerInit(program) {
246
246
  humanLog(' Next steps:');
247
247
  humanLog(" 1. Import components: import { Button } from '@astryxdesign/core'");
248
248
  humanLog(' 2. Optionally add a theme:');
249
- humanLog(" import { defaultTheme } from '@astryxdesign/theme-default'");
250
- humanLog(' <Theme theme={defaultTheme}>...</Theme>');
249
+ humanLog(" import { neutralTheme } from '@astryxdesign/theme-neutral'");
250
+ humanLog(' <Theme theme={neutralTheme}>...</Theme>');
251
251
  humanLog(` 3. ${run} xds --help for all commands`);
252
252
  humanLog('');
253
253
  });
@@ -18,13 +18,10 @@ import {Link} from '@astryxdesign/core/Link';
18
18
  import {Divider} from '@astryxdesign/core/Divider';
19
19
  import {Card} from '@astryxdesign/core/Card';
20
20
  import {Selector} from '@astryxdesign/core/Selector';
21
+ import {radiusVars} from '@astryxdesign/core/theme/tokens.stylex';
21
22
 
22
23
  const ILLUSTRATION_URL =
23
- 'https://lookaside.facebook.com/assets/astryx/illustration-horizontal-1.png';
24
-
25
- // ─────────────────────────────────────────────────────────────
26
- // Constants
27
- // ─────────────────────────────────────────────────────────────
24
+ 'https://lookaside.facebook.com/assets/astryx/light-working-vertical-2.png';
28
25
 
29
26
  const INQUIRY_REASONS = [
30
27
  'New business',
@@ -51,13 +48,9 @@ const CONTACT_COLUMNS = [
51
48
  {label: 'Press & partnerships', email: 'press@company.com'},
52
49
  ];
53
50
 
54
- // ─────────────────────────────────────────────────────────────
55
- // Styles
56
- // ─────────────────────────────────────────────────────────────
57
-
58
- // The only custom styling is fitting the illustration inside its AspectRatio
59
- // box without distortion (contain, not cover — it's line art, don't crop it).
60
- // No objectFit prop on AspectRatio, and there's no Image primitive (#2582).
51
+ // AspectRatio has no objectFit/radius prop and there's no Image primitive
52
+ // (#2582), so the cover photo is styled directly. overflow:hidden masks the
53
+ // cover crop to the rounded corners.
61
54
  const styles = stylex.create({
62
55
  page: {
63
56
  minHeight: '100%',
@@ -65,14 +58,12 @@ const styles = stylex.create({
65
58
  illustrationImg: {
66
59
  width: '100%',
67
60
  height: '100%',
68
- objectFit: 'contain',
61
+ objectFit: 'cover',
62
+ borderRadius: radiusVars['--radius-container'],
63
+ overflow: 'hidden',
69
64
  },
70
65
  });
71
66
 
72
- // ─────────────────────────────────────────────────────────────
73
- // Page
74
- // ─────────────────────────────────────────────────────────────
75
-
76
67
  /**
77
68
  * Form (Two-column) — marketing contact form template.
78
69
  *
@@ -105,15 +96,10 @@ export default function FormTwoColumnPage() {
105
96
 
106
97
  return (
107
98
  <Center xstyle={styles.page}>
108
- <Section
109
- maxWidth={1100}
110
- width="100%"
111
- padding={10}
112
- variant="transparent">
99
+ <Section maxWidth={1100} width="100%" padding={10} variant="transparent">
113
100
  <VStack gap={10}>
114
- {/* ── Top: two-column, stacks to one column below ~520px ── */}
101
+ {/* Two-column; stacks to one column below ~520px. */}
115
102
  <Grid columns={{minWidth: 320}} align="center" gap={10}>
116
- {/* Left: headline + description + illustration */}
117
103
  <VStack gap={6}>
118
104
  <VStack gap={3}>
119
105
  <Text type="display-1" as="h1">
@@ -127,13 +113,12 @@ export default function FormTwoColumnPage() {
127
113
  <AspectRatio ratio={4 / 3}>
128
114
  <img
129
115
  src={ILLUSTRATION_URL}
130
- alt="Person with a laptop and a lightbulb idea"
116
+ alt="Two people working at a desk"
131
117
  {...stylex.props(styles.illustrationImg)}
132
118
  />
133
119
  </AspectRatio>
134
120
  </VStack>
135
121
 
136
- {/* Right: form on a card */}
137
122
  <Card padding={8}>
138
123
  <VStack gap={4}>
139
124
  <Text type="label">Your details</Text>
@@ -188,9 +173,7 @@ export default function FormTwoColumnPage() {
188
173
  </Grid>
189
174
 
190
175
  <VStack gap={2}>
191
- <Text type="label">
192
- What are you reaching out about?
193
- </Text>
176
+ <Text type="label">What are you reaching out about?</Text>
194
177
  <HStack gap={2} wrap="wrap">
195
178
  {INQUIRY_REASONS.map(reason => (
196
179
  <Token
@@ -238,7 +221,7 @@ export default function FormTwoColumnPage() {
238
221
  </Card>
239
222
  </Grid>
240
223
 
241
- {/* ── Bottom: contact strip (stacks below ~440px) ── */}
224
+ {/* Contact strip; stacks below ~440px. */}
242
225
  <VStack gap={6}>
243
226
  <Divider />
244
227
  <Grid columns={{minWidth: 200}} gap={6}>