@pixerate/editor 0.223.6 → 0.223.8

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 (23) hide show
  1. package/dist/tools/skill-evals.d.cts +23 -1
  2. package/dist/tools/skill-evals.d.ts +23 -1
  3. package/package.json +1 -1
  4. package/skills/README.md +8 -3
  5. package/skills/pixerate-accessibility-audit/SKILL.md +98 -0
  6. package/skills/pixerate-accessibility-audit/evals/button-label-contrast.json +97 -0
  7. package/skills/pixerate-accessibility-audit/evals/distinct-for-cvd.json +160 -0
  8. package/skills/pixerate-accessibility-audit/evals/icon-non-text-contrast.json +109 -0
  9. package/skills/pixerate-brand-application/SKILL.md +99 -0
  10. package/skills/pixerate-brand-application/evals/apply-palette.json +148 -0
  11. package/skills/pixerate-brand-application/evals/readable-on-brand.json +104 -0
  12. package/skills/pixerate-brand-application/evals/snap-to-palette.json +148 -0
  13. package/skills/pixerate-color/SKILL.md +8 -0
  14. package/skills/pixerate-effect-authoring/SKILL.md +7 -0
  15. package/skills/pixerate-effect-look-development/SKILL.md +96 -0
  16. package/skills/pixerate-effect-look-development/evals/fade-in.json +55 -0
  17. package/skills/pixerate-effect-look-development/evals/seamless-loop.json +66 -0
  18. package/skills/pixerate-effect-look-development/evals/subtle-preset.json +49 -0
  19. package/skills/pixerate-generation/SKILL.md +7 -0
  20. package/skills/pixerate-generation-budgeted-batch/SKILL.md +99 -0
  21. package/skills/pixerate-generation-budgeted-batch/evals/drafts-within-budget.json +101 -0
  22. package/skills/pixerate-generation-budgeted-batch/evals/right-model-for-format.json +124 -0
  23. package/skills/pixerate-generation-budgeted-batch/evals/vary-earlier.json +101 -0
@@ -20,7 +20,7 @@ export interface SkillEvalCheck extends SkillEvalToolCall {
20
20
  }
21
21
  export interface SkillEval {
22
22
  prompt: string;
23
- fixture: SpreadsheetEvalFixture | ImageEvalFixture | VectorEvalFixture | PaintEvalFixture | VideoEvalFixture | ChartEvalFixture | DiagramEvalFixture | RichTextEvalFixture | AnimationEvalFixture;
23
+ fixture: SpreadsheetEvalFixture | ImageEvalFixture | VectorEvalFixture | PaintEvalFixture | VideoEvalFixture | ChartEvalFixture | DiagramEvalFixture | RichTextEvalFixture | AnimationEvalFixture | GenerationEvalFixture | EffectEvalFixture;
24
24
  checks: SkillEvalCheck[];
25
25
  solution: SkillEvalToolCall[];
26
26
  }
@@ -54,6 +54,8 @@ export interface VectorEvalFixture {
54
54
  id: string;
55
55
  params?: Record<string, unknown>;
56
56
  }>;
57
+ /** Also register the colour tools (`check_color_contrast`, `check_color_palette`, ...). */
58
+ colorTools?: boolean;
57
59
  }
58
60
  export interface PaintEvalFixture {
59
61
  editor: 'paint';
@@ -87,6 +89,26 @@ export interface AnimationEvalFixture {
87
89
  /** Tool calls that build the starting scene (`set_animation_artwork_svg`, `insert_shots`), run in order. */
88
90
  setup?: SkillEvalToolCall[];
89
91
  }
92
+ export interface GenerationEvalFixture {
93
+ editor: 'generation';
94
+ /** The host's model catalogue (`ModelCapabilities` records). */
95
+ models: Array<Record<string, unknown>>;
96
+ /** Price in USD per output, by `quality` (`default` for requests without one); adds `estimate_generation`. */
97
+ prices?: Record<string, number>;
98
+ /** Earlier generations in the history: a model id and the request it ran. */
99
+ history?: Array<{
100
+ id: string;
101
+ model: string;
102
+ request: Record<string, unknown>;
103
+ }>;
104
+ }
105
+ export interface EffectEvalFixture {
106
+ editor: 'effect';
107
+ /** The fake effect provider's effect to author (e.g. `fake.invert`). */
108
+ effectId: string;
109
+ /** Tool calls that prepare the session, run in order. */
110
+ setup?: SkillEvalToolCall[];
111
+ }
90
112
  /** One eval file of a skill. */
91
113
  export interface SkillEvalFile {
92
114
  skill: string;
@@ -20,7 +20,7 @@ export interface SkillEvalCheck extends SkillEvalToolCall {
20
20
  }
21
21
  export interface SkillEval {
22
22
  prompt: string;
23
- fixture: SpreadsheetEvalFixture | ImageEvalFixture | VectorEvalFixture | PaintEvalFixture | VideoEvalFixture | ChartEvalFixture | DiagramEvalFixture | RichTextEvalFixture | AnimationEvalFixture;
23
+ fixture: SpreadsheetEvalFixture | ImageEvalFixture | VectorEvalFixture | PaintEvalFixture | VideoEvalFixture | ChartEvalFixture | DiagramEvalFixture | RichTextEvalFixture | AnimationEvalFixture | GenerationEvalFixture | EffectEvalFixture;
24
24
  checks: SkillEvalCheck[];
25
25
  solution: SkillEvalToolCall[];
26
26
  }
@@ -54,6 +54,8 @@ export interface VectorEvalFixture {
54
54
  id: string;
55
55
  params?: Record<string, unknown>;
56
56
  }>;
57
+ /** Also register the colour tools (`check_color_contrast`, `check_color_palette`, ...). */
58
+ colorTools?: boolean;
57
59
  }
58
60
  export interface PaintEvalFixture {
59
61
  editor: 'paint';
@@ -87,6 +89,26 @@ export interface AnimationEvalFixture {
87
89
  /** Tool calls that build the starting scene (`set_animation_artwork_svg`, `insert_shots`), run in order. */
88
90
  setup?: SkillEvalToolCall[];
89
91
  }
92
+ export interface GenerationEvalFixture {
93
+ editor: 'generation';
94
+ /** The host's model catalogue (`ModelCapabilities` records). */
95
+ models: Array<Record<string, unknown>>;
96
+ /** Price in USD per output, by `quality` (`default` for requests without one); adds `estimate_generation`. */
97
+ prices?: Record<string, number>;
98
+ /** Earlier generations in the history: a model id and the request it ran. */
99
+ history?: Array<{
100
+ id: string;
101
+ model: string;
102
+ request: Record<string, unknown>;
103
+ }>;
104
+ }
105
+ export interface EffectEvalFixture {
106
+ editor: 'effect';
107
+ /** The fake effect provider's effect to author (e.g. `fake.invert`). */
108
+ effectId: string;
109
+ /** Tool calls that prepare the session, run in order. */
110
+ setup?: SkillEvalToolCall[];
111
+ }
90
112
  /** One eval file of a skill. */
91
113
  export interface SkillEvalFile {
92
114
  skill: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pixerate/editor",
3
- "version": "0.223.6",
3
+ "version": "0.223.8",
4
4
  "description": "UI-agnostic rich text and media editing core library",
5
5
  "author": "Pixerate",
6
6
  "license": "MIT",
package/skills/README.md CHANGED
@@ -63,6 +63,10 @@ request like "clean up this export" finds it.
63
63
  | [`pixerate-rich-text-revise`](pixerate-rich-text-revise/SKILL.md) | Rich text (`pixerate-rich-text`) | Proofreading, renaming terms with exceptions and tightening, as minimal targeted edits. |
64
64
  | [`pixerate-animation-motion-graphics`](pixerate-animation-motion-graphics/SKILL.md) | Animation (`pixerate-animation-editor`) | One idea per shot, staggered entrances, natural easing, holds to read, fitting a length, checks and export. |
65
65
  | [`pixerate-animation-logo-reveal`](pixerate-animation-logo-reveal/SKILL.md) | Animation (`pixerate-animation-editor`) | A short reveal built from the logo's parts, landing on the beat and ending on the held, finished logo. |
66
+ | [`pixerate-generation-budgeted-batch`](pixerate-generation-budgeted-batch/SKILL.md) | Generation (`pixerate-generation`) | The right model for the format, plan and estimate before spending, drafts cheap and finals once, safe retries, variations from the history. |
67
+ | [`pixerate-effect-look-development`](pixerate-effect-look-development/SKILL.md) | Effects (`pixerate-effect-authoring`) | Tune a look in a few moves, save it as a preset, animate with eased keyframes, seamless loops. |
68
+ | [`pixerate-accessibility-audit`](pixerate-accessibility-audit/SKILL.md) | Cross-domain (`pixerate-color`) | Text and non-text contrast, colour-blind-safe meaning, both modes, smallest fixes and a report, in any editor. |
69
+ | [`pixerate-brand-application`](pixerate-brand-application/SKILL.md) | Cross-domain (`pixerate-color`) | Brand roles mapped to exact palette values, near-misses snapped, readable text on brand colours, derivatives and house-style checks. |
66
70
  | [`pixerate-talking-head-edit`](pixerate-talking-head-edit/SKILL.md) | Video (`pixerate-video-editor`) | Talking-head cleanup by transcript: fillers, long pauses and stutters found in one pass and cut as one plan reviewed before it applies. Owns the dialogue tools. |
67
71
  | [`pixerate-bgm-alignment`](pixerate-bgm-alignment/SKILL.md) | Video (`pixerate-video-editor`) | Background music: beats and cuts on the beat, ducking under speech, loudness in LUFS. Owns the music tools. |
68
72
 
@@ -126,11 +130,12 @@ same change.
126
130
  `packages/core/tests/agent-skill-evals.test.ts`. An eval has a `prompt` (the task, in the
127
131
  user's words), a `fixture` (the starting document: `{ "editor": "spreadsheet", "rows" }`,
128
132
  `{ "editor": "image", "width", "height", "background", "shapes" }` or `{ "editor":
129
- "vector", "width", "height", "setup" }` with commands that draw the starting art,
133
+ "vector", "width", "height", "setup", "colorTools" }` with commands that draw the starting art,
130
134
  `{ "editor": "paint", "width", "height", "setup" }` `{ "editor": "video", "setup" }` or `{ "editor":
131
135
  "chart", "setup" }` `{ "editor": "diagram", "setup" }` with tool calls,
132
- `{ "editor": "rich-text", "html" }`, or
133
- `{ "editor": "animation", "setup" }`), `checks` (a read-only
136
+ `{ "editor": "rich-text", "html" }`,
137
+ `{ "editor": "animation", "setup" }`, `{ "editor": "generation", "models", "prices", "history" }`
138
+ (a fake host that records each generation) or `{ "editor": "effect", "effectId", "setup" }`), `checks` (a read-only
134
139
  tool, or `run_command` with a read-only command such as `vector.list`, its `args`, what its result must match in `expect`, and `why`) and a `solution` (the
135
140
  tool calls an expert would make). The test proves each eval: some check fails on the fixture,
136
141
  and all pass after the solution. A model harness runs the same file with the model in place
@@ -0,0 +1,98 @@
1
+ ---
2
+ name: pixerate-accessibility-audit
3
+ description: The accessibility audit workflow across Pixerate editors - checking text contrast (WCAG AA 4.5:1, large text 3:1), non-text contrast for icons, borders and controls (3:1), colour-only meaning and palettes under colour-vision deficiencies, light and dark modes, type readability, and fixing what fails with the smallest change, then reporting what was checked and changed. Use when asked to audit, check or fix accessibility, contrast, legibility, readability or colour-blind safety of a design, chart, icon, document, UI or brand palette, or to meet WCAG, or when check_color_contrast, check_color_palette or check_dual_color_contrast are available. Load with pixerate-color, which lists the colour tools, and the skill for the editor that holds the design.
4
+ metadata:
5
+ kind: workflow
6
+ reference: pixerate-color
7
+ ---
8
+
9
+ # Auditing accessibility
10
+
11
+ An accessibility audit finds what some readers cannot see or tell apart, fixes it with the
12
+ smallest change that keeps the design's intent, and reports both. Measure, never eyeball:
13
+ every text and background pair gets a contrast ratio, every set of colours that carries meaning
14
+ gets a colour-vision check, and every fix is measured again.
15
+
16
+ This skill is the method. `pixerate-color` has the colour tools; read colours from the design
17
+ with its editor's tools (`vector.list` in the vector editor, `get_chart` and
18
+ `check_chart_colors` for charts, `measure_region` for an image).
19
+
20
+ ## Goal and quality bar
21
+
22
+ - Normal text at least 4.5:1 against its background, large text (24 px, or 18.5 px bold) at
23
+ least 3:1 (`check_color_contrast`, with `large: true` for large text).
24
+ - Icons, borders, focus rings, chart lines and control outlines at least 3:1 against what they
25
+ sit on.
26
+ - Colours that carry meaning (status, series, categories) distinct under protanopia,
27
+ deuteranopia and tritanopia (`check_color_palette` `ok: true`), and not the only cue (add a
28
+ label, shape or pattern where meaning matters).
29
+ - Both light and dark modes pass when the design has them (`check_dual_color_contrast`).
30
+ - Fixes change as little as possible: the text colour rather than the brand background, a
31
+ darker shade of the same hue rather than a new hue.
32
+ - A report lists each pair checked, its ratio before and after, and what changed.
33
+
34
+ ## Inputs to confirm
35
+
36
+ - **The standard:** WCAG 2 AA (the default) or AAA.
37
+ - **What may change:** usually text and icon colours; brand backgrounds and logos only with
38
+ approval.
39
+ - **Modes:** light, dark or both.
40
+
41
+ ## Working method
42
+
43
+ 1. **Inventory.** Read every foreground and background pair from the design's editor: text on
44
+ surfaces, icons on cards, lines on plots, status colours.
45
+ **Check:** each pair has a role (normal text, large text, graphic, decorative).
46
+ 2. **Measure.** `check_color_contrast` for each pair (`large: true` for large text);
47
+ `check_color_palette` for sets of meaningful colours; `check_dual_color_contrast` for dual
48
+ colours.
49
+ **Check:** a list of failures with their ratios.
50
+ 3. **Fix the smallest thing.** Use the `suggestion` the contrast check returns (a lightness that
51
+ passes), or a darker or lighter shade of the same hue; for palettes, replace the clashing
52
+ colours with ones that differ in lightness as well as hue (blue and orange rather than red and
53
+ green). Apply with the editor's own tools (`vector.setStyle`, `update_chart_series`).
54
+ **Check:** measure the pair again; it passes.
55
+ 4. **Meaning without colour.** Where a palette carries meaning, make sure a label, icon or shape
56
+ carries it too.
57
+ 5. **Report** pairs, ratios before and after, and anything left that needs a decision (a brand
58
+ colour that cannot pass with any text colour).
59
+
60
+ ## Decision table
61
+
62
+ | Element | Requirement | Tool |
63
+ | --- | --- | --- |
64
+ | Body text, labels | 4.5:1 | `check_color_contrast` |
65
+ | Large or bold headings | 3:1 | `check_color_contrast` with `large: true` |
66
+ | Icons, borders, focus rings, chart lines | 3:1 | `check_color_contrast` |
67
+ | Status or series colours | Distinct under CVD | `check_color_palette` |
68
+ | Light and dark themes | Both pass | `check_dual_color_contrast` |
69
+ | Decorative graphics, disabled controls, logos | No requirement | Note them as exempt |
70
+
71
+ ## Recipes
72
+
73
+ **Fix a button label.** `check_color_contrast` `{ foreground: label, background: button }` →
74
+ use its suggested lightness or a dark shade of the button's hue → apply → check again.
75
+
76
+ **Colour-blind-safe status colours.** `check_color_palette` with the three colours → replace with
77
+ a set that varies in lightness (`#0072b2`, `#e69f00`, `#000000`) → check again; keep the labels.
78
+
79
+ **A faint icon.** `check_color_contrast` icon stroke against the card; darken the stroke until it
80
+ reaches 3:1.
81
+
82
+ ## Pitfalls
83
+
84
+ - **Eyeballing contrast.** Screens and eyes vary; measure every pair.
85
+ - **Changing the brand colour** to fix text; change the text first.
86
+ - **Large-text allowance on small text.** 3:1 applies only to large or bold-large text.
87
+ - **Translucent colours** are measured composited over their real backdrop; pass the opaque
88
+ backdrop.
89
+ - **Red and green only** for meaning fails the most common colour-vision deficiency.
90
+ - **Fixing one mode** and breaking the other in a dual theme.
91
+
92
+ ## Tools used
93
+
94
+ - `check_color_contrast`: text and graphic contrast, with a suggested passing lightness
95
+ - `check_color_palette`: distinctness under colour-vision deficiencies
96
+ - `check_dual_color_contrast`, `derive_dual_color`: light and dark modes
97
+ - `inspect_color`: parse and convert a colour
98
+ - `blend_colors`: shades and tints of a hue
@@ -0,0 +1,97 @@
1
+ {
2
+ "prompt": "Our 'Buy now' button fails accessibility review: the label is hard to read on the button. Fix the label colour so it meets WCAG AA for normal text, keeping the button's brand blue.",
3
+ "fixture": {
4
+ "editor": "vector",
5
+ "width": 200,
6
+ "height": 80,
7
+ "colorTools": true,
8
+ "setup": [
9
+ {
10
+ "id": "vector.addShape",
11
+ "params": {
12
+ "kind": "rect",
13
+ "x": 20,
14
+ "y": 20,
15
+ "width": 160,
16
+ "height": 40,
17
+ "name": "Button",
18
+ "fill": "#4da3ff",
19
+ "stroke": null
20
+ }
21
+ },
22
+ {
23
+ "id": "vector.addShape",
24
+ "params": {
25
+ "kind": "rect",
26
+ "x": 60,
27
+ "y": 32,
28
+ "width": 80,
29
+ "height": 16,
30
+ "name": "Label",
31
+ "fill": "#ffffff",
32
+ "stroke": null
33
+ }
34
+ }
35
+ ]
36
+ },
37
+ "checks": [
38
+ {
39
+ "why": "label on button at least 4.5:1",
40
+ "tool": "run_command",
41
+ "args": {
42
+ "id": "vector.list"
43
+ },
44
+ "expect": {
45
+ "result": {
46
+ "nodes": {
47
+ "$expr": "(() => { const L = (h) => { const c = [1, 3, 5].map((i) => parseInt(h.replace('#', '').padEnd(6, '0').slice(i - 1, i + 1), 16) / 255).map((x) => (x <= 0.04045 ? x / 12.92 : ((x + 0.055) / 1.055) ** 2.4)); return 0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2]; }; const cr = (a, b) => { const [x, y] = [L(a), L(b)].sort((p, q) => q - p); return (x + 0.05) / (y + 0.05); }; return cr(v.find((n) => n.name === 'Label').fill, v.find((n) => n.name === 'Button').fill) >= 4.5; })()"
48
+ }
49
+ }
50
+ }
51
+ },
52
+ {
53
+ "why": "the button keeps its blue",
54
+ "tool": "run_command",
55
+ "args": {
56
+ "id": "vector.list"
57
+ },
58
+ "expect": {
59
+ "result": {
60
+ "nodes": {
61
+ "$some": {
62
+ "name": "Button",
63
+ "fill": "#4da3ff"
64
+ }
65
+ }
66
+ }
67
+ }
68
+ }
69
+ ],
70
+ "solution": [
71
+ {
72
+ "tool": "check_color_contrast",
73
+ "args": {
74
+ "foreground": "#ffffff",
75
+ "background": "#4da3ff"
76
+ }
77
+ },
78
+ {
79
+ "tool": "run_command",
80
+ "args": {
81
+ "id": "vector.setStyle",
82
+ "params": {
83
+ "ids": [
84
+ {
85
+ "$read": "run_command",
86
+ "args": {
87
+ "id": "vector.list"
88
+ },
89
+ "path": "result.nodes.1.id"
90
+ }
91
+ ],
92
+ "fill": "#0b1f33"
93
+ }
94
+ }
95
+ }
96
+ ]
97
+ }
@@ -0,0 +1,160 @@
1
+ {
2
+ "prompt": "These three status dots (OK, warning, error) must be told apart by colour-blind users too. Fix their colours.",
3
+ "fixture": {
4
+ "editor": "vector",
5
+ "width": 120,
6
+ "height": 40,
7
+ "colorTools": true,
8
+ "setup": [
9
+ {
10
+ "id": "vector.addShape",
11
+ "params": {
12
+ "kind": "ellipse",
13
+ "x": 10,
14
+ "y": 10,
15
+ "width": 20,
16
+ "height": 20,
17
+ "name": "OK",
18
+ "fill": "#2ca02c",
19
+ "stroke": null
20
+ }
21
+ },
22
+ {
23
+ "id": "vector.addShape",
24
+ "params": {
25
+ "kind": "ellipse",
26
+ "x": 50,
27
+ "y": 10,
28
+ "width": 20,
29
+ "height": 20,
30
+ "name": "Warning",
31
+ "fill": "#bcbd22",
32
+ "stroke": null
33
+ }
34
+ },
35
+ {
36
+ "id": "vector.addShape",
37
+ "params": {
38
+ "kind": "ellipse",
39
+ "x": 90,
40
+ "y": 10,
41
+ "width": 20,
42
+ "height": 20,
43
+ "name": "Error",
44
+ "fill": "#d62728",
45
+ "stroke": null
46
+ }
47
+ }
48
+ ]
49
+ },
50
+ "checks": [
51
+ {
52
+ "why": "the three colours pass the colour-vision check",
53
+ "tool": "check_color_palette",
54
+ "args": {
55
+ "colors": {
56
+ "$read": "run_command",
57
+ "args": {
58
+ "id": "vector.list"
59
+ },
60
+ "path": "result.nodes.*.fill"
61
+ }
62
+ },
63
+ "expect": {
64
+ "ok": true
65
+ }
66
+ },
67
+ {
68
+ "why": "still three dots",
69
+ "tool": "run_command",
70
+ "args": {
71
+ "id": "vector.list"
72
+ },
73
+ "expect": {
74
+ "result": {
75
+ "nodes": {
76
+ "$length": 3
77
+ }
78
+ }
79
+ }
80
+ }
81
+ ],
82
+ "solution": [
83
+ {
84
+ "tool": "check_color_palette",
85
+ "args": {
86
+ "colors": [
87
+ "#2ca02c",
88
+ "#bcbd22",
89
+ "#d62728"
90
+ ]
91
+ }
92
+ },
93
+ {
94
+ "tool": "run_command",
95
+ "args": {
96
+ "id": "vector.setStyle",
97
+ "params": {
98
+ "ids": [
99
+ {
100
+ "$read": "run_command",
101
+ "args": {
102
+ "id": "vector.list"
103
+ },
104
+ "path": "result.nodes.0.id"
105
+ }
106
+ ],
107
+ "fill": "#0072b2"
108
+ }
109
+ }
110
+ },
111
+ {
112
+ "tool": "run_command",
113
+ "args": {
114
+ "id": "vector.setStyle",
115
+ "params": {
116
+ "ids": [
117
+ {
118
+ "$read": "run_command",
119
+ "args": {
120
+ "id": "vector.list"
121
+ },
122
+ "path": "result.nodes.1.id"
123
+ }
124
+ ],
125
+ "fill": "#e69f00"
126
+ }
127
+ }
128
+ },
129
+ {
130
+ "tool": "run_command",
131
+ "args": {
132
+ "id": "vector.setStyle",
133
+ "params": {
134
+ "ids": [
135
+ {
136
+ "$read": "run_command",
137
+ "args": {
138
+ "id": "vector.list"
139
+ },
140
+ "path": "result.nodes.2.id"
141
+ }
142
+ ],
143
+ "fill": "#000000"
144
+ }
145
+ }
146
+ },
147
+ {
148
+ "tool": "check_color_palette",
149
+ "args": {
150
+ "colors": {
151
+ "$read": "run_command",
152
+ "args": {
153
+ "id": "vector.list"
154
+ },
155
+ "path": "result.nodes.*.fill"
156
+ }
157
+ }
158
+ }
159
+ ]
160
+ }
@@ -0,0 +1,109 @@
1
+ {
2
+ "prompt": "The outline icon on this card is too faint. Make sure it meets the WCAG contrast for non-text graphics (3:1) against the card.",
3
+ "fixture": {
4
+ "editor": "vector",
5
+ "width": 120,
6
+ "height": 120,
7
+ "colorTools": true,
8
+ "setup": [
9
+ {
10
+ "id": "vector.addShape",
11
+ "params": {
12
+ "kind": "rect",
13
+ "x": 0,
14
+ "y": 0,
15
+ "width": 120,
16
+ "height": 120,
17
+ "name": "Card",
18
+ "fill": "#ffffff",
19
+ "stroke": null
20
+ }
21
+ },
22
+ {
23
+ "id": "vector.addShape",
24
+ "params": {
25
+ "kind": "ellipse",
26
+ "x": 40,
27
+ "y": 40,
28
+ "width": 40,
29
+ "height": 40,
30
+ "name": "Icon",
31
+ "fill": null,
32
+ "stroke": "#c8c8c8",
33
+ "strokeWidth": 2
34
+ }
35
+ }
36
+ ]
37
+ },
38
+ "checks": [
39
+ {
40
+ "why": "the icon stroke against the card is at least 3:1",
41
+ "tool": "check_color_contrast",
42
+ "args": {
43
+ "foreground": {
44
+ "$read": "run_command",
45
+ "args": {
46
+ "id": "vector.list"
47
+ },
48
+ "path": "result.nodes.1.stroke"
49
+ },
50
+ "background": {
51
+ "$read": "run_command",
52
+ "args": {
53
+ "id": "vector.list"
54
+ },
55
+ "path": "result.nodes.0.fill"
56
+ }
57
+ },
58
+ "expect": {
59
+ "ratio": {
60
+ "$min": 3
61
+ }
62
+ }
63
+ },
64
+ {
65
+ "why": "the card is unchanged",
66
+ "tool": "run_command",
67
+ "args": {
68
+ "id": "vector.list"
69
+ },
70
+ "expect": {
71
+ "result": {
72
+ "nodes": {
73
+ "$some": {
74
+ "name": "Card",
75
+ "fill": "#ffffff"
76
+ }
77
+ }
78
+ }
79
+ }
80
+ }
81
+ ],
82
+ "solution": [
83
+ {
84
+ "tool": "check_color_contrast",
85
+ "args": {
86
+ "foreground": "#c8c8c8",
87
+ "background": "#ffffff"
88
+ }
89
+ },
90
+ {
91
+ "tool": "run_command",
92
+ "args": {
93
+ "id": "vector.setStyle",
94
+ "params": {
95
+ "ids": [
96
+ {
97
+ "$read": "run_command",
98
+ "args": {
99
+ "id": "vector.list"
100
+ },
101
+ "path": "result.nodes.1.id"
102
+ }
103
+ ],
104
+ "stroke": "#767676"
105
+ }
106
+ }
107
+ }
108
+ ]
109
+ }