mdx-artifacts 0.1.2 → 0.2.0

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 (141) hide show
  1. package/README.md +211 -59
  2. package/README.zh-CN.md +299 -42
  3. package/agents/AGENTS.snippet.md +21 -10
  4. package/artifact-docs/examples/commentable-feedback.mdx +76 -70
  5. package/artifact-docs/examples/decision-matrix.mdx +122 -50
  6. package/artifact-docs/examples/layout-composition.mdx +106 -128
  7. package/artifact-docs/examples/streamlit-style-mixed.mdx +100 -85
  8. package/dist/lib/cli/{build.js → commands/build.js} +2 -2
  9. package/dist/lib/cli/{components.js → commands/components.js} +19 -3
  10. package/dist/lib/cli/{dev.js → commands/dev.js} +2 -2
  11. package/dist/lib/cli/commands/interactions.d.ts +2 -0
  12. package/dist/lib/cli/commands/interactions.js +280 -0
  13. package/dist/lib/cli/commands/review.d.ts +1 -0
  14. package/dist/lib/cli/commands/review.js +171 -0
  15. package/dist/lib/cli/commands/scaffold.d.ts +16 -0
  16. package/dist/lib/cli/commands/scaffold.js +440 -0
  17. package/dist/lib/cli/commands/validate.d.ts +18 -0
  18. package/dist/lib/cli/commands/validate.js +311 -0
  19. package/dist/lib/cli/config/config.d.ts +2 -0
  20. package/dist/lib/cli/{config.js → config/config.js} +3 -2
  21. package/dist/lib/cli/{types.d.ts → config/types.d.ts} +2 -1
  22. package/dist/lib/cli/{vite-artifact.d.ts → dev-server/vite-artifact.d.ts} +3 -3
  23. package/dist/lib/cli/{vite-artifact.js → dev-server/vite-artifact.js} +170 -10
  24. package/dist/lib/cli/diagnostics/diagnostics.d.ts +11 -0
  25. package/dist/lib/cli/diagnostics/diagnostics.js +1 -0
  26. package/dist/lib/cli/index.js +39 -18
  27. package/dist/lib/cli/mdx/sortable-list.d.ts +14 -0
  28. package/dist/lib/cli/mdx/sortable-list.js +520 -0
  29. package/dist/lib/cli/resources/resource-policy.d.ts +15 -0
  30. package/dist/lib/cli/resources/resource-policy.js +46 -0
  31. package/dist/lib/cli/resources/safe-path.d.ts +13 -0
  32. package/dist/lib/cli/resources/safe-path.js +55 -0
  33. package/dist/lib/cli/services/interaction-service.d.ts +40 -0
  34. package/dist/lib/cli/services/interaction-service.js +226 -0
  35. package/dist/lib/cli/services/review.d.ts +43 -0
  36. package/dist/lib/cli/{review.js → services/review.js} +34 -172
  37. package/dist/lib/react/{components → composites/comparison-set}/ComparisonSet.d.ts +1 -1
  38. package/dist/lib/react/{components → composites/comparison-set}/ComparisonSet.js +3 -3
  39. package/dist/lib/react/composites/comparison-set/index.d.ts +2 -0
  40. package/dist/lib/react/composites/comparison-set/index.js +1 -0
  41. package/dist/lib/react/composites/content-set/ContentItem.d.ts +37 -0
  42. package/dist/lib/react/composites/content-set/ContentItem.js +49 -0
  43. package/dist/lib/react/composites/content-set/index.d.ts +2 -0
  44. package/dist/lib/react/composites/content-set/index.js +1 -0
  45. package/dist/lib/react/{components → composites/export-panel}/ExportPanel.js +2 -2
  46. package/dist/lib/react/composites/export-panel/index.d.ts +2 -0
  47. package/dist/lib/react/composites/export-panel/index.js +1 -0
  48. package/dist/lib/react/{components → composites/section}/Section.js +1 -1
  49. package/dist/lib/react/composites/section/index.d.ts +2 -0
  50. package/dist/lib/react/composites/section/index.js +1 -0
  51. package/dist/lib/react/index.d.ts +36 -31
  52. package/dist/lib/react/index.js +18 -15
  53. package/dist/lib/react/interactions/artifact-state/index.d.ts +1 -0
  54. package/dist/lib/react/interactions/artifact-state/index.js +1 -0
  55. package/dist/lib/react/{components → interactions/comments}/Comments.d.ts +2 -2
  56. package/dist/lib/react/{components → interactions/comments}/Comments.js +3 -3
  57. package/dist/lib/react/interactions/comments/index.d.ts +1 -0
  58. package/dist/lib/react/interactions/comments/index.js +1 -0
  59. package/dist/lib/react/interactions/sortable-list/SortableList.d.ts +29 -0
  60. package/dist/lib/react/interactions/sortable-list/SortableList.js +282 -0
  61. package/dist/lib/react/interactions/sortable-list/index.d.ts +1 -0
  62. package/dist/lib/react/interactions/sortable-list/index.js +1 -0
  63. package/dist/lib/react/layout/layout-primitives/index.d.ts +2 -0
  64. package/dist/lib/react/layout/layout-primitives/index.js +1 -0
  65. package/dist/lib/react/legacy/LegacyContentComponents.d.ts +65 -0
  66. package/dist/lib/react/legacy/LegacyContentComponents.js +26 -0
  67. package/dist/lib/react/mdx-components.d.ts +5 -0
  68. package/dist/lib/react/mdx-components.js +38 -0
  69. package/dist/lib/react/{components → primitives/annotated-code}/AnnotatedCode.d.ts +1 -1
  70. package/dist/lib/react/{components → primitives/annotated-code}/AnnotatedCode.js +5 -5
  71. package/dist/lib/react/primitives/annotated-code/index.d.ts +2 -0
  72. package/dist/lib/react/primitives/annotated-code/index.js +1 -0
  73. package/dist/lib/react/primitives/callout/Callout.d.ts +11 -0
  74. package/dist/lib/react/{components → primitives/callout}/Callout.js +9 -6
  75. package/dist/lib/react/primitives/callout/index.d.ts +2 -0
  76. package/dist/lib/react/primitives/callout/index.js +1 -0
  77. package/dist/lib/react/primitives/code-block/CodeBlock.d.ts +20 -0
  78. package/dist/lib/react/primitives/code-block/CodeBlock.js +32 -0
  79. package/dist/lib/react/primitives/code-block/index.d.ts +2 -0
  80. package/dist/lib/react/primitives/code-block/index.js +1 -0
  81. package/dist/lib/react/primitives/code-surface/CodeSurface.d.ts +11 -0
  82. package/dist/lib/react/primitives/code-surface/CodeSurface.js +34 -0
  83. package/dist/lib/react/primitives/code-surface/index.d.ts +2 -0
  84. package/dist/lib/react/primitives/code-surface/index.js +1 -0
  85. package/dist/lib/react/primitives/diff-block/DiffBlock.js +25 -0
  86. package/dist/lib/react/primitives/diff-block/index.d.ts +2 -0
  87. package/dist/lib/react/primitives/diff-block/index.js +1 -0
  88. package/dist/lib/react/{components → primitives/inline-text}/InlineText.d.ts +4 -2
  89. package/dist/lib/react/primitives/inline-text/InlineText.js +28 -0
  90. package/dist/lib/react/primitives/inline-text/index.d.ts +2 -0
  91. package/dist/lib/react/primitives/inline-text/index.js +1 -0
  92. package/dist/lib/react/primitives/markdown-body/MarkdownBody.d.ts +9 -0
  93. package/dist/lib/react/primitives/markdown-body/MarkdownBody.js +49 -0
  94. package/dist/lib/react/primitives/markdown-body/index.d.ts +2 -0
  95. package/dist/lib/react/primitives/markdown-body/index.js +1 -0
  96. package/dist/lib/react/primitives/severity-badge/index.d.ts +2 -0
  97. package/dist/lib/react/primitives/severity-badge/index.js +1 -0
  98. package/dist/lib/react/registry.d.ts +10 -0
  99. package/dist/lib/react/registry.js +505 -210
  100. package/dist/lib/react/styles.css +490 -38
  101. package/docs/cli-structure.md +141 -0
  102. package/docs/component-protocol.md +199 -33
  103. package/docs/component-taxonomy.md +40 -4
  104. package/docs/design.md +42 -21
  105. package/docs/design.zh-CN.md +41 -21
  106. package/docs/naming.md +17 -7
  107. package/docs/releasing.md +132 -0
  108. package/docs/testing.md +35 -10
  109. package/package.json +9 -7
  110. package/dist/lib/cli/config.d.ts +0 -2
  111. package/dist/lib/cli/review.d.ts +0 -33
  112. package/dist/lib/cli/scaffold.d.ts +0 -1
  113. package/dist/lib/cli/scaffold.js +0 -56
  114. package/dist/lib/cli/validate.d.ts +0 -6
  115. package/dist/lib/cli/validate.js +0 -79
  116. package/dist/lib/react/components/Callout.d.ts +0 -9
  117. package/dist/lib/react/components/CodeBlock.d.ts +0 -10
  118. package/dist/lib/react/components/CodeBlock.js +0 -28
  119. package/dist/lib/react/components/DecisionMatrix.d.ts +0 -16
  120. package/dist/lib/react/components/DecisionMatrix.js +0 -27
  121. package/dist/lib/react/components/DiffBlock.js +0 -24
  122. package/dist/lib/react/components/InlineText.js +0 -18
  123. package/dist/lib/react/components/MarkdownBody.d.ts +0 -7
  124. package/dist/lib/react/components/MarkdownBody.js +0 -36
  125. package/dist/lib/react/components/OptionGrid.d.ts +0 -13
  126. package/dist/lib/react/components/OptionGrid.js +0 -21
  127. /package/dist/lib/cli/{build.d.ts → commands/build.d.ts} +0 -0
  128. /package/dist/lib/cli/{components.d.ts → commands/components.d.ts} +0 -0
  129. /package/dist/lib/cli/{dev.d.ts → commands/dev.d.ts} +0 -0
  130. /package/dist/lib/cli/{types.js → config/types.js} +0 -0
  131. /package/dist/lib/cli/{artifact-state.d.ts → state/artifact-state.d.ts} +0 -0
  132. /package/dist/lib/cli/{artifact-state.js → state/artifact-state.js} +0 -0
  133. /package/dist/lib/react/{components → composites/export-panel}/ExportPanel.d.ts +0 -0
  134. /package/dist/lib/react/{components → composites/section}/Section.d.ts +0 -0
  135. /package/dist/lib/react/{components → interactions/artifact-state}/ArtifactState.d.ts +0 -0
  136. /package/dist/lib/react/{components → interactions/artifact-state}/ArtifactState.js +0 -0
  137. /package/dist/lib/react/{components → layout/layout-primitives}/Layout.d.ts +0 -0
  138. /package/dist/lib/react/{components → layout/layout-primitives}/Layout.js +0 -0
  139. /package/dist/lib/react/{components → primitives/diff-block}/DiffBlock.d.ts +0 -0
  140. /package/dist/lib/react/{components → primitives/severity-badge}/SeverityBadge.d.ts +0 -0
  141. /package/dist/lib/react/{components → primitives/severity-badge}/SeverityBadge.js +0 -0
@@ -0,0 +1,13 @@
1
+ export type SafeProjectPathResult = {
2
+ ok: true;
3
+ path: string;
4
+ } | {
5
+ ok: false;
6
+ code: "remote_resource_blocked" | "home_resource_blocked" | "resource_path_escape";
7
+ message: string;
8
+ suggestion: string;
9
+ };
10
+ export type SafeProjectPathOptions = {
11
+ realpath?: boolean;
12
+ };
13
+ export declare function resolveSafeProjectPath(projectRoot: string, resourcePath: string, options?: SafeProjectPathOptions): Promise<SafeProjectPathResult>;
@@ -0,0 +1,55 @@
1
+ import { realpath } from "node:fs/promises";
2
+ import path from "node:path";
3
+ export async function resolveSafeProjectPath(projectRoot, resourcePath, options = {}) {
4
+ if (isUrlLike(resourcePath)) {
5
+ return {
6
+ ok: false,
7
+ code: "remote_resource_blocked",
8
+ message: `Remote resources are not allowed: ${resourcePath}`,
9
+ suggestion: "Use a local project-relative resource path."
10
+ };
11
+ }
12
+ if (resourcePath === "~" || resourcePath.startsWith("~/")) {
13
+ return {
14
+ ok: false,
15
+ code: "home_resource_blocked",
16
+ message: `Home-relative resources are not allowed: ${resourcePath}`,
17
+ suggestion: "Move the resource inside the project and reference it with a project-relative path."
18
+ };
19
+ }
20
+ const rootPath = path.resolve(projectRoot);
21
+ const resolvedPath = path.resolve(rootPath, resourcePath);
22
+ if (!isInsidePath(rootPath, resolvedPath)) {
23
+ return {
24
+ ok: false,
25
+ code: "resource_path_escape",
26
+ message: `Resource path must stay inside the project root: ${resourcePath}`,
27
+ suggestion: "Move the resource inside the project root before referencing it."
28
+ };
29
+ }
30
+ if (!options.realpath) {
31
+ return { ok: true, path: resolvedPath };
32
+ }
33
+ try {
34
+ const [realRootPath, realResolvedPath] = await Promise.all([realpath(rootPath), realpath(resolvedPath)]);
35
+ if (!isInsidePath(realRootPath, realResolvedPath)) {
36
+ return {
37
+ ok: false,
38
+ code: "resource_path_escape",
39
+ message: `Resource path must stay inside the project root: ${resourcePath}`,
40
+ suggestion: "Do not reference symlinks that resolve outside the project root."
41
+ };
42
+ }
43
+ return { ok: true, path: realResolvedPath };
44
+ }
45
+ catch {
46
+ return { ok: true, path: resolvedPath };
47
+ }
48
+ }
49
+ function isUrlLike(resourcePath) {
50
+ return /^[a-zA-Z][a-zA-Z\d+.-]*:\/\//.test(resourcePath) || /^(data|blob):/.test(resourcePath) || resourcePath.startsWith("//");
51
+ }
52
+ function isInsidePath(rootPath, candidatePath) {
53
+ const relativePath = path.relative(rootPath, candidatePath);
54
+ return relativePath === "" || (!relativePath.startsWith("..") && !path.isAbsolute(relativePath));
55
+ }
@@ -0,0 +1,40 @@
1
+ import { type ArtifactState } from "../state/artifact-state";
2
+ import { type SortableListSeed } from "../mdx/sortable-list";
3
+ import type { SortableListItem } from "../../react";
4
+ export type InteractionsInspectResult = {
5
+ component: SortableListSeed;
6
+ order: {
7
+ source: "mdx" | "state";
8
+ orderedIds: string[];
9
+ staleIds: string[];
10
+ appendedIds: string[];
11
+ };
12
+ state: {
13
+ path: string;
14
+ exists: boolean;
15
+ };
16
+ source: {
17
+ path: string;
18
+ };
19
+ warnings: string[];
20
+ };
21
+ export type InteractionMutationResult = {
22
+ action: string;
23
+ component: string;
24
+ sourcePath: string;
25
+ statePath: string;
26
+ state: ArtifactState;
27
+ itemId?: string;
28
+ orderedIds?: string[];
29
+ status?: "ok" | "noop";
30
+ reason?: string;
31
+ };
32
+ export declare function inspectInteractionService(projectRoot: string, input: string, id: string): Promise<InteractionsInspectResult>;
33
+ export declare function setInteractionOrderService(projectRoot: string, input: string, id: string, orderedIds: string[]): Promise<InteractionMutationResult>;
34
+ export declare function resetInteractionService(projectRoot: string, input: string, id: string): Promise<InteractionMutationResult>;
35
+ export declare function promoteInteractionService(projectRoot: string, input: string, id: string): Promise<InteractionMutationResult>;
36
+ export declare function addInteractionItemService(projectRoot: string, input: string, id: string, item: SortableListItem, options?: {
37
+ afterId?: string;
38
+ }): Promise<InteractionMutationResult>;
39
+ export declare function removeInteractionItemService(projectRoot: string, input: string, id: string, itemId: string): Promise<InteractionMutationResult>;
40
+ export declare function updateInteractionItemService(projectRoot: string, input: string, id: string, itemId: string, patch: Partial<Omit<SortableListItem, "id">>): Promise<InteractionMutationResult>;
@@ -0,0 +1,226 @@
1
+ import { readFile, writeFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { createArtifactMeta, createArtifactRoute, readArtifactState, writeArtifactState } from "../state/artifact-state.js";
4
+ import { loadConfig } from "../config/config.js";
5
+ import { addSortableListItem as addSortableListItemSource, extractSortableListSeeds, promoteSortableListOrder, removeSortableListItem as removeSortableListItemSource, updateSortableListItem as updateSortableListItemSource } from "../mdx/sortable-list.js";
6
+ export async function inspectInteractionService(projectRoot, input, id) {
7
+ const artifact = await createArtifactFromInput(projectRoot, input);
8
+ const meta = await createArtifactMeta(projectRoot, artifact);
9
+ const state = await readArtifactState(artifact);
10
+ const source = await readFile(artifact.sourcePath, "utf8");
11
+ const component = findSortableList(source, id);
12
+ const persistedOrder = readPersistedOrder(state.interactions[id]);
13
+ const resolvedOrder = resolveOrder(component, persistedOrder);
14
+ const warnings = createInspectWarnings(id, resolvedOrder.staleIds);
15
+ return {
16
+ component,
17
+ order: resolvedOrder,
18
+ state: {
19
+ path: meta.statePath,
20
+ exists: meta.stateExists
21
+ },
22
+ source: {
23
+ path: meta.sourcePath
24
+ },
25
+ warnings
26
+ };
27
+ }
28
+ export async function setInteractionOrderService(projectRoot, input, id, orderedIds) {
29
+ const { artifact, component } = await loadSortableList(projectRoot, input, id);
30
+ validateOrderedIds(component, orderedIds);
31
+ const state = await readArtifactState(artifact);
32
+ const interaction = createSortableListInteraction(component, orderedIds);
33
+ const nextState = await writeArtifactState(projectRoot, artifact, {
34
+ ...state,
35
+ interactions: {
36
+ ...state.interactions,
37
+ [id]: interaction
38
+ }
39
+ });
40
+ return createMutationResult("set-order", artifact, id, nextState, { orderedIds });
41
+ }
42
+ export async function resetInteractionService(projectRoot, input, id) {
43
+ const { artifact } = await loadSortableList(projectRoot, input, id);
44
+ const state = await readArtifactState(artifact);
45
+ const { [id]: _removed, ...interactions } = state.interactions;
46
+ const nextState = await writeArtifactState(projectRoot, artifact, {
47
+ ...state,
48
+ interactions
49
+ });
50
+ return createMutationResult("reset", artifact, id, nextState);
51
+ }
52
+ export async function promoteInteractionService(projectRoot, input, id) {
53
+ const { artifact, component, source } = await loadSortableList(projectRoot, input, id);
54
+ const state = await readArtifactState(artifact);
55
+ const orderedIds = readPersistedOrder(state.interactions[id]);
56
+ if (!orderedIds) {
57
+ return createMutationResult("promote", artifact, id, state, { status: "noop", reason: "no runtime overlay" });
58
+ }
59
+ validateOrderedIds(component, orderedIds);
60
+ const nextSource = promoteSortableListOrder(source, id, orderedIds);
61
+ await writeFile(artifact.sourcePath, nextSource, "utf8");
62
+ const { [id]: _removed, ...interactions } = state.interactions;
63
+ const nextState = await writeArtifactState(projectRoot, artifact, {
64
+ ...state,
65
+ interactions
66
+ });
67
+ return createMutationResult("promote", artifact, id, nextState, { orderedIds });
68
+ }
69
+ export async function addInteractionItemService(projectRoot, input, id, item, options = {}) {
70
+ const { artifact, source } = await loadSortableList(projectRoot, input, id);
71
+ const nextSource = addSortableListItemSource(source, id, item, options);
72
+ const nextComponent = readSortableListFromSource(nextSource, id);
73
+ await writeFile(artifact.sourcePath, nextSource, "utf8");
74
+ const nextState = await syncInteractionOverlay(projectRoot, artifact, id, nextComponent);
75
+ return createMutationResult("add-item", artifact, id, nextState, { itemId: item.id });
76
+ }
77
+ export async function removeInteractionItemService(projectRoot, input, id, itemId) {
78
+ const { artifact, source } = await loadSortableList(projectRoot, input, id);
79
+ const nextSource = removeSortableListItemSource(source, id, itemId);
80
+ const nextComponent = readSortableListFromSource(nextSource, id);
81
+ await writeFile(artifact.sourcePath, nextSource, "utf8");
82
+ const nextState = await syncInteractionOverlay(projectRoot, artifact, id, nextComponent);
83
+ return createMutationResult("remove-item", artifact, id, nextState, { itemId });
84
+ }
85
+ export async function updateInteractionItemService(projectRoot, input, id, itemId, patch) {
86
+ const { artifact, source } = await loadSortableList(projectRoot, input, id);
87
+ const nextSource = updateSortableListItemSource(source, id, itemId, patch);
88
+ const nextComponent = readSortableListFromSource(nextSource, id);
89
+ await writeFile(artifact.sourcePath, nextSource, "utf8");
90
+ const nextState = await syncInteractionOverlay(projectRoot, artifact, id, nextComponent);
91
+ return createMutationResult("update-item", artifact, id, nextState, { itemId });
92
+ }
93
+ async function createArtifactFromInput(projectRoot, input) {
94
+ const config = await loadConfig(projectRoot);
95
+ const mdxPath = path.resolve(projectRoot, input);
96
+ return createArtifactRoute(projectRoot, mdxPath, config.docsDir);
97
+ }
98
+ async function loadSortableList(projectRoot, input, id) {
99
+ const artifact = await createArtifactFromInput(projectRoot, input);
100
+ const source = await readFile(artifact.sourcePath, "utf8");
101
+ const component = findSortableList(source, id);
102
+ return { artifact, component, source };
103
+ }
104
+ function findSortableList(source, id) {
105
+ const seeds = extractSortableListSeeds(source);
106
+ const component = seeds.find((seed) => seed.id === id);
107
+ if (!component) {
108
+ const availableIds = seeds.map((seed) => seed.id);
109
+ throw new Error(availableIds.length > 0
110
+ ? `SortableList not found: ${id}. Available SortableList ids: ${availableIds.join(", ")}.`
111
+ : `SortableList not found: ${id}.`);
112
+ }
113
+ return component;
114
+ }
115
+ function validateOrderedIds(component, orderedIds) {
116
+ const itemIds = component.items.map((item) => item.id);
117
+ const expectedIds = new Set(itemIds);
118
+ const seenIds = new Set();
119
+ const duplicateIds = new Set();
120
+ const unknownIds = new Set();
121
+ for (const orderedId of orderedIds) {
122
+ if (seenIds.has(orderedId)) {
123
+ duplicateIds.add(orderedId);
124
+ }
125
+ seenIds.add(orderedId);
126
+ if (!expectedIds.has(orderedId)) {
127
+ unknownIds.add(orderedId);
128
+ }
129
+ }
130
+ const missingIds = itemIds.filter((itemId) => !seenIds.has(itemId));
131
+ const errors = [
132
+ duplicateIds.size > 0 ? `duplicate ids: ${Array.from(duplicateIds).join(", ")}` : undefined,
133
+ unknownIds.size > 0 ? `unknown ids: ${Array.from(unknownIds).join(", ")}` : undefined,
134
+ missingIds.length > 0 ? `missing ids: ${missingIds.join(", ")}` : undefined
135
+ ].filter(Boolean);
136
+ if (errors.length > 0) {
137
+ throw new Error(`Invalid orderedIds for SortableList ${component.id}: ${errors.join("; ")}.`);
138
+ }
139
+ }
140
+ async function syncInteractionOverlay(projectRoot, artifact, id, component) {
141
+ const state = await readArtifactState(artifact);
142
+ const orderedIds = readPersistedOrder(state.interactions[id]);
143
+ if (!orderedIds) {
144
+ return state;
145
+ }
146
+ const nextOrder = resolveOrder(component, orderedIds).orderedIds;
147
+ return writeArtifactState(projectRoot, artifact, {
148
+ ...state,
149
+ interactions: {
150
+ ...state.interactions,
151
+ [id]: createSortableListInteraction(component, nextOrder)
152
+ }
153
+ });
154
+ }
155
+ function readSortableListFromSource(source, id) {
156
+ const component = extractSortableListSeeds(source).find((seed) => seed.id === id);
157
+ if (!component) {
158
+ throw new Error(`SortableList not found: ${id}.`);
159
+ }
160
+ return component;
161
+ }
162
+ function createSortableListInteraction(component, orderedIds) {
163
+ return {
164
+ type: "sortable-list",
165
+ id: component.id,
166
+ title: component.title,
167
+ orderedIds,
168
+ orderedItems: orderedIds.map((itemId) => {
169
+ const item = component.items.find((candidate) => candidate.id === itemId);
170
+ if (!item) {
171
+ throw new Error(`SortableList item not found: ${itemId}`);
172
+ }
173
+ return item;
174
+ }),
175
+ updatedAt: new Date().toISOString()
176
+ };
177
+ }
178
+ function readPersistedOrder(interaction) {
179
+ if (!isRecord(interaction) || !Array.isArray(interaction.orderedIds)) {
180
+ return undefined;
181
+ }
182
+ const orderedIds = interaction.orderedIds.filter((itemId) => typeof itemId === "string");
183
+ return orderedIds.length > 0 ? orderedIds : undefined;
184
+ }
185
+ function resolveOrder(component, persistedOrder) {
186
+ const itemIds = component.items.map((item) => item.id);
187
+ if (!persistedOrder) {
188
+ return {
189
+ source: "mdx",
190
+ orderedIds: itemIds,
191
+ staleIds: [],
192
+ appendedIds: []
193
+ };
194
+ }
195
+ const knownIds = new Set(itemIds);
196
+ const staleIds = persistedOrder.filter((itemId) => !knownIds.has(itemId));
197
+ const retainedIds = persistedOrder.filter((itemId) => knownIds.has(itemId));
198
+ const retainedIdSet = new Set(retainedIds);
199
+ const appendedIds = itemIds.filter((itemId) => !retainedIdSet.has(itemId));
200
+ return {
201
+ source: "state",
202
+ orderedIds: [...retainedIds, ...appendedIds],
203
+ staleIds,
204
+ appendedIds
205
+ };
206
+ }
207
+ function createInspectWarnings(id, staleIds) {
208
+ if (staleIds.length === 0) {
209
+ return [];
210
+ }
211
+ return [`SortableList ${id} state contains stale orderedIds ignored by inspect: ${staleIds.join(", ")}.`];
212
+ }
213
+ function createMutationResult(action, artifact, id, state, extra = {}) {
214
+ return {
215
+ action,
216
+ component: id,
217
+ sourcePath: artifact.sourceRelativePath,
218
+ statePath: artifact.stateRelativePath,
219
+ state,
220
+ status: extra.status ?? "ok",
221
+ ...extra
222
+ };
223
+ }
224
+ function isRecord(value) {
225
+ return typeof value === "object" && value !== null && !Array.isArray(value);
226
+ }
@@ -0,0 +1,43 @@
1
+ export type ReviewReply = {
2
+ threadId: string;
3
+ body: string;
4
+ };
5
+ export type ReviewAddOptions = {
6
+ anchorId: string;
7
+ body: string;
8
+ title?: string;
9
+ };
10
+ export type ReviewReplyOptions = {
11
+ replies: ReviewReply[];
12
+ status?: string;
13
+ };
14
+ export type ReviewAddResult = {
15
+ anchorId: string;
16
+ statePath: string;
17
+ threadId: string;
18
+ };
19
+ export type ReviewReplyResult = {
20
+ messageRecords: Array<{
21
+ threadId: string;
22
+ messageId: string;
23
+ }>;
24
+ statePath: string;
25
+ };
26
+ export type ReviewValidationResult = {
27
+ anchorCount: number;
28
+ missingThreads: ReviewMissingThread[];
29
+ statePath: string;
30
+ threadCount: number;
31
+ };
32
+ export type ReviewMissingThread = {
33
+ anchorId: string;
34
+ threadId: string;
35
+ title?: string;
36
+ status?: string;
37
+ };
38
+ export declare function addReviewThreadService(projectRoot: string, input: string, options: ReviewAddOptions): Promise<ReviewAddResult>;
39
+ export declare function replyToReviewThreadService(projectRoot: string, input: string, options: ReviewReply & {
40
+ status?: string;
41
+ }): Promise<ReviewReplyResult>;
42
+ export declare function replyToReviewThreadsService(projectRoot: string, input: string, options: ReviewReplyOptions): Promise<ReviewReplyResult>;
43
+ export declare function validateReviewStateService(projectRoot: string, input: string): Promise<ReviewValidationResult>;
@@ -1,31 +1,8 @@
1
1
  import { readFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
- import { createArtifactRoute, readArtifactState, writeArtifactState } from "./artifact-state.js";
4
- import { loadConfig } from "./config.js";
5
- export async function reviewCommand(projectRoot, args) {
6
- const [subcommand] = args;
7
- if (subcommand === "add") {
8
- const options = parseReviewAddArgs(args.slice(1));
9
- console.log(await addReviewThread(projectRoot, options.input, options));
10
- return;
11
- }
12
- if (subcommand === "reply") {
13
- const options = parseReviewReplyArgs(args.slice(1));
14
- console.log(await replyToReviewThreads(projectRoot, options.input, options));
15
- return;
16
- }
17
- if (subcommand === "validate") {
18
- const input = parseReviewValidateArgs(args.slice(1));
19
- const result = await validateReviewState(projectRoot, input);
20
- console.log(result.output);
21
- if (result.missingThreads.length > 0) {
22
- process.exitCode = 1;
23
- }
24
- return;
25
- }
26
- throw new Error("review requires a subcommand. Use `artifact-kit review add <file.mdx>`, `artifact-kit review reply <file.mdx>`, or `artifact-kit review validate <file.mdx>`.");
27
- }
28
- export async function addReviewThread(projectRoot, input, options) {
3
+ import { loadConfig } from "../config/config.js";
4
+ import { createArtifactRoute, readArtifactState, writeArtifactState } from "../state/artifact-state.js";
5
+ export async function addReviewThreadService(projectRoot, input, options) {
29
6
  const artifact = await createArtifactFromInput(projectRoot, input);
30
7
  const state = await readArtifactState(artifact);
31
8
  const source = await readFile(artifact.sourcePath, "utf8");
@@ -49,15 +26,19 @@ export async function addReviewThread(projectRoot, input, options) {
49
26
  ...state,
50
27
  threads: [...state.threads, thread]
51
28
  });
52
- return [`review add ok`, `state: ${artifact.stateRelativePath}`, `thread: ${threadId}`, `anchorId: ${options.anchorId}`].join("\n");
29
+ return {
30
+ anchorId: options.anchorId,
31
+ statePath: artifact.stateRelativePath,
32
+ threadId
33
+ };
53
34
  }
54
- export async function replyToReviewThread(projectRoot, input, options) {
55
- return replyToReviewThreads(projectRoot, input, {
35
+ export async function replyToReviewThreadService(projectRoot, input, options) {
36
+ return replyToReviewThreadsService(projectRoot, input, {
56
37
  replies: [{ threadId: options.threadId, body: options.body }],
57
38
  ...(options.status ? { status: options.status } : {})
58
39
  });
59
40
  }
60
- export async function replyToReviewThreads(projectRoot, input, options) {
41
+ export async function replyToReviewThreadsService(projectRoot, input, options) {
61
42
  const artifact = await createArtifactFromInput(projectRoot, input);
62
43
  const state = await readArtifactState(artifact);
63
44
  const repliesByThread = groupRepliesByThread(options.replies);
@@ -90,14 +71,12 @@ export async function replyToReviewThreads(projectRoot, input, options) {
90
71
  ...state,
91
72
  threads
92
73
  });
93
- return [
94
- `review reply ok`,
95
- `state: ${artifact.stateRelativePath}`,
96
- `messages: ${messageRecords.length}`,
97
- ...messageRecords.map((record) => `- thread: ${record.threadId} message: ${record.messageId}`)
98
- ].join("\n");
74
+ return {
75
+ messageRecords,
76
+ statePath: artifact.stateRelativePath
77
+ };
99
78
  }
100
- export async function validateReviewState(projectRoot, input) {
79
+ export async function validateReviewStateService(projectRoot, input) {
101
80
  const artifact = await createArtifactFromInput(projectRoot, input);
102
81
  const state = await readArtifactState(artifact);
103
82
  const source = await readFile(artifact.sourcePath, "utf8");
@@ -116,32 +95,10 @@ export async function validateReviewState(projectRoot, input) {
116
95
  }
117
96
  ];
118
97
  });
119
- const output = missingThreads.length === 0
120
- ? [
121
- `review validate ok`,
122
- `state: ${artifact.stateRelativePath}`,
123
- `anchors: ${anchorIds.size}`,
124
- `threads: ${threads.length}`
125
- ].join("\n")
126
- : [
127
- `review validate failed`,
128
- `state: ${artifact.stateRelativePath}`,
129
- `anchors: ${anchorIds.size}`,
130
- `threads: ${threads.length}`,
131
- `missing: ${missingThreads.length}`,
132
- ...missingThreads.map((thread) => [
133
- `- thread: ${thread.threadId}`,
134
- `anchorId: ${thread.anchorId}`,
135
- thread.status ? `status: ${thread.status}` : undefined,
136
- thread.title ? `title: ${thread.title}` : undefined
137
- ]
138
- .filter(Boolean)
139
- .join(" "))
140
- ].join("\n");
141
98
  return {
142
99
  anchorCount: anchorIds.size,
143
100
  missingThreads,
144
- output,
101
+ statePath: artifact.stateRelativePath,
145
102
  threadCount: threads.length
146
103
  };
147
104
  }
@@ -150,116 +107,6 @@ async function createArtifactFromInput(projectRoot, input) {
150
107
  const mdxPath = path.resolve(projectRoot, input);
151
108
  return createArtifactRoute(projectRoot, mdxPath, config.docsDir);
152
109
  }
153
- function parseReviewReplyArgs(args) {
154
- const [input, ...rest] = args;
155
- if (!input) {
156
- throw new Error("review reply requires a .mdx file path.");
157
- }
158
- const { replies, status } = parseReviewReplyOptions(rest);
159
- return {
160
- input,
161
- replies,
162
- ...(status ? { status } : {})
163
- };
164
- }
165
- function parseReviewValidateArgs(args) {
166
- const [input, ...rest] = args;
167
- if (!input) {
168
- throw new Error("review validate requires a .mdx file path.");
169
- }
170
- if (rest.length > 0) {
171
- throw new Error(`Unexpected review validate argument: ${rest[0]}`);
172
- }
173
- return input;
174
- }
175
- function parseReviewAddArgs(args) {
176
- const [input, ...rest] = args;
177
- if (!input) {
178
- throw new Error("review add requires a .mdx file path.");
179
- }
180
- const options = parseKeyValueOptions(rest, ["anchor", "body", "title"]);
181
- const anchorId = options.get("anchor");
182
- const body = options.get("body");
183
- const title = options.get("title");
184
- if (!anchorId) {
185
- throw new Error("review add requires --anchor <anchorId>.");
186
- }
187
- if (!body || !body.trim()) {
188
- throw new Error("review add requires --body <message>.");
189
- }
190
- return {
191
- input,
192
- anchorId,
193
- body: body.trim(),
194
- ...(title ? { title } : {})
195
- };
196
- }
197
- function parseKeyValueOptions(args, allowedKeys) {
198
- const options = new Map();
199
- for (let index = 0; index < args.length; index += 1) {
200
- const name = args[index];
201
- if (!name?.startsWith("--")) {
202
- throw new Error(`Unexpected review argument: ${name}`);
203
- }
204
- const key = name.slice(2);
205
- const value = args[index + 1];
206
- if (!allowedKeys.includes(key)) {
207
- throw new Error(`Unknown review option: --${key}.`);
208
- }
209
- if (!value || value.startsWith("--")) {
210
- throw new Error(`Missing value for --${key}.`);
211
- }
212
- options.set(key, value);
213
- index += 1;
214
- }
215
- return options;
216
- }
217
- function parseReviewReplyOptions(args) {
218
- const replies = [];
219
- let status;
220
- let threadId;
221
- for (let index = 0; index < args.length; index += 1) {
222
- const name = args[index];
223
- if (!name?.startsWith("--")) {
224
- throw new Error(`Unexpected review reply argument: ${name}`);
225
- }
226
- const key = name.slice(2);
227
- const value = args[index + 1];
228
- if (!value || value.startsWith("--")) {
229
- throw new Error(`Missing value for --${key}.`);
230
- }
231
- if (key === "thread") {
232
- if (threadId) {
233
- throw new Error(`Missing --body for --thread ${threadId}.`);
234
- }
235
- threadId = value;
236
- }
237
- else if (key === "body") {
238
- if (!threadId) {
239
- throw new Error("review reply requires --thread before --body.");
240
- }
241
- if (!value.trim()) {
242
- throw new Error("review reply requires --body <message>.");
243
- }
244
- replies.push({ threadId, body: value.trim() });
245
- threadId = undefined;
246
- }
247
- else if (key === "status") {
248
- status = value;
249
- }
250
- else {
251
- throw new Error(`Unknown review reply option: --${key}.`);
252
- }
253
- index += 1;
254
- }
255
- if (threadId) {
256
- throw new Error(`Missing --body for --thread ${threadId}.`);
257
- }
258
- if (replies.length === 0) {
259
- throw new Error("review reply requires at least one --thread <threadId> --body <message> pair.");
260
- }
261
- return { replies, status };
262
- }
263
110
  function groupRepliesByThread(replies) {
264
111
  const repliesByThread = new Map();
265
112
  for (const reply of replies) {
@@ -306,8 +153,7 @@ function extractReviewAnchorIds(source) {
306
153
  anchorIds.add(match[2]);
307
154
  }
308
155
  }
309
- addArrayChildAnchors(source, anchorIds, "DecisionMatrix", "options");
310
- addArrayChildAnchors(source, anchorIds, "OptionGrid", "options");
156
+ addCompoundChildAnchors(source, anchorIds, "ContentSet", "Item");
311
157
  addArrayChildAnchors(source, anchorIds, "AnnotatedCode", "annotations", { addCodeChild: true });
312
158
  addComparisonSetChildAnchors(source, anchorIds);
313
159
  return anchorIds;
@@ -332,6 +178,22 @@ function addArrayChildAnchors(source, anchorIds, componentName, arrayPropName, o
332
178
  }
333
179
  }
334
180
  }
181
+ function addCompoundChildAnchors(source, anchorIds, componentName, childName) {
182
+ const componentPattern = new RegExp(`<${componentName}\\b[\\s\\S]*?</${componentName}>`, "g");
183
+ const childPattern = new RegExp(`<${componentName}\\.${childName}\\b[^>]*\\sid\\s*=\\s*["']([^"']+)["'][^>]*>`, "g");
184
+ for (const match of source.matchAll(componentPattern)) {
185
+ const componentSource = match[0];
186
+ const parentId = extractJsxStringProp(componentSource, "id");
187
+ if (!parentId) {
188
+ continue;
189
+ }
190
+ for (const childMatch of componentSource.matchAll(childPattern)) {
191
+ if (childMatch[1]) {
192
+ anchorIds.add(`${parentId}.${childMatch[1]}`);
193
+ }
194
+ }
195
+ }
196
+ }
335
197
  function addComparisonSetChildAnchors(source, anchorIds) {
336
198
  for (const match of source.matchAll(/<ComparisonSet\b[\s\S]*?<\/ComparisonSet>/g)) {
337
199
  const componentSource = match[0];
@@ -1,5 +1,5 @@
1
1
  import { type ReactNode } from "react";
2
- import { type AkCollapseAt, type AkGap } from "./Layout";
2
+ import { type AkCollapseAt, type AkGap } from "../../layout/layout-primitives/Layout";
3
3
  export type ComparisonSetProps = {
4
4
  id?: string;
5
5
  title: string;
@@ -1,8 +1,8 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { createContext, useContext } from "react";
3
- import { Grid } from "./Layout.js";
4
- import { InlineText } from "./InlineText.js";
5
- import { CommentTarget } from "./Comments.js";
3
+ import { CommentTarget } from "../../interactions/comments/Comments.js";
4
+ import { Grid } from "../../layout/layout-primitives/Layout.js";
5
+ import { InlineText } from "../../primitives/inline-text/InlineText.js";
6
6
  const ComparisonSetIdContext = createContext(undefined);
7
7
  function ComparisonSetRoot({ id, title, children, columns = 2, gap = "md", collapseAt = "md", className }) {
8
8
  const targetId = id ?? `comparison:${slugify(title)}`;
@@ -0,0 +1,2 @@
1
+ export { ComparisonSet } from "./ComparisonSet";
2
+ export type { ComparisonSetItemProps, ComparisonSetProps } from "./ComparisonSet";
@@ -0,0 +1 @@
1
+ export { ComparisonSet } from "./ComparisonSet.js";