@astryxdesign/cli 0.6.4-canary.06c8fa3 → 0.6.4-canary.0e1fbdb

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 (254) hide show
  1. package/README.md +49 -40
  2. package/api/build/build.doc.mjs +6 -1
  3. package/api/build/build.test.mjs +22 -0
  4. package/api/build/kit/kit.mjs +44 -5
  5. package/api/component/component.doc.mjs +14 -7
  6. package/api/docs/_adapter.d.mts +8 -3
  7. package/api/docs/_adapter.mjs +14 -6
  8. package/api/docs/docOverlays.test.mjs +27 -1
  9. package/api/docs/docs.doc.mjs +2 -2
  10. package/api/doctor/doctor.doc.mjs +17 -8
  11. package/api/doctor/doctor.type.d.mts +1 -1
  12. package/api/doctor/doctor.type.mjs +1 -1
  13. package/api/gap-report/gap-report.doc.mjs +19 -10
  14. package/api/hook/hook.doc.mjs +6 -3
  15. package/api/index.d.mts +2 -0
  16. package/api/index.mjs +3 -1
  17. package/api/init/init.doc.mjs +17 -12
  18. package/api/integration/add-theme.mjs +22 -1
  19. package/api/integration/add-theme.test.mjs +34 -0
  20. package/api/integration/authoring-checks.mjs +2 -2
  21. package/api/integration/integrationPackCheck.doc.mjs +3 -3
  22. package/api/integration/pack-check.lifecycle-output.test.mjs +2 -0
  23. package/api/integration/pack-check.mjs +54 -6
  24. package/api/integration/pack-check.test.mjs +90 -0
  25. package/api/integration/pack-check.type.mjs +1 -1
  26. package/api/json/assertResponse.doc.mjs +1 -1
  27. package/api/json/index.ts +1 -0
  28. package/api/json/isError.doc.mjs +1 -1
  29. package/api/layout/_adapter.d.mts +34 -0
  30. package/api/layout/_adapter.mjs +148 -0
  31. package/api/layout/check/check.d.mts +16 -0
  32. package/api/layout/check/check.mjs +40 -0
  33. package/api/layout/expand/expand.d.mts +22 -0
  34. package/api/layout/expand/expand.mjs +155 -0
  35. package/api/layout/expand/expand.path-safety.test.mjs +53 -0
  36. package/api/layout/grammar/grammar.d.mts +13 -0
  37. package/api/layout/grammar/grammar.mjs +87 -0
  38. package/api/layout/layout.d.mts +6 -0
  39. package/api/layout/layout.mjs +17 -0
  40. package/api/layout/layout.test.mjs +297 -0
  41. package/api/layout/layout.type.d.mts +89 -0
  42. package/api/layout/layout.type.mjs +103 -0
  43. package/api/layout/layoutCheck.doc.d.mts +11 -0
  44. package/api/layout/layoutCheck.doc.mjs +85 -0
  45. package/api/layout/layoutExpand.doc.d.mts +11 -0
  46. package/api/layout/layoutExpand.doc.mjs +107 -0
  47. package/api/layout/layoutGrammar.doc.d.mts +11 -0
  48. package/api/layout/layoutGrammar.doc.mjs +57 -0
  49. package/api/search/search.d.mts +27 -1
  50. package/api/search/search.doc.mjs +2 -2
  51. package/api/search/search.mjs +228 -16
  52. package/api/swizzle/swizzle.doc.mjs +7 -5
  53. package/api/template/copy/copy.mjs +1 -1
  54. package/api/template/copy/copy.test.mjs +9 -0
  55. package/api/template/template-integration.test.mjs +65 -1
  56. package/api/template/template.doc.mjs +2 -1
  57. package/api/template/template.mjs +1 -1
  58. package/api/theme/generateTonalPalette.doc.mjs +1 -2
  59. package/api/theme/listThemes.doc.mjs +1 -1
  60. package/api/theme/themeAdd.doc.mjs +9 -10
  61. package/api/theme/themeBuild.doc.mjs +13 -13
  62. package/api/theme/themeList.doc.mjs +1 -1
  63. package/api/theme/themeListAvailable.doc.mjs +2 -1
  64. package/api/theme/themePaletteGenerate.doc.mjs +15 -8
  65. package/api/theme/themeTargets.doc.mjs +3 -2
  66. package/api/theme/themeTemplate.doc.mjs +2 -1
  67. package/api/upgrade/run/run.mjs +1 -1
  68. package/api/upgrade/upgrade.doc.mjs +24 -22
  69. package/assets/docs/README.md +4 -2
  70. package/assets/docs/browser-support.doc.mjs +11 -11
  71. package/assets/docs/color.doc.mjs +8 -2
  72. package/assets/docs/elevation.doc.mjs +6 -4
  73. package/assets/docs/getting-started.doc.mjs +5 -16
  74. package/assets/docs/icons.doc.mjs +2 -21
  75. package/assets/docs/illustrations.doc.mjs +7 -15
  76. package/assets/docs/layout.doc.dense.mjs +130 -82
  77. package/assets/docs/layout.doc.mjs +133 -77
  78. package/assets/docs/migration.doc.mjs +19 -21
  79. package/assets/docs/motion.doc.mjs +16 -3
  80. package/assets/docs/principles.doc.dense.mjs +5 -5
  81. package/assets/docs/principles.doc.mjs +8 -0
  82. package/assets/docs/principles.doc.zh.mjs +6 -6
  83. package/assets/docs/shape.doc.mjs +8 -3
  84. package/assets/docs/spacing.doc.mjs +7 -2
  85. package/assets/docs/styling-libraries.doc.mjs +6 -2
  86. package/assets/docs/styling.doc.mjs +19 -23
  87. package/assets/docs/theme.doc.dense.mjs +58 -18
  88. package/assets/docs/theme.doc.mjs +56 -46
  89. package/assets/docs/theme.doc.zh.mjs +9 -8
  90. package/assets/docs/tokens.doc.dense.mjs +2 -2
  91. package/assets/docs/tokens.doc.mjs +389 -8
  92. package/assets/docs/tokens.doc.zh.mjs +2 -2
  93. package/assets/docs/tree/add-a-component.doc.mjs +75 -0
  94. package/assets/docs/tree/add-a-theme.doc.mjs +85 -0
  95. package/assets/docs/tree/add-a-topic.doc.mjs +144 -0
  96. package/assets/docs/tree/agent-guidance.doc.mjs +138 -0
  97. package/assets/docs/tree/block-template.doc.mjs +130 -0
  98. package/assets/docs/tree/build-the-template.doc.mjs +28 -0
  99. package/assets/docs/tree/building-blocks.doc.mjs +46 -0
  100. package/assets/docs/tree/check-your-docs.doc.mjs +137 -0
  101. package/assets/docs/tree/checks.doc.mjs +119 -0
  102. package/assets/docs/tree/codemods.doc.mjs +147 -0
  103. package/assets/docs/tree/component-family.doc.mjs +113 -0
  104. package/assets/docs/tree/component-imports.doc.mjs +69 -0
  105. package/assets/docs/tree/components.doc.mjs +23 -0
  106. package/assets/docs/tree/configuration.doc.mjs +23 -0
  107. package/assets/docs/tree/debug-and-gap-reports.doc.mjs +182 -0
  108. package/assets/docs/tree/define-the-theme.doc.mjs +118 -0
  109. package/assets/docs/tree/describe-the-component.doc.mjs +57 -0
  110. package/assets/docs/tree/docs.doc.mjs +21 -0
  111. package/assets/docs/tree/document-the-template.doc.mjs +28 -0
  112. package/assets/docs/tree/document-the-theme.doc.mjs +68 -0
  113. package/assets/docs/tree/export-template-assets.doc.mjs +147 -0
  114. package/assets/docs/tree/extend-or-replace.doc.mjs +103 -0
  115. package/assets/docs/tree/fonts-and-assets.doc.mjs +106 -0
  116. package/assets/docs/tree/generate-a-palette.doc.mjs +66 -0
  117. package/assets/docs/tree/grade-template-with-agent.doc.mjs +105 -0
  118. package/assets/docs/tree/help.doc.mjs +16 -0
  119. package/assets/docs/tree/integrations.doc.mjs +25 -470
  120. package/assets/docs/tree/links.doc.mjs +98 -0
  121. package/assets/docs/tree/package-and-test.doc.mjs +32 -0
  122. package/assets/docs/tree/page-template.doc.mjs +71 -0
  123. package/assets/docs/tree/publishing.doc.mjs +111 -0
  124. package/assets/docs/tree/quick-start.doc.mjs +272 -0
  125. package/assets/docs/tree/replace-a-core-component.doc.mjs +104 -0
  126. package/assets/docs/tree/replace-a-core-template.doc.mjs +172 -0
  127. package/assets/docs/tree/sections-and-placement.doc.mjs +108 -0
  128. package/assets/docs/tree/see-it-in-an-app.doc.mjs +59 -0
  129. package/assets/docs/tree/ship.doc.mjs +16 -0
  130. package/assets/docs/tree/short-and-findable.doc.mjs +108 -0
  131. package/assets/docs/tree/single-component.doc.mjs +165 -0
  132. package/assets/docs/tree/start-a-template.doc.mjs +143 -0
  133. package/assets/docs/tree/subcomponent.doc.mjs +115 -0
  134. package/assets/docs/tree/template-assets.doc.mjs +64 -0
  135. package/assets/docs/tree/template-doc-overview.doc.mjs +109 -0
  136. package/assets/docs/tree/template-fonts.doc.mjs +102 -0
  137. package/assets/docs/tree/template-grading-rubric.doc.mjs +452 -0
  138. package/assets/docs/tree/template-icons.doc.mjs +97 -0
  139. package/assets/docs/tree/template-images-media.doc.mjs +127 -0
  140. package/assets/docs/tree/template-styles.doc.mjs +93 -0
  141. package/assets/docs/tree/templates.doc.mjs +34 -0
  142. package/assets/docs/tree/test-in-an-app.doc.mjs +115 -0
  143. package/assets/docs/tree/test-template-in-app.doc.mjs +128 -0
  144. package/assets/docs/tree/themes.doc.mjs +39 -0
  145. package/assets/docs/tree/troubleshooting.doc.mjs +149 -0
  146. package/assets/docs/tree/upgrading.doc.mjs +103 -0
  147. package/assets/docs/tree/use-a-theme-in-an-app.doc.mjs +51 -0
  148. package/assets/docs/tree/verify-packed-template.doc.mjs +77 -0
  149. package/assets/docs/tree/versioning.doc.mjs +161 -0
  150. package/assets/docs/tree/write-good-templates.doc.mjs +64 -0
  151. package/assets/docs/tree/write-the-template-file.doc.mjs +154 -0
  152. package/assets/docs/typography.doc.mjs +24 -4
  153. package/assets/docs/working-with-ai.doc.mjs +30 -22
  154. package/authoring/config/config.doc.mjs +2 -2
  155. package/authoring/config/type.ts +2 -2
  156. package/authoring/doctypes/_schema.d.mts +3 -2
  157. package/authoring/doctypes/_schema.mjs +6 -0
  158. package/authoring/doctypes/base/graph-fields.doc.mjs +3 -3
  159. package/authoring/doctypes/base/type.ts +4 -2
  160. package/authoring/doctypes/command/command.doc.mjs +1 -1
  161. package/authoring/doctypes/command/type.ts +1 -1
  162. package/authoring/doctypes/component/component.doc.mjs +6 -0
  163. package/authoring/doctypes/component/type.ts +8 -0
  164. package/authoring/doctypes/reference/reference.doc.mjs +7 -0
  165. package/authoring/doctypes/reference/type.ts +5 -0
  166. package/authoring/doctypes/schema/schema.doc.mjs +2 -2
  167. package/authoring/doctypes/template/template.doc.mjs +1 -1
  168. package/authoring/doctypes/template/type.ts +2 -2
  169. package/authoring/integration/integration.doc.mjs +12 -10
  170. package/clients/cli/command-result-coverage.test.mjs +7 -7
  171. package/clients/cli/commands/component.doc.mjs +4 -3
  172. package/clients/cli/commands/debug-result-summary.test.mjs +2 -2
  173. package/clients/cli/commands/docs.doc.mjs +1 -1
  174. package/clients/cli/commands/docs.mjs +60 -17
  175. package/clients/cli/commands/doctor-integration-docs.doc.mjs +3 -2
  176. package/clients/cli/commands/doctor-integration.test.mjs +53 -0
  177. package/clients/cli/commands/doctor.doc.mjs +3 -1
  178. package/clients/cli/commands/doctor.mjs +49 -5
  179. package/clients/cli/commands/gap-report.doc.mjs +10 -9
  180. package/clients/cli/commands/init.doc.mjs +9 -6
  181. package/clients/cli/commands/integration-add.doc.mjs +9 -9
  182. package/clients/cli/commands/integration-authoring.test.mjs +61 -10
  183. package/clients/cli/commands/integration-pack.doc.mjs +5 -9
  184. package/clients/cli/commands/integration-real-world.test.mjs +1 -1
  185. package/clients/cli/commands/integration-verify.doc.mjs +22 -0
  186. package/clients/cli/commands/integration.doc.mjs +4 -4
  187. package/clients/cli/commands/integration.mjs +74 -43
  188. package/clients/cli/commands/layout-check.doc.mjs +65 -0
  189. package/clients/cli/commands/layout-expand.doc.mjs +83 -0
  190. package/clients/cli/commands/layout-grammar.doc.mjs +30 -0
  191. package/clients/cli/commands/layout.doc.mjs +34 -0
  192. package/clients/cli/commands/layout.error-codes.test.mjs +66 -0
  193. package/clients/cli/commands/layout.exit-parity.test.mjs +41 -0
  194. package/clients/cli/commands/layout.mjs +275 -0
  195. package/clients/cli/commands/layout.path-help.test.mjs +33 -0
  196. package/clients/cli/commands/layout.stdin-cap.test.mjs +47 -0
  197. package/clients/cli/commands/layout.text-fields.test.mjs +39 -0
  198. package/clients/cli/commands/manifest.doc.mjs +1 -1
  199. package/clients/cli/commands/search.doc.mjs +10 -3
  200. package/clients/cli/commands/search.mjs +21 -2
  201. package/clients/cli/commands/search.test.mjs +21 -4
  202. package/clients/cli/commands/swizzle.doc.mjs +1 -1
  203. package/clients/cli/commands/template.doc.mjs +1 -1
  204. package/clients/cli/commands/text-json-parity.test.mjs +24 -1
  205. package/clients/cli/commands/theme-add.doc.mjs +1 -1
  206. package/clients/cli/commands/theme-palette-generate.doc.mjs +3 -2
  207. package/clients/cli/commands/theme-palette.doc.mjs +1 -2
  208. package/clients/cli/commands/theme-targets.doc.mjs +2 -2
  209. package/clients/cli/commands/theme.doc.mjs +2 -1
  210. package/clients/cli/commands/upgrade.doc.mjs +62 -3
  211. package/clients/cli/index.mjs +32 -6
  212. package/clients/cli/lib/define-command.mjs +28 -4
  213. package/clients/cli/lib/define-command.test.mjs +54 -0
  214. package/clients/cli/lib/exit-codes.test.mjs +25 -2
  215. package/clients/cli/lib/json-shim.test.mjs +20 -6
  216. package/clients/cli/lib/manifest.mjs +23 -5
  217. package/foundation/agent-docs/agent-docs.mjs +1 -1
  218. package/foundation/discovery/authoring-self-docs.test.mjs +6 -2
  219. package/foundation/discovery/cli-self-docs.mjs +16 -2
  220. package/foundation/discovery/cli-self-docs.test.mjs +20 -0
  221. package/foundation/discovery/docs-discovery.mjs +5 -1
  222. package/foundation/discovery/docs-discovery.test.mjs +21 -0
  223. package/foundation/discovery/docs-section-key.d.mts +1 -1
  224. package/foundation/discovery/docs-section-key.mjs +1 -1
  225. package/foundation/discovery/template-adapter.mjs +1 -1
  226. package/foundation/doc-compiler/doc-loads.test.mjs +15 -2
  227. package/foundation/doc-compiler/inputs.test.mjs +0 -1
  228. package/foundation/doc-compiler/tree.d.mts +4 -0
  229. package/foundation/doc-compiler/tree.mjs +6 -1
  230. package/foundation/integrations/cli-requirement.d.mts +26 -6
  231. package/foundation/integrations/cli-requirement.mjs +46 -11
  232. package/foundation/integrations/cli-requirement.test.mjs +7 -2
  233. package/foundation/integrations/contribution-inventory.mjs +1 -1
  234. package/foundation/response/error-codes.doc.mjs +6 -8
  235. package/foundation/response/error-codes.test.mjs +30 -5
  236. package/foundation/response/response-types.doc.d.mts +4 -3
  237. package/foundation/response/response-types.doc.mjs +42 -6
  238. package/foundation/response/response.doc.mjs +11 -10
  239. package/foundation/xle/browser.d.mts +3 -3
  240. package/foundation/xle/browser.mjs +3 -3
  241. package/foundation/xle/expand.mjs +2 -2
  242. package/foundation/xle/parse.mjs +1 -1
  243. package/foundation/xle/print.mjs +2 -2
  244. package/foundation/xle/splice.mjs +1 -1
  245. package/package.json +9 -9
  246. package/api/docs/docs.test.mjs +0 -245
  247. package/api/docs/integration-tree.test.mjs +0 -555
  248. package/api/docs/integrationDocs.test.mjs +0 -314
  249. package/api/search/search.test.mjs +0 -530
  250. package/assets/docs/tree/integrations.test.mjs +0 -62
  251. package/assets/docs/tree/writing-docs.doc.mjs +0 -286
  252. package/clients/cli/commands/docs.test.mjs +0 -323
  253. package/foundation/agent-docs/agent-docs.test.mjs +0 -1159
  254. package/foundation/doc-compiler/tree.test.mjs +0 -606
@@ -0,0 +1,107 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file FunctionDoc for `layoutExpand()` / `astryx layout expand`. Colocated
5
+ * with the API function it documents; the response-shape source of truth stays
6
+ * in `layout.type.mjs`.
7
+ * @position packages/cli/api/layout — function documentation
8
+ */
9
+
10
+ /** @type {import('@astryxdesign/cli/authoring').FunctionDoc} */
11
+ export const doc = {
12
+ type: 'function',
13
+ kind: 'api',
14
+ name: 'layoutExpand',
15
+ namespace: 'cli/api',
16
+ displayName: 'layoutExpand()',
17
+ summary: 'Expand a validated layout expression into XDS TSX.',
18
+ description:
19
+ 'The generator behind `astryx layout expand`. Parses and validates a compressed XLE/XLO ' +
20
+ 'expression, then expands it into ready-to-use XDS TSX, auto-routing structural children ' +
21
+ 'into the right slots, scaffolding typed useState for interactive controls, and splicing or ' +
22
+ 'importing any referenced template blocks. Returns the code (and metadata) in a layout.expand ' +
23
+ 'envelope, optionally writing it to a path within cwd.',
24
+ importPath: '@astryxdesign/cli/api',
25
+ signature:
26
+ 'layoutExpand(expression: string, options?: LayoutExpandOptions): Promise<LayoutExpandResponse>',
27
+ keywords: ['layout', 'expand', 'xle', 'xlo', 'tsx', 'scaffold', 'generate'],
28
+ params: [
29
+ {
30
+ name: 'expression',
31
+ type: 'string',
32
+ description:
33
+ 'The layout expression to expand (XLE compact or XLO outline form).',
34
+ required: true,
35
+ },
36
+ {
37
+ name: 'options.targetPath',
38
+ type: 'string',
39
+ description:
40
+ 'Write the generated TSX here (validated to stay within cwd). A path that ends in .tsx, .ts, .jsx, .js, .mjs, .cjs, .css, .scss, .json, .md or .html is used as-is; any other path is a directory that gets <name>.tsx. An existing file is replaced. Omit to return the code without writing.',
41
+ },
42
+ {
43
+ name: 'options.form',
44
+ type: "'compact' | 'outline' | 'auto'",
45
+ description:
46
+ 'Force which input surface the expression is parsed as, or auto-detect it.',
47
+ default: "'auto'",
48
+ },
49
+ {
50
+ name: 'options.loose',
51
+ type: 'boolean',
52
+ description:
53
+ 'Downgrade unknown {hint} references to TODO warnings instead of hard errors.',
54
+ default: 'false',
55
+ },
56
+ {
57
+ name: 'options.name',
58
+ type: 'string',
59
+ description: 'PascalCase name for the generated component.',
60
+ default: "'GeneratedLayout'",
61
+ },
62
+ {
63
+ name: 'options.cwd',
64
+ type: 'string',
65
+ description:
66
+ 'Directory the block catalog, registry, and target path resolve against.',
67
+ },
68
+ ],
69
+ returns: [
70
+ {
71
+ type: 'layout.expand',
72
+ description:
73
+ 'The expansion: the parsed form, the generated TSX code, componentsUsed, states (count of useState hooks scaffolded), todos, blocksReferenced (each {name, mode}), warnings, and written (the relative output path, or null when nothing was written).',
74
+ },
75
+ ],
76
+ throws: [
77
+ {
78
+ code: 'ERR_INVALID_ARGUMENT',
79
+ when: 'the expression is empty, or name is not a PascalCase identifier',
80
+ },
81
+ {
82
+ code: 'ERR_INVALID_OPTION',
83
+ when: 'form is not one of "compact", "outline", or "auto"',
84
+ },
85
+ {
86
+ code: 'ERR_LAYOUT_PARSE',
87
+ when: 'the expression has a syntax error (reported with line/col)',
88
+ },
89
+ {
90
+ code: 'ERR_LAYOUT_INVALID',
91
+ when: 'the expression parses but fails validation (unknown component/prop/enum/block)',
92
+ },
93
+ {code: 'ERR_PATH_TRAVERSAL', when: 'the target path escapes cwd'},
94
+ ],
95
+ examples: [
96
+ {
97
+ label: 'Expand to TSX',
98
+ code: 'const r = await layoutExpand(\'VStack[g4] > Heading"Title" + Text"Body"\');',
99
+ },
100
+ {
101
+ label: 'Write to a file',
102
+ code: "await layoutExpand('Card > Text\"Hi\"', {targetPath: 'src/Generated.tsx', name: 'Generated'});",
103
+ },
104
+ ],
105
+ command: 'layout expand',
106
+ related: ['layoutCheck', 'layoutGrammar'],
107
+ };
@@ -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 `layoutGrammar()` / `astryx layout grammar`. Colocated
6
+ * with the API function it documents; the response-shape source of truth stays
7
+ * in `layout.type.mjs`.
8
+ * @position packages/cli/api/layout — function documentation
9
+ */
10
+ /** @type {import('@astryxdesign/cli/authoring').FunctionDoc} */
11
+ export const doc: import("@astryxdesign/cli/authoring").FunctionDoc;
@@ -0,0 +1,57 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file FunctionDoc for `layoutGrammar()` / `astryx layout grammar`. Colocated
5
+ * with the API function it documents; the response-shape source of truth stays
6
+ * in `layout.type.mjs`.
7
+ * @position packages/cli/api/layout — function documentation
8
+ */
9
+
10
+ /** @type {import('@astryxdesign/cli/authoring').FunctionDoc} */
11
+ export const doc = {
12
+ type: 'function',
13
+ kind: 'api',
14
+ name: 'layoutGrammar',
15
+ namespace: 'cli/api',
16
+ displayName: 'layoutGrammar()',
17
+ summary: 'Return the XLE/XLO grammar cheatsheet for this install.',
18
+ description:
19
+ 'The reference behind `astryx layout grammar`: the agent cheatsheet for writing XLE/XLO ' +
20
+ "layout expressions, with the alias table generated from this branch's registry rather than " +
21
+ 'hand-maintained, so short names always reflect the components actually installed.',
22
+ importPath: '@astryxdesign/cli/api',
23
+ signature:
24
+ 'layoutGrammar(options?: LayoutGrammarOptions): Promise<LayoutGrammarResponse>',
25
+ keywords: [
26
+ 'layout',
27
+ 'grammar',
28
+ 'cheatsheet',
29
+ 'xle',
30
+ 'xlo',
31
+ 'aliases',
32
+ 'reference',
33
+ ],
34
+ params: [
35
+ {
36
+ name: 'options.cwd',
37
+ type: 'string',
38
+ description:
39
+ 'Directory the component registry (and its alias table) resolves against.',
40
+ },
41
+ ],
42
+ returns: [
43
+ {
44
+ type: 'layout.grammar',
45
+ description:
46
+ "The cheatsheet: a text field with the full grammar reference, plus an aliases map (short name → canonical component) generated from this install's registry.",
47
+ },
48
+ ],
49
+ examples: [
50
+ {
51
+ label: 'Get the cheatsheet',
52
+ code: 'const {data} = await layoutGrammar();',
53
+ },
54
+ ],
55
+ command: 'layout grammar',
56
+ related: ['layoutExpand', 'layoutCheck'],
57
+ };
@@ -24,6 +24,23 @@ export function sameWord(a: string, b: string): boolean;
24
24
  * @returns {string[]}
25
25
  */
26
26
  export function tokenizeQuery(term: string): string[];
27
+ /**
28
+ * The first title or heading that holds every word of the query, in order and
29
+ * side by side, or null.
30
+ * @param {string} term - Lowercased full query.
31
+ * @param {string[] | undefined} titles
32
+ * @returns {string | null}
33
+ */
34
+ export function headingWithPhrase(term: string, titles: string[] | undefined): string | null;
35
+ /**
36
+ * The first title or heading of two words or more that the query holds whole,
37
+ * in order and side by side, or null. A question such as "how do I add dark
38
+ * mode" names the "Dark mode" section outright, around words no title has.
39
+ * @param {string} term - Lowercased full query.
40
+ * @param {string[] | undefined} titles
41
+ * @returns {string | null}
42
+ */
43
+ export function titleInQuery(term: string, titles: string[] | undefined): string | null;
27
44
  /**
28
45
  * @param {string} term - Lowercased full query.
29
46
  * @param {string[]} tokens - Content tokens from tokenizeQuery(term).
@@ -49,6 +66,8 @@ export function scoreQuery(term: string, tokens: string[], candidate: Candidate)
49
66
  * @param {string} term - Lowercased search term.
50
67
  * @param {object} candidate
51
68
  * @param {string} candidate.name - Primary identifier (component/hook name, topic, template name).
69
+ * @param {string} [candidate.domain] - A component, hook, or template name
70
+ * also matches typed as words: `command palette` is CommandPalette.
52
71
  * @param {string[]} [candidate.keywords] - Authored intent (componentsUsed, category words).
53
72
  * @param {string[]} [candidate.weakKeywords] - Derived signal (components a page renders).
54
73
  * @param {string} [candidate.description]
@@ -57,8 +76,9 @@ export function scoreQuery(term: string, tokens: string[], candidate: Candidate)
57
76
  * @param {{fuzzy?: boolean}} [opts] - `fuzzy`: allow edit-distance (typo) matches. Default true; multi-word queries pass false.
58
77
  * @returns {{score: number, reason: string} | null}
59
78
  */
60
- export function scoreCandidate(term: string, { name, keywords, weakKeywords, description, prose, guidance, }: {
79
+ export function scoreCandidate(term: string, { name, domain, keywords, weakKeywords, description, prose, guidance, }: {
61
80
  name: string;
81
+ domain?: string | undefined;
62
82
  keywords?: string[] | undefined;
63
83
  weakKeywords?: string[] | undefined;
64
84
  description?: string | undefined;
@@ -122,6 +142,12 @@ export type Candidate = {
122
142
  description?: string | undefined;
123
143
  prose?: string[] | undefined;
124
144
  guidance?: string[] | undefined;
145
+ /**
146
+ * - A doc's title and the headings inside it:
147
+ * the lines a reader scans to pick it. The whole query standing in one of
148
+ * them, or one of them standing whole in the query, is a top-tier match.
149
+ */
150
+ titles?: string[] | undefined;
125
151
  _import?: string | undefined;
126
152
  _title?: string | undefined;
127
153
  /**
@@ -48,7 +48,7 @@ export const doc = {
48
48
  name: 'options.cwd',
49
49
  type: 'string',
50
50
  description:
51
- "Directory to resolve @astryxdesign/core from. A docs-only search (`type: 'doc'`) does not need it.",
51
+ "Directory to resolve @astryxdesign/core from. A docs-only search (`type: 'doc'`) does not need it, and a search with no `type` covers the docs alone when core is missing.",
52
52
  },
53
53
  ],
54
54
  returns: [
@@ -65,7 +65,7 @@ export const doc = {
65
65
  },
66
66
  {
67
67
  code: 'ERR_CORE_NOT_FOUND',
68
- when: '@astryxdesign/core cannot be found from the cwd, and the search reads it: every `type` but `doc`',
68
+ when: '@astryxdesign/core cannot be found from the cwd, and `type` names a domain that reads it: `component`, `hook`, or `template`',
69
69
  },
70
70
  ],
71
71
  examples: [
@@ -42,6 +42,18 @@
42
42
  * sentence, a near miss is usually a different word: "site" is not "side",
43
43
  * "cable" is not "table".
44
44
  *
45
+ * A multi-word query has a reserved top tier (see {@link scoreQuery}): the
46
+ * whole query as a candidate's name or keyword (190-200), then the whole query
47
+ * as a phrase inside a doc's title or one of its headings (170), then a whole
48
+ * title of two words or more inside the query (160-169), then a candidate that
49
+ * matches every word of the query, at least one of them by name or keyword
50
+ * (151-159). Below those sits everything else: a partial match, or every word
51
+ * matched only in prose or through the components a page renders. A section titled "Light/Dark Mode" answers `dark
52
+ * mode` better than any doc that merely names `mode` in code, however exactly;
53
+ * "Dark mode" answers `how do I add dark mode`; and a guide whose title and
54
+ * description hold both words of `troubleshoot integration` answers it better
55
+ * than a doc named `integration`.
56
+ *
45
57
  * Description and guidance are separate tiers on purpose. A component's own
46
58
  * one-line description saying "notification" is a claim about what it IS; the
47
59
  * same word inside another component's best-practice advice is a passing
@@ -105,6 +117,9 @@ import {setResultCoverage} from './coverage.mjs';
105
117
  * @property {string} [description]
106
118
  * @property {string[]} [prose]
107
119
  * @property {string[]} [guidance]
120
+ * @property {string[]} [titles] - A doc's title and the headings inside it:
121
+ * the lines a reader scans to pick it. The whole query standing in one of
122
+ * them, or one of them standing whole in the query, is a top-tier match.
108
123
  * @property {string} [_import]
109
124
  * @property {string} [_title]
110
125
  * @property {string} [_topic] - A doc result's topic or docs-tree route.
@@ -414,6 +429,120 @@ function bestForToken(tok, candidate, opts = {}) {
414
429
  return best;
415
430
  }
416
431
 
432
+ /**
433
+ * The score of a whole-query phrase inside a doc's title or heading: a keyword
434
+ * substring hit (70) promoted by the same 100 as the exact tier. Below an
435
+ * exact name or keyword (190-200), above the token-sum path (~151 at most).
436
+ */
437
+ const TITLE_PHRASE_SCORE = 170;
438
+
439
+ /**
440
+ * The score of a whole title inside a longer query, before its coverage bonus:
441
+ * one step below {@link TITLE_PHRASE_SCORE}. The bonus (one per query term the
442
+ * candidate matches, at most 9) orders the sections that share a common title,
443
+ * so "best practices for spacing" puts Spacing's Best Practices first.
444
+ */
445
+ const TITLE_IN_QUERY_SCORE = 160;
446
+
447
+ /**
448
+ * The score of a candidate that matches every content word of a multi-word
449
+ * query, before a bonus of up to 8 for how strong its strongest match is: just
450
+ * above anything that matches only some of the words. The token-sum path tops
451
+ * out near 150 for a partial match (a 100 on one word, the per-word bonus, and
452
+ * the coverage term), so an AND-match with one keyword-strength hit (see
453
+ * {@link STRONG_TOKEN_SCORE}) always outranks an OR-match, and stays below the
454
+ * title tiers.
455
+ */
456
+ const FULL_COVERAGE_SCORE = 151;
457
+
458
+ /**
459
+ * The strongest single-word hit an every-word match needs to take that tier: a
460
+ * keyword substring. Two passing mentions in prose, or the components a page
461
+ * happens to render, are breadth, not relevance; they stay on the token sum,
462
+ * below an exact name or keyword hit on one of the words.
463
+ */
464
+ const STRONG_TOKEN_SCORE = 70;
465
+
466
+ /**
467
+ * The words of a title or query, lowercased, without punctuation or code ticks.
468
+ * @param {string} text
469
+ * @returns {string[]}
470
+ */
471
+ function phraseWords(text) {
472
+ return unlinkText(text).toLowerCase().match(/[a-z0-9]+/g) ?? [];
473
+ }
474
+
475
+ /**
476
+ * Whether two words are the same word, allowing a plural on either side, so
477
+ * `data attributes selector` still reads "Data attribute selectors".
478
+ * @param {string} a
479
+ * @param {string} b
480
+ */
481
+ function samePhraseWord(a, b) {
482
+ return (
483
+ a === b ||
484
+ `${a}s` === b ||
485
+ `${b}s` === a ||
486
+ `${a}es` === b ||
487
+ `${b}es` === a
488
+ );
489
+ }
490
+
491
+ /**
492
+ * Whether `plural` is the plural of `word`: `integrations` of `integration`,
493
+ * `boxes` of `box`. `es` only follows s, x, z, ch, or sh, so `notes` is not a
494
+ * plural of `not`.
495
+ * @param {string} plural
496
+ * @param {string} word
497
+ */
498
+ function pluralOf(plural, word) {
499
+ if (word.length < 3) return false;
500
+ if (plural === `${word}s`) return true;
501
+ return /(?:s|x|z|ch|sh)$/.test(word) && plural === `${word}es`;
502
+ }
503
+
504
+ /**
505
+ * The first title or heading that holds every word of the query, in order and
506
+ * side by side, or null.
507
+ * @param {string} term - Lowercased full query.
508
+ * @param {string[] | undefined} titles
509
+ * @returns {string | null}
510
+ */
511
+ export function headingWithPhrase(term, titles) {
512
+ const query = phraseWords(term);
513
+ if (query.length < 2 || !titles) return null;
514
+ for (const title of titles) {
515
+ const words = phraseWords(String(title ?? ''));
516
+ for (let i = 0; i + query.length <= words.length; i++) {
517
+ if (query.every((word, j) => samePhraseWord(words[i + j], word)))
518
+ return title;
519
+ }
520
+ }
521
+ return null;
522
+ }
523
+
524
+ /**
525
+ * The first title or heading of two words or more that the query holds whole,
526
+ * in order and side by side, or null. A question such as "how do I add dark
527
+ * mode" names the "Dark mode" section outright, around words no title has.
528
+ * @param {string} term - Lowercased full query.
529
+ * @param {string[] | undefined} titles
530
+ * @returns {string | null}
531
+ */
532
+ export function titleInQuery(term, titles) {
533
+ const query = phraseWords(term);
534
+ if (!titles) return null;
535
+ for (const title of titles) {
536
+ const words = phraseWords(String(title ?? ''));
537
+ if (words.length < 2 || words.length > query.length) continue;
538
+ for (let i = 0; i + words.length <= query.length; i++) {
539
+ if (words.every((word, j) => samePhraseWord(query[i + j], word)))
540
+ return title;
541
+ }
542
+ }
543
+ return null;
544
+ }
545
+
417
546
  /**
418
547
  * @param {string} term - Lowercased full query.
419
548
  * @param {string[]} tokens - Content tokens from tokenizeQuery(term).
@@ -437,16 +566,22 @@ export function scoreQuery(term, tokens, candidate) {
437
566
  // is usually a different word, not a typo.
438
567
  const fuzzy = tokens.length <= 1;
439
568
  const full = scoreCandidate(term, candidate, {fuzzy});
569
+ // A query of several words keeps its phrase tiers below even when stopwords
570
+ // leave one content word: "make an integration" is still the phrase an
571
+ // author declares as a keyword, and "build an integration" still names a
572
+ // title outright, though each tokenizes to `integration` alone.
573
+ const phrase = phraseWords(term).length >= 2;
440
574
 
441
- // 0–1 content tokens: keep whole-phrase fuzzy matching (typo tolerance for
442
- // single words), but if stopwords left exactly one DIFFERENT token (e.g.
443
- // "pricing page" → "pricing"), score that token too and take the stronger.
444
- if (tokens.length <= 1) {
575
+ /** 0–1 content tokens: whole-phrase fuzzy matching (typo tolerance for
576
+ * single words), but if stopwords left exactly one DIFFERENT token (e.g.
577
+ * "pricing page" → "pricing"), score that token too and take the stronger. */
578
+ const fewTokens = () => {
445
579
  const single =
446
580
  tokens.length === 1 ? bestForToken(tokens[0], candidate, {fuzzy}) : null;
447
581
  if (full && (!single || full.score >= single.score)) return asFull(full);
448
582
  return single ? asFull(single) : null;
449
- }
583
+ };
584
+ if (tokens.length <= 1 && !phrase) return fewTokens();
450
585
 
451
586
  // The full (untokenized) query matching a candidate's name or a declared
452
587
  // keyword VERBATIM — full.score 90 or 100, the only two scoreCandidate
@@ -463,6 +598,22 @@ export function scoreQuery(term, tokens, candidate) {
463
598
  return asFull({score: full.score + 100, reason: full.reason});
464
599
  }
465
600
 
601
+ // The whole query standing as a phrase in a doc's title or one of its
602
+ // headings is the next tier down, and still above the token-sum path. The
603
+ // reader named what the section is about, in order: `dark mode` is the
604
+ // "Light/Dark Mode" section. Without this, the title scores a keyword
605
+ // substring (70) and loses to a doc that happens to name `mode` exactly in
606
+ // a code tick (90 on one token, 98 with coverage), so API enum docs outrank
607
+ // the guide section.
608
+ const heading = headingWithPhrase(term, candidate.titles);
609
+ if (heading != null) {
610
+ return asFull({
611
+ score: TITLE_PHRASE_SCORE,
612
+ reason: `title "${heading}" holds the whole query`,
613
+ });
614
+ }
615
+ if (tokens.length <= 1) return fewTokens();
616
+
466
617
  // Multi-word natural language: score each content token, counting only
467
618
  // strong hits, then reward coverage so candidates matching more terms win.
468
619
  let strongest = 0;
@@ -477,8 +628,42 @@ export function scoreQuery(term, tokens, candidate) {
477
628
  hitTerms.push(tok);
478
629
  }
479
630
  }
631
+ // The reverse of the title tier, a step lower: the query holds a whole title
632
+ // of two words or more, so the reader asked a question around the section's
633
+ // name ("how do I add dark mode"). Coverage breaks ties between sections
634
+ // that share a title such as "Best Practices".
635
+ const named = titleInQuery(term, candidate.titles);
636
+ if (named != null) {
637
+ return {
638
+ score: TITLE_IN_QUERY_SCORE + Math.min(matched, 9),
639
+ reason: `the query names the title "${named}"`,
640
+ matched,
641
+ total,
642
+ };
643
+ }
480
644
  if (matched === 0) return full ? asFull(full) : null;
481
645
 
646
+ const reason = `matches ${matched}/${tokens.length} terms: ${hitTerms.join(', ')}`;
647
+
648
+ // Every word matched is its own tier. Summed per word, a doc that matches
649
+ // both words of `troubleshoot integration` in its title and description
650
+ // (50 + bonus + coverage = 77) lost to thirty docs that each match
651
+ // `integration` alone, by name or in a code tick (98-108). The reader asked for
652
+ // both; a candidate that has both comes first, ordered among its peers by
653
+ // how strong its strongest match is. It needs one keyword-strength hit:
654
+ // every word mentioned in prose, or rendered by a page, is breadth, and
655
+ // stays on the token sum below an exact hit on one word.
656
+ if (matched === tokens.length && strongest >= STRONG_TOKEN_SCORE) {
657
+ return {
658
+ score:
659
+ FULL_COVERAGE_SCORE +
660
+ Math.floor((strongest - MIN_TOKEN_SCORE) / 6.25),
661
+ reason,
662
+ matched,
663
+ total,
664
+ };
665
+ }
666
+
482
667
  // Base the score on the STRONGEST concept that matched, plus a bonus per
483
668
  // additional matched concept and a coverage term.
484
669
  //
@@ -500,14 +685,8 @@ export function scoreQuery(term, tokens, candidate) {
500
685
  const tokenScore = Math.round(
501
686
  strongest + Math.min(matched - 1, 3) * 12 + coverage * 15,
502
687
  );
503
-
504
688
  if (full && full.score >= tokenScore) return asFull(full);
505
- return {
506
- score: tokenScore,
507
- reason: `matches ${matched}/${tokens.length} terms: ${hitTerms.join(', ')}`,
508
- matched,
509
- total,
510
- };
689
+ return {score: tokenScore, reason, matched, total};
511
690
  }
512
691
 
513
692
  /**
@@ -518,6 +697,8 @@ export function scoreQuery(term, tokens, candidate) {
518
697
  * @param {string} term - Lowercased search term.
519
698
  * @param {object} candidate
520
699
  * @param {string} candidate.name - Primary identifier (component/hook name, topic, template name).
700
+ * @param {string} [candidate.domain] - A component, hook, or template name
701
+ * also matches typed as words: `command palette` is CommandPalette.
521
702
  * @param {string[]} [candidate.keywords] - Authored intent (componentsUsed, category words).
522
703
  * @param {string[]} [candidate.weakKeywords] - Derived signal (components a page renders).
523
704
  * @param {string} [candidate.description]
@@ -530,6 +711,7 @@ export function scoreCandidate(
530
711
  term,
531
712
  {
532
713
  name,
714
+ domain,
533
715
  keywords = [],
534
716
  weakKeywords = [],
535
717
  description = '',
@@ -552,10 +734,26 @@ export function scoreCandidate(
552
734
  };
553
735
 
554
736
  const nameLower = name.toLowerCase();
737
+ // A placed guide's name is its route, and the route's last segment is its
738
+ // name too, as a flat topic's is: `codemods` is cli/integrations/codemods.
739
+ const leafLower = nameLower.slice(nameLower.lastIndexOf('/') + 1);
555
740
 
556
741
  // ── Name signals ────────────────────────────────────────────────
557
- if (nameLower === term) {
742
+ // A plural of the name is the name: `integration` is the `integrations`
743
+ // guides, `tab` the `tabs` doc.
744
+ // A component, hook, or template name typed as words is its name:
745
+ // `command palette` is CommandPalette. A doc's name is a route or key,
746
+ // matched as written.
747
+ const spelled =
748
+ domain !== 'doc' &&
749
+ !/[\s_-]/.test(nameLower) &&
750
+ nameLower === term.replace(/\s+/g, '');
751
+ if (nameLower === term || leafLower === term || spelled) {
558
752
  consider(100, 'exact name');
753
+ } else if (pluralOf(nameLower, term) || pluralOf(term, nameLower)) {
754
+ // One point under the exact spelling, so the doc named `tokens` still
755
+ // outranks the Token component for `tokens`.
756
+ consider(99, 'plural of the name');
559
757
  } else {
560
758
  if (sameWord(term, nameLower)) consider(95, `name "${name}"`);
561
759
  // The term is a word of the name, or starts one: "input" in TextInput.
@@ -962,11 +1160,14 @@ async function gatherDocs(cwd) {
962
1160
  keywords: [
963
1161
  node.route.slice(node.route.lastIndexOf('/') + 1),
964
1162
  ...(Array.isArray(selfDoc?.keywords) ? selfDoc.keywords : []),
1163
+ // A namespace doc's own keywords, which it declares for search.
1164
+ ...(Array.isArray(node.keywords) ? node.keywords : []),
965
1165
  ...defined,
966
1166
  ...codeTerms({content}),
967
1167
  ],
968
1168
  description: node.summary || '',
969
1169
  prose: sectionProse({title: node.title, content}),
1170
+ titles: [node.title],
970
1171
  _topic: node.route,
971
1172
  _title: path.join(' › '),
972
1173
  _command: `astryx docs ${node.route}`,
@@ -1086,12 +1287,16 @@ function topicCandidates(
1086
1287
  const sections = doc?.sections ?? [];
1087
1288
  const docTitle = path || doc?.title || title || name;
1088
1289
  const split = sections.length > 1;
1290
+ // A placed guide also answers to its last route segment's words:
1291
+ // `quick start` is cli/integrations/quick-start.
1292
+ const leaf = name.slice(name.lastIndexOf('/') + 1);
1089
1293
  /** @type {Candidate[]} */
1090
1294
  const out = [
1091
1295
  {
1092
1296
  domain: 'doc',
1093
1297
  name,
1094
1298
  keywords: [
1299
+ ...(leaf !== name ? [leaf.replaceAll('-', ' ')] : []),
1095
1300
  ...(doc?.title || title ? [doc?.title || title] : []),
1096
1301
  ...(Array.isArray(doc?.keywords) ? doc.keywords : []),
1097
1302
  ],
@@ -1099,6 +1304,11 @@ function topicCandidates(
1099
1304
  prose: split
1100
1305
  ? sections.map(section => section.title).filter(Boolean)
1101
1306
  : sections.flatMap(sectionProse),
1307
+ titles: [
1308
+ doc?.title || title || name,
1309
+ // A topic read whole answers for the headings inside it.
1310
+ ...(split ? [] : sections.flatMap(s => [s.title, ...headings(s)])),
1311
+ ].filter(Boolean),
1102
1312
  _topic: name,
1103
1313
  _title: docTitle,
1104
1314
  _command: split ? `astryx docs ${name} --index` : `astryx docs ${name}`,
@@ -1119,6 +1329,7 @@ function topicCandidates(
1119
1329
  ],
1120
1330
  description: sectionSummary(section),
1121
1331
  prose: sectionProse(section),
1332
+ titles: [section.title, ...headings(section)].filter(Boolean),
1122
1333
  _topic: name,
1123
1334
  _section: key,
1124
1335
  _title: `${docTitle} › ${section.title}`,
@@ -1296,10 +1507,11 @@ export async function search(query, options = {}) {
1296
1507
  const tokens = tokenizeQuery(term);
1297
1508
 
1298
1509
  // `astryx docs` reads docs without @astryxdesign/core, so a docs-only
1299
- // search must too. Every other domain reads core.
1510
+ // search must too. Every other domain reads core: asked for by name, it is
1511
+ // an error without core; an open search then covers the docs alone.
1300
1512
  const docsOnly = type === 'doc';
1301
1513
  const coreDir = docsOnly ? null : findCoreDir(cwd);
1302
- if (!docsOnly && !coreDir) {
1514
+ if (type && !docsOnly && !coreDir) {
1303
1515
  throw new AstryxError(
1304
1516
  'Could not find @astryxdesign/core package',
1305
1517
  undefined,
@@ -1309,7 +1521,7 @@ export async function search(query, options = {}) {
1309
1521
 
1310
1522
  // Gather candidates from each requested domain in parallel.
1311
1523
  /** @param {string} d */
1312
- const wants = d => !type || type === d;
1524
+ const wants = d => (!type && (coreDir != null || d === 'doc')) || type === d;
1313
1525
  const [components, hooks, docTopics, templates] = await Promise.all([
1314
1526
  wants('component')
1315
1527
  ? gatherComponents(/** @type {string} */ (coreDir), cwd)
@@ -29,17 +29,19 @@ export const doc = {
29
29
  name: 'component',
30
30
  type: 'string',
31
31
  description:
32
- 'Bare or XDS-prefixed component name to copy. Omit to list the swizzlable components.',
32
+ "Component name to copy (e.g. 'Button'). Omit to list the swizzlable components.",
33
33
  },
34
34
  {
35
35
  name: 'options.cwd',
36
36
  type: 'string',
37
37
  description: 'Directory to resolve @astryxdesign/core from.',
38
+ default: 'process.cwd()',
38
39
  },
39
40
  {
40
41
  name: 'options.output',
41
42
  type: 'string',
42
- description: 'Output directory; must resolve inside cwd.',
43
+ description:
44
+ 'Output directory, relative to cwd. An absolute path, or one that resolves outside cwd, throws ERR_PATH_TRAVERSAL.',
43
45
  default: "'./components/astryx'",
44
46
  },
45
47
  {
@@ -71,7 +73,7 @@ export const doc = {
71
73
  {
72
74
  type: 'swizzle.copy',
73
75
  description:
74
- 'A receipt after copying the component into the project: the component name, owning package, output directory, files-copied count, the written file names, whether any file uses StyleX, and an optional maintainer-feedback note.',
76
+ 'A receipt after copying the component into the project: the component name, owning package, output directory, files-copied count, the written file names, whether any file uses StyleX, and, when the owner has an issues URL, feedback ({issuesUrl, ghCommand?}): where to report the gap that led to swizzling.',
75
77
  },
76
78
  ],
77
79
  throws: [
@@ -81,7 +83,7 @@ export const doc = {
81
83
  },
82
84
  {
83
85
  code: 'ERR_PATH_TRAVERSAL',
84
- when: 'the component name contains a path separator or traversal, output resolves outside cwd, or an existing output file or directory is a symlink that resolves outside cwd',
86
+ when: 'the component name contains a path separator or traversal, output is absolute or resolves outside cwd, or an existing output file or directory is a symlink that resolves outside cwd',
85
87
  },
86
88
  {
87
89
  code: 'ERR_UNKNOWN_COMPONENT',
@@ -108,7 +110,7 @@ export const doc = {
108
110
  {label: 'Eject a component', code: "await swizzle('Button');"},
109
111
  {
110
112
  label: 'Disambiguate by package',
111
- code: "await swizzle('Button', {package: '@astryxdesign/core'});",
113
+ code: "await swizzle('Button', {package: '@astryxdesign/core', overwrite: true});",
112
114
  },
113
115
  {
114
116
  label: 'Custom output directory',