@agent-native/core 0.80.9 → 0.80.10
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/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +7 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/cli/design-connect.ts +28 -2
- package/corpus/core/src/cli/skills.ts +45 -24
- package/corpus/templates/design/DESIGN-STUDIO-PLAN.md +717 -0
- package/corpus/templates/design/actions/add-breakpoint.ts +143 -0
- package/corpus/templates/design/actions/add-localhost-screens.ts +5 -0
- package/corpus/templates/design/actions/apply-a11y-fix.ts +317 -0
- package/corpus/templates/design/actions/apply-component-prop-edit.ts +433 -0
- package/corpus/templates/design/actions/apply-design-state.ts +200 -0
- package/corpus/templates/design/actions/apply-design-token-edit.ts +202 -0
- package/corpus/templates/design/actions/apply-motion-edit.ts +450 -0
- package/corpus/templates/design/actions/apply-shader-fill.ts +404 -0
- package/corpus/templates/design/actions/apply-visual-edit.ts +191 -5
- package/corpus/templates/design/actions/capture-design-state.ts +224 -0
- package/corpus/templates/design/actions/connect-builder-app.ts +162 -0
- package/corpus/templates/design/actions/create-component.ts +447 -0
- package/corpus/templates/design/actions/create-design-branch.ts +263 -0
- package/corpus/templates/design/actions/create-design-state.ts +162 -0
- package/corpus/templates/design/actions/delete-design-state.ts +55 -0
- package/corpus/templates/design/actions/deploy-design-preview.ts +275 -0
- package/corpus/templates/design/actions/get-component-details.ts +242 -0
- package/corpus/templates/design/actions/get-design-branch-diff.ts +362 -0
- package/corpus/templates/design/actions/get-design-review.ts +314 -0
- package/corpus/templates/design/actions/get-design-surface-index.ts +582 -0
- package/corpus/templates/design/actions/get-motion-timeline.ts +99 -0
- package/corpus/templates/design/actions/index-components.ts +258 -0
- package/corpus/templates/design/actions/index-design-tokens.ts +273 -0
- package/corpus/templates/design/actions/list-design-extensions.ts +309 -0
- package/corpus/templates/design/actions/list-design-source-capabilities.ts +153 -0
- package/corpus/templates/design/actions/list-design-states.ts +72 -0
- package/corpus/templates/design/actions/migrate-inline-design-to-app.ts +298 -0
- package/corpus/templates/design/actions/open-component-source.ts +237 -0
- package/corpus/templates/design/actions/preview-component-prop-edit.ts +254 -0
- package/corpus/templates/design/actions/preview-design-token-edit.ts +113 -0
- package/corpus/templates/design/actions/preview-shader-fill.ts +189 -0
- package/corpus/templates/design/actions/remove-breakpoint.ts +103 -0
- package/corpus/templates/design/actions/remove-motion-timeline.ts +196 -0
- package/corpus/templates/design/actions/run-design-audit.ts +421 -0
- package/corpus/templates/design/actions/run-design-extension-action.ts +284 -0
- package/corpus/templates/design/actions/set-active-breakpoint.ts +39 -0
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +1414 -71
- package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +632 -43
- package/corpus/templates/design/app/components/design/DrawOverlay.tsx +1 -1
- package/corpus/templates/design/app/components/design/EditPanel.tsx +1391 -52
- package/corpus/templates/design/app/components/design/LayersPanel.tsx +4 -3
- package/corpus/templates/design/app/components/design/LocalSourceEditBanner.tsx +157 -0
- package/corpus/templates/design/app/components/design/MotionDock.tsx +1062 -0
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +1052 -98
- package/corpus/templates/design/app/components/design/ReviewPanel.tsx +759 -0
- package/corpus/templates/design/app/components/design/StatesPanel.tsx +579 -0
- package/corpus/templates/design/app/components/design/TokensPanel.tsx +573 -0
- package/corpus/templates/design/app/components/design/canvas-primitive-style.ts +241 -0
- package/corpus/templates/design/app/components/design/index.ts +16 -0
- package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +15 -6
- package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +66 -3
- package/corpus/templates/design/app/components/design/inspector/InspectorAiActions.tsx +145 -0
- package/corpus/templates/design/app/components/design/inspector/SHADER_INTEGRATION.md +42 -12
- package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +1 -1
- package/corpus/templates/design/app/hooks/useAgentEditRequest.ts +131 -0
- package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +3 -1
- package/corpus/templates/design/app/i18n/zh-TW.ts +31 -0
- package/corpus/templates/design/app/i18n-data.ts +269 -0
- package/corpus/templates/design/app/pages/DesignEditor.tsx +2478 -386
- package/corpus/templates/design/changelog/2026-06-29-added-a-review-panel-in-the-design-editor-s-inspector-with-a.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-copying-and-pasting-layers-no-longer-shows-success-notificat.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-device-presets-in-all-screens-view-resize-the-selected-previ.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-layer-and-canvas-drags-keep-the-layer-list-stable-while-chan.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-layer-moves-can-be-undone-and-redone-without-flashing-the-ca.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-screen-previews-use-a-single-blue-hover-border-in-all-screen.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-selected-containers-show-draggable-padding-and-gap-guides-on.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-the-design-editor-adds-a-studio-layer-with-tokens-respon.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-accessibility-findings-now-offer-a-one-click-fix.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-component-instances-now-have-editable-props-in-the-inspe.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-inspect-code-now-shows-the-elements-opening-tag-at-a-gla.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-shader-fill-presets-can-now-be-applied-to-an-element.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-the-motion-timeline-can-now-add-a-track-to-any-element-a.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-visual-editor-selection-layer-paint-and-drawing-controls-are-more-reliable.md +6 -0
- package/corpus/templates/design/e2e/helpers.ts +10 -4
- package/corpus/templates/design/server/db/schema.ts +123 -0
- package/corpus/templates/design/server/plugins/db.ts +90 -0
- package/corpus/templates/design/shared/builder-app.ts +297 -0
- package/corpus/templates/design/shared/capability-resolver.ts +123 -0
- package/corpus/templates/design/shared/capture-sanitize.ts +70 -0
- package/corpus/templates/design/shared/code-layer.ts +1016 -71
- package/corpus/templates/design/shared/component-model.ts +239 -0
- package/corpus/templates/design/shared/design-review.ts +275 -0
- package/corpus/templates/design/shared/design-source-capabilities.ts +286 -0
- package/corpus/templates/design/shared/design-state.ts +112 -0
- package/corpus/templates/design/shared/design-surface-index.ts +258 -0
- package/corpus/templates/design/shared/motion-compiler.ts +278 -0
- package/corpus/templates/design/shared/motion-timeline.ts +193 -0
- package/corpus/templates/design/shared/responsive-classes.ts +452 -0
- package/corpus/templates/design/shared/shader-fill.ts +323 -0
- package/corpus/templates/design/shared/source-mode.ts +245 -0
- package/corpus/templates/plan/app/components/plan/CanvasArea.tsx +6 -2
- package/corpus/templates/plan/changelog/2026-06-29-plan-canvases-open-without-an-initial-pan-and-zoom-flicker.md +6 -0
- package/dist/cli/design-connect.d.ts.map +1 -1
- package/dist/cli/design-connect.js +28 -2
- package/dist/cli/design-connect.js.map +1 -1
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +37 -21
- package/dist/cli/skills.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +2 -2
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/observability/routes.d.ts +7 -7
- package/dist/resources/handlers.d.ts +2 -2
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component model — pure helpers for detecting and describing component
|
|
3
|
+
* instances from code-layer node data.
|
|
4
|
+
*
|
|
5
|
+
* Two detection strategies:
|
|
6
|
+
*
|
|
7
|
+
* 1. **Alpine / annotated inline** — the design HTML carries
|
|
8
|
+
* `data-agent-native-component="Name"` attributes stamped directly on the
|
|
9
|
+
* DOM element that acts as the component root. Optional sibling attributes
|
|
10
|
+
* `data-agent-native-prop-*` carry prop name/value pairs.
|
|
11
|
+
*
|
|
12
|
+
* 2. **Real-app (localhost / fusion)** — a Vite/Babel transform injects
|
|
13
|
+
* `data-agent-native-component` plus `data-agent-native-prop-*` at
|
|
14
|
+
* build time so instances are discoverable from the rendered HTML. In
|
|
15
|
+
* addition, the `component_index` table holds richer metadata (TS prop
|
|
16
|
+
* types, cva variants, Storybook stories) indexed by `index-components`.
|
|
17
|
+
*
|
|
18
|
+
* This module is **pure** — no DB, no IO. It operates on `CodeLayerNode`
|
|
19
|
+
* objects returned by `buildCodeLayerProjection`.
|
|
20
|
+
*
|
|
21
|
+
* See DESIGN-STUDIO-PLAN.md §6.1 for context.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import type { CodeLayerNode } from "./code-layer";
|
|
25
|
+
|
|
26
|
+
// ─── Component detection attributes ───────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
/** The HTML attribute that marks a DOM node as a component root. */
|
|
29
|
+
export const COMPONENT_NAME_ATTR = "data-agent-native-component";
|
|
30
|
+
|
|
31
|
+
/** Prefix for simple prop attributes stamped next to the component root. */
|
|
32
|
+
export const COMPONENT_PROP_PREFIX = "data-agent-native-prop-";
|
|
33
|
+
|
|
34
|
+
// ─── Extracted prop value ─────────────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
export interface ComponentPropValue {
|
|
37
|
+
/** Prop name derived from the attribute (e.g. `"variant"` from `data-agent-native-prop-variant`). */
|
|
38
|
+
name: string;
|
|
39
|
+
/** Raw attribute value string as found in the HTML. */
|
|
40
|
+
value: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ─── Component instance ───────────────────────────────────────────────────────
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* A component instance detected in the rendered HTML.
|
|
47
|
+
*
|
|
48
|
+
* For Alpine / annotated designs this is the authoritative shape. For
|
|
49
|
+
* real-app sources the `componentIndexId` ties back to the `component_index`
|
|
50
|
+
* row, which carries prop types, variants, and Storybook stories.
|
|
51
|
+
*/
|
|
52
|
+
export interface ComponentInstance {
|
|
53
|
+
/**
|
|
54
|
+
* Stable instance id — the `data-agent-native-node-id` of the root node
|
|
55
|
+
* (or the node's `id` field when the node id attribute is absent).
|
|
56
|
+
*/
|
|
57
|
+
instanceId: string;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Component name as declared in `data-agent-native-component`.
|
|
61
|
+
* E.g. `"PrimaryButton"`, `"HeroCard"`.
|
|
62
|
+
*/
|
|
63
|
+
name: string;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Simple prop values extracted from `data-agent-native-prop-*` attributes.
|
|
67
|
+
* Present on annotated Alpine nodes and on real-app nodes instrumented by
|
|
68
|
+
* the Vite/Babel transform.
|
|
69
|
+
*/
|
|
70
|
+
props: ComponentPropValue[];
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The `x-data` Alpine expression on the node, when present. Used by the
|
|
74
|
+
* Alpine editor to show variant/state controls inline.
|
|
75
|
+
*/
|
|
76
|
+
alpineData?: string;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* CSS selector that uniquely addresses this instance on the canvas.
|
|
80
|
+
* Derived from the node's primary selector.
|
|
81
|
+
*/
|
|
82
|
+
selector: string;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* The `data-agent-native-node-id` value (same as `instanceId` for annotated
|
|
86
|
+
* nodes; provided separately so callers can pass it as a stable handle to
|
|
87
|
+
* other code-layer APIs even when `instanceId` is constructed from a
|
|
88
|
+
* different attribute).
|
|
89
|
+
*/
|
|
90
|
+
nodeId: string;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* `component_index.id` of the matching persisted component row, when
|
|
94
|
+
* available. Populated by `index-components` for real-app sources and used
|
|
95
|
+
* by `get-component-details` to load prop types and variants.
|
|
96
|
+
*/
|
|
97
|
+
componentIndexId?: string;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ─── Detection ────────────────────────────────────────────────────────────────
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Return `true` when the node carries a `data-agent-native-component`
|
|
104
|
+
* annotation — i.e. it is the root of an Alpine or real-app component
|
|
105
|
+
* instance.
|
|
106
|
+
*/
|
|
107
|
+
export function isComponentInstance(node: CodeLayerNode): boolean {
|
|
108
|
+
return typeof node.dataAttributes[COMPONENT_NAME_ATTR] === "string";
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Return the component name declared on the node, or `null` when the node is
|
|
113
|
+
* not a component root.
|
|
114
|
+
*/
|
|
115
|
+
export function componentNameFor(node: CodeLayerNode): string | null {
|
|
116
|
+
const raw = node.dataAttributes[COMPONENT_NAME_ATTR];
|
|
117
|
+
if (typeof raw !== "string" || !raw.trim()) return null;
|
|
118
|
+
return raw.trim();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Extract simple prop values from `data-agent-native-prop-*` attributes on a
|
|
123
|
+
* code-layer node.
|
|
124
|
+
*
|
|
125
|
+
* The attribute names are lower-cased by the HTML parser so we convert
|
|
126
|
+
* `data-agent-native-prop-variant` → `{ name: "variant", value: "..." }`.
|
|
127
|
+
*/
|
|
128
|
+
export function extractProps(node: CodeLayerNode): ComponentPropValue[] {
|
|
129
|
+
const props: ComponentPropValue[] = [];
|
|
130
|
+
for (const [attr, value] of Object.entries(node.dataAttributes)) {
|
|
131
|
+
if (!attr.startsWith(COMPONENT_PROP_PREFIX)) continue;
|
|
132
|
+
const rawName = attr.slice(COMPONENT_PROP_PREFIX.length);
|
|
133
|
+
// Convert kebab-case attribute suffix to camelCase prop name.
|
|
134
|
+
const name = rawName.replace(/-([a-z])/g, (_, c: string) =>
|
|
135
|
+
c.toUpperCase(),
|
|
136
|
+
);
|
|
137
|
+
if (name) props.push({ name, value });
|
|
138
|
+
}
|
|
139
|
+
return props;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Build a `ComponentInstance` from a `CodeLayerNode` that carries a
|
|
144
|
+
* `data-agent-native-component` attribute.
|
|
145
|
+
*
|
|
146
|
+
* Returns `null` when the node is not a component root.
|
|
147
|
+
*/
|
|
148
|
+
export function instanceFromNode(
|
|
149
|
+
node: CodeLayerNode,
|
|
150
|
+
componentIndexId?: string,
|
|
151
|
+
): ComponentInstance | null {
|
|
152
|
+
const name = componentNameFor(node);
|
|
153
|
+
if (!name) return null;
|
|
154
|
+
|
|
155
|
+
// `x-data` is stored as a plain attribute (not a data-* attribute) so we
|
|
156
|
+
// look in `node.attributes` rather than `node.dataAttributes`.
|
|
157
|
+
const alpineDataRaw = node.attributes["x-data"];
|
|
158
|
+
const alpineData =
|
|
159
|
+
typeof alpineDataRaw === "string" ? alpineDataRaw : undefined;
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
instanceId: node.id,
|
|
163
|
+
name,
|
|
164
|
+
props: extractProps(node),
|
|
165
|
+
alpineData,
|
|
166
|
+
selector: node.selector,
|
|
167
|
+
nodeId: node.id,
|
|
168
|
+
componentIndexId,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Scan a flat list of `CodeLayerNode` objects and return all component
|
|
174
|
+
* instances, preserving document order.
|
|
175
|
+
*
|
|
176
|
+
* Accepts an optional `indexMap` — a `Map<name, componentIndexId>` — which is
|
|
177
|
+
* populated by `index-components` after writing to `component_index` and used
|
|
178
|
+
* to correlate instances with their persisted metadata.
|
|
179
|
+
*/
|
|
180
|
+
export function detectInstances(
|
|
181
|
+
nodes: CodeLayerNode[],
|
|
182
|
+
indexMap?: Map<string, string>,
|
|
183
|
+
): ComponentInstance[] {
|
|
184
|
+
const instances: ComponentInstance[] = [];
|
|
185
|
+
for (const node of nodes) {
|
|
186
|
+
if (!isComponentInstance(node)) continue;
|
|
187
|
+
const name = componentNameFor(node);
|
|
188
|
+
if (!name) continue;
|
|
189
|
+
const componentIndexId = indexMap?.get(name);
|
|
190
|
+
const instance = instanceFromNode(node, componentIndexId);
|
|
191
|
+
if (instance) instances.push(instance);
|
|
192
|
+
}
|
|
193
|
+
return instances;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// ─── Unique component definitions ────────────────────────────────────────────
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* A distinct component definition derived by collapsing all instances of the
|
|
200
|
+
* same name into one entry.
|
|
201
|
+
*/
|
|
202
|
+
export interface ComponentDefinition {
|
|
203
|
+
name: string;
|
|
204
|
+
/** All instance node ids for this component. */
|
|
205
|
+
instanceNodeIds: string[];
|
|
206
|
+
/** Union of all prop names seen across all instances. */
|
|
207
|
+
observedPropNames: string[];
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Collapse a flat list of `ComponentInstance` objects into one
|
|
212
|
+
* `ComponentDefinition` per unique component name.
|
|
213
|
+
*/
|
|
214
|
+
export function buildDefinitions(
|
|
215
|
+
instances: ComponentInstance[],
|
|
216
|
+
): ComponentDefinition[] {
|
|
217
|
+
const map = new Map<
|
|
218
|
+
string,
|
|
219
|
+
{ instanceNodeIds: string[]; propNames: Set<string> }
|
|
220
|
+
>();
|
|
221
|
+
|
|
222
|
+
for (const instance of instances) {
|
|
223
|
+
let entry = map.get(instance.name);
|
|
224
|
+
if (!entry) {
|
|
225
|
+
entry = { instanceNodeIds: [], propNames: new Set() };
|
|
226
|
+
map.set(instance.name, entry);
|
|
227
|
+
}
|
|
228
|
+
entry.instanceNodeIds.push(instance.nodeId);
|
|
229
|
+
for (const prop of instance.props) {
|
|
230
|
+
entry.propNames.add(prop.name);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return Array.from(map.entries()).map(([name, entry]) => ({
|
|
235
|
+
name,
|
|
236
|
+
instanceNodeIds: entry.instanceNodeIds,
|
|
237
|
+
observedPropNames: Array.from(entry.propNames),
|
|
238
|
+
}));
|
|
239
|
+
}
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Accessibility audit and visual-diff review types for the Design Studio
|
|
3
|
+
* Review panel (§6.5 + §4.3).
|
|
4
|
+
*
|
|
5
|
+
* Results are produced by `run-design-audit` over the rendered DOM and cached
|
|
6
|
+
* in `design_review_snapshot` rows. Fix actions are capability-gated (semantic
|
|
7
|
+
* code fixes are real-app only).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// Accessibility findings
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
export const A11Y_FINDING_SEVERITIES = ["error", "warning", "info"] as const;
|
|
15
|
+
|
|
16
|
+
export type A11ySeverity = (typeof A11Y_FINDING_SEVERITIES)[number];
|
|
17
|
+
|
|
18
|
+
export const A11Y_FINDING_CATEGORIES = [
|
|
19
|
+
"contrast",
|
|
20
|
+
"tap-target",
|
|
21
|
+
"focus-visibility",
|
|
22
|
+
"missing-label",
|
|
23
|
+
"missing-alt",
|
|
24
|
+
"reduced-motion",
|
|
25
|
+
"role",
|
|
26
|
+
"other",
|
|
27
|
+
] as const;
|
|
28
|
+
|
|
29
|
+
export type A11yFindingCategory = (typeof A11Y_FINDING_CATEGORIES)[number];
|
|
30
|
+
|
|
31
|
+
export interface A11yFinding {
|
|
32
|
+
/** Stable identifier for deduplication and navigation (e.g. `"contrast:node-42"`). */
|
|
33
|
+
id: string;
|
|
34
|
+
severity: A11ySeverity;
|
|
35
|
+
category: A11yFindingCategory;
|
|
36
|
+
/** Short human-readable summary (e.g. "Contrast ratio 2.1:1 — minimum is 4.5:1"). */
|
|
37
|
+
message: string;
|
|
38
|
+
/** Optional longer description or remediation guidance. */
|
|
39
|
+
detail?: string;
|
|
40
|
+
/**
|
|
41
|
+
* The `data-agent-native-node-id` of the offending element, when available.
|
|
42
|
+
* Used to navigate the canvas to the affected layer.
|
|
43
|
+
*/
|
|
44
|
+
nodeId?: string;
|
|
45
|
+
/** CSS selector as a fallback when `nodeId` is absent. */
|
|
46
|
+
selector?: string;
|
|
47
|
+
/** WCAG success criterion reference (e.g. "1.4.3"). */
|
|
48
|
+
wcag?: string;
|
|
49
|
+
/**
|
|
50
|
+
* Whether a fix action is available for this finding.
|
|
51
|
+
* Semantic code fixes are real-app only; contrast/alt fixes may be available
|
|
52
|
+
* in Alpine via the deterministic write path.
|
|
53
|
+
*/
|
|
54
|
+
fixAvailable: boolean;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
// Inline auto-fix mapping
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
//
|
|
61
|
+
// Some a11y findings can be repaired inline against the SQL-backed HTML design
|
|
62
|
+
// content using the same deterministic edit primitives the visual editor uses
|
|
63
|
+
// (`apply-visual-edit`: style / class / textContent). Those primitives can set
|
|
64
|
+
// an inline style value, add/remove/replace a class token, or rewrite leaf text
|
|
65
|
+
// — so the fixes we can apply purely inline are the ones that reduce to one of
|
|
66
|
+
// those operations on a *targeted* node:
|
|
67
|
+
//
|
|
68
|
+
// - contrast / color → set an inline `color` (style edit) or swap a text
|
|
69
|
+
// color class, raising the foreground contrast.
|
|
70
|
+
// - tap-target → add a min-size utility class (e.g. `min-h-[44px]`).
|
|
71
|
+
// - focus-visibility → add a `focus-visible:ring-2` utility class.
|
|
72
|
+
//
|
|
73
|
+
// Fixes that require writing a *new attribute* (alt, aria-label,
|
|
74
|
+
// aria-labelledby) or semantic/structural code changes are NOT expressible
|
|
75
|
+
// through the deterministic edit engine's exported intents, so they remain
|
|
76
|
+
// "real-app only" and are surfaced as informational findings (no inline Fix).
|
|
77
|
+
// `a11yFindingToEdit` returns `null` for those.
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* A single deterministic edit that repairs an a11y finding inline. The shape is
|
|
81
|
+
* a strict subset of the `apply-visual-edit` `EditIntent` union — only the kinds
|
|
82
|
+
* the inline (SQL HTML) edit engine can apply without escalating: `style`,
|
|
83
|
+
* `class`, and `textContent`. The `apply-a11y-fix` action forwards this verbatim
|
|
84
|
+
* to the shared `applyVisualEdit` primitive.
|
|
85
|
+
*/
|
|
86
|
+
export type A11yFixEdit =
|
|
87
|
+
| {
|
|
88
|
+
kind: "style";
|
|
89
|
+
target: { nodeId?: string; selector?: string };
|
|
90
|
+
property: string;
|
|
91
|
+
value: string;
|
|
92
|
+
}
|
|
93
|
+
| {
|
|
94
|
+
kind: "class";
|
|
95
|
+
target: { nodeId?: string; selector?: string };
|
|
96
|
+
operation: "add" | "remove" | "replace";
|
|
97
|
+
className?: string;
|
|
98
|
+
classNames?: string[];
|
|
99
|
+
from?: string;
|
|
100
|
+
to?: string;
|
|
101
|
+
}
|
|
102
|
+
| {
|
|
103
|
+
kind: "textContent";
|
|
104
|
+
target: { nodeId?: string; selector?: string };
|
|
105
|
+
value: string;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* A planned inline fix for a finding: the deterministic edit to apply plus a
|
|
110
|
+
* short human-readable label for the UI / agent.
|
|
111
|
+
*/
|
|
112
|
+
export interface A11yFixPlan {
|
|
113
|
+
finding: A11yFinding;
|
|
114
|
+
edit: A11yFixEdit;
|
|
115
|
+
/** Short human summary, e.g. "Raise text contrast" or "Enlarge tap target". */
|
|
116
|
+
label: string;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* A high-contrast foreground color used as the default contrast remediation
|
|
121
|
+
* when a finding does not carry an explicit replacement color. Near-black keeps
|
|
122
|
+
* ≥ 4.5:1 against typical light backgrounds; the agent can refine afterward.
|
|
123
|
+
*/
|
|
124
|
+
const DEFAULT_CONTRAST_COLOR = "#111827";
|
|
125
|
+
|
|
126
|
+
/** Categories whose default inline fix is a class addition, with the utility. */
|
|
127
|
+
const CLASS_ADD_FIX: Partial<Record<A11yFindingCategory, string>> = {
|
|
128
|
+
"tap-target": "min-h-[44px] min-w-[44px]",
|
|
129
|
+
"focus-visibility": "focus-visible:ring-2",
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Map an {@link A11yFinding} to a deterministic inline {@link A11yFixPlan}, or
|
|
134
|
+
* `null` when the finding is not auto-fixable through the inline edit engine.
|
|
135
|
+
*
|
|
136
|
+
* Pure and dependency-free so both the Review panel (to decide whether to show
|
|
137
|
+
* a "Fix" affordance) and the `apply-a11y-fix` action (to compute the edit)
|
|
138
|
+
* share one source of truth.
|
|
139
|
+
*
|
|
140
|
+
* @param finding The audit finding.
|
|
141
|
+
* @param overrides Optional caller-supplied values — e.g. a chosen replacement
|
|
142
|
+
* `color` for contrast fixes — that win over the defaults.
|
|
143
|
+
*/
|
|
144
|
+
export function a11yFindingToEdit(
|
|
145
|
+
finding: A11yFinding,
|
|
146
|
+
overrides?: { color?: string },
|
|
147
|
+
): A11yFixPlan | null {
|
|
148
|
+
// A target is required for every inline edit — without a node id or selector
|
|
149
|
+
// there is nothing to anchor the deterministic patch to.
|
|
150
|
+
const target =
|
|
151
|
+
finding.nodeId || finding.selector
|
|
152
|
+
? { nodeId: finding.nodeId, selector: finding.selector }
|
|
153
|
+
: null;
|
|
154
|
+
if (!target) return null;
|
|
155
|
+
|
|
156
|
+
if (finding.category === "contrast") {
|
|
157
|
+
const color = (overrides?.color ?? "").trim() || DEFAULT_CONTRAST_COLOR;
|
|
158
|
+
return {
|
|
159
|
+
finding,
|
|
160
|
+
label: "Raise text contrast",
|
|
161
|
+
edit: { kind: "style", target, property: "color", value: color },
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const classToAdd = CLASS_ADD_FIX[finding.category];
|
|
166
|
+
if (classToAdd) {
|
|
167
|
+
return {
|
|
168
|
+
finding,
|
|
169
|
+
label:
|
|
170
|
+
finding.category === "tap-target"
|
|
171
|
+
? "Enlarge tap target"
|
|
172
|
+
: "Add focus-visible ring",
|
|
173
|
+
edit: {
|
|
174
|
+
kind: "class",
|
|
175
|
+
target,
|
|
176
|
+
operation: "add",
|
|
177
|
+
classNames: classToAdd.split(/\s+/).filter(Boolean),
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// missing-alt, missing-label, reduced-motion, role, other → require new
|
|
183
|
+
// attributes or semantic/structural rewrites the inline engine can't express.
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Whether a finding can be auto-fixed inline (i.e. {@link a11yFindingToEdit}
|
|
189
|
+
* returns a plan). Convenience wrapper for UI gating.
|
|
190
|
+
*/
|
|
191
|
+
export function isA11yFindingAutoFixable(finding: A11yFinding): boolean {
|
|
192
|
+
return a11yFindingToEdit(finding) !== null;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// ---------------------------------------------------------------------------
|
|
196
|
+
// Visual diff
|
|
197
|
+
// ---------------------------------------------------------------------------
|
|
198
|
+
|
|
199
|
+
export const VISUAL_DIFF_CHANGE_KINDS = [
|
|
200
|
+
"added",
|
|
201
|
+
"removed",
|
|
202
|
+
"modified",
|
|
203
|
+
"moved",
|
|
204
|
+
] as const;
|
|
205
|
+
|
|
206
|
+
export type VisualDiffChangeKind = (typeof VISUAL_DIFF_CHANGE_KINDS)[number];
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* One changed surface between two design versions.
|
|
210
|
+
*/
|
|
211
|
+
export interface VisualDiffEntry {
|
|
212
|
+
id: string;
|
|
213
|
+
kind: VisualDiffChangeKind;
|
|
214
|
+
/**
|
|
215
|
+
* The `data-agent-native-node-id` of the changed element, when resolvable.
|
|
216
|
+
*/
|
|
217
|
+
nodeId?: string;
|
|
218
|
+
/** CSS selector fallback when `nodeId` is absent. */
|
|
219
|
+
selector?: string;
|
|
220
|
+
/** Human-readable description of the change (e.g. "Background color changed"). */
|
|
221
|
+
description?: string;
|
|
222
|
+
/**
|
|
223
|
+
* Bounding box of the changed region in the before/after screenshot,
|
|
224
|
+
* expressed as fractions [0, 1] of the frame dimensions.
|
|
225
|
+
*/
|
|
226
|
+
region?: {
|
|
227
|
+
x: number;
|
|
228
|
+
y: number;
|
|
229
|
+
width: number;
|
|
230
|
+
height: number;
|
|
231
|
+
};
|
|
232
|
+
/** Before screenshot crop URL or data URL, when available. */
|
|
233
|
+
beforeImageUrl?: string;
|
|
234
|
+
/** After screenshot crop URL or data URL, when available. */
|
|
235
|
+
afterImageUrl?: string;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// ---------------------------------------------------------------------------
|
|
239
|
+
// Review snapshot
|
|
240
|
+
// ---------------------------------------------------------------------------
|
|
241
|
+
|
|
242
|
+
export const DESIGN_REVIEW_STATUSES = [
|
|
243
|
+
"pending",
|
|
244
|
+
"running",
|
|
245
|
+
"done",
|
|
246
|
+
"error",
|
|
247
|
+
] as const;
|
|
248
|
+
|
|
249
|
+
export type DesignReviewStatus = (typeof DESIGN_REVIEW_STATUSES)[number];
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Cached accessibility + visual-diff results for a design, optionally scoped
|
|
253
|
+
* to a base/compare version pair. Stored in `design_review_snapshot` rows.
|
|
254
|
+
*/
|
|
255
|
+
export interface DesignReviewSnapshot {
|
|
256
|
+
id: string;
|
|
257
|
+
designId: string;
|
|
258
|
+
/**
|
|
259
|
+
* Opaque source reference identifying the screen or file this snapshot
|
|
260
|
+
* covers (fileId for inline, routeId for localhost/fusion).
|
|
261
|
+
* `null` when the snapshot covers the entire design.
|
|
262
|
+
*/
|
|
263
|
+
sourceRef: string | null;
|
|
264
|
+
/** The older `design_versions` id used as the diff base. `null` for a11y-only runs. */
|
|
265
|
+
baseVersionId: string | null;
|
|
266
|
+
/** The newer `design_versions` id being compared against `baseVersionId`. */
|
|
267
|
+
compareVersionId: string | null;
|
|
268
|
+
a11yFindings: A11yFinding[];
|
|
269
|
+
visualDiff: VisualDiffEntry[];
|
|
270
|
+
status: DesignReviewStatus;
|
|
271
|
+
/** Error message when `status` is `"error"`. */
|
|
272
|
+
errorMessage?: string;
|
|
273
|
+
createdAt: string;
|
|
274
|
+
updatedAt: string;
|
|
275
|
+
}
|