@ycodium-ai/plugin-visualize 0.0.0-stage → 0.2.2944

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.
@@ -0,0 +1,58 @@
1
+ ---
2
+ name: visualize
3
+ description: Authoring contract for visualize_visualize, which renders interactive cards in the conversation. Simulations, algorithm walkthroughs, charts, comparisons, and product-screen mockups. Load before the first visualize_visualize call. It defines the fragment structure, theme variables, size ceiling, and allowed resources the tool checks.
4
+ ---
5
+
6
+ # Visualize
7
+
8
+ Build small interactive surfaces the user can explore inside the chat: parameter-driven simulations, algorithm walkthroughs, charts and plots, side-by-side comparisons, and product-screen mockups. Call `visualize_visualize`. The tool renders your markup as a live sandboxed card.
9
+
10
+ - Reach for a card when seeing or manipulating something answers the question better than prose would. A request that merely mentions data or a web page does not by itself call for one.
11
+ - When the user asks for a real deliverable — a website, a page in their app, a component, a standalone file — build project files instead. That is not an in-conversation card.
12
+ - When a static labeled diagram of nodes and edges already tells the whole story, emit a fenced Mermaid block and skip the tool. The card earns its place through motion, interaction, or adjustable inputs.
13
+
14
+ ## Resources
15
+
16
+ Read from this skill's directory as the task demands. Before the first chart, read `references/charts.md`. Before styling anything, read `references/design.md`.
17
+
18
+ - `references/design.md` — taste, theme tokens, base classes, layout.
19
+ - `references/charts.md` — Chart.js-first charting, theme-color resolution, hand-rolled SVG rules, entrance animation recipes.
20
+ - `examples/interactive-simulator.html` — sliders driving a Chart.js area curve.
21
+ - `examples/comparison-chart.html` — a hand-rolled SVG comparison with direct labels and a hover detail.
22
+
23
+ ## Calling the tool
24
+
25
+ The tool name on the wire is `visualize_visualize`. Pass the markup as `fragment`, with an optional `title` and optional `mode`. The card starts rendering while you are still generating, and a copy of the finished fragment lands in the session workspace under `viz/`.
26
+
27
+ Reserve `mode: "wide"` for layouts where several compact panels must sit beside each other. A single dense chart or a full-page mockup stays at the default width.
28
+
29
+ ## Revising a card
30
+
31
+ Re-sending a whole fragment costs as much as drawing it again. Patch instead: `action: "update"` with the card's `path`, its `title` restated, and one exact `old_str` to `new_str` replacement.
32
+
33
+ - Patch when the correction touches fewer than 20 lines in fewer than 5 places, and at most 4 times per reply. Re-create the card for anything larger.
34
+ - `old_str` must match the current card byte for byte, whitespace included, and appear exactly once. An empty `new_str` deletes the matched region.
35
+ - A card keeps one `path`. Patches rewrite that file, so several patches in one reply each build on the one before.
36
+ - A refused patch quotes what the card actually contains. Correct `old_str` from that and retry in the same reply.
37
+ - The card reloads on every change, so anything the user typed, dragged, or scrolled inside it resets. Batch corrections.
38
+
39
+ ## Fragment rules
40
+
41
+ The card wraps the fragment in its own document, stylesheet, theme, and Content-Security-Policy. The tool rejects violations.
42
+
43
+ - Fragment only. Never emit `<!doctype>`, `<html>`, `<head>`, or `<body>`.
44
+ - Emit real markup with actual newlines, not a string-escaped rendition (`\"`, `\n`) of it.
45
+ - Write every user-visible label, caption, control, tooltip, and annotation in the language the user primarily uses, unless they request another language. Keep established product names and technical terms when translating them would make the visualization less precise.
46
+ - Give the root element a unique id and locate it with `document.getElementById(...)`. Scripts must not rely on `document.currentScript` to find their root.
47
+ - Stay under the fragment size limit (1 MB unless the setting is lower). Reduce large inline datasets first: fewer rows, coarser bins, fewer decimal places, no unused fields.
48
+ - Inline `<script>` and `<style>` work. `fetch`, XHR, WebSocket, and form submission are blocked.
49
+ - Do not load a script or stylesheet from a URL. `<script src>`, `<link rel="stylesheet">`, and `@import` of an `http` or `https` address are rejected. Inline the library or the rules in the fragment. The card is empty when that request cannot run. Images may still use a `data:` URL.
50
+ - Every color comes from a theme variable or a `light-dark()` pair. The token list and utility classes are in `references/design.md`.
51
+ - Never declare `color-scheme` yourself. The document already carries the host scheme. Redeclaring it on the root flips every `light-dark()` in that subtree to the viewer's OS preference.
52
+ - A chart's first draw animates in, once. `references/charts.md` has the recipes. Do not loop decorative motion.
53
+ - Buttons inside the card cannot send the next chat message. They only change the card.
54
+ - Before finishing, confirm every element the script queries exists, every identifier is defined, and the main interaction visibly changes the output.
55
+
56
+ ## In the reply
57
+
58
+ Around the card, write only what helps the user read or act on it — one or two sentences at most. Do not mention the tool, the fragment, files, or this machinery, and never paste the markup into the reply.
@@ -0,0 +1,100 @@
1
+ <!-- Worked example: a compact grouped comparison with direct labels and a
2
+ hover detail. Idioms on display: neutral structure with the accent only
3
+ on the leader, direct value labels instead of a legend, one hover detail
4
+ line instead of a tooltip layer, no gridlines. -->
5
+ <div id="cmp-langs">
6
+ <h3>Build time by toolchain</h3>
7
+ <svg
8
+ id="cmp-langs-plot"
9
+ width="100%"
10
+ height="150"
11
+ role="img"
12
+ aria-label="Build time comparison"
13
+ ></svg>
14
+ <div class="text-small" id="cmp-langs-detail" style="min-height: 1.5em">
15
+ Hover a bar for the breakdown.
16
+ </div>
17
+ </div>
18
+ <script>
19
+ (function () {
20
+ var DATA = [
21
+ { name: "esbuild", seconds: 0.4, note: "Go, parallel from the start" },
22
+ { name: "rolldown", seconds: 0.9, note: "Rust, Rollup-compatible API" },
23
+ { name: "rollup", seconds: 6.8, note: "JS, plugin ecosystem pioneer" },
24
+ { name: "webpack", seconds: 11.2, note: "JS, richest loader ecosystem" },
25
+ ];
26
+ var root = document.getElementById("cmp-langs");
27
+ var svg = document.getElementById("cmp-langs-plot");
28
+ var detail = document.getElementById("cmp-langs-detail");
29
+ var best = Math.min.apply(
30
+ null,
31
+ DATA.map(function (d) {
32
+ return d.seconds;
33
+ }),
34
+ );
35
+ var max = Math.max.apply(
36
+ null,
37
+ DATA.map(function (d) {
38
+ return d.seconds;
39
+ }),
40
+ );
41
+
42
+ function draw() {
43
+ var w = svg.clientWidth || 600,
44
+ rowH = 34,
45
+ labelW = 84,
46
+ valueW = 56;
47
+ svg.setAttribute("viewBox", "0 0 " + w + " " + DATA.length * rowH);
48
+ svg.innerHTML = DATA.map(function (d, i) {
49
+ var y = i * rowH,
50
+ barMax = w - labelW - valueW;
51
+ var barW = Math.max(2, (d.seconds / max) * barMax);
52
+ var lead = d.seconds === best;
53
+ return (
54
+ '<g data-index="' +
55
+ i +
56
+ '">' +
57
+ '<text x="0" y="' +
58
+ (y + 21) +
59
+ '">' +
60
+ d.name +
61
+ "</text>" +
62
+ '<rect x="' +
63
+ labelW +
64
+ '" y="' +
65
+ (y + 8) +
66
+ '" width="' +
67
+ barW.toFixed(1) +
68
+ '" height="18" rx="3" ' +
69
+ 'fill="' +
70
+ (lead ? "var(--viz-series-1)" : "var(--border)") +
71
+ '"/>' +
72
+ '<text x="' +
73
+ (labelW + barW + 8).toFixed(1) +
74
+ '" y="' +
75
+ (y + 21) +
76
+ '" ' +
77
+ 'style="font-variant-numeric: tabular-nums;"' +
78
+ (lead ? ' font-weight="500"' : "") +
79
+ ">" +
80
+ d.seconds.toFixed(1) +
81
+ "s</text>" +
82
+ "</g>"
83
+ );
84
+ }).join("");
85
+ }
86
+
87
+ svg.addEventListener("pointermove", function (event) {
88
+ var group = event.target.closest("g[data-index]");
89
+ if (!group) return;
90
+ var d = DATA[Number(group.getAttribute("data-index"))];
91
+ detail.textContent = d.name + " · " + d.seconds.toFixed(1) + "s — " + d.note;
92
+ });
93
+ svg.addEventListener("pointerleave", function () {
94
+ detail.textContent = "Hover a bar for the breakdown.";
95
+ });
96
+
97
+ new ResizeObserver(draw).observe(root);
98
+ draw();
99
+ })();
100
+ </script>
@@ -0,0 +1,172 @@
1
+ <!-- Worked example: sliders driving a Chart.js area curve with summary stats.
2
+ Chart.js must already be inlined above the script. A src URL is rejected.
3
+ Idioms on display: probe-resolved theme colors (canvas cannot read CSS
4
+ variables), a soft gradient fill under the single measure, container-driven
5
+ sizing, the free first-render entrance, and chart.update() so control
6
+ changes transition in place instead of replaying the entrance. -->
7
+ <div id="sim-cooling">
8
+ <style>
9
+ /* Layout idiom: label | slider | right-aligned bold value on one row;
10
+ stats as bare label + large number, no boxes. */
11
+ #sim-cooling .row {
12
+ display: grid;
13
+ grid-template-columns: 92px 1fr 72px;
14
+ align-items: center;
15
+ gap: 12px;
16
+ margin-bottom: 10px;
17
+ }
18
+ #sim-cooling .row output {
19
+ text-align: right;
20
+ font-weight: 500;
21
+ font-variant-numeric: tabular-nums;
22
+ }
23
+ #sim-cooling .head {
24
+ display: flex;
25
+ justify-content: space-between;
26
+ align-items: baseline;
27
+ margin-bottom: 14px;
28
+ }
29
+ #sim-cooling .stats {
30
+ display: flex;
31
+ flex-wrap: wrap;
32
+ gap: 8px 40px;
33
+ margin: 14px 0 10px;
34
+ }
35
+ #sim-cooling .stats .viz-stat-value {
36
+ font-size: 1.5em;
37
+ }
38
+ </style>
39
+ <div class="head">
40
+ <h3 style="margin: 0">Coffee cooling</h3>
41
+ <span class="text-small">T(t) = Tₐ + (T₀ − Tₐ)·e⁻ᵏᵗ</span>
42
+ </div>
43
+ <div class="row">
44
+ <label class="form-label" for="sim-cooling-rate" style="margin: 0">Cooling rate</label>
45
+ <input id="sim-cooling-rate" type="range" min="2" max="30" step="1" value="10" />
46
+ <output id="sim-cooling-rate-out">0.10/min</output>
47
+ </div>
48
+ <div class="row">
49
+ <label class="form-label" for="sim-cooling-years" style="margin: 0">Window</label>
50
+ <input id="sim-cooling-years" type="range" min="15" max="120" step="5" value="60" />
51
+ <output id="sim-cooling-years-out">60 min</output>
52
+ </div>
53
+ <div class="stats">
54
+ <div>
55
+ <div class="text-small">Temp at window end</div>
56
+ <div class="viz-stat-value" id="sim-cooling-end" style="font-variant-numeric: tabular-nums">
57
+ —
58
+ </div>
59
+ </div>
60
+ <div>
61
+ <div class="text-small">Drinkable (≤60°C) after</div>
62
+ <div class="viz-stat-value" id="sim-cooling-drink" style="font-variant-numeric: tabular-nums">
63
+ —
64
+ </div>
65
+ </div>
66
+ </div>
67
+ <!-- Two-beat entrance: the container (axes, grid) fades in first, then the
68
+ line sweeps across it. animation, not transition, so it self-runs. -->
69
+ <div style="height: 220px; animation: sim-cooling-fade 300ms ease-out">
70
+ <canvas id="sim-cooling-plot"></canvas>
71
+ </div>
72
+ <style>
73
+ @keyframes sim-cooling-fade {
74
+ from {
75
+ opacity: 0;
76
+ }
77
+ }
78
+ </style>
79
+ </div>
80
+ <!-- Inline Chart.js here as a <script> with no src. Do not load it from a URL. -->
81
+ <script>
82
+ (function () {
83
+ var rateEl = document.getElementById("sim-cooling-rate");
84
+ var yearsEl = document.getElementById("sim-cooling-years");
85
+ // Newton's cooling: T(t) = Ta + (T0 - Ta) * e^(-kt), coffee starting hot.
86
+ var T0 = 90,
87
+ TA = 22,
88
+ DRINKABLE = 60;
89
+
90
+ // Canvas cannot resolve CSS variables: resolve theme tokens via a probe.
91
+ function themeColor(token) {
92
+ var probe = document.createElement("span");
93
+ probe.style.color = "var(" + token + ")";
94
+ document.body.appendChild(probe);
95
+ var resolved = getComputedStyle(probe).color;
96
+ probe.remove();
97
+ return resolved;
98
+ }
99
+ var series = themeColor("--viz-series-1");
100
+ var faint = series.replace("rgb", "rgba").replace(")", ", 0.25)");
101
+ Chart.defaults.color = themeColor("--muted-foreground");
102
+ Chart.defaults.borderColor = themeColor("--border");
103
+ Chart.defaults.font.family = "inherit";
104
+
105
+ function values() {
106
+ var k = Number(rateEl.value) / 100,
107
+ n = Number(yearsEl.value);
108
+ var out = [];
109
+ for (var t = 0; t <= n; t++) out.push(TA + (T0 - TA) * Math.exp(-k * t));
110
+ return out;
111
+ }
112
+ function fmt(v) {
113
+ return v.toFixed(0) + "°C";
114
+ }
115
+
116
+ var ctx = document.getElementById("sim-cooling-plot");
117
+ // Gradient fill: the series color fading to transparent under the line.
118
+ var gradient = ctx.getContext("2d").createLinearGradient(0, 0, 0, 220);
119
+ gradient.addColorStop(0, faint);
120
+ gradient.addColorStop(1, "rgba(0, 0, 0, 0)");
121
+
122
+ var data = values();
123
+ var chart = new Chart(ctx, {
124
+ type: "line",
125
+ data: {
126
+ labels: data.map(function (_, y) {
127
+ return y;
128
+ }),
129
+ datasets: [
130
+ {
131
+ data: data,
132
+ borderColor: series,
133
+ backgroundColor: gradient,
134
+ fill: true,
135
+ borderWidth: 2,
136
+ pointRadius: 0,
137
+ tension: 0.25,
138
+ },
139
+ ],
140
+ },
141
+ options: {
142
+ responsive: true,
143
+ maintainAspectRatio: false,
144
+ plugins: { legend: { display: false } },
145
+ scales: {
146
+ x: { grid: { display: false }, ticks: { maxTicksLimit: 9 } },
147
+ y: { ticks: { callback: fmt, maxTicksLimit: 6 } },
148
+ },
149
+ interaction: { mode: "index", intersect: false },
150
+ },
151
+ });
152
+
153
+ function update() {
154
+ var data = values();
155
+ var k = Number(rateEl.value) / 100;
156
+ var toDrinkable = Math.log((T0 - TA) / (DRINKABLE - TA)) / k;
157
+ document.getElementById("sim-cooling-rate-out").textContent = k.toFixed(2) + "/min";
158
+ document.getElementById("sim-cooling-years-out").textContent = yearsEl.value + " min";
159
+ document.getElementById("sim-cooling-end").textContent = fmt(data[data.length - 1]);
160
+ document.getElementById("sim-cooling-drink").textContent = toDrinkable.toFixed(1) + " min";
161
+ chart.data.labels = data.map(function (_, t) {
162
+ return t;
163
+ });
164
+ chart.data.datasets[0].data = data;
165
+ chart.update(); // transitions in place; the entrance never replays
166
+ }
167
+
168
+ rateEl.addEventListener("input", update);
169
+ yearsEl.addEventListener("input", update);
170
+ update();
171
+ })();
172
+ </script>
@@ -0,0 +1,61 @@
1
+ # Charts
2
+
3
+ ## Choosing the tool
4
+
5
+ - **Default** — hand-roll SVG, starting from `../examples/comparison-chart.html`.
6
+ The card has no network, so a chart library loaded from a URL renders nothing.
7
+ - **Chart.js** — only when the whole library is pasted into an inline `<script>`
8
+ with no `src`. A tag such as
9
+ `<script src="https://cdn.jsdelivr.net/...">` is rejected. The simulator
10
+ example shows the Chart.js calls after that inline library. Do not add a
11
+ `src`.
12
+ - D3 follows the same rule: paste the library into the fragment. Do not point
13
+ `src` at a CDN.
14
+
15
+ ## Chart.js setup
16
+
17
+ Canvas cannot resolve CSS variables — `'var(--viz-series-1)'` passed to
18
+ Chart.js silently paints nothing. Resolve tokens through a probe element
19
+ first:
20
+
21
+ ```js
22
+ function themeColor(token) {
23
+ var probe = document.createElement("span");
24
+ probe.style.color = "var(" + token + ")";
25
+ document.body.appendChild(probe);
26
+ var resolved = getComputedStyle(probe).color;
27
+ probe.remove();
28
+ return resolved;
29
+ }
30
+ Chart.defaults.color = themeColor("--muted-foreground");
31
+ Chart.defaults.borderColor = themeColor("--border");
32
+ Chart.defaults.font.family = "inherit";
33
+ ```
34
+
35
+ - Size through the container: a wrapping `div` with an explicit height and
36
+ `responsive: true, maintainAspectRatio: false` in the chart options. Never
37
+ fix the canvas width.
38
+ - First render animates by itself — do not disable it. Control-driven changes
39
+ go through `chart.data` mutation + `chart.update()`, which transitions in
40
+ place instead of replaying the entrance. Exactly the wanted behavior, free.
41
+ - Give the entrance two beats: a ~300ms fade on the chart container lets the
42
+ axes settle first, then the data sweeps across them. One CSS `@keyframes`
43
+ on the wrapper, as in the simulator example.
44
+ - A gradient area fill under a single measure: build a canvas gradient from
45
+ the resolved series color at ~0.25 alpha down to transparent and set it as
46
+ the dataset `backgroundColor` with `fill: true`.
47
+ - Tooltips and legends come themed via the defaults above; drop the legend
48
+ entirely for a single series.
49
+
50
+ ## Hand-rolled SVG rules
51
+
52
+ - Compute the domain from the data with padding; never hard-code it.
53
+ - Size from the measured container and redraw on `ResizeObserver`.
54
+ - Keep every mark inside the plot area; clip if a path can escape.
55
+ - After drawing, check that tick and value labels do not overlap; drop ticks
56
+ or re-anchor labels rather than shipping a collision.
57
+ - Entrance, once: line draws in via `stroke-dashoffset` (~500ms ease-out),
58
+ fills fade up behind it, labels after their marks. Guard with a flag so
59
+ redraws never replay it; skip under `prefers-reduced-motion`.
60
+ - The soft vertical gradient under a single-measure curve (series color at
61
+ ≈0.25 opacity fading to transparent) is the house look for area charts.
@@ -0,0 +1,74 @@
1
+ # Design
2
+
3
+ ## Taste
4
+
5
+ The difference between a widget and a good widget is restraint.
6
+
7
+ - Give the visual room. Work on an 8px spacing rhythm; whitespace is part of
8
+ the design, not wasted area. When in doubt, remove a panel rather than
9
+ shrink four.
10
+ - One focal element per card. Numbers speak loudest, labels whisper: values
11
+ in large `--foreground`, their labels small and `--muted-foreground`,
12
+ never the reverse.
13
+ - Color is meaning. The canvas stays neutral; the accent lands only where
14
+ attention belongs — the active element, the single measure, the selected
15
+ state. Peers get distinct colors only when identity must persist; a chart
16
+ where everything is colorful says nothing.
17
+ - Motion explains change: 150–400ms, ease-out, animating a state the user
18
+ caused. Decorative motion — pulsing, looping, sliding for its own sake —
19
+ ages in seconds. Honor `prefers-reduced-motion`.
20
+ - Format numbers for reading: thousands separators, a unit, a stable number
21
+ of decimals, `font-variant-numeric: tabular-nums` anywhere digits change
22
+ in place.
23
+ - The initial state is already the answer: sensible defaults, a drawn curve,
24
+ a populated grid — never an empty canvas waiting for input.
25
+ - Cut structure that carries no data: gridlines you don't read, borders that
26
+ outline nothing, a legend for a single series.
27
+ - Gradient fills follow the job: a single-measure area chart takes the soft
28
+ series-color gradient by default — it reads as polish. A multi-series
29
+ comparison, or any chart the user reads for precise values, skips fills
30
+ entirely: clarity outranks beauty the moment they compete.
31
+
32
+ ## Theme tokens
33
+
34
+ Every color comes from a variable or a `light-dark(<light>, <dark>)` pair;
35
+ the card bridges the host palette into the frame and tracks theme switches live.
36
+
37
+ - Surfaces and text: `--background`, `--foreground`, `--card`,
38
+ `--card-foreground`, `--muted-foreground`, `--border`.
39
+ - Accent: `--primary`, `--primary-foreground`.
40
+ - Series palette: `--viz-series-1` … `--viz-series-6`; `--viz-series-1` is
41
+ the single measure / active element.
42
+ - SVG text takes `fill: var(--foreground)` at 11px effective size or larger.
43
+
44
+ ## Base classes
45
+
46
+ The frame stylesheet ships these utilities — prefer them over re-implementing
47
+ the same controls, and add custom CSS only where they run out.
48
+
49
+ - `.card` — the single framed surface, for a numeric summary or a bounded
50
+ interactive area. Charts and the fragment root stay transparent and
51
+ unframed; cards never nest.
52
+ - `.viz-stat` / `.viz-stat-value` — one summary figure with a muted label.
53
+ - `.viz-grid` — equal columns that collapse gracefully on narrow widths.
54
+ - `.viz-row` — a wrapping inline group of related values or actions.
55
+ - `.viz-controls` — the control strip governing one visualization.
56
+ - `.viz-badge` — a small display-only accent pill (not a button).
57
+ - `.btn` / `.btn-primary` / `.btn-ghost` — native `<button>` styling; one
58
+ primary action per group.
59
+ - `.form-label`, `.form-control`, `.form-select`, `.form-check`,
60
+ `.form-switch` — native form elements only.
61
+ - `.text-small` for secondary annotations; `.table-responsive` only when a
62
+ table genuinely cannot fit.
63
+
64
+ ## Layout
65
+
66
+ - Build the least UI that serves the request: no invented search boxes,
67
+ filters, reset buttons, KPI strips, or status cards. One mechanism per
68
+ piece of state; controls the user asked for and no more.
69
+ - Span the available card width, and let content reflow — stack and wrap on
70
+ narrow widths. Avoid fixed outer widths, inner scrollbars,
71
+ `position: fixed`, and viewport-height sizing; the card grows to fit the
72
+ content's height.
73
+ - Use semantic elements and native keyboard-accessible controls; leave tab
74
+ order and focus outlines alone.