@stigmer/react 3.1.2 → 3.1.4

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 (285) hide show
  1. package/composer/SessionComposer.d.ts +37 -5
  2. package/composer/SessionComposer.d.ts.map +1 -1
  3. package/composer/SessionComposer.js +13 -5
  4. package/composer/SessionComposer.js.map +1 -1
  5. package/execution/ArtifactApplyButton.d.ts +25 -0
  6. package/execution/ArtifactApplyButton.d.ts.map +1 -0
  7. package/execution/ArtifactApplyButton.js +21 -0
  8. package/execution/ArtifactApplyButton.js.map +1 -0
  9. package/execution/ArtifactContentBody.d.ts +36 -0
  10. package/execution/ArtifactContentBody.d.ts.map +1 -0
  11. package/execution/ArtifactContentBody.js +62 -0
  12. package/execution/ArtifactContentBody.js.map +1 -0
  13. package/execution/ArtifactDocument.d.ts +48 -0
  14. package/execution/ArtifactDocument.d.ts.map +1 -0
  15. package/execution/ArtifactDocument.js +89 -0
  16. package/execution/ArtifactDocument.js.map +1 -0
  17. package/execution/ArtifactPreviewModal.d.ts +9 -9
  18. package/execution/ArtifactPreviewModal.d.ts.map +1 -1
  19. package/execution/ArtifactPreviewModal.js +18 -145
  20. package/execution/ArtifactPreviewModal.js.map +1 -1
  21. package/execution/ArtifactRow.d.ts +49 -0
  22. package/execution/ArtifactRow.d.ts.map +1 -0
  23. package/execution/ArtifactRow.js +56 -0
  24. package/execution/ArtifactRow.js.map +1 -0
  25. package/execution/ArtifactsWidget.d.ts +6 -9
  26. package/execution/ArtifactsWidget.d.ts.map +1 -1
  27. package/execution/ArtifactsWidget.js +15 -19
  28. package/execution/ArtifactsWidget.js.map +1 -1
  29. package/execution/FileChangeProgressBar.d.ts +47 -0
  30. package/execution/FileChangeProgressBar.d.ts.map +1 -0
  31. package/execution/FileChangeProgressBar.js +49 -0
  32. package/execution/FileChangeProgressBar.js.map +1 -0
  33. package/execution/FileChangesView.d.ts.map +1 -1
  34. package/execution/FileChangesView.js +19 -4
  35. package/execution/FileChangesView.js.map +1 -1
  36. package/execution/FileReviewAtoms.d.ts +27 -0
  37. package/execution/FileReviewAtoms.d.ts.map +1 -0
  38. package/execution/FileReviewAtoms.js +64 -0
  39. package/execution/FileReviewAtoms.js.map +1 -0
  40. package/execution/FileReviewCard.d.ts +2 -2
  41. package/execution/FileReviewCard.d.ts.map +1 -1
  42. package/execution/FileReviewCard.js +32 -62
  43. package/execution/FileReviewCard.js.map +1 -1
  44. package/execution/MessageEntry.d.ts +0 -9
  45. package/execution/MessageEntry.d.ts.map +1 -1
  46. package/execution/MessageEntry.js +1 -11
  47. package/execution/MessageEntry.js.map +1 -1
  48. package/execution/MessageThread.d.ts +3 -4
  49. package/execution/MessageThread.d.ts.map +1 -1
  50. package/execution/MessageThread.js +25 -11
  51. package/execution/MessageThread.js.map +1 -1
  52. package/execution/PlanArtifactCard.d.ts +16 -10
  53. package/execution/PlanArtifactCard.d.ts.map +1 -1
  54. package/execution/PlanArtifactCard.js +31 -7
  55. package/execution/PlanArtifactCard.js.map +1 -1
  56. package/execution/PlanCompletionCard.d.ts +4 -4
  57. package/execution/PlanCompletionCard.d.ts.map +1 -1
  58. package/execution/PlanCompletionCard.js +4 -4
  59. package/execution/PlanCompletionCard.js.map +1 -1
  60. package/execution/PlanStreamingCard.d.ts +7 -2
  61. package/execution/PlanStreamingCard.d.ts.map +1 -1
  62. package/execution/PlanStreamingCard.js +11 -6
  63. package/execution/PlanStreamingCard.js.map +1 -1
  64. package/execution/index.d.ts +12 -2
  65. package/execution/index.d.ts.map +1 -1
  66. package/execution/index.js +6 -1
  67. package/execution/index.js.map +1 -1
  68. package/execution/use-build-from-plan-hotkey.d.ts +1 -1
  69. package/execution/use-build-from-plan-hotkey.js +1 -1
  70. package/execution/useArtifactContent.d.ts +6 -1
  71. package/execution/useArtifactContent.d.ts.map +1 -1
  72. package/execution/useArtifactContent.js +7 -2
  73. package/execution/useArtifactContent.js.map +1 -1
  74. package/execution/useArtifactCopy.d.ts +51 -0
  75. package/execution/useArtifactCopy.d.ts.map +1 -0
  76. package/execution/useArtifactCopy.js +75 -0
  77. package/execution/useArtifactCopy.js.map +1 -0
  78. package/execution/useArtifactDownload.d.ts.map +1 -1
  79. package/execution/useArtifactDownload.js +17 -5
  80. package/execution/useArtifactDownload.js.map +1 -1
  81. package/execution/useArtifactInspection.d.ts +85 -0
  82. package/execution/useArtifactInspection.d.ts.map +1 -0
  83. package/execution/useArtifactInspection.js +143 -0
  84. package/execution/useArtifactInspection.js.map +1 -0
  85. package/execution/useCreateAgentExecution.d.ts +16 -3
  86. package/execution/useCreateAgentExecution.d.ts.map +1 -1
  87. package/execution/useCreateAgentExecution.js +1 -0
  88. package/execution/useCreateAgentExecution.js.map +1 -1
  89. package/index.d.ts +4 -4
  90. package/index.d.ts.map +1 -1
  91. package/index.js +3 -3
  92. package/index.js.map +1 -1
  93. package/internal/MermaidDiagram.d.ts +47 -0
  94. package/internal/MermaidDiagram.d.ts.map +1 -0
  95. package/internal/MermaidDiagram.js +118 -0
  96. package/internal/MermaidDiagram.js.map +1 -0
  97. package/internal/file-tree/FileTreeNode.d.ts.map +1 -1
  98. package/internal/file-tree/FileTreeNode.js +2 -2
  99. package/internal/file-tree/FileTreeNode.js.map +1 -1
  100. package/internal/markdown-components.d.ts.map +1 -1
  101. package/internal/markdown-components.js +34 -0
  102. package/internal/markdown-components.js.map +1 -1
  103. package/internal/mermaid-loader.d.ts +26 -0
  104. package/internal/mermaid-loader.d.ts.map +1 -0
  105. package/internal/mermaid-loader.js +34 -0
  106. package/internal/mermaid-loader.js.map +1 -0
  107. package/internal/mermaid-theme-css.d.ts +41 -0
  108. package/internal/mermaid-theme-css.d.ts.map +1 -0
  109. package/internal/mermaid-theme-css.js +179 -0
  110. package/internal/mermaid-theme-css.js.map +1 -0
  111. package/internal/slug.d.ts +9 -5
  112. package/internal/slug.d.ts.map +1 -1
  113. package/internal/slug.js +10 -6
  114. package/internal/slug.js.map +1 -1
  115. package/library/detect-plan-artifact.d.ts +27 -9
  116. package/library/detect-plan-artifact.d.ts.map +1 -1
  117. package/library/detect-plan-artifact.js +26 -6
  118. package/library/detect-plan-artifact.js.map +1 -1
  119. package/library/index.d.ts +1 -1
  120. package/library/index.d.ts.map +1 -1
  121. package/library/index.js +1 -1
  122. package/library/index.js.map +1 -1
  123. package/package.json +5 -4
  124. package/session/PlanEditor.js +1 -1
  125. package/session/PlanEditor.js.map +1 -1
  126. package/session/SessionViewer.d.ts.map +1 -1
  127. package/session/SessionViewer.js +122 -30
  128. package/session/SessionViewer.js.map +1 -1
  129. package/session/artifact-document.d.ts +13 -0
  130. package/session/artifact-document.d.ts.map +1 -0
  131. package/session/artifact-document.js +22 -0
  132. package/session/artifact-document.js.map +1 -0
  133. package/session/facets/ArtifactsTab.d.ts +30 -4
  134. package/session/facets/ArtifactsTab.d.ts.map +1 -1
  135. package/session/facets/ArtifactsTab.js +32 -10
  136. package/session/facets/ArtifactsTab.js.map +1 -1
  137. package/session/index.d.ts +2 -1
  138. package/session/index.d.ts.map +1 -1
  139. package/session/index.js +4 -1
  140. package/session/index.js.map +1 -1
  141. package/session/plan-document.d.ts +14 -2
  142. package/session/plan-document.d.ts.map +1 -1
  143. package/session/plan-document.js +14 -3
  144. package/session/plan-document.js.map +1 -1
  145. package/session/usePlanDraft.d.ts +1 -1
  146. package/session/useSessionArtifacts.d.ts +17 -0
  147. package/session/useSessionArtifacts.d.ts.map +1 -1
  148. package/session/useSessionArtifacts.js +16 -5
  149. package/session/useSessionArtifacts.js.map +1 -1
  150. package/session/useSessionConversation.d.ts +26 -3
  151. package/session/useSessionConversation.d.ts.map +1 -1
  152. package/session/useSessionConversation.js +51 -6
  153. package/session/useSessionConversation.js.map +1 -1
  154. package/session/useSessionPageFlow.d.ts.map +1 -1
  155. package/session/useSessionPageFlow.js +1 -0
  156. package/session/useSessionPageFlow.js.map +1 -1
  157. package/session/useSessionPanel.d.ts +20 -0
  158. package/session/useSessionPanel.d.ts.map +1 -1
  159. package/session/useSessionPanel.js +13 -0
  160. package/session/useSessionPanel.js.map +1 -1
  161. package/session/useSessionRailViews.d.ts +13 -1
  162. package/session/useSessionRailViews.d.ts.map +1 -1
  163. package/session/useSessionRailViews.js +5 -3
  164. package/session/useSessionRailViews.js.map +1 -1
  165. package/src/composer/SessionComposer.tsx +76 -7
  166. package/src/composer/__tests__/SessionComposer-editing.test.tsx +128 -0
  167. package/src/execution/ArtifactApplyButton.tsx +79 -0
  168. package/src/execution/ArtifactContentBody.tsx +255 -0
  169. package/src/execution/ArtifactDocument.tsx +344 -0
  170. package/src/execution/ArtifactPreviewModal.tsx +49 -415
  171. package/src/execution/ArtifactRow.tsx +165 -0
  172. package/src/execution/ArtifactsWidget.tsx +24 -33
  173. package/src/execution/FileChangeProgressBar.tsx +121 -0
  174. package/src/execution/FileChangesView.tsx +18 -4
  175. package/src/execution/FileReviewAtoms.tsx +89 -0
  176. package/src/execution/FileReviewCard.tsx +57 -90
  177. package/src/execution/MessageEntry.tsx +0 -62
  178. package/src/execution/MessageThread.tsx +29 -15
  179. package/src/execution/PlanArtifactCard.tsx +130 -52
  180. package/src/execution/PlanCompletionCard.tsx +5 -5
  181. package/src/execution/PlanStreamingCard.tsx +15 -11
  182. package/src/execution/__tests__/ArtifactContentBody.test.tsx +131 -0
  183. package/src/execution/__tests__/ArtifactDocument.test.tsx +100 -0
  184. package/src/execution/__tests__/ArtifactPreviewModal.test.tsx +6 -6
  185. package/src/execution/__tests__/ArtifactRow.test.tsx +139 -0
  186. package/src/execution/__tests__/ArtifactsWidget.test.tsx +78 -0
  187. package/src/execution/__tests__/FileChangeProgressBar.test.tsx +125 -0
  188. package/src/execution/__tests__/FileReviewCard.test.tsx +66 -7
  189. package/src/execution/__tests__/MessageThread.test.tsx +1 -1
  190. package/src/execution/__tests__/PlanArtifactCard.test.tsx +52 -18
  191. package/src/execution/__tests__/PlanCompletionCard.test.tsx +2 -2
  192. package/src/execution/__tests__/PlanStreamingCard.test.tsx +11 -4
  193. package/src/execution/__tests__/buildThreadItems-plan-completion.test.ts +104 -8
  194. package/src/execution/__tests__/message-entry.test.tsx +56 -39
  195. package/src/execution/__tests__/useArtifactCopy.test.tsx +90 -0
  196. package/src/execution/__tests__/useArtifactDownload.test.tsx +2 -0
  197. package/src/execution/__tests__/useArtifactInspection.test.tsx +151 -0
  198. package/src/execution/__tests__/useCreateAgentExecution.test.tsx +35 -0
  199. package/src/execution/index.ts +20 -2
  200. package/src/execution/use-build-from-plan-hotkey.ts +1 -1
  201. package/src/execution/useArtifactContent.ts +7 -0
  202. package/src/execution/useArtifactCopy.ts +109 -0
  203. package/src/execution/useArtifactDownload.ts +17 -5
  204. package/src/execution/useArtifactInspection.ts +252 -0
  205. package/src/execution/useCreateAgentExecution.ts +17 -3
  206. package/src/index.ts +17 -2
  207. package/src/internal/MermaidDiagram.tsx +164 -0
  208. package/src/internal/__tests__/a11y/mermaid-theme.a11y.test.tsx +122 -0
  209. package/src/internal/__tests__/markdown-components.test.tsx +93 -1
  210. package/src/internal/__tests__/mermaid-diagram.test.tsx +282 -0
  211. package/src/internal/file-tree/FileTreeNode.tsx +13 -2
  212. package/src/internal/markdown-components.tsx +36 -1
  213. package/src/internal/mermaid-loader.ts +44 -0
  214. package/src/internal/mermaid-theme-css.ts +178 -0
  215. package/src/internal/slug.ts +10 -6
  216. package/src/library/__tests__/detect-plan-artifact.test.ts +21 -0
  217. package/src/library/detect-plan-artifact.ts +32 -10
  218. package/src/library/index.ts +2 -0
  219. package/src/session/PlanEditor.tsx +1 -1
  220. package/src/session/SessionViewer.tsx +227 -51
  221. package/src/session/__tests__/PlanEditor.test.tsx +4 -4
  222. package/src/session/__tests__/PlanStreamingDocument.test.tsx +1 -1
  223. package/src/session/__tests__/SessionViewer-artifactTabs.test.tsx +210 -0
  224. package/src/session/__tests__/SessionViewer-editResubmit.test.tsx +233 -0
  225. package/src/session/__tests__/SessionViewer-fileReviewDock.test.tsx +57 -0
  226. package/src/session/__tests__/useSessionArtifacts.test.ts +57 -0
  227. package/src/session/__tests__/useSessionConversation.test.tsx +216 -1
  228. package/src/session/__tests__/useSessionPanel.test.ts +70 -1
  229. package/src/session/artifact-document.ts +23 -0
  230. package/src/session/facets/ArtifactsTab.tsx +66 -18
  231. package/src/session/facets/__tests__/ArtifactsTab.test.tsx +111 -47
  232. package/src/session/index.ts +5 -1
  233. package/src/session/plan-document.ts +14 -3
  234. package/src/session/usePlanDraft.ts +1 -1
  235. package/src/session/useSessionArtifacts.ts +16 -5
  236. package/src/session/useSessionConversation.ts +80 -10
  237. package/src/session/useSessionPageFlow.ts +1 -0
  238. package/src/session/useSessionPanel.ts +41 -0
  239. package/src/session/useSessionRailViews.tsx +21 -1
  240. package/src/workspace/EditorTabs.tsx +38 -9
  241. package/src/workspace/ExplorerTree.tsx +10 -4
  242. package/src/workspace/FileViewer.tsx +27 -6
  243. package/src/workspace/WorkspaceContentSearch.tsx +44 -20
  244. package/src/workspace/WorkspaceEditor.tsx +19 -16
  245. package/src/workspace/WorkspaceFileSearch.tsx +51 -28
  246. package/src/workspace/WorkspaceSurface.tsx +106 -48
  247. package/src/workspace/__tests__/EditorTabs.test.tsx +4 -2
  248. package/src/workspace/__tests__/ExplorerTree.test.tsx +52 -0
  249. package/src/workspace/__tests__/FileViewer.test.tsx +18 -0
  250. package/src/workspace/__tests__/WorkspaceContentSearch.test.tsx +38 -5
  251. package/src/workspace/__tests__/WorkspaceEditor-initialPanel.test.tsx +19 -0
  252. package/src/workspace/__tests__/WorkspaceFileSearch.test.tsx +27 -6
  253. package/src/workspace/__tests__/WorkspaceSurface.test.tsx +70 -0
  254. package/src/workspace/__tests__/a11y/harness.tsx +140 -0
  255. package/src/workspace/__tests__/a11y/mock-capabilities.ts +138 -0
  256. package/src/workspace/__tests__/a11y/workspace-editor.a11y.test.tsx +65 -0
  257. package/src/workspace/__tests__/a11y/workspace-explorer-search.a11y.test.tsx +112 -0
  258. package/src/workspace/__tests__/a11y/workspace-viewer-tabs.a11y.test.tsx +69 -0
  259. package/styles.css +1 -1
  260. package/workspace/EditorTabs.d.ts +21 -3
  261. package/workspace/EditorTabs.d.ts.map +1 -1
  262. package/workspace/EditorTabs.js +18 -6
  263. package/workspace/EditorTabs.js.map +1 -1
  264. package/workspace/ExplorerTree.js +8 -4
  265. package/workspace/ExplorerTree.js.map +1 -1
  266. package/workspace/FileViewer.d.ts.map +1 -1
  267. package/workspace/FileViewer.js +19 -8
  268. package/workspace/FileViewer.js.map +1 -1
  269. package/workspace/WorkspaceContentSearch.d.ts.map +1 -1
  270. package/workspace/WorkspaceContentSearch.js +23 -2
  271. package/workspace/WorkspaceContentSearch.js.map +1 -1
  272. package/workspace/WorkspaceEditor.d.ts.map +1 -1
  273. package/workspace/WorkspaceEditor.js +8 -10
  274. package/workspace/WorkspaceEditor.js.map +1 -1
  275. package/workspace/WorkspaceFileSearch.d.ts.map +1 -1
  276. package/workspace/WorkspaceFileSearch.js +23 -3
  277. package/workspace/WorkspaceFileSearch.js.map +1 -1
  278. package/workspace/WorkspaceSurface.d.ts.map +1 -1
  279. package/workspace/WorkspaceSurface.js +54 -18
  280. package/workspace/WorkspaceSurface.js.map +1 -1
  281. package/execution/ArtifactCard.d.ts +0 -81
  282. package/execution/ArtifactCard.d.ts.map +0 -1
  283. package/execution/ArtifactCard.js +0 -130
  284. package/execution/ArtifactCard.js.map +0 -1
  285. package/src/execution/ArtifactCard.tsx +0 -320
@@ -0,0 +1,140 @@
1
+ // Shared harness for the browser-mode accessibility audits (DD-22).
2
+ //
3
+ // Renders an audited component inside the `.stgm` scope against the SDK's
4
+ // SHIPPED stylesheet (the prebuilt `dist/styles.css`, not a test-time
5
+ // recompile), then runs axe-core with the SAME policy as the e2e page audit
6
+ // (`test/e2e/tests/functional/accessibility.spec.ts`): WCAG 2.0 A/AA tags,
7
+ // fail on critical/serious, log moderate/minor.
8
+ //
9
+ // Why this file exists and how it stays honest:
10
+ // - Contrast/target-size are the point, so it runs in a real browser via
11
+ // `vitest.a11y.config.ts` (happy-dom can't evaluate either).
12
+ // - axe is scoped to the rendered container and restricted to WCAG tags, so
13
+ // page-level rules (`region`, `landmark-one-main`, `html-has-lang`, …) don't
14
+ // false-positive on a component rendered in isolation.
15
+ // - The wrapper carries the themed background so `color-contrast` is measured
16
+ // against the real surface color in BOTH color modes, not the browser default.
17
+
18
+ import "../../../../dist/styles.css";
19
+
20
+ import { render, cleanup } from "@testing-library/react";
21
+ import type { ReactElement } from "react";
22
+ import axe from "axe-core";
23
+ import { expect } from "vitest";
24
+ import type { Stigmer } from "@stigmer/sdk";
25
+ import { StigmerContext } from "../../../context.js";
26
+ import { __clearWorkspaceListingCache } from "../../workspaceListingCache.js";
27
+
28
+ /** The color modes every state is audited under. */
29
+ export const COLOR_MODES = ["light", "dark"] as const;
30
+ export type ColorMode = (typeof COLOR_MODES)[number];
31
+
32
+ /**
33
+ * Rules that are audited but reported as advisory (logged, non-blocking) rather
34
+ * than failing the suite — mirroring the e2e audit's graduated rollout, which
35
+ * ships fixing only some severities first.
36
+ *
37
+ * `color-contrast`: the workspace panel's deliberately-dense small muted text
38
+ * (tree labels, section headers, `text-[0.65rem]` path subtitles/previews,
39
+ * count badges, the empty-state placeholder) falls below WCAG AA 4.5:1 in both
40
+ * modes. This is a PRE-EXISTING, app-wide design-token/density concern (the
41
+ * `--stgm-muted-foreground` token at small sizes), not a Session 18 regression,
42
+ * and its fix needs a deliberate design pass across presets (tracked in DD-22's
43
+ * Follow-ups). Kept advisory here so this audit can land and guard the
44
+ * STRUCTURAL hardening (roles, names, relationships, nesting) immediately.
45
+ * Promote back to blocking once the contrast pass ships.
46
+ */
47
+ const ADVISORY_RULE_IDS = new Set<string>(["color-contrast"]);
48
+
49
+ // The audited surfaces reach the SDK client only through the diff path's
50
+ // artifact hooks, which skip all fetches for inline file changes (the only kind
51
+ // the fixtures build). No client method is ever invoked, so an empty stub is
52
+ // sufficient — and required, since `useStigmer` throws outside a provider.
53
+ const stubClient = {} as unknown as Stigmer;
54
+
55
+ /**
56
+ * Render `ui` inside a themed `.stgm` container for the given color mode.
57
+ * Returns the container so callers can await the settled DOM before auditing.
58
+ */
59
+ export function renderAudited(ui: ReactElement, mode: ColorMode): HTMLElement {
60
+ const container = document.createElement("div");
61
+ container.className = "stgm";
62
+ container.setAttribute("data-stgm-color-mode", mode);
63
+ // The panel always sits on a themed surface; matching it here means
64
+ // color-contrast is computed against the real background, not body white.
65
+ container.style.background = "var(--stgm-background)";
66
+ container.style.color = "var(--stgm-foreground)";
67
+ // A fixed height gives the flex/scroll surfaces (ResizableSplit, editor pane)
68
+ // a real box to lay out in — observer-driven measurement that is a no-op in
69
+ // happy-dom but correct in Chromium.
70
+ container.style.height = "720px";
71
+ container.style.width = "1024px";
72
+ // RTL does NOT attach a caller-provided container to the document, so attach
73
+ // it ourselves — both `screen` queries and axe need it in the live DOM.
74
+ document.body.appendChild(container);
75
+
76
+ render(<StigmerContext.Provider value={stubClient}>{ui}</StigmerContext.Provider>, {
77
+ container,
78
+ });
79
+ return container;
80
+ }
81
+
82
+ function summarize(violations: axe.Result[]): string {
83
+ return violations
84
+ .map((v) => {
85
+ const nodes = v.nodes
86
+ .slice(0, 5)
87
+ .map((n) => ` · ${n.target.join(" ")}\n ${n.html.slice(0, 160)}`)
88
+ .join("\n");
89
+ return ` [${v.impact}] ${v.id}: ${v.help} (${v.nodes.length} node(s))\n${nodes}`;
90
+ })
91
+ .join("\n");
92
+ }
93
+
94
+ /**
95
+ * Run the WCAG 2.0 A/AA audit over `container`. Fails the test on any
96
+ * critical/serious violation (mirroring the e2e gate); logs moderate/minor.
97
+ */
98
+ export async function auditA11y(container: HTMLElement, label: string): Promise<void> {
99
+ const results = await axe.run(container, {
100
+ runOnly: { type: "tag", values: ["wcag2a", "wcag2aa"] },
101
+ });
102
+
103
+ // Blocking: critical/serious violations that are NOT on the advisory list.
104
+ const serious = results.violations.filter(
105
+ (v) =>
106
+ (v.impact === "critical" || v.impact === "serious") &&
107
+ !ADVISORY_RULE_IDS.has(v.id),
108
+ );
109
+ // Advisory: everything else worth logging — the deferred rules plus any
110
+ // moderate/minor findings.
111
+ const advisory = results.violations.filter(
112
+ (v) =>
113
+ ADVISORY_RULE_IDS.has(v.id) ||
114
+ v.impact === "moderate" ||
115
+ v.impact === "minor",
116
+ );
117
+
118
+ if (advisory.length > 0) {
119
+ // Non-blocking a11y signal (mirrors the e2e audit's moderate/minor logging).
120
+ console.warn(`[a11y: ${label}] non-blocking violations:\n${summarize(advisory)}`);
121
+ }
122
+
123
+ expect(
124
+ serious,
125
+ `[a11y: ${label}] critical/serious axe violations:\n${summarize(serious)}`,
126
+ ).toHaveLength(0);
127
+ }
128
+
129
+ /**
130
+ * Reset cross-test state. The listing cache is module-level (keyed by entry id),
131
+ * so without this a scenario could take a prior scenario's cached listing —
132
+ * masking the truncation banner or leaking a stale tree. Call in `afterEach`.
133
+ */
134
+ export function resetWorkspaceAudit(): void {
135
+ cleanup();
136
+ // cleanup() unmounts React trees but does not remove caller-provided
137
+ // containers; drop any leftover `.stgm` wrappers so scenarios never bleed.
138
+ document.querySelectorAll(".stgm").forEach((node) => node.remove());
139
+ __clearWorkspaceListingCache();
140
+ }
@@ -0,0 +1,138 @@
1
+ // In-memory workspace capabilities for the browser-mode accessibility audit
2
+ // (DD-22). These mirror the shapes a real host injects (DD-004) — a GitHub
3
+ // lister/reader on web, a Tauri walker on desktop — but serve a tiny fixed
4
+ // tree so the Session 18 hardened surfaces render deterministically, with no
5
+ // network, backend, or token. Fixtures render REAL component output only; they
6
+ // introduce no Tailwind utility classes of their own, so the prebuilt
7
+ // `dist/styles.css` (generated by `@source` scanning over SDK source) covers
8
+ // every class in the audited DOM.
9
+
10
+ import { create } from "@bufbuild/protobuf";
11
+ import {
12
+ FileChangeSchema,
13
+ FileContentSchema,
14
+ type FileChange,
15
+ } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
16
+ import {
17
+ FileChangeCaptureLevel,
18
+ FileChangeType,
19
+ } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
20
+ import type { WorkspaceEntry } from "../../useWorkspaceEntries.js";
21
+ import type {
22
+ WorkspaceFileEntry,
23
+ WorkspaceFileLister,
24
+ } from "../../WorkspaceFileLister.js";
25
+ import type {
26
+ WorkspaceFileContent,
27
+ WorkspaceFileReader,
28
+ } from "../../WorkspaceFileReader.js";
29
+ import type { WorkspaceContentSearcher } from "../../WorkspaceContentSearcher.js";
30
+
31
+ /** A git entry whose listing is a normal (non-truncated) multi-level tree. */
32
+ export const EXPLORER_ENTRY: WorkspaceEntry = {
33
+ id: "a11y-explorer",
34
+ name: "acme/app",
35
+ type: "git",
36
+ gitUrl: "https://github.com/acme/app.git",
37
+ gitBranch: "main",
38
+ };
39
+
40
+ /** A git entry whose listing is capped, exercising the truncation banner. */
41
+ export const TRUNCATED_ENTRY: WorkspaceEntry = {
42
+ id: "a11y-truncated",
43
+ name: "acme/monorepo",
44
+ type: "git",
45
+ gitUrl: "https://github.com/acme/monorepo.git",
46
+ gitBranch: "main",
47
+ };
48
+
49
+ /** The file opened by the viewer audits (present in {@link EXPLORER_ENTRY}). */
50
+ export const OPEN_FILE_PATH = "src/index.ts";
51
+
52
+ const OPEN_FILE_TEXT = [
53
+ "export interface Greeting {",
54
+ " readonly message: string;",
55
+ "}",
56
+ "",
57
+ "export function greet(name: string): Greeting {",
58
+ " return { message: `Hello, ${name}!` };",
59
+ "}",
60
+ "",
61
+ ].join("\n");
62
+
63
+ const EXPLORER_LISTING: readonly WorkspaceFileEntry[] = [
64
+ { path: "src", isDirectory: true },
65
+ { path: "src/index.ts", isDirectory: false },
66
+ { path: "src/lib", isDirectory: true },
67
+ { path: "src/lib/format.ts", isDirectory: false },
68
+ { path: "src/lib/logo.png", isDirectory: false },
69
+ { path: "README.md", isDirectory: false },
70
+ { path: "package.json", isDirectory: false },
71
+ ];
72
+
73
+ const TRUNCATED_LISTING: readonly WorkspaceFileEntry[] = [
74
+ { path: "src", isDirectory: true },
75
+ { path: "src/a.ts", isDirectory: false },
76
+ { path: "src/b.ts", isDirectory: false },
77
+ // The lister's advisory notice — the cache collapses this into the single
78
+ // `truncated` banner the audit asserts is accessible (DD-11).
79
+ { path: "Listing truncated — repository too large.", isDirectory: false, notice: true },
80
+ ];
81
+
82
+ /** Lister serving the fixed tree, keyed by entry id. */
83
+ export const mockLister: WorkspaceFileLister = async (entry) => {
84
+ if (entry.id === TRUNCATED_ENTRY.id) return [...TRUNCATED_LISTING];
85
+ if (entry.id === EXPLORER_ENTRY.id) return [...EXPLORER_LISTING];
86
+ return [];
87
+ };
88
+
89
+ const TEXT_CONTENT: WorkspaceFileContent = {
90
+ text: OPEN_FILE_TEXT,
91
+ isBinary: false,
92
+ size: OPEN_FILE_TEXT.length,
93
+ encoding: "utf-8",
94
+ };
95
+
96
+ const BINARY_CONTENT: WorkspaceFileContent = {
97
+ text: null,
98
+ isBinary: true,
99
+ size: 2048,
100
+ encoding: "base64",
101
+ };
102
+
103
+ /** Reader returning text for source files and a binary sentinel for the png. */
104
+ export const mockReader: WorkspaceFileReader = async (_entry, path) => {
105
+ if (path.endsWith(".png")) return BINARY_CONTENT;
106
+ return TEXT_CONTENT;
107
+ };
108
+
109
+ /** Content searcher returning a couple of deterministic line hits. */
110
+ export const mockSearcher: WorkspaceContentSearcher = async (entry, query) => {
111
+ if (entry.id !== EXPLORER_ENTRY.id) return null;
112
+ return {
113
+ matches: [
114
+ { path: "src/index.ts", line: 5, preview: `export function ${query}(name: string): Greeting {` },
115
+ { path: "src/lib/format.ts", line: 2, preview: ` // ${query} helper` },
116
+ ],
117
+ truncated: false,
118
+ };
119
+ };
120
+
121
+ /**
122
+ * An inline (non-offloaded) MODIFY change for the open file, so the diff pane
123
+ * resolves its before/after text synchronously — `useFileChangeContent` issues
124
+ * no artifact fetch for inline sides, so the audit needs no live backend.
125
+ */
126
+ export function fileChangeForOpenFile(): FileChange {
127
+ return create(FileChangeSchema, {
128
+ path: OPEN_FILE_PATH,
129
+ changeType: FileChangeType.MODIFY,
130
+ captureLevel: FileChangeCaptureLevel.WHOLE_FILE,
131
+ before: create(FileContentSchema, {
132
+ body: { case: "inline", value: "export function greet(name: string) {\n return name;\n}\n" },
133
+ }),
134
+ after: create(FileContentSchema, {
135
+ body: { case: "inline", value: OPEN_FILE_TEXT },
136
+ }),
137
+ });
138
+ }
@@ -0,0 +1,65 @@
1
+ // Accessibility audit (DD-22) — WorkspaceEditor (the Config-facet workspace
2
+ // entry manager).
3
+ //
4
+ // Covers the Session 18 hardening here: aria-labeled git inputs and focus rings
5
+ // on the editor's buttons. Audits two states — the default action list and the
6
+ // manual "add git repository" form — in light + dark against the shipped CSS.
7
+
8
+ import { describe, it, afterEach } from "vitest";
9
+ import { screen } from "@testing-library/react";
10
+ import { WorkspaceEditor } from "../../WorkspaceEditor.js";
11
+ import type { UseWorkspaceEntriesReturn } from "../../useWorkspaceEntries.js";
12
+ import {
13
+ COLOR_MODES,
14
+ auditA11y,
15
+ renderAudited,
16
+ resetWorkspaceAudit,
17
+ } from "./harness.js";
18
+
19
+ /** A minimal workspace hook return — rendering only reads `entries`. */
20
+ function mockWorkspace(
21
+ entries: UseWorkspaceEntriesReturn["entries"] = [],
22
+ ): UseWorkspaceEntriesReturn {
23
+ return {
24
+ entries,
25
+ addGitRepo: () => {},
26
+ addLocalPath: () => {},
27
+ remove: () => {},
28
+ clear: () => {},
29
+ clearLocal: () => {},
30
+ toInput: () => [],
31
+ hasEntries: entries.length > 0,
32
+ };
33
+ }
34
+
35
+ const GIT_ENTRY = {
36
+ id: "editor-entry",
37
+ name: "acme/app",
38
+ type: "git" as const,
39
+ gitUrl: "https://github.com/acme/app.git",
40
+ gitBranch: "main",
41
+ };
42
+
43
+ afterEach(resetWorkspaceAudit);
44
+
45
+ describe("WorkspaceEditor a11y", () => {
46
+ it.each(COLOR_MODES)("default action list with an entry (%s)", async (mode) => {
47
+ const container = renderAudited(
48
+ <WorkspaceEditor workspace={mockWorkspace([GIT_ENTRY])} enableLocal />,
49
+ mode,
50
+ );
51
+ await screen.findByText("Connect GitHub");
52
+ await auditA11y(container, `editor list · ${mode}`);
53
+ });
54
+
55
+ it.each(COLOR_MODES)("manual add-git form with labeled inputs (%s)", async (mode) => {
56
+ const container = renderAudited(
57
+ // Empty entries + initialPanel="github" with no connection opens the
58
+ // manual URL/branch form (the labeled git inputs Session 18 hardened).
59
+ <WorkspaceEditor workspace={mockWorkspace()} initialPanel="github" />,
60
+ mode,
61
+ );
62
+ await screen.findByLabelText("Git repository URL");
63
+ await auditA11y(container, `editor git form · ${mode}`);
64
+ });
65
+ });
@@ -0,0 +1,112 @@
1
+ // Accessibility audit (DD-22) — the explorer tree and both search surfaces.
2
+ //
3
+ // Covers the Session 18 hardening for these surfaces: file-tree `role="tree"` +
4
+ // `aria-level` + `aria-hidden` glyphs, the neutral truncation banner + Retry
5
+ // focus ring, the search `role="status"` live region, the combobox/listbox
6
+ // results, and the `Name | Text` search-mode radiogroup — each in light + dark
7
+ // against the shipped stylesheet.
8
+
9
+ import { describe, it, afterEach } from "vitest";
10
+ import { screen, fireEvent } from "@testing-library/react";
11
+ import { WorkspaceSurface } from "../../WorkspaceSurface.js";
12
+ import {
13
+ EXPLORER_ENTRY,
14
+ TRUNCATED_ENTRY,
15
+ mockLister,
16
+ mockReader,
17
+ mockSearcher,
18
+ } from "./mock-capabilities.js";
19
+ import {
20
+ COLOR_MODES,
21
+ auditA11y,
22
+ renderAudited,
23
+ resetWorkspaceAudit,
24
+ } from "./harness.js";
25
+
26
+ const noop = () => {};
27
+
28
+ /** The controlled props every WorkspaceSurface render needs, minus the ones a
29
+ * given scenario varies (entries/lister/searcher/view). */
30
+ const baseProps = {
31
+ reader: mockReader,
32
+ editors: [],
33
+ selectedFile: null,
34
+ onOpenFile: noop,
35
+ onActivateEditor: noop,
36
+ onPinEditor: noop,
37
+ onCloseEditor: noop,
38
+ onCollapse: noop,
39
+ };
40
+
41
+ afterEach(resetWorkspaceAudit);
42
+
43
+ describe("WorkspaceSurface a11y — explorer + search", () => {
44
+ it.each(COLOR_MODES)("explorer tree, populated listing (%s)", async (mode) => {
45
+ const container = renderAudited(
46
+ <WorkspaceSurface
47
+ entries={[EXPLORER_ENTRY]}
48
+ lister={mockLister}
49
+ view="files"
50
+ {...baseProps}
51
+ />,
52
+ mode,
53
+ );
54
+ // The first root auto-expands; wait for a top-level file before auditing so
55
+ // axe sees the loaded tree, not the loading state.
56
+ await screen.findByText("README.md");
57
+ await auditA11y(container, `explorer tree · ${mode}`);
58
+ });
59
+
60
+ it.each(COLOR_MODES)("explorer truncation banner (%s)", async (mode) => {
61
+ const container = renderAudited(
62
+ <WorkspaceSurface
63
+ entries={[TRUNCATED_ENTRY]}
64
+ lister={mockLister}
65
+ view="files"
66
+ {...baseProps}
67
+ />,
68
+ mode,
69
+ );
70
+ await screen.findByText(/Showing a partial listing/i);
71
+ await auditA11y(container, `explorer truncation · ${mode}`);
72
+ });
73
+
74
+ it.each(COLOR_MODES)("filename search with results (%s)", async (mode) => {
75
+ const container = renderAudited(
76
+ <WorkspaceSurface
77
+ entries={[EXPLORER_ENTRY]}
78
+ lister={mockLister}
79
+ searcher={mockSearcher}
80
+ view="search"
81
+ {...baseProps}
82
+ />,
83
+ mode,
84
+ );
85
+ const input = await screen.findByRole("combobox", {
86
+ name: "Search workspace files by name",
87
+ });
88
+ fireEvent.change(input, { target: { value: "index" } });
89
+ await screen.findByRole("listbox", { name: "Search results" });
90
+ await auditA11y(container, `filename search · ${mode}`);
91
+ });
92
+
93
+ it.each(COLOR_MODES)("content (text) search with results (%s)", async (mode) => {
94
+ const container = renderAudited(
95
+ <WorkspaceSurface
96
+ entries={[EXPLORER_ENTRY]}
97
+ lister={mockLister}
98
+ searcher={mockSearcher}
99
+ view="search"
100
+ {...baseProps}
101
+ />,
102
+ mode,
103
+ );
104
+ fireEvent.click(await screen.findByRole("radio", { name: "Text" }));
105
+ const input = await screen.findByRole("combobox", {
106
+ name: "Search text in workspace files",
107
+ });
108
+ fireEvent.change(input, { target: { value: "greet" } });
109
+ await screen.findByRole("listbox", { name: "Search results" });
110
+ await auditA11y(container, `content search · ${mode}`);
111
+ });
112
+ });
@@ -0,0 +1,69 @@
1
+ // Accessibility audit (DD-22) — the editor area: open tabs, the file viewer in
2
+ // both File and Diff modes, and the mode toggle.
3
+ //
4
+ // Covers the Session 18 hardening here: `EditorTabs` `role="tablist"`/`tab` with
5
+ // `aria-controls`, the single `role="tabpanel"` body, the `FileViewer`
6
+ // `role="region"` (Escape-focusable) and its `Diff | File` `ViewerModeToggle`
7
+ // radiogroup — each in light + dark against the shipped stylesheet.
8
+
9
+ import { describe, it, afterEach, expect } from "vitest";
10
+ import { screen, waitFor } from "@testing-library/react";
11
+ import { WorkspaceSurface } from "../../WorkspaceSurface.js";
12
+ import {
13
+ EXPLORER_ENTRY,
14
+ OPEN_FILE_PATH,
15
+ fileChangeForOpenFile,
16
+ mockLister,
17
+ mockReader,
18
+ } from "./mock-capabilities.js";
19
+ import {
20
+ COLOR_MODES,
21
+ auditA11y,
22
+ renderAudited,
23
+ resetWorkspaceAudit,
24
+ } from "./harness.js";
25
+
26
+ const noop = () => {};
27
+
28
+ const openEditorProps = {
29
+ entries: [EXPLORER_ENTRY],
30
+ lister: mockLister,
31
+ reader: mockReader,
32
+ editors: [{ entryId: EXPLORER_ENTRY.id, path: OPEN_FILE_PATH, preview: false }],
33
+ selectedFile: { entryId: EXPLORER_ENTRY.id, path: OPEN_FILE_PATH },
34
+ view: "files",
35
+ onOpenFile: noop,
36
+ onActivateEditor: noop,
37
+ onPinEditor: noop,
38
+ onCloseEditor: noop,
39
+ onCollapse: noop,
40
+ };
41
+
42
+ afterEach(resetWorkspaceAudit);
43
+
44
+ describe("WorkspaceSurface a11y — editor tabs + viewer", () => {
45
+ it.each(COLOR_MODES)("open file, File view (%s)", async (mode) => {
46
+ const container = renderAudited(<WorkspaceSurface {...openEditorProps} />, mode);
47
+ await screen.findByRole("region", { name: "File viewer" });
48
+ // Audit the settled surface: the explorer tree loaded AND the file content
49
+ // rendered (not the skeletons) — both sidebar and editor stable.
50
+ await screen.findByText("README.md");
51
+ await waitFor(() =>
52
+ expect(screen.queryByLabelText("Loading file")).toBeNull(),
53
+ );
54
+ await auditA11y(container, `file view · ${mode}`);
55
+ });
56
+
57
+ it.each(COLOR_MODES)("open file, Diff view (%s)", async (mode) => {
58
+ const container = renderAudited(
59
+ <WorkspaceSurface {...openEditorProps} change={fileChangeForOpenFile()} />,
60
+ mode,
61
+ );
62
+ // A change makes the viewer diff-default and exposes the Diff|File toggle;
63
+ // inline diff content resolves synchronously (no artifact fetch).
64
+ await screen.findByRole("radio", { name: "Diff" });
65
+ // Also wait for the explorer tree to settle so we audit the stable surface.
66
+ await screen.findByText("README.md");
67
+ await auditA11y(container, `diff view · ${mode}`);
68
+ });
69
+ });