@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.
- package/composer/SessionComposer.d.ts +37 -5
- package/composer/SessionComposer.d.ts.map +1 -1
- package/composer/SessionComposer.js +13 -5
- package/composer/SessionComposer.js.map +1 -1
- package/execution/ArtifactApplyButton.d.ts +25 -0
- package/execution/ArtifactApplyButton.d.ts.map +1 -0
- package/execution/ArtifactApplyButton.js +21 -0
- package/execution/ArtifactApplyButton.js.map +1 -0
- package/execution/ArtifactContentBody.d.ts +36 -0
- package/execution/ArtifactContentBody.d.ts.map +1 -0
- package/execution/ArtifactContentBody.js +62 -0
- package/execution/ArtifactContentBody.js.map +1 -0
- package/execution/ArtifactDocument.d.ts +48 -0
- package/execution/ArtifactDocument.d.ts.map +1 -0
- package/execution/ArtifactDocument.js +89 -0
- package/execution/ArtifactDocument.js.map +1 -0
- package/execution/ArtifactPreviewModal.d.ts +9 -9
- package/execution/ArtifactPreviewModal.d.ts.map +1 -1
- package/execution/ArtifactPreviewModal.js +18 -145
- package/execution/ArtifactPreviewModal.js.map +1 -1
- package/execution/ArtifactRow.d.ts +49 -0
- package/execution/ArtifactRow.d.ts.map +1 -0
- package/execution/ArtifactRow.js +56 -0
- package/execution/ArtifactRow.js.map +1 -0
- package/execution/ArtifactsWidget.d.ts +6 -9
- package/execution/ArtifactsWidget.d.ts.map +1 -1
- package/execution/ArtifactsWidget.js +15 -19
- package/execution/ArtifactsWidget.js.map +1 -1
- package/execution/FileChangeProgressBar.d.ts +47 -0
- package/execution/FileChangeProgressBar.d.ts.map +1 -0
- package/execution/FileChangeProgressBar.js +49 -0
- package/execution/FileChangeProgressBar.js.map +1 -0
- package/execution/FileChangesView.d.ts.map +1 -1
- package/execution/FileChangesView.js +19 -4
- package/execution/FileChangesView.js.map +1 -1
- package/execution/FileReviewAtoms.d.ts +27 -0
- package/execution/FileReviewAtoms.d.ts.map +1 -0
- package/execution/FileReviewAtoms.js +64 -0
- package/execution/FileReviewAtoms.js.map +1 -0
- package/execution/FileReviewCard.d.ts +2 -2
- package/execution/FileReviewCard.d.ts.map +1 -1
- package/execution/FileReviewCard.js +32 -62
- package/execution/FileReviewCard.js.map +1 -1
- package/execution/MessageEntry.d.ts +0 -9
- package/execution/MessageEntry.d.ts.map +1 -1
- package/execution/MessageEntry.js +1 -11
- package/execution/MessageEntry.js.map +1 -1
- package/execution/MessageThread.d.ts +3 -4
- package/execution/MessageThread.d.ts.map +1 -1
- package/execution/MessageThread.js +25 -11
- package/execution/MessageThread.js.map +1 -1
- package/execution/PlanArtifactCard.d.ts +16 -10
- package/execution/PlanArtifactCard.d.ts.map +1 -1
- package/execution/PlanArtifactCard.js +31 -7
- package/execution/PlanArtifactCard.js.map +1 -1
- package/execution/PlanCompletionCard.d.ts +4 -4
- package/execution/PlanCompletionCard.d.ts.map +1 -1
- package/execution/PlanCompletionCard.js +4 -4
- package/execution/PlanCompletionCard.js.map +1 -1
- package/execution/PlanStreamingCard.d.ts +7 -2
- package/execution/PlanStreamingCard.d.ts.map +1 -1
- package/execution/PlanStreamingCard.js +11 -6
- package/execution/PlanStreamingCard.js.map +1 -1
- package/execution/index.d.ts +12 -2
- package/execution/index.d.ts.map +1 -1
- package/execution/index.js +6 -1
- package/execution/index.js.map +1 -1
- package/execution/use-build-from-plan-hotkey.d.ts +1 -1
- package/execution/use-build-from-plan-hotkey.js +1 -1
- package/execution/useArtifactContent.d.ts +6 -1
- package/execution/useArtifactContent.d.ts.map +1 -1
- package/execution/useArtifactContent.js +7 -2
- package/execution/useArtifactContent.js.map +1 -1
- package/execution/useArtifactCopy.d.ts +51 -0
- package/execution/useArtifactCopy.d.ts.map +1 -0
- package/execution/useArtifactCopy.js +75 -0
- package/execution/useArtifactCopy.js.map +1 -0
- package/execution/useArtifactDownload.d.ts.map +1 -1
- package/execution/useArtifactDownload.js +17 -5
- package/execution/useArtifactDownload.js.map +1 -1
- package/execution/useArtifactInspection.d.ts +85 -0
- package/execution/useArtifactInspection.d.ts.map +1 -0
- package/execution/useArtifactInspection.js +143 -0
- package/execution/useArtifactInspection.js.map +1 -0
- package/execution/useCreateAgentExecution.d.ts +16 -3
- package/execution/useCreateAgentExecution.d.ts.map +1 -1
- package/execution/useCreateAgentExecution.js +1 -0
- package/execution/useCreateAgentExecution.js.map +1 -1
- package/index.d.ts +4 -4
- package/index.d.ts.map +1 -1
- package/index.js +3 -3
- package/index.js.map +1 -1
- package/internal/MermaidDiagram.d.ts +47 -0
- package/internal/MermaidDiagram.d.ts.map +1 -0
- package/internal/MermaidDiagram.js +118 -0
- package/internal/MermaidDiagram.js.map +1 -0
- package/internal/file-tree/FileTreeNode.d.ts.map +1 -1
- package/internal/file-tree/FileTreeNode.js +2 -2
- package/internal/file-tree/FileTreeNode.js.map +1 -1
- package/internal/markdown-components.d.ts.map +1 -1
- package/internal/markdown-components.js +34 -0
- package/internal/markdown-components.js.map +1 -1
- package/internal/mermaid-loader.d.ts +26 -0
- package/internal/mermaid-loader.d.ts.map +1 -0
- package/internal/mermaid-loader.js +34 -0
- package/internal/mermaid-loader.js.map +1 -0
- package/internal/mermaid-theme-css.d.ts +41 -0
- package/internal/mermaid-theme-css.d.ts.map +1 -0
- package/internal/mermaid-theme-css.js +179 -0
- package/internal/mermaid-theme-css.js.map +1 -0
- package/internal/slug.d.ts +9 -5
- package/internal/slug.d.ts.map +1 -1
- package/internal/slug.js +10 -6
- package/internal/slug.js.map +1 -1
- package/library/detect-plan-artifact.d.ts +27 -9
- package/library/detect-plan-artifact.d.ts.map +1 -1
- package/library/detect-plan-artifact.js +26 -6
- package/library/detect-plan-artifact.js.map +1 -1
- package/library/index.d.ts +1 -1
- package/library/index.d.ts.map +1 -1
- package/library/index.js +1 -1
- package/library/index.js.map +1 -1
- package/package.json +5 -4
- package/session/PlanEditor.js +1 -1
- package/session/PlanEditor.js.map +1 -1
- package/session/SessionViewer.d.ts.map +1 -1
- package/session/SessionViewer.js +122 -30
- package/session/SessionViewer.js.map +1 -1
- package/session/artifact-document.d.ts +13 -0
- package/session/artifact-document.d.ts.map +1 -0
- package/session/artifact-document.js +22 -0
- package/session/artifact-document.js.map +1 -0
- package/session/facets/ArtifactsTab.d.ts +30 -4
- package/session/facets/ArtifactsTab.d.ts.map +1 -1
- package/session/facets/ArtifactsTab.js +32 -10
- package/session/facets/ArtifactsTab.js.map +1 -1
- package/session/index.d.ts +2 -1
- package/session/index.d.ts.map +1 -1
- package/session/index.js +4 -1
- package/session/index.js.map +1 -1
- package/session/plan-document.d.ts +14 -2
- package/session/plan-document.d.ts.map +1 -1
- package/session/plan-document.js +14 -3
- package/session/plan-document.js.map +1 -1
- package/session/usePlanDraft.d.ts +1 -1
- package/session/useSessionArtifacts.d.ts +17 -0
- package/session/useSessionArtifacts.d.ts.map +1 -1
- package/session/useSessionArtifacts.js +16 -5
- package/session/useSessionArtifacts.js.map +1 -1
- package/session/useSessionConversation.d.ts +26 -3
- package/session/useSessionConversation.d.ts.map +1 -1
- package/session/useSessionConversation.js +51 -6
- package/session/useSessionConversation.js.map +1 -1
- package/session/useSessionPageFlow.d.ts.map +1 -1
- package/session/useSessionPageFlow.js +1 -0
- package/session/useSessionPageFlow.js.map +1 -1
- package/session/useSessionPanel.d.ts +20 -0
- package/session/useSessionPanel.d.ts.map +1 -1
- package/session/useSessionPanel.js +13 -0
- package/session/useSessionPanel.js.map +1 -1
- package/session/useSessionRailViews.d.ts +13 -1
- package/session/useSessionRailViews.d.ts.map +1 -1
- package/session/useSessionRailViews.js +5 -3
- package/session/useSessionRailViews.js.map +1 -1
- package/src/composer/SessionComposer.tsx +76 -7
- package/src/composer/__tests__/SessionComposer-editing.test.tsx +128 -0
- package/src/execution/ArtifactApplyButton.tsx +79 -0
- package/src/execution/ArtifactContentBody.tsx +255 -0
- package/src/execution/ArtifactDocument.tsx +344 -0
- package/src/execution/ArtifactPreviewModal.tsx +49 -415
- package/src/execution/ArtifactRow.tsx +165 -0
- package/src/execution/ArtifactsWidget.tsx +24 -33
- package/src/execution/FileChangeProgressBar.tsx +121 -0
- package/src/execution/FileChangesView.tsx +18 -4
- package/src/execution/FileReviewAtoms.tsx +89 -0
- package/src/execution/FileReviewCard.tsx +57 -90
- package/src/execution/MessageEntry.tsx +0 -62
- package/src/execution/MessageThread.tsx +29 -15
- package/src/execution/PlanArtifactCard.tsx +130 -52
- package/src/execution/PlanCompletionCard.tsx +5 -5
- package/src/execution/PlanStreamingCard.tsx +15 -11
- package/src/execution/__tests__/ArtifactContentBody.test.tsx +131 -0
- package/src/execution/__tests__/ArtifactDocument.test.tsx +100 -0
- package/src/execution/__tests__/ArtifactPreviewModal.test.tsx +6 -6
- package/src/execution/__tests__/ArtifactRow.test.tsx +139 -0
- package/src/execution/__tests__/ArtifactsWidget.test.tsx +78 -0
- package/src/execution/__tests__/FileChangeProgressBar.test.tsx +125 -0
- package/src/execution/__tests__/FileReviewCard.test.tsx +66 -7
- package/src/execution/__tests__/MessageThread.test.tsx +1 -1
- package/src/execution/__tests__/PlanArtifactCard.test.tsx +52 -18
- package/src/execution/__tests__/PlanCompletionCard.test.tsx +2 -2
- package/src/execution/__tests__/PlanStreamingCard.test.tsx +11 -4
- package/src/execution/__tests__/buildThreadItems-plan-completion.test.ts +104 -8
- package/src/execution/__tests__/message-entry.test.tsx +56 -39
- package/src/execution/__tests__/useArtifactCopy.test.tsx +90 -0
- package/src/execution/__tests__/useArtifactDownload.test.tsx +2 -0
- package/src/execution/__tests__/useArtifactInspection.test.tsx +151 -0
- package/src/execution/__tests__/useCreateAgentExecution.test.tsx +35 -0
- package/src/execution/index.ts +20 -2
- package/src/execution/use-build-from-plan-hotkey.ts +1 -1
- package/src/execution/useArtifactContent.ts +7 -0
- package/src/execution/useArtifactCopy.ts +109 -0
- package/src/execution/useArtifactDownload.ts +17 -5
- package/src/execution/useArtifactInspection.ts +252 -0
- package/src/execution/useCreateAgentExecution.ts +17 -3
- package/src/index.ts +17 -2
- package/src/internal/MermaidDiagram.tsx +164 -0
- package/src/internal/__tests__/a11y/mermaid-theme.a11y.test.tsx +122 -0
- package/src/internal/__tests__/markdown-components.test.tsx +93 -1
- package/src/internal/__tests__/mermaid-diagram.test.tsx +282 -0
- package/src/internal/file-tree/FileTreeNode.tsx +13 -2
- package/src/internal/markdown-components.tsx +36 -1
- package/src/internal/mermaid-loader.ts +44 -0
- package/src/internal/mermaid-theme-css.ts +178 -0
- package/src/internal/slug.ts +10 -6
- package/src/library/__tests__/detect-plan-artifact.test.ts +21 -0
- package/src/library/detect-plan-artifact.ts +32 -10
- package/src/library/index.ts +2 -0
- package/src/session/PlanEditor.tsx +1 -1
- package/src/session/SessionViewer.tsx +227 -51
- package/src/session/__tests__/PlanEditor.test.tsx +4 -4
- package/src/session/__tests__/PlanStreamingDocument.test.tsx +1 -1
- package/src/session/__tests__/SessionViewer-artifactTabs.test.tsx +210 -0
- package/src/session/__tests__/SessionViewer-editResubmit.test.tsx +233 -0
- package/src/session/__tests__/SessionViewer-fileReviewDock.test.tsx +57 -0
- package/src/session/__tests__/useSessionArtifacts.test.ts +57 -0
- package/src/session/__tests__/useSessionConversation.test.tsx +216 -1
- package/src/session/__tests__/useSessionPanel.test.ts +70 -1
- package/src/session/artifact-document.ts +23 -0
- package/src/session/facets/ArtifactsTab.tsx +66 -18
- package/src/session/facets/__tests__/ArtifactsTab.test.tsx +111 -47
- package/src/session/index.ts +5 -1
- package/src/session/plan-document.ts +14 -3
- package/src/session/usePlanDraft.ts +1 -1
- package/src/session/useSessionArtifacts.ts +16 -5
- package/src/session/useSessionConversation.ts +80 -10
- package/src/session/useSessionPageFlow.ts +1 -0
- package/src/session/useSessionPanel.ts +41 -0
- package/src/session/useSessionRailViews.tsx +21 -1
- package/src/workspace/EditorTabs.tsx +38 -9
- package/src/workspace/ExplorerTree.tsx +10 -4
- package/src/workspace/FileViewer.tsx +27 -6
- package/src/workspace/WorkspaceContentSearch.tsx +44 -20
- package/src/workspace/WorkspaceEditor.tsx +19 -16
- package/src/workspace/WorkspaceFileSearch.tsx +51 -28
- package/src/workspace/WorkspaceSurface.tsx +106 -48
- package/src/workspace/__tests__/EditorTabs.test.tsx +4 -2
- package/src/workspace/__tests__/ExplorerTree.test.tsx +52 -0
- package/src/workspace/__tests__/FileViewer.test.tsx +18 -0
- package/src/workspace/__tests__/WorkspaceContentSearch.test.tsx +38 -5
- package/src/workspace/__tests__/WorkspaceEditor-initialPanel.test.tsx +19 -0
- package/src/workspace/__tests__/WorkspaceFileSearch.test.tsx +27 -6
- package/src/workspace/__tests__/WorkspaceSurface.test.tsx +70 -0
- package/src/workspace/__tests__/a11y/harness.tsx +140 -0
- package/src/workspace/__tests__/a11y/mock-capabilities.ts +138 -0
- package/src/workspace/__tests__/a11y/workspace-editor.a11y.test.tsx +65 -0
- package/src/workspace/__tests__/a11y/workspace-explorer-search.a11y.test.tsx +112 -0
- package/src/workspace/__tests__/a11y/workspace-viewer-tabs.a11y.test.tsx +69 -0
- package/styles.css +1 -1
- package/workspace/EditorTabs.d.ts +21 -3
- package/workspace/EditorTabs.d.ts.map +1 -1
- package/workspace/EditorTabs.js +18 -6
- package/workspace/EditorTabs.js.map +1 -1
- package/workspace/ExplorerTree.js +8 -4
- package/workspace/ExplorerTree.js.map +1 -1
- package/workspace/FileViewer.d.ts.map +1 -1
- package/workspace/FileViewer.js +19 -8
- package/workspace/FileViewer.js.map +1 -1
- package/workspace/WorkspaceContentSearch.d.ts.map +1 -1
- package/workspace/WorkspaceContentSearch.js +23 -2
- package/workspace/WorkspaceContentSearch.js.map +1 -1
- package/workspace/WorkspaceEditor.d.ts.map +1 -1
- package/workspace/WorkspaceEditor.js +8 -10
- package/workspace/WorkspaceEditor.js.map +1 -1
- package/workspace/WorkspaceFileSearch.d.ts.map +1 -1
- package/workspace/WorkspaceFileSearch.js +23 -3
- package/workspace/WorkspaceFileSearch.js.map +1 -1
- package/workspace/WorkspaceSurface.d.ts.map +1 -1
- package/workspace/WorkspaceSurface.js +54 -18
- package/workspace/WorkspaceSurface.js.map +1 -1
- package/execution/ArtifactCard.d.ts +0 -81
- package/execution/ArtifactCard.d.ts.map +0 -1
- package/execution/ArtifactCard.js +0 -130
- package/execution/ArtifactCard.js.map +0 -1
- 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
|
+
});
|