@pixerate/editor 0.222.3 → 0.223.1
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/dist/agent/index.cjs +13 -3
- package/dist/agent/index.js +12 -2
- package/dist/bridge/cli.cjs +3 -3
- package/dist/bridge/cli.js +2 -2
- package/dist/bridge/index.cjs +3 -3
- package/dist/bridge/index.js +2 -2
- package/dist/{chunk-I5MRJW7B.js → chunk-67JAEU53.js} +1 -1
- package/dist/{chunk-U4QN36YT.cjs → chunk-BDT2IYXK.cjs} +128 -16
- package/dist/{chunk-S4EIAIEW.cjs → chunk-OJTZKDLJ.cjs} +7 -7
- package/dist/{chunk-XZB3TJ6K.js → chunk-SAN4SBBS.js} +25 -7
- package/dist/{chunk-GI3AVES4.js → chunk-SVHXD2VH.js} +117 -5
- package/dist/{chunk-AMX23CSN.cjs → chunk-Z7BVK32C.cjs} +25 -7
- package/dist/image-editor/index.cjs +12 -11
- package/dist/image-editor/index.js +12 -11
- package/dist/image-editor/layers/panel.d.cts +6 -2
- package/dist/image-editor/layers/panel.d.ts +6 -2
- package/dist/tools/index.cjs +13 -3
- package/dist/tools/index.d.cts +1 -0
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/index.js +12 -2
- package/dist/tools/skill-evals.d.cts +107 -0
- package/dist/tools/skill-evals.d.ts +107 -0
- package/dist/tools/skills.d.cts +12 -1
- package/dist/tools/skills.d.ts +12 -1
- package/package.json +1 -1
- package/skills/README.md +7 -2
- package/skills/pixerate-agent-tools/SKILL.md +5 -3
- package/skills/pixerate-chart/SKILL.md +8 -0
- package/skills/pixerate-chart-annotate/SKILL.md +96 -0
- package/skills/pixerate-chart-annotate/evals/highlight-the-point.json +78 -0
- package/skills/pixerate-chart-annotate/evals/source-and-units.json +76 -0
- package/skills/pixerate-chart-annotate/evals/target-line.json +85 -0
- package/skills/pixerate-chart-choose-and-build/SKILL.md +100 -0
- package/skills/pixerate-chart-choose-and-build/evals/colour-blind-safe.json +113 -0
- package/skills/pixerate-chart-choose-and-build/evals/honest-baseline.json +81 -0
- package/skills/pixerate-chart-choose-and-build/evals/trend-over-time.json +175 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The evals that workflow skills ship in `skills/<name>/evals/*.json`, for any harness to run.
|
|
3
|
+
*
|
|
4
|
+
* An eval is a task a model would be given (`prompt`), the document it starts from
|
|
5
|
+
* (`fixture`), the checks that decide whether the job was done (`checks`: a read tool, its
|
|
6
|
+
* arguments and what its result must match) and one known-good answer (`solution`: the tool
|
|
7
|
+
* calls an expert would make). The package's own tests prove each eval with its `solution`; an
|
|
8
|
+
* agent harness builds the fixture, gives its model the `prompt` in place of the solution, then
|
|
9
|
+
* runs `failedEvalChecks`. The format is described in `skills/README.md`.
|
|
10
|
+
*/
|
|
11
|
+
import type { McpToolResult } from './registry.js';
|
|
12
|
+
export interface SkillEvalToolCall {
|
|
13
|
+
tool: string;
|
|
14
|
+
args?: Record<string, unknown>;
|
|
15
|
+
}
|
|
16
|
+
export interface SkillEvalCheck extends SkillEvalToolCall {
|
|
17
|
+
/** What the check is for, shown when it fails. */
|
|
18
|
+
why: string;
|
|
19
|
+
expect: unknown;
|
|
20
|
+
}
|
|
21
|
+
export interface SkillEval {
|
|
22
|
+
prompt: string;
|
|
23
|
+
fixture: SpreadsheetEvalFixture | ImageEvalFixture | VectorEvalFixture | PaintEvalFixture | VideoEvalFixture | ChartEvalFixture;
|
|
24
|
+
checks: SkillEvalCheck[];
|
|
25
|
+
solution: SkillEvalToolCall[];
|
|
26
|
+
}
|
|
27
|
+
export interface SpreadsheetEvalFixture {
|
|
28
|
+
editor: 'spreadsheet';
|
|
29
|
+
/** Raw inputs from A1, row by row, typed as a person would (`'` forces text). */
|
|
30
|
+
rows: Array<Array<string | number | boolean | null>>;
|
|
31
|
+
}
|
|
32
|
+
export interface ImageEvalFixture {
|
|
33
|
+
editor: 'image';
|
|
34
|
+
width: number;
|
|
35
|
+
height: number;
|
|
36
|
+
/** RGB background. */
|
|
37
|
+
background: [number, number, number];
|
|
38
|
+
/** Shapes painted over it in order, with RGB colours. */
|
|
39
|
+
shapes: Array<{
|
|
40
|
+
shape: 'rect' | 'ellipse';
|
|
41
|
+
x: number;
|
|
42
|
+
y: number;
|
|
43
|
+
width: number;
|
|
44
|
+
height: number;
|
|
45
|
+
color: [number, number, number];
|
|
46
|
+
}>;
|
|
47
|
+
}
|
|
48
|
+
export interface VectorEvalFixture {
|
|
49
|
+
editor: 'vector';
|
|
50
|
+
width: number;
|
|
51
|
+
height: number;
|
|
52
|
+
/** Commands that build the starting artwork, run in order (`"$0.id"` refers to an earlier result). */
|
|
53
|
+
setup?: Array<{
|
|
54
|
+
id: string;
|
|
55
|
+
params?: Record<string, unknown>;
|
|
56
|
+
}>;
|
|
57
|
+
}
|
|
58
|
+
export interface PaintEvalFixture {
|
|
59
|
+
editor: 'paint';
|
|
60
|
+
width: number;
|
|
61
|
+
height: number;
|
|
62
|
+
/** Tool calls that prepare the canvas (paper, layers, strokes), run in order. */
|
|
63
|
+
setup?: SkillEvalToolCall[];
|
|
64
|
+
}
|
|
65
|
+
export interface VideoEvalFixture {
|
|
66
|
+
editor: 'video';
|
|
67
|
+
/** Tool calls that build the starting project (settings, assets, clips, markers), run in order. */
|
|
68
|
+
setup?: SkillEvalToolCall[];
|
|
69
|
+
}
|
|
70
|
+
export interface ChartEvalFixture {
|
|
71
|
+
editor: 'chart';
|
|
72
|
+
/** Tool calls that build the starting chart (`set_chart_data`, `update_chart`), run in order. */
|
|
73
|
+
setup?: SkillEvalToolCall[];
|
|
74
|
+
}
|
|
75
|
+
/** One eval file of a skill. */
|
|
76
|
+
export interface SkillEvalFile {
|
|
77
|
+
skill: string;
|
|
78
|
+
/** Path relative to the skills directory, e.g. `pixerate-image-selection-masking/evals/local-adjustment.json`. */
|
|
79
|
+
path: string;
|
|
80
|
+
eval: SkillEval;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* The evals in a set of skill files (paths relative to the skills directory, contents as text,
|
|
84
|
+
* like `SkillFiles` but including `evals/*.json`), sorted by path.
|
|
85
|
+
*/
|
|
86
|
+
export declare function listSkillEvals(files: Readonly<Record<string, string>>): SkillEvalFile[];
|
|
87
|
+
/** The RGBA pixels of an image fixture: shapes painted over the background in order. */
|
|
88
|
+
export declare function imageEvalFixturePixels(f: ImageEvalFixture): {
|
|
89
|
+
width: number;
|
|
90
|
+
height: number;
|
|
91
|
+
data: Uint8ClampedArray;
|
|
92
|
+
};
|
|
93
|
+
/** Calls a tool by the bare name an eval uses; the harness maps it to its own names if it must. */
|
|
94
|
+
export type SkillEvalToolCaller = (tool: string, args: Record<string, unknown>) => Promise<McpToolResult>;
|
|
95
|
+
/** Calls one eval tool step (resolving `$read` arguments); throws with the tool's error text. */
|
|
96
|
+
export declare function callEvalTool(callTool: SkillEvalToolCaller, { tool, args }: SkillEvalToolCall): Promise<unknown>;
|
|
97
|
+
/**
|
|
98
|
+
* Whether `actual` matches `expected`. Objects match key by key (other keys are ignored),
|
|
99
|
+
* arrays element by element with the same length, other values exactly. Operators:
|
|
100
|
+
* `$min` / `$max` (numbers), `$type`, `$match` (a regular expression, or `[pattern, flags]`), `$in`, `$not`,
|
|
101
|
+
* `$every` / `$some` (array elements), `$length`, `$unique`, `$distinct` (the distinct
|
|
102
|
+
* values), `$sum`, `$column: [index, matcher]` (one column of a 2D array), `$nonEmpty`
|
|
103
|
+
* (the array without empty cells or rows) and `$expr` (a JavaScript expression over `v`).
|
|
104
|
+
*/
|
|
105
|
+
export declare function matchesEvalExpectation(actual: unknown, expected: unknown): boolean;
|
|
106
|
+
/** The checks that fail, each as its `why` with what the tool returned (or its error). */
|
|
107
|
+
export declare function failedEvalChecks(callTool: SkillEvalToolCaller, checks: readonly SkillEvalCheck[]): Promise<string[]>;
|
package/dist/tools/skills.d.cts
CHANGED
|
@@ -13,11 +13,22 @@ export interface SkillInfo {
|
|
|
13
13
|
description: string;
|
|
14
14
|
/** Other files of the skill relative to its directory, e.g. `references/commands.md`. */
|
|
15
15
|
references: string[];
|
|
16
|
+
/**
|
|
17
|
+
* `workflow` for a skill that covers one job from start to finish on a reference skill's tools
|
|
18
|
+
* (frontmatter `metadata.kind: workflow`); otherwise `reference`.
|
|
19
|
+
*/
|
|
20
|
+
kind: 'reference' | 'workflow';
|
|
21
|
+
/** A workflow skill's reference skill (frontmatter `metadata.reference`), whose tools it uses. */
|
|
22
|
+
reference?: string;
|
|
16
23
|
}
|
|
17
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* Reads `name`, `description` and the one-level `metadata` map (`metadata.kind`,
|
|
26
|
+
* `metadata.reference`, …) from a SKILL.md's YAML frontmatter.
|
|
27
|
+
*/
|
|
18
28
|
export declare function parseSkillFrontmatter(markdown: string): {
|
|
19
29
|
name?: string;
|
|
20
30
|
description?: string;
|
|
31
|
+
metadata: Record<string, string>;
|
|
21
32
|
body: string;
|
|
22
33
|
};
|
|
23
34
|
/** The skills in a set of skill files, sorted by name. */
|
package/dist/tools/skills.d.ts
CHANGED
|
@@ -13,11 +13,22 @@ export interface SkillInfo {
|
|
|
13
13
|
description: string;
|
|
14
14
|
/** Other files of the skill relative to its directory, e.g. `references/commands.md`. */
|
|
15
15
|
references: string[];
|
|
16
|
+
/**
|
|
17
|
+
* `workflow` for a skill that covers one job from start to finish on a reference skill's tools
|
|
18
|
+
* (frontmatter `metadata.kind: workflow`); otherwise `reference`.
|
|
19
|
+
*/
|
|
20
|
+
kind: 'reference' | 'workflow';
|
|
21
|
+
/** A workflow skill's reference skill (frontmatter `metadata.reference`), whose tools it uses. */
|
|
22
|
+
reference?: string;
|
|
16
23
|
}
|
|
17
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* Reads `name`, `description` and the one-level `metadata` map (`metadata.kind`,
|
|
26
|
+
* `metadata.reference`, …) from a SKILL.md's YAML frontmatter.
|
|
27
|
+
*/
|
|
18
28
|
export declare function parseSkillFrontmatter(markdown: string): {
|
|
19
29
|
name?: string;
|
|
20
30
|
description?: string;
|
|
31
|
+
metadata: Record<string, string>;
|
|
21
32
|
body: string;
|
|
22
33
|
};
|
|
23
34
|
/** The skills in a set of skill files, sorted by name. */
|
package/package.json
CHANGED
package/skills/README.md
CHANGED
|
@@ -53,6 +53,8 @@ request like "clean up this export" finds it.
|
|
|
53
53
|
| [`pixerate-video-social-cut`](pixerate-video-social-cut/SKILL.md) | Video (`pixerate-video-editor`) | Vertical or square reframes that fill the frame, the hook first, platform lengths, titles in the safe area. |
|
|
54
54
|
| [`pixerate-video-colour-grade`](pixerate-video-colour-grade/SKILL.md) | Video (`pixerate-video-editor`) | Correct and match shots, then one look on an adjustment clip; LUTs at moderate strength with skin protected. |
|
|
55
55
|
| [`pixerate-video-montage`](pixerate-video-montage/SKILL.md) | Video (`pixerate-video-editor`) | Selects on the beat, varied pacing, short transitions with handles, music under everything and a clean ending. |
|
|
56
|
+
| [`pixerate-chart-choose-and-build`](pixerate-chart-choose-and-build/SKILL.md) | Chart (`pixerate-chart`) | From the question to the type, clean ordered data, honest axes, units and colour-blind-safe colours. |
|
|
57
|
+
| [`pixerate-chart-annotate`](pixerate-chart-annotate/SKILL.md) | Chart (`pixerate-chart`) | A takeaway title, highlights, labelled targets and ranges, callouts and a source note, kept few. |
|
|
56
58
|
| [`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. |
|
|
57
59
|
| [`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. |
|
|
58
60
|
|
|
@@ -117,11 +119,14 @@ same change.
|
|
|
117
119
|
user's words), a `fixture` (the starting document: `{ "editor": "spreadsheet", "rows" }`,
|
|
118
120
|
`{ "editor": "image", "width", "height", "background", "shapes" }` or `{ "editor":
|
|
119
121
|
"vector", "width", "height", "setup" }` with commands that draw the starting art,
|
|
120
|
-
`{ "editor": "paint", "width", "height", "setup" }`
|
|
122
|
+
`{ "editor": "paint", "width", "height", "setup" }` `{ "editor": "video", "setup" }` or `{ "editor":
|
|
123
|
+
"chart", "setup" }` with tool calls), `checks` (a read-only
|
|
121
124
|
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
|
|
122
125
|
tool calls an expert would make). The test proves each eval: some check fails on the fixture,
|
|
123
126
|
and all pass after the solution. A model harness runs the same file with the model in place
|
|
124
|
-
of the solution
|
|
127
|
+
of the solution, using `@pixerate/editor/tools`: `listSkillEvals` reads the eval files,
|
|
128
|
+
`imageEvalFixturePixels` paints an image fixture, and `failedEvalChecks` runs the checks
|
|
129
|
+
through the harness's own tool calls (`matchesEvalExpectation` is the matcher on its own). In `expect`, objects match key by key and arrays element by element, with
|
|
125
130
|
operators for ranges and lists (`$min`, `$max`, `$match`, `$every`, `$some`, `$length`,
|
|
126
131
|
`$unique`, `$distinct`, `$sum`, `$column`, `$nonEmpty`, `$not`, `$in`, `$type`); a check's
|
|
127
132
|
argument can read a value the job created with `{ "$read": "list_layers", "path":
|
|
@@ -42,7 +42,9 @@ editor's, or (in discovery mode) only `list_toolsets`, `search_tools`, `enable_t
|
|
|
42
42
|
toolset when you will use many of its tools). The tools then appear in your list. Use
|
|
43
43
|
`disable_tools` to drop a toolset you are done with.
|
|
44
44
|
4. **Read the skill before working in a toolset.** `get_skill` with the skill `list_toolsets`
|
|
45
|
-
names (or the MCP prompt / `skill://` resource of the same name).
|
|
45
|
+
names (or the MCP prompt / `skill://` resource of the same name). When the task is a whole
|
|
46
|
+
job one of the toolset's `workflows` covers (a cutout, a photo correction, an icon set), read
|
|
47
|
+
that workflow skill too: it has the steps in order and the check after each.
|
|
46
48
|
5. **Read the house style when there is one.** A toolset with a `style` field in
|
|
47
49
|
`list_toolsets` (or an `enable_tools` result with a `style` line) has house-style skills:
|
|
48
50
|
call `get_style` with that domain before you make or change work there.
|
|
@@ -149,11 +151,11 @@ Never repeat an identical failing call. Never work around a read-only refusal; t
|
|
|
149
151
|
| Tool | Use it to |
|
|
150
152
|
| --- | --- |
|
|
151
153
|
| `list_editors` | See which editors are open in the browser and connected to the MCP bridge (id, name, kind, tool count, last activity). Call it first when tools come through the bridge; pass an id or part of a name as `editor` to any tool to pick one when several are open, otherwise the most recently active editor with that tool answers. If none is connected, ask the user to open the app and connect it. |
|
|
152
|
-
| `list_toolsets` | See the toolsets open now: name, title, what each covers, its skill, how many tools it has and whether they are in your tool list (`enabled`: all, some, none). Call it first in a discovery view, then `search_tools` or `enable_tools`. |
|
|
154
|
+
| `list_toolsets` | See the toolsets open now: name, title, what each covers, its skill, its workflow skills (`workflows`), how many tools it has and whether they are in your tool list (`enabled`: all, some, none). Call it first in a discovery view, then `search_tools` or `enable_tools`. |
|
|
153
155
|
| `search_tools` | Find the tool for a task: pass `query` in plain words (and `toolset` to search one editor, `limit` for more than 10). Returns the best matches first with name, toolset, title, one-sentence summary, whether it only reads and whether it is listed. Follow with `enable_tools`. |
|
|
154
156
|
| `enable_tools` | Add tools to your tool list by full name (`tools`, from `search_tools`) and/or whole `toolsets` (from `list_toolsets`). Returns the names added and how many are listed. Prefer single tools to large toolsets. Unknown names fail and change nothing. Allowed tools can also be called by name without enabling them. |
|
|
155
157
|
| `disable_tools` | Drop tools or whole toolsets you are done with from your tool list, to keep it short. Returns the names removed. |
|
|
156
|
-
| `get_skill` | Read a toolset's skill (markdown) before working with it; pass `file` for one of its reference files (listed at the top). The note at the top says how names are prefixed here. |
|
|
158
|
+
| `get_skill` | Read a toolset's skill (markdown) before working with it, and the workflow skill for the job when one fits; pass `file` for one of its reference files (listed at the top). The note at the top says how names are prefixed here. |
|
|
157
159
|
| `list_styles` | See the house-style skills here, general layer first: name, description, layer, the domains each applies to, which have a profile, whether each is on, and issues that leave all or part of one out. Pass `domain` to see one domain's. Follow with `get_style`. |
|
|
158
160
|
| `get_style` | Read the house style for a `domain` before you make or change work there: each skill's preferences as a `<style_skill>` block naming its layer, the merged profile (tokens resolved), `sources` (which layer set each field) and `issues`. Then check your work with the domain's `check_<domain>_style`. |
|
|
159
161
|
| `get_style_file` | Read one file of a house-style skill (`name`, `file`), such as a `references/*.md` its preferences point to, or its raw profile (`chart.json`) before tokens are resolved. An unknown file fails with the list of its files. |
|
|
@@ -44,6 +44,14 @@ Charts inside a rich-text document are not edited with these tools: use `insert_
|
|
|
44
44
|
`list_document_charts` and `update_chart_in_document` from the `pixerate-rich-text` skill
|
|
45
45
|
(a chart block goes in the prose, never inside a table row).
|
|
46
46
|
|
|
47
|
+
## Workflow skills
|
|
48
|
+
|
|
49
|
+
For these jobs, load the workflow skill too: it has the method, the checks between steps and
|
|
50
|
+
the pitfalls, and uses the tools below.
|
|
51
|
+
|
|
52
|
+
- `pixerate-chart-choose-and-build`: the type from the question, clean data, honest axes, units, colours.
|
|
53
|
+
- `pixerate-chart-annotate`: a takeaway title, highlights, targets, ranges, callouts and the source.
|
|
54
|
+
|
|
47
55
|
## Working method
|
|
48
56
|
|
|
49
57
|
0. **House style.** When the host has one (`list_toolsets` shows a `style` field on the chart
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pixerate-chart-annotate
|
|
3
|
+
description: The annotation workflow in the Pixerate chart editor - turning a correct chart into one that makes its point - a title that states the takeaway, highlighting the categories or series that matter, reference lines for targets and averages, bands for ranges, callouts on the points that need explaining, units, a source note and a check that the annotations stay few and honest. Use when asked to make a chart tell a story, highlight or call out a point, show a target, goal, threshold, average or range, add a source or footnote, or make a chart ready for a report, slide or board pack, or when highlight_chart, add_chart_reference_line, add_chart_reference_band or set_chart_callout are available. Load with pixerate-chart, which lists every tool.
|
|
4
|
+
metadata:
|
|
5
|
+
kind: workflow
|
|
6
|
+
reference: pixerate-chart
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Annotating a chart
|
|
10
|
+
|
|
11
|
+
A chart for a report or a slide should say what it means, not leave the reader to find it. Once
|
|
12
|
+
the type and data are right (`pixerate-chart-choose-and-build`), add the few marks that point at
|
|
13
|
+
the message: a title that states it, a highlight on what matters, a reference line or band for
|
|
14
|
+
the benchmark, a callout where something needs explaining, and a source note. Fewer is better:
|
|
15
|
+
every annotation competes with the data.
|
|
16
|
+
|
|
17
|
+
This skill is the method. `pixerate-chart` has the full tool reference.
|
|
18
|
+
|
|
19
|
+
## Goal and quality bar
|
|
20
|
+
|
|
21
|
+
- The title states the takeaway ("Q3 was our record quarter"), the subtitle the measure and
|
|
22
|
+
units if the axis does not.
|
|
23
|
+
- What matters stands out (`highlight`), and everything else recedes.
|
|
24
|
+
- Benchmarks are drawn, labelled, on the right axis: a target line, an average, a normal range.
|
|
25
|
+
- At most one or two reference lines, one band and a couple of callouts.
|
|
26
|
+
- The source is cited in `note`; units are stated.
|
|
27
|
+
- The data is unchanged by annotating.
|
|
28
|
+
|
|
29
|
+
## Inputs to confirm
|
|
30
|
+
|
|
31
|
+
- **The message:** the one thing the reader should take away.
|
|
32
|
+
- **Benchmarks:** targets, averages, thresholds or ranges, with their values and units.
|
|
33
|
+
- **Source** of the data, and the audience (report, slide, dashboard).
|
|
34
|
+
|
|
35
|
+
## Working method
|
|
36
|
+
|
|
37
|
+
1. **Read.** `get_chart`: the type, categories, series, existing annotations and warnings.
|
|
38
|
+
**Check:** the chart is already the right type with clean data; if not, fix that first.
|
|
39
|
+
2. **Title the takeaway.** `update_chart` `title` with the message, `subtitle` with the measure
|
|
40
|
+
and units.
|
|
41
|
+
3. **Highlight.** `highlight_chart` with the category labels or series names that carry the
|
|
42
|
+
message; the rest dims.
|
|
43
|
+
**Check:** `get_chart` `spec.highlight`.
|
|
44
|
+
4. **Benchmarks.** `add_chart_reference_line` for a target or average (`value`, `label`, `axis`);
|
|
45
|
+
`add_chart_reference_band` for a range (`from`, `to`, `label`).
|
|
46
|
+
**Check:** `spec.referenceLines` / `referenceBands` have the values and labels.
|
|
47
|
+
5. **Callouts** for points that need a word (`set_chart_callout` on a series and category), such
|
|
48
|
+
as "Launch" or "Strike".
|
|
49
|
+
6. **Source and units.** `update_chart` `note` ("Source: …"); the axis `format` or title for
|
|
50
|
+
units.
|
|
51
|
+
7. **Review.** `render_chart`; remove any annotation that does not help the message.
|
|
52
|
+
|
|
53
|
+
## Decision table
|
|
54
|
+
|
|
55
|
+
| Want to say | Annotation |
|
|
56
|
+
| --- | --- |
|
|
57
|
+
| This one is the point | `highlight_chart` on it; title states it |
|
|
58
|
+
| Above or below a goal | `add_chart_reference_line` at the target, labelled |
|
|
59
|
+
| Inside or outside a normal range | `add_chart_reference_band`, labelled |
|
|
60
|
+
| Compared with the average | Reference line at the average, labelled "Average" |
|
|
61
|
+
| Something happened here | `set_chart_callout` on that point |
|
|
62
|
+
| Where the numbers came from | `note` |
|
|
63
|
+
| The trend, not the noise | `add_chart_trendline` (say what it models) |
|
|
64
|
+
|
|
65
|
+
## Recipes
|
|
66
|
+
|
|
67
|
+
**Record quarter.** `highlight_chart` `{ names: ["Q3"] }` → `update_chart` `{ title: "Q3 was our
|
|
68
|
+
record quarter" }`.
|
|
69
|
+
|
|
70
|
+
**Target.** `add_chart_reference_line` `{ value: 100000, label: "Target" }`.
|
|
71
|
+
|
|
72
|
+
**Board-ready.** `update_chart` `{ note: "Source: internal CRM, 2026", yAxis: { format: { style:
|
|
73
|
+
"currency", currency: "USD" } } }`.
|
|
74
|
+
|
|
75
|
+
## Pitfalls
|
|
76
|
+
|
|
77
|
+
- **Neutral titles** ("Quarterly revenue") make the reader do the work; state the finding when
|
|
78
|
+
the chart has one.
|
|
79
|
+
- **Annotation clutter**: many lines, bands and callouts hide the data.
|
|
80
|
+
- **Recolouring instead of highlighting**: changing one series' colour by hand breaks palettes
|
|
81
|
+
and colour checks; `highlight_chart` keeps them.
|
|
82
|
+
- **Unlabelled lines**: a line without a label is a puzzle.
|
|
83
|
+
- **Targets on the wrong axis**: put a reference line on the axis of the series it belongs to
|
|
84
|
+
(`axis: "y2"` for a secondary-axis series).
|
|
85
|
+
- **Claims the data does not support**: the title must be true of the chart as drawn.
|
|
86
|
+
|
|
87
|
+
## Tools used
|
|
88
|
+
|
|
89
|
+
- `get_chart`, `render_chart`: read and look
|
|
90
|
+
- `update_chart`: title, subtitle, note and axis formats
|
|
91
|
+
- `highlight_chart`: what stands out
|
|
92
|
+
- `add_chart_reference_line`, `update_chart_reference_line`, `remove_chart_reference_line`: targets and averages
|
|
93
|
+
- `add_chart_reference_band`, `update_chart_reference_band`, `remove_chart_reference_band`: ranges
|
|
94
|
+
- `set_chart_callout`: notes on points
|
|
95
|
+
- `add_chart_trendline`: a fitted trend
|
|
96
|
+
- `undo_chart`: take back a step
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"prompt": "Make this chart tell the reader that Q3 was our record quarter.",
|
|
3
|
+
"fixture": {
|
|
4
|
+
"editor": "chart",
|
|
5
|
+
"setup": [
|
|
6
|
+
{
|
|
7
|
+
"tool": "set_chart_data",
|
|
8
|
+
"args": {
|
|
9
|
+
"categories": [
|
|
10
|
+
"Q1",
|
|
11
|
+
"Q2",
|
|
12
|
+
"Q3",
|
|
13
|
+
"Q4"
|
|
14
|
+
],
|
|
15
|
+
"series": [
|
|
16
|
+
{
|
|
17
|
+
"name": "Revenue",
|
|
18
|
+
"values": [
|
|
19
|
+
82000,
|
|
20
|
+
91000,
|
|
21
|
+
118000,
|
|
22
|
+
96000
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
]
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"tool": "update_chart",
|
|
30
|
+
"args": {
|
|
31
|
+
"type": "bar",
|
|
32
|
+
"title": "Quarterly revenue"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
]
|
|
36
|
+
},
|
|
37
|
+
"checks": [
|
|
38
|
+
{
|
|
39
|
+
"why": "Q3 stands out",
|
|
40
|
+
"tool": "get_chart",
|
|
41
|
+
"expect": {
|
|
42
|
+
"spec": {
|
|
43
|
+
"$expr": "v.highlight.includes('Q3') || v.series.some((s) => (s.callouts || [])[2]) || v.series.some((s) => s.pointColors && s.pointColors[2] && s.pointColors.filter(Boolean).length === 1)"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"why": "the title states the takeaway",
|
|
49
|
+
"tool": "get_chart",
|
|
50
|
+
"expect": {
|
|
51
|
+
"spec": {
|
|
52
|
+
"title": {
|
|
53
|
+
"$match": [
|
|
54
|
+
"q3|third quarter",
|
|
55
|
+
"i"
|
|
56
|
+
]
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
],
|
|
62
|
+
"solution": [
|
|
63
|
+
{
|
|
64
|
+
"tool": "highlight_chart",
|
|
65
|
+
"args": {
|
|
66
|
+
"names": [
|
|
67
|
+
"Q3"
|
|
68
|
+
]
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"tool": "update_chart",
|
|
73
|
+
"args": {
|
|
74
|
+
"title": "Q3 was our record quarter"
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
]
|
|
78
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
{
|
|
2
|
+
"prompt": "Before this chart goes in the board report, add the source (internal CRM, 2026) and make the units clear.",
|
|
3
|
+
"fixture": {
|
|
4
|
+
"editor": "chart",
|
|
5
|
+
"setup": [
|
|
6
|
+
{
|
|
7
|
+
"tool": "set_chart_data",
|
|
8
|
+
"args": {
|
|
9
|
+
"categories": [
|
|
10
|
+
"Q1",
|
|
11
|
+
"Q2",
|
|
12
|
+
"Q3",
|
|
13
|
+
"Q4"
|
|
14
|
+
],
|
|
15
|
+
"series": [
|
|
16
|
+
{
|
|
17
|
+
"name": "Revenue",
|
|
18
|
+
"values": [
|
|
19
|
+
82000,
|
|
20
|
+
91000,
|
|
21
|
+
118000,
|
|
22
|
+
96000
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
]
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"tool": "update_chart",
|
|
30
|
+
"args": {
|
|
31
|
+
"type": "bar",
|
|
32
|
+
"title": "Quarterly revenue"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
]
|
|
36
|
+
},
|
|
37
|
+
"checks": [
|
|
38
|
+
{
|
|
39
|
+
"why": "a source note",
|
|
40
|
+
"tool": "get_chart",
|
|
41
|
+
"expect": {
|
|
42
|
+
"spec": {
|
|
43
|
+
"note": {
|
|
44
|
+
"$match": [
|
|
45
|
+
"crm",
|
|
46
|
+
"i"
|
|
47
|
+
]
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"why": "the units are stated (axis title, format or subtitle)",
|
|
54
|
+
"tool": "get_chart",
|
|
55
|
+
"expect": {
|
|
56
|
+
"spec": {
|
|
57
|
+
"$expr": "/usd|\\$|dollar/i.test(v.yAxis.title + ' ' + v.subtitle) || v.yAxis.format.style === 'currency'"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
],
|
|
62
|
+
"solution": [
|
|
63
|
+
{
|
|
64
|
+
"tool": "update_chart",
|
|
65
|
+
"args": {
|
|
66
|
+
"note": "Source: internal CRM, 2026",
|
|
67
|
+
"yAxis": {
|
|
68
|
+
"format": {
|
|
69
|
+
"style": "currency",
|
|
70
|
+
"currency": "USD"
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
]
|
|
76
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
{
|
|
2
|
+
"prompt": "Show our quarterly target of 100,000 on the revenue chart so it's clear which quarters hit it.",
|
|
3
|
+
"fixture": {
|
|
4
|
+
"editor": "chart",
|
|
5
|
+
"setup": [
|
|
6
|
+
{
|
|
7
|
+
"tool": "set_chart_data",
|
|
8
|
+
"args": {
|
|
9
|
+
"categories": [
|
|
10
|
+
"Q1",
|
|
11
|
+
"Q2",
|
|
12
|
+
"Q3",
|
|
13
|
+
"Q4"
|
|
14
|
+
],
|
|
15
|
+
"series": [
|
|
16
|
+
{
|
|
17
|
+
"name": "Revenue",
|
|
18
|
+
"values": [
|
|
19
|
+
82000,
|
|
20
|
+
91000,
|
|
21
|
+
118000,
|
|
22
|
+
96000
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
]
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"tool": "update_chart",
|
|
30
|
+
"args": {
|
|
31
|
+
"type": "bar",
|
|
32
|
+
"title": "Quarterly revenue"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
]
|
|
36
|
+
},
|
|
37
|
+
"checks": [
|
|
38
|
+
{
|
|
39
|
+
"why": "a labelled reference line at 100,000 on the value axis",
|
|
40
|
+
"tool": "get_chart",
|
|
41
|
+
"expect": {
|
|
42
|
+
"spec": {
|
|
43
|
+
"referenceLines": {
|
|
44
|
+
"$some": {
|
|
45
|
+
"value": 100000,
|
|
46
|
+
"label": {
|
|
47
|
+
"$match": [
|
|
48
|
+
"target",
|
|
49
|
+
"i"
|
|
50
|
+
]
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"why": "the data is unchanged",
|
|
59
|
+
"tool": "get_chart",
|
|
60
|
+
"expect": {
|
|
61
|
+
"spec": {
|
|
62
|
+
"series": [
|
|
63
|
+
{
|
|
64
|
+
"values": [
|
|
65
|
+
82000,
|
|
66
|
+
91000,
|
|
67
|
+
118000,
|
|
68
|
+
96000
|
|
69
|
+
]
|
|
70
|
+
}
|
|
71
|
+
]
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
],
|
|
76
|
+
"solution": [
|
|
77
|
+
{
|
|
78
|
+
"tool": "add_chart_reference_line",
|
|
79
|
+
"args": {
|
|
80
|
+
"value": 100000,
|
|
81
|
+
"label": "Target"
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
]
|
|
85
|
+
}
|