From 1b0fc48d36bcbc6eba004e1e44f33d1263a90ada Mon Sep 17 00:00:00 2001 From: Ivan Cheung Date: Sun, 7 Jun 2026 12:13:25 -0400 Subject: [PATCH 1/6] docs(plan): add latency/token reduction experimentation plan Experiment to reduce diagram-generation latency and tokens across three runners (Claude Code, AGY, OpenCode), measured on VMs against baseline vs a combined-fixes build. Decisions baked in: one shared model across runners and a pilot-first first pass. --- ...6-07-latency-token-experimentation-plan.md | 271 ++++++++++++++++++ 1 file changed, 271 insertions(+) create mode 100644 docs/plans/2026-06-07-latency-token-experimentation-plan.md diff --git a/docs/plans/2026-06-07-latency-token-experimentation-plan.md b/docs/plans/2026-06-07-latency-token-experimentation-plan.md new file mode 100644 index 0000000..2b9ceab --- /dev/null +++ b/docs/plans/2026-06-07-latency-token-experimentation-plan.md @@ -0,0 +1,271 @@ +# Latency + token-reduction experimentation plan + +**Branch:** `perf/reduce-latency` · **Date:** 2026-06-07 · **Status:** draft + +## 1. Goal + +Reduce the **end-to-end cost of generating a termchart diagram from a coding agent**, +measured on two axes: + +1. **Latency** — wall-clock from "agent decides to draw" to "diagram is rendered + inline / visible in the viewer." +2. **Tokens** — input + output (+ cache) tokens the runner spends to produce a + correct diagram, including context the skills inject and any retries. + +We test this across three **runners** (the coding agents that consume termchart's +`AGENTS.md` / skills): **Claude Code**, **Antigravity (AGY)**, and **OpenCode**. +Each runner is installed on a VM together with termchart and a candidate set of +**experimentation fixes**, then driven through a fixed diagram-task suite. We compare +each fix (and the combined build) against the current `master` baseline. + +Success = a fix ships only if it cuts median tokens/diagram **or** p50 latency by a +pre-registered threshold (§9) with **no regression** in diagram correctness. + +## 2. Where cost actually comes from (grounded) + +Findings from mapping the repo (file:line anchors). These define the levers. + +### 2a. Token cost — agent-facing context + +The runner only pays for what it loads. Always-on cost is small; on-demand detail +files and examples dominate. + +| Surface | File | Bytes | When it enters context | +|---|---|---:|---| +| Cross-tool guide (always) | `AGENTS.md` | 3,379 | session start | +| Plugin manifest desc (always) | `plugin/.claude-plugin/plugin.json` | 1,106 | indexed | +| Router skill | `plugin/skills/diagram-recipes/SKILL.md` | 15,993 | on skill invoke | +| Terminal skill | `plugin/skills/termchart/SKILL.md` | 8,933 | on skill invoke | +| Detail: flow | `diagram-recipes/flow.md` | 13,949 | when type chosen | +| Detail: component | `diagram-recipes/component.md` | 13,110 | when type chosen | +| Detail: status/state | `diagram-recipes/status-state.md` | 10,322 | when type chosen | +| Example (worst) | `examples/raci-matrix.component.json` | 45,731 | when agent reads it | +| Example (worst) | `examples/risk-matrix.component.json` | 43,226 | when agent reads it | + +- `plugin/skills/diagram-recipes/examples/` is **~298 KB / 45 files** — ~78% of the + plugin's bytes. The two `*-matrix.component.json` files (~89 KB combined) are + **pretty-printed** trees (1,495 and 1,367 lines). +- Realistic worst-case load per diagram ≈ router SKILL (16 KB) + one detail `.md` + (~13 KB) + one example (often <1 KB, up to 45 KB). +- `AGENTS.md` has a **stale exit-code claim** ("push/status exit 3"); code returns + `4` (`EXIT_NO_VIEWER`, `packages/cli/src/viewer-detect.ts:15`). Cheap correctness fix. + +### 2b. Latency cost — render + network + +| Source | Where | Cost | +|---|---|---| +| A* edge router (flow/state) | `beautiful-mermaid` via `packages/cli/src/render.ts:103-116` | super-linear; ~24 nodes ≈130 ms, 25 = cliff (~30 s). Guarded by `MAX_FLOWCHART_NODES=24` (`packages/cli/src/lint.ts:93`) | +| `--fit` re-render | `packages/cli/src/render.ts:156-181` | up to **10×** full renders (sweeps padding) | +| `begin` round-trips | `packages/cli/src/begin.ts:85-113` | **3 sequential POSTs** before any content | +| `push` vs `patch` | `packages/viewer/src/server.ts:384-421` | re-push resends whole tree; patch sends ops. README claims **18–190× fewer tokens/change** (`packages/viewer/README.md:69-73`) | +| Server-side flow geometry | `packages/viewer/src/flow-geometry.ts:152-317` | 2–3 dagre passes per flow/panes push (incl. alt-direction recursion `:305-314`) | +| First paint | `packages/viewer/src/registry.ts:12-21` | lazy per-type chunk download (mermaid ~3.7 MB, vega ~0.4 MB, flow 446 KB) | +| CLI install | `plugin/hooks/ensure-cli.sh` (SessionStart) | one-time `npm i -g` on first use (can be 10s+) | + +### 2c. What we already have to build on + +- `scripts/corpus_run.py` already times every render deterministically + (`elapsed_ms`, `--color none`, `LANG=C.UTF-8`, piped stdout) and selects the + binary via `TERMCHART_BIN`. This is the render-latency measurement spine. +- `corpus//*.mmd` (8 industries + edge-cases) = ready ground-truth inputs. +- An `act` + podman GHA path (`corpus/README.md:61-88`) already exercises the real + `npm pack` + `npm i -g` install in a VM — reuse for the "faithful install" check. + +## 3. Independent variables — the experimentation fixes (treatments) + +Each fix is a branch/feature-flag on top of `master`, tested in isolation (ablation) +and combined. Grouped by the lever they pull. + +### Token-reduction fixes +- **T1 — Minify examples.** Ship `examples/*.json` minified (and/or move pretty + copies to a non-loaded path). Target the two matrices first (~89 KB → est. ~35 KB). +- **T2 — Slim the router + detail docs.** Cut `diagram-recipes/SKILL.md` and the + detail `.md` files to the decision-critical subset; push verbose prose to a + "deep dive" file the agent rarely needs. +- **T3 — Tighten routing.** Make `SKILL.md` route the agent to **exactly one** detail + file + **one** example, reducing over-reading (measure files-read/diagram). +- **T4 — Patch-first guidance.** Make the skill steer edits to `patch` instead of + re-`push` (biggest per-edit token lever). +- **T5 — Fix stale `AGENTS.md`** exit-code text (correctness; avoids agent confusion + retries). + +### Latency-reduction fixes +- **L1 — Single-shot `begin`.** Collapse the 3 POSTs into one endpoint that fans out + server-side (placeholder + focus + loader) → 1 RTT. +- **L2 — Render fast-path.** Skip / cap the `--fit` 10× re-render with a single-pass + width estimate; measure vs current. +- **L3 — Pre-warmed CLI.** Bake `termchart` into the VM image so the SessionStart + `npm i -g` never runs during a measured task. +- **L4 — Viewer chunk pre-warm.** Pre-fetch the per-type renderer chunk so first + paint isn't gated on a cold download. +- **L5 — Geometry pass reduction.** Cache/skip the alt-direction dagre recursion in + `flow-geometry.ts` for the common case. + +### Combined +- **C1 — "fixes" build.** All accepted T*/L* together = the headline candidate. + +> Baseline (control) = current `master` at the cloned HEAD, with the same VM image +> minus the treatments. + +## 4. Experimental design + +Factorial, with repetitions for LLM nondeterminism. + +- **Factor A — Runner:** {Claude Code, AGY, OpenCode}. +- **Factor B — Condition:** {baseline, T1…T5, L1…L5, C1}. +- **Factor C — Task:** N tasks from the diagram-task suite (§6). +- **Repetitions:** k ≥ 5 per cell (LLM output varies; we report medians + CIs). +- **Model control (decided):** hold the **underlying model constant across all three + runners**, pinned via the proxy, so we measure *runner* differences, not model + differences. Native-default models are out of scope for this pass. +- **Order:** randomize task/condition order per runner; pin everything else. + +Total runs ≈ `|A| × |B| × |C| × k`. + +**First pass (decided): pilot.** 3 runners × {baseline, C1 combined-fixes} × 5 tasks × +5 reps = **150 runs** to validate the harness and get a baseline-vs-combined signal +*before* committing to the full T*/L* ablation. The ablation (P2) runs only after the +pilot proves the harness and shows C1 moves the metrics. + +## 5. Metrics + instrumentation + +### Measurement spine: an LLM proxy +Route **all three runners** through one logging proxy (e.g. LiteLLM / a recording +HTTP proxy). This gives provider-neutral ground truth per task: +- prompt tokens, completion tokens, cache-read/write tokens +- time-to-first-token, total completion time +- call count (≈ tool-use turns) + +This avoids each runner's idiosyncratic token accounting and makes cross-runner +numbers comparable. Tag every request with `run_id` (runner × condition × task × rep). + +### termchart-side metrics +- Render ms + exit code via the `corpus_run.py` path (extend to emit per-call JSON). +- Viewer: instrument server log for endpoint, RTT, and geometry-pass count + (`begin` = 3 vs L1 = 1; flow push dagre passes). +- Files-read-per-diagram (from runner transcript) → context-bytes loaded. + +### Primary metrics +1. **Median tokens / correct diagram** (input+output, cache split out). +2. **p50 / p95 end-to-end latency** to first *correct* diagram. + +### Secondary metrics +- render ms, RTT count, retry count, success rate (lint/validate pass), + edits-via-patch ratio, cold-vs-warm install time, first-paint time. + +### Correctness gate (so we don't "win" by producing junk) +A diagram counts as **correct** if: CLI exits 0 (`render`) or validate passes +(`push`), the corpus rectangularity invariant holds (`corpus_run.py:112-129`), and a +lightweight rubric/LLM-judge confirms it matches the task intent. + +## 6. Workload — the diagram-task suite + +Two tiers: + +- **Tier A (render-only, deterministic):** reuse `corpus//*.mmd` through + `corpus_run.py` to measure pure render latency per condition (no runner, no LLM). + Cheap, high-N, isolates L2/render fixes. +- **Tier B (agent-in-the-loop):** ~15–20 natural-language prompts that *elicit* a + diagram, one per recipe category (flow / component / vegalite / panes / status), + e.g. "Draw the auth call graph for `packages/cli`", "Compare these 3 plans as a + table", "Show this sprint's burndown", "Diagram this stacktrace". Each task has a + ground-truth reference (a corpus fixture or a stored gold diagram) + a rubric. + Tier B is where token cost and routing/skill fixes (T1–T4) actually move. + +## 7. VM infrastructure + +Default to **GCE** (gcloud SDK is already present; aligns with the GCS/Vertex pattern +used by the sibling `agent-generator` repo). + +### Image +One base image (`e2-standard-4`, fixed shape so render-CPU timings are comparable): +- Node 20 + Python 3.11 +- All three runners installed, pinned versions +- termchart built from a given git ref (the **condition** = a branch/flag) +- A local viewer (`node packages/viewer/dist/server.js`, `PUSH_TOKEN=dev`) on loopback +- The LLM proxy +- `termchart` pre-installed on PATH (so L3 / SessionStart install never runs mid-task) + +### Orchestration +- Each **cell** runs in a clean sandbox (fresh VM or bwrap, mirroring + `agent-generator`'s isolation) to avoid cache contamination between conditions. +- A startup script: checkout ref → `npm ci && npm run build` → start viewer + proxy → + run the suite headlessly → upload `report.json` + transcripts to a GCS bucket + (e.g. `gs:///termchart-latency//`). +- Headless runner invocation: Claude Code `claude -p`, OpenCode `opencode run`, AGY + headless mode — each fed the same task prompt and the same proxy env. +- Keep the **viewer on loopback** to isolate server CPU from network jitter; add one + separate "realistic RTT" condition to measure round-trip-sensitive fixes (L1). +- Reuse the existing `act` + podman workflow for the faithful `npm pack` install check. + +### Reproducibility +Pin and record per run: runner version, model version/id, termchart commit, Node +version, lockfile hash, VM machine type, image id. + +## 8. Harness + +Extend, don't replace: + +1. **`corpus_run.py` → emit per-call JSON metrics** (already has `elapsed_ms`, + `TERMCHART_BIN`). Add a `--metrics-out` that writes one record per render. Tier A + done. +2. **New `scripts/agent_run.py`** (Tier B): for each cell, set proxy + viewer env, + invoke the runner headlessly with a task, collect (a) proxy log, (b) runner + transcript, (c) termchart timings, (d) correctness verdict; write `report.json` + in the same shape as `corpus_run.py` for a shared analysis path. +3. **Aggregator**: roll up all `report.json` across cells → per-condition deltas vs + baseline with bootstrap CIs over repetitions. + +## 9. Analysis + decision gate + +- Compare each condition to baseline per (runner, metric). Use bootstrap CIs over the + k repetitions; report medians (LLM tails are heavy → medians over means). +- **Ship gate (pre-registered):** a fix is accepted if it reduces median + tokens/diagram **or** p50 latency by **≥15%** with a non-overlapping CI **and** no + drop in correctness rate (and no >2% regression on the other axis). +- **Dogfood the output:** render the results *with termchart* — a Vega-Lite bar chart + of tokens/diagram by condition×runner, a flow diagram of the measured pipeline, and + a component table of the ship/no-ship verdicts. (Also serves as an integration test.) + +## 10. Risks + confounds + +- **LLM nondeterminism** → repetitions + medians + CIs; pin model versions. +- **Cross-runner token accounting differences** → the proxy is the single source of truth. +- **VM noise on render CPU** → fixed machine shape, multiple samples, report medians. +- **Model/version drift over the run window** → pin and timestamp; run a condition's + full matrix close together. +- **Network variance for viewer RTT** → loopback isolates server CPU; one explicit + RTT condition for L1. +- **"Winning" by degrading quality** → the correctness gate (§5) blocks this. +- **Runner headless-mode gaps** (AGY especially) → validate headless invocation in the + pilot before committing to the full matrix. + +## 11. Phases + milestones + +- **P0 — Foundation:** VM image, proxy spine (single shared model), extend + `corpus_run.py`, write `agent_run.py`, wire GCS upload. + **Status: landed** in `scripts/experiments/` (+ `corpus_run.py --metrics-out`). + Harness, runner/condition config, proxy config + callback, aggregator, the pilot + task suite, and GCE `startup.sh`/`provision.sh` are in place; `--dry-run` produces + a full report and 20 unit tests pass. See `scripts/experiments/README.md`. +- **P1 — Pilot (first pass, decided):** 3 runners × {baseline, C1} × 5 tasks × 5 reps + = 150 runs. Exit: clean report + a baseline-vs-C1 signal. **Gate to continue:** C1 + shows a directional improvement and the harness is stable across all 3 runners. +- **P2 — Ablation:** each T*/L* fix vs baseline; record deltas + CIs. (Only after P1.) +- **P3 — Combined (C1) full:** the accepted fixes together at full N; validate no + interaction regressions. +- **P4 — Report + ship:** apply the §9 gate, dogfood the results with termchart, + open PRs for the winning fixes. + +## 12. Decisions + +**Resolved** +- **Model strategy** — one shared model held constant across all three runners (§4). +- **First pass** — pilot: {baseline, C1} only, then gate to the ablation (§11 P1). + +**Still open** +1. **Cloud target** — GCE assumed (gcloud SDK present). OK, or another provider / local VMs? +2. **Which shared model** — pick the single model id all runners use, and confirm API + budget/keys cover all three runners hitting it via the proxy. +3. **Runner versions** — pin to specific releases of Claude Code / AGY / OpenCode? +4. **Results store** — confirm the GCS bucket (or reuse `agent-generator`'s convention). From 1f688ecbab1bf1cfdcc378f8dad590fd5c43e791 Mon Sep 17 00:00:00 2001 From: Ivan Cheung Date: Sun, 7 Jun 2026 12:13:26 -0400 Subject: [PATCH 2/6] feat(experiments): P0 harness for the latency/token experiment - corpus_run.py: --metrics-out emits per-render JSONL (Tier A spine) + test - scripts/experiments: agent_run (Tier B orchestrator, --dry-run safe), metrics (RunRecord, median/p95/bootstrap CI, proxy-log parse), config (runners/conditions/tasks), aggregate (pool per-VM streams) - pilot task suite, LiteLLM proxy config + spend logger callback - GCE vm/startup.sh + vm/provision.sh (env-parameterized) - pytest suite (20 tests, dry-run, no network) --- scripts/corpus_run.py | 26 ++ scripts/experiments/README.md | 83 +++++ scripts/experiments/agent_run.py | 299 ++++++++++++++++++ scripts/experiments/aggregate.py | 82 +++++ scripts/experiments/config.py | 166 ++++++++++ scripts/experiments/metrics.py | 228 +++++++++++++ scripts/experiments/proxy/litellm.config.yaml | 33 ++ scripts/experiments/proxy/spend_logger.py | 68 ++++ scripts/experiments/tasks/pilot.jsonl | 5 + scripts/experiments/test_experiments.py | 203 ++++++++++++ scripts/experiments/vm/provision.sh | 55 ++++ scripts/experiments/vm/startup.sh | 106 +++++++ scripts/test_corpus_run.py | 32 ++ 13 files changed, 1386 insertions(+) create mode 100644 scripts/experiments/README.md create mode 100755 scripts/experiments/agent_run.py create mode 100755 scripts/experiments/aggregate.py create mode 100644 scripts/experiments/config.py create mode 100644 scripts/experiments/metrics.py create mode 100644 scripts/experiments/proxy/litellm.config.yaml create mode 100644 scripts/experiments/proxy/spend_logger.py create mode 100644 scripts/experiments/tasks/pilot.jsonl create mode 100644 scripts/experiments/test_experiments.py create mode 100755 scripts/experiments/vm/provision.sh create mode 100755 scripts/experiments/vm/startup.sh diff --git a/scripts/corpus_run.py b/scripts/corpus_run.py index 4731e3b..e879b32 100644 --- a/scripts/corpus_run.py +++ b/scripts/corpus_run.py @@ -205,6 +205,27 @@ def run_fixture(bin_argv: list[str], mmd: Path) -> dict: } +def write_metrics_jsonl(results: list[dict], bin_str: str, path: Path) -> None: + """Append one JSON record per render to ``path`` (JSONL). + + This is the Tier-A (render-only) latency spine for the latency/token + experiment (docs/plans/2026-06-07-latency-token-experimentation-plan.md): a + flat, append-friendly stream of per-render timings keyed so many runs can be + concatenated and grouped by ``run_id``. Deliberately excludes ``stdout`` so the + file stays small and shareable. + """ + run_id = os.environ.get("EXPERIMENT_RUN_ID", "") + ts = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()) + keep = ("industry", "name", "path", "argv_tail", "exit", "expected_exit", + "outcome", "surprise", "rectangular", "elapsed_ms") + path.parent.mkdir(parents=True, exist_ok=True) + with path.open("a", encoding="utf-8") as fh: + for r in results: + rec = {"run_id": run_id, "ts": ts, "tier": "render", "binary": bin_str} + rec.update({k: r[k] for k in keep}) + fh.write(json.dumps(rec) + "\n") + + def is_candidate(r: dict) -> bool: """A clean, gallery-opted-in, non-surprising render.""" return r["exit"] == 0 and r["gallery_optin"] and not r["surprise"] and r["stdout"].strip() != "" @@ -326,6 +347,9 @@ def main(argv: list[str] | None = None) -> int: ap.add_argument("--strict", action="store_true", help="also fail if any fixture did not cleanly render") ap.add_argument("--corpus", default=str(CORPUS_DIR), help="corpus directory") + ap.add_argument("--metrics-out", default=None, + help="append per-render JSONL metrics to this path " + "(latency experiment Tier-A spine)") args = ap.parse_args(argv) bin_str = os.environ.get("TERMCHART_BIN", DEFAULT_BIN) @@ -340,6 +364,8 @@ def main(argv: list[str] | None = None) -> int: results = [run_fixture(bin_argv, f) for f in fixtures] candidates = write_candidates(results) write_reports(results, candidates, bin_str) + if args.metrics_out: + write_metrics_jsonl(results, bin_str, Path(args.metrics_out)) # Console summary. surprises = [r for r in results if r["surprise"]] diff --git a/scripts/experiments/README.md b/scripts/experiments/README.md new file mode 100644 index 0000000..e02e24b --- /dev/null +++ b/scripts/experiments/README.md @@ -0,0 +1,83 @@ +# termchart latency/token experiment harness (P0) + +Foundation for the latency + token-reduction experiment. Drives the three runners +(Claude Code / AGY / OpenCode) through a diagram-task suite under each condition +(baseline vs combined fixes), measuring **tokens** (via a shared-model proxy) and +**latency**, then emits per-condition deltas with bootstrap CIs. + +Plan: [`docs/plans/2026-06-07-latency-token-experimentation-plan.md`](../../docs/plans/2026-06-07-latency-token-experimentation-plan.md) + +## Layout + +``` +scripts/ + corpus_run.py Tier A (render-only) timings; now writes --metrics-out JSONL + experiments/ + config.py runners, conditions, env defaults, task loader + metrics.py RunRecord + stats (median/percentile/bootstrap CI) + proxy-log parse + agent_run.py Tier B orchestrator (cells -> records -> report); --dry-run safe + aggregate.py pool many metric streams (per-VM) -> one verdict + test_experiments.py pytest-native tests (no runners, no network) + tasks/pilot.jsonl the 5 pilot diagram tasks (+ rubrics) + proxy/litellm.config.yaml one shared model + JSONL spend log + proxy/spend_logger.py LiteLLM callback in metrics.parse_proxy_log's shape + vm/startup.sh GCE bootstrap+run for one condition set + vm/provision.sh create one VM per runner (SPOT, fixed shape) +``` + +## Decisions baked in (plan §4 / §12) + +- **One shared model** across all runners, set via `EXPERIMENT_MODEL` and aliased in + the proxy to `shared-model` (what every runner requests). +- **First pass = pilot**: `{baseline, c1}` only (default `--conditions`). + +## Quick start + +### Dry run (no runners, no network) — validates the whole pipeline +```bash +python3 scripts/experiments/agent_run.py --dry-run --reps 5 \ + --out out/experiment --metrics-out out/experiment/agent.jsonl +# -> out/experiment/report.md (synthetic numbers, clearly marked DRY RUN) +``` + +### Tests +```bash +python3 -m pytest scripts/experiments/test_experiments.py scripts/test_corpus_run.py -q +``` + +### Tier A (render-only timings; needs a built CLI) +```bash +npm run build +python3 scripts/corpus_run.py --metrics-out out/render.jsonl +``` + +### Real pilot on GCE +```bash +EXPERIMENT_MODEL= \ +EXPERIMENT_GCS_BUCKET=gs:///termchart-latency \ +scripts/experiments/vm/provision.sh +# each VM runs vm/startup.sh -> uploads out/ to gs://...// + +# then pool the streams: +python3 scripts/experiments/aggregate.py 'out/**/agent.jsonl' --out out/aggregate +``` + +## Measurement model + +- **Tokens + latency** come from the LiteLLM proxy spend log (provider-neutral, same + for every runner), correlated to each cell by `run_id` + (`EXPERIMENT_RUN_ID` / `x-run-id` header). +- **Render ms** comes from `corpus_run.py` (Tier A) and, in P1, from termchart-side + instrumentation in Tier B. +- **Cost metrics are compared over *successful* diagrams only**; `success_rate` is + tracked separately so a fix can't "win" by degrading quality (plan §5/§9 gate). + +## Status / what's stubbed for P1 + +- `agent_run.invoke_runner` (real mode) captures wall-clock + proxy usage and a coarse + success signal. termchart-side `render_ms`/`rtt_count` capture and the **rubric + LLM-judge** for correctness are wired in P1. +- Runner install + headless invocation in `vm/startup.sh` are best-effort; **AGY** + headless is the flagged risk to validate first in the pilot. +- Still-open inputs (plan §12): cloud target (GCE assumed), the shared model id + + provider creds, runner version pins, GCS bucket. diff --git a/scripts/experiments/agent_run.py b/scripts/experiments/agent_run.py new file mode 100755 index 0000000..eed525c --- /dev/null +++ b/scripts/experiments/agent_run.py @@ -0,0 +1,299 @@ +#!/usr/bin/env python3 +"""termchart latency/token experiment — Tier B agent-in-the-loop harness. + +Drives each runner (Claude Code / AGY / OpenCode) through the diagram-task suite +under each condition (baseline vs combined fixes), capturing tokens (via the proxy) +and latency per cell, then writes a ``report.{json,md}`` and appends per-cell JSONL +metrics. Mirrors ``corpus_run.py``'s report shape so analysis is shared. + +Two modes: + * ``--dry-run`` (default-safe): no runners, no network. Synthesizes deterministic + records so the whole pipeline (cells -> records -> summary -> report) is + exercised and testable. Synthetic numbers are clearly marked ``dry_run: true``. + * real: invokes each runner headlessly against the shared-model proxy. Requires + ``EXPERIMENT_MODEL`` and a reachable proxy/viewer. Token/latency come from the + proxy spend log (``--proxy-log``). + +Design: docs/plans/2026-06-07-latency-token-experimentation-plan.md +""" +from __future__ import annotations + +import argparse +import json +import os +import random +import subprocess +import sys +import time +from pathlib import Path + +import config +import metrics +from metrics import RunRecord + +REPORT_METRICS = ["tokens_total", "tokens_in", "tokens_out", + "latency_ms_total", "render_ms", "rtt_count", "retries"] + +# Synthetic cost model for --dry-run: (tokens_in, tokens_out, latency_s, rtt) by +# category, at BASELINE. The c1 condition applies the multipliers below. +_BASE_COST = { + "flow": (5200, 1400, 19.0, 4), + "component": (6800, 1700, 23.0, 4), + "vegalite": (4200, 1100, 15.0, 4), + "status": (2600, 600, 9.0, 3), +} +_DEFAULT_COST = (3800, 900, 14.0, 3) +# c1 = combined fixes: fewer context tokens (T1-T3), fewer round-trips (L1), faster. +_C1 = {"tokens": 0.68, "latency": 0.74, "rtt": 0.5} +_RUNNER_MULT = {"claude-code": 1.00, "opencode": 1.06, "agy": 1.12} + + +def cell_run_id(experiment_id: str, runner: str, cond: str, task: str, rep: int) -> str: + return f"{experiment_id}:{runner}:{cond}:{task}:r{rep}" + + +def synthesize(experiment_id: str, runner: config.Runner, cond: config.Condition, + task: config.Task, rep: int, model: str) -> RunRecord: + """Deterministic fake record so the pipeline is testable without runners.""" + rid = cell_run_id(experiment_id, runner.name, cond.name, task.id, rep) + rng = random.Random(rid) # seeded by identity -> stable across processes + ti, to, lat_s, rtt = _BASE_COST.get(task.category, _DEFAULT_COST) + rmult = _RUNNER_MULT.get(runner.name, 1.0) + is_c1 = cond.name == "c1" + tok_k = (_C1["tokens"] if is_c1 else 1.0) * rmult + lat_k = (_C1["latency"] if is_c1 else 1.0) * rmult + + def jitter(x: float, spread: float = 0.10) -> float: + return x * (1.0 + rng.uniform(-spread, spread)) + + tokens_in = int(jitter(ti) * tok_k) + tokens_out = int(jitter(to) * tok_k) + latency_ms = int(jitter(lat_s) * lat_k * 1000) + rtt_count = max(1, int(round((rtt * (_C1["rtt"] if is_c1 else 1.0))))) + render_ms = int(jitter(120 if task.surface == "terminal" else 60)) + return RunRecord( + run_id=rid, runner=runner.name, condition=cond.name, task_id=task.id, + category=task.category, surface=task.surface, rep=rep, + success=True, outcome="ok", + tokens_in=tokens_in, tokens_out=tokens_out, + tokens_cache_read=int(tokens_in * 0.3), tokens_cache_write=0, + latency_ms_total=latency_ms, latency_ms_first_token=int(latency_ms * 0.15), + llm_calls=rng.randint(2, 5), + termchart_calls=rng.randint(1, 3), render_ms=render_ms, + rtt_count=rtt_count, retries=0 if is_c1 else rng.randint(0, 1), + files_read=rng.randint(2, 4) if not is_c1 else rng.randint(1, 2), + context_bytes=int(jitter(30000) * (0.6 if is_c1 else 1.0)), + model=model, dry_run=True, + ) + + +def invoke_runner(experiment_id: str, runner: config.Runner, cond: config.Condition, + task: config.Task, rep: int, *, model: str, proxy_url: str, + proxy_key: str, proxy_log: Path | None, timeout_s: int) -> RunRecord: + """Real invocation: run the runner headlessly, then read usage from the proxy log. + + P0 scope: captures wall-clock latency + proxy token usage and a coarse success + signal (exit 0 and >=1 LLM call). termchart-side render_ms/rtt instrumentation + and the rubric LLM-judge are wired in P1 (see plan §5/§8). This path is not + exercised by the unit tests (no runners installed); validate it in the pilot. + """ + rid = cell_run_id(experiment_id, runner.name, cond.name, task.id, rep) + env = runner.build_env(dict(os.environ), proxy=proxy_url, model=model, + api_key=proxy_key) + env["EXPERIMENT_RUN_ID"] = rid + env["TERMCHART_VIEWER_URL"] = config.VIEWER_URL + env["TERMCHART_VIEWER_TOKEN"] = config.VIEWER_TOKEN + argv = runner.build_argv(task.prompt, model) + + start = time.perf_counter() + err = "" + rc = -1 + try: + proc = subprocess.run(argv, capture_output=True, text=True, + timeout=timeout_s, env=env) + rc = proc.returncode + except FileNotFoundError: + err = f"runner not on PATH: {runner.bin}" + except subprocess.TimeoutExpired: + err = f"timeout after {timeout_s}s" + wall_ms = int((time.perf_counter() - start) * 1000) + + usage = metrics.parse_proxy_log(proxy_log, rid) if proxy_log else {} + success = (rc == 0 and not err and usage.get("llm_calls", 0) > 0) + return RunRecord( + run_id=rid, runner=runner.name, condition=cond.name, task_id=task.id, + category=task.category, surface=task.surface, rep=rep, + success=success, outcome="ok" if success else (err or f"exit {rc}"), + tokens_in=usage.get("tokens_in", 0), tokens_out=usage.get("tokens_out", 0), + tokens_cache_read=usage.get("tokens_cache_read", 0), + tokens_cache_write=usage.get("tokens_cache_write", 0), + latency_ms_total=usage.get("latency_ms_total", 0) or wall_ms, + latency_ms_first_token=usage.get("latency_ms_first_token", 0), + llm_calls=usage.get("llm_calls", 0), + model=model, dry_run=False, error=err, + ) + + +def build_cells(runners, conditions, tasks, reps): + for runner in runners: + for cond in conditions: + for task in tasks: + for rep in range(reps): + yield runner, cond, task, rep + + +def write_reports(records: list[RunRecord], out_dir: Path, *, experiment_id: str, + model: str, dry_run: bool) -> dict: + out_dir.mkdir(parents=True, exist_ok=True) + summary = metrics.summarize(records, REPORT_METRICS) + deltas = metrics.deltas_vs_baseline(summary, REPORT_METRICS) + report = { + "experiment_id": experiment_id, + "model": model, + "dry_run": dry_run, + "total": len(records), + "metrics": REPORT_METRICS, + "summary": summary, + "deltas_vs_baseline": deltas, + "results": [r.to_dict() for r in records], + } + (out_dir / "report.json").write_text(json.dumps(report, indent=2) + "\n", + encoding="utf-8") + (out_dir / "report.md").write_text(render_markdown(report), encoding="utf-8") + return report + + +def _fmt_pct(p: float) -> str: + return f"{p:+.1f}%" + + +def render_markdown(report: dict) -> str: + md: list[str] = [] + md.append("# termchart latency/token experiment — report\n") + md.append(f"- Experiment: `{report['experiment_id']}`") + md.append(f"- Model: `{report['model'] or '(unset)'}`" + f"{' · **DRY RUN (synthetic)**' if report['dry_run'] else ''}") + md.append(f"- Cells: **{report['total']}**\n") + + per = report["summary"]["per_condition"] + md.append("## Per-condition (successful diagrams)\n") + md.append("| condition | n | success | median tokens | p95 tokens | median latency (s) |") + md.append("|---|---|---|---|---|---|") + for cond, c in sorted(per.items()): + md.append( + f"| {cond} | {c['n']} | {c['success_rate']*100:.0f}% " + f"| {c['tokens_total']['median']:.0f} | {c['tokens_total']['p95']:.0f} " + f"| {c['latency_ms_total']['median']/1000:.1f} |" + ) + md.append("") + + deltas = report["deltas_vs_baseline"] + if deltas: + md.append("## Δ vs baseline (median; negative = improvement)\n") + md.append("| condition | tokens | latency | render | rtt | CI disjoint (tokens) |") + md.append("|---|---|---|---|---|---|") + for cond, dm in sorted(deltas.items()): + md.append( + f"| {cond} | {_fmt_pct(dm['tokens_total']['pct_change'])} " + f"| {_fmt_pct(dm['latency_ms_total']['pct_change'])} " + f"| {_fmt_pct(dm['render_ms']['pct_change'])} " + f"| {_fmt_pct(dm['rtt_count']['pct_change'])} " + f"| {'yes' if dm['tokens_total']['ci_disjoint'] else 'no'} |" + ) + md.append("") + + md.append("## Per condition × runner (median tokens / median latency s)\n") + pcr = report["summary"]["per_condition_runner"] + md.append("| condition | runner | n | tokens | latency (s) |") + md.append("|---|---|---|---|---|") + for cond in sorted(pcr): + for runner in sorted(pcr[cond]): + c = pcr[cond][runner] + md.append(f"| {cond} | {runner} | {c['n']} " + f"| {c['tokens_total']['median']:.0f} " + f"| {c['latency_ms_total']['median']/1000:.1f} |") + md.append("") + return "\n".join(md) + + +def write_metrics_jsonl(records: list[RunRecord], path: Path) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + with path.open("a", encoding="utf-8") as fh: + for r in records: + rec = {"tier": "agent", **r.to_dict()} + fh.write(json.dumps(rec) + "\n") + + +def main(argv: list[str] | None = None) -> int: + ap = argparse.ArgumentParser(description="termchart Tier-B agent experiment runner") + ap.add_argument("--tasks", default="pilot.jsonl", + help="task suite (name under tasks/ or a path)") + ap.add_argument("--runners", default="claude-code,opencode,agy", + help="comma-separated runner names") + ap.add_argument("--conditions", default="baseline,c1", + help="comma-separated condition names") + ap.add_argument("--reps", type=int, default=5, help="repetitions per cell") + ap.add_argument("--out", default=str(config.REPO_ROOT / "out" / "experiment"), + help="output directory for report.{json,md}") + ap.add_argument("--metrics-out", default=None, + help="append per-cell JSONL metrics here") + ap.add_argument("--experiment-id", default=None, + help="defaults to a UTC timestamp") + ap.add_argument("--model", default=config.SHARED_MODEL, + help="the single shared model (env EXPERIMENT_MODEL)") + ap.add_argument("--proxy-log", default=None, + help="LiteLLM spend-log path (real-mode token source)") + ap.add_argument("--timeout", type=int, default=600, help="per-cell timeout (s)") + ap.add_argument("--dry-run", action="store_true", + help="synthesize records; no runners, no network") + ap.add_argument("--no-dry-run", dest="dry_run", action="store_false") + ap.set_defaults(dry_run=True) + args = ap.parse_args(argv) + + experiment_id = args.experiment_id or time.strftime("%Y%m%dT%H%M%SZ", time.gmtime()) + + try: + runners = [config.RUNNERS[n.strip()] for n in args.runners.split(",") if n.strip()] + conditions = [config.CONDITIONS[n.strip()] for n in args.conditions.split(",") if n.strip()] + except KeyError as e: + print(f"unknown runner/condition: {e}", file=sys.stderr) + return 3 + tasks = config.load_tasks(args.tasks) + + if not args.dry_run and not args.model: + print("real mode requires --model / EXPERIMENT_MODEL (the shared model).", + file=sys.stderr) + return 3 + + proxy_log = Path(args.proxy_log) if args.proxy_log else None + records: list[RunRecord] = [] + for runner, cond, task, rep in build_cells(runners, conditions, tasks, args.reps): + if args.dry_run: + rec = synthesize(experiment_id, runner, cond, task, rep, args.model or "dry-model") + else: + rec = invoke_runner(experiment_id, runner, cond, task, rep, + model=args.model, proxy_url=config.PROXY_URL, + proxy_key=config.PROXY_API_KEY, proxy_log=proxy_log, + timeout_s=args.timeout) + records.append(rec) + flag = "" if rec.success else " FAIL" + print(f"[{rec.condition:<8}] {rec.runner:<12} {rec.task_id:<24} " + f"tok={rec.tokens_total:<6} lat={rec.latency_ms_total/1000:5.1f}s{flag}") + + out_dir = Path(args.out) + report = write_reports(records, out_dir, experiment_id=experiment_id, + model=args.model, dry_run=args.dry_run) + if args.metrics_out: + write_metrics_jsonl(records, Path(args.metrics_out)) + + n_fail = sum(1 for r in records if not r.success) + print(f"\n{len(records)} cells · {n_fail} failures · report: {out_dir / 'report.md'}") + deltas = report["deltas_vs_baseline"].get("c1", {}) + if deltas: + print(f"c1 vs baseline: tokens {_fmt_pct(deltas['tokens_total']['pct_change'])}, " + f"latency {_fmt_pct(deltas['latency_ms_total']['pct_change'])}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/experiments/aggregate.py b/scripts/experiments/aggregate.py new file mode 100755 index 0000000..03b5c5f --- /dev/null +++ b/scripts/experiments/aggregate.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +"""Aggregate many agent-experiment metric streams into one report. + +Reads one or more JSONL files produced by ``agent_run.py --metrics-out`` (each line +is an agent ``RunRecord`` with ``tier == "agent"``), pools them, and recomputes the +per-condition summary + Δ-vs-baseline with bootstrap CIs. Use this to combine the +per-VM / per-runner streams uploaded to GCS into a single pilot verdict. + + python aggregate.py out/**/agent.jsonl --out out/aggregate +""" +from __future__ import annotations + +import argparse +import json +import sys +from pathlib import Path + +import metrics +from agent_run import REPORT_METRICS, render_markdown +from metrics import RunRecord + +_FIELDS = set(RunRecord.__dataclass_fields__.keys()) # type: ignore[attr-defined] + + +def load_records(paths: list[str]) -> list[RunRecord]: + records: list[RunRecord] = [] + for path in paths: + p = Path(path) + if not p.exists(): + print(f"skip (missing): {p}", file=sys.stderr) + continue + for line in p.read_text(encoding="utf-8").splitlines(): + line = line.strip() + if not line: + continue + obj = json.loads(line) + if obj.get("tier") not in (None, "agent"): + continue # ignore render-tier rows in a mixed stream + kwargs = {k: v for k, v in obj.items() if k in _FIELDS} + records.append(RunRecord(**kwargs)) + return records + + +def main(argv: list[str] | None = None) -> int: + ap = argparse.ArgumentParser(description="aggregate agent-experiment metrics") + ap.add_argument("inputs", nargs="+", help="JSONL metric files (agent tier)") + ap.add_argument("--out", default="out/aggregate", help="output dir") + ap.add_argument("--experiment-id", default="aggregate") + args = ap.parse_args(argv) + + records = load_records(args.inputs) + if not records: + print("no agent records found", file=sys.stderr) + return 3 + + summary = metrics.summarize(records, REPORT_METRICS) + deltas = metrics.deltas_vs_baseline(summary, REPORT_METRICS) + report = { + "experiment_id": args.experiment_id, + "model": next((r.model for r in records if r.model), ""), + "dry_run": all(r.dry_run for r in records), + "total": len(records), + "metrics": REPORT_METRICS, + "summary": summary, + "deltas_vs_baseline": deltas, + "results": [r.to_dict() for r in records], + } + out = Path(args.out) + out.mkdir(parents=True, exist_ok=True) + (out / "aggregate.json").write_text(json.dumps(report, indent=2) + "\n", + encoding="utf-8") + (out / "aggregate.md").write_text(render_markdown(report), encoding="utf-8") + print(f"aggregated {len(records)} records -> {out / 'aggregate.md'}") + if "c1" in deltas: + d = deltas["c1"] + print(f"c1 vs baseline: tokens {d['tokens_total']['pct_change']:+.1f}%, " + f"latency {d['latency_ms_total']['pct_change']:+.1f}%") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/experiments/config.py b/scripts/experiments/config.py new file mode 100644 index 0000000..054727b --- /dev/null +++ b/scripts/experiments/config.py @@ -0,0 +1,166 @@ +"""Configuration for the termchart latency/token experiment (Tier B, agent-in-loop). + +Defines the three **runners**, the **conditions** (baseline vs combined fixes), the +env-driven defaults that encode the still-open plan decisions (model, cloud bucket), +and a loader for the diagram-task suite. + +Pure stdlib so it runs on a bare VM with only Python 3.11+. See +``docs/plans/2026-06-07-latency-token-experimentation-plan.md``. +""" +from __future__ import annotations + +import json +import os +import shutil +from dataclasses import dataclass, field +from pathlib import Path + +EXPERIMENTS_DIR = Path(__file__).resolve().parent +REPO_ROOT = EXPERIMENTS_DIR.parent.parent +TASKS_DIR = EXPERIMENTS_DIR / "tasks" + + +def _env(name: str, default: str = "") -> str: + return os.environ.get(name, default) + + +# --- Decisions encoded as env defaults (§12 of the plan) ---------------------- +# Decided: ONE shared model held constant across all runners (set via the proxy). +SHARED_MODEL = _env("EXPERIMENT_MODEL", "") # required at real run-time +# Still-open: cloud bucket for results, proxy endpoint, viewer endpoint. +GCS_BUCKET = _env("EXPERIMENT_GCS_BUCKET", "") +PROXY_URL = _env("EXPERIMENT_PROXY_URL", "http://127.0.0.1:4000") +PROXY_API_KEY = _env("EXPERIMENT_PROXY_API_KEY", "sk-experiment") # LiteLLM master key +VIEWER_URL = _env("TERMCHART_VIEWER_URL", "http://127.0.0.1:8080/w/me") +VIEWER_TOKEN = _env("TERMCHART_VIEWER_TOKEN", "dev") + + +@dataclass(frozen=True) +class Runner: + """A coding-agent CLI we drive headlessly. + + All three are pointed at the **same** OpenAI-compatible proxy endpoint so they + share one model and tokens are measured uniformly. ``argv`` and ``env`` are + best-effort templates to be validated in the pilot (the plan flags AGY headless + mode as the riskiest); they never run in ``--dry-run``. + """ + + name: str + bin: str + version_env: str # env var holding the pinned version (recorded per run) + argv_template: list[str] # tokens; "{prompt}" and "{model}" are substituted + env_template: dict[str, str] # values may contain {proxy},{model},{api_key} + + def available(self) -> bool: + return shutil.which(self.bin) is not None + + def version(self) -> str: + return _env(self.version_env, "unpinned") + + def build_argv(self, prompt: str, model: str) -> list[str]: + return [tok.replace("{prompt}", prompt).replace("{model}", model) + for tok in self.argv_template] + + def build_env(self, base: dict[str, str], *, proxy: str, model: str, + api_key: str) -> dict[str, str]: + out = dict(base) + for k, v in self.env_template.items(): + out[k] = (v.replace("{proxy}", proxy) + .replace("{model}", model) + .replace("{api_key}", api_key)) + return out + + +# OpenAI-compatible proxy: every runner talks to LiteLLM's /v1 with one model id. +RUNNERS: dict[str, Runner] = { + "claude-code": Runner( + name="claude-code", + bin="claude", + version_env="CLAUDE_CODE_VERSION", + argv_template=["claude", "-p", "{prompt}", + "--output-format", "json", "--model", "{model}"], + # Claude Code honours ANTHROPIC_BASE_URL; point it at the proxy. + env_template={ + "ANTHROPIC_BASE_URL": "{proxy}", + "ANTHROPIC_API_KEY": "{api_key}", + "ANTHROPIC_MODEL": "{model}", + }, + ), + "opencode": Runner( + name="opencode", + bin="opencode", + version_env="OPENCODE_VERSION", + argv_template=["opencode", "run", "{prompt}", "--model", "{model}"], + # OpenCode reads provider base URL/key from env (OpenAI-compatible provider). + env_template={ + "OPENAI_BASE_URL": "{proxy}", + "OPENAI_API_KEY": "{api_key}", + }, + ), + "agy": Runner( + name="agy", + bin="agy", + version_env="AGY_VERSION", + argv_template=["agy", "run", "--headless", "--prompt", "{prompt}", + "--model", "{model}"], + env_template={ + "AGY_BASE_URL": "{proxy}", + "AGY_API_KEY": "{api_key}", + }, + ), +} + + +@dataclass(frozen=True) +class Condition: + """A termchart build under test. ``ref`` is the git ref to check out/build.""" + + name: str + ref: str + description: str + env: dict[str, str] = field(default_factory=dict) # runtime feature flags + + +CONDITIONS: dict[str, Condition] = { + "baseline": Condition( + name="baseline", ref="master", + description="current published behavior (control)"), + "c1": Condition( + name="c1", ref="perf/reduce-latency", + description="combined experimentation fixes (T1-T5 + L1-L5)"), +} + + +@dataclass(frozen=True) +class Task: + id: str + category: str + surface: str # "terminal" | "viewer" + type: str # flow|component|vegalite|status|mermaid + prompt: str + rubric: str + reference: str | None = None + + +def load_tasks(path: str | Path) -> list[Task]: + """Load a JSONL task suite (e.g. ``tasks/pilot.jsonl``).""" + p = Path(path) + if not p.is_absolute() and not p.exists(): + p = TASKS_DIR / p + tasks: list[Task] = [] + for i, line in enumerate(p.read_text(encoding="utf-8").splitlines(), 1): + line = line.strip() + if not line or line.startswith("#"): + continue + try: + d = json.loads(line) + except json.JSONDecodeError as e: + raise SystemExit(f"Malformed task at {p}:{i}: {e}") + tasks.append(Task( + id=d["id"], category=d["category"], surface=d["surface"], + type=d["type"], prompt=d["prompt"], rubric=d["rubric"], + reference=d.get("reference"), + )) + if not tasks: + raise SystemExit(f"No tasks found in {p}") + return tasks diff --git a/scripts/experiments/metrics.py b/scripts/experiments/metrics.py new file mode 100644 index 0000000..1d0303a --- /dev/null +++ b/scripts/experiments/metrics.py @@ -0,0 +1,228 @@ +"""Metrics types + statistics for the latency/token experiment. + +Holds the per-run record produced by ``agent_run.py``, small stdlib-only +statistics (median / percentile / bootstrap CI), a parser for the LiteLLM proxy +spend log (the token/latency source of truth), and a grouping summarizer used by +both ``agent_run.py`` and ``aggregate.py``. +""" +from __future__ import annotations + +import json +import random +from collections.abc import Sequence +from dataclasses import asdict, dataclass, field +from pathlib import Path +from statistics import median as _median + + +@dataclass +class RunRecord: + """One (runner x condition x task x rep) cell. + + Token/latency fields come from the proxy (ground truth, provider-neutral); + termchart fields come from the CLI/viewer instrumentation. ``success`` is the + correctness verdict (the §5 gate) — cost numbers are only compared across + *successful* diagrams. + """ + + run_id: str + runner: str + condition: str + task_id: str + category: str + surface: str + rep: int + success: bool + outcome: str # "ok" | "render-failed" | "no-diagram" | "error" | ... + # token usage (proxy) + tokens_in: int = 0 + tokens_out: int = 0 + tokens_cache_read: int = 0 + tokens_cache_write: int = 0 + # latency + latency_ms_total: int = 0 + latency_ms_first_token: int = 0 + llm_calls: int = 0 + # termchart-side + termchart_calls: int = 0 + render_ms: int = 0 + rtt_count: int = 0 + retries: int = 0 + files_read: int = 0 + context_bytes: int = 0 + # provenance + model: str = "" + dry_run: bool = False + error: str = "" + + @property + def tokens_total(self) -> int: + return self.tokens_in + self.tokens_out + + def to_dict(self) -> dict: + d = asdict(self) + d["tokens_total"] = self.tokens_total + return d + + +# --- statistics (stdlib only) ------------------------------------------------- + +def median(xs: Sequence[float]) -> float: + return float(_median(xs)) if xs else 0.0 + + +def percentile(xs: Sequence[float], p: float) -> float: + """Linear-interpolation percentile (p in [0,100]). Empty -> 0.""" + if not xs: + return 0.0 + s = sorted(xs) + if len(s) == 1: + return float(s[0]) + rank = (p / 100.0) * (len(s) - 1) + lo = int(rank) + hi = min(lo + 1, len(s) - 1) + frac = rank - lo + return float(s[lo] + (s[hi] - s[lo]) * frac) + + +def bootstrap_ci(xs: Sequence[float], *, iters: int = 2000, alpha: float = 0.05, + seed: int = 0) -> tuple[float, float]: + """Percentile bootstrap CI for the median. Empty -> (0,0).""" + if not xs: + return (0.0, 0.0) + if len(xs) == 1: + return (float(xs[0]), float(xs[0])) + rng = random.Random(seed) + n = len(xs) + meds = [] + for _ in range(iters): + sample = [xs[rng.randrange(n)] for _ in range(n)] + meds.append(_median(sample)) + meds.sort() + lo = percentile(meds, 100 * alpha / 2) + hi = percentile(meds, 100 * (1 - alpha / 2)) + return (lo, hi) + + +# --- proxy log parsing (token/latency source of truth) ------------------------ + +def parse_proxy_log(path: str | Path, run_id: str) -> dict: + """Aggregate token usage + latency for one ``run_id`` from a LiteLLM JSONL log. + + LiteLLM can emit a JSON line per call. We read tolerantly: any object whose + metadata carries our ``run_id`` (in ``metadata.run_id`` or a top-level + ``run_id``) contributes its ``usage`` and timing. Lines we can't parse are + skipped so a noisy log never crashes a run. + """ + p = Path(path) + agg = {"tokens_in": 0, "tokens_out": 0, "tokens_cache_read": 0, + "tokens_cache_write": 0, "llm_calls": 0, "latency_ms_total": 0, + "latency_ms_first_token": 0} + if not p.exists(): + return agg + first_token_ms: list[int] = [] + for line in p.read_text(encoding="utf-8").splitlines(): + line = line.strip() + if not line: + continue + try: + obj = json.loads(line) + except json.JSONDecodeError: + continue + meta = obj.get("metadata") or {} + rid = obj.get("run_id") or meta.get("run_id") + if rid != run_id: + continue + usage = obj.get("usage") or {} + agg["tokens_in"] += int(usage.get("prompt_tokens", 0) or 0) + agg["tokens_out"] += int(usage.get("completion_tokens", 0) or 0) + details = usage.get("prompt_tokens_details") or {} + agg["tokens_cache_read"] += int(details.get("cached_tokens", 0) or 0) + agg["tokens_cache_write"] += int(usage.get("cache_creation_input_tokens", 0) or 0) + agg["latency_ms_total"] += int(obj.get("latency_ms", 0) or 0) + if obj.get("first_token_ms"): + first_token_ms.append(int(obj["first_token_ms"])) + agg["llm_calls"] += 1 + if first_token_ms: + agg["latency_ms_first_token"] = min(first_token_ms) + return agg + + +# --- grouping / summary ------------------------------------------------------- + +# Metrics where lower is better (used to sign the delta vs baseline). +LOWER_IS_BETTER = ("tokens_total", "tokens_in", "tokens_out", + "latency_ms_total", "render_ms", "rtt_count", "retries") + + +def _values(records: list[RunRecord], metric: str, *, successful_only: bool) -> list[float]: + out = [] + for r in records: + if successful_only and not r.success: + continue + out.append(float(getattr(r, metric) if hasattr(r, metric) else r.to_dict()[metric])) + return out + + +def summarize(records: list[RunRecord], metrics: list[str]) -> dict: + """Per (condition, runner) and per-condition rollups, with median + CI. + + Cost metrics are computed over *successful* diagrams only; ``success_rate`` and + ``n`` are computed over all attempts. + """ + def cell(recs: list[RunRecord]) -> dict: + n = len(recs) + succ = [r for r in recs if r.success] + out: dict = {"n": n, "n_success": len(succ), + "success_rate": (len(succ) / n) if n else 0.0} + for m in metrics: + vals = _values(succ, m, successful_only=False) + lo, hi = bootstrap_ci(vals) + out[m] = {"median": median(vals), "p95": percentile(vals, 95), + "ci95": [lo, hi], "n": len(vals)} + return out + + by_cond: dict[str, list[RunRecord]] = {} + by_cond_runner: dict[str, dict[str, list[RunRecord]]] = {} + for r in records: + by_cond.setdefault(r.condition, []).append(r) + by_cond_runner.setdefault(r.condition, {}).setdefault(r.runner, []).append(r) + + return { + "per_condition": {c: cell(rs) for c, rs in by_cond.items()}, + "per_condition_runner": { + c: {rn: cell(rs) for rn, rs in runners.items()} + for c, runners in by_cond_runner.items() + }, + } + + +def deltas_vs_baseline(summary: dict, metrics: list[str], + baseline: str = "baseline") -> dict: + """Percent change of each condition vs baseline, per metric (median-based). + + Negative %% = improvement for LOWER_IS_BETTER metrics. ``ci_disjoint`` flags + whether the condition's CI does not overlap baseline's (a cheap significance hint). + """ + per = summary.get("per_condition", {}) + base = per.get(baseline) + out: dict = {} + if not base: + return out + for cond, cell in per.items(): + if cond == baseline: + continue + out[cond] = {} + for m in metrics: + b = base[m]["median"] + v = cell[m]["median"] + pct = ((v - b) / b * 100.0) if b else 0.0 + b_lo, b_hi = base[m]["ci95"] + v_lo, v_hi = cell[m]["ci95"] + disjoint = (v_hi < b_lo) or (v_lo > b_hi) + out[cond][m] = { + "baseline_median": b, "median": v, "pct_change": pct, + "improved": (pct < 0) if m in LOWER_IS_BETTER else (pct > 0), + "ci_disjoint": disjoint, + } + return out diff --git a/scripts/experiments/proxy/litellm.config.yaml b/scripts/experiments/proxy/litellm.config.yaml new file mode 100644 index 0000000..f0dc87f --- /dev/null +++ b/scripts/experiments/proxy/litellm.config.yaml @@ -0,0 +1,33 @@ +# LiteLLM proxy — the single shared-model endpoint + token/latency measurement spine. +# +# All three runners (Claude Code / AGY / OpenCode) point their base URL at this proxy +# so they exercise ONE model (the decided cross-runner control) and every call's token +# usage + latency is logged uniformly to a JSONL spend log that metrics.parse_proxy_log +# reads. +# +# Start (on the VM): +# pip install 'litellm[proxy]' +# EXPERIMENT_MODEL=... LITELLM_MASTER_KEY=sk-experiment \ +# litellm --config litellm.config.yaml --port 4000 +# +# The model id the runners send ("shared-model") is aliased here to the real provider +# model from $EXPERIMENT_MODEL, so swapping the model never touches the harness. + +model_list: + - model_name: shared-model # what every runner asks for + litellm_params: + model: os.environ/EXPERIMENT_MODEL # the real provider model (decided per pilot) + # Provider creds come from the environment (e.g. VERTEX_PROJECT / ANTHROPIC_API_KEY / + # OPENAI_API_KEY) depending on which provider EXPERIMENT_MODEL names. + +litellm_settings: + drop_params: true + # Custom callback writes one JSONL record per call (run_id, usage, latency) that the + # harness correlates by run_id. See proxy/spend_logger.py. + callbacks: ["spend_logger.run_logger"] + +general_settings: + master_key: os.environ/LITELLM_MASTER_KEY + # Surface the request header the harness uses to tag each call with its cell id. + # Runners forward EXPERIMENT_RUN_ID as the `x-run-id` header (validated in the pilot; + # for runners that can't add headers we fall back to time-window correlation). diff --git a/scripts/experiments/proxy/spend_logger.py b/scripts/experiments/proxy/spend_logger.py new file mode 100644 index 0000000..e4b7102 --- /dev/null +++ b/scripts/experiments/proxy/spend_logger.py @@ -0,0 +1,68 @@ +"""LiteLLM custom callback: one JSONL line per call for the experiment. + +Writes records in the exact shape ``metrics.parse_proxy_log`` consumes, so token +usage + latency are correlated back to each (runner x condition x task x rep) cell by +``run_id``. The log path is ``$EXPERIMENT_PROXY_LOG`` (default ``./spend.jsonl``). + +Referenced from ``litellm.config.yaml`` as ``callbacks: ["spend_logger.run_logger"]``. +Kept import-tolerant so this file can be unit-imported without litellm installed. +""" +from __future__ import annotations + +import json +import os +import time + +try: # litellm is only present on the proxy VM + from litellm.integrations.custom_logger import CustomLogger +except Exception: # pragma: no cover - exercised only off-VM + class CustomLogger: # minimal shim so the module imports anywhere + pass + +LOG_PATH = os.environ.get("EXPERIMENT_PROXY_LOG", "spend.jsonl") + + +def _extract_run_id(kwargs: dict) -> str: + meta = (kwargs.get("litellm_params", {}) or {}).get("metadata", {}) or {} + if meta.get("run_id"): + return str(meta["run_id"]) + # Fall back to the request header the harness forwards. + req = meta.get("proxy_server_request", {}) or {} + headers = req.get("headers", {}) or {} + return str(headers.get("x-run-id", "")) + + +def _record(kwargs: dict, response, start_time, end_time) -> dict: + usage = {} + try: + usage = dict(getattr(response, "usage", {}) or response.get("usage", {})) + except Exception: + usage = {} + latency_ms = 0 + try: + latency_ms = int((end_time - start_time).total_seconds() * 1000) + except Exception: + pass + return { + "run_id": _extract_run_id(kwargs), + "ts": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()), + "model": kwargs.get("model", ""), + "usage": usage, + "latency_ms": latency_ms, + } + + +def _write(rec: dict) -> None: + with open(LOG_PATH, "a", encoding="utf-8") as fh: + fh.write(json.dumps(rec, default=str) + "\n") + + +class RunLogger(CustomLogger): + def log_success_event(self, kwargs, response_obj, start_time, end_time): + _write(_record(kwargs, response_obj, start_time, end_time)) + + async def async_log_success_event(self, kwargs, response_obj, start_time, end_time): + _write(_record(kwargs, response_obj, start_time, end_time)) + + +run_logger = RunLogger() diff --git a/scripts/experiments/tasks/pilot.jsonl b/scripts/experiments/tasks/pilot.jsonl new file mode 100644 index 0000000..c31b784 --- /dev/null +++ b/scripts/experiments/tasks/pilot.jsonl @@ -0,0 +1,5 @@ +{"id": "flow-auth-callgraph", "category": "flow", "surface": "viewer", "type": "flow", "prompt": "Draw the authentication call graph for a typical web service: Client -> API gateway -> Auth service -> (Token store, User DB). Show the gateway rejecting unauthenticated requests. Use termchart to render it.", "rubric": "A directed graph with >=5 nodes including a client, gateway, auth service, and at least one datastore; an explicit unauthenticated-reject edge/branch.", "reference": "corpus/saas"} +{"id": "component-plan-comparison", "category": "component", "surface": "viewer", "type": "component", "prompt": "Compare three subscription plans (Free, Pro, Enterprise) across price, seats, SSO, and support SLA as a table. Render it with termchart.", "rubric": "A component/table with 3 plan columns and >=4 feature rows; values present for each cell.", "reference": null} +{"id": "vegalite-sprint-burndown", "category": "vegalite", "surface": "viewer", "type": "vegalite", "prompt": "Show this sprint's burndown: ideal vs actual remaining story points over a 10-day sprint. Render it with termchart.", "rubric": "A line/area chart with a day axis (10 points) and two series (ideal, actual).", "reference": "plugin/skills/diagram-recipes/examples/sprint-burndown.vegalite.json"} +{"id": "status-build-progress", "category": "status", "surface": "viewer", "type": "status", "prompt": "Narrate a 3-stage build (install -> compile -> test) using termchart status overlays: show a loader while running, then a success when each stage completes.", "rubric": "At least one loader/progress status emitted and a terminal success state; keyed by a stable id.", "reference": null} +{"id": "terminal-er-orders", "category": "flow", "surface": "terminal", "type": "mermaid", "prompt": "Produce an inline ASCII entity-relationship diagram for an e-commerce orders schema (Customer, Order, OrderItem, Product) with cardinalities. Render it to a file with --color none and paste the output.", "rubric": "An erDiagram rendered to ASCII with 4 entities and relationships; rectangular grid; exit 0.", "reference": "corpus/ecommerce"} diff --git a/scripts/experiments/test_experiments.py b/scripts/experiments/test_experiments.py new file mode 100644 index 0000000..b195ad5 --- /dev/null +++ b/scripts/experiments/test_experiments.py @@ -0,0 +1,203 @@ +"""Tests for the latency/token experiment harness (Tier B). + +Pytest-native, stdlib-only. Exercises the full dry-run pipeline (cells -> records +-> summary -> report -> aggregate) plus the statistics and proxy-log parsing, with +no runners and no network. + +Run: pytest scripts/experiments/test_experiments.py +""" +from __future__ import annotations + +import json + +import pytest + +import aggregate +import agent_run +import config +import metrics + + +# --- config / tasks ----------------------------------------------------------- + +def test_pilot_suite_loads_five_tasks(): + tasks = config.load_tasks("pilot.jsonl") + assert len(tasks) == 5 + assert {t.category for t in tasks} >= {"flow", "component", "vegalite", "status"} + assert all(t.prompt and t.rubric for t in tasks) + + +def test_load_tasks_rejects_malformed(tmp_path): + bad = tmp_path / "bad.jsonl" + bad.write_text('{"id": "x"\n', encoding="utf-8") # truncated JSON + with pytest.raises(SystemExit): + config.load_tasks(bad) + + +def test_runner_builds_argv_and_env(): + r = config.RUNNERS["claude-code"] + argv = r.build_argv("draw X", "shared-model") + assert "draw X" in argv and "shared-model" in argv + env = r.build_env({}, proxy="http://p:4000", model="shared-model", api_key="k") + assert env["ANTHROPIC_BASE_URL"] == "http://p:4000" + assert env["ANTHROPIC_API_KEY"] == "k" + + +# --- statistics --------------------------------------------------------------- + +def test_median_and_percentile(): + assert metrics.median([3, 1, 2]) == 2 + assert metrics.percentile([0, 10], 50) == 5 + assert metrics.percentile([], 95) == 0.0 + assert metrics.percentile([7], 95) == 7 + + +def test_bootstrap_ci_is_deterministic_and_brackets_median(): + xs = [10, 11, 12, 13, 100] + lo, hi = metrics.bootstrap_ci(xs, seed=1) + lo2, hi2 = metrics.bootstrap_ci(xs, seed=1) + assert (lo, hi) == (lo2, hi2) # seeded -> reproducible + assert lo <= metrics.median(xs) <= hi + + +# --- proxy log parsing -------------------------------------------------------- + +def test_parse_proxy_log_sums_usage_for_run_id(tmp_path): + log = tmp_path / "spend.jsonl" + log.write_text("\n".join([ + json.dumps({"run_id": "R1", "usage": {"prompt_tokens": 100, + "completion_tokens": 20, + "prompt_tokens_details": {"cached_tokens": 40}}, + "latency_ms": 500, "first_token_ms": 120}), + json.dumps({"run_id": "R1", "usage": {"prompt_tokens": 50, + "completion_tokens": 10}, "latency_ms": 300}), + json.dumps({"run_id": "OTHER", "usage": {"prompt_tokens": 999, + "completion_tokens": 999}}), + "not json", + ]) + "\n", encoding="utf-8") + agg = metrics.parse_proxy_log(log, "R1") + assert agg["tokens_in"] == 150 + assert agg["tokens_out"] == 30 + assert agg["tokens_cache_read"] == 40 + assert agg["llm_calls"] == 2 + assert agg["latency_ms_total"] == 800 + assert agg["latency_ms_first_token"] == 120 + + +def test_parse_proxy_log_missing_file_is_empty(tmp_path): + agg = metrics.parse_proxy_log(tmp_path / "nope.jsonl", "R1") + assert agg["llm_calls"] == 0 + + +# --- synthesize determinism --------------------------------------------------- + +def test_synthesize_is_deterministic_by_identity(): + runner = config.RUNNERS["claude-code"] + cond = config.CONDITIONS["baseline"] + task = config.load_tasks("pilot.jsonl")[0] + a = agent_run.synthesize("exp", runner, cond, task, 0, "m") + b = agent_run.synthesize("exp", runner, cond, task, 0, "m") + assert a.to_dict() == b.to_dict() + + +def test_c1_cheaper_than_baseline_in_synthetic_model(): + runner = config.RUNNERS["claude-code"] + task = config.load_tasks("pilot.jsonl")[0] + base = agent_run.synthesize("exp", runner, config.CONDITIONS["baseline"], task, 0, "m") + c1 = agent_run.synthesize("exp", runner, config.CONDITIONS["c1"], task, 0, "m") + assert c1.tokens_total < base.tokens_total + assert c1.rtt_count <= base.rtt_count + + +# --- summarize / deltas ------------------------------------------------------- + +def _rec(cond, tokens, latency, success=True, runner="claude-code"): + return metrics.RunRecord( + run_id="x", runner=runner, condition=cond, task_id="t", category="flow", + surface="viewer", rep=0, success=success, outcome="ok", + tokens_in=tokens, tokens_out=0, latency_ms_total=latency) + + +def test_summarize_excludes_failures_from_cost_but_counts_success_rate(): + records = [ + _rec("baseline", 1000, 10000), + _rec("baseline", 1000, 10000, success=False), # failure: excluded from cost + ] + summary = metrics.summarize(records, ["tokens_total"]) + cell = summary["per_condition"]["baseline"] + assert cell["n"] == 2 + assert cell["success_rate"] == 0.5 + assert cell["tokens_total"]["median"] == 1000 # only the successful one + + +def test_deltas_flag_improvement_and_disjoint_ci(): + records = [] + for _ in range(8): + records.append(_rec("baseline", 1000, 20000)) + records.append(_rec("c1", 600, 14000)) + summary = metrics.summarize(records, ["tokens_total", "latency_ms_total"]) + deltas = metrics.deltas_vs_baseline(summary, ["tokens_total", "latency_ms_total"]) + assert deltas["c1"]["tokens_total"]["pct_change"] < 0 + assert deltas["c1"]["tokens_total"]["improved"] is True + assert deltas["c1"]["tokens_total"]["ci_disjoint"] is True + + +# --- full dry-run pipeline ---------------------------------------------------- + +def test_dry_run_main_produces_report_and_metrics(tmp_path): + out = tmp_path / "exp" + mout = tmp_path / "agent.jsonl" + rc = agent_run.main([ + "--dry-run", "--tasks", "pilot.jsonl", + "--runners", "claude-code,opencode", "--conditions", "baseline,c1", + "--reps", "5", "--out", str(out), "--metrics-out", str(mout), + "--experiment-id", "pilot-test", + ]) + assert rc == 0 + report = json.loads((out / "report.json").read_text(encoding="utf-8")) + # 2 runners x 2 conditions x 5 tasks x 5 reps + assert report["total"] == 2 * 2 * 5 * 5 + assert report["dry_run"] is True + # c1 should beat baseline on tokens and latency in the synthetic model. + d = report["deltas_vs_baseline"]["c1"] + assert d["tokens_total"]["pct_change"] < 0 + assert d["latency_ms_total"]["pct_change"] < 0 + assert (out / "report.md").exists() + # JSONL metrics: one line per cell. + lines = mout.read_text(encoding="utf-8").strip().splitlines() + assert len(lines) == report["total"] + assert all(json.loads(l)["tier"] == "agent" for l in lines) + + +def test_unknown_runner_exits_3(tmp_path): + rc = agent_run.main(["--dry-run", "--runners", "nope", + "--out", str(tmp_path / "o")]) + assert rc == 3 + + +def test_real_mode_requires_model(tmp_path, monkeypatch): + monkeypatch.setattr(config, "SHARED_MODEL", "") + rc = agent_run.main(["--no-dry-run", "--model", "", + "--out", str(tmp_path / "o")]) + assert rc == 3 + + +# --- aggregator --------------------------------------------------------------- + +def test_aggregate_pools_streams(tmp_path): + # Produce two metric streams via dry-run, then aggregate them. + s1 = tmp_path / "a.jsonl" + s2 = tmp_path / "b.jsonl" + agent_run.main(["--dry-run", "--runners", "claude-code", "--reps", "3", + "--out", str(tmp_path / "o1"), "--metrics-out", str(s1), + "--experiment-id", "e1"]) + agent_run.main(["--dry-run", "--runners", "opencode", "--reps", "3", + "--out", str(tmp_path / "o2"), "--metrics-out", str(s2), + "--experiment-id", "e2"]) + out = tmp_path / "agg" + rc = aggregate.main([str(s1), str(s2), "--out", str(out)]) + assert rc == 0 + agg = json.loads((out / "aggregate.json").read_text(encoding="utf-8")) + # 2 runners x 2 conditions x 5 tasks x 3 reps + assert agg["total"] == 2 * 2 * 5 * 3 + assert "c1" in agg["deltas_vs_baseline"] diff --git a/scripts/experiments/vm/provision.sh b/scripts/experiments/vm/provision.sh new file mode 100755 index 0000000..96e3568 --- /dev/null +++ b/scripts/experiments/vm/provision.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +# Provision GCE VM(s) that run the experiment via startup.sh, then self-report to GCS. +# +# One VM per runner (default) so runners execute in parallel and stay isolated; each +# VM runs ALL conditions for its runner sequentially (fair: same hardware per cell). +# A fixed machine type keeps render-CPU timings comparable across conditions. +# +# Usage: +# EXPERIMENT_MODEL= \ +# EXPERIMENT_GCS_BUCKET=gs:///termchart-latency \ +# ./provision.sh +# +# Requires: gcloud auth + a project. Decisions still open (plan §12): confirm the +# model id, the bucket, and the service-account / provider creds the VM needs. +set -euo pipefail + +PROJECT="${PROJECT:-$(gcloud config get-value project 2>/dev/null)}" +ZONE="${ZONE:-us-central1-a}" +MACHINE_TYPE="${MACHINE_TYPE:-e2-standard-4}" # fixed shape -> comparable CPU timings +IMAGE_FAMILY="${IMAGE_FAMILY:-ubuntu-2204-lts}" +IMAGE_PROJECT="${IMAGE_PROJECT:-ubuntu-os-cloud}" +SERVICE_ACCOUNT="${SERVICE_ACCOUNT:-}" # needs storage.objectAdmin on the bucket +RUNNERS="${EXPERIMENT_RUNNERS:-claude-code,opencode,agy}" +EXPERIMENT_ID="${EXPERIMENT_ID:-$(date -u +%Y%m%dT%H%M%SZ)}" +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +: "${EXPERIMENT_MODEL:?set EXPERIMENT_MODEL (the single shared model id)}" + +sa_flag=() +[[ -n "$SERVICE_ACCOUNT" ]] && sa_flag=(--service-account "$SERVICE_ACCOUNT" \ + --scopes "https://www.googleapis.com/auth/cloud-platform") + +IFS=',' read -ra RUNNER_ARR <<< "$RUNNERS" +for runner in "${RUNNER_ARR[@]}"; do + name="tc-exp-${runner//[^a-z0-9]/-}-${EXPERIMENT_ID,,}" + echo "creating ${name} (runner=${runner})" + gcloud compute instances create "$name" \ + --project="$PROJECT" --zone="$ZONE" \ + --machine-type="$MACHINE_TYPE" \ + --image-family="$IMAGE_FAMILY" --image-project="$IMAGE_PROJECT" \ + --provisioning-model=SPOT --instance-termination-action=DELETE \ + "${sa_flag[@]}" \ + --metadata=\ +EXPERIMENT_ID="$EXPERIMENT_ID",\ +EXPERIMENT_RUNNERS="$runner",\ +EXPERIMENT_MODEL="$EXPERIMENT_MODEL",\ +EXPERIMENT_GCS_BUCKET="${EXPERIMENT_GCS_BUCKET:-}",\ +EXPERIMENT_CONDITIONS="${EXPERIMENT_CONDITIONS:-baseline,c1}",\ +EXPERIMENT_REPS="${EXPERIMENT_REPS:-5}",\ +TERMCHART_REF="${TERMCHART_REF:-perf/reduce-latency}" \ + --metadata-from-file=startup-script="$HERE/startup.sh" +done + +echo "provisioned ${#RUNNER_ARR[@]} VM(s). Results -> ${EXPERIMENT_GCS_BUCKET:-}/${EXPERIMENT_ID}/" +echo "tail logs: gcloud compute ssh --zone $ZONE --command 'sudo journalctl -u google-startup-scripts -f'" diff --git a/scripts/experiments/vm/startup.sh b/scripts/experiments/vm/startup.sh new file mode 100755 index 0000000..b8fb302 --- /dev/null +++ b/scripts/experiments/vm/startup.sh @@ -0,0 +1,106 @@ +#!/usr/bin/env bash +# termchart latency/token experiment — VM bootstrap + run (one condition). +# +# Runs on a fresh GCE VM (or any Ubuntu box). Installs Node/Python, the three +# runners, the LiteLLM proxy + termchart at a given ref, starts the viewer + proxy +# on loopback (isolates server CPU from network jitter), runs Tier A (render) and +# Tier B (agent) harnesses, and uploads results to GCS. +# +# Everything is env-parameterized so the still-open plan decisions are just inputs: +# TERMCHART_REF git ref / condition to build (default: perf/reduce-latency) +# EXPERIMENT_MODEL the single shared model id (REQUIRED for real run) +# EXPERIMENT_GCS_BUCKET gs://... results destination (optional; skips upload if unset) +# EXPERIMENT_RUNNERS comma list (default: claude-code,opencode,agy) +# EXPERIMENT_CONDITIONS comma list (default: baseline,c1) +# EXPERIMENT_REPS reps per cell (default: 5) +# EXPERIMENT_TASKS suite file (default: pilot.jsonl) +# CLAUDE_CODE_VERSION / OPENCODE_VERSION / AGY_VERSION pinned versions (recorded) +# +# NOTE: the runner-install commands are best-effort and MUST be validated in the +# pilot (the plan flags AGY headless mode as the riskiest). Lines that need a real +# install recipe are marked TODO(pilot). +set -euo pipefail + +REPO_URL="${REPO_URL:-https://github.com/ivanmkc/termchart}" +TERMCHART_REF="${TERMCHART_REF:-perf/reduce-latency}" +EXPERIMENT_RUNNERS="${EXPERIMENT_RUNNERS:-claude-code,opencode,agy}" +EXPERIMENT_CONDITIONS="${EXPERIMENT_CONDITIONS:-baseline,c1}" +EXPERIMENT_REPS="${EXPERIMENT_REPS:-5}" +EXPERIMENT_TASKS="${EXPERIMENT_TASKS:-pilot.jsonl}" +EXPERIMENT_ID="${EXPERIMENT_ID:-$(date -u +%Y%m%dT%H%M%SZ)}" +WORK="${WORK:-/opt/experiment}" +export LITELLM_MASTER_KEY="${LITELLM_MASTER_KEY:-sk-experiment}" +export EXPERIMENT_PROXY_URL="${EXPERIMENT_PROXY_URL:-http://127.0.0.1:4000}" +export EXPERIMENT_PROXY_API_KEY="$LITELLM_MASTER_KEY" +export EXPERIMENT_PROXY_LOG="${WORK}/spend.jsonl" +export TERMCHART_VIEWER_URL="${TERMCHART_VIEWER_URL:-http://127.0.0.1:8080/w/me}" +export TERMCHART_VIEWER_TOKEN="${TERMCHART_VIEWER_TOKEN:-dev}" + +log() { echo "[startup $(date -u +%H:%M:%S)] $*"; } + +# --- 1. system deps ---------------------------------------------------------- +log "installing system deps" +export DEBIAN_FRONTEND=noninteractive +apt-get update -y +apt-get install -y git curl jq python3 python3-pip python3-venv ca-certificates +curl -fsSL https://deb.nodesource.com/setup_20.x | bash - +apt-get install -y nodejs + +# --- 2. runners (pinned; validate in pilot) ---------------------------------- +log "installing runners: ${EXPERIMENT_RUNNERS}" +npm i -g "@anthropic-ai/claude-code@${CLAUDE_CODE_VERSION:-latest}" || log "WARN claude-code install" +npm i -g "opencode-ai@${OPENCODE_VERSION:-latest}" || log "WARN opencode install" +# TODO(pilot): AGY headless install recipe (no public npm package assumed). +# curl -fsSL https://.../agy-install.sh | bash # AGY_VERSION=${AGY_VERSION:-latest} + +# --- 3. proxy (token/latency spine) ------------------------------------------ +log "installing litellm proxy" +python3 -m pip install --quiet 'litellm[proxy]' + +# --- 4. termchart at the condition ref --------------------------------------- +log "building termchart @ ${TERMCHART_REF}" +mkdir -p "$WORK" +git clone "$REPO_URL" "$WORK/termchart" +git -C "$WORK/termchart" checkout "$TERMCHART_REF" +( cd "$WORK/termchart" && npm ci && npm run build ) +# Pre-warm the CLI on PATH (L3): the SessionStart hook must never npm-install mid-task. +npm i -g "$WORK/termchart/packages/cli" +export TERMCHART_BIN="termchart" + +# --- 5. start proxy + viewer (background, loopback) --------------------------- +log "starting proxy + viewer" +( cd "$WORK/termchart/scripts/experiments/proxy" \ + && EXPERIMENT_PROXY_LOG="$EXPERIMENT_PROXY_LOG" \ + litellm --config litellm.config.yaml --port 4000 >"$WORK/proxy.log" 2>&1 & ) +( cd "$WORK/termchart" \ + && PUSH_TOKEN="$TERMCHART_VIEWER_TOKEN" PORT=8080 \ + node packages/viewer/dist/server.js >"$WORK/viewer.log" 2>&1 & ) +# wait for health +for i in $(seq 1 30); do curl -sf http://127.0.0.1:8080/healthz && break || sleep 2; done + +# --- 6. Tier A: render-only latency (no LLM) --------------------------------- +log "Tier A: corpus render timings" +EXPERIMENT_RUN_ID="${EXPERIMENT_ID}:render" \ + python3 "$WORK/termchart/scripts/corpus_run.py" \ + --metrics-out "$WORK/out/render.jsonl" || log "WARN corpus_run" + +# --- 7. Tier B: agent-in-the-loop (real) ------------------------------------- +log "Tier B: agent runs" +python3 "$WORK/termchart/scripts/experiments/agent_run.py" \ + --no-dry-run \ + --tasks "$EXPERIMENT_TASKS" \ + --runners "$EXPERIMENT_RUNNERS" \ + --conditions "$EXPERIMENT_CONDITIONS" \ + --reps "$EXPERIMENT_REPS" \ + --model "$EXPERIMENT_MODEL" \ + --proxy-log "$EXPERIMENT_PROXY_LOG" \ + --experiment-id "$EXPERIMENT_ID" \ + --out "$WORK/out/experiment" \ + --metrics-out "$WORK/out/agent.jsonl" + +# --- 8. upload --------------------------------------------------------------- +if [[ -n "${EXPERIMENT_GCS_BUCKET:-}" ]]; then + log "uploading to ${EXPERIMENT_GCS_BUCKET}/${EXPERIMENT_ID}/" + gsutil -m cp -r "$WORK/out" "${EXPERIMENT_GCS_BUCKET}/${EXPERIMENT_ID}/" || log "WARN upload" +fi +log "done: ${EXPERIMENT_ID}" diff --git a/scripts/test_corpus_run.py b/scripts/test_corpus_run.py index 7759864..b16b120 100644 --- a/scripts/test_corpus_run.py +++ b/scripts/test_corpus_run.py @@ -117,6 +117,38 @@ def test_empty_output_at_exit0_is_surprise(tmp_path, monkeypatch): assert not (out / "gallery-candidates" / "demo" / "blank.txt").exists() +def test_metrics_out_writes_jsonl(tmp_path, monkeypatch): + """--metrics-out appends one render record per fixture, keyed by run_id.""" + stub = tmp_path / "stub.py" + stub.write_text(STUB, encoding="utf-8") + corpus = tmp_path / "corpus" + _write(corpus / "demo" / "a.mmd", "exit=0\nstdout=BOX\n") + _write(corpus / "demo" / "b.mmd", "exit=0\nstdout=BOX\n") + + out = tmp_path / "out" + metrics = tmp_path / "metrics" / "render.jsonl" + monkeypatch.setattr(corpus_run, "OUT_DIR", out) + monkeypatch.setenv("TERMCHART_BIN", f"{sys.executable} {stub}") + monkeypatch.setenv("EXPERIMENT_RUN_ID", "pilot-xyz") + + rc = corpus_run.main(["--corpus", str(corpus), "--metrics-out", str(metrics)]) + assert rc == 0 + + lines = metrics.read_text(encoding="utf-8").strip().splitlines() + assert len(lines) == 2 + recs = [json.loads(line) for line in lines] + assert {r["name"] for r in recs} == {"a", "b"} + for r in recs: + assert r["run_id"] == "pilot-xyz" + assert r["tier"] == "render" + assert isinstance(r["elapsed_ms"], int) + assert "stdout" not in r # kept small/shareable + + # A second run appends rather than truncates (many runs -> one stream). + corpus_run.main(["--corpus", str(corpus), "--metrics-out", str(metrics)]) + assert len(metrics.read_text(encoding="utf-8").strip().splitlines()) == 4 + + def test_nonrectangular_render_is_flagged(tmp_path, monkeypatch): stub = tmp_path / "stub.py" stub.write_text(STUB, encoding="utf-8") From b99064594a253f06582be60a0e5cc30b71727557 Mon Sep 17 00:00:00 2001 From: Ivan Cheung Date: Sun, 7 Jun 2026 13:02:05 -0400 Subject: [PATCH 3/6] feat(experiments): local podman runner + Vertex proxy (working vertical slice) - podman/: Containerfile (node+python+claude-code+opencode+litellm), run_local.sh (proxy container + per-cell containers + aggregate), entrypoint_cell.sh (build termchart per condition, run runner headless as non-root node, emit RunRecord) - proxy: route shared-model -> Vertex Gemini 2.5 Flash via ADC; clean spend-log usage; Claude is a one-line EXPERIMENT_MODEL flip once Model Garden is enabled - metrics: spend-log slice correlation (parse_proxy_log_slice, count_log_lines) - cell_record.py: per-cell RunRecord from runner output + proxy spend slice - proven: Claude Code in-container draws an ER diagram via termchart end-to-end on Vertex Gemini (57.7k in / 3.0k out tokens captured) Refs #142 --- ...6-07-latency-token-experimentation-plan.md | 26 ++++-- scripts/experiments/cell_record.py | 75 +++++++++++++++++ scripts/experiments/metrics.py | 44 ++++++++++ scripts/experiments/podman/Containerfile | 32 +++++++ scripts/experiments/podman/entrypoint_cell.sh | 54 ++++++++++++ scripts/experiments/podman/run_local.sh | 84 +++++++++++++++++++ scripts/experiments/proxy/litellm.config.yaml | 48 ++++++----- scripts/experiments/proxy/spend_logger.py | 29 +++++-- scripts/experiments/test_experiments.py | 24 ++++++ 9 files changed, 385 insertions(+), 31 deletions(-) create mode 100755 scripts/experiments/cell_record.py create mode 100644 scripts/experiments/podman/Containerfile create mode 100755 scripts/experiments/podman/entrypoint_cell.sh create mode 100755 scripts/experiments/podman/run_local.sh diff --git a/docs/plans/2026-06-07-latency-token-experimentation-plan.md b/docs/plans/2026-06-07-latency-token-experimentation-plan.md index 2b9ceab..e1ea50b 100644 --- a/docs/plans/2026-06-07-latency-token-experimentation-plan.md +++ b/docs/plans/2026-06-07-latency-token-experimentation-plan.md @@ -262,10 +262,26 @@ Extend, don't replace: **Resolved** - **Model strategy** — one shared model held constant across all three runners (§4). - **First pass** — pilot: {baseline, C1} only, then gate to the ablation (§11 P1). +- **Execution substrate** — **local podman** containers (not GCE). One long-lived + proxy container + one container per cell (isolation), driven by + `scripts/experiments/podman/run_local.sh`. GCE `vm/` scripts remain for later scale-out. +- **Project / auth** — `adk-coding-agents` via ADC (mounted into the proxy container). +- **Shared model** — **Gemini 2.5 Flash on Vertex** (`vertex_ai/gemini-2.5-flash`), + reached through LiteLLM so all runners share it. **Claude is blocked**: Anthropic + Model Garden isn't enabled for the project (404 in all regions; needs the EULA + accepted in the console). Switching to Claude is a one-line `EXPERIMENT_MODEL` flip + once enabled. + +**Proven (local podman vertical slice)** +- Claude Code runs headless in a container (as non-root `node`; it refuses + `--bypass` as root), talks the Anthropic API to LiteLLM → Vertex Gemini, and + **actually drives termchart to render a diagram**. Tokens/latency captured from the + proxy spend-log slice. First real cell: 57.7k in / 3.0k out tokens, 4 calls, 24.5s. **Still open** -1. **Cloud target** — GCE assumed (gcloud SDK present). OK, or another provider / local VMs? -2. **Which shared model** — pick the single model id all runners use, and confirm API - budget/keys cover all three runners hitting it via the proxy. -3. **Runner versions** — pin to specific releases of Claude Code / AGY / OpenCode? -4. **Results store** — confirm the GCS bucket (or reuse `agent-generator`'s convention). +1. **Runner coverage** — OpenCode wired (unvalidated); **AGY** still needs a headless + recipe (flagged risk). +2. **c1 ≠ baseline** — c1 currently only adds experiment scaffolding; the actual + T*/L* fixes must land before baseline-vs-c1 is meaningful. +3. **Runner versions** — pin releases (currently Claude Code 2.1.168, OpenCode 1.16.2). +4. **Results store** — local `out/podman/` for now; add GCS when scaling out. diff --git a/scripts/experiments/cell_record.py b/scripts/experiments/cell_record.py new file mode 100755 index 0000000..50c2eba --- /dev/null +++ b/scripts/experiments/cell_record.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 +"""Emit one RunRecord JSON for a single container cell (real run). + +Reads the runner's own output (e.g. Claude Code's --output-format json) for a +coarse success signal, and the proxy spend-log *slice* appended during this cell +(SPEND_START..end) for provider-neutral token/latency truth. Writes +``$OUT/cells/.json`` and appends to ``$OUT/agent.jsonl`` (tier=agent), the +same stream ``aggregate.py`` consumes. + +Driven entirely by env (set by podman/entrypoint_cell.sh). +""" +from __future__ import annotations + +import json +import os +import sys + +import metrics +from metrics import RunRecord + + +def _env(name: str, default: str = "") -> str: + return os.environ.get(name, default) + + +def main() -> int: + runner = _env("RUNNER"); cond = _env("CONDITION"); task = _env("TASK_ID") + rep = int(_env("REP", "0")); model = _env("MODEL") + out = _env("OUT", "/work/out") + spend = _env("EXPERIMENT_PROXY_LOG", os.path.join(out, "spend.jsonl")) + start = int(_env("SPEND_START", "0")); wall = int(_env("WALL_MS", "0")) + rid = f"{cond}:{runner}:{task}:r{rep}" + + agg = metrics.parse_proxy_log_slice(spend, start) + + # Runner self-reported outcome (Claude Code JSON: {is_error, result, modelUsage}). + result_ok = False; err = "" + rj = os.path.join(out, "runner.json") + if os.path.exists(rj): + try: + d = json.load(open(rj, encoding="utf-8")) + result_ok = (not d.get("is_error")) and bool(d.get("result")) + except Exception as e: # malformed/empty -> failure, captured + err = f"runner output parse: {e}" + else: + err = "no runner output" + + # Correctness (coarse, P0): runner succeeded AND it actually hit the model. + # The rubric LLM-judge + rendered-diagram check land in P1. + success = bool(result_ok and agg["llm_calls"] > 0) + + rec = RunRecord( + run_id=rid, runner=runner, condition=cond, task_id=task, + category=_env("TASK_CATEGORY", "unknown"), surface=_env("TASK_SURFACE", "viewer"), + rep=rep, success=success, outcome="ok" if success else (err or "no-diagram"), + tokens_in=agg["tokens_in"], tokens_out=agg["tokens_out"], + tokens_cache_read=agg["tokens_cache_read"], + tokens_cache_write=agg["tokens_cache_write"], + latency_ms_total=agg["latency_ms_total"] or wall, + llm_calls=agg["llm_calls"], model=model, dry_run=False, error=err, + ) + + cells = os.path.join(out, "cells"); os.makedirs(cells, exist_ok=True) + safe = rid.replace(":", "_").replace("/", "_") + json.dump(rec.to_dict(), open(os.path.join(cells, f"{safe}.json"), "w"), indent=2) + with open(os.path.join(out, "agent.jsonl"), "a", encoding="utf-8") as f: + f.write(json.dumps({"tier": "agent", **rec.to_dict()}) + "\n") + print(f"[cell] {rid} success={success} tok_in={agg['tokens_in']} " + f"tok_out={agg['tokens_out']} calls={agg['llm_calls']} " + f"lat={agg['latency_ms_total']}ms") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/experiments/metrics.py b/scripts/experiments/metrics.py index 1d0303a..bbf8c4a 100644 --- a/scripts/experiments/metrics.py +++ b/scripts/experiments/metrics.py @@ -148,6 +148,50 @@ def parse_proxy_log(path: str | Path, run_id: str) -> dict: return agg +def _accumulate(agg: dict, obj: dict) -> None: + usage = obj.get("usage") or {} + agg["tokens_in"] += int(usage.get("prompt_tokens", 0) or 0) + agg["tokens_out"] += int(usage.get("completion_tokens", 0) or 0) + details = usage.get("prompt_tokens_details") or {} + agg["tokens_cache_read"] += int(details.get("cached_tokens", 0) or 0) + agg["tokens_cache_write"] += int(usage.get("cache_creation_input_tokens", 0) or 0) + agg["latency_ms_total"] += int(obj.get("latency_ms", 0) or 0) + agg["llm_calls"] += 1 + + +def parse_proxy_log_slice(path: str | Path, start_line: int = 0) -> dict: + """Aggregate usage from spend-log lines at index >= ``start_line`` (0-based). + + Correlation by position rather than ``run_id``: when runners can't forward a + ``run_id`` header, the harness records the log's line count *before* a cell and + reads everything appended *during* it. Requires cells to run sequentially + against a shared proxy log. + """ + p = Path(path) + agg = {"tokens_in": 0, "tokens_out": 0, "tokens_cache_read": 0, + "tokens_cache_write": 0, "llm_calls": 0, "latency_ms_total": 0, + "latency_ms_first_token": 0} + if not p.exists(): + return agg + lines = p.read_text(encoding="utf-8").splitlines() + for line in lines[start_line:]: + line = line.strip() + if not line: + continue + try: + _accumulate(agg, json.loads(line)) + except json.JSONDecodeError: + continue + return agg + + +def count_log_lines(path: str | Path) -> int: + p = Path(path) + if not p.exists(): + return 0 + return sum(1 for _ in p.open(encoding="utf-8")) + + # --- grouping / summary ------------------------------------------------------- # Metrics where lower is better (used to sign the delta vs baseline). diff --git a/scripts/experiments/podman/Containerfile b/scripts/experiments/podman/Containerfile new file mode 100644 index 0000000..a5afb1a --- /dev/null +++ b/scripts/experiments/podman/Containerfile @@ -0,0 +1,32 @@ +# Experiment runner image: toolchain + coding-agent runners + LiteLLM proxy. +# +# Generic on purpose — termchart itself is built at run time from a bind-mounted +# checkout (so one image serves both the baseline and c1 conditions). The harness +# scripts are also bind-mounted, so iterating on them needs no rebuild. +# +# Build: podman build -t termchart-exp -f scripts/experiments/podman/Containerfile . +FROM node:20-bookworm + +ENV DEBIAN_FRONTEND=noninteractive \ + PIP_DISABLE_PIP_VERSION_CHECK=1 \ + PATH=/opt/venv/bin:$PATH + +RUN apt-get update && apt-get install -y --no-install-recommends \ + git curl jq ca-certificates python3 python3-venv python3-pip \ + && rm -rf /var/lib/apt/lists/* + +# Python venv for the proxy + harness (Debian PEP-668 safe). +# google-auth + aiplatform are required for the Vertex AI backend (litellm[proxy] +# does not bundle them). +RUN python3 -m venv /opt/venv && pip install --no-cache-dir \ + 'litellm[proxy]' google-auth google-cloud-aiplatform + +# Coding-agent runners (pinned via build args; default latest). AGY has no public +# headless package assumed -> added in a later iteration (flagged risk in the plan). +ARG CLAUDE_CODE_VERSION=latest +ARG OPENCODE_VERSION=latest +RUN npm i -g "@anthropic-ai/claude-code@${CLAUDE_CODE_VERSION}" \ + "opencode-ai@${OPENCODE_VERSION}" || true + +WORKDIR /work +ENTRYPOINT ["/bin/bash"] diff --git a/scripts/experiments/podman/entrypoint_cell.sh b/scripts/experiments/podman/entrypoint_cell.sh new file mode 100755 index 0000000..6172633 --- /dev/null +++ b/scripts/experiments/podman/entrypoint_cell.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash +# One experiment cell, inside the container. Builds termchart for the condition +# (cached in the /cache volume), stages a node-owned copy so the agent loads that +# condition's AGENTS.md/skills, runs the runner headless as the non-root `node` +# user (Claude Code refuses --bypass as root), then emits a RunRecord via +# cell_record.py. Token/latency truth comes from the shared proxy spend-log slice. +# +# Inputs (env): RUNNER CONDITION TERMCHART_REF TASK_ID TASK_PROMPT TASK_CATEGORY +# TASK_SURFACE REP PROXY_URL PROXY_KEY MODEL EXPERIMENT_PROXY_LOG SPEND_START +# Mounts: /harness (worktree, ro) /src (full clone, ro) /work/out (rw) /cache (vol) +set -uo pipefail + +OUT=/work/out +HARNESS=/harness/scripts/experiments +: "${RUNNER:?}" "${CONDITION:?}" "${TERMCHART_REF:?}" "${TASK_ID:?}" "${TASK_PROMPT:?}" +PROXY_URL="${PROXY_URL:-http://host.containers.internal:4000}" +PROXY_KEY="${PROXY_KEY:-sk-experiment}" + +log() { echo "[cell $(date -u +%H:%M:%S)] $*"; } + +# 1. build termchart for this condition (once per condition; cached in /cache). +BD="/cache/${CONDITION}" +if [ ! -f "$BD/.built" ]; then + log "building termchart @ ${TERMCHART_REF}" + rm -rf "$BD"; git clone --quiet -b "$TERMCHART_REF" /src "$BD" + ( cd "$BD" && npm ci --no-audit --no-fund --silent && npm run build --silent ) \ + && touch "$BD/.built" || { log "build FAILED"; exit 1; } +fi +( cd "$BD" && npm i -g ./packages/cli --silent ) +log "termchart $(termchart --version 2>&1 | head -1)" + +# 2. stage a node-owned workdir (so the agent can write + bypass perms as non-root). +AW=/work/agent; rm -rf "$AW"; mkdir -p "$AW" +( cd "$BD" && tar cf - --exclude=node_modules --exclude=.git . ) | ( cd "$AW" && tar xf - ) +chown -R node:node "$AW" + +# 3. run the runner headless as `node`, timed. +COMMON_ENV="HOME=/home/node ANTHROPIC_BASE_URL=${PROXY_URL} ANTHROPIC_API_KEY=${PROXY_KEY} \ +ANTHROPIC_MODEL=shared-model ANTHROPIC_SMALL_FAST_MODEL=shared-model \ +OPENAI_BASE_URL=${PROXY_URL}/v1 OPENAI_API_KEY=${PROXY_KEY}" +case "$RUNNER" in + claude-code) RUN_CMD="claude -p \"\$TASK_PROMPT\" --permission-mode bypassPermissions --output-format json" ;; + opencode) RUN_CMD="opencode run \"\$TASK_PROMPT\" --model shared-model" ;; + *) log "runner '$RUNNER' not wired"; echo '{"is_error":true,"result":""}' >"$AW/runner.json" ; RUN_CMD="true" ;; +esac + +T0=$(date +%s%3N) +su node -c "cd $AW && export $COMMON_ENV TASK_PROMPT=\"$TASK_PROMPT\" && $RUN_CMD >$AW/runner.json 2>$AW/runner.err" || true +T1=$(date +%s%3N) +cp "$AW/runner.json" "$AW/runner.err" "$OUT/" 2>/dev/null || true +cp "$AW"/*.txt "$AW"/*.mmd "$OUT/" 2>/dev/null || true + +# 4. emit the RunRecord (reads proxy spend-log slice for ground-truth tokens). +WALL_MS=$((T1 - T0)) python3 "$HARNESS/cell_record.py" diff --git a/scripts/experiments/podman/run_local.sh b/scripts/experiments/podman/run_local.sh new file mode 100755 index 0000000..a60d46b --- /dev/null +++ b/scripts/experiments/podman/run_local.sh @@ -0,0 +1,84 @@ +#!/usr/bin/env bash +# Run the experiment locally in podman (no cloud). Starts one long-lived proxy +# container (shared model -> Vertex via ADC), then runs each cell in its own +# container (isolation), and aggregates the per-cell RunRecords. +# +# Usage (env-tunable): +# RUNNERS=claude-code CONDITIONS=baseline,c1 TASKS=terminal-er-orders REPS=3 \ +# scripts/experiments/podman/run_local.sh +# +# Prereqs: podman machine running; ADC present; image built +# podman build -t termchart-exp -f scripts/experiments/podman/Containerfile scripts/experiments/podman +set -euo pipefail + +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT="$(cd "$HERE/../../.." && pwd)" # worktree root +IMAGE="${IMAGE:-termchart-exp}" +MAIN_CLONE="${MAIN_CLONE:-/Users/ivanmkc/Documents/code/termchart}" # full .git (both refs) +ADC="${ADC:-$HOME/.config/gcloud/application_default_credentials.json}" +OUT="${OUT:-$ROOT/out/podman}" +MODEL="${EXPERIMENT_MODEL:-vertex_ai/gemini-2.5-flash}" +PROJECT="${VERTEXAI_PROJECT:-adk-coding-agents}" +LOCATION="${VERTEXAI_LOCATION:-us-central1}" +KEY="${LITELLM_MASTER_KEY:-sk-experiment}" +RUNNERS="${RUNNERS:-claude-code}" +CONDITIONS="${CONDITIONS:-c1}" +TASKS="${TASKS:-terminal-er-orders}" +REPS="${REPS:-1}" +SPEND="$OUT/spend.jsonl" + +mkdir -p "$OUT/cells" +: >"$SPEND" # fresh spend log for this run + +ref_for() { case "$1" in baseline) echo master;; c1) echo perf/reduce-latency;; *) echo "$1";; esac; } +task_field() { python3 -c "import json,sys +for l in open('$ROOT/scripts/experiments/tasks/pilot.jsonl'): + d=json.loads(l) + if d['id']==sys.argv[1]: print(d[sys.argv[2]]); break" "$1" "$2"; } + +echo "[run] image=$IMAGE model=$MODEL runners=$RUNNERS conditions=$CONDITIONS tasks=$TASKS reps=$REPS" + +# 1. proxy container --------------------------------------------------------- +podman rm -f tc-proxy >/dev/null 2>&1 || true +podman run -d --name tc-proxy -p 4000:4000 \ + -v "$ROOT/scripts/experiments/proxy":/proxy:ro \ + -v "$ADC":/adc.json:ro -v "$OUT":/work/out \ + -e EXPERIMENT_MODEL="$MODEL" -e VERTEXAI_PROJECT="$PROJECT" -e VERTEXAI_LOCATION="$LOCATION" \ + -e GOOGLE_APPLICATION_CREDENTIALS=/adc.json -e LITELLM_MASTER_KEY="$KEY" \ + -e EXPERIMENT_PROXY_LOG=/work/out/spend.jsonl -e PYTHONPATH=/proxy \ + "$IMAGE" -c 'cd /proxy && litellm --config litellm.config.yaml --port 4000' >/dev/null +echo "[run] waiting for proxy…" +for i in $(seq 1 60); do curl -sf http://127.0.0.1:4000/health/liveliness >/dev/null 2>&1 && break; sleep 1; done + +# 2. cells (sequential -> spend-log slice correlation is unambiguous) -------- +for cond in ${CONDITIONS//,/ }; do + REF="$(ref_for "$cond")" + for runner in ${RUNNERS//,/ }; do + for task in ${TASKS//,/ }; do + CAT="$(task_field "$task" category)"; SUR="$(task_field "$task" surface)" + PROMPT="$(task_field "$task" prompt)" + for rep in $(seq 0 $((REPS - 1))); do + START="$(wc -l <"$SPEND" 2>/dev/null | tr -d ' ' || echo 0)" + echo "[cell] $cond/$runner/$task r$rep (spend_start=$START)" + podman run --rm \ + -v "$ROOT":/harness:ro -v "$MAIN_CLONE":/src:ro \ + -v "$OUT":/work/out -v tc-cache:/cache \ + -e RUNNER="$runner" -e CONDITION="$cond" -e TERMCHART_REF="$REF" \ + -e TASK_ID="$task" -e TASK_CATEGORY="$CAT" -e TASK_SURFACE="$SUR" \ + -e TASK_PROMPT="$PROMPT" -e REP="$rep" \ + -e PROXY_URL="http://host.containers.internal:4000" -e PROXY_KEY="$KEY" \ + -e MODEL="$MODEL" -e EXPERIMENT_PROXY_LOG=/work/out/spend.jsonl \ + -e SPEND_START="$START" \ + "$IMAGE" -c 'bash /harness/scripts/experiments/podman/entrypoint_cell.sh' \ + 2>&1 | sed 's/^/ /' + done + done + done +done + +# 3. aggregate --------------------------------------------------------------- +podman rm -f tc-proxy >/dev/null 2>&1 || true +echo "[run] aggregating" +python3 "$ROOT/scripts/experiments/aggregate.py" "$OUT/agent.jsonl" --out "$OUT/aggregate" \ + || echo "[run] (aggregate needs >=1 cell)" +echo "[run] done -> $OUT/aggregate/aggregate.md" diff --git a/scripts/experiments/proxy/litellm.config.yaml b/scripts/experiments/proxy/litellm.config.yaml index f0dc87f..9ae5c73 100644 --- a/scripts/experiments/proxy/litellm.config.yaml +++ b/scripts/experiments/proxy/litellm.config.yaml @@ -1,33 +1,41 @@ # LiteLLM proxy — the single shared-model endpoint + token/latency measurement spine. # -# All three runners (Claude Code / AGY / OpenCode) point their base URL at this proxy -# so they exercise ONE model (the decided cross-runner control) and every call's token -# usage + latency is logged uniformly to a JSONL spend log that metrics.parse_proxy_log -# reads. +# All three runners point their base URL at this proxy so they exercise ONE model +# (the decided cross-runner control) and every call's tokens + latency are logged +# uniformly to a JSONL spend log that metrics.parse_proxy_log reads. # -# Start (on the VM): -# pip install 'litellm[proxy]' -# EXPERIMENT_MODEL=... LITELLM_MASTER_KEY=sk-experiment \ -# litellm --config litellm.config.yaml --port 4000 +# Backend: Vertex AI. Auth via ADC (GOOGLE_APPLICATION_CREDENTIALS) — the container +# mounts the host application_default_credentials.json. Project/region come from env. +# +# CURRENT shared model (works in adk-coding-agents today): Gemini 2.5 Flash, set via +# EXPERIMENT_MODEL=vertex_ai/gemini-2.5-flash +# CLAUDE FLIP (one line): once Anthropic is enabled in Model Garden, set +# EXPERIMENT_MODEL=vertex_ai/claude-sonnet-4@20250514 (region us-east5) +# and nothing else changes — runners still ask for "shared-model". # -# The model id the runners send ("shared-model") is aliased here to the real provider -# model from $EXPERIMENT_MODEL, so swapping the model never touches the harness. +# Start (in the container): +# EXPERIMENT_MODEL=vertex_ai/gemini-2.5-flash \ +# VERTEXAI_PROJECT=adk-coding-agents VERTEXAI_LOCATION=us-central1 \ +# GOOGLE_APPLICATION_CREDENTIALS=/adc.json LITELLM_MASTER_KEY=sk-experiment \ +# litellm --config litellm.config.yaml --port 4000 model_list: - - model_name: shared-model # what every runner asks for + - model_name: shared-model # what every runner asks for + litellm_params: + model: os.environ/EXPERIMENT_MODEL # vertex_ai/gemini-2.5-flash (or claude-…) + vertex_project: os.environ/VERTEXAI_PROJECT + vertex_location: os.environ/VERTEXAI_LOCATION + # Wildcard so Anthropic-format runners (Claude Code -> /v1/messages) whose request + # carries any model name still resolve to the one shared backend. + - model_name: "*" litellm_params: - model: os.environ/EXPERIMENT_MODEL # the real provider model (decided per pilot) - # Provider creds come from the environment (e.g. VERTEX_PROJECT / ANTHROPIC_API_KEY / - # OPENAI_API_KEY) depending on which provider EXPERIMENT_MODEL names. + model: os.environ/EXPERIMENT_MODEL + vertex_project: os.environ/VERTEXAI_PROJECT + vertex_location: os.environ/VERTEXAI_LOCATION litellm_settings: drop_params: true - # Custom callback writes one JSONL record per call (run_id, usage, latency) that the - # harness correlates by run_id. See proxy/spend_logger.py. - callbacks: ["spend_logger.run_logger"] + callbacks: ["spend_logger.run_logger"] # writes $EXPERIMENT_PROXY_LOG (JSONL) general_settings: master_key: os.environ/LITELLM_MASTER_KEY - # Surface the request header the harness uses to tag each call with its cell id. - # Runners forward EXPERIMENT_RUN_ID as the `x-run-id` header (validated in the pilot; - # for runners that can't add headers we fall back to time-window correlation). diff --git a/scripts/experiments/proxy/spend_logger.py b/scripts/experiments/proxy/spend_logger.py index e4b7102..9256bab 100644 --- a/scripts/experiments/proxy/spend_logger.py +++ b/scripts/experiments/proxy/spend_logger.py @@ -32,12 +32,29 @@ def _extract_run_id(kwargs: dict) -> str: return str(headers.get("x-run-id", "")) +def _get(obj, key, default: object = 0): + if obj is None: + return default + if isinstance(obj, dict): + return obj.get(key, default) + return getattr(obj, key, default) + + +def _usage_dict(response) -> dict: + """Clean, JSON-safe usage in the exact shape metrics.parse_proxy_log reads.""" + u = getattr(response, "usage", None) + if u is None and isinstance(response, dict): + u = response.get("usage") + details = _get(u, "prompt_tokens_details", {}) + return { + "prompt_tokens": int(_get(u, "prompt_tokens", 0) or 0), + "completion_tokens": int(_get(u, "completion_tokens", 0) or 0), + "prompt_tokens_details": {"cached_tokens": int(_get(details, "cached_tokens", 0) or 0)}, + "cache_creation_input_tokens": int(_get(u, "cache_creation_input_tokens", 0) or 0), + } + + def _record(kwargs: dict, response, start_time, end_time) -> dict: - usage = {} - try: - usage = dict(getattr(response, "usage", {}) or response.get("usage", {})) - except Exception: - usage = {} latency_ms = 0 try: latency_ms = int((end_time - start_time).total_seconds() * 1000) @@ -47,7 +64,7 @@ def _record(kwargs: dict, response, start_time, end_time) -> dict: "run_id": _extract_run_id(kwargs), "ts": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()), "model": kwargs.get("model", ""), - "usage": usage, + "usage": _usage_dict(response), "latency_ms": latency_ms, } diff --git a/scripts/experiments/test_experiments.py b/scripts/experiments/test_experiments.py index b195ad5..38f3a89 100644 --- a/scripts/experiments/test_experiments.py +++ b/scripts/experiments/test_experiments.py @@ -89,6 +89,30 @@ def test_parse_proxy_log_missing_file_is_empty(tmp_path): assert agg["llm_calls"] == 0 +def test_parse_proxy_log_slice_correlates_by_position(tmp_path): + """Sequential-cell correlation: only lines appended during the cell count.""" + log = tmp_path / "spend.jsonl" + def rec(p, c): + return json.dumps({"usage": {"prompt_tokens": p, "completion_tokens": c, + "prompt_tokens_details": {"cached_tokens": 0}}, + "latency_ms": 100}) + # cell A wrote 1 line, then cell B wrote 2 lines. + log.write_text("\n".join([rec(100, 10), rec(200, 20), rec(50, 5)]) + "\n", + encoding="utf-8") + start = 1 # cell B began after 1 line existed + agg = metrics.parse_proxy_log_slice(log, start) + assert agg["tokens_in"] == 250 # 200 + 50, not the first 100 + assert agg["tokens_out"] == 25 + assert agg["llm_calls"] == 2 + + +def test_count_log_lines(tmp_path): + log = tmp_path / "s.jsonl" + assert metrics.count_log_lines(log) == 0 + log.write_text("a\nb\nc\n", encoding="utf-8") + assert metrics.count_log_lines(log) == 3 + + # --- synthesize determinism --------------------------------------------------- def test_synthesize_is_deterministic_by_identity(): From 5bb1089245463687dc9bede9141301cc01017e92 Mon Sep 17 00:00:00 2001 From: Ivan Cheung Date: Sun, 7 Jun 2026 18:40:28 -0400 Subject: [PATCH 4/6] fix(docs): AGENTS.md push/status exit code is 4, not 3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When TERMCHART_VIEWER_URL/TOKEN are unset, push and status return EXIT_NO_VIEWER=4 (packages/cli/src/viewer-detect.ts:15), and the message is '…are not set: no termchart viewer configured.' AGENTS.md claimed exit 3 with a non-matching hint, which can mislead an agent into a wrong retry path. --- AGENTS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AGENTS.md b/AGENTS.md index 5b14412..cb1e12f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -39,7 +39,7 @@ Supported subset: `graph TD|LR` nodes `A[x]` `B{decision}` `C[(db)]` edges `-->` Push native JSON to a running viewer — run it locally (`PUSH_TOKEN=dev PORT=8080 node packages/viewer/dist/server.js`, open `http://localhost:8080/w/me/`; the viewer ships in this repo, not the npm CLI) or point at a deployed one. Configured by two -env vars — **if either is unset, `push`/`status` exit 3** with `Set TERMCHART_VIEWER_URL and TERMCHART_VIEWER_TOKEN.`: +env vars — **if either is unset, `push`/`status` exit 4** with `…are not set: no termchart viewer configured.`: ```bash export TERMCHART_VIEWER_URL="http://localhost:8080/w/me" From d5f7002e738b36b569ffe3f57ad326dd2523fc62 Mon Sep 17 00:00:00 2001 From: Ivan Cheung Date: Sun, 7 Jun 2026 18:41:22 -0400 Subject: [PATCH 5/6] perf(skills): minify diagram example JSONs (45% smaller) The diagram-recipes examples are loaded verbatim into agent context when an example is adapted. Pretty-printed, they were ~298 KB (the two *-matrix trees alone ~89 KB across ~2,800 lines). Minifying to compact JSON is byte-for-byte the same data but ~45% fewer bytes (305,190 -> 167,886), cutting tokens an agent spends to load an example. Still valid JSON; flow-geometry.test.ts JSON.parses them so it is unaffected. Fix T1 from the latency/token experiment plan. Refs #142. --- .../examples/agent-trace.component.json | 595 +------ .../examples/algorithm-walkthrough.panes.json | 21 +- .../examples/architecture-zones.flow.json | 51 +- .../examples/before-after.component.json | 153 +- .../examples/bfs-traversal.flow.json | 26 +- .../examples/binary-search.flow.json | 36 +- .../examples/c4-architecture.panes.json | 26 +- .../examples/calendar-heatmap.vegalite.json | 52 +- .../examples/call-hierarchy.flow.json | 58 +- .../examples/class-diagram.flow.json | 99 +- .../examples/complexity.vegalite.json | 23 +- .../correlation-heatmap.vegalite.json | 68 +- .../examples/critical-path.flow.json | 250 +-- .../examples/data-lineage.flow.json | 43 +- .../diagram-recipes/examples/debug.panes.json | 16 +- .../examples/diy-project-plan.component.json | 548 +----- .../examples/er-diagram.flow.json | 142 +- .../examples/event-driven.flow.json | 31 +- .../examples/explainer-bayes.panes.json | 26 +- .../examples/explainer-entropy.panes.json | 26 +- .../examples/explainer-relativity.panes.json | 26 +- .../examples/flame-graph.component.json | 410 +---- .../examples/gantt.vegalite.json | 90 +- .../examples/map-routes.component.json | 94 +- .../examples/metrics-dashboard.component.json | 229 +-- .../observability-dashboard.panes.json | 26 +- .../examples/pr-review-summary.component.json | 385 +---- .../examples/pr-review.flow.json | 188 +-- .../product-comparison.component.json | 431 +---- .../examples/query-plan.flow.json | 26 +- .../examples/raci-matrix.component.json | 1496 +---------------- .../examples/recursion-tree.flow.json | 30 +- .../examples/risk-matrix.component.json | 1368 +-------------- .../examples/roadmap.vegalite.json | 54 +- .../examples/sequence.flow.json | 155 +- .../examples/service-health.flow.json | 68 +- .../examples/sprint-burndown.vegalite.json | 238 +-- .../examples/stacktrace.component.json | 178 +- .../examples/state-machine.flow.json | 78 +- .../examples/swimlane.flow.json | 49 +- .../examples/system-architecture.flow.json | 46 +- .../examples/task-checklist.component.json | 82 +- .../examples/task-tracker.component.json | 462 +---- .../examples/trace-waterfall.vegalite.json | 156 +- .../examples/user-journey.flow.json | 132 +- 45 files changed, 45 insertions(+), 8742 deletions(-) diff --git a/plugin/skills/diagram-recipes/examples/agent-trace.component.json b/plugin/skills/diagram-recipes/examples/agent-trace.component.json index 8ed8fbd..7c54749 100644 --- a/plugin/skills/diagram-recipes/examples/agent-trace.component.json +++ b/plugin/skills/diagram-recipes/examples/agent-trace.component.json @@ -1,594 +1 @@ -{ - "type": "Stack", - "props": { - "gap": "sm", - "p": "md" - }, - "children": [ - { - "type": "Group", - "props": { - "justify": "space-between", - "align": "flex-end", - "wrap": "nowrap" - }, - "children": [ - { - "type": "Title", - "props": { - "order": 4 - }, - "children": "Agent run — debugging checkout 504s" - }, - { - "type": "Stack", - "props": { - "gap": 0, - "align": "flex-end" - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "indigo", - "variant": "light", - "size": "lg" - }, - "children": "steps 6–19 of 19" - }, - { - "type": "Text", - "props": { - "size": "xs", - "c": "dimmed", - "mt": 2 - }, - "children": "▲ root cause located" - } - ] - } - ] - }, - { - "type": "Divider", - "props": {} - }, - { - "type": "Timeline", - "props": { - "active": 13, - "bulletSize": 22, - "lineWidth": 2 - }, - "children": [ - { - "type": "Timeline.Item", - "props": { - "title": "Step 6 — git log --oneline --since=2h on the checkout service", - "color": "yellow", - "lineVariant": "solid" - }, - "children": [ - { - "type": "Group", - "props": { - "gap": "xs", - "mt": 4, - "mb": 2 - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "yellow", - "variant": "light", - "size": "xs" - }, - "children": "tool-call" - } - ] - }, - { - "type": "Text", - "props": { - "size": "sm", - "c": "dimmed" - }, - "children": "Looking for what changed right before the 504s began." - } - ] - }, - { - "type": "Timeline.Item", - "props": { - "title": "Step 7 — found deploy v2.18.0 landed 13:58, errors began 14:01", - "color": "gray", - "lineVariant": "solid" - }, - "children": [ - { - "type": "Group", - "props": { - "gap": "xs", - "mt": 4, - "mb": 2 - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "gray", - "variant": "light", - "size": "xs" - }, - "children": "observation" - } - ] - }, - { - "type": "Text", - "props": { - "size": "sm", - "c": "dimmed" - }, - "children": "Three-minute gap lines up suspiciously with the first failed checkout." - } - ] - }, - { - "type": "Timeline.Item", - "props": { - "title": "Step 8 — hypothesis: v2.18.0 changed an upstream timeout", - "color": "blue", - "lineVariant": "solid" - }, - "children": [ - { - "type": "Group", - "props": { - "gap": "xs", - "mt": 4, - "mb": 2 - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "blue", - "variant": "light", - "size": "xs" - }, - "children": "think" - } - ] - }, - { - "type": "Text", - "props": { - "size": "sm", - "c": "dimmed" - }, - "children": "504 = gateway timeout, so a tightened upstream deadline would explain it." - } - ] - }, - { - "type": "Timeline.Item", - "props": { - "title": "Step 9 — git diff v2.17.4..v2.18.0 -- config/upstream.yaml", - "color": "yellow", - "lineVariant": "solid" - }, - "children": [ - { - "type": "Group", - "props": { - "gap": "xs", - "mt": 4, - "mb": 2 - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "yellow", - "variant": "light", - "size": "xs" - }, - "children": "tool-call" - } - ] - }, - { - "type": "Text", - "props": { - "size": "sm", - "c": "dimmed" - }, - "children": "Diffing the release that landed at 13:58 against the prior good one." - } - ] - }, - { - "type": "Timeline.Item", - "props": { - "title": "Step 10 — upstream read_timeout 30s → 3s in v2.18.0", - "color": "gray", - "lineVariant": "solid" - }, - "children": [ - { - "type": "Group", - "props": { - "gap": "xs", - "mt": 4, - "mb": 2 - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "gray", - "variant": "light", - "size": "xs" - }, - "children": "observation" - } - ] - }, - { - "type": "Text", - "props": { - "size": "sm", - "c": "dimmed" - }, - "children": "payments-api p99 is ~6s, so a 3s deadline trips on every slow call." - } - ] - }, - { - "type": "Timeline.Item", - "props": { - "title": "Step 11 — grep error logs for 'context deadline exceeded'", - "color": "yellow", - "lineVariant": "solid" - }, - "children": [ - { - "type": "Group", - "props": { - "gap": "xs", - "mt": 4, - "mb": 2 - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "yellow", - "variant": "light", - "size": "xs" - }, - "children": "tool-call" - } - ] - }, - { - "type": "Text", - "props": { - "size": "sm", - "c": "dimmed" - }, - "children": "Confirming the failure mode is the upstream call, not the DB." - } - ] - }, - { - "type": "Timeline.Item", - "props": { - "title": "Step 12 — 1,204 deadline-exceeded errors, all on POST /charge", - "color": "gray", - "lineVariant": "solid" - }, - "children": [ - { - "type": "Group", - "props": { - "gap": "xs", - "mt": 4, - "mb": 2 - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "gray", - "variant": "light", - "size": "xs" - }, - "children": "observation" - } - ] - }, - { - "type": "Text", - "props": { - "size": "sm", - "c": "dimmed" - }, - "children": "Every 504 traces back to the payments upstream call timing out." - } - ] - }, - { - "type": "Timeline.Item", - "props": { - "title": "Step 13 — rule out load: traffic flat across the window", - "color": "blue", - "lineVariant": "solid" - }, - "children": [ - { - "type": "Group", - "props": { - "gap": "xs", - "mt": 4, - "mb": 2 - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "blue", - "variant": "light", - "size": "xs" - }, - "children": "think" - } - ] - }, - { - "type": "Text", - "props": { - "size": "sm", - "c": "dimmed" - }, - "children": "Requests/min unchanged vs. yesterday — not a capacity problem." - } - ] - }, - { - "type": "Timeline.Item", - "props": { - "title": "Step 14 — query traces for POST /charge upstream latency", - "color": "yellow", - "lineVariant": "solid" - }, - "children": [ - { - "type": "Group", - "props": { - "gap": "xs", - "mt": 4, - "mb": 2 - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "yellow", - "variant": "light", - "size": "xs" - }, - "children": "tool-call" - } - ] - }, - { - "type": "Text", - "props": { - "size": "sm", - "c": "dimmed" - }, - "children": "p50 1.1s · p99 5.9s — well under 30s, far over the new 3s." - } - ] - }, - { - "type": "Timeline.Item", - "props": { - "title": "Step 15 — healthy spans take 4–6s, now cut off at 3s", - "color": "gray", - "lineVariant": "solid" - }, - "children": [ - { - "type": "Group", - "props": { - "gap": "xs", - "mt": 4, - "mb": 2 - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "gray", - "variant": "light", - "size": "xs" - }, - "children": "observation" - } - ] - }, - { - "type": "Text", - "props": { - "size": "sm", - "c": "dimmed" - }, - "children": "The work itself is fine; only the deadline regressed." - } - ] - }, - { - "type": "Timeline.Item", - "props": { - "title": "Step 16 — replay a failed checkout against staging", - "color": "yellow", - "lineVariant": "solid" - }, - "children": [ - { - "type": "Group", - "props": { - "gap": "xs", - "mt": 4, - "mb": 2 - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "yellow", - "variant": "light", - "size": "xs" - }, - "children": "tool-call" - } - ] - }, - { - "type": "Text", - "props": { - "size": "sm", - "c": "dimmed" - }, - "children": "Reproduce end-to-end to confirm the timeout is the trigger." - } - ] - }, - { - "type": "Timeline.Item", - "props": { - "title": "Step 17 — staging returns 504 after exactly 3.0s", - "color": "gray", - "lineVariant": "solid" - }, - "children": [ - { - "type": "Group", - "props": { - "gap": "xs", - "mt": 4, - "mb": 2 - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "gray", - "variant": "light", - "size": "xs" - }, - "children": "observation" - } - ] - }, - { - "type": "Text", - "props": { - "size": "sm", - "c": "dimmed" - }, - "children": "Deterministic cutoff at the new deadline — reproduced." - } - ] - }, - { - "type": "Timeline.Item", - "props": { - "title": "Step 18 — bump read_timeout back to 30s in staging", - "color": "blue", - "lineVariant": "solid" - }, - "children": [ - { - "type": "Group", - "props": { - "gap": "xs", - "mt": 4, - "mb": 2 - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "blue", - "variant": "light", - "size": "xs" - }, - "children": "think" - } - ] - }, - { - "type": "Text", - "props": { - "size": "sm", - "c": "dimmed" - }, - "children": "Smallest reversible change to test the fix." - } - ] - }, - { - "type": "Timeline.Item", - "props": { - "title": "Step 19 — staging checkout succeeds in 5.2s, no 504", - "color": "teal", - "lineVariant": "solid" - }, - "children": [ - { - "type": "Group", - "props": { - "gap": "xs", - "mt": 4, - "mb": 2 - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "teal", - "variant": "light", - "size": "xs" - }, - "children": "result" - } - ] - }, - { - "type": "Text", - "props": { - "size": "sm", - "c": "dimmed" - }, - "children": "Restoring the timeout clears the error. Root cause confirmed." - } - ] - } - ] - }, - { - "type": "Alert", - "props": { - "color": "red", - "variant": "light", - "title": "Root cause — deploy v2.18.0" - }, - "children": "v2.18.0 cut the upstream read_timeout 30s → 3s (config/upstream.yaml); payments-api p99 is ~6s, so every POST /charge trips the deadline and returns 504. Fix: restore the 30s timeout (or raise it past p99) and ship a hotfix." - } - ] -} +{"type":"Stack","props":{"gap":"sm","p":"md"},"children":[{"type":"Group","props":{"justify":"space-between","align":"flex-end","wrap":"nowrap"},"children":[{"type":"Title","props":{"order":4},"children":"Agent run — debugging checkout 504s"},{"type":"Stack","props":{"gap":0,"align":"flex-end"},"children":[{"type":"Badge","props":{"color":"indigo","variant":"light","size":"lg"},"children":"steps 6–19 of 19"},{"type":"Text","props":{"size":"xs","c":"dimmed","mt":2},"children":"▲ root cause located"}]}]},{"type":"Divider","props":{}},{"type":"Timeline","props":{"active":13,"bulletSize":22,"lineWidth":2},"children":[{"type":"Timeline.Item","props":{"title":"Step 6 — git log --oneline --since=2h on the checkout service","color":"yellow","lineVariant":"solid"},"children":[{"type":"Group","props":{"gap":"xs","mt":4,"mb":2},"children":[{"type":"Badge","props":{"color":"yellow","variant":"light","size":"xs"},"children":"tool-call"}]},{"type":"Text","props":{"size":"sm","c":"dimmed"},"children":"Looking for what changed right before the 504s began."}]},{"type":"Timeline.Item","props":{"title":"Step 7 — found deploy v2.18.0 landed 13:58, errors began 14:01","color":"gray","lineVariant":"solid"},"children":[{"type":"Group","props":{"gap":"xs","mt":4,"mb":2},"children":[{"type":"Badge","props":{"color":"gray","variant":"light","size":"xs"},"children":"observation"}]},{"type":"Text","props":{"size":"sm","c":"dimmed"},"children":"Three-minute gap lines up suspiciously with the first failed checkout."}]},{"type":"Timeline.Item","props":{"title":"Step 8 — hypothesis: v2.18.0 changed an upstream timeout","color":"blue","lineVariant":"solid"},"children":[{"type":"Group","props":{"gap":"xs","mt":4,"mb":2},"children":[{"type":"Badge","props":{"color":"blue","variant":"light","size":"xs"},"children":"think"}]},{"type":"Text","props":{"size":"sm","c":"dimmed"},"children":"504 = gateway timeout, so a tightened upstream deadline would explain it."}]},{"type":"Timeline.Item","props":{"title":"Step 9 — git diff v2.17.4..v2.18.0 -- config/upstream.yaml","color":"yellow","lineVariant":"solid"},"children":[{"type":"Group","props":{"gap":"xs","mt":4,"mb":2},"children":[{"type":"Badge","props":{"color":"yellow","variant":"light","size":"xs"},"children":"tool-call"}]},{"type":"Text","props":{"size":"sm","c":"dimmed"},"children":"Diffing the release that landed at 13:58 against the prior good one."}]},{"type":"Timeline.Item","props":{"title":"Step 10 — upstream read_timeout 30s → 3s in v2.18.0","color":"gray","lineVariant":"solid"},"children":[{"type":"Group","props":{"gap":"xs","mt":4,"mb":2},"children":[{"type":"Badge","props":{"color":"gray","variant":"light","size":"xs"},"children":"observation"}]},{"type":"Text","props":{"size":"sm","c":"dimmed"},"children":"payments-api p99 is ~6s, so a 3s deadline trips on every slow call."}]},{"type":"Timeline.Item","props":{"title":"Step 11 — grep error logs for 'context deadline exceeded'","color":"yellow","lineVariant":"solid"},"children":[{"type":"Group","props":{"gap":"xs","mt":4,"mb":2},"children":[{"type":"Badge","props":{"color":"yellow","variant":"light","size":"xs"},"children":"tool-call"}]},{"type":"Text","props":{"size":"sm","c":"dimmed"},"children":"Confirming the failure mode is the upstream call, not the DB."}]},{"type":"Timeline.Item","props":{"title":"Step 12 — 1,204 deadline-exceeded errors, all on POST /charge","color":"gray","lineVariant":"solid"},"children":[{"type":"Group","props":{"gap":"xs","mt":4,"mb":2},"children":[{"type":"Badge","props":{"color":"gray","variant":"light","size":"xs"},"children":"observation"}]},{"type":"Text","props":{"size":"sm","c":"dimmed"},"children":"Every 504 traces back to the payments upstream call timing out."}]},{"type":"Timeline.Item","props":{"title":"Step 13 — rule out load: traffic flat across the window","color":"blue","lineVariant":"solid"},"children":[{"type":"Group","props":{"gap":"xs","mt":4,"mb":2},"children":[{"type":"Badge","props":{"color":"blue","variant":"light","size":"xs"},"children":"think"}]},{"type":"Text","props":{"size":"sm","c":"dimmed"},"children":"Requests/min unchanged vs. yesterday — not a capacity problem."}]},{"type":"Timeline.Item","props":{"title":"Step 14 — query traces for POST /charge upstream latency","color":"yellow","lineVariant":"solid"},"children":[{"type":"Group","props":{"gap":"xs","mt":4,"mb":2},"children":[{"type":"Badge","props":{"color":"yellow","variant":"light","size":"xs"},"children":"tool-call"}]},{"type":"Text","props":{"size":"sm","c":"dimmed"},"children":"p50 1.1s · p99 5.9s — well under 30s, far over the new 3s."}]},{"type":"Timeline.Item","props":{"title":"Step 15 — healthy spans take 4–6s, now cut off at 3s","color":"gray","lineVariant":"solid"},"children":[{"type":"Group","props":{"gap":"xs","mt":4,"mb":2},"children":[{"type":"Badge","props":{"color":"gray","variant":"light","size":"xs"},"children":"observation"}]},{"type":"Text","props":{"size":"sm","c":"dimmed"},"children":"The work itself is fine; only the deadline regressed."}]},{"type":"Timeline.Item","props":{"title":"Step 16 — replay a failed checkout against staging","color":"yellow","lineVariant":"solid"},"children":[{"type":"Group","props":{"gap":"xs","mt":4,"mb":2},"children":[{"type":"Badge","props":{"color":"yellow","variant":"light","size":"xs"},"children":"tool-call"}]},{"type":"Text","props":{"size":"sm","c":"dimmed"},"children":"Reproduce end-to-end to confirm the timeout is the trigger."}]},{"type":"Timeline.Item","props":{"title":"Step 17 — staging returns 504 after exactly 3.0s","color":"gray","lineVariant":"solid"},"children":[{"type":"Group","props":{"gap":"xs","mt":4,"mb":2},"children":[{"type":"Badge","props":{"color":"gray","variant":"light","size":"xs"},"children":"observation"}]},{"type":"Text","props":{"size":"sm","c":"dimmed"},"children":"Deterministic cutoff at the new deadline — reproduced."}]},{"type":"Timeline.Item","props":{"title":"Step 18 — bump read_timeout back to 30s in staging","color":"blue","lineVariant":"solid"},"children":[{"type":"Group","props":{"gap":"xs","mt":4,"mb":2},"children":[{"type":"Badge","props":{"color":"blue","variant":"light","size":"xs"},"children":"think"}]},{"type":"Text","props":{"size":"sm","c":"dimmed"},"children":"Smallest reversible change to test the fix."}]},{"type":"Timeline.Item","props":{"title":"Step 19 — staging checkout succeeds in 5.2s, no 504","color":"teal","lineVariant":"solid"},"children":[{"type":"Group","props":{"gap":"xs","mt":4,"mb":2},"children":[{"type":"Badge","props":{"color":"teal","variant":"light","size":"xs"},"children":"result"}]},{"type":"Text","props":{"size":"sm","c":"dimmed"},"children":"Restoring the timeout clears the error. Root cause confirmed."}]}]},{"type":"Alert","props":{"color":"red","variant":"light","title":"Root cause — deploy v2.18.0"},"children":"v2.18.0 cut the upstream read_timeout 30s → 3s (config/upstream.yaml); payments-api p99 is ~6s, so every POST /charge trips the deadline and returns 504. Fix: restore the 30s timeout (or raise it past p99) and ship a hotfix."}]} diff --git a/plugin/skills/diagram-recipes/examples/algorithm-walkthrough.panes.json b/plugin/skills/diagram-recipes/examples/algorithm-walkthrough.panes.json index d741f8e..ac7df5b 100644 --- a/plugin/skills/diagram-recipes/examples/algorithm-walkthrough.panes.json +++ b/plugin/skills/diagram-recipes/examples/algorithm-walkthrough.panes.json @@ -1,20 +1 @@ -{ - "layout": "columns", - "panes": [ - { - "title": "Algorithm", - "type": "markdown", - "content": "## Binary search — `O(log n)`\n\nFind a target in a **sorted** array by halving the search window each step.\n\n```ts\nfunction search(a: number[], t: number) {\n let lo = 0, hi = a.length - 1;\n while (lo <= hi) {\n const mid = (lo + hi) >> 1;\n if (a[mid] === t) return mid; // hit\n if (a[mid] < t) lo = mid + 1; // go right\n else hi = mid - 1; // go left\n }\n return -1; // not found\n}\n```\n\nEach iteration discards half the remaining elements, so it finishes in at most\n`⌈log₂ n⌉` comparisons — 10 for a thousand items, 20 for a million." - }, - { - "title": "Trace", - "type": "component", - "content": "{\"type\":\"Stack\",\"props\":{\"gap\":\"sm\",\"p\":\"sm\"},\"children\":[{\"type\":\"Title\",\"props\":{\"order\":4},\"children\":\"Trace — find 23\"},{\"type\":\"Text\",\"props\":{\"c\":\"dimmed\",\"size\":\"sm\",\"mt\":-4},\"children\":\"a = [2, 5, 8, 12, 16, 23, 38, 56, 72, 91]\"},{\"type\":\"Table\",\"props\":{\"withTableBorder\":true,\"withColumnBorders\":true,\"data\":{\"head\":[\"step\",\"lo\",\"hi\",\"mid\",\"a[mid]\",\"action\"],\"body\":[[\"1\",\"0\",\"9\",\"4\",\"16\",\"16 < 23 → go right\"],[\"2\",\"5\",\"9\",\"7\",\"56\",\"56 > 23 → go left\"],[\"3\",\"5\",\"6\",\"5\",\"23\",\"23 = 23 → found @ 5\"]]}}},{\"type\":\"Alert\",\"props\":{\"color\":\"green\",\"title\":\"Found at index 5 in 3 steps\"},\"children\":\"Linear scan would take 6 comparisons; binary search took 3 (⌈log₂ 10⌉ = 4 max).\"}]}" - }, - { - "title": "Complexity", - "type": "vegalite", - "content": "{\"$schema\":\"https://vega.github.io/schema/vega-lite/v6.json\",\"title\":{\"text\":\"log n vs n\",\"subtitle\":\"comparisons to find one item\",\"anchor\":\"start\"},\"width\":\"container\",\"height\":300,\"data\":{\"values\":[{\"n\":1,\"ops\":0,\"kind\":\"binary search O(log n)\"},{\"n\":1,\"ops\":1,\"kind\":\"linear scan O(n)\"},{\"n\":8,\"ops\":3,\"kind\":\"binary search O(log n)\"},{\"n\":8,\"ops\":8,\"kind\":\"linear scan O(n)\"},{\"n\":16,\"ops\":4,\"kind\":\"binary search O(log n)\"},{\"n\":16,\"ops\":16,\"kind\":\"linear scan O(n)\"},{\"n\":32,\"ops\":5,\"kind\":\"binary search O(log n)\"},{\"n\":32,\"ops\":32,\"kind\":\"linear scan O(n)\"},{\"n\":64,\"ops\":6,\"kind\":\"binary search O(log n)\"},{\"n\":64,\"ops\":64,\"kind\":\"linear scan O(n)\"},{\"n\":128,\"ops\":7,\"kind\":\"binary search O(log n)\"},{\"n\":128,\"ops\":128,\"kind\":\"linear scan O(n)\"},{\"n\":256,\"ops\":8,\"kind\":\"binary search O(log n)\"},{\"n\":256,\"ops\":256,\"kind\":\"linear scan O(n)\"},{\"n\":512,\"ops\":9,\"kind\":\"binary search O(log n)\"},{\"n\":512,\"ops\":512,\"kind\":\"linear scan O(n)\"},{\"n\":1024,\"ops\":10,\"kind\":\"binary search O(log n)\"},{\"n\":1024,\"ops\":1024,\"kind\":\"linear scan O(n)\"}]},\"mark\":{\"type\":\"line\",\"point\":true,\"tooltip\":true},\"encoding\":{\"x\":{\"field\":\"n\",\"type\":\"quantitative\",\"title\":\"array size (n)\"},\"y\":{\"field\":\"ops\",\"type\":\"quantitative\",\"title\":\"comparisons\",\"scale\":{\"type\":\"symlog\"}},\"color\":{\"field\":\"kind\",\"type\":\"nominal\",\"title\":null,\"scale\":{\"range\":[\"#22c55e\",\"#ef4444\"]}}}}" - } - ] -} +{"layout":"columns","panes":[{"title":"Algorithm","type":"markdown","content":"## Binary search — `O(log n)`\n\nFind a target in a **sorted** array by halving the search window each step.\n\n```ts\nfunction search(a: number[], t: number) {\n let lo = 0, hi = a.length - 1;\n while (lo <= hi) {\n const mid = (lo + hi) >> 1;\n if (a[mid] === t) return mid; // hit\n if (a[mid] < t) lo = mid + 1; // go right\n else hi = mid - 1; // go left\n }\n return -1; // not found\n}\n```\n\nEach iteration discards half the remaining elements, so it finishes in at most\n`⌈log₂ n⌉` comparisons — 10 for a thousand items, 20 for a million."},{"title":"Trace","type":"component","content":"{\"type\":\"Stack\",\"props\":{\"gap\":\"sm\",\"p\":\"sm\"},\"children\":[{\"type\":\"Title\",\"props\":{\"order\":4},\"children\":\"Trace — find 23\"},{\"type\":\"Text\",\"props\":{\"c\":\"dimmed\",\"size\":\"sm\",\"mt\":-4},\"children\":\"a = [2, 5, 8, 12, 16, 23, 38, 56, 72, 91]\"},{\"type\":\"Table\",\"props\":{\"withTableBorder\":true,\"withColumnBorders\":true,\"data\":{\"head\":[\"step\",\"lo\",\"hi\",\"mid\",\"a[mid]\",\"action\"],\"body\":[[\"1\",\"0\",\"9\",\"4\",\"16\",\"16 < 23 → go right\"],[\"2\",\"5\",\"9\",\"7\",\"56\",\"56 > 23 → go left\"],[\"3\",\"5\",\"6\",\"5\",\"23\",\"23 = 23 → found @ 5\"]]}}},{\"type\":\"Alert\",\"props\":{\"color\":\"green\",\"title\":\"Found at index 5 in 3 steps\"},\"children\":\"Linear scan would take 6 comparisons; binary search took 3 (⌈log₂ 10⌉ = 4 max).\"}]}"},{"title":"Complexity","type":"vegalite","content":"{\"$schema\":\"https://vega.github.io/schema/vega-lite/v6.json\",\"title\":{\"text\":\"log n vs n\",\"subtitle\":\"comparisons to find one item\",\"anchor\":\"start\"},\"width\":\"container\",\"height\":300,\"data\":{\"values\":[{\"n\":1,\"ops\":0,\"kind\":\"binary search O(log n)\"},{\"n\":1,\"ops\":1,\"kind\":\"linear scan O(n)\"},{\"n\":8,\"ops\":3,\"kind\":\"binary search O(log n)\"},{\"n\":8,\"ops\":8,\"kind\":\"linear scan O(n)\"},{\"n\":16,\"ops\":4,\"kind\":\"binary search O(log n)\"},{\"n\":16,\"ops\":16,\"kind\":\"linear scan O(n)\"},{\"n\":32,\"ops\":5,\"kind\":\"binary search O(log n)\"},{\"n\":32,\"ops\":32,\"kind\":\"linear scan O(n)\"},{\"n\":64,\"ops\":6,\"kind\":\"binary search O(log n)\"},{\"n\":64,\"ops\":64,\"kind\":\"linear scan O(n)\"},{\"n\":128,\"ops\":7,\"kind\":\"binary search O(log n)\"},{\"n\":128,\"ops\":128,\"kind\":\"linear scan O(n)\"},{\"n\":256,\"ops\":8,\"kind\":\"binary search O(log n)\"},{\"n\":256,\"ops\":256,\"kind\":\"linear scan O(n)\"},{\"n\":512,\"ops\":9,\"kind\":\"binary search O(log n)\"},{\"n\":512,\"ops\":512,\"kind\":\"linear scan O(n)\"},{\"n\":1024,\"ops\":10,\"kind\":\"binary search O(log n)\"},{\"n\":1024,\"ops\":1024,\"kind\":\"linear scan O(n)\"}]},\"mark\":{\"type\":\"line\",\"point\":true,\"tooltip\":true},\"encoding\":{\"x\":{\"field\":\"n\",\"type\":\"quantitative\",\"title\":\"array size (n)\"},\"y\":{\"field\":\"ops\",\"type\":\"quantitative\",\"title\":\"comparisons\",\"scale\":{\"type\":\"symlog\"}},\"color\":{\"field\":\"kind\",\"type\":\"nominal\",\"title\":null,\"scale\":{\"range\":[\"#22c55e\",\"#ef4444\"]}}}}"}]} diff --git a/plugin/skills/diagram-recipes/examples/architecture-zones.flow.json b/plugin/skills/diagram-recipes/examples/architecture-zones.flow.json index a8919de..b21a2f2 100644 --- a/plugin/skills/diagram-recipes/examples/architecture-zones.flow.json +++ b/plugin/skills/diagram-recipes/examples/architecture-zones.flow.json @@ -1,50 +1 @@ -{ - "direction": "LR", - "groups": [ - { "id": "edge", "label": "Edge", "color": "#a1a1aa" }, - { "id": "frontend", "label": "Frontend", "color": "#60a5fa" }, - { "id": "services", "label": "Services", "color": "#fbbf24" }, - { "id": "data", "label": "Data stores", "color": "#22c55e" }, - { "id": "async", "label": "Async / streaming", "color": "#a855f7" } - ], - "legend": [ - { "label": "request path", "color": "#a1a1aa" }, - { "label": "datastore write", "color": "#22c55e" }, - { "label": "event stream", "color": "#a855f7", "dash": true } - ], - "nodes": [ - { "id": "client", "type": "change", "group": "edge", "data": { "label": "Client", "status": "info", "icon": "user" } }, - { "id": "cdn", "type": "change", "group": "edge", "data": { "label": "CDN", "status": "neutral" } }, - { "id": "lb", "type": "change", "group": "edge", "data": { "label": "Load Balancer", "status": "neutral" } }, - - { "id": "web", "type": "change", "group": "frontend", "data": { "label": "Web App", "status": "info" } }, - { "id": "bff", "type": "change", "group": "frontend", "data": { "label": "BFF / Gateway", "status": "info" } }, - - { "id": "auth", "type": "change", "group": "services", "data": { "label": "Auth", "status": "active" } }, - { "id": "orders", "type": "change", "group": "services", "data": { "label": "Orders", "status": "active" } }, - { "id": "inventory", "type": "change", "group": "services", "data": { "label": "Inventory", "status": "active" } }, - { "id": "payments", "type": "change", "group": "services", "data": { "label": "Payments", "status": "active" } }, - - { "id": "pg", "type": "change", "group": "data", "data": { "label": "PostgreSQL", "status": "success", "icon": "db" } }, - { "id": "redis", "type": "change", "group": "data", "data": { "label": "Redis", "status": "warn", "icon": "db" } }, - - { "id": "kafka", "type": "change", "group": "async", "data": { "label": "Kafka", "status": "info", "icon": "bolt" } }, - { "id": "worker", "type": "change", "group": "async", "data": { "label": "Fulfillment Worker", "status": "active" } } - ], - "edges": [ - { "source": "client", "target": "cdn" }, - { "source": "cdn", "target": "lb" }, - { "source": "lb", "target": "web" }, - { "source": "web", "target": "bff" }, - { "source": "bff", "target": "auth" }, - { "source": "bff", "target": "orders" }, - { "source": "bff", "target": "inventory" }, - { "source": "bff", "target": "payments" }, - { "source": "auth", "target": "redis", "style": { "stroke": "#22c55e" }, "markerEnd": { "type": "arrowclosed", "color": "#22c55e" } }, - { "source": "orders", "target": "pg", "style": { "stroke": "#22c55e" }, "markerEnd": { "type": "arrowclosed", "color": "#22c55e" } }, - { "source": "inventory", "target": "pg", "style": { "stroke": "#22c55e" }, "markerEnd": { "type": "arrowclosed", "color": "#22c55e" } }, - { "source": "payments", "target": "pg", "style": { "stroke": "#22c55e" }, "markerEnd": { "type": "arrowclosed", "color": "#22c55e" } }, - { "source": "orders", "target": "kafka", "animated": true, "style": { "stroke": "#a855f7" }, "markerEnd": { "type": "arrowclosed", "color": "#a855f7" } }, - { "source": "kafka", "target": "worker", "animated": true, "style": { "stroke": "#a855f7" }, "markerEnd": { "type": "arrowclosed", "color": "#a855f7" } } - ] -} +{"direction":"LR","groups":[{"id":"edge","label":"Edge","color":"#a1a1aa"},{"id":"frontend","label":"Frontend","color":"#60a5fa"},{"id":"services","label":"Services","color":"#fbbf24"},{"id":"data","label":"Data stores","color":"#22c55e"},{"id":"async","label":"Async / streaming","color":"#a855f7"}],"legend":[{"label":"request path","color":"#a1a1aa"},{"label":"datastore write","color":"#22c55e"},{"label":"event stream","color":"#a855f7","dash":true}],"nodes":[{"id":"client","type":"change","group":"edge","data":{"label":"Client","status":"info","icon":"user"}},{"id":"cdn","type":"change","group":"edge","data":{"label":"CDN","status":"neutral"}},{"id":"lb","type":"change","group":"edge","data":{"label":"Load Balancer","status":"neutral"}},{"id":"web","type":"change","group":"frontend","data":{"label":"Web App","status":"info"}},{"id":"bff","type":"change","group":"frontend","data":{"label":"BFF / Gateway","status":"info"}},{"id":"auth","type":"change","group":"services","data":{"label":"Auth","status":"active"}},{"id":"orders","type":"change","group":"services","data":{"label":"Orders","status":"active"}},{"id":"inventory","type":"change","group":"services","data":{"label":"Inventory","status":"active"}},{"id":"payments","type":"change","group":"services","data":{"label":"Payments","status":"active"}},{"id":"pg","type":"change","group":"data","data":{"label":"PostgreSQL","status":"success","icon":"db"}},{"id":"redis","type":"change","group":"data","data":{"label":"Redis","status":"warn","icon":"db"}},{"id":"kafka","type":"change","group":"async","data":{"label":"Kafka","status":"info","icon":"bolt"}},{"id":"worker","type":"change","group":"async","data":{"label":"Fulfillment Worker","status":"active"}}],"edges":[{"source":"client","target":"cdn"},{"source":"cdn","target":"lb"},{"source":"lb","target":"web"},{"source":"web","target":"bff"},{"source":"bff","target":"auth"},{"source":"bff","target":"orders"},{"source":"bff","target":"inventory"},{"source":"bff","target":"payments"},{"source":"auth","target":"redis","style":{"stroke":"#22c55e"},"markerEnd":{"type":"arrowclosed","color":"#22c55e"}},{"source":"orders","target":"pg","style":{"stroke":"#22c55e"},"markerEnd":{"type":"arrowclosed","color":"#22c55e"}},{"source":"inventory","target":"pg","style":{"stroke":"#22c55e"},"markerEnd":{"type":"arrowclosed","color":"#22c55e"}},{"source":"payments","target":"pg","style":{"stroke":"#22c55e"},"markerEnd":{"type":"arrowclosed","color":"#22c55e"}},{"source":"orders","target":"kafka","animated":true,"style":{"stroke":"#a855f7"},"markerEnd":{"type":"arrowclosed","color":"#a855f7"}},{"source":"kafka","target":"worker","animated":true,"style":{"stroke":"#a855f7"},"markerEnd":{"type":"arrowclosed","color":"#a855f7"}}]} diff --git a/plugin/skills/diagram-recipes/examples/before-after.component.json b/plugin/skills/diagram-recipes/examples/before-after.component.json index 7bdc388..99731f4 100644 --- a/plugin/skills/diagram-recipes/examples/before-after.component.json +++ b/plugin/skills/diagram-recipes/examples/before-after.component.json @@ -1,152 +1 @@ -{ - "type": "Stack", - "props": { - "gap": "md", - "p": "md" - }, - "children": [ - { - "type": "Title", - "props": { - "order": 3 - }, - "children": "Refactor: auth module" - }, - { - "type": "SimpleGrid", - "props": { - "cols": 2, - "spacing": "lg" - }, - "children": [ - { - "type": "Card", - "props": { - "withBorder": true, - "padding": "md" - }, - "children": [ - { - "type": "Group", - "props": { - "justify": "space-between" - }, - "children": [ - { - "type": "Text", - "props": { - "fw": 700 - }, - "children": "Before" - }, - { - "type": "Badge", - "props": { - "color": "gray" - }, - "children": "main@a1b2c3" - } - ] - }, - { - "type": "Table", - "props": { - "mt": "sm", - "data": { - "head": [ - "Metric", - "Value" - ], - "body": [ - [ - "LOC", - "412" - ], - [ - "Cyclomatic", - "37" - ], - [ - "Test cov.", - "61%" - ], - [ - "p95 latency", - "240ms" - ] - ] - } - } - } - ] - }, - { - "type": "Card", - "props": { - "withBorder": true, - "padding": "md" - }, - "children": [ - { - "type": "Group", - "props": { - "justify": "space-between" - }, - "children": [ - { - "type": "Text", - "props": { - "fw": 700 - }, - "children": "After" - }, - { - "type": "Badge", - "props": { - "color": "green" - }, - "children": "pr#214" - } - ] - }, - { - "type": "Table", - "props": { - "mt": "sm", - "data": { - "head": [ - "Metric", - "Value", - "\u0394" - ], - "body": [ - [ - "LOC", - "286", - "-126" - ], - [ - "Cyclomatic", - "18", - "-19" - ], - [ - "Test cov.", - "89%", - "+28%" - ], - [ - "p95 latency", - "150ms", - "-90ms" - ] - ] - } - } - } - ] - } - ] - } - ] -} +{"type":"Stack","props":{"gap":"md","p":"md"},"children":[{"type":"Title","props":{"order":3},"children":"Refactor: auth module"},{"type":"SimpleGrid","props":{"cols":2,"spacing":"lg"},"children":[{"type":"Card","props":{"withBorder":true,"padding":"md"},"children":[{"type":"Group","props":{"justify":"space-between"},"children":[{"type":"Text","props":{"fw":700},"children":"Before"},{"type":"Badge","props":{"color":"gray"},"children":"main@a1b2c3"}]},{"type":"Table","props":{"mt":"sm","data":{"head":["Metric","Value"],"body":[["LOC","412"],["Cyclomatic","37"],["Test cov.","61%"],["p95 latency","240ms"]]}}}]},{"type":"Card","props":{"withBorder":true,"padding":"md"},"children":[{"type":"Group","props":{"justify":"space-between"},"children":[{"type":"Text","props":{"fw":700},"children":"After"},{"type":"Badge","props":{"color":"green"},"children":"pr#214"}]},{"type":"Table","props":{"mt":"sm","data":{"head":["Metric","Value","Δ"],"body":[["LOC","286","-126"],["Cyclomatic","18","-19"],["Test cov.","89%","+28%"],["p95 latency","150ms","-90ms"]]}}}]}]}]} diff --git a/plugin/skills/diagram-recipes/examples/bfs-traversal.flow.json b/plugin/skills/diagram-recipes/examples/bfs-traversal.flow.json index 4046b7f..60af3ce 100644 --- a/plugin/skills/diagram-recipes/examples/bfs-traversal.flow.json +++ b/plugin/skills/diagram-recipes/examples/bfs-traversal.flow.json @@ -1,25 +1 @@ -{ - "direction": "TB", - "legend": [ - { "label": "start", "color": "#60a5fa" }, - { "label": "frontier (queued)", "color": "#fbbf24" }, - { "label": "visited", "color": "#22c55e" }, - { "label": "undiscovered", "color": "#52525b" } - ], - "nodes": [ - { "id": "S", "type": "change", "data": { "label": "S", "status": "info", "sub": "start", "meta": "0" } }, - { "id": "A", "type": "change", "data": { "label": "A", "status": "success", "meta": "1" } }, - { "id": "B", "type": "change", "data": { "label": "B", "status": "active", "sub": "queued", "meta": "1" } }, - { "id": "C", "type": "change", "data": { "label": "C", "status": "active", "sub": "queued", "meta": "2" } }, - { "id": "D", "type": "change", "data": { "label": "D", "status": "active", "sub": "queued", "meta": "2" } }, - { "id": "E", "type": "change", "data": { "label": "E", "status": "neutral" } } - ], - "edges": [ - { "source": "S", "target": "A", "markerEnd": { "type": "arrowclosed" } }, - { "source": "S", "target": "B", "markerEnd": { "type": "arrowclosed" } }, - { "source": "A", "target": "C", "markerEnd": { "type": "arrowclosed" } }, - { "source": "A", "target": "D", "markerEnd": { "type": "arrowclosed" } }, - { "source": "B", "target": "D", "markerEnd": { "type": "arrowclosed" } }, - { "source": "B", "target": "E", "markerEnd": { "type": "arrowclosed" } } - ] -} +{"direction":"TB","legend":[{"label":"start","color":"#60a5fa"},{"label":"frontier (queued)","color":"#fbbf24"},{"label":"visited","color":"#22c55e"},{"label":"undiscovered","color":"#52525b"}],"nodes":[{"id":"S","type":"change","data":{"label":"S","status":"info","sub":"start","meta":"0"}},{"id":"A","type":"change","data":{"label":"A","status":"success","meta":"1"}},{"id":"B","type":"change","data":{"label":"B","status":"active","sub":"queued","meta":"1"}},{"id":"C","type":"change","data":{"label":"C","status":"active","sub":"queued","meta":"2"}},{"id":"D","type":"change","data":{"label":"D","status":"active","sub":"queued","meta":"2"}},{"id":"E","type":"change","data":{"label":"E","status":"neutral"}}],"edges":[{"source":"S","target":"A","markerEnd":{"type":"arrowclosed"}},{"source":"S","target":"B","markerEnd":{"type":"arrowclosed"}},{"source":"A","target":"C","markerEnd":{"type":"arrowclosed"}},{"source":"A","target":"D","markerEnd":{"type":"arrowclosed"}},{"source":"B","target":"D","markerEnd":{"type":"arrowclosed"}},{"source":"B","target":"E","markerEnd":{"type":"arrowclosed"}}]} diff --git a/plugin/skills/diagram-recipes/examples/binary-search.flow.json b/plugin/skills/diagram-recipes/examples/binary-search.flow.json index b4087ae..f6281a7 100644 --- a/plugin/skills/diagram-recipes/examples/binary-search.flow.json +++ b/plugin/skills/diagram-recipes/examples/binary-search.flow.json @@ -1,35 +1 @@ -{ - "direction": "LR", - "height": 150, - "legend": [ - { "label": "in window", "color": "#fbbf24" }, - { "label": "mid (compare)", "color": "#60a5fa" }, - { "label": "eliminated", "color": "#52525b" }, - { "label": "found", "color": "#22c55e" } - ], - "nodes": [ - { "id": "c0", "type": "change", "data": { "label": "3", "status": "neutral", "meta": "0" } }, - { "id": "c1", "type": "change", "data": { "label": "8", "status": "neutral", "meta": "1" } }, - { "id": "c2", "type": "change", "data": { "label": "15", "status": "active", "meta": "2" } }, - { "id": "c3", "type": "change", "data": { "label": "23", "status": "active", "meta": "3" } }, - { "id": "c4", "type": "change", "data": { "label": "42", "status": "active", "meta": "4", "sub": "target" } }, - { "id": "c5", "type": "change", "data": { "label": "56", "status": "info", "meta": "5", "sub": "mid" } }, - { "id": "c6", "type": "change", "data": { "label": "61", "status": "neutral", "meta": "6" } }, - { "id": "c7", "type": "change", "data": { "label": "70", "status": "neutral", "meta": "7" } }, - { "id": "c8", "type": "change", "data": { "label": "84", "status": "neutral", "meta": "8" } }, - { "id": "c9", "type": "change", "data": { "label": "91", "status": "neutral", "meta": "9" } }, - { "id": "c10", "type": "change", "data": { "label": "99", "status": "neutral", "meta": "10" } } - ], - "edges": [ - { "source": "c0", "target": "c1", "style": { "stroke": "#52525b" } }, - { "source": "c1", "target": "c2", "style": { "stroke": "#52525b" } }, - { "source": "c2", "target": "c3", "style": { "stroke": "#52525b" } }, - { "source": "c3", "target": "c4", "style": { "stroke": "#52525b" } }, - { "source": "c4", "target": "c5", "style": { "stroke": "#52525b" } }, - { "source": "c5", "target": "c6", "style": { "stroke": "#52525b" } }, - { "source": "c6", "target": "c7", "style": { "stroke": "#52525b" } }, - { "source": "c7", "target": "c8", "style": { "stroke": "#52525b" } }, - { "source": "c8", "target": "c9", "style": { "stroke": "#52525b" } }, - { "source": "c9", "target": "c10", "style": { "stroke": "#52525b" } } - ] -} +{"direction":"LR","height":150,"legend":[{"label":"in window","color":"#fbbf24"},{"label":"mid (compare)","color":"#60a5fa"},{"label":"eliminated","color":"#52525b"},{"label":"found","color":"#22c55e"}],"nodes":[{"id":"c0","type":"change","data":{"label":"3","status":"neutral","meta":"0"}},{"id":"c1","type":"change","data":{"label":"8","status":"neutral","meta":"1"}},{"id":"c2","type":"change","data":{"label":"15","status":"active","meta":"2"}},{"id":"c3","type":"change","data":{"label":"23","status":"active","meta":"3"}},{"id":"c4","type":"change","data":{"label":"42","status":"active","meta":"4","sub":"target"}},{"id":"c5","type":"change","data":{"label":"56","status":"info","meta":"5","sub":"mid"}},{"id":"c6","type":"change","data":{"label":"61","status":"neutral","meta":"6"}},{"id":"c7","type":"change","data":{"label":"70","status":"neutral","meta":"7"}},{"id":"c8","type":"change","data":{"label":"84","status":"neutral","meta":"8"}},{"id":"c9","type":"change","data":{"label":"91","status":"neutral","meta":"9"}},{"id":"c10","type":"change","data":{"label":"99","status":"neutral","meta":"10"}}],"edges":[{"source":"c0","target":"c1","style":{"stroke":"#52525b"}},{"source":"c1","target":"c2","style":{"stroke":"#52525b"}},{"source":"c2","target":"c3","style":{"stroke":"#52525b"}},{"source":"c3","target":"c4","style":{"stroke":"#52525b"}},{"source":"c4","target":"c5","style":{"stroke":"#52525b"}},{"source":"c5","target":"c6","style":{"stroke":"#52525b"}},{"source":"c6","target":"c7","style":{"stroke":"#52525b"}},{"source":"c7","target":"c8","style":{"stroke":"#52525b"}},{"source":"c8","target":"c9","style":{"stroke":"#52525b"}},{"source":"c9","target":"c10","style":{"stroke":"#52525b"}}]} diff --git a/plugin/skills/diagram-recipes/examples/c4-architecture.panes.json b/plugin/skills/diagram-recipes/examples/c4-architecture.panes.json index e5a2991..d04ced2 100644 --- a/plugin/skills/diagram-recipes/examples/c4-architecture.panes.json +++ b/plugin/skills/diagram-recipes/examples/c4-architecture.panes.json @@ -1,25 +1 @@ -{ - "layout": "rows", - "panes": [ - { - "title": "C4 model — three levels of zoom", - "type": "markdown", - "content": "## C4 — zoom levels for architecture\n\nDon't cram a whole system into one diagram. The **C4 model** draws it at increasing zoom, each\na separate view — stop at the level your audience needs:\n\n- **① Context** — the system as a black box: who uses it, what it talks to.\n- **② Container** — open the box: the apps, services, and data stores inside.\n- **③ Component** — open one container: its major building blocks.\n\nBelow: an internet-banking system, zoomed three times." - }, - { - "title": "① System Context — the black box", - "type": "flow", - "content": "{\"direction\":\"TB\",\"legend\":[{\"label\":\"person\",\"color\":\"#60a5fa\"},{\"label\":\"this system\",\"color\":\"#fbbf24\"},{\"label\":\"external/within\",\"color\":\"#52525b\"}],\"nodes\":[{\"id\":\"cust\",\"type\":\"change\",\"data\":{\"label\":\"Personal Banking Customer\",\"kind\":\"person\",\"status\":\"info\",\"icon\":\"user\",\"sub\":\"views accounts, makes payments\"}},{\"id\":\"ibs\",\"type\":\"change\",\"data\":{\"label\":\"Internet Banking System\",\"kind\":\"software system\",\"status\":\"active\",\"icon\":\"code\",\"sub\":\"the system in focus\"}},{\"id\":\"mf\",\"type\":\"change\",\"data\":{\"label\":\"Mainframe Banking System\",\"kind\":\"external system\",\"status\":\"neutral\",\"sub\":\"core accounts\"}},{\"id\":\"mail\",\"type\":\"change\",\"data\":{\"label\":\"Email System\",\"kind\":\"external system\",\"status\":\"neutral\",\"sub\":\"e.g. SendGrid\"}}],\"edges\":[{\"id\":\"c1\",\"source\":\"cust\",\"target\":\"ibs\",\"label\":\"uses\",\"markerEnd\":{\"type\":\"arrowclosed\",\"color\":\"#60a5fa\"},\"style\":{\"stroke\":\"#60a5fa\"}},{\"id\":\"c2\",\"source\":\"ibs\",\"target\":\"mf\",\"label\":\"reads/writes accounts\",\"markerEnd\":{\"type\":\"arrowclosed\",\"color\":\"#52525b\"},\"style\":{\"stroke\":\"#52525b\"}},{\"id\":\"c3\",\"source\":\"ibs\",\"target\":\"mail\",\"label\":\"sends mail via\",\"markerEnd\":{\"type\":\"arrowclosed\",\"color\":\"#52525b\"},\"style\":{\"stroke\":\"#52525b\"}}]}" - }, - { - "title": "② Containers — inside the system", - "type": "flow", - "content": "{\"direction\":\"TB\",\"legend\":[{\"label\":\"person\",\"color\":\"#60a5fa\"},{\"label\":\"this system\",\"color\":\"#fbbf24\"},{\"label\":\"external/within\",\"color\":\"#52525b\"}],\"nodes\":[{\"id\":\"cust\",\"type\":\"change\",\"data\":{\"label\":\"Customer\",\"kind\":\"person\",\"status\":\"info\",\"icon\":\"user\"}},{\"id\":\"web\",\"type\":\"change\",\"data\":{\"label\":\"Web App\",\"kind\":\"container\",\"status\":\"active\",\"icon\":\"code\",\"sub\":\"Java / Spring MVC · serves the SPA\"}},{\"id\":\"spa\",\"type\":\"change\",\"data\":{\"label\":\"Single-Page App\",\"kind\":\"container\",\"status\":\"active\",\"icon\":\"code\",\"sub\":\"React · runs in the browser\"}},{\"id\":\"api\",\"type\":\"change\",\"data\":{\"label\":\"API Application\",\"kind\":\"container\",\"status\":\"active\",\"icon\":\"bolt\",\"sub\":\"Java / Spring · the business logic\"}},{\"id\":\"db\",\"type\":\"change\",\"data\":{\"label\":\"Database\",\"kind\":\"container\",\"status\":\"info\",\"icon\":\"db\",\"sub\":\"accounts, audit log\"}},{\"id\":\"mf\",\"type\":\"change\",\"data\":{\"label\":\"Mainframe Banking System\",\"kind\":\"external\",\"status\":\"neutral\"}},{\"id\":\"mail\",\"type\":\"change\",\"data\":{\"label\":\"Email System\",\"kind\":\"external\",\"status\":\"neutral\"}}],\"edges\":[{\"id\":\"k1\",\"source\":\"cust\",\"target\":\"web\",\"label\":\"uses (HTTPS)\",\"markerEnd\":{\"type\":\"arrowclosed\",\"color\":\"#60a5fa\"},\"style\":{\"stroke\":\"#60a5fa\"}},{\"id\":\"k2\",\"source\":\"web\",\"target\":\"spa\",\"label\":\"delivers\",\"markerEnd\":{\"type\":\"arrowclosed\",\"color\":\"#fbbf24\"},\"style\":{\"stroke\":\"#fbbf24\"}},{\"id\":\"k3\",\"source\":\"spa\",\"target\":\"api\",\"label\":\"calls (JSON/HTTPS)\",\"markerEnd\":{\"type\":\"arrowclosed\",\"color\":\"#fbbf24\"},\"style\":{\"stroke\":\"#fbbf24\"}},{\"id\":\"k4\",\"source\":\"api\",\"target\":\"db\",\"label\":\"reads/writes (JDBC)\",\"markerEnd\":{\"type\":\"arrowclosed\",\"color\":\"#fbbf24\"},\"style\":{\"stroke\":\"#fbbf24\"}},{\"id\":\"k5\",\"source\":\"api\",\"target\":\"mf\",\"label\":\"uses (XML/HTTPS)\",\"markerEnd\":{\"type\":\"arrowclosed\",\"color\":\"#52525b\"},\"style\":{\"stroke\":\"#52525b\"}},{\"id\":\"k6\",\"source\":\"api\",\"target\":\"mail\",\"label\":\"sends via (SMTP)\",\"markerEnd\":{\"type\":\"arrowclosed\",\"color\":\"#52525b\"},\"style\":{\"stroke\":\"#52525b\"}}]}" - }, - { - "title": "③ Components — inside the API Application", - "type": "flow", - "content": "{\"direction\":\"TB\",\"legend\":[{\"label\":\"caller\",\"color\":\"#60a5fa\"},{\"label\":\"component\",\"color\":\"#fbbf24\"},{\"label\":\"external/within\",\"color\":\"#52525b\"}],\"nodes\":[{\"id\":\"spa\",\"type\":\"change\",\"data\":{\"label\":\"Single-Page App\",\"kind\":\"container\",\"status\":\"info\",\"icon\":\"code\",\"sub\":\"calls the API\"}},{\"id\":\"signin\",\"type\":\"change\",\"data\":{\"label\":\"Sign In Controller\",\"kind\":\"component\",\"status\":\"active\",\"sub\":\"REST · /auth\"}},{\"id\":\"acct\",\"type\":\"change\",\"data\":{\"label\":\"Accounts Summary Controller\",\"kind\":\"component\",\"status\":\"active\",\"sub\":\"REST · /accounts\"}},{\"id\":\"sec\",\"type\":\"change\",\"data\":{\"label\":\"Security Component\",\"kind\":\"component\",\"status\":\"active\",\"sub\":\"hashes & checks credentials\"}},{\"id\":\"facade\",\"type\":\"change\",\"data\":{\"label\":\"Mainframe Facade\",\"kind\":\"component\",\"status\":\"active\",\"sub\":\"wraps the mainframe API\"}},{\"id\":\"db\",\"type\":\"change\",\"data\":{\"label\":\"Database\",\"kind\":\"external\",\"status\":\"neutral\",\"icon\":\"db\"}},{\"id\":\"mf\",\"type\":\"change\",\"data\":{\"label\":\"Mainframe Banking System\",\"kind\":\"external\",\"status\":\"neutral\"}}],\"edges\":[{\"id\":\"m1\",\"source\":\"spa\",\"target\":\"signin\",\"label\":\"POST /auth\",\"markerEnd\":{\"type\":\"arrowclosed\",\"color\":\"#60a5fa\"},\"style\":{\"stroke\":\"#60a5fa\"}},{\"id\":\"m2\",\"source\":\"spa\",\"target\":\"acct\",\"label\":\"GET /accounts\",\"markerEnd\":{\"type\":\"arrowclosed\",\"color\":\"#60a5fa\"},\"style\":{\"stroke\":\"#60a5fa\"}},{\"id\":\"m3\",\"source\":\"signin\",\"target\":\"sec\",\"label\":\"uses\",\"markerEnd\":{\"type\":\"arrowclosed\",\"color\":\"#fbbf24\"},\"style\":{\"stroke\":\"#fbbf24\"}},{\"id\":\"m4\",\"source\":\"sec\",\"target\":\"db\",\"label\":\"reads creds\",\"markerEnd\":{\"type\":\"arrowclosed\",\"color\":\"#52525b\"},\"style\":{\"stroke\":\"#52525b\"}},{\"id\":\"m5\",\"source\":\"acct\",\"target\":\"facade\",\"label\":\"uses\",\"markerEnd\":{\"type\":\"arrowclosed\",\"color\":\"#fbbf24\"},\"style\":{\"stroke\":\"#fbbf24\"}},{\"id\":\"m6\",\"source\":\"facade\",\"target\":\"mf\",\"label\":\"calls\",\"markerEnd\":{\"type\":\"arrowclosed\",\"color\":\"#52525b\"},\"style\":{\"stroke\":\"#52525b\"}}]}" - } - ] -} +{"layout":"rows","panes":[{"title":"C4 model — three levels of zoom","type":"markdown","content":"## C4 — zoom levels for architecture\n\nDon't cram a whole system into one diagram. The **C4 model** draws it at increasing zoom, each\na separate view — stop at the level your audience needs:\n\n- **① Context** — the system as a black box: who uses it, what it talks to.\n- **② Container** — open the box: the apps, services, and data stores inside.\n- **③ Component** — open one container: its major building blocks.\n\nBelow: an internet-banking system, zoomed three times."},{"title":"① System Context — the black box","type":"flow","content":"{\"direction\":\"TB\",\"legend\":[{\"label\":\"person\",\"color\":\"#60a5fa\"},{\"label\":\"this system\",\"color\":\"#fbbf24\"},{\"label\":\"external/within\",\"color\":\"#52525b\"}],\"nodes\":[{\"id\":\"cust\",\"type\":\"change\",\"data\":{\"label\":\"Personal Banking Customer\",\"kind\":\"person\",\"status\":\"info\",\"icon\":\"user\",\"sub\":\"views accounts, makes payments\"}},{\"id\":\"ibs\",\"type\":\"change\",\"data\":{\"label\":\"Internet Banking System\",\"kind\":\"software system\",\"status\":\"active\",\"icon\":\"code\",\"sub\":\"the system in focus\"}},{\"id\":\"mf\",\"type\":\"change\",\"data\":{\"label\":\"Mainframe Banking System\",\"kind\":\"external system\",\"status\":\"neutral\",\"sub\":\"core accounts\"}},{\"id\":\"mail\",\"type\":\"change\",\"data\":{\"label\":\"Email System\",\"kind\":\"external system\",\"status\":\"neutral\",\"sub\":\"e.g. SendGrid\"}}],\"edges\":[{\"id\":\"c1\",\"source\":\"cust\",\"target\":\"ibs\",\"label\":\"uses\",\"markerEnd\":{\"type\":\"arrowclosed\",\"color\":\"#60a5fa\"},\"style\":{\"stroke\":\"#60a5fa\"}},{\"id\":\"c2\",\"source\":\"ibs\",\"target\":\"mf\",\"label\":\"reads/writes accounts\",\"markerEnd\":{\"type\":\"arrowclosed\",\"color\":\"#52525b\"},\"style\":{\"stroke\":\"#52525b\"}},{\"id\":\"c3\",\"source\":\"ibs\",\"target\":\"mail\",\"label\":\"sends mail via\",\"markerEnd\":{\"type\":\"arrowclosed\",\"color\":\"#52525b\"},\"style\":{\"stroke\":\"#52525b\"}}]}"},{"title":"② Containers — inside the system","type":"flow","content":"{\"direction\":\"TB\",\"legend\":[{\"label\":\"person\",\"color\":\"#60a5fa\"},{\"label\":\"this system\",\"color\":\"#fbbf24\"},{\"label\":\"external/within\",\"color\":\"#52525b\"}],\"nodes\":[{\"id\":\"cust\",\"type\":\"change\",\"data\":{\"label\":\"Customer\",\"kind\":\"person\",\"status\":\"info\",\"icon\":\"user\"}},{\"id\":\"web\",\"type\":\"change\",\"data\":{\"label\":\"Web App\",\"kind\":\"container\",\"status\":\"active\",\"icon\":\"code\",\"sub\":\"Java / Spring MVC · serves the SPA\"}},{\"id\":\"spa\",\"type\":\"change\",\"data\":{\"label\":\"Single-Page App\",\"kind\":\"container\",\"status\":\"active\",\"icon\":\"code\",\"sub\":\"React · runs in the browser\"}},{\"id\":\"api\",\"type\":\"change\",\"data\":{\"label\":\"API Application\",\"kind\":\"container\",\"status\":\"active\",\"icon\":\"bolt\",\"sub\":\"Java / Spring · the business logic\"}},{\"id\":\"db\",\"type\":\"change\",\"data\":{\"label\":\"Database\",\"kind\":\"container\",\"status\":\"info\",\"icon\":\"db\",\"sub\":\"accounts, audit log\"}},{\"id\":\"mf\",\"type\":\"change\",\"data\":{\"label\":\"Mainframe Banking System\",\"kind\":\"external\",\"status\":\"neutral\"}},{\"id\":\"mail\",\"type\":\"change\",\"data\":{\"label\":\"Email System\",\"kind\":\"external\",\"status\":\"neutral\"}}],\"edges\":[{\"id\":\"k1\",\"source\":\"cust\",\"target\":\"web\",\"label\":\"uses (HTTPS)\",\"markerEnd\":{\"type\":\"arrowclosed\",\"color\":\"#60a5fa\"},\"style\":{\"stroke\":\"#60a5fa\"}},{\"id\":\"k2\",\"source\":\"web\",\"target\":\"spa\",\"label\":\"delivers\",\"markerEnd\":{\"type\":\"arrowclosed\",\"color\":\"#fbbf24\"},\"style\":{\"stroke\":\"#fbbf24\"}},{\"id\":\"k3\",\"source\":\"spa\",\"target\":\"api\",\"label\":\"calls (JSON/HTTPS)\",\"markerEnd\":{\"type\":\"arrowclosed\",\"color\":\"#fbbf24\"},\"style\":{\"stroke\":\"#fbbf24\"}},{\"id\":\"k4\",\"source\":\"api\",\"target\":\"db\",\"label\":\"reads/writes (JDBC)\",\"markerEnd\":{\"type\":\"arrowclosed\",\"color\":\"#fbbf24\"},\"style\":{\"stroke\":\"#fbbf24\"}},{\"id\":\"k5\",\"source\":\"api\",\"target\":\"mf\",\"label\":\"uses (XML/HTTPS)\",\"markerEnd\":{\"type\":\"arrowclosed\",\"color\":\"#52525b\"},\"style\":{\"stroke\":\"#52525b\"}},{\"id\":\"k6\",\"source\":\"api\",\"target\":\"mail\",\"label\":\"sends via (SMTP)\",\"markerEnd\":{\"type\":\"arrowclosed\",\"color\":\"#52525b\"},\"style\":{\"stroke\":\"#52525b\"}}]}"},{"title":"③ Components — inside the API Application","type":"flow","content":"{\"direction\":\"TB\",\"legend\":[{\"label\":\"caller\",\"color\":\"#60a5fa\"},{\"label\":\"component\",\"color\":\"#fbbf24\"},{\"label\":\"external/within\",\"color\":\"#52525b\"}],\"nodes\":[{\"id\":\"spa\",\"type\":\"change\",\"data\":{\"label\":\"Single-Page App\",\"kind\":\"container\",\"status\":\"info\",\"icon\":\"code\",\"sub\":\"calls the API\"}},{\"id\":\"signin\",\"type\":\"change\",\"data\":{\"label\":\"Sign In Controller\",\"kind\":\"component\",\"status\":\"active\",\"sub\":\"REST · /auth\"}},{\"id\":\"acct\",\"type\":\"change\",\"data\":{\"label\":\"Accounts Summary Controller\",\"kind\":\"component\",\"status\":\"active\",\"sub\":\"REST · /accounts\"}},{\"id\":\"sec\",\"type\":\"change\",\"data\":{\"label\":\"Security Component\",\"kind\":\"component\",\"status\":\"active\",\"sub\":\"hashes & checks credentials\"}},{\"id\":\"facade\",\"type\":\"change\",\"data\":{\"label\":\"Mainframe Facade\",\"kind\":\"component\",\"status\":\"active\",\"sub\":\"wraps the mainframe API\"}},{\"id\":\"db\",\"type\":\"change\",\"data\":{\"label\":\"Database\",\"kind\":\"external\",\"status\":\"neutral\",\"icon\":\"db\"}},{\"id\":\"mf\",\"type\":\"change\",\"data\":{\"label\":\"Mainframe Banking System\",\"kind\":\"external\",\"status\":\"neutral\"}}],\"edges\":[{\"id\":\"m1\",\"source\":\"spa\",\"target\":\"signin\",\"label\":\"POST /auth\",\"markerEnd\":{\"type\":\"arrowclosed\",\"color\":\"#60a5fa\"},\"style\":{\"stroke\":\"#60a5fa\"}},{\"id\":\"m2\",\"source\":\"spa\",\"target\":\"acct\",\"label\":\"GET /accounts\",\"markerEnd\":{\"type\":\"arrowclosed\",\"color\":\"#60a5fa\"},\"style\":{\"stroke\":\"#60a5fa\"}},{\"id\":\"m3\",\"source\":\"signin\",\"target\":\"sec\",\"label\":\"uses\",\"markerEnd\":{\"type\":\"arrowclosed\",\"color\":\"#fbbf24\"},\"style\":{\"stroke\":\"#fbbf24\"}},{\"id\":\"m4\",\"source\":\"sec\",\"target\":\"db\",\"label\":\"reads creds\",\"markerEnd\":{\"type\":\"arrowclosed\",\"color\":\"#52525b\"},\"style\":{\"stroke\":\"#52525b\"}},{\"id\":\"m5\",\"source\":\"acct\",\"target\":\"facade\",\"label\":\"uses\",\"markerEnd\":{\"type\":\"arrowclosed\",\"color\":\"#fbbf24\"},\"style\":{\"stroke\":\"#fbbf24\"}},{\"id\":\"m6\",\"source\":\"facade\",\"target\":\"mf\",\"label\":\"calls\",\"markerEnd\":{\"type\":\"arrowclosed\",\"color\":\"#52525b\"},\"style\":{\"stroke\":\"#52525b\"}}]}"}]} diff --git a/plugin/skills/diagram-recipes/examples/calendar-heatmap.vegalite.json b/plugin/skills/diagram-recipes/examples/calendar-heatmap.vegalite.json index dc6211f..223cb24 100644 --- a/plugin/skills/diagram-recipes/examples/calendar-heatmap.vegalite.json +++ b/plugin/skills/diagram-recipes/examples/calendar-heatmap.vegalite.json @@ -1,51 +1 @@ -{ - "$schema": "https://vega.github.io/schema/vega-lite/v6.json", - "title": { "text": "Activity — last 12 weeks", "subtitle": "daily commits, GitHub-style contribution graph", "anchor": "start" }, - "width": { "step": 18 }, - "height": { "step": 18 }, - "data": { - "values": [ - { "date": "2026-03-16", "week": 0, "dow": "Mon", "count": 3 }, { "date": "2026-03-17", "week": 0, "dow": "Tue", "count": 1 }, { "date": "2026-03-18", "week": 0, "dow": "Wed", "count": 0 }, { "date": "2026-03-19", "week": 0, "dow": "Thu", "count": 5 }, { "date": "2026-03-20", "week": 0, "dow": "Fri", "count": 2 }, { "date": "2026-03-21", "week": 0, "dow": "Sat", "count": 0 }, { "date": "2026-03-22", "week": 0, "dow": "Sun", "count": 0 }, - { "date": "2026-03-23", "week": 1, "dow": "Mon", "count": 6 }, { "date": "2026-03-24", "week": 1, "dow": "Tue", "count": 4 }, { "date": "2026-03-25", "week": 1, "dow": "Wed", "count": 8 }, { "date": "2026-03-26", "week": 1, "dow": "Thu", "count": 2 }, { "date": "2026-03-27", "week": 1, "dow": "Fri", "count": 1 }, { "date": "2026-03-28", "week": 1, "dow": "Sat", "count": 0 }, { "date": "2026-03-29", "week": 1, "dow": "Sun", "count": 1 }, - { "date": "2026-03-30", "week": 2, "dow": "Mon", "count": 2 }, { "date": "2026-03-31", "week": 2, "dow": "Tue", "count": 0 }, { "date": "2026-04-01", "week": 2, "dow": "Wed", "count": 3 }, { "date": "2026-04-02", "week": 2, "dow": "Thu", "count": 7 }, { "date": "2026-04-03", "week": 2, "dow": "Fri", "count": 4 }, { "date": "2026-04-04", "week": 2, "dow": "Sat", "count": 0 }, { "date": "2026-04-05", "week": 2, "dow": "Sun", "count": 0 }, - { "date": "2026-04-06", "week": 3, "dow": "Mon", "count": 9 }, { "date": "2026-04-07", "week": 3, "dow": "Tue", "count": 11 }, { "date": "2026-04-08", "week": 3, "dow": "Wed", "count": 6 }, { "date": "2026-04-09", "week": 3, "dow": "Thu", "count": 3 }, { "date": "2026-04-10", "week": 3, "dow": "Fri", "count": 5 }, { "date": "2026-04-11", "week": 3, "dow": "Sat", "count": 1 }, { "date": "2026-04-12", "week": 3, "dow": "Sun", "count": 0 }, - { "date": "2026-04-13", "week": 4, "dow": "Mon", "count": 1 }, { "date": "2026-04-14", "week": 4, "dow": "Tue", "count": 0 }, { "date": "2026-04-15", "week": 4, "dow": "Wed", "count": 0 }, { "date": "2026-04-16", "week": 4, "dow": "Thu", "count": 2 }, { "date": "2026-04-17", "week": 4, "dow": "Fri", "count": 4 }, { "date": "2026-04-18", "week": 4, "dow": "Sat", "count": 0 }, { "date": "2026-04-19", "week": 4, "dow": "Sun", "count": 3 }, - { "date": "2026-04-20", "week": 5, "dow": "Mon", "count": 7 }, { "date": "2026-04-21", "week": 5, "dow": "Tue", "count": 5 }, { "date": "2026-04-22", "week": 5, "dow": "Wed", "count": 2 }, { "date": "2026-04-23", "week": 5, "dow": "Thu", "count": 0 }, { "date": "2026-04-24", "week": 5, "dow": "Fri", "count": 6 }, { "date": "2026-04-25", "week": 5, "dow": "Sat", "count": 1 }, { "date": "2026-04-26", "week": 5, "dow": "Sun", "count": 0 }, - { "date": "2026-04-27", "week": 6, "dow": "Mon", "count": 4 }, { "date": "2026-04-28", "week": 6, "dow": "Tue", "count": 8 }, { "date": "2026-04-29", "week": 6, "dow": "Wed", "count": 12 }, { "date": "2026-04-30", "week": 6, "dow": "Thu", "count": 9 }, { "date": "2026-05-01", "week": 6, "dow": "Fri", "count": 3 }, { "date": "2026-05-02", "week": 6, "dow": "Sat", "count": 0 }, { "date": "2026-05-03", "week": 6, "dow": "Sun", "count": 2 }, - { "date": "2026-05-04", "week": 7, "dow": "Mon", "count": 5 }, { "date": "2026-05-05", "week": 7, "dow": "Tue", "count": 3 }, { "date": "2026-05-06", "week": 7, "dow": "Wed", "count": 1 }, { "date": "2026-05-07", "week": 7, "dow": "Thu", "count": 0 }, { "date": "2026-05-08", "week": 7, "dow": "Fri", "count": 2 }, { "date": "2026-05-09", "week": 7, "dow": "Sat", "count": 0 }, { "date": "2026-05-10", "week": 7, "dow": "Sun", "count": 0 }, - { "date": "2026-05-11", "week": 8, "dow": "Mon", "count": 10 }, { "date": "2026-05-12", "week": 8, "dow": "Tue", "count": 7 }, { "date": "2026-05-13", "week": 8, "dow": "Wed", "count": 4 }, { "date": "2026-05-14", "week": 8, "dow": "Thu", "count": 6 }, { "date": "2026-05-15", "week": 8, "dow": "Fri", "count": 8 }, { "date": "2026-05-16", "week": 8, "dow": "Sat", "count": 1 }, { "date": "2026-05-17", "week": 8, "dow": "Sun", "count": 0 }, - { "date": "2026-05-18", "week": 9, "dow": "Mon", "count": 2 }, { "date": "2026-05-19", "week": 9, "dow": "Tue", "count": 0 }, { "date": "2026-05-20", "week": 9, "dow": "Wed", "count": 3 }, { "date": "2026-05-21", "week": 9, "dow": "Thu", "count": 5 }, { "date": "2026-05-22", "week": 9, "dow": "Fri", "count": 1 }, { "date": "2026-05-23", "week": 9, "dow": "Sat", "count": 0 }, { "date": "2026-05-24", "week": 9, "dow": "Sun", "count": 0 }, - { "date": "2026-05-25", "week": 10, "dow": "Mon", "count": 6 }, { "date": "2026-05-26", "week": 10, "dow": "Tue", "count": 13 }, { "date": "2026-05-27", "week": 10, "dow": "Wed", "count": 9 }, { "date": "2026-05-28", "week": 10, "dow": "Thu", "count": 4 }, { "date": "2026-05-29", "week": 10, "dow": "Fri", "count": 2 }, { "date": "2026-05-30", "week": 10, "dow": "Sat", "count": 0 }, { "date": "2026-05-31", "week": 10, "dow": "Sun", "count": 1 }, - { "date": "2026-06-01", "week": 11, "dow": "Mon", "count": 3 }, { "date": "2026-06-02", "week": 11, "dow": "Tue", "count": 5 }, { "date": "2026-06-03", "week": 11, "dow": "Wed", "count": 7 }, { "date": "2026-06-04", "week": 11, "dow": "Thu", "count": 4 }, { "date": "2026-06-05", "week": 11, "dow": "Fri", "count": 2 }, { "date": "2026-06-06", "week": 11, "dow": "Sat", "count": 0 }, { "date": "2026-06-07", "week": 11, "dow": "Sun", "count": 0 } - ] - }, - "mark": { "type": "rect", "cornerRadius": 2, "width": { "band": 0.88 }, "height": { "band": 0.88 } }, - "encoding": { - "x": { - "field": "week", - "type": "ordinal", - "title": null, - "axis": { "labelExpr": "datum.value % 4 === 0 ? 'wk ' + datum.value : ''", "ticks": false, "domain": false, "labelAngle": 0 } - }, - "y": { - "field": "dow", - "type": "ordinal", - "sort": ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"], - "title": null, - "axis": { "labelExpr": "indexof(['Mon','Wed','Fri'], datum.value) >= 0 ? datum.value : ''", "ticks": false, "domain": false } - }, - "color": { - "field": "count", - "type": "quantitative", - "title": "commits", - "scale": { "scheme": "greens", "domainMin": 0 }, - "legend": { "orient": "top", "gradientLength": 120 } - }, - "tooltip": [ - { "field": "date", "type": "temporal", "title": "date", "format": "%a %b %d, %Y" }, - { "field": "week", "type": "ordinal", "title": "week #" }, - { "field": "count", "type": "quantitative", "title": "commits" } - ] - }, - "config": { "view": { "stroke": null }, "axis": { "grid": false } } -} +{"$schema":"https://vega.github.io/schema/vega-lite/v6.json","title":{"text":"Activity — last 12 weeks","subtitle":"daily commits, GitHub-style contribution graph","anchor":"start"},"width":{"step":18},"height":{"step":18},"data":{"values":[{"date":"2026-03-16","week":0,"dow":"Mon","count":3},{"date":"2026-03-17","week":0,"dow":"Tue","count":1},{"date":"2026-03-18","week":0,"dow":"Wed","count":0},{"date":"2026-03-19","week":0,"dow":"Thu","count":5},{"date":"2026-03-20","week":0,"dow":"Fri","count":2},{"date":"2026-03-21","week":0,"dow":"Sat","count":0},{"date":"2026-03-22","week":0,"dow":"Sun","count":0},{"date":"2026-03-23","week":1,"dow":"Mon","count":6},{"date":"2026-03-24","week":1,"dow":"Tue","count":4},{"date":"2026-03-25","week":1,"dow":"Wed","count":8},{"date":"2026-03-26","week":1,"dow":"Thu","count":2},{"date":"2026-03-27","week":1,"dow":"Fri","count":1},{"date":"2026-03-28","week":1,"dow":"Sat","count":0},{"date":"2026-03-29","week":1,"dow":"Sun","count":1},{"date":"2026-03-30","week":2,"dow":"Mon","count":2},{"date":"2026-03-31","week":2,"dow":"Tue","count":0},{"date":"2026-04-01","week":2,"dow":"Wed","count":3},{"date":"2026-04-02","week":2,"dow":"Thu","count":7},{"date":"2026-04-03","week":2,"dow":"Fri","count":4},{"date":"2026-04-04","week":2,"dow":"Sat","count":0},{"date":"2026-04-05","week":2,"dow":"Sun","count":0},{"date":"2026-04-06","week":3,"dow":"Mon","count":9},{"date":"2026-04-07","week":3,"dow":"Tue","count":11},{"date":"2026-04-08","week":3,"dow":"Wed","count":6},{"date":"2026-04-09","week":3,"dow":"Thu","count":3},{"date":"2026-04-10","week":3,"dow":"Fri","count":5},{"date":"2026-04-11","week":3,"dow":"Sat","count":1},{"date":"2026-04-12","week":3,"dow":"Sun","count":0},{"date":"2026-04-13","week":4,"dow":"Mon","count":1},{"date":"2026-04-14","week":4,"dow":"Tue","count":0},{"date":"2026-04-15","week":4,"dow":"Wed","count":0},{"date":"2026-04-16","week":4,"dow":"Thu","count":2},{"date":"2026-04-17","week":4,"dow":"Fri","count":4},{"date":"2026-04-18","week":4,"dow":"Sat","count":0},{"date":"2026-04-19","week":4,"dow":"Sun","count":3},{"date":"2026-04-20","week":5,"dow":"Mon","count":7},{"date":"2026-04-21","week":5,"dow":"Tue","count":5},{"date":"2026-04-22","week":5,"dow":"Wed","count":2},{"date":"2026-04-23","week":5,"dow":"Thu","count":0},{"date":"2026-04-24","week":5,"dow":"Fri","count":6},{"date":"2026-04-25","week":5,"dow":"Sat","count":1},{"date":"2026-04-26","week":5,"dow":"Sun","count":0},{"date":"2026-04-27","week":6,"dow":"Mon","count":4},{"date":"2026-04-28","week":6,"dow":"Tue","count":8},{"date":"2026-04-29","week":6,"dow":"Wed","count":12},{"date":"2026-04-30","week":6,"dow":"Thu","count":9},{"date":"2026-05-01","week":6,"dow":"Fri","count":3},{"date":"2026-05-02","week":6,"dow":"Sat","count":0},{"date":"2026-05-03","week":6,"dow":"Sun","count":2},{"date":"2026-05-04","week":7,"dow":"Mon","count":5},{"date":"2026-05-05","week":7,"dow":"Tue","count":3},{"date":"2026-05-06","week":7,"dow":"Wed","count":1},{"date":"2026-05-07","week":7,"dow":"Thu","count":0},{"date":"2026-05-08","week":7,"dow":"Fri","count":2},{"date":"2026-05-09","week":7,"dow":"Sat","count":0},{"date":"2026-05-10","week":7,"dow":"Sun","count":0},{"date":"2026-05-11","week":8,"dow":"Mon","count":10},{"date":"2026-05-12","week":8,"dow":"Tue","count":7},{"date":"2026-05-13","week":8,"dow":"Wed","count":4},{"date":"2026-05-14","week":8,"dow":"Thu","count":6},{"date":"2026-05-15","week":8,"dow":"Fri","count":8},{"date":"2026-05-16","week":8,"dow":"Sat","count":1},{"date":"2026-05-17","week":8,"dow":"Sun","count":0},{"date":"2026-05-18","week":9,"dow":"Mon","count":2},{"date":"2026-05-19","week":9,"dow":"Tue","count":0},{"date":"2026-05-20","week":9,"dow":"Wed","count":3},{"date":"2026-05-21","week":9,"dow":"Thu","count":5},{"date":"2026-05-22","week":9,"dow":"Fri","count":1},{"date":"2026-05-23","week":9,"dow":"Sat","count":0},{"date":"2026-05-24","week":9,"dow":"Sun","count":0},{"date":"2026-05-25","week":10,"dow":"Mon","count":6},{"date":"2026-05-26","week":10,"dow":"Tue","count":13},{"date":"2026-05-27","week":10,"dow":"Wed","count":9},{"date":"2026-05-28","week":10,"dow":"Thu","count":4},{"date":"2026-05-29","week":10,"dow":"Fri","count":2},{"date":"2026-05-30","week":10,"dow":"Sat","count":0},{"date":"2026-05-31","week":10,"dow":"Sun","count":1},{"date":"2026-06-01","week":11,"dow":"Mon","count":3},{"date":"2026-06-02","week":11,"dow":"Tue","count":5},{"date":"2026-06-03","week":11,"dow":"Wed","count":7},{"date":"2026-06-04","week":11,"dow":"Thu","count":4},{"date":"2026-06-05","week":11,"dow":"Fri","count":2},{"date":"2026-06-06","week":11,"dow":"Sat","count":0},{"date":"2026-06-07","week":11,"dow":"Sun","count":0}]},"mark":{"type":"rect","cornerRadius":2,"width":{"band":0.88},"height":{"band":0.88}},"encoding":{"x":{"field":"week","type":"ordinal","title":null,"axis":{"labelExpr":"datum.value % 4 === 0 ? 'wk ' + datum.value : ''","ticks":false,"domain":false,"labelAngle":0}},"y":{"field":"dow","type":"ordinal","sort":["Mon","Tue","Wed","Thu","Fri","Sat","Sun"],"title":null,"axis":{"labelExpr":"indexof(['Mon','Wed','Fri'], datum.value) >= 0 ? datum.value : ''","ticks":false,"domain":false}},"color":{"field":"count","type":"quantitative","title":"commits","scale":{"scheme":"greens","domainMin":0},"legend":{"orient":"top","gradientLength":120}},"tooltip":[{"field":"date","type":"temporal","title":"date","format":"%a %b %d, %Y"},{"field":"week","type":"ordinal","title":"week #"},{"field":"count","type":"quantitative","title":"commits"}]},"config":{"view":{"stroke":null},"axis":{"grid":false}}} diff --git a/plugin/skills/diagram-recipes/examples/call-hierarchy.flow.json b/plugin/skills/diagram-recipes/examples/call-hierarchy.flow.json index c798844..6b935c6 100644 --- a/plugin/skills/diagram-recipes/examples/call-hierarchy.flow.json +++ b/plugin/skills/diagram-recipes/examples/call-hierarchy.flow.json @@ -1,57 +1 @@ -{ - "direction": "TB", - "nodes": [ - { - "id": "handler", - "data": { - "label": "handleRequest()" - } - }, - { - "id": "auth", - "data": { - "label": "authenticate()" - } - }, - { - "id": "cache", - "data": { - "label": "checkCache()" - } - }, - { - "id": "db", - "data": { - "label": "queryDB()" - } - }, - { - "id": "render", - "data": { - "label": "renderResponse()" - } - } - ], - "edges": [ - { - "source": "handler", - "target": "auth" - }, - { - "source": "handler", - "target": "cache" - }, - { - "source": "handler", - "target": "db" - }, - { - "source": "db", - "target": "render" - }, - { - "source": "cache", - "target": "render" - } - ] -} +{"direction":"TB","nodes":[{"id":"handler","data":{"label":"handleRequest()"}},{"id":"auth","data":{"label":"authenticate()"}},{"id":"cache","data":{"label":"checkCache()"}},{"id":"db","data":{"label":"queryDB()"}},{"id":"render","data":{"label":"renderResponse()"}}],"edges":[{"source":"handler","target":"auth"},{"source":"handler","target":"cache"},{"source":"handler","target":"db"},{"source":"db","target":"render"},{"source":"cache","target":"render"}]} diff --git a/plugin/skills/diagram-recipes/examples/class-diagram.flow.json b/plugin/skills/diagram-recipes/examples/class-diagram.flow.json index b530b04..660decc 100644 --- a/plugin/skills/diagram-recipes/examples/class-diagram.flow.json +++ b/plugin/skills/diagram-recipes/examples/class-diagram.flow.json @@ -1,98 +1 @@ -{ - "direction": "BT", - "nodes": [ - { - "id": "shape", - "type": "entity", - "width": 200, - "height": 104, - "data": { - "label": "Shape \u00ababstract\u00bb", - "fields": [ - { - "name": "name", - "type": "str" - }, - { - "name": "area()", - "type": "num" - }, - { - "name": "draw()", - "type": "void" - } - ] - } - }, - { - "id": "circle", - "type": "entity", - "width": 200, - "height": 104, - "data": { - "label": "Circle", - "fields": [ - { - "name": "r", - "type": "num" - }, - { - "name": "area()", - "type": "num" - }, - { - "name": "draw()", - "type": "void" - } - ] - } - }, - { - "id": "rect", - "type": "entity", - "width": 200, - "height": 126, - "data": { - "label": "Rectangle", - "fields": [ - { - "name": "w", - "type": "num" - }, - { - "name": "h", - "type": "num" - }, - { - "name": "area()", - "type": "num" - }, - { - "name": "draw()", - "type": "void" - } - ] - } - } - ], - "edges": [ - { - "source": "circle", - "target": "shape", - "label": "extends", - "type": "smoothstep", - "markerEnd": { - "type": "arrowclosed" - } - }, - { - "source": "rect", - "target": "shape", - "label": "extends", - "type": "smoothstep", - "markerEnd": { - "type": "arrowclosed" - } - } - ] -} +{"direction":"BT","nodes":[{"id":"shape","type":"entity","width":200,"height":104,"data":{"label":"Shape «abstract»","fields":[{"name":"name","type":"str"},{"name":"area()","type":"num"},{"name":"draw()","type":"void"}]}},{"id":"circle","type":"entity","width":200,"height":104,"data":{"label":"Circle","fields":[{"name":"r","type":"num"},{"name":"area()","type":"num"},{"name":"draw()","type":"void"}]}},{"id":"rect","type":"entity","width":200,"height":126,"data":{"label":"Rectangle","fields":[{"name":"w","type":"num"},{"name":"h","type":"num"},{"name":"area()","type":"num"},{"name":"draw()","type":"void"}]}}],"edges":[{"source":"circle","target":"shape","label":"extends","type":"smoothstep","markerEnd":{"type":"arrowclosed"}},{"source":"rect","target":"shape","label":"extends","type":"smoothstep","markerEnd":{"type":"arrowclosed"}}]} diff --git a/plugin/skills/diagram-recipes/examples/complexity.vegalite.json b/plugin/skills/diagram-recipes/examples/complexity.vegalite.json index 9aa755a..e83ffec 100644 --- a/plugin/skills/diagram-recipes/examples/complexity.vegalite.json +++ b/plugin/skills/diagram-recipes/examples/complexity.vegalite.json @@ -1,22 +1 @@ -{ - "$schema": "https://vega.github.io/schema/vega-lite/v6.json", - "title": { "text": "Big-O growth — operations vs input size", "subtitle": "why the algorithm you pick matters as n grows", "anchor": "start" }, - "width": "container", - "height": 360, - "data": { - "values": [ - { "n": 1, "ops": 1, "class": "O(1)" }, { "n": 1, "ops": 0, "class": "O(log n)" }, { "n": 1, "ops": 1, "class": "O(n)" }, { "n": 1, "ops": 0, "class": "O(n log n)" }, { "n": 1, "ops": 1, "class": "O(n²)" }, - { "n": 5, "ops": 1, "class": "O(1)" }, { "n": 5, "ops": 2.3, "class": "O(log n)" }, { "n": 5, "ops": 5, "class": "O(n)" }, { "n": 5, "ops": 11.6, "class": "O(n log n)" }, { "n": 5, "ops": 25, "class": "O(n²)" }, - { "n": 10, "ops": 1, "class": "O(1)" }, { "n": 10, "ops": 3.3, "class": "O(log n)" }, { "n": 10, "ops": 10, "class": "O(n)" }, { "n": 10, "ops": 33, "class": "O(n log n)" }, { "n": 10, "ops": 100, "class": "O(n²)" }, - { "n": 20, "ops": 1, "class": "O(1)" }, { "n": 20, "ops": 4.3, "class": "O(log n)" }, { "n": 20, "ops": 20, "class": "O(n)" }, { "n": 20, "ops": 86, "class": "O(n log n)" }, { "n": 20, "ops": 400, "class": "O(n²)" }, - { "n": 30, "ops": 1, "class": "O(1)" }, { "n": 30, "ops": 4.9, "class": "O(log n)" }, { "n": 30, "ops": 30, "class": "O(n)" }, { "n": 30, "ops": 147, "class": "O(n log n)" }, { "n": 30, "ops": 900, "class": "O(n²)" }, - { "n": 40, "ops": 1, "class": "O(1)" }, { "n": 40, "ops": 5.3, "class": "O(log n)" }, { "n": 40, "ops": 40, "class": "O(n)" }, { "n": 40, "ops": 213, "class": "O(n log n)" }, { "n": 40, "ops": 1600, "class": "O(n²)" } - ] - }, - "mark": { "type": "line", "point": true, "tooltip": true }, - "encoding": { - "x": { "field": "n", "type": "quantitative", "title": "input size (n)" }, - "y": { "field": "ops", "type": "quantitative", "title": "operations", "scale": { "type": "symlog" } }, - "color": { "field": "class", "type": "nominal", "title": "complexity", "sort": ["O(1)", "O(log n)", "O(n)", "O(n log n)", "O(n²)"], "scale": { "scheme": "turbo" } } - } -} +{"$schema":"https://vega.github.io/schema/vega-lite/v6.json","title":{"text":"Big-O growth — operations vs input size","subtitle":"why the algorithm you pick matters as n grows","anchor":"start"},"width":"container","height":360,"data":{"values":[{"n":1,"ops":1,"class":"O(1)"},{"n":1,"ops":0,"class":"O(log n)"},{"n":1,"ops":1,"class":"O(n)"},{"n":1,"ops":0,"class":"O(n log n)"},{"n":1,"ops":1,"class":"O(n²)"},{"n":5,"ops":1,"class":"O(1)"},{"n":5,"ops":2.3,"class":"O(log n)"},{"n":5,"ops":5,"class":"O(n)"},{"n":5,"ops":11.6,"class":"O(n log n)"},{"n":5,"ops":25,"class":"O(n²)"},{"n":10,"ops":1,"class":"O(1)"},{"n":10,"ops":3.3,"class":"O(log n)"},{"n":10,"ops":10,"class":"O(n)"},{"n":10,"ops":33,"class":"O(n log n)"},{"n":10,"ops":100,"class":"O(n²)"},{"n":20,"ops":1,"class":"O(1)"},{"n":20,"ops":4.3,"class":"O(log n)"},{"n":20,"ops":20,"class":"O(n)"},{"n":20,"ops":86,"class":"O(n log n)"},{"n":20,"ops":400,"class":"O(n²)"},{"n":30,"ops":1,"class":"O(1)"},{"n":30,"ops":4.9,"class":"O(log n)"},{"n":30,"ops":30,"class":"O(n)"},{"n":30,"ops":147,"class":"O(n log n)"},{"n":30,"ops":900,"class":"O(n²)"},{"n":40,"ops":1,"class":"O(1)"},{"n":40,"ops":5.3,"class":"O(log n)"},{"n":40,"ops":40,"class":"O(n)"},{"n":40,"ops":213,"class":"O(n log n)"},{"n":40,"ops":1600,"class":"O(n²)"}]},"mark":{"type":"line","point":true,"tooltip":true},"encoding":{"x":{"field":"n","type":"quantitative","title":"input size (n)"},"y":{"field":"ops","type":"quantitative","title":"operations","scale":{"type":"symlog"}},"color":{"field":"class","type":"nominal","title":"complexity","sort":["O(1)","O(log n)","O(n)","O(n log n)","O(n²)"],"scale":{"scheme":"turbo"}}}} diff --git a/plugin/skills/diagram-recipes/examples/correlation-heatmap.vegalite.json b/plugin/skills/diagram-recipes/examples/correlation-heatmap.vegalite.json index 5e6c282..7918135 100644 --- a/plugin/skills/diagram-recipes/examples/correlation-heatmap.vegalite.json +++ b/plugin/skills/diagram-recipes/examples/correlation-heatmap.vegalite.json @@ -1,67 +1 @@ -{ - "$schema": "https://vega.github.io/schema/vega-lite/v6.json", - "title": { "text": "Service mesh — p95 latency matrix", "subtitle": "caller → callee, milliseconds (last 1h); blank = no calls", "anchor": "start" }, - "width": "container", - "height": 360, - "data": { - "values": [ - { "caller": "gateway", "callee": "gateway", "ms": null }, { "caller": "gateway", "callee": "auth", "ms": 18 }, { "caller": "gateway", "callee": "orders", "ms": 42 }, { "caller": "gateway", "callee": "catalog", "ms": 31 }, { "caller": "gateway", "callee": "payments", "ms": 64 }, { "caller": "gateway", "callee": "search", "ms": 27 }, - { "caller": "auth", "callee": "gateway", "ms": null }, { "caller": "auth", "callee": "auth", "ms": null }, { "caller": "auth", "callee": "orders", "ms": null }, { "caller": "auth", "callee": "catalog", "ms": null }, { "caller": "auth", "callee": "payments", "ms": null }, { "caller": "auth", "callee": "search", "ms": null }, - { "caller": "orders", "callee": "gateway", "ms": null }, { "caller": "orders", "callee": "auth", "ms": 12 }, { "caller": "orders", "callee": "orders", "ms": null }, { "caller": "orders", "callee": "catalog", "ms": 38 }, { "caller": "orders", "callee": "payments", "ms": 112 }, { "caller": "orders", "callee": "search", "ms": null }, - { "caller": "catalog", "callee": "gateway", "ms": null }, { "caller": "catalog", "callee": "auth", "ms": 9 }, { "caller": "catalog", "callee": "orders", "ms": null }, { "caller": "catalog", "callee": "catalog", "ms": null }, { "caller": "catalog", "callee": "payments", "ms": null }, { "caller": "catalog", "callee": "search", "ms": 21 }, - { "caller": "payments", "callee": "gateway", "ms": null }, { "caller": "payments", "callee": "auth", "ms": 14 }, { "caller": "payments", "callee": "orders", "ms": 47 }, { "caller": "payments", "callee": "catalog", "ms": null }, { "caller": "payments", "callee": "payments", "ms": null }, { "caller": "payments", "callee": "search", "ms": null }, - { "caller": "search", "callee": "gateway", "ms": null }, { "caller": "search", "callee": "auth", "ms": 11 }, { "caller": "search", "callee": "orders", "ms": null }, { "caller": "search", "callee": "catalog", "ms": 73 }, { "caller": "search", "callee": "payments", "ms": null }, { "caller": "search", "callee": "search", "ms": null } - ] - }, - "encoding": { - "x": { - "field": "callee", - "type": "nominal", - "title": "callee (downstream)", - "sort": ["gateway", "auth", "orders", "catalog", "payments", "search"], - "axis": { "labelAngle": 0, "orient": "top", "domain": false, "ticks": false } - }, - "y": { - "field": "caller", - "type": "nominal", - "title": "caller (upstream)", - "sort": ["gateway", "auth", "orders", "catalog", "payments", "search"], - "axis": { "domain": false, "ticks": false } - } - }, - "layer": [ - { - "mark": { "type": "rect", "cornerRadius": 3, "fill": "#1e293b", "stroke": "#0f172a", "strokeWidth": 2 } - }, - { - "transform": [{ "filter": "datum.ms != null" }], - "mark": { "type": "rect", "cornerRadius": 3, "stroke": "#0f172a", "strokeWidth": 2 }, - "encoding": { - "color": { - "field": "ms", - "type": "quantitative", - "title": "p95 (ms)", - "scale": { "scheme": "yelloworangered", "domain": [0, 120] }, - "legend": { "orient": "right", "gradientLength": 160 } - }, - "tooltip": [ - { "field": "caller", "type": "nominal", "title": "caller" }, - { "field": "callee", "type": "nominal", "title": "callee" }, - { "field": "ms", "type": "quantitative", "title": "p95 (ms)" } - ] - } - }, - { - "transform": [{ "filter": "datum.ms != null" }], - "mark": { "type": "text", "fontSize": 13, "fontWeight": "bold" }, - "encoding": { - "text": { "field": "ms", "type": "quantitative" }, - "color": { - "condition": { "test": "datum.ms > 70", "value": "#fef2f2" }, - "value": "#1f2937" - } - } - } - ], - "config": { "view": { "stroke": null } } -} +{"$schema":"https://vega.github.io/schema/vega-lite/v6.json","title":{"text":"Service mesh — p95 latency matrix","subtitle":"caller → callee, milliseconds (last 1h); blank = no calls","anchor":"start"},"width":"container","height":360,"data":{"values":[{"caller":"gateway","callee":"gateway","ms":null},{"caller":"gateway","callee":"auth","ms":18},{"caller":"gateway","callee":"orders","ms":42},{"caller":"gateway","callee":"catalog","ms":31},{"caller":"gateway","callee":"payments","ms":64},{"caller":"gateway","callee":"search","ms":27},{"caller":"auth","callee":"gateway","ms":null},{"caller":"auth","callee":"auth","ms":null},{"caller":"auth","callee":"orders","ms":null},{"caller":"auth","callee":"catalog","ms":null},{"caller":"auth","callee":"payments","ms":null},{"caller":"auth","callee":"search","ms":null},{"caller":"orders","callee":"gateway","ms":null},{"caller":"orders","callee":"auth","ms":12},{"caller":"orders","callee":"orders","ms":null},{"caller":"orders","callee":"catalog","ms":38},{"caller":"orders","callee":"payments","ms":112},{"caller":"orders","callee":"search","ms":null},{"caller":"catalog","callee":"gateway","ms":null},{"caller":"catalog","callee":"auth","ms":9},{"caller":"catalog","callee":"orders","ms":null},{"caller":"catalog","callee":"catalog","ms":null},{"caller":"catalog","callee":"payments","ms":null},{"caller":"catalog","callee":"search","ms":21},{"caller":"payments","callee":"gateway","ms":null},{"caller":"payments","callee":"auth","ms":14},{"caller":"payments","callee":"orders","ms":47},{"caller":"payments","callee":"catalog","ms":null},{"caller":"payments","callee":"payments","ms":null},{"caller":"payments","callee":"search","ms":null},{"caller":"search","callee":"gateway","ms":null},{"caller":"search","callee":"auth","ms":11},{"caller":"search","callee":"orders","ms":null},{"caller":"search","callee":"catalog","ms":73},{"caller":"search","callee":"payments","ms":null},{"caller":"search","callee":"search","ms":null}]},"encoding":{"x":{"field":"callee","type":"nominal","title":"callee (downstream)","sort":["gateway","auth","orders","catalog","payments","search"],"axis":{"labelAngle":0,"orient":"top","domain":false,"ticks":false}},"y":{"field":"caller","type":"nominal","title":"caller (upstream)","sort":["gateway","auth","orders","catalog","payments","search"],"axis":{"domain":false,"ticks":false}}},"layer":[{"mark":{"type":"rect","cornerRadius":3,"fill":"#1e293b","stroke":"#0f172a","strokeWidth":2}},{"transform":[{"filter":"datum.ms != null"}],"mark":{"type":"rect","cornerRadius":3,"stroke":"#0f172a","strokeWidth":2},"encoding":{"color":{"field":"ms","type":"quantitative","title":"p95 (ms)","scale":{"scheme":"yelloworangered","domain":[0,120]},"legend":{"orient":"right","gradientLength":160}},"tooltip":[{"field":"caller","type":"nominal","title":"caller"},{"field":"callee","type":"nominal","title":"callee"},{"field":"ms","type":"quantitative","title":"p95 (ms)"}]}},{"transform":[{"filter":"datum.ms != null"}],"mark":{"type":"text","fontSize":13,"fontWeight":"bold"},"encoding":{"text":{"field":"ms","type":"quantitative"},"color":{"condition":{"test":"datum.ms > 70","value":"#fef2f2"},"value":"#1f2937"}}}],"config":{"view":{"stroke":null}}} diff --git a/plugin/skills/diagram-recipes/examples/critical-path.flow.json b/plugin/skills/diagram-recipes/examples/critical-path.flow.json index 7a75e39..e5a0a18 100644 --- a/plugin/skills/diagram-recipes/examples/critical-path.flow.json +++ b/plugin/skills/diagram-recipes/examples/critical-path.flow.json @@ -1,249 +1 @@ -{ - "direction": "LR", - "legend": [ - { - "label": "critical path", - "color": "#ef4444" - }, - { - "label": "has slack", - "color": "#71717a", - "dash": true - } - ], - "nodes": [ - { - "id": "a", - "type": "change", - "data": { - "label": "Requirements", - "sub": "Priya", - "meta": "3d", - "status": "error", - "icon": "bolt" - } - }, - { - "id": "b", - "type": "change", - "data": { - "label": "Wireframes", - "sub": "Dana · 2d slack", - "meta": "4d", - "status": "neutral", - "icon": "clock" - } - }, - { - "id": "c", - "type": "change", - "data": { - "label": "API design", - "sub": "Marco", - "meta": "6d", - "status": "error", - "icon": "bolt" - } - }, - { - "id": "d", - "type": "change", - "data": { - "label": "Copy & content", - "sub": "Priya · 5d slack", - "meta": "3d", - "status": "neutral", - "icon": "clock" - } - }, - { - "id": "e", - "type": "change", - "data": { - "label": "Backend build", - "sub": "Marco", - "meta": "5d", - "status": "error", - "icon": "bolt" - } - }, - { - "id": "f", - "type": "change", - "data": { - "label": "Frontend build", - "sub": "Ivan · 1d slack", - "meta": "5d", - "status": "neutral", - "icon": "clock" - } - }, - { - "id": "g", - "type": "change", - "data": { - "label": "Integration", - "sub": "Team", - "meta": "4d", - "status": "error", - "icon": "bolt" - } - }, - { - "id": "h", - "type": "change", - "data": { - "label": "Docs", - "sub": "Priya · 4d slack", - "meta": "2d", - "status": "neutral", - "icon": "clock" - } - }, - { - "id": "i", - "type": "change", - "data": { - "label": "Launch", - "sub": "Sam", - "meta": "2d", - "status": "error", - "icon": "bolt" - } - } - ], - "edges": [ - { - "source": "a", - "target": "c", - "style": { - "stroke": "#ef4444", - "strokeWidth": 2.5 - }, - "markerEnd": { - "type": "arrowclosed", - "color": "#ef4444" - } - }, - { - "source": "c", - "target": "e", - "style": { - "stroke": "#ef4444", - "strokeWidth": 2.5 - }, - "markerEnd": { - "type": "arrowclosed", - "color": "#ef4444" - } - }, - { - "source": "e", - "target": "g", - "style": { - "stroke": "#ef4444", - "strokeWidth": 2.5 - }, - "markerEnd": { - "type": "arrowclosed", - "color": "#ef4444" - } - }, - { - "source": "g", - "target": "i", - "style": { - "stroke": "#ef4444", - "strokeWidth": 2.5 - }, - "markerEnd": { - "type": "arrowclosed", - "color": "#ef4444" - } - }, - { - "source": "a", - "target": "b", - "style": { - "stroke": "#71717a", - "strokeDasharray": "6 4" - }, - "markerEnd": { - "type": "arrowclosed", - "color": "#71717a" - } - }, - { - "source": "a", - "target": "d", - "style": { - "stroke": "#71717a", - "strokeDasharray": "6 4" - }, - "markerEnd": { - "type": "arrowclosed", - "color": "#71717a" - } - }, - { - "source": "b", - "target": "f", - "style": { - "stroke": "#71717a", - "strokeDasharray": "6 4" - }, - "markerEnd": { - "type": "arrowclosed", - "color": "#71717a" - } - }, - { - "source": "c", - "target": "f", - "style": { - "stroke": "#71717a", - "strokeDasharray": "6 4" - }, - "markerEnd": { - "type": "arrowclosed", - "color": "#71717a" - } - }, - { - "source": "d", - "target": "f", - "style": { - "stroke": "#71717a", - "strokeDasharray": "6 4" - }, - "markerEnd": { - "type": "arrowclosed", - "color": "#71717a" - } - }, - { - "source": "f", - "target": "g", - "style": { - "stroke": "#71717a", - "strokeDasharray": "6 4" - }, - "markerEnd": { - "type": "arrowclosed", - "color": "#71717a" - } - }, - { - "source": "g", - "target": "h", - "style": { - "stroke": "#71717a", - "strokeDasharray": "6 4" - }, - "markerEnd": { - "type": "arrowclosed", - "color": "#71717a" - } - } - ] -} +{"direction":"LR","legend":[{"label":"critical path","color":"#ef4444"},{"label":"has slack","color":"#71717a","dash":true}],"nodes":[{"id":"a","type":"change","data":{"label":"Requirements","sub":"Priya","meta":"3d","status":"error","icon":"bolt"}},{"id":"b","type":"change","data":{"label":"Wireframes","sub":"Dana · 2d slack","meta":"4d","status":"neutral","icon":"clock"}},{"id":"c","type":"change","data":{"label":"API design","sub":"Marco","meta":"6d","status":"error","icon":"bolt"}},{"id":"d","type":"change","data":{"label":"Copy & content","sub":"Priya · 5d slack","meta":"3d","status":"neutral","icon":"clock"}},{"id":"e","type":"change","data":{"label":"Backend build","sub":"Marco","meta":"5d","status":"error","icon":"bolt"}},{"id":"f","type":"change","data":{"label":"Frontend build","sub":"Ivan · 1d slack","meta":"5d","status":"neutral","icon":"clock"}},{"id":"g","type":"change","data":{"label":"Integration","sub":"Team","meta":"4d","status":"error","icon":"bolt"}},{"id":"h","type":"change","data":{"label":"Docs","sub":"Priya · 4d slack","meta":"2d","status":"neutral","icon":"clock"}},{"id":"i","type":"change","data":{"label":"Launch","sub":"Sam","meta":"2d","status":"error","icon":"bolt"}}],"edges":[{"source":"a","target":"c","style":{"stroke":"#ef4444","strokeWidth":2.5},"markerEnd":{"type":"arrowclosed","color":"#ef4444"}},{"source":"c","target":"e","style":{"stroke":"#ef4444","strokeWidth":2.5},"markerEnd":{"type":"arrowclosed","color":"#ef4444"}},{"source":"e","target":"g","style":{"stroke":"#ef4444","strokeWidth":2.5},"markerEnd":{"type":"arrowclosed","color":"#ef4444"}},{"source":"g","target":"i","style":{"stroke":"#ef4444","strokeWidth":2.5},"markerEnd":{"type":"arrowclosed","color":"#ef4444"}},{"source":"a","target":"b","style":{"stroke":"#71717a","strokeDasharray":"6 4"},"markerEnd":{"type":"arrowclosed","color":"#71717a"}},{"source":"a","target":"d","style":{"stroke":"#71717a","strokeDasharray":"6 4"},"markerEnd":{"type":"arrowclosed","color":"#71717a"}},{"source":"b","target":"f","style":{"stroke":"#71717a","strokeDasharray":"6 4"},"markerEnd":{"type":"arrowclosed","color":"#71717a"}},{"source":"c","target":"f","style":{"stroke":"#71717a","strokeDasharray":"6 4"},"markerEnd":{"type":"arrowclosed","color":"#71717a"}},{"source":"d","target":"f","style":{"stroke":"#71717a","strokeDasharray":"6 4"},"markerEnd":{"type":"arrowclosed","color":"#71717a"}},{"source":"f","target":"g","style":{"stroke":"#71717a","strokeDasharray":"6 4"},"markerEnd":{"type":"arrowclosed","color":"#71717a"}},{"source":"g","target":"h","style":{"stroke":"#71717a","strokeDasharray":"6 4"},"markerEnd":{"type":"arrowclosed","color":"#71717a"}}]} diff --git a/plugin/skills/diagram-recipes/examples/data-lineage.flow.json b/plugin/skills/diagram-recipes/examples/data-lineage.flow.json index 3cd53df..efa85c3 100644 --- a/plugin/skills/diagram-recipes/examples/data-lineage.flow.json +++ b/plugin/skills/diagram-recipes/examples/data-lineage.flow.json @@ -1,42 +1 @@ -{ - "direction": "TB", - "groups": [ - { "id": "sources", "label": "Sources", "color": "#60a5fa" }, - { "id": "staging", "label": "Staging (dbt)", "color": "#a1a1aa" }, - { "id": "marts", "label": "Marts", "color": "#fbbf24" }, - { "id": "consumers", "label": "Consumers", "color": "#22c55e" } - ], - "legend": [ - { "label": "fresh", "color": "#22c55e" }, - { "label": "stale > SLA", "color": "#ef4444", "dash": true } - ], - "nodes": [ - { "id": "pg", "type": "change", "group": "sources", "data": { "label": "orders (Postgres)", "status": "info", "icon": "db" } }, - { "id": "stripe", "type": "change", "group": "sources", "data": { "label": "Stripe API", "status": "info", "icon": "code" } }, - { "id": "events", "type": "change", "group": "sources", "data": { "label": "Clickstream", "status": "info", "icon": "bolt" } }, - - { "id": "stg_orders", "type": "change", "group": "staging", "data": { "label": "stg_orders", "status": "neutral", "meta": "12m ago" } }, - { "id": "stg_pay", "type": "change", "group": "staging", "data": { "label": "stg_payments", "status": "neutral", "meta": "12m ago" } }, - { "id": "stg_events", "type": "change", "group": "staging", "data": { "label": "stg_events", "status": "warn", "meta": "3h ago" } }, - - { "id": "dim_cust", "type": "change", "group": "marts", "data": { "label": "dim_customer", "status": "active" } }, - { "id": "fct_orders", "type": "change", "group": "marts", "data": { "label": "fct_orders", "status": "active" } }, - { "id": "fct_rev", "type": "change", "group": "marts", "data": { "label": "fct_revenue", "status": "active" } }, - - { "id": "exec", "type": "change", "group": "consumers", "data": { "label": "Exec dashboard", "status": "success", "icon": "search" } }, - { "id": "churn", "type": "change", "group": "consumers", "data": { "label": "Churn model", "status": "success", "icon": "code" } }, - { "id": "retl", "type": "change", "group": "consumers", "data": { "label": "Reverse-ETL → CRM", "status": "success", "icon": "arrow" } } - ], - "edges": [ - { "source": "pg", "target": "stg_orders", "style": { "stroke": "#22c55e" }, "markerEnd": { "type": "arrowclosed", "color": "#22c55e" } }, - { "source": "stripe", "target": "stg_pay", "style": { "stroke": "#22c55e" }, "markerEnd": { "type": "arrowclosed", "color": "#22c55e" } }, - { "source": "events", "target": "stg_events", "label": "stale", "style": { "stroke": "#ef4444", "strokeDasharray": "5 5" }, "markerEnd": { "type": "arrowclosed", "color": "#ef4444" } }, - { "source": "stg_orders", "target": "fct_orders", "style": { "stroke": "#22c55e" }, "markerEnd": { "type": "arrowclosed", "color": "#22c55e" } }, - { "source": "stg_orders", "target": "dim_cust", "style": { "stroke": "#22c55e" }, "markerEnd": { "type": "arrowclosed", "color": "#22c55e" } }, - { "source": "stg_orders", "target": "fct_rev", "style": { "stroke": "#22c55e" }, "markerEnd": { "type": "arrowclosed", "color": "#22c55e" } }, - { "source": "stg_pay", "target": "fct_rev", "style": { "stroke": "#22c55e" }, "markerEnd": { "type": "arrowclosed", "color": "#22c55e" } }, - { "source": "dim_cust", "target": "churn", "style": { "stroke": "#22c55e" }, "markerEnd": { "type": "arrowclosed", "color": "#22c55e" } }, - { "source": "fct_rev", "target": "exec", "style": { "stroke": "#22c55e" }, "markerEnd": { "type": "arrowclosed", "color": "#22c55e" } }, - { "source": "fct_orders", "target": "retl", "style": { "stroke": "#22c55e" }, "markerEnd": { "type": "arrowclosed", "color": "#22c55e" } } - ] -} +{"direction":"TB","groups":[{"id":"sources","label":"Sources","color":"#60a5fa"},{"id":"staging","label":"Staging (dbt)","color":"#a1a1aa"},{"id":"marts","label":"Marts","color":"#fbbf24"},{"id":"consumers","label":"Consumers","color":"#22c55e"}],"legend":[{"label":"fresh","color":"#22c55e"},{"label":"stale > SLA","color":"#ef4444","dash":true}],"nodes":[{"id":"pg","type":"change","group":"sources","data":{"label":"orders (Postgres)","status":"info","icon":"db"}},{"id":"stripe","type":"change","group":"sources","data":{"label":"Stripe API","status":"info","icon":"code"}},{"id":"events","type":"change","group":"sources","data":{"label":"Clickstream","status":"info","icon":"bolt"}},{"id":"stg_orders","type":"change","group":"staging","data":{"label":"stg_orders","status":"neutral","meta":"12m ago"}},{"id":"stg_pay","type":"change","group":"staging","data":{"label":"stg_payments","status":"neutral","meta":"12m ago"}},{"id":"stg_events","type":"change","group":"staging","data":{"label":"stg_events","status":"warn","meta":"3h ago"}},{"id":"dim_cust","type":"change","group":"marts","data":{"label":"dim_customer","status":"active"}},{"id":"fct_orders","type":"change","group":"marts","data":{"label":"fct_orders","status":"active"}},{"id":"fct_rev","type":"change","group":"marts","data":{"label":"fct_revenue","status":"active"}},{"id":"exec","type":"change","group":"consumers","data":{"label":"Exec dashboard","status":"success","icon":"search"}},{"id":"churn","type":"change","group":"consumers","data":{"label":"Churn model","status":"success","icon":"code"}},{"id":"retl","type":"change","group":"consumers","data":{"label":"Reverse-ETL → CRM","status":"success","icon":"arrow"}}],"edges":[{"source":"pg","target":"stg_orders","style":{"stroke":"#22c55e"},"markerEnd":{"type":"arrowclosed","color":"#22c55e"}},{"source":"stripe","target":"stg_pay","style":{"stroke":"#22c55e"},"markerEnd":{"type":"arrowclosed","color":"#22c55e"}},{"source":"events","target":"stg_events","label":"stale","style":{"stroke":"#ef4444","strokeDasharray":"5 5"},"markerEnd":{"type":"arrowclosed","color":"#ef4444"}},{"source":"stg_orders","target":"fct_orders","style":{"stroke":"#22c55e"},"markerEnd":{"type":"arrowclosed","color":"#22c55e"}},{"source":"stg_orders","target":"dim_cust","style":{"stroke":"#22c55e"},"markerEnd":{"type":"arrowclosed","color":"#22c55e"}},{"source":"stg_orders","target":"fct_rev","style":{"stroke":"#22c55e"},"markerEnd":{"type":"arrowclosed","color":"#22c55e"}},{"source":"stg_pay","target":"fct_rev","style":{"stroke":"#22c55e"},"markerEnd":{"type":"arrowclosed","color":"#22c55e"}},{"source":"dim_cust","target":"churn","style":{"stroke":"#22c55e"},"markerEnd":{"type":"arrowclosed","color":"#22c55e"}},{"source":"fct_rev","target":"exec","style":{"stroke":"#22c55e"},"markerEnd":{"type":"arrowclosed","color":"#22c55e"}},{"source":"fct_orders","target":"retl","style":{"stroke":"#22c55e"},"markerEnd":{"type":"arrowclosed","color":"#22c55e"}}]} diff --git a/plugin/skills/diagram-recipes/examples/debug.panes.json b/plugin/skills/diagram-recipes/examples/debug.panes.json index 4d26861..41dac72 100644 --- a/plugin/skills/diagram-recipes/examples/debug.panes.json +++ b/plugin/skills/diagram-recipes/examples/debug.panes.json @@ -1,15 +1 @@ -{ - "layout": "rows", - "panes": [ - { - "title": "Failure", - "type": "component", - "content": "{\"type\": \"Stack\", \"props\": {\"gap\": \"sm\", \"p\": \"sm\"}, \"children\": [{\"type\": \"Alert\", \"props\": {\"color\": \"red\", \"title\": \"TypeError: cannot read 'id' of undefined\"}, \"children\": \"at resolveUser (auth.ts:42)\"}, {\"type\": \"Table\", \"props\": {\"data\": {\"head\": [\"var\", \"value\"], \"body\": [[\"user\", \"undefined\"], [\"token\", \"\\\"eyJ\\u2026\\\"\"], [\"retries\", \"3\"]]}}}]}" - }, - { - "title": "Call stack", - "type": "flow", - "content": "{\"direction\": \"TB\", \"nodes\": [{\"id\": \"h\", \"data\": {\"label\": \"handleLogin()\"}}, {\"id\": \"r\", \"data\": {\"label\": \"resolveUser()\"}}, {\"id\": \"q\", \"data\": {\"label\": \"queryDB()\"}}], \"edges\": [{\"source\": \"h\", \"target\": \"r\"}, {\"source\": \"r\", \"target\": \"q\"}]}" - } - ] -} +{"layout":"rows","panes":[{"title":"Failure","type":"component","content":"{\"type\": \"Stack\", \"props\": {\"gap\": \"sm\", \"p\": \"sm\"}, \"children\": [{\"type\": \"Alert\", \"props\": {\"color\": \"red\", \"title\": \"TypeError: cannot read 'id' of undefined\"}, \"children\": \"at resolveUser (auth.ts:42)\"}, {\"type\": \"Table\", \"props\": {\"data\": {\"head\": [\"var\", \"value\"], \"body\": [[\"user\", \"undefined\"], [\"token\", \"\\\"eyJ\\u2026\\\"\"], [\"retries\", \"3\"]]}}}]}"},{"title":"Call stack","type":"flow","content":"{\"direction\": \"TB\", \"nodes\": [{\"id\": \"h\", \"data\": {\"label\": \"handleLogin()\"}}, {\"id\": \"r\", \"data\": {\"label\": \"resolveUser()\"}}, {\"id\": \"q\", \"data\": {\"label\": \"queryDB()\"}}], \"edges\": [{\"source\": \"h\", \"target\": \"r\"}, {\"source\": \"r\", \"target\": \"q\"}]}"}]} diff --git a/plugin/skills/diagram-recipes/examples/diy-project-plan.component.json b/plugin/skills/diagram-recipes/examples/diy-project-plan.component.json index 4317488..3458b43 100644 --- a/plugin/skills/diagram-recipes/examples/diy-project-plan.component.json +++ b/plugin/skills/diagram-recipes/examples/diy-project-plan.component.json @@ -1,547 +1 @@ -{ - "type": "Stack", - "props": { - "gap": "lg", - "p": "md", - "maw": 940 - }, - "children": [ - { - "type": "Stack", - "props": { - "gap": 2 - }, - "children": [ - { - "type": "Title", - "props": { - "order": 2 - }, - "children": "\ud83e\udeb5 DIY \u2014 Cedar Privacy Fence" - }, - { - "type": "Text", - "props": { - "c": "dimmed", - "size": "sm" - }, - "children": "40 ft run \u00b7 6 ft tall \u00b7 west property line" - } - ] - }, - { - "type": "Group", - "props": { - "gap": "xs" - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "green", - "variant": "light", - "size": "lg" - }, - "children": "\u2248 $1,180 materials" - }, - { - "type": "Badge", - "props": { - "color": "blue", - "variant": "light", - "size": "lg" - }, - "children": "2 weekends" - }, - { - "type": "Badge", - "props": { - "color": "orange", - "variant": "light", - "size": "lg" - }, - "children": "Intermediate" - }, - { - "type": "Badge", - "props": { - "color": "gray", - "variant": "light", - "size": "lg" - }, - "children": "No permit (< 7 ft)" - } - ] - }, - { - "type": "Alert", - "props": { - "color": "blue", - "variant": "light", - "title": "Plan" - }, - "children": "Seven 8-ft bays on 4\u00d74 cedar posts set in concrete, three backer rails per bay, 1\u00d76 pickets with a \u00bd\u2033 gap. Buy lumber kiln-dried; let posts cure a day before rails. Tick steps off below as you go \u2014 the screen stays in sync." - }, - { - "type": "Title", - "props": { - "order": 4 - }, - "children": "Materials & cut list" - }, - { - "type": "Table", - "props": { - "striped": true, - "withTableBorder": true, - "fz": "sm", - "verticalSpacing": 4 - }, - "children": [ - { - "type": "Table.Thead", - "children": { - "type": "Table.Tr", - "children": [ - { - "type": "Table.Th", - "children": "Material" - }, - { - "type": "Table.Th", - "children": "Spec / cut" - }, - { - "type": "Table.Th", - "children": "Qty" - }, - { - "type": "Table.Th", - "children": "Cost" - } - ] - } - }, - { - "type": "Table.Tbody", - "children": [ - { - "type": "Table.Tr", - "children": [ - { - "type": "Table.Td", - "children": "Cedar post" - }, - { - "type": "Table.Td", - "children": "4\u00d74\u00d78" - }, - { - "type": "Table.Td", - "children": "7" - }, - { - "type": "Table.Td", - "children": "$168" - } - ] - }, - { - "type": "Table.Tr", - "children": [ - { - "type": "Table.Td", - "children": "Backer rail" - }, - { - "type": "Table.Td", - "children": "2\u00d74\u00d78" - }, - { - "type": "Table.Td", - "children": "21" - }, - { - "type": "Table.Td", - "children": "$189" - } - ] - }, - { - "type": "Table.Tr", - "children": [ - { - "type": "Table.Td", - "children": "Picket" - }, - { - "type": "Table.Td", - "children": "1\u00d76\u00d76" - }, - { - "type": "Table.Td", - "children": "80" - }, - { - "type": "Table.Td", - "children": "$560" - } - ] - }, - { - "type": "Table.Tr", - "children": [ - { - "type": "Table.Td", - "children": "Concrete" - }, - { - "type": "Table.Td", - "children": "60 lb bag" - }, - { - "type": "Table.Td", - "children": "14" - }, - { - "type": "Table.Td", - "children": "$84" - } - ] - }, - { - "type": "Table.Tr", - "children": [ - { - "type": "Table.Td", - "children": "Screws + caps" - }, - { - "type": "Table.Td", - "children": "galv. 2\u00bd\u2033" - }, - { - "type": "Table.Td", - "children": "\u2014" - }, - { - "type": "Table.Td", - "children": "$91" - } - ] - }, - { - "type": "Table.Tr", - "children": [ - { - "type": "Table.Td", - "children": "Stain / sealer" - }, - { - "type": "Table.Td", - "children": "1 gal" - }, - { - "type": "Table.Td", - "children": "1" - }, - { - "type": "Table.Td", - "children": "$80" - } - ] - } - ] - } - ] - }, - { - "type": "Title", - "props": { - "order": 4 - }, - "children": "Tools \u2014 tick what you have" - }, - { - "type": "Checklist", - "props": { - "id": "tools", - "editable": true, - "items": [ - { - "id": "digger", - "label": "Post-hole digger or auger", - "checked": false - }, - { - "id": "level", - "label": "4 ft level + post level", - "checked": false - }, - { - "id": "saw", - "label": "Circular saw", - "checked": false - }, - { - "id": "drill", - "label": "Drill / impact driver", - "checked": false - }, - { - "id": "string", - "label": "String line + stakes", - "checked": false - }, - { - "id": "tape", - "label": "Tape measure + speed square", - "checked": false - } - ] - } - }, - { - "type": "Title", - "props": { - "order": 4 - }, - "children": "Build steps" - }, - { - "type": "Checklist", - "props": { - "id": "steps", - "editable": true, - "items": [ - { - "id": "locate", - "label": "Call 811 \u2014 locate buried utilities", - "checked": true - }, - { - "id": "layout", - "label": "Mark the line + posts (8 ft on-center)", - "checked": true - }, - { - "id": "dig", - "label": "Dig post holes (24\u2033 deep, below frost line)", - "checked": false - }, - { - "id": "set", - "label": "Set end/corner posts in concrete, plumb", - "checked": false - }, - { - "id": "line", - "label": "String line \u2192 set the line posts", - "checked": false - }, - { - "id": "cure", - "label": "Cure concrete 24\u201348 h", - "checked": false - }, - { - "id": "rails", - "label": "Attach 3 backer rails per bay", - "checked": false - }, - { - "id": "pickets", - "label": "Hang pickets (\u00bd\u2033 gap, check level often)", - "checked": false - }, - { - "id": "caps", - "label": "Add post caps", - "checked": false - }, - { - "id": "seal", - "label": "Seal / stain after 2\u20134 weeks", - "checked": false - } - ] - } - }, - { - "type": "Title", - "props": { - "order": 4 - }, - "children": "Watch first" - }, - { - "type": "SimpleGrid", - "props": { - "cols": { - "base": 1, - "sm": 2, - "md": 3 - }, - "spacing": "md" - }, - "children": [ - { - "type": "Video", - "props": { - "url": "https://www.youtube.com/watch?v=UufyTrVba5Y", - "title": "Privacy fence & cedar gate \u2014 cost, materials, full build", - "channel": "DIY", - "duration": "23:11" - } - }, - { - "type": "Video", - "props": { - "url": "https://www.youtube.com/watch?v=BomBfTtZA5g", - "title": "Cedar privacy fence, Part 1 \u2014 install the posts", - "channel": "DIY", - "duration": "14:02" - } - }, - { - "type": "Video", - "props": { - "url": "https://www.youtube.com/watch?v=gZPuWDFml2o", - "title": "Build a cedar fence (with a no-sag gate)", - "channel": "DIY", - "duration": "18:47" - } - } - ] - }, - { - "type": "Title", - "props": { - "order": 4 - }, - "children": "Where to buy" - }, - { - "type": "Map", - "props": { - "h": 280, - "center": { - "lat": 45.523, - "lng": -122.672 - }, - "zoom": 13, - "markers": [ - { - "lat": 45.523, - "lng": -122.676, - "label": "Home", - "color": "blue" - }, - { - "lat": 45.512, - "lng": -122.69, - "label": "Cedar lumber yard", - "color": "green" - }, - { - "lat": 45.531, - "lng": -122.654, - "label": "Hardware store", - "color": "red" - } - ], - "routes": [ - { - "from": 0, - "to": 1, - "color": "green", - "label": "Lumber \u00b7 ~12 min" - }, - { - "from": 0, - "to": 2, - "color": "red", - "label": "Hardware \u00b7 ~8 min" - } - ] - } - }, - { - "type": "Title", - "props": { - "order": 4 - }, - "children": "Budget breakdown" - }, - { - "type": "VegaLite", - "props": { - "spec": { - "$schema": "https://vega.github.io/schema/vega-lite/v6.json", - "width": "container", - "height": 190, - "title": { - "text": "Budget by category", - "subtitle": "materials, pre-tax", - "anchor": "start" - }, - "data": { - "values": [ - { - "cat": "Pickets", - "cost": 560 - }, - { - "cat": "Rails", - "cost": 189 - }, - { - "cat": "Posts", - "cost": 168 - }, - { - "cat": "Hardware", - "cost": 91 - }, - { - "cat": "Concrete", - "cost": 84 - }, - { - "cat": "Finish", - "cost": 80 - } - ] - }, - "mark": { - "type": "bar", - "cornerRadiusEnd": 3 - }, - "encoding": { - "y": { - "field": "cat", - "type": "nominal", - "sort": "-x", - "title": null - }, - "x": { - "field": "cost", - "type": "quantitative", - "title": "USD" - }, - "tooltip": [ - { - "field": "cat", - "title": "Category" - }, - { - "field": "cost", - "title": "USD" - } - ] - } - } - } - } - ] -} \ No newline at end of file +{"type":"Stack","props":{"gap":"lg","p":"md","maw":940},"children":[{"type":"Stack","props":{"gap":2},"children":[{"type":"Title","props":{"order":2},"children":"🪵 DIY — Cedar Privacy Fence"},{"type":"Text","props":{"c":"dimmed","size":"sm"},"children":"40 ft run · 6 ft tall · west property line"}]},{"type":"Group","props":{"gap":"xs"},"children":[{"type":"Badge","props":{"color":"green","variant":"light","size":"lg"},"children":"≈ $1,180 materials"},{"type":"Badge","props":{"color":"blue","variant":"light","size":"lg"},"children":"2 weekends"},{"type":"Badge","props":{"color":"orange","variant":"light","size":"lg"},"children":"Intermediate"},{"type":"Badge","props":{"color":"gray","variant":"light","size":"lg"},"children":"No permit (< 7 ft)"}]},{"type":"Alert","props":{"color":"blue","variant":"light","title":"Plan"},"children":"Seven 8-ft bays on 4×4 cedar posts set in concrete, three backer rails per bay, 1×6 pickets with a ½″ gap. Buy lumber kiln-dried; let posts cure a day before rails. Tick steps off below as you go — the screen stays in sync."},{"type":"Title","props":{"order":4},"children":"Materials & cut list"},{"type":"Table","props":{"striped":true,"withTableBorder":true,"fz":"sm","verticalSpacing":4},"children":[{"type":"Table.Thead","children":{"type":"Table.Tr","children":[{"type":"Table.Th","children":"Material"},{"type":"Table.Th","children":"Spec / cut"},{"type":"Table.Th","children":"Qty"},{"type":"Table.Th","children":"Cost"}]}},{"type":"Table.Tbody","children":[{"type":"Table.Tr","children":[{"type":"Table.Td","children":"Cedar post"},{"type":"Table.Td","children":"4×4×8"},{"type":"Table.Td","children":"7"},{"type":"Table.Td","children":"$168"}]},{"type":"Table.Tr","children":[{"type":"Table.Td","children":"Backer rail"},{"type":"Table.Td","children":"2×4×8"},{"type":"Table.Td","children":"21"},{"type":"Table.Td","children":"$189"}]},{"type":"Table.Tr","children":[{"type":"Table.Td","children":"Picket"},{"type":"Table.Td","children":"1×6×6"},{"type":"Table.Td","children":"80"},{"type":"Table.Td","children":"$560"}]},{"type":"Table.Tr","children":[{"type":"Table.Td","children":"Concrete"},{"type":"Table.Td","children":"60 lb bag"},{"type":"Table.Td","children":"14"},{"type":"Table.Td","children":"$84"}]},{"type":"Table.Tr","children":[{"type":"Table.Td","children":"Screws + caps"},{"type":"Table.Td","children":"galv. 2½″"},{"type":"Table.Td","children":"—"},{"type":"Table.Td","children":"$91"}]},{"type":"Table.Tr","children":[{"type":"Table.Td","children":"Stain / sealer"},{"type":"Table.Td","children":"1 gal"},{"type":"Table.Td","children":"1"},{"type":"Table.Td","children":"$80"}]}]}]},{"type":"Title","props":{"order":4},"children":"Tools — tick what you have"},{"type":"Checklist","props":{"id":"tools","editable":true,"items":[{"id":"digger","label":"Post-hole digger or auger","checked":false},{"id":"level","label":"4 ft level + post level","checked":false},{"id":"saw","label":"Circular saw","checked":false},{"id":"drill","label":"Drill / impact driver","checked":false},{"id":"string","label":"String line + stakes","checked":false},{"id":"tape","label":"Tape measure + speed square","checked":false}]}},{"type":"Title","props":{"order":4},"children":"Build steps"},{"type":"Checklist","props":{"id":"steps","editable":true,"items":[{"id":"locate","label":"Call 811 — locate buried utilities","checked":true},{"id":"layout","label":"Mark the line + posts (8 ft on-center)","checked":true},{"id":"dig","label":"Dig post holes (24″ deep, below frost line)","checked":false},{"id":"set","label":"Set end/corner posts in concrete, plumb","checked":false},{"id":"line","label":"String line → set the line posts","checked":false},{"id":"cure","label":"Cure concrete 24–48 h","checked":false},{"id":"rails","label":"Attach 3 backer rails per bay","checked":false},{"id":"pickets","label":"Hang pickets (½″ gap, check level often)","checked":false},{"id":"caps","label":"Add post caps","checked":false},{"id":"seal","label":"Seal / stain after 2–4 weeks","checked":false}]}},{"type":"Title","props":{"order":4},"children":"Watch first"},{"type":"SimpleGrid","props":{"cols":{"base":1,"sm":2,"md":3},"spacing":"md"},"children":[{"type":"Video","props":{"url":"https://www.youtube.com/watch?v=UufyTrVba5Y","title":"Privacy fence & cedar gate — cost, materials, full build","channel":"DIY","duration":"23:11"}},{"type":"Video","props":{"url":"https://www.youtube.com/watch?v=BomBfTtZA5g","title":"Cedar privacy fence, Part 1 — install the posts","channel":"DIY","duration":"14:02"}},{"type":"Video","props":{"url":"https://www.youtube.com/watch?v=gZPuWDFml2o","title":"Build a cedar fence (with a no-sag gate)","channel":"DIY","duration":"18:47"}}]},{"type":"Title","props":{"order":4},"children":"Where to buy"},{"type":"Map","props":{"h":280,"center":{"lat":45.523,"lng":-122.672},"zoom":13,"markers":[{"lat":45.523,"lng":-122.676,"label":"Home","color":"blue"},{"lat":45.512,"lng":-122.69,"label":"Cedar lumber yard","color":"green"},{"lat":45.531,"lng":-122.654,"label":"Hardware store","color":"red"}],"routes":[{"from":0,"to":1,"color":"green","label":"Lumber · ~12 min"},{"from":0,"to":2,"color":"red","label":"Hardware · ~8 min"}]}},{"type":"Title","props":{"order":4},"children":"Budget breakdown"},{"type":"VegaLite","props":{"spec":{"$schema":"https://vega.github.io/schema/vega-lite/v6.json","width":"container","height":190,"title":{"text":"Budget by category","subtitle":"materials, pre-tax","anchor":"start"},"data":{"values":[{"cat":"Pickets","cost":560},{"cat":"Rails","cost":189},{"cat":"Posts","cost":168},{"cat":"Hardware","cost":91},{"cat":"Concrete","cost":84},{"cat":"Finish","cost":80}]},"mark":{"type":"bar","cornerRadiusEnd":3},"encoding":{"y":{"field":"cat","type":"nominal","sort":"-x","title":null},"x":{"field":"cost","type":"quantitative","title":"USD"},"tooltip":[{"field":"cat","title":"Category"},{"field":"cost","title":"USD"}]}}}}]} diff --git a/plugin/skills/diagram-recipes/examples/er-diagram.flow.json b/plugin/skills/diagram-recipes/examples/er-diagram.flow.json index c9b2b9e..ed9b31a 100644 --- a/plugin/skills/diagram-recipes/examples/er-diagram.flow.json +++ b/plugin/skills/diagram-recipes/examples/er-diagram.flow.json @@ -1,141 +1 @@ -{ - "direction": "TB", - "nodes": [ - { - "id": "user", - "type": "entity", - "width": 190, - "height": 104, - "data": { - "label": "User", - "fields": [ - { - "name": "id", - "type": "int", - "key": "PK" - }, - { - "name": "name", - "type": "text" - }, - { - "name": "email", - "type": "text" - } - ] - } - }, - { - "id": "order", - "type": "entity", - "width": 190, - "height": 126, - "data": { - "label": "Order", - "fields": [ - { - "name": "id", - "type": "int", - "key": "PK" - }, - { - "name": "user_id", - "type": "int", - "key": "FK" - }, - { - "name": "total", - "type": "num" - }, - { - "name": "status", - "type": "text" - } - ] - } - }, - { - "id": "product", - "type": "entity", - "width": 190, - "height": 104, - "data": { - "label": "Product", - "fields": [ - { - "name": "id", - "type": "int", - "key": "PK" - }, - { - "name": "name", - "type": "text" - }, - { - "name": "price", - "type": "num" - } - ] - } - }, - { - "id": "item", - "type": "entity", - "width": 190, - "height": 126, - "data": { - "label": "OrderItem", - "fields": [ - { - "name": "id", - "type": "int", - "key": "PK" - }, - { - "name": "order_id", - "type": "int", - "key": "FK" - }, - { - "name": "product_id", - "type": "int", - "key": "FK" - }, - { - "name": "qty", - "type": "int" - } - ] - } - } - ], - "edges": [ - { - "source": "user", - "target": "order", - "label": "places 1..*", - "type": "smoothstep", - "markerEnd": { - "type": "arrowclosed" - } - }, - { - "source": "order", - "target": "item", - "label": "contains 1..*", - "type": "smoothstep", - "markerEnd": { - "type": "arrowclosed" - } - }, - { - "source": "product", - "target": "item", - "label": "in 1..*", - "type": "smoothstep", - "markerEnd": { - "type": "arrowclosed" - } - } - ] -} +{"direction":"TB","nodes":[{"id":"user","type":"entity","width":190,"height":104,"data":{"label":"User","fields":[{"name":"id","type":"int","key":"PK"},{"name":"name","type":"text"},{"name":"email","type":"text"}]}},{"id":"order","type":"entity","width":190,"height":126,"data":{"label":"Order","fields":[{"name":"id","type":"int","key":"PK"},{"name":"user_id","type":"int","key":"FK"},{"name":"total","type":"num"},{"name":"status","type":"text"}]}},{"id":"product","type":"entity","width":190,"height":104,"data":{"label":"Product","fields":[{"name":"id","type":"int","key":"PK"},{"name":"name","type":"text"},{"name":"price","type":"num"}]}},{"id":"item","type":"entity","width":190,"height":126,"data":{"label":"OrderItem","fields":[{"name":"id","type":"int","key":"PK"},{"name":"order_id","type":"int","key":"FK"},{"name":"product_id","type":"int","key":"FK"},{"name":"qty","type":"int"}]}}],"edges":[{"source":"user","target":"order","label":"places 1..*","type":"smoothstep","markerEnd":{"type":"arrowclosed"}},{"source":"order","target":"item","label":"contains 1..*","type":"smoothstep","markerEnd":{"type":"arrowclosed"}},{"source":"product","target":"item","label":"in 1..*","type":"smoothstep","markerEnd":{"type":"arrowclosed"}}]} diff --git a/plugin/skills/diagram-recipes/examples/event-driven.flow.json b/plugin/skills/diagram-recipes/examples/event-driven.flow.json index f984498..8f4655a 100644 --- a/plugin/skills/diagram-recipes/examples/event-driven.flow.json +++ b/plugin/skills/diagram-recipes/examples/event-driven.flow.json @@ -1,30 +1 @@ -{ - "direction": "TB", - "legend": [ - { "label": "producer", "color": "#60a5fa" }, - { "label": "broker / stream", "color": "#fbbf24" }, - { "label": "consumer", "color": "#22c55e" }, - { "label": "dead-letter", "color": "#ef4444" } - ], - "nodes": [ - { "id": "web", "type": "change", "data": { "label": "Web checkout", "status": "info", "kind": "producer" } }, - { "id": "mobile", "type": "change", "data": { "label": "Mobile app", "status": "info", "kind": "producer" } }, - { "id": "iot", "type": "change", "data": { "label": "Device telemetry", "status": "info", "kind": "producer" } }, - { "id": "gw", "type": "change", "data": { "label": "Ingest Gateway", "status": "active", "kind": "service" } }, - { "id": "topic", "type": "change", "data": { "label": "orders.events", "status": "warn", "icon": "bolt", "kind": "topic", "meta": "3 partitions" } }, - { "id": "analytics", "type": "change", "data": { "label": "Analytics sink", "status": "success", "kind": "consumer" } }, - { "id": "notify", "type": "change", "data": { "label": "Notifications", "status": "success", "kind": "consumer" } }, - { "id": "fraud", "type": "change", "data": { "label": "Fraud scoring", "status": "success", "kind": "consumer" } }, - { "id": "dlq", "type": "change", "data": { "label": "Dead-letter queue", "status": "error", "icon": "warn", "kind": "retry" } } - ], - "edges": [ - { "source": "web", "target": "gw" }, - { "source": "mobile", "target": "gw" }, - { "source": "iot", "target": "gw" }, - { "source": "gw", "target": "topic", "animated": true, "style": { "stroke": "#fbbf24" }, "markerEnd": { "type": "arrowclosed", "color": "#fbbf24" } }, - { "source": "topic", "target": "analytics", "animated": true, "style": { "stroke": "#22c55e" }, "markerEnd": { "type": "arrowclosed", "color": "#22c55e" } }, - { "source": "topic", "target": "notify", "animated": true, "style": { "stroke": "#22c55e" }, "markerEnd": { "type": "arrowclosed", "color": "#22c55e" } }, - { "source": "topic", "target": "fraud", "animated": true, "style": { "stroke": "#22c55e" }, "markerEnd": { "type": "arrowclosed", "color": "#22c55e" } }, - { "source": "fraud", "target": "dlq", "label": "on error", "style": { "stroke": "#ef4444", "strokeDasharray": "5 5" }, "markerEnd": { "type": "arrowclosed", "color": "#ef4444" } } - ] -} +{"direction":"TB","legend":[{"label":"producer","color":"#60a5fa"},{"label":"broker / stream","color":"#fbbf24"},{"label":"consumer","color":"#22c55e"},{"label":"dead-letter","color":"#ef4444"}],"nodes":[{"id":"web","type":"change","data":{"label":"Web checkout","status":"info","kind":"producer"}},{"id":"mobile","type":"change","data":{"label":"Mobile app","status":"info","kind":"producer"}},{"id":"iot","type":"change","data":{"label":"Device telemetry","status":"info","kind":"producer"}},{"id":"gw","type":"change","data":{"label":"Ingest Gateway","status":"active","kind":"service"}},{"id":"topic","type":"change","data":{"label":"orders.events","status":"warn","icon":"bolt","kind":"topic","meta":"3 partitions"}},{"id":"analytics","type":"change","data":{"label":"Analytics sink","status":"success","kind":"consumer"}},{"id":"notify","type":"change","data":{"label":"Notifications","status":"success","kind":"consumer"}},{"id":"fraud","type":"change","data":{"label":"Fraud scoring","status":"success","kind":"consumer"}},{"id":"dlq","type":"change","data":{"label":"Dead-letter queue","status":"error","icon":"warn","kind":"retry"}}],"edges":[{"source":"web","target":"gw"},{"source":"mobile","target":"gw"},{"source":"iot","target":"gw"},{"source":"gw","target":"topic","animated":true,"style":{"stroke":"#fbbf24"},"markerEnd":{"type":"arrowclosed","color":"#fbbf24"}},{"source":"topic","target":"analytics","animated":true,"style":{"stroke":"#22c55e"},"markerEnd":{"type":"arrowclosed","color":"#22c55e"}},{"source":"topic","target":"notify","animated":true,"style":{"stroke":"#22c55e"},"markerEnd":{"type":"arrowclosed","color":"#22c55e"}},{"source":"topic","target":"fraud","animated":true,"style":{"stroke":"#22c55e"},"markerEnd":{"type":"arrowclosed","color":"#22c55e"}},{"source":"fraud","target":"dlq","label":"on error","style":{"stroke":"#ef4444","strokeDasharray":"5 5"},"markerEnd":{"type":"arrowclosed","color":"#ef4444"}}]} diff --git a/plugin/skills/diagram-recipes/examples/explainer-bayes.panes.json b/plugin/skills/diagram-recipes/examples/explainer-bayes.panes.json index e6a75b2..42347ec 100644 --- a/plugin/skills/diagram-recipes/examples/explainer-bayes.panes.json +++ b/plugin/skills/diagram-recipes/examples/explainer-bayes.panes.json @@ -1,25 +1 @@ -{ - "layout": "rows", - "panes": [ - { - "title": "Bayes' theorem · the base-rate trap", - "type": "markdown", - "content": "## A 99%-accurate test says you're sick. Are you?\n\n**Probably not — and that's not a trick.** For a *rare* disease, a positive result from a\nhighly accurate test still usually means you're fine.\n\nThe catch our intuition misses: the test's accuracy is **not** your chance of being sick. What\ndominates is the **base rate** — how rare the disease is to begin with. Reason it out with real\ncounts, not percentages, and it becomes obvious.\n\nBayes' theorem makes it precise:\n\n$$P(\\text{sick}\\mid+) = \\frac{P(+\\mid\\text{sick})\\,P(\\text{sick})}{P(+)}$$\n\nThe tiny prior $P(\\text{sick})$ — the base rate — drags the answer down, however accurate the test." - }, - { - "title": "Count it out, don't use percentages", - "type": "flow", - "content": "{\"direction\":\"TB\",\"legend\":[{\"label\":\"given\",\"color\":\"#60a5fa\"},{\"label\":\"count it out\",\"color\":\"#fbbf24\"},{\"label\":\"answer\",\"color\":\"#22c55e\"}],\"nodes\":[{\"id\":\"p\",\"type\":\"change\",\"data\":{\"label\":\"Start with 10,000 people\",\"kind\":\"given\",\"status\":\"info\",\"icon\":\"info\",\"sub\":\"natural frequencies beat percentages\"}},{\"id\":\"b\",\"type\":\"change\",\"data\":{\"label\":\"1% have it → 100 sick, 9,900 healthy\",\"kind\":\"prior\",\"status\":\"info\",\"icon\":\"dot\",\"sub\":\"the base rate\"}},{\"id\":\"tp\",\"type\":\"change\",\"data\":{\"label\":\"99% sensitive → 99 of the sick test +\",\"kind\":\"count\",\"status\":\"active\",\"icon\":\"check\",\"sub\":\"true positives\"}},{\"id\":\"fp\",\"type\":\"change\",\"data\":{\"label\":\"1% false-positive → 99 of the healthy test +\",\"kind\":\"count\",\"status\":\"active\",\"icon\":\"warn\",\"sub\":\"false positives — same number!\"}},{\"id\":\"r\",\"type\":\"change\",\"data\":{\"label\":\"So a '+' means sick with prob 99 / (99+99) = 50%\",\"kind\":\"answer\",\"status\":\"done\",\"icon\":\"check\",\"sub\":\"posterior, not the 99% accuracy\"}}],\"edges\":[{\"id\":\"e1\",\"source\":\"p\",\"target\":\"b\",\"markerEnd\":{\"type\":\"arrowclosed\",\"color\":\"#60a5fa\"},\"style\":{\"stroke\":\"#60a5fa\"}},{\"id\":\"e2\",\"source\":\"b\",\"target\":\"tp\",\"markerEnd\":{\"type\":\"arrowclosed\",\"color\":\"#fbbf24\"},\"style\":{\"stroke\":\"#fbbf24\"}},{\"id\":\"e3\",\"source\":\"b\",\"target\":\"fp\",\"markerEnd\":{\"type\":\"arrowclosed\",\"color\":\"#fbbf24\"},\"style\":{\"stroke\":\"#fbbf24\"}},{\"id\":\"e4\",\"source\":\"tp\",\"target\":\"r\",\"style\":{\"stroke\":\"#22c55e\",\"strokeWidth\":2},\"markerEnd\":{\"type\":\"arrowclosed\",\"color\":\"#22c55e\"}},{\"id\":\"e5\",\"source\":\"fp\",\"target\":\"r\",\"style\":{\"stroke\":\"#22c55e\",\"strokeWidth\":2},\"markerEnd\":{\"type\":\"arrowclosed\",\"color\":\"#22c55e\"}}]}" - }, - { - "title": "The quantitative heart", - "type": "vegalite", - "content": "{\"title\":{\"text\":\"Among everyone who tests positive (of 10,000)\",\"subtitle\":\"half the positives are false alarms — so a '+' is a coin flip, not 99%. Make the disease rarer and it gets worse.\"},\"width\":\"container\",\"height\":220,\"data\":{\"values\":[{\"kind\":\"Sick (true +)\",\"n\":99},{\"kind\":\"Healthy (false +)\",\"n\":99}]},\"mark\":{\"type\":\"bar\",\"tooltip\":true},\"encoding\":{\"x\":{\"field\":\"kind\",\"type\":\"nominal\",\"title\":null,\"axis\":{\"labelAngle\":0}},\"y\":{\"field\":\"n\",\"type\":\"quantitative\",\"title\":\"people who test positive\"},\"color\":{\"field\":\"kind\",\"type\":\"nominal\",\"legend\":null,\"scale\":{\"domain\":[\"Sick (true +)\",\"Healthy (false +)\"],\"range\":[\"#22c55e\",\"#ef4444\"]}}}}" - }, - { - "title": "Real numbers, real proof", - "type": "component", - "content": "{\"type\":\"Stack\",\"props\":{\"gap\":\"sm\"},\"children\":[{\"type\":\"Title\",\"props\":{\"order\":5},\"children\":\"The numbers (Bayes' theorem)\"},{\"type\":\"SimpleGrid\",\"props\":{\"cols\":3,\"spacing\":\"sm\"},\"children\":[{\"type\":\"Card\",\"props\":{\"withBorder\":true,\"padding\":\"sm\",\"radius\":\"md\"},\"children\":[{\"type\":\"Text\",\"props\":{\"size\":\"xs\",\"c\":\"dimmed\"},\"children\":\"Base rate (prior)\"},{\"type\":\"Text\",\"props\":{\"fw\":700,\"size\":\"lg\"},\"children\":\"1%\"},{\"type\":\"Text\",\"props\":{\"size\":\"xs\",\"c\":\"dimmed\"},\"children\":\"how common it is\"}]},{\"type\":\"Card\",\"props\":{\"withBorder\":true,\"padding\":\"sm\",\"radius\":\"md\"},\"children\":[{\"type\":\"Text\",\"props\":{\"size\":\"xs\",\"c\":\"dimmed\"},\"children\":\"Test accuracy\"},{\"type\":\"Text\",\"props\":{\"fw\":700,\"size\":\"lg\"},\"children\":\"99%\"},{\"type\":\"Text\",\"props\":{\"size\":\"xs\",\"c\":\"dimmed\"},\"children\":\"sensitivity & specificity\"}]},{\"type\":\"Card\",\"props\":{\"withBorder\":true,\"padding\":\"sm\",\"radius\":\"md\"},\"children\":[{\"type\":\"Text\",\"props\":{\"size\":\"xs\",\"c\":\"dimmed\"},\"children\":\"Chance you're sick given +\"},{\"type\":\"Text\",\"props\":{\"fw\":700,\"size\":\"lg\"},\"children\":\"≈ 50%\"},{\"type\":\"Text\",\"props\":{\"size\":\"xs\",\"c\":\"dimmed\"},\"children\":\"the posterior\"}]}]},{\"type\":\"Alert\",\"props\":{\"color\":\"blue\",\"variant\":\"light\",\"title\":\"P(sick | +) = P(+ | sick)·P(sick) / P(+)\"},\"children\":\"= (0.99 × 0.01) / (0.99×0.01 + 0.01×0.99) = 0.0099 / 0.0198 = 50%. Rarer disease ⇒ even lower. This is exactly why doctors confirm a scary result with a second, independent test — each test multiplies the evidence.\"},{\"type\":\"Alert\",\"props\":{\"color\":\"green\",\"variant\":\"light\",\"title\":\"Misconception: 'accuracy' ≠ 'my probability'\"},\"children\":\"A test's accuracy describes the test, not you. Your probability also depends on how likely you were before testing (the base rate). Ignore the base rate and you'll badly over-react to rare-event positives — in medicine, security screening, and spam filters alike.\"}]}" - } - ] -} \ No newline at end of file +{"layout":"rows","panes":[{"title":"Bayes' theorem · the base-rate trap","type":"markdown","content":"## A 99%-accurate test says you're sick. Are you?\n\n**Probably not — and that's not a trick.** For a *rare* disease, a positive result from a\nhighly accurate test still usually means you're fine.\n\nThe catch our intuition misses: the test's accuracy is **not** your chance of being sick. What\ndominates is the **base rate** — how rare the disease is to begin with. Reason it out with real\ncounts, not percentages, and it becomes obvious.\n\nBayes' theorem makes it precise:\n\n$$P(\\text{sick}\\mid+) = \\frac{P(+\\mid\\text{sick})\\,P(\\text{sick})}{P(+)}$$\n\nThe tiny prior $P(\\text{sick})$ — the base rate — drags the answer down, however accurate the test."},{"title":"Count it out, don't use percentages","type":"flow","content":"{\"direction\":\"TB\",\"legend\":[{\"label\":\"given\",\"color\":\"#60a5fa\"},{\"label\":\"count it out\",\"color\":\"#fbbf24\"},{\"label\":\"answer\",\"color\":\"#22c55e\"}],\"nodes\":[{\"id\":\"p\",\"type\":\"change\",\"data\":{\"label\":\"Start with 10,000 people\",\"kind\":\"given\",\"status\":\"info\",\"icon\":\"info\",\"sub\":\"natural frequencies beat percentages\"}},{\"id\":\"b\",\"type\":\"change\",\"data\":{\"label\":\"1% have it → 100 sick, 9,900 healthy\",\"kind\":\"prior\",\"status\":\"info\",\"icon\":\"dot\",\"sub\":\"the base rate\"}},{\"id\":\"tp\",\"type\":\"change\",\"data\":{\"label\":\"99% sensitive → 99 of the sick test +\",\"kind\":\"count\",\"status\":\"active\",\"icon\":\"check\",\"sub\":\"true positives\"}},{\"id\":\"fp\",\"type\":\"change\",\"data\":{\"label\":\"1% false-positive → 99 of the healthy test +\",\"kind\":\"count\",\"status\":\"active\",\"icon\":\"warn\",\"sub\":\"false positives — same number!\"}},{\"id\":\"r\",\"type\":\"change\",\"data\":{\"label\":\"So a '+' means sick with prob 99 / (99+99) = 50%\",\"kind\":\"answer\",\"status\":\"done\",\"icon\":\"check\",\"sub\":\"posterior, not the 99% accuracy\"}}],\"edges\":[{\"id\":\"e1\",\"source\":\"p\",\"target\":\"b\",\"markerEnd\":{\"type\":\"arrowclosed\",\"color\":\"#60a5fa\"},\"style\":{\"stroke\":\"#60a5fa\"}},{\"id\":\"e2\",\"source\":\"b\",\"target\":\"tp\",\"markerEnd\":{\"type\":\"arrowclosed\",\"color\":\"#fbbf24\"},\"style\":{\"stroke\":\"#fbbf24\"}},{\"id\":\"e3\",\"source\":\"b\",\"target\":\"fp\",\"markerEnd\":{\"type\":\"arrowclosed\",\"color\":\"#fbbf24\"},\"style\":{\"stroke\":\"#fbbf24\"}},{\"id\":\"e4\",\"source\":\"tp\",\"target\":\"r\",\"style\":{\"stroke\":\"#22c55e\",\"strokeWidth\":2},\"markerEnd\":{\"type\":\"arrowclosed\",\"color\":\"#22c55e\"}},{\"id\":\"e5\",\"source\":\"fp\",\"target\":\"r\",\"style\":{\"stroke\":\"#22c55e\",\"strokeWidth\":2},\"markerEnd\":{\"type\":\"arrowclosed\",\"color\":\"#22c55e\"}}]}"},{"title":"The quantitative heart","type":"vegalite","content":"{\"title\":{\"text\":\"Among everyone who tests positive (of 10,000)\",\"subtitle\":\"half the positives are false alarms — so a '+' is a coin flip, not 99%. Make the disease rarer and it gets worse.\"},\"width\":\"container\",\"height\":220,\"data\":{\"values\":[{\"kind\":\"Sick (true +)\",\"n\":99},{\"kind\":\"Healthy (false +)\",\"n\":99}]},\"mark\":{\"type\":\"bar\",\"tooltip\":true},\"encoding\":{\"x\":{\"field\":\"kind\",\"type\":\"nominal\",\"title\":null,\"axis\":{\"labelAngle\":0}},\"y\":{\"field\":\"n\",\"type\":\"quantitative\",\"title\":\"people who test positive\"},\"color\":{\"field\":\"kind\",\"type\":\"nominal\",\"legend\":null,\"scale\":{\"domain\":[\"Sick (true +)\",\"Healthy (false +)\"],\"range\":[\"#22c55e\",\"#ef4444\"]}}}}"},{"title":"Real numbers, real proof","type":"component","content":"{\"type\":\"Stack\",\"props\":{\"gap\":\"sm\"},\"children\":[{\"type\":\"Title\",\"props\":{\"order\":5},\"children\":\"The numbers (Bayes' theorem)\"},{\"type\":\"SimpleGrid\",\"props\":{\"cols\":3,\"spacing\":\"sm\"},\"children\":[{\"type\":\"Card\",\"props\":{\"withBorder\":true,\"padding\":\"sm\",\"radius\":\"md\"},\"children\":[{\"type\":\"Text\",\"props\":{\"size\":\"xs\",\"c\":\"dimmed\"},\"children\":\"Base rate (prior)\"},{\"type\":\"Text\",\"props\":{\"fw\":700,\"size\":\"lg\"},\"children\":\"1%\"},{\"type\":\"Text\",\"props\":{\"size\":\"xs\",\"c\":\"dimmed\"},\"children\":\"how common it is\"}]},{\"type\":\"Card\",\"props\":{\"withBorder\":true,\"padding\":\"sm\",\"radius\":\"md\"},\"children\":[{\"type\":\"Text\",\"props\":{\"size\":\"xs\",\"c\":\"dimmed\"},\"children\":\"Test accuracy\"},{\"type\":\"Text\",\"props\":{\"fw\":700,\"size\":\"lg\"},\"children\":\"99%\"},{\"type\":\"Text\",\"props\":{\"size\":\"xs\",\"c\":\"dimmed\"},\"children\":\"sensitivity & specificity\"}]},{\"type\":\"Card\",\"props\":{\"withBorder\":true,\"padding\":\"sm\",\"radius\":\"md\"},\"children\":[{\"type\":\"Text\",\"props\":{\"size\":\"xs\",\"c\":\"dimmed\"},\"children\":\"Chance you're sick given +\"},{\"type\":\"Text\",\"props\":{\"fw\":700,\"size\":\"lg\"},\"children\":\"≈ 50%\"},{\"type\":\"Text\",\"props\":{\"size\":\"xs\",\"c\":\"dimmed\"},\"children\":\"the posterior\"}]}]},{\"type\":\"Alert\",\"props\":{\"color\":\"blue\",\"variant\":\"light\",\"title\":\"P(sick | +) = P(+ | sick)·P(sick) / P(+)\"},\"children\":\"= (0.99 × 0.01) / (0.99×0.01 + 0.01×0.99) = 0.0099 / 0.0198 = 50%. Rarer disease ⇒ even lower. This is exactly why doctors confirm a scary result with a second, independent test — each test multiplies the evidence.\"},{\"type\":\"Alert\",\"props\":{\"color\":\"green\",\"variant\":\"light\",\"title\":\"Misconception: 'accuracy' ≠ 'my probability'\"},\"children\":\"A test's accuracy describes the test, not you. Your probability also depends on how likely you were before testing (the base rate). Ignore the base rate and you'll badly over-react to rare-event positives — in medicine, security screening, and spam filters alike.\"}]}"}]} diff --git a/plugin/skills/diagram-recipes/examples/explainer-entropy.panes.json b/plugin/skills/diagram-recipes/examples/explainer-entropy.panes.json index 4040e40..c58aee5 100644 --- a/plugin/skills/diagram-recipes/examples/explainer-entropy.panes.json +++ b/plugin/skills/diagram-recipes/examples/explainer-entropy.panes.json @@ -1,25 +1 @@ -{ - "layout": "rows", - "panes": [ - { - "title": "Entropy · why disorder always wins", - "type": "markdown", - "content": "## Why does a shuffled deck never sort itself?\n\n**Spilled milk never un-spills; hot coffee never reheats from a cold room.** Nothing in physics\n*forbids* it — the molecules could, in principle, line back up.\n\nSo why doesn't it happen, ever? Not because disorder is a *force*. It's pure **counting**: there\nare overwhelmingly more messy arrangements than tidy ones, so a random nudge almost always lands\nyou in mess.\n\nBoltzmann made it exact — entropy just *counts* the microscopic arrangements $W$ of a state:\n\n$$S = k_B \\ln W$$\n\nMore ways to arrange $\\Rightarrow$ higher $S$, so $S$ almost always climbs." - }, - { - "title": "The argument, step by step", - "type": "flow", - "content": "{\"direction\":\"TB\",\"legend\":[{\"label\":\"premise\",\"color\":\"#60a5fa\"},{\"label\":\"reasoning\",\"color\":\"#fbbf24\"},{\"label\":\"conclusion\",\"color\":\"#22c55e\"}],\"nodes\":[{\"id\":\"p\",\"type\":\"change\",\"data\":{\"label\":\"A 'state' = countless microscopic arrangements\",\"kind\":\"premise\",\"status\":\"info\",\"icon\":\"info\",\"sub\":\"microstates: the exact position of every particle\"}},{\"id\":\"o\",\"type\":\"change\",\"data\":{\"label\":\"Messy states have vastly more microstates\",\"kind\":\"observe\",\"status\":\"active\",\"icon\":\"search\",\"sub\":\"one way to be sorted; astronomically many to be mixed\"}},{\"id\":\"e\",\"type\":\"change\",\"data\":{\"label\":\"Every microstate is equally likely\",\"kind\":\"premise\",\"status\":\"info\",\"icon\":\"dot\",\"sub\":\"physics plays no favorites\"}},{\"id\":\"th\",\"type\":\"change\",\"data\":{\"label\":\"So a random nudge almost always lands in mess\",\"kind\":\"therefore\",\"status\":\"active\",\"icon\":\"bolt\",\"sub\":\"more microstates ⇒ overwhelmingly more probable\"}},{\"id\":\"r\",\"type\":\"change\",\"data\":{\"label\":\"Entropy increases\",\"kind\":\"result\",\"status\":\"done\",\"icon\":\"check\",\"sub\":\"the 2nd law of thermodynamics\"}}],\"edges\":[{\"id\":\"e1\",\"source\":\"p\",\"target\":\"o\",\"markerEnd\":{\"type\":\"arrowclosed\",\"color\":\"#60a5fa\"},\"style\":{\"stroke\":\"#60a5fa\"}},{\"id\":\"e2\",\"source\":\"e\",\"target\":\"th\",\"markerEnd\":{\"type\":\"arrowclosed\",\"color\":\"#60a5fa\"},\"style\":{\"stroke\":\"#60a5fa\"}},{\"id\":\"e3\",\"source\":\"o\",\"target\":\"th\",\"markerEnd\":{\"type\":\"arrowclosed\",\"color\":\"#fbbf24\"},\"style\":{\"stroke\":\"#fbbf24\"}},{\"id\":\"e4\",\"source\":\"th\",\"target\":\"r\",\"style\":{\"stroke\":\"#22c55e\",\"strokeWidth\":2},\"markerEnd\":{\"type\":\"arrowclosed\",\"color\":\"#22c55e\"}}]}" - }, - { - "title": "The quantitative heart", - "type": "vegalite", - "content": "{\"title\":{\"text\":\"Ways to arrange vs. how ordered it is\",\"subtitle\":\"flip 20 coins: the count of arrangements explodes toward the 50/50 (most-mixed) middle — perfect order (all heads/tails) is the rarest\"},\"width\":\"container\",\"height\":230,\"data\":{\"values\":[{\"heads\":0,\"ways\":1},{\"heads\":1,\"ways\":20},{\"heads\":2,\"ways\":190},{\"heads\":3,\"ways\":1140},{\"heads\":4,\"ways\":4845},{\"heads\":5,\"ways\":15504},{\"heads\":6,\"ways\":38760},{\"heads\":7,\"ways\":77520},{\"heads\":8,\"ways\":125970},{\"heads\":9,\"ways\":167960},{\"heads\":10,\"ways\":184756},{\"heads\":11,\"ways\":167960},{\"heads\":12,\"ways\":125970},{\"heads\":13,\"ways\":77520},{\"heads\":14,\"ways\":38760},{\"heads\":15,\"ways\":15504},{\"heads\":16,\"ways\":4845},{\"heads\":17,\"ways\":1140},{\"heads\":18,\"ways\":190},{\"heads\":19,\"ways\":20},{\"heads\":20,\"ways\":1}]},\"mark\":{\"type\":\"bar\",\"tooltip\":true},\"encoding\":{\"x\":{\"field\":\"heads\",\"type\":\"ordinal\",\"title\":\"# heads of 20 (0 or 20 = perfectly ordered)\"},\"y\":{\"field\":\"ways\",\"type\":\"quantitative\",\"title\":\"number of arrangements (microstates)\"}}}" - }, - { - "title": "Real numbers, real proof", - "type": "component", - "content": "{\"type\":\"Stack\",\"props\":{\"gap\":\"sm\"},\"children\":[{\"type\":\"Title\",\"props\":{\"order\":5},\"children\":\"Put numbers on it\"},{\"type\":\"SimpleGrid\",\"props\":{\"cols\":3,\"spacing\":\"sm\"},\"children\":[{\"type\":\"Card\",\"props\":{\"withBorder\":true,\"padding\":\"sm\",\"radius\":\"md\"},\"children\":[{\"type\":\"Text\",\"props\":{\"size\":\"xs\",\"c\":\"dimmed\"},\"children\":\"Sorted deck\"},{\"type\":\"Text\",\"props\":{\"fw\":700,\"size\":\"lg\"},\"children\":\"1 way\"},{\"type\":\"Text\",\"props\":{\"size\":\"xs\",\"c\":\"dimmed\"},\"children\":\"only one tidy order\"}]},{\"type\":\"Card\",\"props\":{\"withBorder\":true,\"padding\":\"sm\",\"radius\":\"md\"},\"children\":[{\"type\":\"Text\",\"props\":{\"size\":\"xs\",\"c\":\"dimmed\"},\"children\":\"Shuffled deck\"},{\"type\":\"Text\",\"props\":{\"fw\":700,\"size\":\"lg\"},\"children\":\"52! ≈ 8×10⁶⁷\"},{\"type\":\"Text\",\"props\":{\"size\":\"xs\",\"c\":\"dimmed\"},\"children\":\"more than atoms on Earth\"}]},{\"type\":\"Card\",\"props\":{\"withBorder\":true,\"padding\":\"sm\",\"radius\":\"md\"},\"children\":[{\"type\":\"Text\",\"props\":{\"size\":\"xs\",\"c\":\"dimmed\"},\"children\":\"Re-sort by chance\"},{\"type\":\"Text\",\"props\":{\"fw\":700,\"size\":\"lg\"},\"children\":\"≈ 0\"},{\"type\":\"Text\",\"props\":{\"size\":\"xs\",\"c\":\"dimmed\"},\"children\":\"never, in the age of the universe\"}]}]},{\"type\":\"Alert\",\"props\":{\"color\":\"blue\",\"variant\":\"light\",\"title\":\"The same law sets the fate of the universe\"},\"children\":\"Stars burn, heat spreads, gradients smooth out. Taken to the limit, the universe trends toward 'heat death' — energy evenly smeared, no differences left to do work. Same counting argument, cosmic scale.\"},{\"type\":\"Alert\",\"props\":{\"color\":\"green\",\"variant\":\"light\",\"title\":\"Misconception: entropy isn't a force\"},\"children\":\"Nothing pushes things toward disorder — disorder simply has far more ways to happen, so randomness lands there. You CAN build local order (a fridge, a living cell), but only by dumping even more entropy into the surroundings.\"}]}" - } - ] -} \ No newline at end of file +{"layout":"rows","panes":[{"title":"Entropy · why disorder always wins","type":"markdown","content":"## Why does a shuffled deck never sort itself?\n\n**Spilled milk never un-spills; hot coffee never reheats from a cold room.** Nothing in physics\n*forbids* it — the molecules could, in principle, line back up.\n\nSo why doesn't it happen, ever? Not because disorder is a *force*. It's pure **counting**: there\nare overwhelmingly more messy arrangements than tidy ones, so a random nudge almost always lands\nyou in mess.\n\nBoltzmann made it exact — entropy just *counts* the microscopic arrangements $W$ of a state:\n\n$$S = k_B \\ln W$$\n\nMore ways to arrange $\\Rightarrow$ higher $S$, so $S$ almost always climbs."},{"title":"The argument, step by step","type":"flow","content":"{\"direction\":\"TB\",\"legend\":[{\"label\":\"premise\",\"color\":\"#60a5fa\"},{\"label\":\"reasoning\",\"color\":\"#fbbf24\"},{\"label\":\"conclusion\",\"color\":\"#22c55e\"}],\"nodes\":[{\"id\":\"p\",\"type\":\"change\",\"data\":{\"label\":\"A 'state' = countless microscopic arrangements\",\"kind\":\"premise\",\"status\":\"info\",\"icon\":\"info\",\"sub\":\"microstates: the exact position of every particle\"}},{\"id\":\"o\",\"type\":\"change\",\"data\":{\"label\":\"Messy states have vastly more microstates\",\"kind\":\"observe\",\"status\":\"active\",\"icon\":\"search\",\"sub\":\"one way to be sorted; astronomically many to be mixed\"}},{\"id\":\"e\",\"type\":\"change\",\"data\":{\"label\":\"Every microstate is equally likely\",\"kind\":\"premise\",\"status\":\"info\",\"icon\":\"dot\",\"sub\":\"physics plays no favorites\"}},{\"id\":\"th\",\"type\":\"change\",\"data\":{\"label\":\"So a random nudge almost always lands in mess\",\"kind\":\"therefore\",\"status\":\"active\",\"icon\":\"bolt\",\"sub\":\"more microstates ⇒ overwhelmingly more probable\"}},{\"id\":\"r\",\"type\":\"change\",\"data\":{\"label\":\"Entropy increases\",\"kind\":\"result\",\"status\":\"done\",\"icon\":\"check\",\"sub\":\"the 2nd law of thermodynamics\"}}],\"edges\":[{\"id\":\"e1\",\"source\":\"p\",\"target\":\"o\",\"markerEnd\":{\"type\":\"arrowclosed\",\"color\":\"#60a5fa\"},\"style\":{\"stroke\":\"#60a5fa\"}},{\"id\":\"e2\",\"source\":\"e\",\"target\":\"th\",\"markerEnd\":{\"type\":\"arrowclosed\",\"color\":\"#60a5fa\"},\"style\":{\"stroke\":\"#60a5fa\"}},{\"id\":\"e3\",\"source\":\"o\",\"target\":\"th\",\"markerEnd\":{\"type\":\"arrowclosed\",\"color\":\"#fbbf24\"},\"style\":{\"stroke\":\"#fbbf24\"}},{\"id\":\"e4\",\"source\":\"th\",\"target\":\"r\",\"style\":{\"stroke\":\"#22c55e\",\"strokeWidth\":2},\"markerEnd\":{\"type\":\"arrowclosed\",\"color\":\"#22c55e\"}}]}"},{"title":"The quantitative heart","type":"vegalite","content":"{\"title\":{\"text\":\"Ways to arrange vs. how ordered it is\",\"subtitle\":\"flip 20 coins: the count of arrangements explodes toward the 50/50 (most-mixed) middle — perfect order (all heads/tails) is the rarest\"},\"width\":\"container\",\"height\":230,\"data\":{\"values\":[{\"heads\":0,\"ways\":1},{\"heads\":1,\"ways\":20},{\"heads\":2,\"ways\":190},{\"heads\":3,\"ways\":1140},{\"heads\":4,\"ways\":4845},{\"heads\":5,\"ways\":15504},{\"heads\":6,\"ways\":38760},{\"heads\":7,\"ways\":77520},{\"heads\":8,\"ways\":125970},{\"heads\":9,\"ways\":167960},{\"heads\":10,\"ways\":184756},{\"heads\":11,\"ways\":167960},{\"heads\":12,\"ways\":125970},{\"heads\":13,\"ways\":77520},{\"heads\":14,\"ways\":38760},{\"heads\":15,\"ways\":15504},{\"heads\":16,\"ways\":4845},{\"heads\":17,\"ways\":1140},{\"heads\":18,\"ways\":190},{\"heads\":19,\"ways\":20},{\"heads\":20,\"ways\":1}]},\"mark\":{\"type\":\"bar\",\"tooltip\":true},\"encoding\":{\"x\":{\"field\":\"heads\",\"type\":\"ordinal\",\"title\":\"# heads of 20 (0 or 20 = perfectly ordered)\"},\"y\":{\"field\":\"ways\",\"type\":\"quantitative\",\"title\":\"number of arrangements (microstates)\"}}}"},{"title":"Real numbers, real proof","type":"component","content":"{\"type\":\"Stack\",\"props\":{\"gap\":\"sm\"},\"children\":[{\"type\":\"Title\",\"props\":{\"order\":5},\"children\":\"Put numbers on it\"},{\"type\":\"SimpleGrid\",\"props\":{\"cols\":3,\"spacing\":\"sm\"},\"children\":[{\"type\":\"Card\",\"props\":{\"withBorder\":true,\"padding\":\"sm\",\"radius\":\"md\"},\"children\":[{\"type\":\"Text\",\"props\":{\"size\":\"xs\",\"c\":\"dimmed\"},\"children\":\"Sorted deck\"},{\"type\":\"Text\",\"props\":{\"fw\":700,\"size\":\"lg\"},\"children\":\"1 way\"},{\"type\":\"Text\",\"props\":{\"size\":\"xs\",\"c\":\"dimmed\"},\"children\":\"only one tidy order\"}]},{\"type\":\"Card\",\"props\":{\"withBorder\":true,\"padding\":\"sm\",\"radius\":\"md\"},\"children\":[{\"type\":\"Text\",\"props\":{\"size\":\"xs\",\"c\":\"dimmed\"},\"children\":\"Shuffled deck\"},{\"type\":\"Text\",\"props\":{\"fw\":700,\"size\":\"lg\"},\"children\":\"52! ≈ 8×10⁶⁷\"},{\"type\":\"Text\",\"props\":{\"size\":\"xs\",\"c\":\"dimmed\"},\"children\":\"more than atoms on Earth\"}]},{\"type\":\"Card\",\"props\":{\"withBorder\":true,\"padding\":\"sm\",\"radius\":\"md\"},\"children\":[{\"type\":\"Text\",\"props\":{\"size\":\"xs\",\"c\":\"dimmed\"},\"children\":\"Re-sort by chance\"},{\"type\":\"Text\",\"props\":{\"fw\":700,\"size\":\"lg\"},\"children\":\"≈ 0\"},{\"type\":\"Text\",\"props\":{\"size\":\"xs\",\"c\":\"dimmed\"},\"children\":\"never, in the age of the universe\"}]}]},{\"type\":\"Alert\",\"props\":{\"color\":\"blue\",\"variant\":\"light\",\"title\":\"The same law sets the fate of the universe\"},\"children\":\"Stars burn, heat spreads, gradients smooth out. Taken to the limit, the universe trends toward 'heat death' — energy evenly smeared, no differences left to do work. Same counting argument, cosmic scale.\"},{\"type\":\"Alert\",\"props\":{\"color\":\"green\",\"variant\":\"light\",\"title\":\"Misconception: entropy isn't a force\"},\"children\":\"Nothing pushes things toward disorder — disorder simply has far more ways to happen, so randomness lands there. You CAN build local order (a fridge, a living cell), but only by dumping even more entropy into the surroundings.\"}]}"}]} diff --git a/plugin/skills/diagram-recipes/examples/explainer-relativity.panes.json b/plugin/skills/diagram-recipes/examples/explainer-relativity.panes.json index fab1f1e..945f121 100644 --- a/plugin/skills/diagram-recipes/examples/explainer-relativity.panes.json +++ b/plugin/skills/diagram-recipes/examples/explainer-relativity.panes.json @@ -1,25 +1 @@ -{ - "layout": "rows", - "panes": [ - { - "title": "Special relativity · time dilation", - "type": "markdown", - "content": "## Why do moving clocks run slow?\n\n**One fact breaks our intuition about time:** light travels at the same speed $c$ for\n*everyone* — however fast you're moving. Chase a light beam at 99% of light speed and it\nstill races away from you at the full $c$.\n\nThat single rule forces time itself to stretch by the **Lorentz factor**:\n\n$$\\gamma = \\frac{1}{\\sqrt{1 - v^2/c^2}}$$\n\nA clock moving at speed $v$ ticks $\\gamma\\times$ slower than one at rest. Below: the intuition in four steps, then how fast $\\gamma$ blows up as $v \\to c$." - }, - { - "title": "The argument, step by step", - "type": "flow", - "content": "{\"direction\":\"TB\",\"legend\":[{\"label\":\"postulate\",\"color\":\"#60a5fa\"},{\"label\":\"reasoning\",\"color\":\"#fbbf24\"},{\"label\":\"conclusion\",\"color\":\"#22c55e\"}],\"nodes\":[{\"id\":\"p\",\"type\":\"change\",\"data\":{\"label\":\"Light speed = c\",\"kind\":\"postulate\",\"status\":\"info\",\"icon\":\"info\",\"sub\":\"identical for every observer\"}},{\"id\":\"o\",\"type\":\"change\",\"data\":{\"label\":\"A passing clock's light takes a diagonal path\",\"kind\":\"observe\",\"status\":\"active\",\"icon\":\"search\",\"sub\":\"longer than straight up-and-down (a 'light clock')\"}},{\"id\":\"c\",\"type\":\"change\",\"data\":{\"label\":\"Speed can't rise to cover the extra distance\",\"kind\":\"constraint\",\"status\":\"active\",\"icon\":\"bolt\",\"sub\":\"c is fixed — step 1\"}},{\"id\":\"t\",\"type\":\"change\",\"data\":{\"label\":\"So each tick must take more time\",\"kind\":\"therefore\",\"status\":\"active\",\"icon\":\"clock\",\"sub\":\"distance ↑, speed fixed ⇒ time ↑\"}},{\"id\":\"r\",\"type\":\"change\",\"data\":{\"label\":\"Moving clocks run slow\",\"kind\":\"result\",\"status\":\"done\",\"icon\":\"check\",\"sub\":\"time dilation\"}}],\"edges\":[{\"id\":\"e1\",\"source\":\"p\",\"target\":\"o\",\"markerEnd\":{\"type\":\"arrowclosed\",\"color\":\"#60a5fa\"},\"style\":{\"stroke\":\"#60a5fa\"}},{\"id\":\"e2\",\"source\":\"o\",\"target\":\"c\",\"markerEnd\":{\"type\":\"arrowclosed\",\"color\":\"#fbbf24\"},\"style\":{\"stroke\":\"#fbbf24\"}},{\"id\":\"e3\",\"source\":\"c\",\"target\":\"t\",\"markerEnd\":{\"type\":\"arrowclosed\",\"color\":\"#fbbf24\"},\"style\":{\"stroke\":\"#fbbf24\"}},{\"id\":\"e4\",\"source\":\"t\",\"target\":\"r\",\"style\":{\"stroke\":\"#22c55e\",\"strokeWidth\":2},\"markerEnd\":{\"type\":\"arrowclosed\",\"color\":\"#22c55e\"}}]}" - }, - { - "title": "The quantitative heart", - "type": "vegalite", - "content": "{\"title\":{\"text\":\"How much slower? The Lorentz factor γ\",\"subtitle\":\"γ = time-stretch factor = 1 / √(1 − v²/c²) · at 0.87c a clock runs 2× slow; γ → ∞ as v → c\"},\"width\":\"container\",\"height\":240,\"data\":{\"values\":[{\"v\":0,\"gamma\":1},{\"v\":0.02,\"gamma\":1.000200060020007},{\"v\":0.04,\"gamma\":1.0008009612817945},{\"v\":0.06,\"gamma\":1.0018048746260764},{\"v\":0.08,\"gamma\":1.00321544238141},{\"v\":0.1,\"gamma\":1.005037815259212},{\"v\":0.12,\"gamma\":1.0072787050317253},{\"v\":0.14,\"gamma\":1.009946454058471},{\"v\":0.16,\"gamma\":1.0130511230913846},{\"v\":0.18,\"gamma\":1.0166045992028059},{\"v\":0.2,\"gamma\":1.0206207261596576},{\"v\":0.22,\"gamma\":1.025115460130912},{\"v\":0.24,\"gamma\":1.0301070542879114},{\"v\":0.26,\"gamma\":1.0356162766686667},{\"v\":0.28,\"gamma\":1.0416666666666667},{\"v\":0.3,\"gamma\":1.0482848367219182},{\"v\":0.32,\"gamma\":1.0555008273018727},{\"v\":0.34,\"gamma\":1.0633485251477837},{\"v\":0.36,\"gamma\":1.0718661571406802},{\"v\":0.38,\"gamma\":1.0810968751610264},{\"v\":0.4,\"gamma\":1.0910894511799618},{\"v\":0.42,\"gamma\":1.1018991068031498},{\"v\":0.44,\"gamma\":1.113588507968435},{\"v\":0.46,\"gamma\":1.1262289639991432},{\"v\":0.48,\"gamma\":1.139901881468883},{\"v\":0.5,\"gamma\":1.1547005383792517},{\"v\":0.52,\"gamma\":1.1707322644771174},{\"v\":0.54,\"gamma\":1.1881211413043937},{\"v\":0.56,\"gamma\":1.2070113739631692},{\"v\":0.58,\"gamma\":1.2275715403505907},{\"v\":0.6,\"gamma\":1.25},{\"v\":0.62,\"gamma\":1.2745318548364544},{\"v\":0.64,\"gamma\":1.3014480157383839},{\"v\":0.66,\"gamma\":1.331087170162505},{\"v\":0.68,\"gamma\":1.3638618139749525},{\"v\":0.7,\"gamma\":1.4002800840280099},{\"v\":0.72,\"gamma\":1.4409760442605875},{\"v\":0.74,\"gamma\":1.4867525836251314},{\"v\":0.76,\"gamma\":1.538643637241659},{\"v\":0.78,\"gamma\":1.5980069302514828},{\"v\":0.8,\"gamma\":1.666666666666667},{\"v\":0.82,\"gamma\":1.7471413945365302},{\"v\":0.84,\"gamma\":1.8430244519362138},{\"v\":0.86,\"gamma\":1.9596545041740512},{\"v\":0.88,\"gamma\":2.1053798026662975},{\"v\":0.9,\"gamma\":2.294157338705618},{\"v\":0.92,\"gamma\":2.551551815399144},{\"v\":0.94,\"gamma\":2.931051908802745},{\"v\":0.96,\"gamma\":3.571428571428571},{\"v\":0.98,\"gamma\":5.025189076296055},{\"v\":0.99,\"gamma\":7.088812050083354}]},\"mark\":{\"type\":\"line\",\"interpolate\":\"monotone\",\"point\":false,\"strokeWidth\":2.5},\"encoding\":{\"x\":{\"field\":\"v\",\"type\":\"quantitative\",\"title\":\"speed (fraction of light speed, v/c)\",\"axis\":{\"format\":\"%\"}},\"y\":{\"field\":\"gamma\",\"type\":\"quantitative\",\"title\":\"γ (clock slow-down)\",\"scale\":{\"domain\":[1,7]}},\"tooltip\":[{\"field\":\"v\",\"type\":\"quantitative\",\"title\":\"v/c\",\"format\":\".0%\"},{\"field\":\"gamma\",\"type\":\"quantitative\",\"title\":\"γ\",\"format\":\".2f\"}]}}" - }, - { - "title": "Real numbers, real proof", - "type": "component", - "content": "{\"type\":\"Stack\",\"props\":{\"gap\":\"sm\"},\"children\":[{\"type\":\"Title\",\"props\":{\"order\":5},\"children\":\"Put numbers on it\"},{\"type\":\"SimpleGrid\",\"props\":{\"cols\":3,\"spacing\":\"sm\"},\"children\":[{\"type\":\"Card\",\"props\":{\"withBorder\":true,\"padding\":\"sm\",\"radius\":\"md\"},\"children\":[{\"type\":\"Text\",\"props\":{\"size\":\"xs\",\"c\":\"dimmed\"},\"children\":\"10% of c\"},{\"type\":\"Text\",\"props\":{\"fw\":700,\"size\":\"lg\"},\"children\":\"γ ≈ 1.005\"},{\"type\":\"Text\",\"props\":{\"size\":\"xs\",\"c\":\"dimmed\"},\"children\":\"barely any effect\"}]},{\"type\":\"Card\",\"props\":{\"withBorder\":true,\"padding\":\"sm\",\"radius\":\"md\"},\"children\":[{\"type\":\"Text\",\"props\":{\"size\":\"xs\",\"c\":\"dimmed\"},\"children\":\"87% of c\"},{\"type\":\"Text\",\"props\":{\"fw\":700,\"size\":\"lg\"},\"children\":\"γ = 2\"},{\"type\":\"Text\",\"props\":{\"size\":\"xs\",\"c\":\"dimmed\"},\"children\":\"time runs half as fast\"}]},{\"type\":\"Card\",\"props\":{\"withBorder\":true,\"padding\":\"sm\",\"radius\":\"md\"},\"children\":[{\"type\":\"Text\",\"props\":{\"size\":\"xs\",\"c\":\"dimmed\"},\"children\":\"99% of c\"},{\"type\":\"Text\",\"props\":{\"fw\":700,\"size\":\"lg\"},\"children\":\"γ ≈ 7\"},{\"type\":\"Text\",\"props\":{\"size\":\"xs\",\"c\":\"dimmed\"},\"children\":\"1 yr aboard = 7 yrs home\"}]}]},{\"type\":\"Alert\",\"props\":{\"color\":\"blue\",\"variant\":\"light\",\"title\":\"This isn't abstract — your phone depends on it\"},\"children\":\"GPS satellites move fast enough that, uncorrected, the relativistic clock error would push your location off by ~10 km within a day. Atomic clocks flown on airplanes return measurably behind the ones left home — exactly as the math predicts.\"},{\"type\":\"Alert\",\"props\":{\"color\":\"green\",\"variant\":\"light\",\"title\":\"It's every clock, not just light clocks\"},\"children\":\"Nothing here was special about photons. Atomic, mechanical, and biological clocks all slow by the same γ — so a traveler at 0.87c really would age half as fast. The light clock was just the easiest way to see why.\"}]}" - } - ] -} +{"layout":"rows","panes":[{"title":"Special relativity · time dilation","type":"markdown","content":"## Why do moving clocks run slow?\n\n**One fact breaks our intuition about time:** light travels at the same speed $c$ for\n*everyone* — however fast you're moving. Chase a light beam at 99% of light speed and it\nstill races away from you at the full $c$.\n\nThat single rule forces time itself to stretch by the **Lorentz factor**:\n\n$$\\gamma = \\frac{1}{\\sqrt{1 - v^2/c^2}}$$\n\nA clock moving at speed $v$ ticks $\\gamma\\times$ slower than one at rest. Below: the intuition in four steps, then how fast $\\gamma$ blows up as $v \\to c$."},{"title":"The argument, step by step","type":"flow","content":"{\"direction\":\"TB\",\"legend\":[{\"label\":\"postulate\",\"color\":\"#60a5fa\"},{\"label\":\"reasoning\",\"color\":\"#fbbf24\"},{\"label\":\"conclusion\",\"color\":\"#22c55e\"}],\"nodes\":[{\"id\":\"p\",\"type\":\"change\",\"data\":{\"label\":\"Light speed = c\",\"kind\":\"postulate\",\"status\":\"info\",\"icon\":\"info\",\"sub\":\"identical for every observer\"}},{\"id\":\"o\",\"type\":\"change\",\"data\":{\"label\":\"A passing clock's light takes a diagonal path\",\"kind\":\"observe\",\"status\":\"active\",\"icon\":\"search\",\"sub\":\"longer than straight up-and-down (a 'light clock')\"}},{\"id\":\"c\",\"type\":\"change\",\"data\":{\"label\":\"Speed can't rise to cover the extra distance\",\"kind\":\"constraint\",\"status\":\"active\",\"icon\":\"bolt\",\"sub\":\"c is fixed — step 1\"}},{\"id\":\"t\",\"type\":\"change\",\"data\":{\"label\":\"So each tick must take more time\",\"kind\":\"therefore\",\"status\":\"active\",\"icon\":\"clock\",\"sub\":\"distance ↑, speed fixed ⇒ time ↑\"}},{\"id\":\"r\",\"type\":\"change\",\"data\":{\"label\":\"Moving clocks run slow\",\"kind\":\"result\",\"status\":\"done\",\"icon\":\"check\",\"sub\":\"time dilation\"}}],\"edges\":[{\"id\":\"e1\",\"source\":\"p\",\"target\":\"o\",\"markerEnd\":{\"type\":\"arrowclosed\",\"color\":\"#60a5fa\"},\"style\":{\"stroke\":\"#60a5fa\"}},{\"id\":\"e2\",\"source\":\"o\",\"target\":\"c\",\"markerEnd\":{\"type\":\"arrowclosed\",\"color\":\"#fbbf24\"},\"style\":{\"stroke\":\"#fbbf24\"}},{\"id\":\"e3\",\"source\":\"c\",\"target\":\"t\",\"markerEnd\":{\"type\":\"arrowclosed\",\"color\":\"#fbbf24\"},\"style\":{\"stroke\":\"#fbbf24\"}},{\"id\":\"e4\",\"source\":\"t\",\"target\":\"r\",\"style\":{\"stroke\":\"#22c55e\",\"strokeWidth\":2},\"markerEnd\":{\"type\":\"arrowclosed\",\"color\":\"#22c55e\"}}]}"},{"title":"The quantitative heart","type":"vegalite","content":"{\"title\":{\"text\":\"How much slower? The Lorentz factor γ\",\"subtitle\":\"γ = time-stretch factor = 1 / √(1 − v²/c²) · at 0.87c a clock runs 2× slow; γ → ∞ as v → c\"},\"width\":\"container\",\"height\":240,\"data\":{\"values\":[{\"v\":0,\"gamma\":1},{\"v\":0.02,\"gamma\":1.000200060020007},{\"v\":0.04,\"gamma\":1.0008009612817945},{\"v\":0.06,\"gamma\":1.0018048746260764},{\"v\":0.08,\"gamma\":1.00321544238141},{\"v\":0.1,\"gamma\":1.005037815259212},{\"v\":0.12,\"gamma\":1.0072787050317253},{\"v\":0.14,\"gamma\":1.009946454058471},{\"v\":0.16,\"gamma\":1.0130511230913846},{\"v\":0.18,\"gamma\":1.0166045992028059},{\"v\":0.2,\"gamma\":1.0206207261596576},{\"v\":0.22,\"gamma\":1.025115460130912},{\"v\":0.24,\"gamma\":1.0301070542879114},{\"v\":0.26,\"gamma\":1.0356162766686667},{\"v\":0.28,\"gamma\":1.0416666666666667},{\"v\":0.3,\"gamma\":1.0482848367219182},{\"v\":0.32,\"gamma\":1.0555008273018727},{\"v\":0.34,\"gamma\":1.0633485251477837},{\"v\":0.36,\"gamma\":1.0718661571406802},{\"v\":0.38,\"gamma\":1.0810968751610264},{\"v\":0.4,\"gamma\":1.0910894511799618},{\"v\":0.42,\"gamma\":1.1018991068031498},{\"v\":0.44,\"gamma\":1.113588507968435},{\"v\":0.46,\"gamma\":1.1262289639991432},{\"v\":0.48,\"gamma\":1.139901881468883},{\"v\":0.5,\"gamma\":1.1547005383792517},{\"v\":0.52,\"gamma\":1.1707322644771174},{\"v\":0.54,\"gamma\":1.1881211413043937},{\"v\":0.56,\"gamma\":1.2070113739631692},{\"v\":0.58,\"gamma\":1.2275715403505907},{\"v\":0.6,\"gamma\":1.25},{\"v\":0.62,\"gamma\":1.2745318548364544},{\"v\":0.64,\"gamma\":1.3014480157383839},{\"v\":0.66,\"gamma\":1.331087170162505},{\"v\":0.68,\"gamma\":1.3638618139749525},{\"v\":0.7,\"gamma\":1.4002800840280099},{\"v\":0.72,\"gamma\":1.4409760442605875},{\"v\":0.74,\"gamma\":1.4867525836251314},{\"v\":0.76,\"gamma\":1.538643637241659},{\"v\":0.78,\"gamma\":1.5980069302514828},{\"v\":0.8,\"gamma\":1.666666666666667},{\"v\":0.82,\"gamma\":1.7471413945365302},{\"v\":0.84,\"gamma\":1.8430244519362138},{\"v\":0.86,\"gamma\":1.9596545041740512},{\"v\":0.88,\"gamma\":2.1053798026662975},{\"v\":0.9,\"gamma\":2.294157338705618},{\"v\":0.92,\"gamma\":2.551551815399144},{\"v\":0.94,\"gamma\":2.931051908802745},{\"v\":0.96,\"gamma\":3.571428571428571},{\"v\":0.98,\"gamma\":5.025189076296055},{\"v\":0.99,\"gamma\":7.088812050083354}]},\"mark\":{\"type\":\"line\",\"interpolate\":\"monotone\",\"point\":false,\"strokeWidth\":2.5},\"encoding\":{\"x\":{\"field\":\"v\",\"type\":\"quantitative\",\"title\":\"speed (fraction of light speed, v/c)\",\"axis\":{\"format\":\"%\"}},\"y\":{\"field\":\"gamma\",\"type\":\"quantitative\",\"title\":\"γ (clock slow-down)\",\"scale\":{\"domain\":[1,7]}},\"tooltip\":[{\"field\":\"v\",\"type\":\"quantitative\",\"title\":\"v/c\",\"format\":\".0%\"},{\"field\":\"gamma\",\"type\":\"quantitative\",\"title\":\"γ\",\"format\":\".2f\"}]}}"},{"title":"Real numbers, real proof","type":"component","content":"{\"type\":\"Stack\",\"props\":{\"gap\":\"sm\"},\"children\":[{\"type\":\"Title\",\"props\":{\"order\":5},\"children\":\"Put numbers on it\"},{\"type\":\"SimpleGrid\",\"props\":{\"cols\":3,\"spacing\":\"sm\"},\"children\":[{\"type\":\"Card\",\"props\":{\"withBorder\":true,\"padding\":\"sm\",\"radius\":\"md\"},\"children\":[{\"type\":\"Text\",\"props\":{\"size\":\"xs\",\"c\":\"dimmed\"},\"children\":\"10% of c\"},{\"type\":\"Text\",\"props\":{\"fw\":700,\"size\":\"lg\"},\"children\":\"γ ≈ 1.005\"},{\"type\":\"Text\",\"props\":{\"size\":\"xs\",\"c\":\"dimmed\"},\"children\":\"barely any effect\"}]},{\"type\":\"Card\",\"props\":{\"withBorder\":true,\"padding\":\"sm\",\"radius\":\"md\"},\"children\":[{\"type\":\"Text\",\"props\":{\"size\":\"xs\",\"c\":\"dimmed\"},\"children\":\"87% of c\"},{\"type\":\"Text\",\"props\":{\"fw\":700,\"size\":\"lg\"},\"children\":\"γ = 2\"},{\"type\":\"Text\",\"props\":{\"size\":\"xs\",\"c\":\"dimmed\"},\"children\":\"time runs half as fast\"}]},{\"type\":\"Card\",\"props\":{\"withBorder\":true,\"padding\":\"sm\",\"radius\":\"md\"},\"children\":[{\"type\":\"Text\",\"props\":{\"size\":\"xs\",\"c\":\"dimmed\"},\"children\":\"99% of c\"},{\"type\":\"Text\",\"props\":{\"fw\":700,\"size\":\"lg\"},\"children\":\"γ ≈ 7\"},{\"type\":\"Text\",\"props\":{\"size\":\"xs\",\"c\":\"dimmed\"},\"children\":\"1 yr aboard = 7 yrs home\"}]}]},{\"type\":\"Alert\",\"props\":{\"color\":\"blue\",\"variant\":\"light\",\"title\":\"This isn't abstract — your phone depends on it\"},\"children\":\"GPS satellites move fast enough that, uncorrected, the relativistic clock error would push your location off by ~10 km within a day. Atomic clocks flown on airplanes return measurably behind the ones left home — exactly as the math predicts.\"},{\"type\":\"Alert\",\"props\":{\"color\":\"green\",\"variant\":\"light\",\"title\":\"It's every clock, not just light clocks\"},\"children\":\"Nothing here was special about photons. Atomic, mechanical, and biological clocks all slow by the same γ — so a traveler at 0.87c really would age half as fast. The light clock was just the easiest way to see why.\"}]}"}]} diff --git a/plugin/skills/diagram-recipes/examples/flame-graph.component.json b/plugin/skills/diagram-recipes/examples/flame-graph.component.json index bdceaf0..f92da9a 100644 --- a/plugin/skills/diagram-recipes/examples/flame-graph.component.json +++ b/plugin/skills/diagram-recipes/examples/flame-graph.component.json @@ -1,409 +1 @@ -{ - "type": "Stack", - "props": { - "gap": "sm", - "p": "md" - }, - "children": [ - { - "type": "Title", - "props": { - "order": 3 - }, - "children": "Flame graph — CPU profile (render path)" - }, - { - "type": "Text", - "props": { - "c": "dimmed", - "size": "sm", - "mt": -6 - }, - "children": "width = share of CPU samples (time); depth = call stack; warmer = more self-time" - }, - { - "type": "Box", - "props": { - "style": { - "position": "relative", - "height": 88, - "width": "100%" - } - }, - "children": [ - { - "type": "Box", - "props": { - "style": { - "position": "absolute", - "left": "0%", - "width": "calc(100% - 2px)", - "top": 0, - "height": 20, - "background": "rgb(250,201,20)", - "borderRadius": 3, - "border": "1px solid rgba(0,0,0,0.25)", - "overflow": "hidden", - "boxSizing": "border-box" - } - }, - "children": { - "type": "Text", - "props": { - "size": "xs", - "c": "rgba(0,0,0,0.8)", - "fw": 600, - "truncate": "end", - "style": { - "lineHeight": "20px", - "paddingLeft": 6, - "paddingRight": 4, - "pointerEvents": "none" - } - }, - "children": "main · 1200ms" - } - }, - { - "type": "Box", - "props": { - "style": { - "position": "absolute", - "left": "0%", - "width": "calc(18% - 2px)", - "top": 22, - "height": 20, - "background": "rgb(245,158,11)", - "borderRadius": 3, - "border": "1px solid rgba(0,0,0,0.25)", - "overflow": "hidden", - "boxSizing": "border-box" - } - }, - "children": { - "type": "Text", - "props": { - "size": "xs", - "c": "rgba(0,0,0,0.85)", - "fw": 600, - "truncate": "end", - "style": { - "lineHeight": "20px", - "paddingLeft": 6, - "paddingRight": 4, - "pointerEvents": "none" - } - }, - "children": "parse_config · 216ms" - } - }, - { - "type": "Box", - "props": { - "style": { - "position": "absolute", - "left": "18%", - "width": "calc(64% - 2px)", - "top": 22, - "height": 20, - "background": "rgb(249,192,18)", - "borderRadius": 3, - "border": "1px solid rgba(0,0,0,0.25)", - "overflow": "hidden", - "boxSizing": "border-box" - } - }, - "children": { - "type": "Text", - "props": { - "size": "xs", - "c": "rgba(0,0,0,0.85)", - "fw": 600, - "truncate": "end", - "style": { - "lineHeight": "20px", - "paddingLeft": 6, - "paddingRight": 4, - "pointerEvents": "none" - } - }, - "children": "render_frame · 768ms" - } - }, - { - "type": "Box", - "props": { - "style": { - "position": "absolute", - "left": "82%", - "width": "calc(18% - 2px)", - "top": 22, - "height": 20, - "background": "rgb(236,100,12)", - "borderRadius": 3, - "border": "1px solid rgba(0,0,0,0.25)", - "overflow": "hidden", - "boxSizing": "border-box" - } - }, - "children": { - "type": "Text", - "props": { - "size": "xs", - "c": "rgba(0,0,0,0.85)", - "fw": 600, - "truncate": "end", - "style": { - "lineHeight": "20px", - "paddingLeft": 6, - "paddingRight": 4, - "pointerEvents": "none" - } - }, - "children": "flush_io · 216ms" - } - }, - { - "type": "Box", - "props": { - "style": { - "position": "absolute", - "left": "18%", - "width": "calc(22% - 2px)", - "top": 44, - "height": 20, - "background": "rgb(247,173,14)", - "borderRadius": 3, - "border": "1px solid rgba(0,0,0,0.25)", - "overflow": "hidden", - "boxSizing": "border-box" - } - }, - "children": { - "type": "Text", - "props": { - "size": "xs", - "c": "rgba(0,0,0,0.85)", - "fw": 600, - "truncate": "end", - "style": { - "lineHeight": "20px", - "paddingLeft": 6, - "paddingRight": 4, - "pointerEvents": "none" - } - }, - "children": "layout · 264ms" - } - }, - { - "type": "Box", - "props": { - "style": { - "position": "absolute", - "left": "40%", - "width": "calc(30% - 2px)", - "top": 44, - "height": 20, - "background": "rgb(248,186,17)", - "borderRadius": 3, - "border": "1px solid rgba(0,0,0,0.25)", - "overflow": "hidden", - "boxSizing": "border-box" - } - }, - "children": { - "type": "Text", - "props": { - "size": "xs", - "c": "rgba(0,0,0,0.85)", - "fw": 600, - "truncate": "end", - "style": { - "lineHeight": "20px", - "paddingLeft": 6, - "paddingRight": 4, - "pointerEvents": "none" - } - }, - "children": "rasterize · 360ms" - } - }, - { - "type": "Box", - "props": { - "style": { - "position": "absolute", - "left": "70%", - "width": "calc(12% - 2px)", - "top": 44, - "height": 20, - "background": "rgb(234,88,12)", - "borderRadius": 3, - "border": "1px solid rgba(0,0,0,0.25)", - "overflow": "hidden", - "boxSizing": "border-box" - } - }, - "children": { - "type": "Text", - "props": { - "size": "xs", - "c": "rgba(0,0,0,0.85)", - "fw": 600, - "truncate": "end", - "style": { - "lineHeight": "20px", - "paddingLeft": 6, - "paddingRight": 4, - "pointerEvents": "none" - } - }, - "children": "composite · 144ms" - } - }, - { - "type": "Box", - "props": { - "style": { - "position": "absolute", - "left": "0%", - "width": "calc(10% - 2px)", - "top": 44, - "height": 20, - "background": "rgb(234,88,12)", - "borderRadius": 3, - "border": "1px solid rgba(0,0,0,0.25)", - "overflow": "hidden", - "boxSizing": "border-box" - } - }, - "children": { - "type": "Text", - "props": { - "size": "xs", - "c": "rgba(0,0,0,0.85)", - "fw": 600, - "truncate": "end", - "style": { - "lineHeight": "20px", - "paddingLeft": 6, - "paddingRight": 4, - "pointerEvents": "none" - } - }, - "children": "tokenize · 120ms" - } - }, - { - "type": "Box", - "props": { - "style": { - "position": "absolute", - "left": "40%", - "width": "calc(18% - 2px)", - "top": 66, - "height": 20, - "background": "rgb(234,88,12)", - "borderRadius": 3, - "border": "1px solid rgba(0,0,0,0.25)", - "overflow": "hidden", - "boxSizing": "border-box" - } - }, - "children": { - "type": "Text", - "props": { - "size": "xs", - "c": "rgba(0,0,0,0.85)", - "fw": 600, - "truncate": "end", - "style": { - "lineHeight": "20px", - "paddingLeft": 6, - "paddingRight": 4, - "pointerEvents": "none" - } - }, - "children": "shade_pixels · 216ms" - } - }, - { - "type": "Box", - "props": { - "style": { - "position": "absolute", - "left": "58%", - "width": "calc(12% - 2px)", - "top": 66, - "height": 20, - "background": "rgb(234,88,12)", - "borderRadius": 3, - "border": "1px solid rgba(0,0,0,0.25)", - "overflow": "hidden", - "boxSizing": "border-box" - } - }, - "children": { - "type": "Text", - "props": { - "size": "xs", - "c": "rgba(0,0,0,0.85)", - "fw": 600, - "truncate": "end", - "style": { - "lineHeight": "20px", - "paddingLeft": 6, - "paddingRight": 4, - "pointerEvents": "none" - } - }, - "children": "blend · 144ms" - } - }, - { - "type": "Box", - "props": { - "style": { - "position": "absolute", - "left": "18%", - "width": "calc(12% - 2px)", - "top": 66, - "height": 20, - "background": "rgb(234,88,12)", - "borderRadius": 3, - "border": "1px solid rgba(0,0,0,0.25)", - "overflow": "hidden", - "boxSizing": "border-box" - } - }, - "children": { - "type": "Text", - "props": { - "size": "xs", - "c": "rgba(0,0,0,0.85)", - "fw": 600, - "truncate": "end", - "style": { - "lineHeight": "20px", - "paddingLeft": 6, - "paddingRight": 4, - "pointerEvents": "none" - } - }, - "children": "measure_text · 144ms" - } - } - ] - }, - { - "type": "Text", - "props": { - "c": "dimmed", - "size": "xs", - "mt": 2 - }, - "children": "Widest frame = hottest path. composite · shade_pixels · flush_io carry the most self-time." - } - ] -} +{"type":"Stack","props":{"gap":"sm","p":"md"},"children":[{"type":"Title","props":{"order":3},"children":"Flame graph — CPU profile (render path)"},{"type":"Text","props":{"c":"dimmed","size":"sm","mt":-6},"children":"width = share of CPU samples (time); depth = call stack; warmer = more self-time"},{"type":"Box","props":{"style":{"position":"relative","height":88,"width":"100%"}},"children":[{"type":"Box","props":{"style":{"position":"absolute","left":"0%","width":"calc(100% - 2px)","top":0,"height":20,"background":"rgb(250,201,20)","borderRadius":3,"border":"1px solid rgba(0,0,0,0.25)","overflow":"hidden","boxSizing":"border-box"}},"children":{"type":"Text","props":{"size":"xs","c":"rgba(0,0,0,0.8)","fw":600,"truncate":"end","style":{"lineHeight":"20px","paddingLeft":6,"paddingRight":4,"pointerEvents":"none"}},"children":"main · 1200ms"}},{"type":"Box","props":{"style":{"position":"absolute","left":"0%","width":"calc(18% - 2px)","top":22,"height":20,"background":"rgb(245,158,11)","borderRadius":3,"border":"1px solid rgba(0,0,0,0.25)","overflow":"hidden","boxSizing":"border-box"}},"children":{"type":"Text","props":{"size":"xs","c":"rgba(0,0,0,0.85)","fw":600,"truncate":"end","style":{"lineHeight":"20px","paddingLeft":6,"paddingRight":4,"pointerEvents":"none"}},"children":"parse_config · 216ms"}},{"type":"Box","props":{"style":{"position":"absolute","left":"18%","width":"calc(64% - 2px)","top":22,"height":20,"background":"rgb(249,192,18)","borderRadius":3,"border":"1px solid rgba(0,0,0,0.25)","overflow":"hidden","boxSizing":"border-box"}},"children":{"type":"Text","props":{"size":"xs","c":"rgba(0,0,0,0.85)","fw":600,"truncate":"end","style":{"lineHeight":"20px","paddingLeft":6,"paddingRight":4,"pointerEvents":"none"}},"children":"render_frame · 768ms"}},{"type":"Box","props":{"style":{"position":"absolute","left":"82%","width":"calc(18% - 2px)","top":22,"height":20,"background":"rgb(236,100,12)","borderRadius":3,"border":"1px solid rgba(0,0,0,0.25)","overflow":"hidden","boxSizing":"border-box"}},"children":{"type":"Text","props":{"size":"xs","c":"rgba(0,0,0,0.85)","fw":600,"truncate":"end","style":{"lineHeight":"20px","paddingLeft":6,"paddingRight":4,"pointerEvents":"none"}},"children":"flush_io · 216ms"}},{"type":"Box","props":{"style":{"position":"absolute","left":"18%","width":"calc(22% - 2px)","top":44,"height":20,"background":"rgb(247,173,14)","borderRadius":3,"border":"1px solid rgba(0,0,0,0.25)","overflow":"hidden","boxSizing":"border-box"}},"children":{"type":"Text","props":{"size":"xs","c":"rgba(0,0,0,0.85)","fw":600,"truncate":"end","style":{"lineHeight":"20px","paddingLeft":6,"paddingRight":4,"pointerEvents":"none"}},"children":"layout · 264ms"}},{"type":"Box","props":{"style":{"position":"absolute","left":"40%","width":"calc(30% - 2px)","top":44,"height":20,"background":"rgb(248,186,17)","borderRadius":3,"border":"1px solid rgba(0,0,0,0.25)","overflow":"hidden","boxSizing":"border-box"}},"children":{"type":"Text","props":{"size":"xs","c":"rgba(0,0,0,0.85)","fw":600,"truncate":"end","style":{"lineHeight":"20px","paddingLeft":6,"paddingRight":4,"pointerEvents":"none"}},"children":"rasterize · 360ms"}},{"type":"Box","props":{"style":{"position":"absolute","left":"70%","width":"calc(12% - 2px)","top":44,"height":20,"background":"rgb(234,88,12)","borderRadius":3,"border":"1px solid rgba(0,0,0,0.25)","overflow":"hidden","boxSizing":"border-box"}},"children":{"type":"Text","props":{"size":"xs","c":"rgba(0,0,0,0.85)","fw":600,"truncate":"end","style":{"lineHeight":"20px","paddingLeft":6,"paddingRight":4,"pointerEvents":"none"}},"children":"composite · 144ms"}},{"type":"Box","props":{"style":{"position":"absolute","left":"0%","width":"calc(10% - 2px)","top":44,"height":20,"background":"rgb(234,88,12)","borderRadius":3,"border":"1px solid rgba(0,0,0,0.25)","overflow":"hidden","boxSizing":"border-box"}},"children":{"type":"Text","props":{"size":"xs","c":"rgba(0,0,0,0.85)","fw":600,"truncate":"end","style":{"lineHeight":"20px","paddingLeft":6,"paddingRight":4,"pointerEvents":"none"}},"children":"tokenize · 120ms"}},{"type":"Box","props":{"style":{"position":"absolute","left":"40%","width":"calc(18% - 2px)","top":66,"height":20,"background":"rgb(234,88,12)","borderRadius":3,"border":"1px solid rgba(0,0,0,0.25)","overflow":"hidden","boxSizing":"border-box"}},"children":{"type":"Text","props":{"size":"xs","c":"rgba(0,0,0,0.85)","fw":600,"truncate":"end","style":{"lineHeight":"20px","paddingLeft":6,"paddingRight":4,"pointerEvents":"none"}},"children":"shade_pixels · 216ms"}},{"type":"Box","props":{"style":{"position":"absolute","left":"58%","width":"calc(12% - 2px)","top":66,"height":20,"background":"rgb(234,88,12)","borderRadius":3,"border":"1px solid rgba(0,0,0,0.25)","overflow":"hidden","boxSizing":"border-box"}},"children":{"type":"Text","props":{"size":"xs","c":"rgba(0,0,0,0.85)","fw":600,"truncate":"end","style":{"lineHeight":"20px","paddingLeft":6,"paddingRight":4,"pointerEvents":"none"}},"children":"blend · 144ms"}},{"type":"Box","props":{"style":{"position":"absolute","left":"18%","width":"calc(12% - 2px)","top":66,"height":20,"background":"rgb(234,88,12)","borderRadius":3,"border":"1px solid rgba(0,0,0,0.25)","overflow":"hidden","boxSizing":"border-box"}},"children":{"type":"Text","props":{"size":"xs","c":"rgba(0,0,0,0.85)","fw":600,"truncate":"end","style":{"lineHeight":"20px","paddingLeft":6,"paddingRight":4,"pointerEvents":"none"}},"children":"measure_text · 144ms"}}]},{"type":"Text","props":{"c":"dimmed","size":"xs","mt":2},"children":"Widest frame = hottest path. composite · shade_pixels · flush_io carry the most self-time."}]} diff --git a/plugin/skills/diagram-recipes/examples/gantt.vegalite.json b/plugin/skills/diagram-recipes/examples/gantt.vegalite.json index fe34174..12ce0b5 100644 --- a/plugin/skills/diagram-recipes/examples/gantt.vegalite.json +++ b/plugin/skills/diagram-recipes/examples/gantt.vegalite.json @@ -1,89 +1 @@ -{ - "$schema": "https://vega.github.io/schema/vega-lite/v6.json", - "title": { "text": "Q2 release — project Gantt", "subtitle": "tasks spanning start → end, colored by status, against today's date", "anchor": "start" }, - "width": "container", - "height": 360, - "data": { - "values": [ - { "task": "Discovery & specs", "start": "2026-04-01", "end": "2026-04-14", "status": "done", "owner": "Priya", "track": "Product" }, - { "task": "API design", "start": "2026-04-10", "end": "2026-04-24", "status": "done", "owner": "Marco", "track": "Backend" }, - { "task": "Schema migration", "start": "2026-04-21", "end": "2026-05-05", "status": "done", "owner": "Marco", "track": "Backend" }, - { "task": "Ingest pipeline", "start": "2026-05-04", "end": "2026-05-26", "status": "active", "owner": "Lena", "track": "Data" }, - { "task": "Viewer UI", "start": "2026-05-11", "end": "2026-06-12", "status": "active", "owner": "Ivan", "track": "Frontend" }, - { "task": "Auth & tenancy", "start": "2026-05-18", "end": "2026-06-05", "status": "blocked", "owner": "Sam", "track": "Backend" }, - { "task": "Load testing", "start": "2026-06-08", "end": "2026-06-19", "status": "planned", "owner": "Lena", "track": "Data" }, - { "task": "Docs & recipes", "start": "2026-06-15", "end": "2026-06-26", "status": "planned", "owner": "Priya", "track": "Product" }, - { "task": "Beta rollout", "start": "2026-06-22", "end": "2026-07-03", "status": "planned", "owner": "Ivan", "track": "Frontend" }, - { "task": "GA launch", "start": "2026-06-29", "end": "2026-07-10", "status": "planned", "owner": "Sam", "track": "Product" } - ] - }, - "transform": [ - { - "calculate": "datum.status === 'done' ? '✓' : datum.status === 'blocked' ? '✕' : datum.status === 'active' ? '▶' : '·'", - "as": "symbol" - } - ], - "layer": [ - { - "mark": { "type": "bar", "cornerRadius": 3, "height": { "band": 0.7 } }, - "encoding": { - "y": { - "field": "task", - "type": "nominal", - "sort": { "field": "start", "order": "ascending" }, - "title": null, - "axis": { "labelLimit": 220 } - }, - "x": { - "field": "start", - "type": "temporal", - "title": null, - "axis": { "format": "%b %d", "grid": true, "tickCount": "week" } - }, - "x2": { "field": "end" }, - "color": { - "field": "status", - "type": "nominal", - "title": "status", - "scale": { - "domain": ["done", "active", "blocked", "planned"], - "range": ["#22c55e", "#60a5fa", "#ef4444", "#52525b"] - }, - "legend": { "orient": "top" } - }, - "tooltip": [ - { "field": "task", "type": "nominal", "title": "task" }, - { "field": "status", "type": "nominal", "title": "status" }, - { "field": "owner", "type": "nominal", "title": "owner" }, - { "field": "track", "type": "nominal", "title": "track" }, - { "field": "start", "type": "temporal", "title": "start", "format": "%b %d, %Y" }, - { "field": "end", "type": "temporal", "title": "end", "format": "%b %d, %Y" } - ] - } - }, - { - "mark": { "type": "text", "align": "left", "baseline": "middle", "dx": 4, "color": "#f8fafc", "fontWeight": "bold", "fontSize": 12 }, - "encoding": { - "y": { "field": "task", "type": "nominal", "sort": { "field": "start", "order": "ascending" } }, - "x": { "field": "start", "type": "temporal" }, - "text": { "field": "symbol" } - } - }, - { - "data": { "values": [ { "today": "2026-06-06", "label": "today" } ] }, - "mark": { "type": "rule", "color": "#fbbf24", "strokeWidth": 2, "strokeDash": [4, 3] }, - "encoding": { - "x": { "field": "today", "type": "temporal" }, - "tooltip": [ { "field": "today", "type": "temporal", "title": "today", "format": "%b %d, %Y" } ] - } - }, - { - "data": { "values": [ { "today": "2026-06-06", "label": "today" } ] }, - "mark": { "type": "text", "color": "#fbbf24", "align": "left", "dx": 5, "dy": -2, "baseline": "top", "fontWeight": "bold", "fontSize": 11 }, - "encoding": { - "x": { "field": "today", "type": "temporal" }, - "text": { "field": "label" } - } - } - ] -} +{"$schema":"https://vega.github.io/schema/vega-lite/v6.json","title":{"text":"Q2 release — project Gantt","subtitle":"tasks spanning start → end, colored by status, against today's date","anchor":"start"},"width":"container","height":360,"data":{"values":[{"task":"Discovery & specs","start":"2026-04-01","end":"2026-04-14","status":"done","owner":"Priya","track":"Product"},{"task":"API design","start":"2026-04-10","end":"2026-04-24","status":"done","owner":"Marco","track":"Backend"},{"task":"Schema migration","start":"2026-04-21","end":"2026-05-05","status":"done","owner":"Marco","track":"Backend"},{"task":"Ingest pipeline","start":"2026-05-04","end":"2026-05-26","status":"active","owner":"Lena","track":"Data"},{"task":"Viewer UI","start":"2026-05-11","end":"2026-06-12","status":"active","owner":"Ivan","track":"Frontend"},{"task":"Auth & tenancy","start":"2026-05-18","end":"2026-06-05","status":"blocked","owner":"Sam","track":"Backend"},{"task":"Load testing","start":"2026-06-08","end":"2026-06-19","status":"planned","owner":"Lena","track":"Data"},{"task":"Docs & recipes","start":"2026-06-15","end":"2026-06-26","status":"planned","owner":"Priya","track":"Product"},{"task":"Beta rollout","start":"2026-06-22","end":"2026-07-03","status":"planned","owner":"Ivan","track":"Frontend"},{"task":"GA launch","start":"2026-06-29","end":"2026-07-10","status":"planned","owner":"Sam","track":"Product"}]},"transform":[{"calculate":"datum.status === 'done' ? '✓' : datum.status === 'blocked' ? '✕' : datum.status === 'active' ? '▶' : '·'","as":"symbol"}],"layer":[{"mark":{"type":"bar","cornerRadius":3,"height":{"band":0.7}},"encoding":{"y":{"field":"task","type":"nominal","sort":{"field":"start","order":"ascending"},"title":null,"axis":{"labelLimit":220}},"x":{"field":"start","type":"temporal","title":null,"axis":{"format":"%b %d","grid":true,"tickCount":"week"}},"x2":{"field":"end"},"color":{"field":"status","type":"nominal","title":"status","scale":{"domain":["done","active","blocked","planned"],"range":["#22c55e","#60a5fa","#ef4444","#52525b"]},"legend":{"orient":"top"}},"tooltip":[{"field":"task","type":"nominal","title":"task"},{"field":"status","type":"nominal","title":"status"},{"field":"owner","type":"nominal","title":"owner"},{"field":"track","type":"nominal","title":"track"},{"field":"start","type":"temporal","title":"start","format":"%b %d, %Y"},{"field":"end","type":"temporal","title":"end","format":"%b %d, %Y"}]}},{"mark":{"type":"text","align":"left","baseline":"middle","dx":4,"color":"#f8fafc","fontWeight":"bold","fontSize":12},"encoding":{"y":{"field":"task","type":"nominal","sort":{"field":"start","order":"ascending"}},"x":{"field":"start","type":"temporal"},"text":{"field":"symbol"}}},{"data":{"values":[{"today":"2026-06-06","label":"today"}]},"mark":{"type":"rule","color":"#fbbf24","strokeWidth":2,"strokeDash":[4,3]},"encoding":{"x":{"field":"today","type":"temporal"},"tooltip":[{"field":"today","type":"temporal","title":"today","format":"%b %d, %Y"}]}},{"data":{"values":[{"today":"2026-06-06","label":"today"}]},"mark":{"type":"text","color":"#fbbf24","align":"left","dx":5,"dy":-2,"baseline":"top","fontWeight":"bold","fontSize":11},"encoding":{"x":{"field":"today","type":"temporal"},"text":{"field":"label"}}}]} diff --git a/plugin/skills/diagram-recipes/examples/map-routes.component.json b/plugin/skills/diagram-recipes/examples/map-routes.component.json index 29e69bf..242aaca 100644 --- a/plugin/skills/diagram-recipes/examples/map-routes.component.json +++ b/plugin/skills/diagram-recipes/examples/map-routes.component.json @@ -1,93 +1 @@ -{ - "type": "Stack", - "props": { "gap": "md", "p": "md" }, - "children": [ - { - "type": "Group", - "props": { "justify": "space-between", "align": "flex-end" }, - "children": [ - { - "type": "Stack", - "props": { "gap": 2 }, - "children": [ - { "type": "Title", "props": { "order": 3 }, "children": "Kagoshima dojo finder — drive times from home" }, - { "type": "Text", "props": { "c": "dimmed", "size": "sm" }, "children": "Origin pin + 4 destinations, routes color-coded by category" } - ] - }, - { "type": "Badge", "props": { "variant": "light", "color": "blue" }, "children": "OSM · interactive" } - ] - }, - { - "type": "Grid", - "props": { "gutter": "md" }, - "children": [ - { - "type": "Grid.Col", - "props": { "span": { "base": 12, "md": 7 } }, - "children": [ - { - "type": "Map", - "props": { - "h": 380, - "center": { "lat": 31.575, "lng": 130.555 }, - "zoom": 12, - "tileLayer": "osm", - "markers": [ - { "lat": 31.5602, "lng": 130.5581, "label": "Home (鹿児島中央)", "color": "blue" }, - { "lat": 31.5240, "lng": 130.5230, "label": "柔士会 (谷山中)", "color": "red" }, - { "lat": 31.5018, "lng": 130.4995, "label": "星ヶ峯JC (松元中)", "color": "amber" }, - { "lat": 31.5780, "lng": 130.5400, "label": "県総合体育センター", "color": "green" }, - { "lat": 31.5965, "lng": 130.5572, "label": "ドロップイン道場", "color": "purple" } - ], - "routes": [ - { "from": 0, "to": 1, "color": "red", "label": "柔士会 · ~10 min · 4 km" }, - { "from": 0, "to": 2, "color": "amber", "label": "星ヶ峯JC · ~15 min · 7 km" }, - { "from": 0, "to": 3, "color": "green", "label": "県総合体育センター · ~20 min · 9 km" }, - { "from": 0, "to": 4, "color": "purple", "label": "ドロップイン · ~12 min · 5 km" } - ] - } - } - ] - }, - { - "type": "Grid.Col", - "props": { "span": { "base": 12, "md": 5 } }, - "children": [ - { - "type": "Card", - "props": { "withBorder": true, "padding": "md", "radius": "md" }, - "children": [ - { "type": "Text", "props": { "fw": 700, "mb": "xs" }, "children": "Destinations" }, - { - "type": "Table", - "props": { - "data": { - "head": ["", "Venue", "Category", "Drive"], - "body": [ - ["🔴", "柔士会 (谷山中)", "dojo", "~10 min"], - ["🟡", "星ヶ峯JC (松元中)", "dojo", "~15 min"], - ["🟢", "県総合体育センター", "event venue", "~20 min"], - ["🟣", "ドロップイン道場", "drop-in", "~12 min"] - ] - } - } - }, - { "type": "Divider", "props": { "my": "sm" } }, - { - "type": "Group", - "props": { "gap": "xs" }, - "children": [ - { "type": "Badge", "props": { "color": "red", "variant": "light" }, "children": "dojo" }, - { "type": "Badge", "props": { "color": "green", "variant": "light" }, "children": "event" }, - { "type": "Badge", "props": { "color": "purple", "variant": "light" }, "children": "drop-in" } - ] - }, - { "type": "Text", "props": { "c": "dimmed", "size": "xs", "mt": "sm" }, "children": "Hover a route on the map for its drive time. Straight-line routes shown; pass routes[].path for road geometry." } - ] - } - ] - } - ] - } - ] -} +{"type":"Stack","props":{"gap":"md","p":"md"},"children":[{"type":"Group","props":{"justify":"space-between","align":"flex-end"},"children":[{"type":"Stack","props":{"gap":2},"children":[{"type":"Title","props":{"order":3},"children":"Kagoshima dojo finder — drive times from home"},{"type":"Text","props":{"c":"dimmed","size":"sm"},"children":"Origin pin + 4 destinations, routes color-coded by category"}]},{"type":"Badge","props":{"variant":"light","color":"blue"},"children":"OSM · interactive"}]},{"type":"Grid","props":{"gutter":"md"},"children":[{"type":"Grid.Col","props":{"span":{"base":12,"md":7}},"children":[{"type":"Map","props":{"h":380,"center":{"lat":31.575,"lng":130.555},"zoom":12,"tileLayer":"osm","markers":[{"lat":31.5602,"lng":130.5581,"label":"Home (鹿児島中央)","color":"blue"},{"lat":31.524,"lng":130.523,"label":"柔士会 (谷山中)","color":"red"},{"lat":31.5018,"lng":130.4995,"label":"星ヶ峯JC (松元中)","color":"amber"},{"lat":31.578,"lng":130.54,"label":"県総合体育センター","color":"green"},{"lat":31.5965,"lng":130.5572,"label":"ドロップイン道場","color":"purple"}],"routes":[{"from":0,"to":1,"color":"red","label":"柔士会 · ~10 min · 4 km"},{"from":0,"to":2,"color":"amber","label":"星ヶ峯JC · ~15 min · 7 km"},{"from":0,"to":3,"color":"green","label":"県総合体育センター · ~20 min · 9 km"},{"from":0,"to":4,"color":"purple","label":"ドロップイン · ~12 min · 5 km"}]}}]},{"type":"Grid.Col","props":{"span":{"base":12,"md":5}},"children":[{"type":"Card","props":{"withBorder":true,"padding":"md","radius":"md"},"children":[{"type":"Text","props":{"fw":700,"mb":"xs"},"children":"Destinations"},{"type":"Table","props":{"data":{"head":["","Venue","Category","Drive"],"body":[["🔴","柔士会 (谷山中)","dojo","~10 min"],["🟡","星ヶ峯JC (松元中)","dojo","~15 min"],["🟢","県総合体育センター","event venue","~20 min"],["🟣","ドロップイン道場","drop-in","~12 min"]]}}},{"type":"Divider","props":{"my":"sm"}},{"type":"Group","props":{"gap":"xs"},"children":[{"type":"Badge","props":{"color":"red","variant":"light"},"children":"dojo"},{"type":"Badge","props":{"color":"green","variant":"light"},"children":"event"},{"type":"Badge","props":{"color":"purple","variant":"light"},"children":"drop-in"}]},{"type":"Text","props":{"c":"dimmed","size":"xs","mt":"sm"},"children":"Hover a route on the map for its drive time. Straight-line routes shown; pass routes[].path for road geometry."}]}]}]}]} diff --git a/plugin/skills/diagram-recipes/examples/metrics-dashboard.component.json b/plugin/skills/diagram-recipes/examples/metrics-dashboard.component.json index ca7bc95..922bf0e 100644 --- a/plugin/skills/diagram-recipes/examples/metrics-dashboard.component.json +++ b/plugin/skills/diagram-recipes/examples/metrics-dashboard.component.json @@ -1,228 +1 @@ -{ - "type": "Stack", - "props": { - "gap": "md", - "p": "md" - }, - "children": [ - { - "type": "Title", - "props": { - "order": 3 - }, - "children": "Service health \u2014 last 24h" - }, - { - "type": "Text", - "props": { - "c": "dimmed", - "size": "sm", - "mt": -4 - }, - "children": "prod \u00b7 us-central1 \u00b7 vs. previous 24h" - }, - { - "type": "SimpleGrid", - "props": { - "cols": 3, - "spacing": "md" - }, - "children": [ - { - "type": "Card", - "props": { - "withBorder": true, - "padding": "md" - }, - "children": [ - { - "type": "Text", - "props": { - "c": "dimmed", - "size": "sm" - }, - "children": "Requests" - }, - { - "type": "Text", - "props": { - "fw": 700, - "size": "xl" - }, - "children": "1.24M" - }, - { - "type": "Text", - "props": { - "c": "green", - "size": "xs", - "mt": 2 - }, - "children": "\u25b2 8% WoW" - } - ] - }, - { - "type": "Card", - "props": { - "withBorder": true, - "padding": "md" - }, - "children": [ - { - "type": "Text", - "props": { - "c": "dimmed", - "size": "sm" - }, - "children": "Error rate" - }, - { - "type": "Text", - "props": { - "fw": 700, - "size": "xl" - }, - "children": "0.18%" - }, - { - "type": "Badge", - "props": { - "color": "green", - "mt": "xs" - }, - "children": "within SLO" - } - ] - }, - { - "type": "Card", - "props": { - "withBorder": true, - "padding": "md" - }, - "children": [ - { - "type": "Text", - "props": { - "c": "dimmed", - "size": "sm" - }, - "children": "p95 latency" - }, - { - "type": "Text", - "props": { - "fw": 700, - "size": "xl" - }, - "children": "152ms" - }, - { - "type": "Text", - "props": { - "c": "red", - "size": "xs", - "mt": 2 - }, - "children": "\u25b2 12ms" - } - ] - } - ] - }, - { - "type": "Card", - "props": { - "withBorder": true, - "padding": "md" - }, - "children": [ - { - "type": "VegaLite", - "props": { - "spec": { - "$schema": "https://vega.github.io/schema/vega-lite/v6.json", - "width": "container", - "height": 170, - "title": { - "text": "Requests / hour", - "subtitle": "last 24h \u00b7 2-hour buckets", - "anchor": "start" - }, - "data": { - "values": [ - { - "h": 0, - "req": 42 - }, - { - "h": 2, - "req": 38 - }, - { - "h": 4, - "req": 31 - }, - { - "h": 6, - "req": 28 - }, - { - "h": 8, - "req": 35 - }, - { - "h": 10, - "req": 60 - }, - { - "h": 12, - "req": 88 - }, - { - "h": 14, - "req": 120 - }, - { - "h": 16, - "req": 110 - }, - { - "h": 18, - "req": 95 - }, - { - "h": 20, - "req": 70 - }, - { - "h": 22, - "req": 52 - } - ] - }, - "mark": { - "type": "line", - "point": true, - "tooltip": true - }, - "encoding": { - "x": { - "field": "h", - "type": "quantitative", - "title": "hour of day" - }, - "y": { - "field": "req", - "type": "quantitative", - "title": "requests (k)" - } - } - } - } - } - ] - } - ] -} +{"type":"Stack","props":{"gap":"md","p":"md"},"children":[{"type":"Title","props":{"order":3},"children":"Service health — last 24h"},{"type":"Text","props":{"c":"dimmed","size":"sm","mt":-4},"children":"prod · us-central1 · vs. previous 24h"},{"type":"SimpleGrid","props":{"cols":3,"spacing":"md"},"children":[{"type":"Card","props":{"withBorder":true,"padding":"md"},"children":[{"type":"Text","props":{"c":"dimmed","size":"sm"},"children":"Requests"},{"type":"Text","props":{"fw":700,"size":"xl"},"children":"1.24M"},{"type":"Text","props":{"c":"green","size":"xs","mt":2},"children":"▲ 8% WoW"}]},{"type":"Card","props":{"withBorder":true,"padding":"md"},"children":[{"type":"Text","props":{"c":"dimmed","size":"sm"},"children":"Error rate"},{"type":"Text","props":{"fw":700,"size":"xl"},"children":"0.18%"},{"type":"Badge","props":{"color":"green","mt":"xs"},"children":"within SLO"}]},{"type":"Card","props":{"withBorder":true,"padding":"md"},"children":[{"type":"Text","props":{"c":"dimmed","size":"sm"},"children":"p95 latency"},{"type":"Text","props":{"fw":700,"size":"xl"},"children":"152ms"},{"type":"Text","props":{"c":"red","size":"xs","mt":2},"children":"▲ 12ms"}]}]},{"type":"Card","props":{"withBorder":true,"padding":"md"},"children":[{"type":"VegaLite","props":{"spec":{"$schema":"https://vega.github.io/schema/vega-lite/v6.json","width":"container","height":170,"title":{"text":"Requests / hour","subtitle":"last 24h · 2-hour buckets","anchor":"start"},"data":{"values":[{"h":0,"req":42},{"h":2,"req":38},{"h":4,"req":31},{"h":6,"req":28},{"h":8,"req":35},{"h":10,"req":60},{"h":12,"req":88},{"h":14,"req":120},{"h":16,"req":110},{"h":18,"req":95},{"h":20,"req":70},{"h":22,"req":52}]},"mark":{"type":"line","point":true,"tooltip":true},"encoding":{"x":{"field":"h","type":"quantitative","title":"hour of day"},"y":{"field":"req","type":"quantitative","title":"requests (k)"}}}}}]}]} diff --git a/plugin/skills/diagram-recipes/examples/observability-dashboard.panes.json b/plugin/skills/diagram-recipes/examples/observability-dashboard.panes.json index bf128a9..48653ae 100644 --- a/plugin/skills/diagram-recipes/examples/observability-dashboard.panes.json +++ b/plugin/skills/diagram-recipes/examples/observability-dashboard.panes.json @@ -1,25 +1 @@ -{ - "layout": "grid", - "panes": [ - { - "title": "SLOs", - "type": "component", - "content": "{\"type\":\"Stack\",\"props\":{\"gap\":\"sm\",\"p\":\"md\"},\"children\":[{\"type\":\"Group\",\"props\":{\"justify\":\"space-between\",\"align\":\"center\"},\"children\":[{\"type\":\"Title\",\"props\":{\"order\":4},\"children\":\"Service SLOs — 28d\"},{\"type\":\"Badge\",\"props\":{\"color\":\"green\",\"variant\":\"light\"},\"children\":\"All within budget\"}]},{\"type\":\"SimpleGrid\",\"props\":{\"cols\":3,\"spacing\":\"xs\"},\"children\":[{\"type\":\"Stack\",\"props\":{\"align\":\"center\",\"gap\":2},\"children\":[{\"type\":\"RingProgress\",\"props\":{\"size\":118,\"thickness\":11,\"roundCaps\":true,\"sections\":[{\"value\":99.95,\"color\":\"teal\"}],\"label\":{\"type\":\"Text\",\"props\":{\"ta\":\"center\",\"fw\":700,\"size\":\"lg\"},\"children\":\"99.95%\"}}},{\"type\":\"Text\",\"props\":{\"c\":\"dimmed\",\"size\":\"xs\",\"ta\":\"center\"},\"children\":\"Availability · target 99.9\"}]},{\"type\":\"Stack\",\"props\":{\"align\":\"center\",\"gap\":2},\"children\":[{\"type\":\"RingProgress\",\"props\":{\"size\":118,\"thickness\":11,\"roundCaps\":true,\"sections\":[{\"value\":82,\"color\":\"yellow\"}],\"label\":{\"type\":\"Text\",\"props\":{\"ta\":\"center\",\"fw\":700,\"size\":\"lg\"},\"children\":\"82%\"}}},{\"type\":\"Text\",\"props\":{\"c\":\"dimmed\",\"size\":\"xs\",\"ta\":\"center\"},\"children\":\"Latency budget left\"}]},{\"type\":\"Stack\",\"props\":{\"align\":\"center\",\"gap\":2},\"children\":[{\"type\":\"RingProgress\",\"props\":{\"size\":118,\"thickness\":11,\"roundCaps\":true,\"sections\":[{\"value\":36,\"color\":\"red\"}],\"label\":{\"type\":\"Text\",\"props\":{\"ta\":\"center\",\"fw\":700,\"size\":\"lg\"},\"children\":\"36%\"}}},{\"type\":\"Text\",\"props\":{\"c\":\"dimmed\",\"size\":\"xs\",\"ta\":\"center\"},\"children\":\"Error budget used\"}]}]},{\"type\":\"Divider\",\"props\":{\"my\":2}},{\"type\":\"SimpleGrid\",\"props\":{\"cols\":3,\"spacing\":\"xs\"},\"children\":[{\"type\":\"Stack\",\"props\":{\"gap\":0},\"children\":[{\"type\":\"Text\",\"props\":{\"c\":\"dimmed\",\"size\":\"xs\"},\"children\":\"Requests\"},{\"type\":\"Text\",\"props\":{\"fw\":700},\"children\":\"1.24M\"}]},{\"type\":\"Stack\",\"props\":{\"gap\":0},\"children\":[{\"type\":\"Text\",\"props\":{\"c\":\"dimmed\",\"size\":\"xs\"},\"children\":\"p95 latency\"},{\"type\":\"Text\",\"props\":{\"fw\":700},\"children\":\"214 ms\"}]},{\"type\":\"Stack\",\"props\":{\"gap\":0},\"children\":[{\"type\":\"Text\",\"props\":{\"c\":\"dimmed\",\"size\":\"xs\"},\"children\":\"Error rate\"},{\"type\":\"Text\",\"props\":{\"fw\":700,\"c\":\"red\"},\"children\":\"0.42%\"}]}]}]}" - }, - { - "title": "Latency", - "type": "vegalite", - "content": "{\"$schema\":\"https://vega.github.io/schema/vega-lite/v6.json\",\"title\":{\"text\":\"Latency by percentile (ms)\",\"subtitle\":\"last 24h · dash + label = percentile (color-independent)\",\"anchor\":\"start\"},\"width\":\"container\",\"height\":300,\"data\":{\"values\":[{\"h\":0,\"ms\":83,\"pct\":\"p50\"},{\"h\":1,\"ms\":86,\"pct\":\"p50\"},{\"h\":2,\"ms\":90,\"pct\":\"p50\"},{\"h\":3,\"ms\":93,\"pct\":\"p50\"},{\"h\":4,\"ms\":94,\"pct\":\"p50\"},{\"h\":5,\"ms\":95,\"pct\":\"p50\"},{\"h\":6,\"ms\":94,\"pct\":\"p50\"},{\"h\":7,\"ms\":93,\"pct\":\"p50\"},{\"h\":8,\"ms\":90,\"pct\":\"p50\"},{\"h\":9,\"ms\":87,\"pct\":\"p50\"},{\"h\":10,\"ms\":83,\"pct\":\"p50\"},{\"h\":11,\"ms\":79,\"pct\":\"p50\"},{\"h\":12,\"ms\":75,\"pct\":\"p50\"},{\"h\":13,\"ms\":73,\"pct\":\"p50\"},{\"h\":14,\"ms\":71,\"pct\":\"p50\"},{\"h\":15,\"ms\":70,\"pct\":\"p50\"},{\"h\":16,\"ms\":71,\"pct\":\"p50\"},{\"h\":17,\"ms\":72,\"pct\":\"p50\"},{\"h\":18,\"ms\":75,\"pct\":\"p50\"},{\"h\":19,\"ms\":78,\"pct\":\"p50\"},{\"h\":20,\"ms\":82,\"pct\":\"p50\"},{\"h\":21,\"ms\":86,\"pct\":\"p50\"},{\"h\":22,\"ms\":89,\"pct\":\"p50\"},{\"h\":23,\"ms\":92,\"pct\":\"p50\"},{\"h\":0,\"ms\":226,\"pct\":\"p95\"},{\"h\":1,\"ms\":235,\"pct\":\"p95\"},{\"h\":2,\"ms\":243,\"pct\":\"p95\"},{\"h\":3,\"ms\":248,\"pct\":\"p95\"},{\"h\":4,\"ms\":250,\"pct\":\"p95\"},{\"h\":5,\"ms\":248,\"pct\":\"p95\"},{\"h\":6,\"ms\":244,\"pct\":\"p95\"},{\"h\":7,\"ms\":236,\"pct\":\"p95\"},{\"h\":8,\"ms\":226,\"pct\":\"p95\"},{\"h\":9,\"ms\":216,\"pct\":\"p95\"},{\"h\":10,\"ms\":205,\"pct\":\"p95\"},{\"h\":11,\"ms\":195,\"pct\":\"p95\"},{\"h\":12,\"ms\":187,\"pct\":\"p95\"},{\"h\":13,\"ms\":182,\"pct\":\"p95\"},{\"h\":14,\"ms\":180,\"pct\":\"p95\"},{\"h\":15,\"ms\":181,\"pct\":\"p95\"},{\"h\":16,\"ms\":186,\"pct\":\"p95\"},{\"h\":17,\"ms\":194,\"pct\":\"p95\"},{\"h\":18,\"ms\":203,\"pct\":\"p95\"},{\"h\":19,\"ms\":214,\"pct\":\"p95\"},{\"h\":20,\"ms\":225,\"pct\":\"p95\"},{\"h\":21,\"ms\":235,\"pct\":\"p95\"},{\"h\":22,\"ms\":243,\"pct\":\"p95\"},{\"h\":23,\"ms\":248,\"pct\":\"p95\"},{\"h\":0,\"ms\":431,\"pct\":\"p99\"},{\"h\":1,\"ms\":446,\"pct\":\"p99\"},{\"h\":2,\"ms\":456,\"pct\":\"p99\"},{\"h\":3,\"ms\":460,\"pct\":\"p99\"},{\"h\":4,\"ms\":457,\"pct\":\"p99\"},{\"h\":5,\"ms\":447,\"pct\":\"p99\"},{\"h\":6,\"ms\":432,\"pct\":\"p99\"},{\"h\":7,\"ms\":413,\"pct\":\"p99\"},{\"h\":8,\"ms\":391,\"pct\":\"p99\"},{\"h\":9,\"ms\":370,\"pct\":\"p99\"},{\"h\":10,\"ms\":350,\"pct\":\"p99\"},{\"h\":11,\"ms\":334,\"pct\":\"p99\"},{\"h\":12,\"ms\":324,\"pct\":\"p99\"},{\"h\":13,\"ms\":320,\"pct\":\"p99\"},{\"h\":14,\"ms\":323,\"pct\":\"p99\"},{\"h\":15,\"ms\":332,\"pct\":\"p99\"},{\"h\":16,\"ms\":347,\"pct\":\"p99\"},{\"h\":17,\"ms\":366,\"pct\":\"p99\"},{\"h\":18,\"ms\":388,\"pct\":\"p99\"},{\"h\":19,\"ms\":409,\"pct\":\"p99\"},{\"h\":20,\"ms\":429,\"pct\":\"p99\"},{\"h\":21,\"ms\":445,\"pct\":\"p99\"},{\"h\":22,\"ms\":456,\"pct\":\"p99\"},{\"h\":23,\"ms\":460,\"pct\":\"p99\"}]},\"encoding\":{\"x\":{\"field\":\"h\",\"type\":\"quantitative\",\"title\":\"hour\",\"axis\":{\"tickMinStep\":3}},\"color\":{\"field\":\"pct\",\"type\":\"nominal\",\"title\":null,\"scale\":{\"domain\":[\"p50\",\"p95\",\"p99\"],\"range\":[\"#22c55e\",\"#fbbf24\",\"#ef4444\"]}}},\"layer\":[{\"mark\":{\"type\":\"line\",\"point\":false,\"tooltip\":true,\"interpolate\":\"monotone\",\"strokeWidth\":2},\"encoding\":{\"y\":{\"field\":\"ms\",\"type\":\"quantitative\",\"title\":\"ms\"},\"strokeDash\":{\"field\":\"pct\",\"type\":\"nominal\",\"title\":null,\"scale\":{\"domain\":[\"p50\",\"p95\",\"p99\"],\"range\":[[1,0],[6,3],[2,3]]}}}},{\"transform\":[{\"window\":[{\"op\":\"last_value\",\"field\":\"h\",\"as\":\"lastH\"}],\"frame\":[null,null],\"groupby\":[\"pct\"]},{\"filter\":\"datum.h === datum.lastH\"}],\"mark\":{\"type\":\"text\",\"align\":\"left\",\"baseline\":\"middle\",\"dx\":6,\"fontWeight\":\"bold\",\"fontSize\":12},\"encoding\":{\"y\":{\"field\":\"ms\",\"type\":\"quantitative\"},\"text\":{\"field\":\"pct\"}}}]}" - }, - { - "title": "Top errors", - "type": "component", - "content": "{\"type\":\"Stack\",\"props\":{\"gap\":\"sm\",\"p\":\"md\"},\"children\":[{\"type\":\"Title\",\"props\":{\"order\":4},\"children\":\"Top errors — 1h\"},{\"type\":\"Table\",\"props\":{\"withTableBorder\":true,\"striped\":true,\"data\":{\"head\":[\"error\",\"service\",\"count\",\"share\"],\"body\":[[\"504 upstream timeout\",\"checkout\",\"1,204\",\"38%\"],[\"TypeError: undefined id\",\"auth\",\"642\",\"20%\"],[\"429 rate limited\",\"search\",\"511\",\"16%\"],[\"DB deadlock\",\"orders\",\"388\",\"12%\"],[\"NULL payment_intent\",\"payments\",\"203\",\"6%\"]]}}},{\"type\":\"Alert\",\"props\":{\"color\":\"red\",\"title\":\"Spike: 504s on checkout (+312% vs 1h ago)\"},\"children\":\"Correlates with deploy v2.18.0 at 14:02 — consider rollback.\"}]}" - }, - { - "title": "Throughput", - "type": "vegalite", - "content": "{\"$schema\":\"https://vega.github.io/schema/vega-lite/v6.json\",\"title\":{\"text\":\"Requests / min\",\"subtitle\":\"last 24h\",\"anchor\":\"start\"},\"width\":\"container\",\"height\":300,\"data\":{\"values\":[{\"h\":0,\"rpm\":1060},{\"h\":1,\"rpm\":1140},{\"h\":2,\"rpm\":1212},{\"h\":3,\"rpm\":1270},{\"h\":4,\"rpm\":1307},{\"h\":5,\"rpm\":1320},{\"h\":6,\"rpm\":1308},{\"h\":7,\"rpm\":1272},{\"h\":8,\"rpm\":1216},{\"h\":9,\"rpm\":1144},{\"h\":10,\"rpm\":1064},{\"h\":11,\"rpm\":984},{\"h\":12,\"rpm\":911},{\"h\":13,\"rpm\":853},{\"h\":14,\"rpm\":815},{\"h\":15,\"rpm\":800},{\"h\":16,\"rpm\":811},{\"h\":17,\"rpm\":845},{\"h\":18,\"rpm\":901},{\"h\":19,\"rpm\":972},{\"h\":20,\"rpm\":1051},{\"h\":21,\"rpm\":1132},{\"h\":22,\"rpm\":1205},{\"h\":23,\"rpm\":1264}]},\"mark\":{\"type\":\"area\",\"line\":{\"color\":\"#60a5fa\"},\"color\":{\"x1\":1,\"y1\":1,\"x2\":1,\"y2\":0,\"gradient\":\"linear\",\"stops\":[{\"offset\":0,\"color\":\"rgba(96,165,250,0.05)\"},{\"offset\":1,\"color\":\"rgba(96,165,250,0.5)\"}]},\"tooltip\":true,\"interpolate\":\"monotone\"},\"encoding\":{\"x\":{\"field\":\"h\",\"type\":\"quantitative\",\"title\":\"hour\"},\"y\":{\"field\":\"rpm\",\"type\":\"quantitative\",\"title\":\"req/min\"}}}" - } - ] -} +{"layout":"grid","panes":[{"title":"SLOs","type":"component","content":"{\"type\":\"Stack\",\"props\":{\"gap\":\"sm\",\"p\":\"md\"},\"children\":[{\"type\":\"Group\",\"props\":{\"justify\":\"space-between\",\"align\":\"center\"},\"children\":[{\"type\":\"Title\",\"props\":{\"order\":4},\"children\":\"Service SLOs — 28d\"},{\"type\":\"Badge\",\"props\":{\"color\":\"green\",\"variant\":\"light\"},\"children\":\"All within budget\"}]},{\"type\":\"SimpleGrid\",\"props\":{\"cols\":3,\"spacing\":\"xs\"},\"children\":[{\"type\":\"Stack\",\"props\":{\"align\":\"center\",\"gap\":2},\"children\":[{\"type\":\"RingProgress\",\"props\":{\"size\":118,\"thickness\":11,\"roundCaps\":true,\"sections\":[{\"value\":99.95,\"color\":\"teal\"}],\"label\":{\"type\":\"Text\",\"props\":{\"ta\":\"center\",\"fw\":700,\"size\":\"lg\"},\"children\":\"99.95%\"}}},{\"type\":\"Text\",\"props\":{\"c\":\"dimmed\",\"size\":\"xs\",\"ta\":\"center\"},\"children\":\"Availability · target 99.9\"}]},{\"type\":\"Stack\",\"props\":{\"align\":\"center\",\"gap\":2},\"children\":[{\"type\":\"RingProgress\",\"props\":{\"size\":118,\"thickness\":11,\"roundCaps\":true,\"sections\":[{\"value\":82,\"color\":\"yellow\"}],\"label\":{\"type\":\"Text\",\"props\":{\"ta\":\"center\",\"fw\":700,\"size\":\"lg\"},\"children\":\"82%\"}}},{\"type\":\"Text\",\"props\":{\"c\":\"dimmed\",\"size\":\"xs\",\"ta\":\"center\"},\"children\":\"Latency budget left\"}]},{\"type\":\"Stack\",\"props\":{\"align\":\"center\",\"gap\":2},\"children\":[{\"type\":\"RingProgress\",\"props\":{\"size\":118,\"thickness\":11,\"roundCaps\":true,\"sections\":[{\"value\":36,\"color\":\"red\"}],\"label\":{\"type\":\"Text\",\"props\":{\"ta\":\"center\",\"fw\":700,\"size\":\"lg\"},\"children\":\"36%\"}}},{\"type\":\"Text\",\"props\":{\"c\":\"dimmed\",\"size\":\"xs\",\"ta\":\"center\"},\"children\":\"Error budget used\"}]}]},{\"type\":\"Divider\",\"props\":{\"my\":2}},{\"type\":\"SimpleGrid\",\"props\":{\"cols\":3,\"spacing\":\"xs\"},\"children\":[{\"type\":\"Stack\",\"props\":{\"gap\":0},\"children\":[{\"type\":\"Text\",\"props\":{\"c\":\"dimmed\",\"size\":\"xs\"},\"children\":\"Requests\"},{\"type\":\"Text\",\"props\":{\"fw\":700},\"children\":\"1.24M\"}]},{\"type\":\"Stack\",\"props\":{\"gap\":0},\"children\":[{\"type\":\"Text\",\"props\":{\"c\":\"dimmed\",\"size\":\"xs\"},\"children\":\"p95 latency\"},{\"type\":\"Text\",\"props\":{\"fw\":700},\"children\":\"214 ms\"}]},{\"type\":\"Stack\",\"props\":{\"gap\":0},\"children\":[{\"type\":\"Text\",\"props\":{\"c\":\"dimmed\",\"size\":\"xs\"},\"children\":\"Error rate\"},{\"type\":\"Text\",\"props\":{\"fw\":700,\"c\":\"red\"},\"children\":\"0.42%\"}]}]}]}"},{"title":"Latency","type":"vegalite","content":"{\"$schema\":\"https://vega.github.io/schema/vega-lite/v6.json\",\"title\":{\"text\":\"Latency by percentile (ms)\",\"subtitle\":\"last 24h · dash + label = percentile (color-independent)\",\"anchor\":\"start\"},\"width\":\"container\",\"height\":300,\"data\":{\"values\":[{\"h\":0,\"ms\":83,\"pct\":\"p50\"},{\"h\":1,\"ms\":86,\"pct\":\"p50\"},{\"h\":2,\"ms\":90,\"pct\":\"p50\"},{\"h\":3,\"ms\":93,\"pct\":\"p50\"},{\"h\":4,\"ms\":94,\"pct\":\"p50\"},{\"h\":5,\"ms\":95,\"pct\":\"p50\"},{\"h\":6,\"ms\":94,\"pct\":\"p50\"},{\"h\":7,\"ms\":93,\"pct\":\"p50\"},{\"h\":8,\"ms\":90,\"pct\":\"p50\"},{\"h\":9,\"ms\":87,\"pct\":\"p50\"},{\"h\":10,\"ms\":83,\"pct\":\"p50\"},{\"h\":11,\"ms\":79,\"pct\":\"p50\"},{\"h\":12,\"ms\":75,\"pct\":\"p50\"},{\"h\":13,\"ms\":73,\"pct\":\"p50\"},{\"h\":14,\"ms\":71,\"pct\":\"p50\"},{\"h\":15,\"ms\":70,\"pct\":\"p50\"},{\"h\":16,\"ms\":71,\"pct\":\"p50\"},{\"h\":17,\"ms\":72,\"pct\":\"p50\"},{\"h\":18,\"ms\":75,\"pct\":\"p50\"},{\"h\":19,\"ms\":78,\"pct\":\"p50\"},{\"h\":20,\"ms\":82,\"pct\":\"p50\"},{\"h\":21,\"ms\":86,\"pct\":\"p50\"},{\"h\":22,\"ms\":89,\"pct\":\"p50\"},{\"h\":23,\"ms\":92,\"pct\":\"p50\"},{\"h\":0,\"ms\":226,\"pct\":\"p95\"},{\"h\":1,\"ms\":235,\"pct\":\"p95\"},{\"h\":2,\"ms\":243,\"pct\":\"p95\"},{\"h\":3,\"ms\":248,\"pct\":\"p95\"},{\"h\":4,\"ms\":250,\"pct\":\"p95\"},{\"h\":5,\"ms\":248,\"pct\":\"p95\"},{\"h\":6,\"ms\":244,\"pct\":\"p95\"},{\"h\":7,\"ms\":236,\"pct\":\"p95\"},{\"h\":8,\"ms\":226,\"pct\":\"p95\"},{\"h\":9,\"ms\":216,\"pct\":\"p95\"},{\"h\":10,\"ms\":205,\"pct\":\"p95\"},{\"h\":11,\"ms\":195,\"pct\":\"p95\"},{\"h\":12,\"ms\":187,\"pct\":\"p95\"},{\"h\":13,\"ms\":182,\"pct\":\"p95\"},{\"h\":14,\"ms\":180,\"pct\":\"p95\"},{\"h\":15,\"ms\":181,\"pct\":\"p95\"},{\"h\":16,\"ms\":186,\"pct\":\"p95\"},{\"h\":17,\"ms\":194,\"pct\":\"p95\"},{\"h\":18,\"ms\":203,\"pct\":\"p95\"},{\"h\":19,\"ms\":214,\"pct\":\"p95\"},{\"h\":20,\"ms\":225,\"pct\":\"p95\"},{\"h\":21,\"ms\":235,\"pct\":\"p95\"},{\"h\":22,\"ms\":243,\"pct\":\"p95\"},{\"h\":23,\"ms\":248,\"pct\":\"p95\"},{\"h\":0,\"ms\":431,\"pct\":\"p99\"},{\"h\":1,\"ms\":446,\"pct\":\"p99\"},{\"h\":2,\"ms\":456,\"pct\":\"p99\"},{\"h\":3,\"ms\":460,\"pct\":\"p99\"},{\"h\":4,\"ms\":457,\"pct\":\"p99\"},{\"h\":5,\"ms\":447,\"pct\":\"p99\"},{\"h\":6,\"ms\":432,\"pct\":\"p99\"},{\"h\":7,\"ms\":413,\"pct\":\"p99\"},{\"h\":8,\"ms\":391,\"pct\":\"p99\"},{\"h\":9,\"ms\":370,\"pct\":\"p99\"},{\"h\":10,\"ms\":350,\"pct\":\"p99\"},{\"h\":11,\"ms\":334,\"pct\":\"p99\"},{\"h\":12,\"ms\":324,\"pct\":\"p99\"},{\"h\":13,\"ms\":320,\"pct\":\"p99\"},{\"h\":14,\"ms\":323,\"pct\":\"p99\"},{\"h\":15,\"ms\":332,\"pct\":\"p99\"},{\"h\":16,\"ms\":347,\"pct\":\"p99\"},{\"h\":17,\"ms\":366,\"pct\":\"p99\"},{\"h\":18,\"ms\":388,\"pct\":\"p99\"},{\"h\":19,\"ms\":409,\"pct\":\"p99\"},{\"h\":20,\"ms\":429,\"pct\":\"p99\"},{\"h\":21,\"ms\":445,\"pct\":\"p99\"},{\"h\":22,\"ms\":456,\"pct\":\"p99\"},{\"h\":23,\"ms\":460,\"pct\":\"p99\"}]},\"encoding\":{\"x\":{\"field\":\"h\",\"type\":\"quantitative\",\"title\":\"hour\",\"axis\":{\"tickMinStep\":3}},\"color\":{\"field\":\"pct\",\"type\":\"nominal\",\"title\":null,\"scale\":{\"domain\":[\"p50\",\"p95\",\"p99\"],\"range\":[\"#22c55e\",\"#fbbf24\",\"#ef4444\"]}}},\"layer\":[{\"mark\":{\"type\":\"line\",\"point\":false,\"tooltip\":true,\"interpolate\":\"monotone\",\"strokeWidth\":2},\"encoding\":{\"y\":{\"field\":\"ms\",\"type\":\"quantitative\",\"title\":\"ms\"},\"strokeDash\":{\"field\":\"pct\",\"type\":\"nominal\",\"title\":null,\"scale\":{\"domain\":[\"p50\",\"p95\",\"p99\"],\"range\":[[1,0],[6,3],[2,3]]}}}},{\"transform\":[{\"window\":[{\"op\":\"last_value\",\"field\":\"h\",\"as\":\"lastH\"}],\"frame\":[null,null],\"groupby\":[\"pct\"]},{\"filter\":\"datum.h === datum.lastH\"}],\"mark\":{\"type\":\"text\",\"align\":\"left\",\"baseline\":\"middle\",\"dx\":6,\"fontWeight\":\"bold\",\"fontSize\":12},\"encoding\":{\"y\":{\"field\":\"ms\",\"type\":\"quantitative\"},\"text\":{\"field\":\"pct\"}}}]}"},{"title":"Top errors","type":"component","content":"{\"type\":\"Stack\",\"props\":{\"gap\":\"sm\",\"p\":\"md\"},\"children\":[{\"type\":\"Title\",\"props\":{\"order\":4},\"children\":\"Top errors — 1h\"},{\"type\":\"Table\",\"props\":{\"withTableBorder\":true,\"striped\":true,\"data\":{\"head\":[\"error\",\"service\",\"count\",\"share\"],\"body\":[[\"504 upstream timeout\",\"checkout\",\"1,204\",\"38%\"],[\"TypeError: undefined id\",\"auth\",\"642\",\"20%\"],[\"429 rate limited\",\"search\",\"511\",\"16%\"],[\"DB deadlock\",\"orders\",\"388\",\"12%\"],[\"NULL payment_intent\",\"payments\",\"203\",\"6%\"]]}}},{\"type\":\"Alert\",\"props\":{\"color\":\"red\",\"title\":\"Spike: 504s on checkout (+312% vs 1h ago)\"},\"children\":\"Correlates with deploy v2.18.0 at 14:02 — consider rollback.\"}]}"},{"title":"Throughput","type":"vegalite","content":"{\"$schema\":\"https://vega.github.io/schema/vega-lite/v6.json\",\"title\":{\"text\":\"Requests / min\",\"subtitle\":\"last 24h\",\"anchor\":\"start\"},\"width\":\"container\",\"height\":300,\"data\":{\"values\":[{\"h\":0,\"rpm\":1060},{\"h\":1,\"rpm\":1140},{\"h\":2,\"rpm\":1212},{\"h\":3,\"rpm\":1270},{\"h\":4,\"rpm\":1307},{\"h\":5,\"rpm\":1320},{\"h\":6,\"rpm\":1308},{\"h\":7,\"rpm\":1272},{\"h\":8,\"rpm\":1216},{\"h\":9,\"rpm\":1144},{\"h\":10,\"rpm\":1064},{\"h\":11,\"rpm\":984},{\"h\":12,\"rpm\":911},{\"h\":13,\"rpm\":853},{\"h\":14,\"rpm\":815},{\"h\":15,\"rpm\":800},{\"h\":16,\"rpm\":811},{\"h\":17,\"rpm\":845},{\"h\":18,\"rpm\":901},{\"h\":19,\"rpm\":972},{\"h\":20,\"rpm\":1051},{\"h\":21,\"rpm\":1132},{\"h\":22,\"rpm\":1205},{\"h\":23,\"rpm\":1264}]},\"mark\":{\"type\":\"area\",\"line\":{\"color\":\"#60a5fa\"},\"color\":{\"x1\":1,\"y1\":1,\"x2\":1,\"y2\":0,\"gradient\":\"linear\",\"stops\":[{\"offset\":0,\"color\":\"rgba(96,165,250,0.05)\"},{\"offset\":1,\"color\":\"rgba(96,165,250,0.5)\"}]},\"tooltip\":true,\"interpolate\":\"monotone\"},\"encoding\":{\"x\":{\"field\":\"h\",\"type\":\"quantitative\",\"title\":\"hour\"},\"y\":{\"field\":\"rpm\",\"type\":\"quantitative\",\"title\":\"req/min\"}}}"}]} diff --git a/plugin/skills/diagram-recipes/examples/pr-review-summary.component.json b/plugin/skills/diagram-recipes/examples/pr-review-summary.component.json index 9c677a8..07ac84d 100644 --- a/plugin/skills/diagram-recipes/examples/pr-review-summary.component.json +++ b/plugin/skills/diagram-recipes/examples/pr-review-summary.component.json @@ -1,384 +1 @@ -{ - "type": "Stack", - "props": { - "gap": "md", - "p": "md" - }, - "children": [ - { - "type": "Group", - "props": { - "justify": "space-between", - "align": "center", - "wrap": "nowrap" - }, - "children": [ - { - "type": "Title", - "props": { - "order": 3 - }, - "children": "PR #128 — Cache layer for the search API" - }, - { - "type": "Anchor", - "props": { - "href": "https://github.com/acme/search/pull/128", - "target": "_blank", - "rel": "noopener noreferrer", - "fw": 600, - "size": "sm" - }, - "children": "View PR #128 →" - } - ] - }, - { - "type": "Text", - "props": { - "c": "dimmed", - "size": "sm" - }, - "children": "Goal: cut repeat-query latency without changing the public API or risking stale results." - }, - { - "type": "SimpleGrid", - "props": { - "cols": 2, - "spacing": "md" - }, - "children": [ - { - "type": "Card", - "props": { - "withBorder": true, - "padding": "md" - }, - "children": [ - { - "type": "Group", - "props": { - "justify": "space-between" - }, - "children": [ - { - "type": "Text", - "props": { - "fw": 700 - }, - "children": "Before" - }, - { - "type": "Badge", - "props": { - "color": "red", - "variant": "light" - }, - "children": "the problem" - } - ] - }, - { - "type": "Text", - "props": { - "size": "sm", - "mt": "xs" - }, - "children": "Every search hit the database, even for identical back-to-back queries." - }, - { - "type": "List", - "props": { - "size": "sm", - "spacing": "xs", - "mt": "xs" - }, - "children": [ - { - "type": "List.Item", - "children": "p95 latency ~240ms under load" - }, - { - "type": "List.Item", - "children": "DB CPU spiked on popular queries" - } - ] - } - ] - }, - { - "type": "Card", - "props": { - "withBorder": true, - "padding": "md" - }, - "children": [ - { - "type": "Group", - "props": { - "justify": "space-between" - }, - "children": [ - { - "type": "Text", - "props": { - "fw": 700 - }, - "children": "After" - }, - { - "type": "Badge", - "props": { - "color": "green", - "variant": "light" - }, - "children": "this PR" - } - ] - }, - { - "type": "Text", - "props": { - "size": "sm", - "mt": "xs" - }, - "children": "Identical queries are served from a short-lived in-memory cache; writes invalidate it." - }, - { - "type": "List", - "props": { - "size": "sm", - "spacing": "xs", - "mt": "xs" - }, - "children": [ - { - "type": "List.Item", - "children": "p95 latency ~90ms on cache hit" - }, - { - "type": "List.Item", - "children": "DB load drops ~60% on hot queries" - }, - { - "type": "List.Item", - "children": "Behaviour unchanged on a miss" - } - ] - } - ] - } - ] - }, - { - "type": "Divider", - "props": { - "label": "How a query flows now", - "labelPosition": "center" - } - }, - { - "type": "Timeline", - "props": { - "active": 3, - "bulletSize": 22, - "lineWidth": 2 - }, - "children": [ - { - "type": "Timeline.Item", - "props": { - "title": "1 — Request" - }, - "children": [ - { - "type": "Text", - "props": { - "size": "sm", - "c": "dimmed" - }, - "children": "search(q) computes a cache key from the normalized query + filters." - } - ] - }, - { - "type": "Timeline.Item", - "props": { - "title": "2 — Lookup" - }, - "children": [ - { - "type": "Text", - "props": { - "size": "sm", - "c": "dimmed" - }, - "children": "On a hit (TTL 30s), return the cached page; on a miss, fall through to the DB." - } - ] - }, - { - "type": "Timeline.Item", - "props": { - "title": "3 — Fill" - }, - "children": [ - { - "type": "Text", - "props": { - "size": "sm", - "c": "dimmed" - }, - "children": "DB result is cached before returning, so the next identical query is a hit." - } - ] - }, - { - "type": "Timeline.Item", - "props": { - "title": "4 — Invalidate" - }, - "children": [ - { - "type": "Text", - "props": { - "size": "sm", - "c": "dimmed" - }, - "children": "Any write to the indexed tables clears matching keys, so results never go stale." - } - ] - } - ] - }, - { - "type": "Alert", - "props": { - "color": "green", - "variant": "light", - "title": "Validated" - }, - "children": "Load test + full suite: 0 regressions, p95 240ms → 90ms on the hot path, cache hit-rate 71%." - }, - { - "type": "Divider", - "props": { - "label": "FAQ — questions we worked through", - "labelPosition": "center" - } - }, - { - "type": "SimpleGrid", - "props": { - "cols": 2, - "spacing": "sm" - }, - "children": [ - { - "type": "Card", - "props": { - "withBorder": true, - "padding": "sm" - }, - "children": [ - { - "type": "Text", - "props": { - "fw": 600, - "size": "sm" - }, - "children": "Why in-memory and not Redis?" - }, - { - "type": "Text", - "props": { - "size": "sm", - "c": "dimmed", - "mt": 4 - }, - "children": "Single-instance service; an in-process LRU avoids a network hop and a new dependency. Redis stays an option if we scale out." - } - ] - }, - { - "type": "Card", - "props": { - "withBorder": true, - "padding": "sm" - }, - "children": [ - { - "type": "Text", - "props": { - "fw": 600, - "size": "sm" - }, - "children": "How do we avoid stale results?" - }, - { - "type": "Text", - "props": { - "size": "sm", - "c": "dimmed", - "mt": 4 - }, - "children": "Writes to the indexed tables invalidate matching keys; TTL is a 30s backstop. Reads after a write always reflect it." - } - ] - }, - { - "type": "Card", - "props": { - "withBorder": true, - "padding": "sm" - }, - "children": [ - { - "type": "Text", - "props": { - "fw": 600, - "size": "sm" - }, - "children": "What's the memory ceiling?" - }, - { - "type": "Text", - "props": { - "size": "sm", - "c": "dimmed", - "mt": 4 - }, - "children": "LRU capped at 5k entries (~40MB). Evicts least-recently-used; bounded regardless of traffic." - } - ] - }, - { - "type": "Card", - "props": { - "withBorder": true, - "padding": "sm" - }, - "children": [ - { - "type": "Text", - "props": { - "fw": 600, - "size": "sm" - }, - "children": "Does this change the API?" - }, - { - "type": "Text", - "props": { - "size": "sm", - "c": "dimmed", - "mt": 4 - }, - "children": "No. search() is unchanged for callers — caching is transparent and can be disabled with a flag." - } - ] - } - ] - } - ] -} +{"type":"Stack","props":{"gap":"md","p":"md"},"children":[{"type":"Group","props":{"justify":"space-between","align":"center","wrap":"nowrap"},"children":[{"type":"Title","props":{"order":3},"children":"PR #128 — Cache layer for the search API"},{"type":"Anchor","props":{"href":"https://github.com/acme/search/pull/128","target":"_blank","rel":"noopener noreferrer","fw":600,"size":"sm"},"children":"View PR #128 →"}]},{"type":"Text","props":{"c":"dimmed","size":"sm"},"children":"Goal: cut repeat-query latency without changing the public API or risking stale results."},{"type":"SimpleGrid","props":{"cols":2,"spacing":"md"},"children":[{"type":"Card","props":{"withBorder":true,"padding":"md"},"children":[{"type":"Group","props":{"justify":"space-between"},"children":[{"type":"Text","props":{"fw":700},"children":"Before"},{"type":"Badge","props":{"color":"red","variant":"light"},"children":"the problem"}]},{"type":"Text","props":{"size":"sm","mt":"xs"},"children":"Every search hit the database, even for identical back-to-back queries."},{"type":"List","props":{"size":"sm","spacing":"xs","mt":"xs"},"children":[{"type":"List.Item","children":"p95 latency ~240ms under load"},{"type":"List.Item","children":"DB CPU spiked on popular queries"}]}]},{"type":"Card","props":{"withBorder":true,"padding":"md"},"children":[{"type":"Group","props":{"justify":"space-between"},"children":[{"type":"Text","props":{"fw":700},"children":"After"},{"type":"Badge","props":{"color":"green","variant":"light"},"children":"this PR"}]},{"type":"Text","props":{"size":"sm","mt":"xs"},"children":"Identical queries are served from a short-lived in-memory cache; writes invalidate it."},{"type":"List","props":{"size":"sm","spacing":"xs","mt":"xs"},"children":[{"type":"List.Item","children":"p95 latency ~90ms on cache hit"},{"type":"List.Item","children":"DB load drops ~60% on hot queries"},{"type":"List.Item","children":"Behaviour unchanged on a miss"}]}]}]},{"type":"Divider","props":{"label":"How a query flows now","labelPosition":"center"}},{"type":"Timeline","props":{"active":3,"bulletSize":22,"lineWidth":2},"children":[{"type":"Timeline.Item","props":{"title":"1 — Request"},"children":[{"type":"Text","props":{"size":"sm","c":"dimmed"},"children":"search(q) computes a cache key from the normalized query + filters."}]},{"type":"Timeline.Item","props":{"title":"2 — Lookup"},"children":[{"type":"Text","props":{"size":"sm","c":"dimmed"},"children":"On a hit (TTL 30s), return the cached page; on a miss, fall through to the DB."}]},{"type":"Timeline.Item","props":{"title":"3 — Fill"},"children":[{"type":"Text","props":{"size":"sm","c":"dimmed"},"children":"DB result is cached before returning, so the next identical query is a hit."}]},{"type":"Timeline.Item","props":{"title":"4 — Invalidate"},"children":[{"type":"Text","props":{"size":"sm","c":"dimmed"},"children":"Any write to the indexed tables clears matching keys, so results never go stale."}]}]},{"type":"Alert","props":{"color":"green","variant":"light","title":"Validated"},"children":"Load test + full suite: 0 regressions, p95 240ms → 90ms on the hot path, cache hit-rate 71%."},{"type":"Divider","props":{"label":"FAQ — questions we worked through","labelPosition":"center"}},{"type":"SimpleGrid","props":{"cols":2,"spacing":"sm"},"children":[{"type":"Card","props":{"withBorder":true,"padding":"sm"},"children":[{"type":"Text","props":{"fw":600,"size":"sm"},"children":"Why in-memory and not Redis?"},{"type":"Text","props":{"size":"sm","c":"dimmed","mt":4},"children":"Single-instance service; an in-process LRU avoids a network hop and a new dependency. Redis stays an option if we scale out."}]},{"type":"Card","props":{"withBorder":true,"padding":"sm"},"children":[{"type":"Text","props":{"fw":600,"size":"sm"},"children":"How do we avoid stale results?"},{"type":"Text","props":{"size":"sm","c":"dimmed","mt":4},"children":"Writes to the indexed tables invalidate matching keys; TTL is a 30s backstop. Reads after a write always reflect it."}]},{"type":"Card","props":{"withBorder":true,"padding":"sm"},"children":[{"type":"Text","props":{"fw":600,"size":"sm"},"children":"What's the memory ceiling?"},{"type":"Text","props":{"size":"sm","c":"dimmed","mt":4},"children":"LRU capped at 5k entries (~40MB). Evicts least-recently-used; bounded regardless of traffic."}]},{"type":"Card","props":{"withBorder":true,"padding":"sm"},"children":[{"type":"Text","props":{"fw":600,"size":"sm"},"children":"Does this change the API?"},{"type":"Text","props":{"size":"sm","c":"dimmed","mt":4},"children":"No. search() is unchanged for callers — caching is transparent and can be disabled with a flag."}]}]}]} diff --git a/plugin/skills/diagram-recipes/examples/pr-review.flow.json b/plugin/skills/diagram-recipes/examples/pr-review.flow.json index 509e70c..d429c00 100644 --- a/plugin/skills/diagram-recipes/examples/pr-review.flow.json +++ b/plugin/skills/diagram-recipes/examples/pr-review.flow.json @@ -1,187 +1 @@ -{ - "direction": "TB", - "legend": [ - { - "label": "added", - "color": "#22c55e" - }, - { - "label": "removed", - "color": "#ef4444", - "dash": true - }, - { - "label": "modified", - "color": "#fbbf24" - }, - { - "label": "unchanged", - "color": "#52525b" - } - ], - "nodes": [ - { - "id": "auth", - "type": "change", - "data": { - "label": "auth", - "kind": "module", - "status": "modified", - "sub": "3 files touched" - } - }, - { - "id": "login", - "type": "change", - "data": { - "label": "login()", - "kind": "fn", - "status": "unchanged", - "sub": "auth/login.ts" - } - }, - { - "id": "validate", - "type": "change", - "data": { - "label": "validateToken()", - "kind": "fn", - "status": "added", - "sub": "auth/token.ts", - "meta": "+42" - } - }, - { - "id": "legacy", - "type": "change", - "data": { - "label": "legacyHash()", - "kind": "fn", - "status": "removed", - "sub": "auth/hash.ts", - "meta": "−31" - } - }, - { - "id": "refresh", - "type": "change", - "data": { - "label": "refreshToken()", - "kind": "fn", - "status": "added", - "sub": "auth/token.ts", - "meta": "+28" - } - }, - { - "id": "session", - "type": "change", - "data": { - "label": "Session", - "kind": "class", - "status": "modified", - "sub": "auth/session.ts", - "meta": "+12 −5" - } - }, - { - "id": "db", - "type": "change", - "data": { - "label": "db", - "kind": "module", - "status": "unchanged" - } - } - ], - "edges": [ - { - "id": "e1", - "source": "auth", - "target": "login", - "markerEnd": { - "type": "arrowclosed", - "color": "#52525b" - }, - "style": { - "stroke": "#52525b" - } - }, - { - "id": "e2", - "source": "auth", - "target": "validate", - "markerEnd": { - "type": "arrowclosed", - "color": "#22c55e" - }, - "style": { - "stroke": "#22c55e", - "strokeWidth": 2 - } - }, - { - "id": "e3", - "source": "auth", - "target": "legacy", - "markerEnd": { - "type": "arrowclosed", - "color": "#ef4444" - }, - "style": { - "stroke": "#ef4444", - "strokeDasharray": "6 4" - } - }, - { - "id": "e4", - "source": "auth", - "target": "refresh", - "markerEnd": { - "type": "arrowclosed", - "color": "#22c55e" - }, - "style": { - "stroke": "#22c55e", - "strokeWidth": 2 - } - }, - { - "id": "e5", - "source": "login", - "target": "session", - "markerEnd": { - "type": "arrowclosed", - "color": "#52525b" - }, - "style": { - "stroke": "#52525b" - } - }, - { - "id": "e6", - "source": "validate", - "target": "session", - "markerEnd": { - "type": "arrowclosed", - "color": "#22c55e" - }, - "style": { - "stroke": "#22c55e", - "strokeWidth": 2 - } - }, - { - "id": "e7", - "source": "session", - "target": "db", - "markerEnd": { - "type": "arrowclosed", - "color": "#52525b" - }, - "style": { - "stroke": "#52525b" - } - } - ] -} +{"direction":"TB","legend":[{"label":"added","color":"#22c55e"},{"label":"removed","color":"#ef4444","dash":true},{"label":"modified","color":"#fbbf24"},{"label":"unchanged","color":"#52525b"}],"nodes":[{"id":"auth","type":"change","data":{"label":"auth","kind":"module","status":"modified","sub":"3 files touched"}},{"id":"login","type":"change","data":{"label":"login()","kind":"fn","status":"unchanged","sub":"auth/login.ts"}},{"id":"validate","type":"change","data":{"label":"validateToken()","kind":"fn","status":"added","sub":"auth/token.ts","meta":"+42"}},{"id":"legacy","type":"change","data":{"label":"legacyHash()","kind":"fn","status":"removed","sub":"auth/hash.ts","meta":"−31"}},{"id":"refresh","type":"change","data":{"label":"refreshToken()","kind":"fn","status":"added","sub":"auth/token.ts","meta":"+28"}},{"id":"session","type":"change","data":{"label":"Session","kind":"class","status":"modified","sub":"auth/session.ts","meta":"+12 −5"}},{"id":"db","type":"change","data":{"label":"db","kind":"module","status":"unchanged"}}],"edges":[{"id":"e1","source":"auth","target":"login","markerEnd":{"type":"arrowclosed","color":"#52525b"},"style":{"stroke":"#52525b"}},{"id":"e2","source":"auth","target":"validate","markerEnd":{"type":"arrowclosed","color":"#22c55e"},"style":{"stroke":"#22c55e","strokeWidth":2}},{"id":"e3","source":"auth","target":"legacy","markerEnd":{"type":"arrowclosed","color":"#ef4444"},"style":{"stroke":"#ef4444","strokeDasharray":"6 4"}},{"id":"e4","source":"auth","target":"refresh","markerEnd":{"type":"arrowclosed","color":"#22c55e"},"style":{"stroke":"#22c55e","strokeWidth":2}},{"id":"e5","source":"login","target":"session","markerEnd":{"type":"arrowclosed","color":"#52525b"},"style":{"stroke":"#52525b"}},{"id":"e6","source":"validate","target":"session","markerEnd":{"type":"arrowclosed","color":"#22c55e"},"style":{"stroke":"#22c55e","strokeWidth":2}},{"id":"e7","source":"session","target":"db","markerEnd":{"type":"arrowclosed","color":"#52525b"},"style":{"stroke":"#52525b"}}]} diff --git a/plugin/skills/diagram-recipes/examples/product-comparison.component.json b/plugin/skills/diagram-recipes/examples/product-comparison.component.json index a5b2165..828a16e 100644 --- a/plugin/skills/diagram-recipes/examples/product-comparison.component.json +++ b/plugin/skills/diagram-recipes/examples/product-comparison.component.json @@ -1,430 +1 @@ -{ - "type": "Stack", - "props": { - "gap": "xs", - "p": "md" - }, - "children": [ - { - "type": "Title", - "props": { - "order": 3 - }, - "children": "Compare: noise-cancelling headphones" - }, - { - "type": "Text", - "props": { - "c": "dimmed", - "size": "sm" - }, - "children": "Three picks for travel — each photo, name, and “View deal” links out to the product page." - }, - { - "type": "SimpleGrid", - "props": { - "cols": 3, - "spacing": "md", - "mt": "xs" - }, - "children": [ - { - "type": "Card", - "props": { - "withBorder": true, - "padding": 0, - "radius": "md" - }, - "children": [ - { - "type": "Card.Section", - "children": { - "type": "Anchor", - "props": { - "href": "https://electronics.sony.com/headphones", - "target": "_blank", - "rel": "noopener noreferrer" - }, - "children": { - "type": "Image", - "props": { - "src": "data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22360%22%20height%3D%22170%22%20viewBox%3D%220%200%20360%20170%22%3E%0A%3Cdefs%3E%3ClinearGradient%20id%3D%22g%22%20x1%3D%220%22%20y1%3D%220%22%20x2%3D%221%22%20y2%3D%221%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%236d28d9%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%232563eb%22%2F%3E%3C%2FlinearGradient%3E%0A%3CradialGradient%20id%3D%22r%22%20cx%3D%220.5%22%20cy%3D%220.42%22%20r%3D%220.6%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%23ffffff%22%20stop-opacity%3D%220.18%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%23ffffff%22%20stop-opacity%3D%220%22%2F%3E%3C%2FradialGradient%3E%3C%2Fdefs%3E%0A%3Crect%20width%3D%22360%22%20height%3D%22170%22%20fill%3D%22url(%23g)%22%2F%3E%3Crect%20width%3D%22360%22%20height%3D%22170%22%20fill%3D%22url(%23r)%22%2F%3E%0A%3Cg%20fill%3D%22none%22%20stroke%3D%22%23ffffff%22%20stroke-width%3D%2213%22%20stroke-linecap%3D%22round%22%20opacity%3D%220.96%22%3E%0A%3Cpath%20d%3D%22M132%2096%20Q180%2036%20228%2096%22%2F%3E%3C%2Fg%3E%0A%3Crect%20x%3D%22120%22%20y%3D%2286%22%20width%3D%2226%22%20height%3D%2254%22%20rx%3D%2213%22%20fill%3D%22%23ffffff%22%20opacity%3D%220.96%22%2F%3E%0A%3Crect%20x%3D%22214%22%20y%3D%2286%22%20width%3D%2226%22%20height%3D%2254%22%20rx%3D%2213%22%20fill%3D%22%23ffffff%22%20opacity%3D%220.96%22%2F%3E%0A%3Crect%20x%3D%22124%22%20y%3D%2296%22%20width%3D%2218%22%20height%3D%2234%22%20rx%3D%229%22%20fill%3D%22%236d28d9%22%20opacity%3D%220.55%22%2F%3E%0A%3Crect%20x%3D%22218%22%20y%3D%2296%22%20width%3D%2218%22%20height%3D%2234%22%20rx%3D%229%22%20fill%3D%22%236d28d9%22%20opacity%3D%220.55%22%2F%3E%3C%2Fsvg%3E", - "alt": "Sony WH-1000XM6 product photo", - "h": 150, - "fit": "cover" - } - } - } - }, - { - "type": "Stack", - "props": { - "gap": 6, - "p": "md" - }, - "children": [ - { - "type": "Group", - "props": { - "justify": "space-between", - "wrap": "nowrap", - "align": "center" - }, - "children": [ - { - "type": "Anchor", - "props": { - "href": "https://electronics.sony.com/headphones", - "target": "_blank", - "rel": "noopener noreferrer", - "fw": 700, - "c": "var(--fg)", - "underline": "never" - }, - "children": "Sony WH-1000XM6" - }, - { - "type": "Badge", - "props": { - "color": "blue", - "size": "sm", - "variant": "light" - }, - "children": "Popular" - } - ] - }, - { - "type": "Group", - "props": { - "justify": "space-between", - "align": "center" - }, - "children": [ - { - "type": "Text", - "props": { - "fw": 700, - "size": "xl" - }, - "children": "$429" - }, - { - "type": "Badge", - "props": { - "color": "yellow", - "variant": "light" - }, - "children": "★ 4.7" - } - ] - }, - { - "type": "Badge", - "props": { - "color": "green", - "variant": "light", - "radius": "sm" - }, - "children": "Best overall" - }, - { - "type": "List", - "props": { - "size": "sm", - "spacing": 4, - "mt": 4 - }, - "children": [ - { - "type": "List.Item", - "children": "Industry-leading ANC" - }, - { - "type": "List.Item", - "children": "30h battery life" - }, - { - "type": "List.Item", - "children": "Multipoint Bluetooth" - } - ] - }, - { - "type": "Anchor", - "props": { - "href": "https://electronics.sony.com/headphones", - "target": "_blank", - "rel": "noopener noreferrer", - "mt": 4, - "fw": 600, - "size": "sm" - }, - "children": "View deal →" - } - ] - } - ] - }, - { - "type": "Card", - "props": { - "withBorder": true, - "padding": 0, - "radius": "md" - }, - "children": [ - { - "type": "Card.Section", - "children": { - "type": "Anchor", - "props": { - "href": "https://www.bose.com", - "target": "_blank", - "rel": "noopener noreferrer" - }, - "children": { - "type": "Image", - "props": { - "src": "data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22360%22%20height%3D%22170%22%20viewBox%3D%220%200%20360%20170%22%3E%0A%3Cdefs%3E%3ClinearGradient%20id%3D%22g%22%20x1%3D%220%22%20y1%3D%220%22%20x2%3D%221%22%20y2%3D%221%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%230f766e%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%230ea5e9%22%2F%3E%3C%2FlinearGradient%3E%0A%3CradialGradient%20id%3D%22r%22%20cx%3D%220.5%22%20cy%3D%220.42%22%20r%3D%220.6%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%23ffffff%22%20stop-opacity%3D%220.18%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%23ffffff%22%20stop-opacity%3D%220%22%2F%3E%3C%2FradialGradient%3E%3C%2Fdefs%3E%0A%3Crect%20width%3D%22360%22%20height%3D%22170%22%20fill%3D%22url(%23g)%22%2F%3E%3Crect%20width%3D%22360%22%20height%3D%22170%22%20fill%3D%22url(%23r)%22%2F%3E%0A%3Cg%20fill%3D%22none%22%20stroke%3D%22%23ffffff%22%20stroke-width%3D%2213%22%20stroke-linecap%3D%22round%22%20opacity%3D%220.96%22%3E%0A%3Cpath%20d%3D%22M132%2096%20Q180%2036%20228%2096%22%2F%3E%3C%2Fg%3E%0A%3Crect%20x%3D%22120%22%20y%3D%2286%22%20width%3D%2226%22%20height%3D%2254%22%20rx%3D%2213%22%20fill%3D%22%23ffffff%22%20opacity%3D%220.96%22%2F%3E%0A%3Crect%20x%3D%22214%22%20y%3D%2286%22%20width%3D%2226%22%20height%3D%2254%22%20rx%3D%2213%22%20fill%3D%22%23ffffff%22%20opacity%3D%220.96%22%2F%3E%0A%3Crect%20x%3D%22124%22%20y%3D%2296%22%20width%3D%2218%22%20height%3D%2234%22%20rx%3D%229%22%20fill%3D%22%230f766e%22%20opacity%3D%220.55%22%2F%3E%0A%3Crect%20x%3D%22218%22%20y%3D%2296%22%20width%3D%2218%22%20height%3D%2234%22%20rx%3D%229%22%20fill%3D%22%230f766e%22%20opacity%3D%220.55%22%2F%3E%3C%2Fsvg%3E", - "alt": "Bose QC Ultra product photo", - "h": 150, - "fit": "cover" - } - } - } - }, - { - "type": "Stack", - "props": { - "gap": 6, - "p": "md" - }, - "children": [ - { - "type": "Group", - "props": { - "justify": "space-between", - "wrap": "nowrap", - "align": "center" - }, - "children": [ - { - "type": "Anchor", - "props": { - "href": "https://www.bose.com", - "target": "_blank", - "rel": "noopener noreferrer", - "fw": 700, - "c": "var(--fg)", - "underline": "never" - }, - "children": "Bose QC Ultra" - } - ] - }, - { - "type": "Group", - "props": { - "justify": "space-between", - "align": "center" - }, - "children": [ - { - "type": "Text", - "props": { - "fw": 700, - "size": "xl" - }, - "children": "$429" - }, - { - "type": "Badge", - "props": { - "color": "yellow", - "variant": "light" - }, - "children": "★ 4.6" - } - ] - }, - { - "type": "Badge", - "props": { - "color": "blue", - "variant": "light", - "radius": "sm" - }, - "children": "Most comfortable" - }, - { - "type": "List", - "props": { - "size": "sm", - "spacing": 4, - "mt": 4 - }, - "children": [ - { - "type": "List.Item", - "children": "Plush all-day fit" - }, - { - "type": "List.Item", - "children": "Immersive spatial audio" - }, - { - "type": "List.Item", - "children": "Excellent ANC" - } - ] - }, - { - "type": "Anchor", - "props": { - "href": "https://www.bose.com", - "target": "_blank", - "rel": "noopener noreferrer", - "mt": 4, - "fw": 600, - "size": "sm" - }, - "children": "View deal →" - } - ] - } - ] - }, - { - "type": "Card", - "props": { - "withBorder": true, - "padding": 0, - "radius": "md" - }, - "children": [ - { - "type": "Card.Section", - "children": { - "type": "Anchor", - "props": { - "href": "https://www.sonos.com/ace", - "target": "_blank", - "rel": "noopener noreferrer" - }, - "children": { - "type": "Image", - "props": { - "src": "data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22360%22%20height%3D%22170%22%20viewBox%3D%220%200%20360%20170%22%3E%0A%3Cdefs%3E%3ClinearGradient%20id%3D%22g%22%20x1%3D%220%22%20y1%3D%220%22%20x2%3D%221%22%20y2%3D%221%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%239333ea%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%23db2777%22%2F%3E%3C%2FlinearGradient%3E%0A%3CradialGradient%20id%3D%22r%22%20cx%3D%220.5%22%20cy%3D%220.42%22%20r%3D%220.6%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%23ffffff%22%20stop-opacity%3D%220.18%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%23ffffff%22%20stop-opacity%3D%220%22%2F%3E%3C%2FradialGradient%3E%3C%2Fdefs%3E%0A%3Crect%20width%3D%22360%22%20height%3D%22170%22%20fill%3D%22url(%23g)%22%2F%3E%3Crect%20width%3D%22360%22%20height%3D%22170%22%20fill%3D%22url(%23r)%22%2F%3E%0A%3Cg%20fill%3D%22none%22%20stroke%3D%22%23ffffff%22%20stroke-width%3D%2213%22%20stroke-linecap%3D%22round%22%20opacity%3D%220.96%22%3E%0A%3Cpath%20d%3D%22M132%2096%20Q180%2036%20228%2096%22%2F%3E%3C%2Fg%3E%0A%3Crect%20x%3D%22120%22%20y%3D%2286%22%20width%3D%2226%22%20height%3D%2254%22%20rx%3D%2213%22%20fill%3D%22%23ffffff%22%20opacity%3D%220.96%22%2F%3E%0A%3Crect%20x%3D%22214%22%20y%3D%2286%22%20width%3D%2226%22%20height%3D%2254%22%20rx%3D%2213%22%20fill%3D%22%23ffffff%22%20opacity%3D%220.96%22%2F%3E%0A%3Crect%20x%3D%22124%22%20y%3D%2296%22%20width%3D%2218%22%20height%3D%2234%22%20rx%3D%229%22%20fill%3D%22%239333ea%22%20opacity%3D%220.55%22%2F%3E%0A%3Crect%20x%3D%22218%22%20y%3D%2296%22%20width%3D%2218%22%20height%3D%2234%22%20rx%3D%229%22%20fill%3D%22%239333ea%22%20opacity%3D%220.55%22%2F%3E%3C%2Fsvg%3E", - "alt": "Sonos Ace product photo", - "h": 150, - "fit": "cover" - } - } - } - }, - { - "type": "Stack", - "props": { - "gap": 6, - "p": "md" - }, - "children": [ - { - "type": "Group", - "props": { - "justify": "space-between", - "wrap": "nowrap", - "align": "center" - }, - "children": [ - { - "type": "Anchor", - "props": { - "href": "https://www.sonos.com/ace", - "target": "_blank", - "rel": "noopener noreferrer", - "fw": 700, - "c": "var(--fg)", - "underline": "never" - }, - "children": "Sonos Ace" - } - ] - }, - { - "type": "Group", - "props": { - "justify": "space-between", - "align": "center" - }, - "children": [ - { - "type": "Text", - "props": { - "fw": 700, - "size": "xl" - }, - "children": "$449" - }, - { - "type": "Badge", - "props": { - "color": "yellow", - "variant": "light" - }, - "children": "★ 4.4" - } - ] - }, - { - "type": "Badge", - "props": { - "color": "grape", - "variant": "light", - "radius": "sm" - }, - "children": "Best for TV" - }, - { - "type": "List", - "props": { - "size": "sm", - "spacing": 4, - "mt": 4 - }, - "children": [ - { - "type": "List.Item", - "children": "TV audio swap" - }, - { - "type": "List.Item", - "children": "Lossless USB-C" - }, - { - "type": "List.Item", - "children": "Spatial audio + head tracking" - } - ] - }, - { - "type": "Anchor", - "props": { - "href": "https://www.sonos.com/ace", - "target": "_blank", - "rel": "noopener noreferrer", - "mt": 4, - "fw": 600, - "size": "sm" - }, - "children": "View deal →" - } - ] - } - ] - } - ] - } - ] -} +{"type":"Stack","props":{"gap":"xs","p":"md"},"children":[{"type":"Title","props":{"order":3},"children":"Compare: noise-cancelling headphones"},{"type":"Text","props":{"c":"dimmed","size":"sm"},"children":"Three picks for travel — each photo, name, and “View deal” links out to the product page."},{"type":"SimpleGrid","props":{"cols":3,"spacing":"md","mt":"xs"},"children":[{"type":"Card","props":{"withBorder":true,"padding":0,"radius":"md"},"children":[{"type":"Card.Section","children":{"type":"Anchor","props":{"href":"https://electronics.sony.com/headphones","target":"_blank","rel":"noopener noreferrer"},"children":{"type":"Image","props":{"src":"data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22360%22%20height%3D%22170%22%20viewBox%3D%220%200%20360%20170%22%3E%0A%3Cdefs%3E%3ClinearGradient%20id%3D%22g%22%20x1%3D%220%22%20y1%3D%220%22%20x2%3D%221%22%20y2%3D%221%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%236d28d9%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%232563eb%22%2F%3E%3C%2FlinearGradient%3E%0A%3CradialGradient%20id%3D%22r%22%20cx%3D%220.5%22%20cy%3D%220.42%22%20r%3D%220.6%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%23ffffff%22%20stop-opacity%3D%220.18%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%23ffffff%22%20stop-opacity%3D%220%22%2F%3E%3C%2FradialGradient%3E%3C%2Fdefs%3E%0A%3Crect%20width%3D%22360%22%20height%3D%22170%22%20fill%3D%22url(%23g)%22%2F%3E%3Crect%20width%3D%22360%22%20height%3D%22170%22%20fill%3D%22url(%23r)%22%2F%3E%0A%3Cg%20fill%3D%22none%22%20stroke%3D%22%23ffffff%22%20stroke-width%3D%2213%22%20stroke-linecap%3D%22round%22%20opacity%3D%220.96%22%3E%0A%3Cpath%20d%3D%22M132%2096%20Q180%2036%20228%2096%22%2F%3E%3C%2Fg%3E%0A%3Crect%20x%3D%22120%22%20y%3D%2286%22%20width%3D%2226%22%20height%3D%2254%22%20rx%3D%2213%22%20fill%3D%22%23ffffff%22%20opacity%3D%220.96%22%2F%3E%0A%3Crect%20x%3D%22214%22%20y%3D%2286%22%20width%3D%2226%22%20height%3D%2254%22%20rx%3D%2213%22%20fill%3D%22%23ffffff%22%20opacity%3D%220.96%22%2F%3E%0A%3Crect%20x%3D%22124%22%20y%3D%2296%22%20width%3D%2218%22%20height%3D%2234%22%20rx%3D%229%22%20fill%3D%22%236d28d9%22%20opacity%3D%220.55%22%2F%3E%0A%3Crect%20x%3D%22218%22%20y%3D%2296%22%20width%3D%2218%22%20height%3D%2234%22%20rx%3D%229%22%20fill%3D%22%236d28d9%22%20opacity%3D%220.55%22%2F%3E%3C%2Fsvg%3E","alt":"Sony WH-1000XM6 product photo","h":150,"fit":"cover"}}}},{"type":"Stack","props":{"gap":6,"p":"md"},"children":[{"type":"Group","props":{"justify":"space-between","wrap":"nowrap","align":"center"},"children":[{"type":"Anchor","props":{"href":"https://electronics.sony.com/headphones","target":"_blank","rel":"noopener noreferrer","fw":700,"c":"var(--fg)","underline":"never"},"children":"Sony WH-1000XM6"},{"type":"Badge","props":{"color":"blue","size":"sm","variant":"light"},"children":"Popular"}]},{"type":"Group","props":{"justify":"space-between","align":"center"},"children":[{"type":"Text","props":{"fw":700,"size":"xl"},"children":"$429"},{"type":"Badge","props":{"color":"yellow","variant":"light"},"children":"★ 4.7"}]},{"type":"Badge","props":{"color":"green","variant":"light","radius":"sm"},"children":"Best overall"},{"type":"List","props":{"size":"sm","spacing":4,"mt":4},"children":[{"type":"List.Item","children":"Industry-leading ANC"},{"type":"List.Item","children":"30h battery life"},{"type":"List.Item","children":"Multipoint Bluetooth"}]},{"type":"Anchor","props":{"href":"https://electronics.sony.com/headphones","target":"_blank","rel":"noopener noreferrer","mt":4,"fw":600,"size":"sm"},"children":"View deal →"}]}]},{"type":"Card","props":{"withBorder":true,"padding":0,"radius":"md"},"children":[{"type":"Card.Section","children":{"type":"Anchor","props":{"href":"https://www.bose.com","target":"_blank","rel":"noopener noreferrer"},"children":{"type":"Image","props":{"src":"data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22360%22%20height%3D%22170%22%20viewBox%3D%220%200%20360%20170%22%3E%0A%3Cdefs%3E%3ClinearGradient%20id%3D%22g%22%20x1%3D%220%22%20y1%3D%220%22%20x2%3D%221%22%20y2%3D%221%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%230f766e%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%230ea5e9%22%2F%3E%3C%2FlinearGradient%3E%0A%3CradialGradient%20id%3D%22r%22%20cx%3D%220.5%22%20cy%3D%220.42%22%20r%3D%220.6%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%23ffffff%22%20stop-opacity%3D%220.18%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%23ffffff%22%20stop-opacity%3D%220%22%2F%3E%3C%2FradialGradient%3E%3C%2Fdefs%3E%0A%3Crect%20width%3D%22360%22%20height%3D%22170%22%20fill%3D%22url(%23g)%22%2F%3E%3Crect%20width%3D%22360%22%20height%3D%22170%22%20fill%3D%22url(%23r)%22%2F%3E%0A%3Cg%20fill%3D%22none%22%20stroke%3D%22%23ffffff%22%20stroke-width%3D%2213%22%20stroke-linecap%3D%22round%22%20opacity%3D%220.96%22%3E%0A%3Cpath%20d%3D%22M132%2096%20Q180%2036%20228%2096%22%2F%3E%3C%2Fg%3E%0A%3Crect%20x%3D%22120%22%20y%3D%2286%22%20width%3D%2226%22%20height%3D%2254%22%20rx%3D%2213%22%20fill%3D%22%23ffffff%22%20opacity%3D%220.96%22%2F%3E%0A%3Crect%20x%3D%22214%22%20y%3D%2286%22%20width%3D%2226%22%20height%3D%2254%22%20rx%3D%2213%22%20fill%3D%22%23ffffff%22%20opacity%3D%220.96%22%2F%3E%0A%3Crect%20x%3D%22124%22%20y%3D%2296%22%20width%3D%2218%22%20height%3D%2234%22%20rx%3D%229%22%20fill%3D%22%230f766e%22%20opacity%3D%220.55%22%2F%3E%0A%3Crect%20x%3D%22218%22%20y%3D%2296%22%20width%3D%2218%22%20height%3D%2234%22%20rx%3D%229%22%20fill%3D%22%230f766e%22%20opacity%3D%220.55%22%2F%3E%3C%2Fsvg%3E","alt":"Bose QC Ultra product photo","h":150,"fit":"cover"}}}},{"type":"Stack","props":{"gap":6,"p":"md"},"children":[{"type":"Group","props":{"justify":"space-between","wrap":"nowrap","align":"center"},"children":[{"type":"Anchor","props":{"href":"https://www.bose.com","target":"_blank","rel":"noopener noreferrer","fw":700,"c":"var(--fg)","underline":"never"},"children":"Bose QC Ultra"}]},{"type":"Group","props":{"justify":"space-between","align":"center"},"children":[{"type":"Text","props":{"fw":700,"size":"xl"},"children":"$429"},{"type":"Badge","props":{"color":"yellow","variant":"light"},"children":"★ 4.6"}]},{"type":"Badge","props":{"color":"blue","variant":"light","radius":"sm"},"children":"Most comfortable"},{"type":"List","props":{"size":"sm","spacing":4,"mt":4},"children":[{"type":"List.Item","children":"Plush all-day fit"},{"type":"List.Item","children":"Immersive spatial audio"},{"type":"List.Item","children":"Excellent ANC"}]},{"type":"Anchor","props":{"href":"https://www.bose.com","target":"_blank","rel":"noopener noreferrer","mt":4,"fw":600,"size":"sm"},"children":"View deal →"}]}]},{"type":"Card","props":{"withBorder":true,"padding":0,"radius":"md"},"children":[{"type":"Card.Section","children":{"type":"Anchor","props":{"href":"https://www.sonos.com/ace","target":"_blank","rel":"noopener noreferrer"},"children":{"type":"Image","props":{"src":"data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22360%22%20height%3D%22170%22%20viewBox%3D%220%200%20360%20170%22%3E%0A%3Cdefs%3E%3ClinearGradient%20id%3D%22g%22%20x1%3D%220%22%20y1%3D%220%22%20x2%3D%221%22%20y2%3D%221%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%239333ea%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%23db2777%22%2F%3E%3C%2FlinearGradient%3E%0A%3CradialGradient%20id%3D%22r%22%20cx%3D%220.5%22%20cy%3D%220.42%22%20r%3D%220.6%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%23ffffff%22%20stop-opacity%3D%220.18%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%23ffffff%22%20stop-opacity%3D%220%22%2F%3E%3C%2FradialGradient%3E%3C%2Fdefs%3E%0A%3Crect%20width%3D%22360%22%20height%3D%22170%22%20fill%3D%22url(%23g)%22%2F%3E%3Crect%20width%3D%22360%22%20height%3D%22170%22%20fill%3D%22url(%23r)%22%2F%3E%0A%3Cg%20fill%3D%22none%22%20stroke%3D%22%23ffffff%22%20stroke-width%3D%2213%22%20stroke-linecap%3D%22round%22%20opacity%3D%220.96%22%3E%0A%3Cpath%20d%3D%22M132%2096%20Q180%2036%20228%2096%22%2F%3E%3C%2Fg%3E%0A%3Crect%20x%3D%22120%22%20y%3D%2286%22%20width%3D%2226%22%20height%3D%2254%22%20rx%3D%2213%22%20fill%3D%22%23ffffff%22%20opacity%3D%220.96%22%2F%3E%0A%3Crect%20x%3D%22214%22%20y%3D%2286%22%20width%3D%2226%22%20height%3D%2254%22%20rx%3D%2213%22%20fill%3D%22%23ffffff%22%20opacity%3D%220.96%22%2F%3E%0A%3Crect%20x%3D%22124%22%20y%3D%2296%22%20width%3D%2218%22%20height%3D%2234%22%20rx%3D%229%22%20fill%3D%22%239333ea%22%20opacity%3D%220.55%22%2F%3E%0A%3Crect%20x%3D%22218%22%20y%3D%2296%22%20width%3D%2218%22%20height%3D%2234%22%20rx%3D%229%22%20fill%3D%22%239333ea%22%20opacity%3D%220.55%22%2F%3E%3C%2Fsvg%3E","alt":"Sonos Ace product photo","h":150,"fit":"cover"}}}},{"type":"Stack","props":{"gap":6,"p":"md"},"children":[{"type":"Group","props":{"justify":"space-between","wrap":"nowrap","align":"center"},"children":[{"type":"Anchor","props":{"href":"https://www.sonos.com/ace","target":"_blank","rel":"noopener noreferrer","fw":700,"c":"var(--fg)","underline":"never"},"children":"Sonos Ace"}]},{"type":"Group","props":{"justify":"space-between","align":"center"},"children":[{"type":"Text","props":{"fw":700,"size":"xl"},"children":"$449"},{"type":"Badge","props":{"color":"yellow","variant":"light"},"children":"★ 4.4"}]},{"type":"Badge","props":{"color":"grape","variant":"light","radius":"sm"},"children":"Best for TV"},{"type":"List","props":{"size":"sm","spacing":4,"mt":4},"children":[{"type":"List.Item","children":"TV audio swap"},{"type":"List.Item","children":"Lossless USB-C"},{"type":"List.Item","children":"Spatial audio + head tracking"}]},{"type":"Anchor","props":{"href":"https://www.sonos.com/ace","target":"_blank","rel":"noopener noreferrer","mt":4,"fw":600,"size":"sm"},"children":"View deal →"}]}]}]}]} diff --git a/plugin/skills/diagram-recipes/examples/query-plan.flow.json b/plugin/skills/diagram-recipes/examples/query-plan.flow.json index 1bef0d4..198dbc5 100644 --- a/plugin/skills/diagram-recipes/examples/query-plan.flow.json +++ b/plugin/skills/diagram-recipes/examples/query-plan.flow.json @@ -1,25 +1 @@ -{ - "direction": "TB", - "legend": [ - { "label": "cheap", "color": "#22c55e" }, - { "label": "moderate", "color": "#fbbf24" }, - { "label": "hot / costly", "color": "#ef4444" } - ], - "nodes": [ - { "id": "limit", "type": "change", "data": { "label": "Limit", "status": "neutral", "kind": "100 rows", "meta": "cost 0.0–9.2" } }, - { "id": "sort", "type": "change", "data": { "label": "Sort", "status": "active", "kind": "by revenue desc", "sub": "ext. merge, 18MB", "meta": "cost 9.2" } }, - { "id": "agg", "type": "change", "data": { "label": "HashAggregate", "status": "active", "kind": "group by customer_id", "meta": "rows 48k · cost 7.1" } }, - { "id": "join", "type": "change", "data": { "label": "Hash Join", "status": "warn", "kind": "orders.customer_id = c.id", "meta": "rows 1.2M · cost 5.4" } }, - { "id": "scan_orders", "type": "change", "data": { "label": "Seq Scan · orders", "status": "error", "icon": "warn", "sub": "filter: created_at > now()-30d", "meta": "rows 1.2M · cost 3.9 ⚠" } }, - { "id": "hash", "type": "change", "data": { "label": "Hash", "status": "neutral", "meta": "rows 48k" } }, - { "id": "scan_cust", "type": "change", "data": { "label": "Index Scan · customers_pkey", "status": "success", "icon": "check", "meta": "rows 48k · cost 0.4" } } - ], - "edges": [ - { "source": "limit", "target": "sort" }, - { "source": "sort", "target": "agg" }, - { "source": "agg", "target": "join" }, - { "source": "join", "target": "scan_orders", "label": "outer", "style": { "stroke": "#ef4444" }, "markerEnd": { "type": "arrowclosed", "color": "#ef4444" } }, - { "source": "join", "target": "hash", "label": "inner" }, - { "source": "hash", "target": "scan_cust", "style": { "stroke": "#22c55e" }, "markerEnd": { "type": "arrowclosed", "color": "#22c55e" } } - ] -} +{"direction":"TB","legend":[{"label":"cheap","color":"#22c55e"},{"label":"moderate","color":"#fbbf24"},{"label":"hot / costly","color":"#ef4444"}],"nodes":[{"id":"limit","type":"change","data":{"label":"Limit","status":"neutral","kind":"100 rows","meta":"cost 0.0–9.2"}},{"id":"sort","type":"change","data":{"label":"Sort","status":"active","kind":"by revenue desc","sub":"ext. merge, 18MB","meta":"cost 9.2"}},{"id":"agg","type":"change","data":{"label":"HashAggregate","status":"active","kind":"group by customer_id","meta":"rows 48k · cost 7.1"}},{"id":"join","type":"change","data":{"label":"Hash Join","status":"warn","kind":"orders.customer_id = c.id","meta":"rows 1.2M · cost 5.4"}},{"id":"scan_orders","type":"change","data":{"label":"Seq Scan · orders","status":"error","icon":"warn","sub":"filter: created_at > now()-30d","meta":"rows 1.2M · cost 3.9 ⚠"}},{"id":"hash","type":"change","data":{"label":"Hash","status":"neutral","meta":"rows 48k"}},{"id":"scan_cust","type":"change","data":{"label":"Index Scan · customers_pkey","status":"success","icon":"check","meta":"rows 48k · cost 0.4"}}],"edges":[{"source":"limit","target":"sort"},{"source":"sort","target":"agg"},{"source":"agg","target":"join"},{"source":"join","target":"scan_orders","label":"outer","style":{"stroke":"#ef4444"},"markerEnd":{"type":"arrowclosed","color":"#ef4444"}},{"source":"join","target":"hash","label":"inner"},{"source":"hash","target":"scan_cust","style":{"stroke":"#22c55e"},"markerEnd":{"type":"arrowclosed","color":"#22c55e"}}]} diff --git a/plugin/skills/diagram-recipes/examples/raci-matrix.component.json b/plugin/skills/diagram-recipes/examples/raci-matrix.component.json index 7a61237..f619daf 100644 --- a/plugin/skills/diagram-recipes/examples/raci-matrix.component.json +++ b/plugin/skills/diagram-recipes/examples/raci-matrix.component.json @@ -1,1495 +1 @@ -{ - "type": "Stack", - "props": { - "gap": "sm", - "p": "md" - }, - "children": [ - { - "type": "Title", - "props": { - "order": 3 - }, - "children": "RACI — launch responsibilities" - }, - { - "type": "Text", - "props": { - "c": "dimmed", - "size": "sm", - "mt": -6 - }, - "children": "who is Responsible / Accountable / Consulted / Informed per deliverable" - }, - { - "type": "Table", - "props": { - "withTableBorder": true, - "withColumnBorders": true, - "striped": true, - "verticalSpacing": "xs" - }, - "children": [ - { - "type": "Table.Thead", - "children": { - "type": "Table.Tr", - "children": [ - { - "type": "Table.Th", - "props": { - "style": { - "minWidth": 200 - } - }, - "children": { - "type": "Text", - "props": { - "size": "sm", - "fw": 700 - }, - "children": "Task / Deliverable" - } - }, - { - "type": "Table.Th", - "props": { - "style": { - "textAlign": "center" - } - }, - "children": { - "type": "Text", - "props": { - "ta": "center", - "size": "sm", - "fw": 700 - }, - "children": "PM" - } - }, - { - "type": "Table.Th", - "props": { - "style": { - "textAlign": "center" - } - }, - "children": { - "type": "Text", - "props": { - "ta": "center", - "size": "sm", - "fw": 700 - }, - "children": "Eng" - } - }, - { - "type": "Table.Th", - "props": { - "style": { - "textAlign": "center" - } - }, - "children": { - "type": "Text", - "props": { - "ta": "center", - "size": "sm", - "fw": 700 - }, - "children": "Design" - } - }, - { - "type": "Table.Th", - "props": { - "style": { - "textAlign": "center" - } - }, - "children": { - "type": "Text", - "props": { - "ta": "center", - "size": "sm", - "fw": 700 - }, - "children": "QA" - } - }, - { - "type": "Table.Th", - "props": { - "style": { - "textAlign": "center" - } - }, - "children": { - "type": "Text", - "props": { - "ta": "center", - "size": "sm", - "fw": 700 - }, - "children": "DevOps" - } - } - ] - } - }, - { - "type": "Table.Tbody", - "children": [ - { - "type": "Table.Tr", - "children": [ - { - "type": "Table.Td", - "children": { - "type": "Text", - "props": { - "size": "sm" - }, - "children": "Define requirements" - } - }, - { - "type": "Table.Td", - "props": { - "style": { - "textAlign": "center", - "verticalAlign": "middle" - } - }, - "children": { - "type": "Group", - "props": { - "justify": "center" - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "grape", - "variant": "filled", - "radius": "sm", - "w": 30, - "style": { - "fontWeight": 700 - } - }, - "children": "A" - } - ] - } - }, - { - "type": "Table.Td", - "props": { - "style": { - "textAlign": "center", - "verticalAlign": "middle" - } - }, - "children": { - "type": "Group", - "props": { - "justify": "center" - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "gray", - "variant": "outline", - "radius": "sm", - "w": 30, - "style": { - "fontWeight": 700 - } - }, - "children": "C" - } - ] - } - }, - { - "type": "Table.Td", - "props": { - "style": { - "textAlign": "center", - "verticalAlign": "middle" - } - }, - "children": { - "type": "Group", - "props": { - "justify": "center" - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "gray", - "variant": "outline", - "radius": "sm", - "w": 30, - "style": { - "fontWeight": 700 - } - }, - "children": "C" - } - ] - } - }, - { - "type": "Table.Td", - "props": { - "style": { - "textAlign": "center", - "verticalAlign": "middle" - } - }, - "children": { - "type": "Group", - "props": { - "justify": "center" - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "gray", - "variant": "light", - "radius": "sm", - "w": 30, - "style": { - "fontWeight": 700 - } - }, - "children": "I" - } - ] - } - }, - { - "type": "Table.Td", - "props": { - "style": { - "textAlign": "center", - "verticalAlign": "middle" - } - }, - "children": { - "type": "Group", - "props": { - "justify": "center" - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "gray", - "variant": "light", - "radius": "sm", - "w": 30, - "style": { - "fontWeight": 700 - } - }, - "children": "I" - } - ] - } - } - ] - }, - { - "type": "Table.Tr", - "children": [ - { - "type": "Table.Td", - "children": { - "type": "Text", - "props": { - "size": "sm" - }, - "children": "UX / wireframes" - } - }, - { - "type": "Table.Td", - "props": { - "style": { - "textAlign": "center", - "verticalAlign": "middle" - } - }, - "children": { - "type": "Group", - "props": { - "justify": "center" - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "gray", - "variant": "outline", - "radius": "sm", - "w": 30, - "style": { - "fontWeight": 700 - } - }, - "children": "C" - } - ] - } - }, - { - "type": "Table.Td", - "props": { - "style": { - "textAlign": "center", - "verticalAlign": "middle" - } - }, - "children": { - "type": "Group", - "props": { - "justify": "center" - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "gray", - "variant": "outline", - "radius": "sm", - "w": 30, - "style": { - "fontWeight": 700 - } - }, - "children": "C" - } - ] - } - }, - { - "type": "Table.Td", - "props": { - "style": { - "textAlign": "center", - "verticalAlign": "middle" - } - }, - "children": { - "type": "Group", - "props": { - "justify": "center" - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "grape", - "variant": "filled", - "radius": "sm", - "w": 30, - "style": { - "fontWeight": 700 - } - }, - "children": "A" - } - ] - } - }, - { - "type": "Table.Td", - "props": { - "style": { - "textAlign": "center", - "verticalAlign": "middle" - } - }, - "children": { - "type": "Group", - "props": { - "justify": "center" - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "gray", - "variant": "light", - "radius": "sm", - "w": 30, - "style": { - "fontWeight": 700 - } - }, - "children": "I" - } - ] - } - }, - { - "type": "Table.Td", - "props": { - "style": { - "textAlign": "center" - } - }, - "children": null - } - ] - }, - { - "type": "Table.Tr", - "children": [ - { - "type": "Table.Td", - "children": { - "type": "Text", - "props": { - "size": "sm" - }, - "children": "API design" - } - }, - { - "type": "Table.Td", - "props": { - "style": { - "textAlign": "center", - "verticalAlign": "middle" - } - }, - "children": { - "type": "Group", - "props": { - "justify": "center" - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "gray", - "variant": "outline", - "radius": "sm", - "w": 30, - "style": { - "fontWeight": 700 - } - }, - "children": "C" - } - ] - } - }, - { - "type": "Table.Td", - "props": { - "style": { - "textAlign": "center", - "verticalAlign": "middle" - } - }, - "children": { - "type": "Group", - "props": { - "justify": "center" - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "grape", - "variant": "filled", - "radius": "sm", - "w": 30, - "style": { - "fontWeight": 700 - } - }, - "children": "A" - } - ] - } - }, - { - "type": "Table.Td", - "props": { - "style": { - "textAlign": "center", - "verticalAlign": "middle" - } - }, - "children": { - "type": "Group", - "props": { - "justify": "center" - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "gray", - "variant": "light", - "radius": "sm", - "w": 30, - "style": { - "fontWeight": 700 - } - }, - "children": "I" - } - ] - } - }, - { - "type": "Table.Td", - "props": { - "style": { - "textAlign": "center", - "verticalAlign": "middle" - } - }, - "children": { - "type": "Group", - "props": { - "justify": "center" - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "gray", - "variant": "outline", - "radius": "sm", - "w": 30, - "style": { - "fontWeight": 700 - } - }, - "children": "C" - } - ] - } - }, - { - "type": "Table.Td", - "props": { - "style": { - "textAlign": "center", - "verticalAlign": "middle" - } - }, - "children": { - "type": "Group", - "props": { - "justify": "center" - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "gray", - "variant": "outline", - "radius": "sm", - "w": 30, - "style": { - "fontWeight": 700 - } - }, - "children": "C" - } - ] - } - } - ] - }, - { - "type": "Table.Tr", - "children": [ - { - "type": "Table.Td", - "children": { - "type": "Text", - "props": { - "size": "sm" - }, - "children": "Implementation" - } - }, - { - "type": "Table.Td", - "props": { - "style": { - "textAlign": "center", - "verticalAlign": "middle" - } - }, - "children": { - "type": "Group", - "props": { - "justify": "center" - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "gray", - "variant": "light", - "radius": "sm", - "w": 30, - "style": { - "fontWeight": 700 - } - }, - "children": "I" - } - ] - } - }, - { - "type": "Table.Td", - "props": { - "style": { - "textAlign": "center", - "verticalAlign": "middle" - } - }, - "children": { - "type": "Group", - "props": { - "justify": "center" - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "grape", - "variant": "filled", - "radius": "sm", - "w": 30, - "style": { - "fontWeight": 700 - } - }, - "children": "A" - } - ] - } - }, - { - "type": "Table.Td", - "props": { - "style": { - "textAlign": "center", - "verticalAlign": "middle" - } - }, - "children": { - "type": "Group", - "props": { - "justify": "center" - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "gray", - "variant": "outline", - "radius": "sm", - "w": 30, - "style": { - "fontWeight": 700 - } - }, - "children": "C" - } - ] - } - }, - { - "type": "Table.Td", - "props": { - "style": { - "textAlign": "center", - "verticalAlign": "middle" - } - }, - "children": { - "type": "Group", - "props": { - "justify": "center" - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "gray", - "variant": "outline", - "radius": "sm", - "w": 30, - "style": { - "fontWeight": 700 - } - }, - "children": "C" - } - ] - } - }, - { - "type": "Table.Td", - "props": { - "style": { - "textAlign": "center", - "verticalAlign": "middle" - } - }, - "children": { - "type": "Group", - "props": { - "justify": "center" - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "gray", - "variant": "outline", - "radius": "sm", - "w": 30, - "style": { - "fontWeight": 700 - } - }, - "children": "C" - } - ] - } - } - ] - }, - { - "type": "Table.Tr", - "children": [ - { - "type": "Table.Td", - "children": { - "type": "Text", - "props": { - "size": "sm" - }, - "children": "Test plan & QA" - } - }, - { - "type": "Table.Td", - "props": { - "style": { - "textAlign": "center", - "verticalAlign": "middle" - } - }, - "children": { - "type": "Group", - "props": { - "justify": "center" - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "gray", - "variant": "light", - "radius": "sm", - "w": 30, - "style": { - "fontWeight": 700 - } - }, - "children": "I" - } - ] - } - }, - { - "type": "Table.Td", - "props": { - "style": { - "textAlign": "center", - "verticalAlign": "middle" - } - }, - "children": { - "type": "Group", - "props": { - "justify": "center" - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "gray", - "variant": "outline", - "radius": "sm", - "w": 30, - "style": { - "fontWeight": 700 - } - }, - "children": "C" - } - ] - } - }, - { - "type": "Table.Td", - "props": { - "style": { - "textAlign": "center", - "verticalAlign": "middle" - } - }, - "children": { - "type": "Group", - "props": { - "justify": "center" - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "gray", - "variant": "light", - "radius": "sm", - "w": 30, - "style": { - "fontWeight": 700 - } - }, - "children": "I" - } - ] - } - }, - { - "type": "Table.Td", - "props": { - "style": { - "textAlign": "center", - "verticalAlign": "middle" - } - }, - "children": { - "type": "Group", - "props": { - "justify": "center" - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "grape", - "variant": "filled", - "radius": "sm", - "w": 30, - "style": { - "fontWeight": 700 - } - }, - "children": "A" - } - ] - } - }, - { - "type": "Table.Td", - "props": { - "style": { - "textAlign": "center", - "verticalAlign": "middle" - } - }, - "children": { - "type": "Group", - "props": { - "justify": "center" - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "gray", - "variant": "light", - "radius": "sm", - "w": 30, - "style": { - "fontWeight": 700 - } - }, - "children": "I" - } - ] - } - } - ] - }, - { - "type": "Table.Tr", - "children": [ - { - "type": "Table.Td", - "children": { - "type": "Text", - "props": { - "size": "sm" - }, - "children": "CI/CD pipeline" - } - }, - { - "type": "Table.Td", - "props": { - "style": { - "textAlign": "center", - "verticalAlign": "middle" - } - }, - "children": { - "type": "Group", - "props": { - "justify": "center" - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "gray", - "variant": "light", - "radius": "sm", - "w": 30, - "style": { - "fontWeight": 700 - } - }, - "children": "I" - } - ] - } - }, - { - "type": "Table.Td", - "props": { - "style": { - "textAlign": "center", - "verticalAlign": "middle" - } - }, - "children": { - "type": "Group", - "props": { - "justify": "center" - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "gray", - "variant": "outline", - "radius": "sm", - "w": 30, - "style": { - "fontWeight": 700 - } - }, - "children": "C" - } - ] - } - }, - { - "type": "Table.Td", - "props": { - "style": { - "textAlign": "center" - } - }, - "children": null - }, - { - "type": "Table.Td", - "props": { - "style": { - "textAlign": "center", - "verticalAlign": "middle" - } - }, - "children": { - "type": "Group", - "props": { - "justify": "center" - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "gray", - "variant": "outline", - "radius": "sm", - "w": 30, - "style": { - "fontWeight": 700 - } - }, - "children": "C" - } - ] - } - }, - { - "type": "Table.Td", - "props": { - "style": { - "textAlign": "center", - "verticalAlign": "middle" - } - }, - "children": { - "type": "Group", - "props": { - "justify": "center" - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "grape", - "variant": "filled", - "radius": "sm", - "w": 30, - "style": { - "fontWeight": 700 - } - }, - "children": "A" - } - ] - } - } - ] - }, - { - "type": "Table.Tr", - "children": [ - { - "type": "Table.Td", - "children": { - "type": "Text", - "props": { - "size": "sm" - }, - "children": "Release & deploy" - } - }, - { - "type": "Table.Td", - "props": { - "style": { - "textAlign": "center", - "verticalAlign": "middle" - } - }, - "children": { - "type": "Group", - "props": { - "justify": "center" - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "grape", - "variant": "filled", - "radius": "sm", - "w": 30, - "style": { - "fontWeight": 700 - } - }, - "children": "A" - } - ] - } - }, - { - "type": "Table.Td", - "props": { - "style": { - "textAlign": "center", - "verticalAlign": "middle" - } - }, - "children": { - "type": "Group", - "props": { - "justify": "center" - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "blue", - "variant": "filled", - "radius": "sm", - "w": 30, - "style": { - "fontWeight": 700 - } - }, - "children": "R" - } - ] - } - }, - { - "type": "Table.Td", - "props": { - "style": { - "textAlign": "center" - } - }, - "children": null - }, - { - "type": "Table.Td", - "props": { - "style": { - "textAlign": "center", - "verticalAlign": "middle" - } - }, - "children": { - "type": "Group", - "props": { - "justify": "center" - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "gray", - "variant": "outline", - "radius": "sm", - "w": 30, - "style": { - "fontWeight": 700 - } - }, - "children": "C" - } - ] - } - }, - { - "type": "Table.Td", - "props": { - "style": { - "textAlign": "center", - "verticalAlign": "middle" - } - }, - "children": { - "type": "Group", - "props": { - "justify": "center" - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "blue", - "variant": "filled", - "radius": "sm", - "w": 30, - "style": { - "fontWeight": 700 - } - }, - "children": "R" - } - ] - } - } - ] - }, - { - "type": "Table.Tr", - "children": [ - { - "type": "Table.Td", - "children": { - "type": "Text", - "props": { - "size": "sm" - }, - "children": "Post-launch monitoring" - } - }, - { - "type": "Table.Td", - "props": { - "style": { - "textAlign": "center", - "verticalAlign": "middle" - } - }, - "children": { - "type": "Group", - "props": { - "justify": "center" - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "gray", - "variant": "light", - "radius": "sm", - "w": 30, - "style": { - "fontWeight": 700 - } - }, - "children": "I" - } - ] - } - }, - { - "type": "Table.Td", - "props": { - "style": { - "textAlign": "center", - "verticalAlign": "middle" - } - }, - "children": { - "type": "Group", - "props": { - "justify": "center" - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "gray", - "variant": "outline", - "radius": "sm", - "w": 30, - "style": { - "fontWeight": 700 - } - }, - "children": "C" - } - ] - } - }, - { - "type": "Table.Td", - "props": { - "style": { - "textAlign": "center" - } - }, - "children": null - }, - { - "type": "Table.Td", - "props": { - "style": { - "textAlign": "center", - "verticalAlign": "middle" - } - }, - "children": { - "type": "Group", - "props": { - "justify": "center" - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "gray", - "variant": "light", - "radius": "sm", - "w": 30, - "style": { - "fontWeight": 700 - } - }, - "children": "I" - } - ] - } - }, - { - "type": "Table.Td", - "props": { - "style": { - "textAlign": "center", - "verticalAlign": "middle" - } - }, - "children": { - "type": "Group", - "props": { - "justify": "center" - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "grape", - "variant": "filled", - "radius": "sm", - "w": 30, - "style": { - "fontWeight": 700 - } - }, - "children": "A" - } - ] - } - } - ] - } - ] - } - ] - }, - { - "type": "Group", - "props": { - "gap": "lg", - "mt": 4 - }, - "children": [ - { - "type": "Group", - "props": { - "gap": 6 - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "blue", - "variant": "filled", - "w": 28 - }, - "children": "R" - }, - { - "type": "Text", - "props": { - "size": "xs", - "c": "dimmed" - }, - "children": "Responsible" - } - ] - }, - { - "type": "Group", - "props": { - "gap": 6 - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "grape", - "variant": "filled", - "w": 28 - }, - "children": "A" - }, - { - "type": "Text", - "props": { - "size": "xs", - "c": "dimmed" - }, - "children": "Accountable" - } - ] - }, - { - "type": "Group", - "props": { - "gap": 6 - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "gray", - "variant": "outline", - "w": 28 - }, - "children": "C" - }, - { - "type": "Text", - "props": { - "size": "xs", - "c": "dimmed" - }, - "children": "Consulted" - } - ] - }, - { - "type": "Group", - "props": { - "gap": 6 - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "gray", - "variant": "light", - "w": 28 - }, - "children": "I" - }, - { - "type": "Text", - "props": { - "size": "xs", - "c": "dimmed" - }, - "children": "Informed" - } - ] - } - ] - } - ] -} +{"type":"Stack","props":{"gap":"sm","p":"md"},"children":[{"type":"Title","props":{"order":3},"children":"RACI — launch responsibilities"},{"type":"Text","props":{"c":"dimmed","size":"sm","mt":-6},"children":"who is Responsible / Accountable / Consulted / Informed per deliverable"},{"type":"Table","props":{"withTableBorder":true,"withColumnBorders":true,"striped":true,"verticalSpacing":"xs"},"children":[{"type":"Table.Thead","children":{"type":"Table.Tr","children":[{"type":"Table.Th","props":{"style":{"minWidth":200}},"children":{"type":"Text","props":{"size":"sm","fw":700},"children":"Task / Deliverable"}},{"type":"Table.Th","props":{"style":{"textAlign":"center"}},"children":{"type":"Text","props":{"ta":"center","size":"sm","fw":700},"children":"PM"}},{"type":"Table.Th","props":{"style":{"textAlign":"center"}},"children":{"type":"Text","props":{"ta":"center","size":"sm","fw":700},"children":"Eng"}},{"type":"Table.Th","props":{"style":{"textAlign":"center"}},"children":{"type":"Text","props":{"ta":"center","size":"sm","fw":700},"children":"Design"}},{"type":"Table.Th","props":{"style":{"textAlign":"center"}},"children":{"type":"Text","props":{"ta":"center","size":"sm","fw":700},"children":"QA"}},{"type":"Table.Th","props":{"style":{"textAlign":"center"}},"children":{"type":"Text","props":{"ta":"center","size":"sm","fw":700},"children":"DevOps"}}]}},{"type":"Table.Tbody","children":[{"type":"Table.Tr","children":[{"type":"Table.Td","children":{"type":"Text","props":{"size":"sm"},"children":"Define requirements"}},{"type":"Table.Td","props":{"style":{"textAlign":"center","verticalAlign":"middle"}},"children":{"type":"Group","props":{"justify":"center"},"children":[{"type":"Badge","props":{"color":"grape","variant":"filled","radius":"sm","w":30,"style":{"fontWeight":700}},"children":"A"}]}},{"type":"Table.Td","props":{"style":{"textAlign":"center","verticalAlign":"middle"}},"children":{"type":"Group","props":{"justify":"center"},"children":[{"type":"Badge","props":{"color":"gray","variant":"outline","radius":"sm","w":30,"style":{"fontWeight":700}},"children":"C"}]}},{"type":"Table.Td","props":{"style":{"textAlign":"center","verticalAlign":"middle"}},"children":{"type":"Group","props":{"justify":"center"},"children":[{"type":"Badge","props":{"color":"gray","variant":"outline","radius":"sm","w":30,"style":{"fontWeight":700}},"children":"C"}]}},{"type":"Table.Td","props":{"style":{"textAlign":"center","verticalAlign":"middle"}},"children":{"type":"Group","props":{"justify":"center"},"children":[{"type":"Badge","props":{"color":"gray","variant":"light","radius":"sm","w":30,"style":{"fontWeight":700}},"children":"I"}]}},{"type":"Table.Td","props":{"style":{"textAlign":"center","verticalAlign":"middle"}},"children":{"type":"Group","props":{"justify":"center"},"children":[{"type":"Badge","props":{"color":"gray","variant":"light","radius":"sm","w":30,"style":{"fontWeight":700}},"children":"I"}]}}]},{"type":"Table.Tr","children":[{"type":"Table.Td","children":{"type":"Text","props":{"size":"sm"},"children":"UX / wireframes"}},{"type":"Table.Td","props":{"style":{"textAlign":"center","verticalAlign":"middle"}},"children":{"type":"Group","props":{"justify":"center"},"children":[{"type":"Badge","props":{"color":"gray","variant":"outline","radius":"sm","w":30,"style":{"fontWeight":700}},"children":"C"}]}},{"type":"Table.Td","props":{"style":{"textAlign":"center","verticalAlign":"middle"}},"children":{"type":"Group","props":{"justify":"center"},"children":[{"type":"Badge","props":{"color":"gray","variant":"outline","radius":"sm","w":30,"style":{"fontWeight":700}},"children":"C"}]}},{"type":"Table.Td","props":{"style":{"textAlign":"center","verticalAlign":"middle"}},"children":{"type":"Group","props":{"justify":"center"},"children":[{"type":"Badge","props":{"color":"grape","variant":"filled","radius":"sm","w":30,"style":{"fontWeight":700}},"children":"A"}]}},{"type":"Table.Td","props":{"style":{"textAlign":"center","verticalAlign":"middle"}},"children":{"type":"Group","props":{"justify":"center"},"children":[{"type":"Badge","props":{"color":"gray","variant":"light","radius":"sm","w":30,"style":{"fontWeight":700}},"children":"I"}]}},{"type":"Table.Td","props":{"style":{"textAlign":"center"}},"children":null}]},{"type":"Table.Tr","children":[{"type":"Table.Td","children":{"type":"Text","props":{"size":"sm"},"children":"API design"}},{"type":"Table.Td","props":{"style":{"textAlign":"center","verticalAlign":"middle"}},"children":{"type":"Group","props":{"justify":"center"},"children":[{"type":"Badge","props":{"color":"gray","variant":"outline","radius":"sm","w":30,"style":{"fontWeight":700}},"children":"C"}]}},{"type":"Table.Td","props":{"style":{"textAlign":"center","verticalAlign":"middle"}},"children":{"type":"Group","props":{"justify":"center"},"children":[{"type":"Badge","props":{"color":"grape","variant":"filled","radius":"sm","w":30,"style":{"fontWeight":700}},"children":"A"}]}},{"type":"Table.Td","props":{"style":{"textAlign":"center","verticalAlign":"middle"}},"children":{"type":"Group","props":{"justify":"center"},"children":[{"type":"Badge","props":{"color":"gray","variant":"light","radius":"sm","w":30,"style":{"fontWeight":700}},"children":"I"}]}},{"type":"Table.Td","props":{"style":{"textAlign":"center","verticalAlign":"middle"}},"children":{"type":"Group","props":{"justify":"center"},"children":[{"type":"Badge","props":{"color":"gray","variant":"outline","radius":"sm","w":30,"style":{"fontWeight":700}},"children":"C"}]}},{"type":"Table.Td","props":{"style":{"textAlign":"center","verticalAlign":"middle"}},"children":{"type":"Group","props":{"justify":"center"},"children":[{"type":"Badge","props":{"color":"gray","variant":"outline","radius":"sm","w":30,"style":{"fontWeight":700}},"children":"C"}]}}]},{"type":"Table.Tr","children":[{"type":"Table.Td","children":{"type":"Text","props":{"size":"sm"},"children":"Implementation"}},{"type":"Table.Td","props":{"style":{"textAlign":"center","verticalAlign":"middle"}},"children":{"type":"Group","props":{"justify":"center"},"children":[{"type":"Badge","props":{"color":"gray","variant":"light","radius":"sm","w":30,"style":{"fontWeight":700}},"children":"I"}]}},{"type":"Table.Td","props":{"style":{"textAlign":"center","verticalAlign":"middle"}},"children":{"type":"Group","props":{"justify":"center"},"children":[{"type":"Badge","props":{"color":"grape","variant":"filled","radius":"sm","w":30,"style":{"fontWeight":700}},"children":"A"}]}},{"type":"Table.Td","props":{"style":{"textAlign":"center","verticalAlign":"middle"}},"children":{"type":"Group","props":{"justify":"center"},"children":[{"type":"Badge","props":{"color":"gray","variant":"outline","radius":"sm","w":30,"style":{"fontWeight":700}},"children":"C"}]}},{"type":"Table.Td","props":{"style":{"textAlign":"center","verticalAlign":"middle"}},"children":{"type":"Group","props":{"justify":"center"},"children":[{"type":"Badge","props":{"color":"gray","variant":"outline","radius":"sm","w":30,"style":{"fontWeight":700}},"children":"C"}]}},{"type":"Table.Td","props":{"style":{"textAlign":"center","verticalAlign":"middle"}},"children":{"type":"Group","props":{"justify":"center"},"children":[{"type":"Badge","props":{"color":"gray","variant":"outline","radius":"sm","w":30,"style":{"fontWeight":700}},"children":"C"}]}}]},{"type":"Table.Tr","children":[{"type":"Table.Td","children":{"type":"Text","props":{"size":"sm"},"children":"Test plan & QA"}},{"type":"Table.Td","props":{"style":{"textAlign":"center","verticalAlign":"middle"}},"children":{"type":"Group","props":{"justify":"center"},"children":[{"type":"Badge","props":{"color":"gray","variant":"light","radius":"sm","w":30,"style":{"fontWeight":700}},"children":"I"}]}},{"type":"Table.Td","props":{"style":{"textAlign":"center","verticalAlign":"middle"}},"children":{"type":"Group","props":{"justify":"center"},"children":[{"type":"Badge","props":{"color":"gray","variant":"outline","radius":"sm","w":30,"style":{"fontWeight":700}},"children":"C"}]}},{"type":"Table.Td","props":{"style":{"textAlign":"center","verticalAlign":"middle"}},"children":{"type":"Group","props":{"justify":"center"},"children":[{"type":"Badge","props":{"color":"gray","variant":"light","radius":"sm","w":30,"style":{"fontWeight":700}},"children":"I"}]}},{"type":"Table.Td","props":{"style":{"textAlign":"center","verticalAlign":"middle"}},"children":{"type":"Group","props":{"justify":"center"},"children":[{"type":"Badge","props":{"color":"grape","variant":"filled","radius":"sm","w":30,"style":{"fontWeight":700}},"children":"A"}]}},{"type":"Table.Td","props":{"style":{"textAlign":"center","verticalAlign":"middle"}},"children":{"type":"Group","props":{"justify":"center"},"children":[{"type":"Badge","props":{"color":"gray","variant":"light","radius":"sm","w":30,"style":{"fontWeight":700}},"children":"I"}]}}]},{"type":"Table.Tr","children":[{"type":"Table.Td","children":{"type":"Text","props":{"size":"sm"},"children":"CI/CD pipeline"}},{"type":"Table.Td","props":{"style":{"textAlign":"center","verticalAlign":"middle"}},"children":{"type":"Group","props":{"justify":"center"},"children":[{"type":"Badge","props":{"color":"gray","variant":"light","radius":"sm","w":30,"style":{"fontWeight":700}},"children":"I"}]}},{"type":"Table.Td","props":{"style":{"textAlign":"center","verticalAlign":"middle"}},"children":{"type":"Group","props":{"justify":"center"},"children":[{"type":"Badge","props":{"color":"gray","variant":"outline","radius":"sm","w":30,"style":{"fontWeight":700}},"children":"C"}]}},{"type":"Table.Td","props":{"style":{"textAlign":"center"}},"children":null},{"type":"Table.Td","props":{"style":{"textAlign":"center","verticalAlign":"middle"}},"children":{"type":"Group","props":{"justify":"center"},"children":[{"type":"Badge","props":{"color":"gray","variant":"outline","radius":"sm","w":30,"style":{"fontWeight":700}},"children":"C"}]}},{"type":"Table.Td","props":{"style":{"textAlign":"center","verticalAlign":"middle"}},"children":{"type":"Group","props":{"justify":"center"},"children":[{"type":"Badge","props":{"color":"grape","variant":"filled","radius":"sm","w":30,"style":{"fontWeight":700}},"children":"A"}]}}]},{"type":"Table.Tr","children":[{"type":"Table.Td","children":{"type":"Text","props":{"size":"sm"},"children":"Release & deploy"}},{"type":"Table.Td","props":{"style":{"textAlign":"center","verticalAlign":"middle"}},"children":{"type":"Group","props":{"justify":"center"},"children":[{"type":"Badge","props":{"color":"grape","variant":"filled","radius":"sm","w":30,"style":{"fontWeight":700}},"children":"A"}]}},{"type":"Table.Td","props":{"style":{"textAlign":"center","verticalAlign":"middle"}},"children":{"type":"Group","props":{"justify":"center"},"children":[{"type":"Badge","props":{"color":"blue","variant":"filled","radius":"sm","w":30,"style":{"fontWeight":700}},"children":"R"}]}},{"type":"Table.Td","props":{"style":{"textAlign":"center"}},"children":null},{"type":"Table.Td","props":{"style":{"textAlign":"center","verticalAlign":"middle"}},"children":{"type":"Group","props":{"justify":"center"},"children":[{"type":"Badge","props":{"color":"gray","variant":"outline","radius":"sm","w":30,"style":{"fontWeight":700}},"children":"C"}]}},{"type":"Table.Td","props":{"style":{"textAlign":"center","verticalAlign":"middle"}},"children":{"type":"Group","props":{"justify":"center"},"children":[{"type":"Badge","props":{"color":"blue","variant":"filled","radius":"sm","w":30,"style":{"fontWeight":700}},"children":"R"}]}}]},{"type":"Table.Tr","children":[{"type":"Table.Td","children":{"type":"Text","props":{"size":"sm"},"children":"Post-launch monitoring"}},{"type":"Table.Td","props":{"style":{"textAlign":"center","verticalAlign":"middle"}},"children":{"type":"Group","props":{"justify":"center"},"children":[{"type":"Badge","props":{"color":"gray","variant":"light","radius":"sm","w":30,"style":{"fontWeight":700}},"children":"I"}]}},{"type":"Table.Td","props":{"style":{"textAlign":"center","verticalAlign":"middle"}},"children":{"type":"Group","props":{"justify":"center"},"children":[{"type":"Badge","props":{"color":"gray","variant":"outline","radius":"sm","w":30,"style":{"fontWeight":700}},"children":"C"}]}},{"type":"Table.Td","props":{"style":{"textAlign":"center"}},"children":null},{"type":"Table.Td","props":{"style":{"textAlign":"center","verticalAlign":"middle"}},"children":{"type":"Group","props":{"justify":"center"},"children":[{"type":"Badge","props":{"color":"gray","variant":"light","radius":"sm","w":30,"style":{"fontWeight":700}},"children":"I"}]}},{"type":"Table.Td","props":{"style":{"textAlign":"center","verticalAlign":"middle"}},"children":{"type":"Group","props":{"justify":"center"},"children":[{"type":"Badge","props":{"color":"grape","variant":"filled","radius":"sm","w":30,"style":{"fontWeight":700}},"children":"A"}]}}]}]}]},{"type":"Group","props":{"gap":"lg","mt":4},"children":[{"type":"Group","props":{"gap":6},"children":[{"type":"Badge","props":{"color":"blue","variant":"filled","w":28},"children":"R"},{"type":"Text","props":{"size":"xs","c":"dimmed"},"children":"Responsible"}]},{"type":"Group","props":{"gap":6},"children":[{"type":"Badge","props":{"color":"grape","variant":"filled","w":28},"children":"A"},{"type":"Text","props":{"size":"xs","c":"dimmed"},"children":"Accountable"}]},{"type":"Group","props":{"gap":6},"children":[{"type":"Badge","props":{"color":"gray","variant":"outline","w":28},"children":"C"},{"type":"Text","props":{"size":"xs","c":"dimmed"},"children":"Consulted"}]},{"type":"Group","props":{"gap":6},"children":[{"type":"Badge","props":{"color":"gray","variant":"light","w":28},"children":"I"},{"type":"Text","props":{"size":"xs","c":"dimmed"},"children":"Informed"}]}]}]} diff --git a/plugin/skills/diagram-recipes/examples/recursion-tree.flow.json b/plugin/skills/diagram-recipes/examples/recursion-tree.flow.json index ad27b75..f0f4f98 100644 --- a/plugin/skills/diagram-recipes/examples/recursion-tree.flow.json +++ b/plugin/skills/diagram-recipes/examples/recursion-tree.flow.json @@ -1,29 +1 @@ -{ - "direction": "TB", - "legend": [ - { "label": "recursive call", "color": "#fbbf24" }, - { "label": "base case", "color": "#22c55e" }, - { "label": "memoized hit", "color": "#60a5fa" } - ], - "nodes": [ - { "id": "f5", "type": "change", "data": { "label": "fib(5)", "status": "active", "sub": "→ 5", "meta": "call" } }, - { "id": "f4", "type": "change", "data": { "label": "fib(4)", "status": "active", "sub": "→ 3" } }, - { "id": "f3a", "type": "change", "data": { "label": "fib(3)", "status": "active", "sub": "→ 2" } }, - { "id": "f3b", "type": "change", "data": { "label": "fib(3)", "status": "info", "icon": "bolt", "meta": "memo hit" } }, - { "id": "f2a", "type": "change", "data": { "label": "fib(2)", "status": "active", "sub": "→ 1" } }, - { "id": "f2b", "type": "change", "data": { "label": "fib(2)", "status": "info", "icon": "bolt", "meta": "memo hit" } }, - { "id": "f1a", "type": "change", "data": { "label": "fib(1)", "status": "success", "sub": "= 1", "meta": "base" } }, - { "id": "f1b", "type": "change", "data": { "label": "fib(1)", "status": "success", "sub": "= 1", "meta": "base" } }, - { "id": "f0a", "type": "change", "data": { "label": "fib(0)", "status": "success", "sub": "= 0", "meta": "base" } } - ], - "edges": [ - { "source": "f5", "target": "f4" }, - { "source": "f5", "target": "f3b", "label": "cached", "style": { "stroke": "#60a5fa" }, "markerEnd": { "type": "arrowclosed", "color": "#60a5fa" } }, - { "source": "f4", "target": "f3a" }, - { "source": "f4", "target": "f2b", "label": "cached", "style": { "stroke": "#60a5fa" }, "markerEnd": { "type": "arrowclosed", "color": "#60a5fa" } }, - { "source": "f3a", "target": "f2a" }, - { "source": "f3a", "target": "f1b" }, - { "source": "f2a", "target": "f1a" }, - { "source": "f2a", "target": "f0a" } - ] -} +{"direction":"TB","legend":[{"label":"recursive call","color":"#fbbf24"},{"label":"base case","color":"#22c55e"},{"label":"memoized hit","color":"#60a5fa"}],"nodes":[{"id":"f5","type":"change","data":{"label":"fib(5)","status":"active","sub":"→ 5","meta":"call"}},{"id":"f4","type":"change","data":{"label":"fib(4)","status":"active","sub":"→ 3"}},{"id":"f3a","type":"change","data":{"label":"fib(3)","status":"active","sub":"→ 2"}},{"id":"f3b","type":"change","data":{"label":"fib(3)","status":"info","icon":"bolt","meta":"memo hit"}},{"id":"f2a","type":"change","data":{"label":"fib(2)","status":"active","sub":"→ 1"}},{"id":"f2b","type":"change","data":{"label":"fib(2)","status":"info","icon":"bolt","meta":"memo hit"}},{"id":"f1a","type":"change","data":{"label":"fib(1)","status":"success","sub":"= 1","meta":"base"}},{"id":"f1b","type":"change","data":{"label":"fib(1)","status":"success","sub":"= 1","meta":"base"}},{"id":"f0a","type":"change","data":{"label":"fib(0)","status":"success","sub":"= 0","meta":"base"}}],"edges":[{"source":"f5","target":"f4"},{"source":"f5","target":"f3b","label":"cached","style":{"stroke":"#60a5fa"},"markerEnd":{"type":"arrowclosed","color":"#60a5fa"}},{"source":"f4","target":"f3a"},{"source":"f4","target":"f2b","label":"cached","style":{"stroke":"#60a5fa"},"markerEnd":{"type":"arrowclosed","color":"#60a5fa"}},{"source":"f3a","target":"f2a"},{"source":"f3a","target":"f1b"},{"source":"f2a","target":"f1a"},{"source":"f2a","target":"f0a"}]} diff --git a/plugin/skills/diagram-recipes/examples/risk-matrix.component.json b/plugin/skills/diagram-recipes/examples/risk-matrix.component.json index f54cd1d..285dd02 100644 --- a/plugin/skills/diagram-recipes/examples/risk-matrix.component.json +++ b/plugin/skills/diagram-recipes/examples/risk-matrix.component.json @@ -1,1367 +1 @@ -{ - "type": "Stack", - "props": { - "gap": "sm", - "p": "md" - }, - "children": [ - { - "type": "Title", - "props": { - "order": 3 - }, - "children": "Risk matrix — likelihood × impact" - }, - { - "type": "Text", - "props": { - "c": "dimmed", - "size": "sm", - "mt": -6 - }, - "children": "Q3 launch risk register · cell = likelihood × impact score, tinted by severity" - }, - { - "type": "Table", - "props": { - "withTableBorder": true, - "withColumnBorders": true, - "horizontalSpacing": "xs", - "verticalSpacing": "xs", - "style": { - "tableLayout": "fixed" - } - }, - "children": [ - { - "type": "Table.Thead", - "children": [ - { - "type": "Table.Tr", - "children": [ - { - "type": "Table.Th", - "props": { - "style": { - "background": "transparent", - "border": "none" - } - }, - "children": "" - }, - { - "type": "Table.Th", - "props": { - "colSpan": 5, - "style": { - "textAlign": "center" - } - }, - "children": { - "type": "Text", - "props": { - "ta": "center", - "c": "dimmed", - "size": "xs", - "fw": 600 - }, - "children": "Likelihood →" - } - } - ] - }, - { - "type": "Table.Tr", - "children": [ - { - "type": "Table.Th", - "props": { - "style": { - "width": 32 - } - }, - "children": { - "type": "Text", - "props": { - "c": "dimmed", - "size": "xs", - "fw": 600, - "style": { - "writingMode": "vertical-rl" - } - }, - "children": "Impact" - } - }, - { - "type": "Table.Th", - "props": { - "style": { - "textAlign": "center" - } - }, - "children": { - "type": "Text", - "props": { - "ta": "center", - "size": "xs", - "fw": 600 - }, - "children": "Rare" - } - }, - { - "type": "Table.Th", - "props": { - "style": { - "textAlign": "center" - } - }, - "children": { - "type": "Text", - "props": { - "ta": "center", - "size": "xs", - "fw": 600 - }, - "children": "Unlikely" - } - }, - { - "type": "Table.Th", - "props": { - "style": { - "textAlign": "center" - } - }, - "children": { - "type": "Text", - "props": { - "ta": "center", - "size": "xs", - "fw": 600 - }, - "children": "Possible" - } - }, - { - "type": "Table.Th", - "props": { - "style": { - "textAlign": "center" - } - }, - "children": { - "type": "Text", - "props": { - "ta": "center", - "size": "xs", - "fw": 600 - }, - "children": "Likely" - } - }, - { - "type": "Table.Th", - "props": { - "style": { - "textAlign": "center" - } - }, - "children": { - "type": "Text", - "props": { - "ta": "center", - "size": "xs", - "fw": 600 - }, - "children": "Almost certain" - } - } - ] - } - ] - }, - { - "type": "Table.Tbody", - "children": [ - { - "type": "Table.Tr", - "children": [ - { - "type": "Table.Th", - "props": { - "style": { - "whiteSpace": "nowrap" - } - }, - "children": { - "type": "Text", - "props": { - "size": "xs", - "fw": 600 - }, - "children": "Severe" - } - }, - { - "type": "Table.Td", - "props": { - "style": { - "background": "rgba(106,195,75,0.30)", - "textAlign": "center", - "verticalAlign": "middle", - "height": 56, - "minWidth": 92 - } - }, - "children": { - "type": "Stack", - "props": { - "gap": 4, - "align": "center" - }, - "children": [ - { - "type": "Text", - "props": { - "ta": "center", - "size": "xs", - "c": "var(--fg)", - "fw": 700 - }, - "children": "5" - } - ] - } - }, - { - "type": "Table.Td", - "props": { - "style": { - "background": "rgba(197,193,51,0.30)", - "textAlign": "center", - "verticalAlign": "middle", - "height": 56, - "minWidth": 92 - } - }, - "children": { - "type": "Stack", - "props": { - "gap": 4, - "align": "center" - }, - "children": [ - { - "type": "Text", - "props": { - "ta": "center", - "size": "xs", - "c": "var(--fg)", - "fw": 700 - }, - "children": "10" - }, - { - "type": "Group", - "props": { - "gap": 4, - "justify": "center" - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "yellow", - "size": "sm", - "variant": "filled", - "radius": "sm" - }, - "children": "R10" - } - ] - } - ] - } - }, - { - "type": "Table.Td", - "props": { - "style": { - "background": "rgba(249,171,41,0.30)", - "textAlign": "center", - "verticalAlign": "middle", - "height": 56, - "minWidth": 92 - } - }, - "children": { - "type": "Stack", - "props": { - "gap": 4, - "align": "center" - }, - "children": [ - { - "type": "Text", - "props": { - "ta": "center", - "size": "xs", - "c": "var(--fg)", - "fw": 700 - }, - "children": "15" - } - ] - } - }, - { - "type": "Table.Td", - "props": { - "style": { - "background": "rgba(244,119,55,0.30)", - "textAlign": "center", - "verticalAlign": "middle", - "height": 56, - "minWidth": 92 - } - }, - "children": { - "type": "Stack", - "props": { - "gap": 4, - "align": "center" - }, - "children": [ - { - "type": "Text", - "props": { - "ta": "center", - "size": "xs", - "c": "var(--fg)", - "fw": 700 - }, - "children": "20" - }, - { - "type": "Group", - "props": { - "gap": 4, - "justify": "center" - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "red", - "size": "sm", - "variant": "filled", - "radius": "sm" - }, - "children": "R1" - } - ] - } - ] - } - }, - { - "type": "Table.Td", - "props": { - "style": { - "background": "rgba(239,68,68,0.30)", - "textAlign": "center", - "verticalAlign": "middle", - "height": 56, - "minWidth": 92 - } - }, - "children": { - "type": "Stack", - "props": { - "gap": 4, - "align": "center" - }, - "children": [ - { - "type": "Text", - "props": { - "ta": "center", - "size": "xs", - "c": "var(--fg)", - "fw": 700 - }, - "children": "25" - }, - { - "type": "Group", - "props": { - "gap": 4, - "justify": "center" - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "red", - "size": "sm", - "variant": "filled", - "radius": "sm" - }, - "children": "R2" - } - ] - } - ] - } - } - ] - }, - { - "type": "Table.Tr", - "children": [ - { - "type": "Table.Th", - "props": { - "style": { - "whiteSpace": "nowrap" - } - }, - "children": { - "type": "Text", - "props": { - "size": "xs", - "fw": 600 - }, - "children": "Major" - } - }, - { - "type": "Table.Td", - "props": { - "style": { - "background": "rgba(88,196,80,0.30)", - "textAlign": "center", - "verticalAlign": "middle", - "height": 56, - "minWidth": 92 - } - }, - "children": { - "type": "Stack", - "props": { - "gap": 4, - "align": "center" - }, - "children": [ - { - "type": "Text", - "props": { - "ta": "center", - "size": "xs", - "c": "var(--fg)", - "fw": 700 - }, - "children": "4" - } - ] - } - }, - { - "type": "Table.Td", - "props": { - "style": { - "background": "rgba(161,194,60,0.30)", - "textAlign": "center", - "verticalAlign": "middle", - "height": 56, - "minWidth": 92 - } - }, - "children": { - "type": "Stack", - "props": { - "gap": 4, - "align": "center" - }, - "children": [ - { - "type": "Text", - "props": { - "ta": "center", - "size": "xs", - "c": "var(--fg)", - "fw": 700 - }, - "children": "8" - } - ] - } - }, - { - "type": "Table.Td", - "props": { - "style": { - "background": "rgba(233,192,41,0.30)", - "textAlign": "center", - "verticalAlign": "middle", - "height": 56, - "minWidth": 92 - } - }, - "children": { - "type": "Stack", - "props": { - "gap": 4, - "align": "center" - }, - "children": [ - { - "type": "Text", - "props": { - "ta": "center", - "size": "xs", - "c": "var(--fg)", - "fw": 700 - }, - "children": "12" - }, - { - "type": "Group", - "props": { - "gap": 4, - "justify": "center" - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "orange", - "size": "sm", - "variant": "filled", - "radius": "sm" - }, - "children": "R4" - } - ] - } - ] - } - }, - { - "type": "Table.Td", - "props": { - "style": { - "background": "rgba(248,160,44,0.30)", - "textAlign": "center", - "verticalAlign": "middle", - "height": 56, - "minWidth": 92 - } - }, - "children": { - "type": "Stack", - "props": { - "gap": 4, - "align": "center" - }, - "children": [ - { - "type": "Text", - "props": { - "ta": "center", - "size": "xs", - "c": "var(--fg)", - "fw": 700 - }, - "children": "16" - }, - { - "type": "Group", - "props": { - "gap": 4, - "justify": "center" - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "orange", - "size": "sm", - "variant": "filled", - "radius": "sm" - }, - "children": "R3" - } - ] - } - ] - } - }, - { - "type": "Table.Td", - "props": { - "style": { - "background": "rgba(244,119,55,0.30)", - "textAlign": "center", - "verticalAlign": "middle", - "height": 56, - "minWidth": 92 - } - }, - "children": { - "type": "Stack", - "props": { - "gap": 4, - "align": "center" - }, - "children": [ - { - "type": "Text", - "props": { - "ta": "center", - "size": "xs", - "c": "var(--fg)", - "fw": 700 - }, - "children": "20" - } - ] - } - } - ] - }, - { - "type": "Table.Tr", - "children": [ - { - "type": "Table.Th", - "props": { - "style": { - "whiteSpace": "nowrap" - } - }, - "children": { - "type": "Text", - "props": { - "size": "xs", - "fw": 600 - }, - "children": "Moderate" - } - }, - { - "type": "Table.Td", - "props": { - "style": { - "background": "rgba(70,196,84,0.30)", - "textAlign": "center", - "verticalAlign": "middle", - "height": 56, - "minWidth": 92 - } - }, - "children": { - "type": "Stack", - "props": { - "gap": 4, - "align": "center" - }, - "children": [ - { - "type": "Text", - "props": { - "ta": "center", - "size": "xs", - "c": "var(--fg)", - "fw": 700 - }, - "children": "3" - } - ] - } - }, - { - "type": "Table.Td", - "props": { - "style": { - "background": "rgba(124,195,70,0.30)", - "textAlign": "center", - "verticalAlign": "middle", - "height": 56, - "minWidth": 92 - } - }, - "children": { - "type": "Stack", - "props": { - "gap": 4, - "align": "center" - }, - "children": [ - { - "type": "Text", - "props": { - "ta": "center", - "size": "xs", - "c": "var(--fg)", - "fw": 700 - }, - "children": "6" - } - ] - } - }, - { - "type": "Table.Td", - "props": { - "style": { - "background": "rgba(179,193,55,0.30)", - "textAlign": "center", - "verticalAlign": "middle", - "height": 56, - "minWidth": 92 - } - }, - "children": { - "type": "Stack", - "props": { - "gap": 4, - "align": "center" - }, - "children": [ - { - "type": "Text", - "props": { - "ta": "center", - "size": "xs", - "c": "var(--fg)", - "fw": 700 - }, - "children": "9" - }, - { - "type": "Group", - "props": { - "gap": 4, - "justify": "center" - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "yellow", - "size": "sm", - "variant": "filled", - "radius": "sm" - }, - "children": "R5" - } - ] - } - ] - } - }, - { - "type": "Table.Td", - "props": { - "style": { - "background": "rgba(233,192,41,0.30)", - "textAlign": "center", - "verticalAlign": "middle", - "height": 56, - "minWidth": 92 - } - }, - "children": { - "type": "Stack", - "props": { - "gap": 4, - "align": "center" - }, - "children": [ - { - "type": "Text", - "props": { - "ta": "center", - "size": "xs", - "c": "var(--fg)", - "fw": 700 - }, - "children": "12" - } - ] - } - }, - { - "type": "Table.Td", - "props": { - "style": { - "background": "rgba(249,171,41,0.30)", - "textAlign": "center", - "verticalAlign": "middle", - "height": 56, - "minWidth": 92 - } - }, - "children": { - "type": "Stack", - "props": { - "gap": 4, - "align": "center" - }, - "children": [ - { - "type": "Text", - "props": { - "ta": "center", - "size": "xs", - "c": "var(--fg)", - "fw": 700 - }, - "children": "15" - }, - { - "type": "Group", - "props": { - "gap": 4, - "justify": "center" - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "orange", - "size": "sm", - "variant": "filled", - "radius": "sm" - }, - "children": "R6" - } - ] - } - ] - } - } - ] - }, - { - "type": "Table.Tr", - "children": [ - { - "type": "Table.Th", - "props": { - "style": { - "whiteSpace": "nowrap" - } - }, - "children": { - "type": "Text", - "props": { - "size": "xs", - "fw": 600 - }, - "children": "Minor" - } - }, - { - "type": "Table.Td", - "props": { - "style": { - "background": "rgba(52,197,89,0.30)", - "textAlign": "center", - "verticalAlign": "middle", - "height": 56, - "minWidth": 92 - } - }, - "children": { - "type": "Stack", - "props": { - "gap": 4, - "align": "center" - }, - "children": [ - { - "type": "Text", - "props": { - "ta": "center", - "size": "xs", - "c": "var(--fg)", - "fw": 700 - }, - "children": "2" - } - ] - } - }, - { - "type": "Table.Td", - "props": { - "style": { - "background": "rgba(88,196,80,0.30)", - "textAlign": "center", - "verticalAlign": "middle", - "height": 56, - "minWidth": 92 - } - }, - "children": { - "type": "Stack", - "props": { - "gap": 4, - "align": "center" - }, - "children": [ - { - "type": "Text", - "props": { - "ta": "center", - "size": "xs", - "c": "var(--fg)", - "fw": 700 - }, - "children": "4" - }, - { - "type": "Group", - "props": { - "gap": 4, - "justify": "center" - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "teal", - "size": "sm", - "variant": "filled", - "radius": "sm" - }, - "children": "R7" - } - ] - } - ] - } - }, - { - "type": "Table.Td", - "props": { - "style": { - "background": "rgba(124,195,70,0.30)", - "textAlign": "center", - "verticalAlign": "middle", - "height": 56, - "minWidth": 92 - } - }, - "children": { - "type": "Stack", - "props": { - "gap": 4, - "align": "center" - }, - "children": [ - { - "type": "Text", - "props": { - "ta": "center", - "size": "xs", - "c": "var(--fg)", - "fw": 700 - }, - "children": "6" - } - ] - } - }, - { - "type": "Table.Td", - "props": { - "style": { - "background": "rgba(161,194,60,0.30)", - "textAlign": "center", - "verticalAlign": "middle", - "height": 56, - "minWidth": 92 - } - }, - "children": { - "type": "Stack", - "props": { - "gap": 4, - "align": "center" - }, - "children": [ - { - "type": "Text", - "props": { - "ta": "center", - "size": "xs", - "c": "var(--fg)", - "fw": 700 - }, - "children": "8" - }, - { - "type": "Group", - "props": { - "gap": 4, - "justify": "center" - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "yellow", - "size": "sm", - "variant": "filled", - "radius": "sm" - }, - "children": "R8" - } - ] - } - ] - } - }, - { - "type": "Table.Td", - "props": { - "style": { - "background": "rgba(197,193,51,0.30)", - "textAlign": "center", - "verticalAlign": "middle", - "height": 56, - "minWidth": 92 - } - }, - "children": { - "type": "Stack", - "props": { - "gap": 4, - "align": "center" - }, - "children": [ - { - "type": "Text", - "props": { - "ta": "center", - "size": "xs", - "c": "var(--fg)", - "fw": 700 - }, - "children": "10" - } - ] - } - } - ] - }, - { - "type": "Table.Tr", - "children": [ - { - "type": "Table.Th", - "props": { - "style": { - "whiteSpace": "nowrap" - } - }, - "children": { - "type": "Text", - "props": { - "size": "xs", - "fw": 600 - }, - "children": "Negligible" - } - }, - { - "type": "Table.Td", - "props": { - "style": { - "background": "rgba(34,197,94,0.30)", - "textAlign": "center", - "verticalAlign": "middle", - "height": 56, - "minWidth": 92 - } - }, - "children": { - "type": "Stack", - "props": { - "gap": 4, - "align": "center" - }, - "children": [ - { - "type": "Text", - "props": { - "ta": "center", - "size": "xs", - "c": "var(--fg)", - "fw": 700 - }, - "children": "1" - }, - { - "type": "Group", - "props": { - "gap": 4, - "justify": "center" - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "green", - "size": "sm", - "variant": "filled", - "radius": "sm" - }, - "children": "R9" - } - ] - } - ] - } - }, - { - "type": "Table.Td", - "props": { - "style": { - "background": "rgba(52,197,89,0.30)", - "textAlign": "center", - "verticalAlign": "middle", - "height": 56, - "minWidth": 92 - } - }, - "children": { - "type": "Stack", - "props": { - "gap": 4, - "align": "center" - }, - "children": [ - { - "type": "Text", - "props": { - "ta": "center", - "size": "xs", - "c": "var(--fg)", - "fw": 700 - }, - "children": "2" - } - ] - } - }, - { - "type": "Table.Td", - "props": { - "style": { - "background": "rgba(70,196,84,0.30)", - "textAlign": "center", - "verticalAlign": "middle", - "height": 56, - "minWidth": 92 - } - }, - "children": { - "type": "Stack", - "props": { - "gap": 4, - "align": "center" - }, - "children": [ - { - "type": "Text", - "props": { - "ta": "center", - "size": "xs", - "c": "var(--fg)", - "fw": 700 - }, - "children": "3" - } - ] - } - }, - { - "type": "Table.Td", - "props": { - "style": { - "background": "rgba(88,196,80,0.30)", - "textAlign": "center", - "verticalAlign": "middle", - "height": 56, - "minWidth": 92 - } - }, - "children": { - "type": "Stack", - "props": { - "gap": 4, - "align": "center" - }, - "children": [ - { - "type": "Text", - "props": { - "ta": "center", - "size": "xs", - "c": "var(--fg)", - "fw": 700 - }, - "children": "4" - } - ] - } - }, - { - "type": "Table.Td", - "props": { - "style": { - "background": "rgba(106,195,75,0.30)", - "textAlign": "center", - "verticalAlign": "middle", - "height": 56, - "minWidth": 92 - } - }, - "children": { - "type": "Stack", - "props": { - "gap": 4, - "align": "center" - }, - "children": [ - { - "type": "Text", - "props": { - "ta": "center", - "size": "xs", - "c": "var(--fg)", - "fw": 700 - }, - "children": "5" - } - ] - } - } - ] - } - ] - } - ] - }, - { - "type": "Group", - "props": { - "gap": "lg", - "align": "center", - "mt": 4 - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "gray", - "variant": "light", - "size": "sm", - "tt": "none" - }, - "children": "R1–R10 · risk register" - }, - { - "type": "Group", - "props": { - "gap": 6 - }, - "children": [ - { - "type": "Box", - "props": { - "w": 14, - "h": 14, - "style": { - "background": "rgba(52,197,89,0.30)", - "borderRadius": 3 - } - }, - "children": null - }, - { - "type": "Text", - "props": { - "size": "xs", - "c": "dimmed" - }, - "children": "low (1–5)" - } - ] - }, - { - "type": "Group", - "props": { - "gap": 6 - }, - "children": [ - { - "type": "Box", - "props": { - "w": 14, - "h": 14, - "style": { - "background": "rgba(233,192,41,0.30)", - "borderRadius": 3 - } - }, - "children": null - }, - { - "type": "Text", - "props": { - "size": "xs", - "c": "dimmed" - }, - "children": "medium (6–12)" - } - ] - }, - { - "type": "Group", - "props": { - "gap": 6 - }, - "children": [ - { - "type": "Box", - "props": { - "w": 14, - "h": 14, - "style": { - "background": "rgba(244,119,55,0.30)", - "borderRadius": 3 - } - }, - "children": null - }, - { - "type": "Text", - "props": { - "size": "xs", - "c": "dimmed" - }, - "children": "high (15–25)" - } - ] - } - ] - } - ] -} +{"type":"Stack","props":{"gap":"sm","p":"md"},"children":[{"type":"Title","props":{"order":3},"children":"Risk matrix — likelihood × impact"},{"type":"Text","props":{"c":"dimmed","size":"sm","mt":-6},"children":"Q3 launch risk register · cell = likelihood × impact score, tinted by severity"},{"type":"Table","props":{"withTableBorder":true,"withColumnBorders":true,"horizontalSpacing":"xs","verticalSpacing":"xs","style":{"tableLayout":"fixed"}},"children":[{"type":"Table.Thead","children":[{"type":"Table.Tr","children":[{"type":"Table.Th","props":{"style":{"background":"transparent","border":"none"}},"children":""},{"type":"Table.Th","props":{"colSpan":5,"style":{"textAlign":"center"}},"children":{"type":"Text","props":{"ta":"center","c":"dimmed","size":"xs","fw":600},"children":"Likelihood →"}}]},{"type":"Table.Tr","children":[{"type":"Table.Th","props":{"style":{"width":32}},"children":{"type":"Text","props":{"c":"dimmed","size":"xs","fw":600,"style":{"writingMode":"vertical-rl"}},"children":"Impact"}},{"type":"Table.Th","props":{"style":{"textAlign":"center"}},"children":{"type":"Text","props":{"ta":"center","size":"xs","fw":600},"children":"Rare"}},{"type":"Table.Th","props":{"style":{"textAlign":"center"}},"children":{"type":"Text","props":{"ta":"center","size":"xs","fw":600},"children":"Unlikely"}},{"type":"Table.Th","props":{"style":{"textAlign":"center"}},"children":{"type":"Text","props":{"ta":"center","size":"xs","fw":600},"children":"Possible"}},{"type":"Table.Th","props":{"style":{"textAlign":"center"}},"children":{"type":"Text","props":{"ta":"center","size":"xs","fw":600},"children":"Likely"}},{"type":"Table.Th","props":{"style":{"textAlign":"center"}},"children":{"type":"Text","props":{"ta":"center","size":"xs","fw":600},"children":"Almost certain"}}]}]},{"type":"Table.Tbody","children":[{"type":"Table.Tr","children":[{"type":"Table.Th","props":{"style":{"whiteSpace":"nowrap"}},"children":{"type":"Text","props":{"size":"xs","fw":600},"children":"Severe"}},{"type":"Table.Td","props":{"style":{"background":"rgba(106,195,75,0.30)","textAlign":"center","verticalAlign":"middle","height":56,"minWidth":92}},"children":{"type":"Stack","props":{"gap":4,"align":"center"},"children":[{"type":"Text","props":{"ta":"center","size":"xs","c":"var(--fg)","fw":700},"children":"5"}]}},{"type":"Table.Td","props":{"style":{"background":"rgba(197,193,51,0.30)","textAlign":"center","verticalAlign":"middle","height":56,"minWidth":92}},"children":{"type":"Stack","props":{"gap":4,"align":"center"},"children":[{"type":"Text","props":{"ta":"center","size":"xs","c":"var(--fg)","fw":700},"children":"10"},{"type":"Group","props":{"gap":4,"justify":"center"},"children":[{"type":"Badge","props":{"color":"yellow","size":"sm","variant":"filled","radius":"sm"},"children":"R10"}]}]}},{"type":"Table.Td","props":{"style":{"background":"rgba(249,171,41,0.30)","textAlign":"center","verticalAlign":"middle","height":56,"minWidth":92}},"children":{"type":"Stack","props":{"gap":4,"align":"center"},"children":[{"type":"Text","props":{"ta":"center","size":"xs","c":"var(--fg)","fw":700},"children":"15"}]}},{"type":"Table.Td","props":{"style":{"background":"rgba(244,119,55,0.30)","textAlign":"center","verticalAlign":"middle","height":56,"minWidth":92}},"children":{"type":"Stack","props":{"gap":4,"align":"center"},"children":[{"type":"Text","props":{"ta":"center","size":"xs","c":"var(--fg)","fw":700},"children":"20"},{"type":"Group","props":{"gap":4,"justify":"center"},"children":[{"type":"Badge","props":{"color":"red","size":"sm","variant":"filled","radius":"sm"},"children":"R1"}]}]}},{"type":"Table.Td","props":{"style":{"background":"rgba(239,68,68,0.30)","textAlign":"center","verticalAlign":"middle","height":56,"minWidth":92}},"children":{"type":"Stack","props":{"gap":4,"align":"center"},"children":[{"type":"Text","props":{"ta":"center","size":"xs","c":"var(--fg)","fw":700},"children":"25"},{"type":"Group","props":{"gap":4,"justify":"center"},"children":[{"type":"Badge","props":{"color":"red","size":"sm","variant":"filled","radius":"sm"},"children":"R2"}]}]}}]},{"type":"Table.Tr","children":[{"type":"Table.Th","props":{"style":{"whiteSpace":"nowrap"}},"children":{"type":"Text","props":{"size":"xs","fw":600},"children":"Major"}},{"type":"Table.Td","props":{"style":{"background":"rgba(88,196,80,0.30)","textAlign":"center","verticalAlign":"middle","height":56,"minWidth":92}},"children":{"type":"Stack","props":{"gap":4,"align":"center"},"children":[{"type":"Text","props":{"ta":"center","size":"xs","c":"var(--fg)","fw":700},"children":"4"}]}},{"type":"Table.Td","props":{"style":{"background":"rgba(161,194,60,0.30)","textAlign":"center","verticalAlign":"middle","height":56,"minWidth":92}},"children":{"type":"Stack","props":{"gap":4,"align":"center"},"children":[{"type":"Text","props":{"ta":"center","size":"xs","c":"var(--fg)","fw":700},"children":"8"}]}},{"type":"Table.Td","props":{"style":{"background":"rgba(233,192,41,0.30)","textAlign":"center","verticalAlign":"middle","height":56,"minWidth":92}},"children":{"type":"Stack","props":{"gap":4,"align":"center"},"children":[{"type":"Text","props":{"ta":"center","size":"xs","c":"var(--fg)","fw":700},"children":"12"},{"type":"Group","props":{"gap":4,"justify":"center"},"children":[{"type":"Badge","props":{"color":"orange","size":"sm","variant":"filled","radius":"sm"},"children":"R4"}]}]}},{"type":"Table.Td","props":{"style":{"background":"rgba(248,160,44,0.30)","textAlign":"center","verticalAlign":"middle","height":56,"minWidth":92}},"children":{"type":"Stack","props":{"gap":4,"align":"center"},"children":[{"type":"Text","props":{"ta":"center","size":"xs","c":"var(--fg)","fw":700},"children":"16"},{"type":"Group","props":{"gap":4,"justify":"center"},"children":[{"type":"Badge","props":{"color":"orange","size":"sm","variant":"filled","radius":"sm"},"children":"R3"}]}]}},{"type":"Table.Td","props":{"style":{"background":"rgba(244,119,55,0.30)","textAlign":"center","verticalAlign":"middle","height":56,"minWidth":92}},"children":{"type":"Stack","props":{"gap":4,"align":"center"},"children":[{"type":"Text","props":{"ta":"center","size":"xs","c":"var(--fg)","fw":700},"children":"20"}]}}]},{"type":"Table.Tr","children":[{"type":"Table.Th","props":{"style":{"whiteSpace":"nowrap"}},"children":{"type":"Text","props":{"size":"xs","fw":600},"children":"Moderate"}},{"type":"Table.Td","props":{"style":{"background":"rgba(70,196,84,0.30)","textAlign":"center","verticalAlign":"middle","height":56,"minWidth":92}},"children":{"type":"Stack","props":{"gap":4,"align":"center"},"children":[{"type":"Text","props":{"ta":"center","size":"xs","c":"var(--fg)","fw":700},"children":"3"}]}},{"type":"Table.Td","props":{"style":{"background":"rgba(124,195,70,0.30)","textAlign":"center","verticalAlign":"middle","height":56,"minWidth":92}},"children":{"type":"Stack","props":{"gap":4,"align":"center"},"children":[{"type":"Text","props":{"ta":"center","size":"xs","c":"var(--fg)","fw":700},"children":"6"}]}},{"type":"Table.Td","props":{"style":{"background":"rgba(179,193,55,0.30)","textAlign":"center","verticalAlign":"middle","height":56,"minWidth":92}},"children":{"type":"Stack","props":{"gap":4,"align":"center"},"children":[{"type":"Text","props":{"ta":"center","size":"xs","c":"var(--fg)","fw":700},"children":"9"},{"type":"Group","props":{"gap":4,"justify":"center"},"children":[{"type":"Badge","props":{"color":"yellow","size":"sm","variant":"filled","radius":"sm"},"children":"R5"}]}]}},{"type":"Table.Td","props":{"style":{"background":"rgba(233,192,41,0.30)","textAlign":"center","verticalAlign":"middle","height":56,"minWidth":92}},"children":{"type":"Stack","props":{"gap":4,"align":"center"},"children":[{"type":"Text","props":{"ta":"center","size":"xs","c":"var(--fg)","fw":700},"children":"12"}]}},{"type":"Table.Td","props":{"style":{"background":"rgba(249,171,41,0.30)","textAlign":"center","verticalAlign":"middle","height":56,"minWidth":92}},"children":{"type":"Stack","props":{"gap":4,"align":"center"},"children":[{"type":"Text","props":{"ta":"center","size":"xs","c":"var(--fg)","fw":700},"children":"15"},{"type":"Group","props":{"gap":4,"justify":"center"},"children":[{"type":"Badge","props":{"color":"orange","size":"sm","variant":"filled","radius":"sm"},"children":"R6"}]}]}}]},{"type":"Table.Tr","children":[{"type":"Table.Th","props":{"style":{"whiteSpace":"nowrap"}},"children":{"type":"Text","props":{"size":"xs","fw":600},"children":"Minor"}},{"type":"Table.Td","props":{"style":{"background":"rgba(52,197,89,0.30)","textAlign":"center","verticalAlign":"middle","height":56,"minWidth":92}},"children":{"type":"Stack","props":{"gap":4,"align":"center"},"children":[{"type":"Text","props":{"ta":"center","size":"xs","c":"var(--fg)","fw":700},"children":"2"}]}},{"type":"Table.Td","props":{"style":{"background":"rgba(88,196,80,0.30)","textAlign":"center","verticalAlign":"middle","height":56,"minWidth":92}},"children":{"type":"Stack","props":{"gap":4,"align":"center"},"children":[{"type":"Text","props":{"ta":"center","size":"xs","c":"var(--fg)","fw":700},"children":"4"},{"type":"Group","props":{"gap":4,"justify":"center"},"children":[{"type":"Badge","props":{"color":"teal","size":"sm","variant":"filled","radius":"sm"},"children":"R7"}]}]}},{"type":"Table.Td","props":{"style":{"background":"rgba(124,195,70,0.30)","textAlign":"center","verticalAlign":"middle","height":56,"minWidth":92}},"children":{"type":"Stack","props":{"gap":4,"align":"center"},"children":[{"type":"Text","props":{"ta":"center","size":"xs","c":"var(--fg)","fw":700},"children":"6"}]}},{"type":"Table.Td","props":{"style":{"background":"rgba(161,194,60,0.30)","textAlign":"center","verticalAlign":"middle","height":56,"minWidth":92}},"children":{"type":"Stack","props":{"gap":4,"align":"center"},"children":[{"type":"Text","props":{"ta":"center","size":"xs","c":"var(--fg)","fw":700},"children":"8"},{"type":"Group","props":{"gap":4,"justify":"center"},"children":[{"type":"Badge","props":{"color":"yellow","size":"sm","variant":"filled","radius":"sm"},"children":"R8"}]}]}},{"type":"Table.Td","props":{"style":{"background":"rgba(197,193,51,0.30)","textAlign":"center","verticalAlign":"middle","height":56,"minWidth":92}},"children":{"type":"Stack","props":{"gap":4,"align":"center"},"children":[{"type":"Text","props":{"ta":"center","size":"xs","c":"var(--fg)","fw":700},"children":"10"}]}}]},{"type":"Table.Tr","children":[{"type":"Table.Th","props":{"style":{"whiteSpace":"nowrap"}},"children":{"type":"Text","props":{"size":"xs","fw":600},"children":"Negligible"}},{"type":"Table.Td","props":{"style":{"background":"rgba(34,197,94,0.30)","textAlign":"center","verticalAlign":"middle","height":56,"minWidth":92}},"children":{"type":"Stack","props":{"gap":4,"align":"center"},"children":[{"type":"Text","props":{"ta":"center","size":"xs","c":"var(--fg)","fw":700},"children":"1"},{"type":"Group","props":{"gap":4,"justify":"center"},"children":[{"type":"Badge","props":{"color":"green","size":"sm","variant":"filled","radius":"sm"},"children":"R9"}]}]}},{"type":"Table.Td","props":{"style":{"background":"rgba(52,197,89,0.30)","textAlign":"center","verticalAlign":"middle","height":56,"minWidth":92}},"children":{"type":"Stack","props":{"gap":4,"align":"center"},"children":[{"type":"Text","props":{"ta":"center","size":"xs","c":"var(--fg)","fw":700},"children":"2"}]}},{"type":"Table.Td","props":{"style":{"background":"rgba(70,196,84,0.30)","textAlign":"center","verticalAlign":"middle","height":56,"minWidth":92}},"children":{"type":"Stack","props":{"gap":4,"align":"center"},"children":[{"type":"Text","props":{"ta":"center","size":"xs","c":"var(--fg)","fw":700},"children":"3"}]}},{"type":"Table.Td","props":{"style":{"background":"rgba(88,196,80,0.30)","textAlign":"center","verticalAlign":"middle","height":56,"minWidth":92}},"children":{"type":"Stack","props":{"gap":4,"align":"center"},"children":[{"type":"Text","props":{"ta":"center","size":"xs","c":"var(--fg)","fw":700},"children":"4"}]}},{"type":"Table.Td","props":{"style":{"background":"rgba(106,195,75,0.30)","textAlign":"center","verticalAlign":"middle","height":56,"minWidth":92}},"children":{"type":"Stack","props":{"gap":4,"align":"center"},"children":[{"type":"Text","props":{"ta":"center","size":"xs","c":"var(--fg)","fw":700},"children":"5"}]}}]}]}]},{"type":"Group","props":{"gap":"lg","align":"center","mt":4},"children":[{"type":"Badge","props":{"color":"gray","variant":"light","size":"sm","tt":"none"},"children":"R1–R10 · risk register"},{"type":"Group","props":{"gap":6},"children":[{"type":"Box","props":{"w":14,"h":14,"style":{"background":"rgba(52,197,89,0.30)","borderRadius":3}},"children":null},{"type":"Text","props":{"size":"xs","c":"dimmed"},"children":"low (1–5)"}]},{"type":"Group","props":{"gap":6},"children":[{"type":"Box","props":{"w":14,"h":14,"style":{"background":"rgba(233,192,41,0.30)","borderRadius":3}},"children":null},{"type":"Text","props":{"size":"xs","c":"dimmed"},"children":"medium (6–12)"}]},{"type":"Group","props":{"gap":6},"children":[{"type":"Box","props":{"w":14,"h":14,"style":{"background":"rgba(244,119,55,0.30)","borderRadius":3}},"children":null},{"type":"Text","props":{"size":"xs","c":"dimmed"},"children":"high (15–25)"}]}]}]} diff --git a/plugin/skills/diagram-recipes/examples/roadmap.vegalite.json b/plugin/skills/diagram-recipes/examples/roadmap.vegalite.json index fc0c69d..da73be1 100644 --- a/plugin/skills/diagram-recipes/examples/roadmap.vegalite.json +++ b/plugin/skills/diagram-recipes/examples/roadmap.vegalite.json @@ -1,53 +1 @@ -{ - "$schema": "https://vega.github.io/schema/vega-lite/v6.json", - "title": { "text": "2026 product roadmap", "subtitle": "initiatives across four quarters, grouped by team", "anchor": "start" }, - "width": "container", - "height": 360, - "data": { - "values": [ - { "initiative": "Onboarding revamp", "team": "Growth", "start": "2026-01-01", "end": "2026-04-01", "span": "Q1" }, - { "initiative": "Billing v2", "team": "Platform", "start": "2026-01-01", "end": "2026-07-01", "span": "Q1–Q2" }, - { "initiative": "Realtime collaboration", "team": "Product", "start": "2026-04-01", "end": "2026-10-01", "span": "Q2–Q3" }, - { "initiative": "Mobile app", "team": "Product", "start": "2026-04-01", "end": "2026-07-01", "span": "Q2" }, - { "initiative": "Data warehouse", "team": "Data", "start": "2026-04-01", "end": "2027-01-01", "span": "Q2–Q4" }, - { "initiative": "AI insights", "team": "Data", "start": "2026-07-01", "end": "2027-01-01", "span": "Q3–Q4" }, - { "initiative": "Enterprise SSO", "team": "Platform", "start": "2026-07-01", "end": "2026-10-01", "span": "Q3" }, - { "initiative": "Marketplace", "team": "Growth", "start": "2026-10-01", "end": "2027-01-01", "span": "Q4" } - ] - }, - "mark": { "type": "bar", "cornerRadius": 4, "height": { "band": 0.65 } }, - "encoding": { - "y": { - "field": "initiative", - "type": "nominal", - "sort": { "field": "start", "order": "ascending" }, - "title": null, - "axis": { "labelLimit": 220 } - }, - "x": { - "field": "start", - "type": "temporal", - "title": null, - "scale": { "domain": ["2026-01-01", "2027-01-01"] }, - "axis": { "format": "Q%q '%y", "tickCount": "quarter", "grid": true, "labelAngle": 0 } - }, - "x2": { "field": "end" }, - "color": { - "field": "team", - "type": "nominal", - "title": "team", - "scale": { - "domain": ["Product", "Platform", "Data", "Growth"], - "range": ["#60a5fa", "#a78bfa", "#34d399", "#f472b6"] - }, - "legend": { "orient": "top" } - }, - "tooltip": [ - { "field": "initiative", "type": "nominal", "title": "initiative" }, - { "field": "team", "type": "nominal", "title": "team" }, - { "field": "span", "type": "nominal", "title": "span" }, - { "field": "start", "type": "temporal", "title": "start", "format": "%b %Y" }, - { "field": "end", "type": "temporal", "title": "end", "format": "%b %Y" } - ] - } -} +{"$schema":"https://vega.github.io/schema/vega-lite/v6.json","title":{"text":"2026 product roadmap","subtitle":"initiatives across four quarters, grouped by team","anchor":"start"},"width":"container","height":360,"data":{"values":[{"initiative":"Onboarding revamp","team":"Growth","start":"2026-01-01","end":"2026-04-01","span":"Q1"},{"initiative":"Billing v2","team":"Platform","start":"2026-01-01","end":"2026-07-01","span":"Q1–Q2"},{"initiative":"Realtime collaboration","team":"Product","start":"2026-04-01","end":"2026-10-01","span":"Q2–Q3"},{"initiative":"Mobile app","team":"Product","start":"2026-04-01","end":"2026-07-01","span":"Q2"},{"initiative":"Data warehouse","team":"Data","start":"2026-04-01","end":"2027-01-01","span":"Q2–Q4"},{"initiative":"AI insights","team":"Data","start":"2026-07-01","end":"2027-01-01","span":"Q3–Q4"},{"initiative":"Enterprise SSO","team":"Platform","start":"2026-07-01","end":"2026-10-01","span":"Q3"},{"initiative":"Marketplace","team":"Growth","start":"2026-10-01","end":"2027-01-01","span":"Q4"}]},"mark":{"type":"bar","cornerRadius":4,"height":{"band":0.65}},"encoding":{"y":{"field":"initiative","type":"nominal","sort":{"field":"start","order":"ascending"},"title":null,"axis":{"labelLimit":220}},"x":{"field":"start","type":"temporal","title":null,"scale":{"domain":["2026-01-01","2027-01-01"]},"axis":{"format":"Q%q '%y","tickCount":"quarter","grid":true,"labelAngle":0}},"x2":{"field":"end"},"color":{"field":"team","type":"nominal","title":"team","scale":{"domain":["Product","Platform","Data","Growth"],"range":["#60a5fa","#a78bfa","#34d399","#f472b6"]},"legend":{"orient":"top"}},"tooltip":[{"field":"initiative","type":"nominal","title":"initiative"},{"field":"team","type":"nominal","title":"team"},{"field":"span","type":"nominal","title":"span"},{"field":"start","type":"temporal","title":"start","format":"%b %Y"},{"field":"end","type":"temporal","title":"end","format":"%b %Y"}]}} diff --git a/plugin/skills/diagram-recipes/examples/sequence.flow.json b/plugin/skills/diagram-recipes/examples/sequence.flow.json index 988a63f..6c5c46b 100644 --- a/plugin/skills/diagram-recipes/examples/sequence.flow.json +++ b/plugin/skills/diagram-recipes/examples/sequence.flow.json @@ -1,154 +1 @@ -{ - "nodes": [ - { - "id": "lf_user", - "type": "lifeline", - "position": { - "x": 0, - "y": 0 - }, - "data": { - "label": "User", - "height": 240 - } - }, - { - "id": "lf_api", - "type": "lifeline", - "position": { - "x": 240, - "y": 0 - }, - "data": { - "label": "API", - "height": 240 - } - }, - { - "id": "lf_db", - "type": "lifeline", - "position": { - "x": 480, - "y": 0 - }, - "data": { - "label": "DB", - "height": 240 - } - }, - { - "id": "u80", - "type": "point", - "position": { - "x": 70, - "y": 80 - }, - "data": {} - }, - { - "id": "a80", - "type": "point", - "position": { - "x": 310, - "y": 80 - }, - "data": {} - }, - { - "id": "a140", - "type": "point", - "position": { - "x": 310, - "y": 140 - }, - "data": {} - }, - { - "id": "d140", - "type": "point", - "position": { - "x": 550, - "y": 140 - }, - "data": {} - }, - { - "id": "d200", - "type": "point", - "position": { - "x": 550, - "y": 200 - }, - "data": {} - }, - { - "id": "a200", - "type": "point", - "position": { - "x": 310, - "y": 200 - }, - "data": {} - }, - { - "id": "a260", - "type": "point", - "position": { - "x": 310, - "y": 260 - }, - "data": {} - }, - { - "id": "u260", - "type": "point", - "position": { - "x": 70, - "y": 260 - }, - "data": {} - } - ], - "edges": [ - { - "id": "m1", - "source": "u80", - "target": "a80", - "label": "login(user, pw)", - "type": "straight", - "markerEnd": { - "type": "arrowclosed" - } - }, - { - "id": "m2", - "source": "a140", - "target": "d140", - "label": "SELECT * users", - "type": "straight", - "markerEnd": { - "type": "arrowclosed" - } - }, - { - "id": "m3", - "source": "d200", - "target": "a200", - "label": "user row", - "type": "straight", - "markerEnd": { - "type": "arrowclosed" - } - }, - { - "id": "m4", - "source": "a260", - "target": "u260", - "label": "200 + token", - "type": "straight", - "markerEnd": { - "type": "arrowclosed" - } - } - ] -} +{"nodes":[{"id":"lf_user","type":"lifeline","position":{"x":0,"y":0},"data":{"label":"User","height":240}},{"id":"lf_api","type":"lifeline","position":{"x":240,"y":0},"data":{"label":"API","height":240}},{"id":"lf_db","type":"lifeline","position":{"x":480,"y":0},"data":{"label":"DB","height":240}},{"id":"u80","type":"point","position":{"x":70,"y":80},"data":{}},{"id":"a80","type":"point","position":{"x":310,"y":80},"data":{}},{"id":"a140","type":"point","position":{"x":310,"y":140},"data":{}},{"id":"d140","type":"point","position":{"x":550,"y":140},"data":{}},{"id":"d200","type":"point","position":{"x":550,"y":200},"data":{}},{"id":"a200","type":"point","position":{"x":310,"y":200},"data":{}},{"id":"a260","type":"point","position":{"x":310,"y":260},"data":{}},{"id":"u260","type":"point","position":{"x":70,"y":260},"data":{}}],"edges":[{"id":"m1","source":"u80","target":"a80","label":"login(user, pw)","type":"straight","markerEnd":{"type":"arrowclosed"}},{"id":"m2","source":"a140","target":"d140","label":"SELECT * users","type":"straight","markerEnd":{"type":"arrowclosed"}},{"id":"m3","source":"d200","target":"a200","label":"user row","type":"straight","markerEnd":{"type":"arrowclosed"}},{"id":"m4","source":"a260","target":"u260","label":"200 + token","type":"straight","markerEnd":{"type":"arrowclosed"}}]} diff --git a/plugin/skills/diagram-recipes/examples/service-health.flow.json b/plugin/skills/diagram-recipes/examples/service-health.flow.json index 4f15773..0739418 100644 --- a/plugin/skills/diagram-recipes/examples/service-health.flow.json +++ b/plugin/skills/diagram-recipes/examples/service-health.flow.json @@ -1,67 +1 @@ -{ - "direction": "LR", - "legend": [ - { "label": "healthy", "color": "#22c55e" }, - { "label": "degraded", "color": "#fbbf24" }, - { "label": "down", "color": "#ef4444" }, - { "label": "request path", "color": "#a1a1aa" } - ], - "nodes": [ - { "id": "gateway", "type": "change", "data": { - "label": "API Gateway", "status": "success", "kind": "edge", "icon": "arrow", - "sub": "ingress · 12k rps", "meta": "p95 41ms", - "spark": [38, 40, 39, 42, 40, 41, 39, 41] } }, - - { "id": "auth", "type": "change", "data": { - "label": "Auth", "status": "success", "kind": "service", "icon": "user", - "sub": "token issuance", "meta": "p95 58ms", - "spark": [55, 60, 57, 59, 56, 58, 57, 58] } }, - - { "id": "orders", "type": "change", "data": { - "label": "Orders", "status": "warn", "kind": "service", "icon": "warn", - "sub": "checkout · degrading", "meta": "p95 214ms", - "spark": [88, 96, 110, 128, 150, 176, 198, 214] } }, - - { "id": "inventory", "type": "change", "data": { - "label": "Inventory", "status": "success", "kind": "service", "icon": "check", - "sub": "stock lookups", "meta": "p95 73ms", - "spark": [70, 75, 72, 78, 71, 74, 73, 73] } }, - - { "id": "payments", "type": "change", "data": { - "label": "Payments", "status": "error", "kind": "service", "icon": "x", - "sub": "provider timeout", "meta": "err 38%", - "spark": [2, 3, 6, 12, 22, 30, 35, 38], "sparkKind": "bar" } }, - - { "id": "search", "type": "change", "data": { - "label": "Search", "status": "success", "kind": "service", "icon": "search", - "sub": "catalog index", "meta": "p95 64ms", - "spark": [62, 68, 60, 66, 63, 65, 64, 64] } }, - - { "id": "pg", "type": "change", "data": { - "label": "PostgreSQL", "status": "warn", "kind": "datastore", "icon": "db", - "sub": "orders primary", "meta": "conns 91%", - "spark": [60, 64, 70, 75, 80, 84, 88, 91], "sparkKind": "bar" } }, - - { "id": "redis", "type": "change", "data": { - "label": "Redis", "status": "success", "kind": "cache", "icon": "db", - "sub": "session / hot keys", "meta": "hit 98%", - "spark": [97, 98, 98, 97, 99, 98, 98, 98] } }, - - { "id": "payments-db", "type": "change", "data": { - "label": "Payments DB", "status": "success", "kind": "datastore", "icon": "db", - "sub": "ledger", "meta": "p95 19ms", - "spark": [18, 20, 19, 21, 18, 20, 19, 19] } } - ], - "edges": [ - { "source": "gateway", "target": "auth", "style": { "stroke": "#22c55e" }, "markerEnd": { "type": "arrowclosed", "color": "#22c55e" } }, - { "source": "gateway", "target": "orders", "style": { "stroke": "#fbbf24" }, "markerEnd": { "type": "arrowclosed", "color": "#fbbf24" } }, - { "source": "gateway", "target": "search", "style": { "stroke": "#22c55e" }, "markerEnd": { "type": "arrowclosed", "color": "#22c55e" } }, - { "source": "orders", "target": "inventory", "style": { "stroke": "#22c55e" }, "markerEnd": { "type": "arrowclosed", "color": "#22c55e" } }, - { "source": "orders", "target": "payments", "style": { "stroke": "#ef4444", "strokeWidth": 2 }, "markerEnd": { "type": "arrowclosed", "color": "#ef4444" } }, - { "source": "orders", "target": "pg", "style": { "stroke": "#fbbf24" }, "markerEnd": { "type": "arrowclosed", "color": "#fbbf24" } }, - { "source": "auth", "target": "redis", "style": { "stroke": "#22c55e" }, "markerEnd": { "type": "arrowclosed", "color": "#22c55e" } }, - { "source": "inventory", "target": "pg", "style": { "stroke": "#fbbf24" }, "markerEnd": { "type": "arrowclosed", "color": "#fbbf24" } }, - { "source": "payments", "target": "payments-db", "style": { "stroke": "#22c55e" }, "markerEnd": { "type": "arrowclosed", "color": "#22c55e" } }, - { "source": "search", "target": "redis", "style": { "stroke": "#22c55e" }, "markerEnd": { "type": "arrowclosed", "color": "#22c55e" } } - ] -} +{"direction":"LR","legend":[{"label":"healthy","color":"#22c55e"},{"label":"degraded","color":"#fbbf24"},{"label":"down","color":"#ef4444"},{"label":"request path","color":"#a1a1aa"}],"nodes":[{"id":"gateway","type":"change","data":{"label":"API Gateway","status":"success","kind":"edge","icon":"arrow","sub":"ingress · 12k rps","meta":"p95 41ms","spark":[38,40,39,42,40,41,39,41]}},{"id":"auth","type":"change","data":{"label":"Auth","status":"success","kind":"service","icon":"user","sub":"token issuance","meta":"p95 58ms","spark":[55,60,57,59,56,58,57,58]}},{"id":"orders","type":"change","data":{"label":"Orders","status":"warn","kind":"service","icon":"warn","sub":"checkout · degrading","meta":"p95 214ms","spark":[88,96,110,128,150,176,198,214]}},{"id":"inventory","type":"change","data":{"label":"Inventory","status":"success","kind":"service","icon":"check","sub":"stock lookups","meta":"p95 73ms","spark":[70,75,72,78,71,74,73,73]}},{"id":"payments","type":"change","data":{"label":"Payments","status":"error","kind":"service","icon":"x","sub":"provider timeout","meta":"err 38%","spark":[2,3,6,12,22,30,35,38],"sparkKind":"bar"}},{"id":"search","type":"change","data":{"label":"Search","status":"success","kind":"service","icon":"search","sub":"catalog index","meta":"p95 64ms","spark":[62,68,60,66,63,65,64,64]}},{"id":"pg","type":"change","data":{"label":"PostgreSQL","status":"warn","kind":"datastore","icon":"db","sub":"orders primary","meta":"conns 91%","spark":[60,64,70,75,80,84,88,91],"sparkKind":"bar"}},{"id":"redis","type":"change","data":{"label":"Redis","status":"success","kind":"cache","icon":"db","sub":"session / hot keys","meta":"hit 98%","spark":[97,98,98,97,99,98,98,98]}},{"id":"payments-db","type":"change","data":{"label":"Payments DB","status":"success","kind":"datastore","icon":"db","sub":"ledger","meta":"p95 19ms","spark":[18,20,19,21,18,20,19,19]}}],"edges":[{"source":"gateway","target":"auth","style":{"stroke":"#22c55e"},"markerEnd":{"type":"arrowclosed","color":"#22c55e"}},{"source":"gateway","target":"orders","style":{"stroke":"#fbbf24"},"markerEnd":{"type":"arrowclosed","color":"#fbbf24"}},{"source":"gateway","target":"search","style":{"stroke":"#22c55e"},"markerEnd":{"type":"arrowclosed","color":"#22c55e"}},{"source":"orders","target":"inventory","style":{"stroke":"#22c55e"},"markerEnd":{"type":"arrowclosed","color":"#22c55e"}},{"source":"orders","target":"payments","style":{"stroke":"#ef4444","strokeWidth":2},"markerEnd":{"type":"arrowclosed","color":"#ef4444"}},{"source":"orders","target":"pg","style":{"stroke":"#fbbf24"},"markerEnd":{"type":"arrowclosed","color":"#fbbf24"}},{"source":"auth","target":"redis","style":{"stroke":"#22c55e"},"markerEnd":{"type":"arrowclosed","color":"#22c55e"}},{"source":"inventory","target":"pg","style":{"stroke":"#fbbf24"},"markerEnd":{"type":"arrowclosed","color":"#fbbf24"}},{"source":"payments","target":"payments-db","style":{"stroke":"#22c55e"},"markerEnd":{"type":"arrowclosed","color":"#22c55e"}},{"source":"search","target":"redis","style":{"stroke":"#22c55e"},"markerEnd":{"type":"arrowclosed","color":"#22c55e"}}]} diff --git a/plugin/skills/diagram-recipes/examples/sprint-burndown.vegalite.json b/plugin/skills/diagram-recipes/examples/sprint-burndown.vegalite.json index a564319..90226a9 100644 --- a/plugin/skills/diagram-recipes/examples/sprint-burndown.vegalite.json +++ b/plugin/skills/diagram-recipes/examples/sprint-burndown.vegalite.json @@ -1,237 +1 @@ -{ - "$schema": "https://vega.github.io/schema/vega-lite/v6.json", - "title": { - "text": "Sprint 24 — burndown", - "subtitle": "ideal vs. actual story points remaining · 10-day sprint", - "anchor": "start" - }, - "width": "container", - "height": 320, - "layer": [ - { - "data": { - "values": [ - { - "day": 0, - "pts": 80, - "series": "ideal" - }, - { - "day": 1, - "pts": 72, - "series": "ideal" - }, - { - "day": 2, - "pts": 64, - "series": "ideal" - }, - { - "day": 3, - "pts": 56, - "series": "ideal" - }, - { - "day": 4, - "pts": 48, - "series": "ideal" - }, - { - "day": 5, - "pts": 40, - "series": "ideal" - }, - { - "day": 6, - "pts": 32, - "series": "ideal" - }, - { - "day": 7, - "pts": 24, - "series": "ideal" - }, - { - "day": 8, - "pts": 16, - "series": "ideal" - }, - { - "day": 9, - "pts": 8, - "series": "ideal" - }, - { - "day": 10, - "pts": 0, - "series": "ideal" - } - ] - }, - "mark": { - "type": "line", - "strokeDash": [ - 6, - 4 - ], - "color": "#71717a", - "strokeWidth": 2, - "point": false - }, - "encoding": { - "x": { - "field": "day", - "type": "quantitative", - "title": "sprint day", - "axis": { - "tickMinStep": 1, - "grid": true - } - }, - "y": { - "field": "pts", - "type": "quantitative", - "title": "story points remaining", - "scale": { - "domain": [ - 0, - 80 - ] - } - } - } - }, - { - "data": { - "values": [ - { - "day": 0, - "pts": 80, - "series": "actual" - }, - { - "day": 1, - "pts": 80, - "series": "actual" - }, - { - "day": 2, - "pts": 74, - "series": "actual" - }, - { - "day": 3, - "pts": 70, - "series": "actual" - }, - { - "day": 4, - "pts": 67, - "series": "actual" - }, - { - "day": 5, - "pts": 58, - "series": "actual" - }, - { - "day": 6, - "pts": 49, - "series": "actual" - } - ] - }, - "mark": { - "type": "line", - "color": "#60a5fa", - "strokeWidth": 2.5, - "point": { - "filled": true, - "size": 55 - }, - "tooltip": true - }, - "encoding": { - "x": { - "field": "day", - "type": "quantitative" - }, - "y": { - "field": "pts", - "type": "quantitative" - }, - "tooltip": [ - { - "field": "day", - "type": "quantitative", - "title": "day" - }, - { - "field": "pts", - "type": "quantitative", - "title": "remaining" - } - ] - } - }, - { - "data": { - "values": [ - { - "day": 6, - "label": "today" - } - ] - }, - "mark": { - "type": "rule", - "color": "#fbbf24", - "strokeWidth": 2, - "strokeDash": [ - 4, - 3 - ] - }, - "encoding": { - "x": { - "field": "day", - "type": "quantitative" - } - } - }, - { - "data": { - "values": [ - { - "day": 6, - "label": "today", - "y": 80 - } - ] - }, - "mark": { - "type": "text", - "color": "#fbbf24", - "align": "left", - "dx": 5, - "dy": 2, - "baseline": "top", - "fontWeight": "bold", - "fontSize": 11 - }, - "encoding": { - "x": { - "field": "day", - "type": "quantitative" - }, - "y": { - "field": "y", - "type": "quantitative" - }, - "text": { - "field": "label" - } - } - } - ] -} +{"$schema":"https://vega.github.io/schema/vega-lite/v6.json","title":{"text":"Sprint 24 — burndown","subtitle":"ideal vs. actual story points remaining · 10-day sprint","anchor":"start"},"width":"container","height":320,"layer":[{"data":{"values":[{"day":0,"pts":80,"series":"ideal"},{"day":1,"pts":72,"series":"ideal"},{"day":2,"pts":64,"series":"ideal"},{"day":3,"pts":56,"series":"ideal"},{"day":4,"pts":48,"series":"ideal"},{"day":5,"pts":40,"series":"ideal"},{"day":6,"pts":32,"series":"ideal"},{"day":7,"pts":24,"series":"ideal"},{"day":8,"pts":16,"series":"ideal"},{"day":9,"pts":8,"series":"ideal"},{"day":10,"pts":0,"series":"ideal"}]},"mark":{"type":"line","strokeDash":[6,4],"color":"#71717a","strokeWidth":2,"point":false},"encoding":{"x":{"field":"day","type":"quantitative","title":"sprint day","axis":{"tickMinStep":1,"grid":true}},"y":{"field":"pts","type":"quantitative","title":"story points remaining","scale":{"domain":[0,80]}}}},{"data":{"values":[{"day":0,"pts":80,"series":"actual"},{"day":1,"pts":80,"series":"actual"},{"day":2,"pts":74,"series":"actual"},{"day":3,"pts":70,"series":"actual"},{"day":4,"pts":67,"series":"actual"},{"day":5,"pts":58,"series":"actual"},{"day":6,"pts":49,"series":"actual"}]},"mark":{"type":"line","color":"#60a5fa","strokeWidth":2.5,"point":{"filled":true,"size":55},"tooltip":true},"encoding":{"x":{"field":"day","type":"quantitative"},"y":{"field":"pts","type":"quantitative"},"tooltip":[{"field":"day","type":"quantitative","title":"day"},{"field":"pts","type":"quantitative","title":"remaining"}]}},{"data":{"values":[{"day":6,"label":"today"}]},"mark":{"type":"rule","color":"#fbbf24","strokeWidth":2,"strokeDash":[4,3]},"encoding":{"x":{"field":"day","type":"quantitative"}}},{"data":{"values":[{"day":6,"label":"today","y":80}]},"mark":{"type":"text","color":"#fbbf24","align":"left","dx":5,"dy":2,"baseline":"top","fontWeight":"bold","fontSize":11},"encoding":{"x":{"field":"day","type":"quantitative"},"y":{"field":"y","type":"quantitative"},"text":{"field":"label"}}}]} diff --git a/plugin/skills/diagram-recipes/examples/stacktrace.component.json b/plugin/skills/diagram-recipes/examples/stacktrace.component.json index 22643c6..7403b55 100644 --- a/plugin/skills/diagram-recipes/examples/stacktrace.component.json +++ b/plugin/skills/diagram-recipes/examples/stacktrace.component.json @@ -1,177 +1 @@ -{ - "type": "Stack", - "props": { - "gap": "sm", - "p": "md" - }, - "children": [ - { - "type": "Title", - "props": { - "order": 4 - }, - "children": "Unhandled exception — request pipeline" - }, - { - "type": "Text", - "props": { - "c": "dimmed", - "size": "sm" - }, - "children": "Innermost frame first; colored by origin so the culprit stands out." - }, - { - "type": "Group", - "props": { - "gap": "xs", - "mt": 2 - }, - "children": [ - { - "type": "Badge", - "props": { - "color": "red", - "variant": "light", - "size": "sm" - }, - "children": "error / culprit" - }, - { - "type": "Badge", - "props": { - "color": "blue", - "variant": "light", - "size": "sm" - }, - "children": "your code" - }, - { - "type": "Badge", - "props": { - "color": "gray", - "variant": "light", - "size": "sm" - }, - "children": "library / runtime" - } - ] - }, - { - "type": "Card", - "props": { - "withBorder": true, - "padding": "sm", - "radius": "sm", - "mt": "xs" - }, - "children": { - "type": "Stack", - "props": { - "gap": 0 - }, - "children": [ - { - "type": "Text", - "props": { - "ff": "monospace", - "size": "xs", - "style": { - "whiteSpace": "pre", - "lineHeight": "1.6" - }, - "c": "var(--tone-error)" - }, - "children": "TypeError: Cannot read properties of undefined (reading 'id')" - }, - { - "type": "Text", - "props": { - "ff": "monospace", - "size": "xs", - "style": { - "whiteSpace": "pre", - "lineHeight": "1.6" - }, - "c": "var(--tone-error)" - }, - "children": " at getUser (src/services/user.ts:42:18)" - }, - { - "type": "Text", - "props": { - "ff": "monospace", - "size": "xs", - "style": { - "whiteSpace": "pre", - "lineHeight": "1.6" - }, - "c": "var(--tone-info)" - }, - "children": " at loadProfile (src/services/profile.ts:17:10)" - }, - { - "type": "Text", - "props": { - "ff": "monospace", - "size": "xs", - "style": { - "whiteSpace": "pre", - "lineHeight": "1.6" - }, - "c": "var(--tone-info)" - }, - "children": " at GET /me (src/api/handler.ts:88:5)" - }, - { - "type": "Text", - "props": { - "ff": "monospace", - "size": "xs", - "style": { - "whiteSpace": "pre", - "lineHeight": "1.6" - }, - "c": "dimmed" - }, - "children": " at Layer.handle (node_modules/express/lib/router/layer.js:95)" - }, - { - "type": "Text", - "props": { - "ff": "monospace", - "size": "xs", - "style": { - "whiteSpace": "pre", - "lineHeight": "1.6" - }, - "c": "dimmed" - }, - "children": " at Router.dispatch (node_modules/express/lib/router.js:281)" - }, - { - "type": "Text", - "props": { - "ff": "monospace", - "size": "xs", - "style": { - "whiteSpace": "pre", - "lineHeight": "1.6" - }, - "c": "dimmed" - }, - "children": " at Server.emit (node:events:520:28)" - } - ] - } - }, - { - "type": "Alert", - "props": { - "color": "red", - "variant": "light", - "title": "Likely cause" - }, - "children": "getUser() returns undefined on a cache miss — guard the result before reading .id (src/services/user.ts:42)." - } - ] -} +{"type":"Stack","props":{"gap":"sm","p":"md"},"children":[{"type":"Title","props":{"order":4},"children":"Unhandled exception — request pipeline"},{"type":"Text","props":{"c":"dimmed","size":"sm"},"children":"Innermost frame first; colored by origin so the culprit stands out."},{"type":"Group","props":{"gap":"xs","mt":2},"children":[{"type":"Badge","props":{"color":"red","variant":"light","size":"sm"},"children":"error / culprit"},{"type":"Badge","props":{"color":"blue","variant":"light","size":"sm"},"children":"your code"},{"type":"Badge","props":{"color":"gray","variant":"light","size":"sm"},"children":"library / runtime"}]},{"type":"Card","props":{"withBorder":true,"padding":"sm","radius":"sm","mt":"xs"},"children":{"type":"Stack","props":{"gap":0},"children":[{"type":"Text","props":{"ff":"monospace","size":"xs","style":{"whiteSpace":"pre","lineHeight":"1.6"},"c":"var(--tone-error)"},"children":"TypeError: Cannot read properties of undefined (reading 'id')"},{"type":"Text","props":{"ff":"monospace","size":"xs","style":{"whiteSpace":"pre","lineHeight":"1.6"},"c":"var(--tone-error)"},"children":" at getUser (src/services/user.ts:42:18)"},{"type":"Text","props":{"ff":"monospace","size":"xs","style":{"whiteSpace":"pre","lineHeight":"1.6"},"c":"var(--tone-info)"},"children":" at loadProfile (src/services/profile.ts:17:10)"},{"type":"Text","props":{"ff":"monospace","size":"xs","style":{"whiteSpace":"pre","lineHeight":"1.6"},"c":"var(--tone-info)"},"children":" at GET /me (src/api/handler.ts:88:5)"},{"type":"Text","props":{"ff":"monospace","size":"xs","style":{"whiteSpace":"pre","lineHeight":"1.6"},"c":"dimmed"},"children":" at Layer.handle (node_modules/express/lib/router/layer.js:95)"},{"type":"Text","props":{"ff":"monospace","size":"xs","style":{"whiteSpace":"pre","lineHeight":"1.6"},"c":"dimmed"},"children":" at Router.dispatch (node_modules/express/lib/router.js:281)"},{"type":"Text","props":{"ff":"monospace","size":"xs","style":{"whiteSpace":"pre","lineHeight":"1.6"},"c":"dimmed"},"children":" at Server.emit (node:events:520:28)"}]}},{"type":"Alert","props":{"color":"red","variant":"light","title":"Likely cause"},"children":"getUser() returns undefined on a cache miss — guard the result before reading .id (src/services/user.ts:42)."}]} diff --git a/plugin/skills/diagram-recipes/examples/state-machine.flow.json b/plugin/skills/diagram-recipes/examples/state-machine.flow.json index 8aa4f7f..585284c 100644 --- a/plugin/skills/diagram-recipes/examples/state-machine.flow.json +++ b/plugin/skills/diagram-recipes/examples/state-machine.flow.json @@ -1,77 +1 @@ -{ - "direction": "TB", - "nodes": [ - { - "id": "pending", - "data": { - "label": "Pending" - } - }, - { - "id": "paid", - "data": { - "label": "Paid" - } - }, - { - "id": "shipped", - "data": { - "label": "Shipped" - } - }, - { - "id": "delivered", - "data": { - "label": "Delivered" - } - }, - { - "id": "cancelled", - "data": { - "label": "Cancelled" - } - } - ], - "edges": [ - { - "source": "pending", - "target": "paid", - "label": "pay", - "markerEnd": { - "type": "arrowclosed" - } - }, - { - "source": "paid", - "target": "shipped", - "label": "fulfill", - "markerEnd": { - "type": "arrowclosed" - } - }, - { - "source": "shipped", - "target": "delivered", - "label": "deliver", - "markerEnd": { - "type": "arrowclosed" - } - }, - { - "source": "pending", - "target": "cancelled", - "label": "cancel", - "markerEnd": { - "type": "arrowclosed" - } - }, - { - "source": "paid", - "target": "cancelled", - "label": "refund", - "markerEnd": { - "type": "arrowclosed" - } - } - ] -} +{"direction":"TB","nodes":[{"id":"pending","data":{"label":"Pending"}},{"id":"paid","data":{"label":"Paid"}},{"id":"shipped","data":{"label":"Shipped"}},{"id":"delivered","data":{"label":"Delivered"}},{"id":"cancelled","data":{"label":"Cancelled"}}],"edges":[{"source":"pending","target":"paid","label":"pay","markerEnd":{"type":"arrowclosed"}},{"source":"paid","target":"shipped","label":"fulfill","markerEnd":{"type":"arrowclosed"}},{"source":"shipped","target":"delivered","label":"deliver","markerEnd":{"type":"arrowclosed"}},{"source":"pending","target":"cancelled","label":"cancel","markerEnd":{"type":"arrowclosed"}},{"source":"paid","target":"cancelled","label":"refund","markerEnd":{"type":"arrowclosed"}}]} diff --git a/plugin/skills/diagram-recipes/examples/swimlane.flow.json b/plugin/skills/diagram-recipes/examples/swimlane.flow.json index f1ea8c5..00ef68f 100644 --- a/plugin/skills/diagram-recipes/examples/swimlane.flow.json +++ b/plugin/skills/diagram-recipes/examples/swimlane.flow.json @@ -1,48 +1 @@ -{ - "lanes": true, - "direction": "LR", - "groups": [ - { "id": "customer", "label": "Customer", "color": "#60a5fa" }, - { "id": "frontend", "label": "Storefront", "color": "#a855f7" }, - { "id": "backend", "label": "Order Service", "color": "#fbbf24" }, - { "id": "ops", "label": "Fulfillment / Ops", "color": "#22c55e" } - ], - "legend": [ - { "label": "happy path", "color": "#22c55e" }, - { "label": "async / event", "color": "#a855f7", "dash": true }, - { "label": "failure / retry", "color": "#ef4444" } - ], - "nodes": [ - { "id": "browse", "type": "change", "group": "customer", "data": { "label": "Browse catalog", "status": "info", "kind": "step", "icon": "search", "sub": "product pages" } }, - { "id": "checkout", "type": "change", "group": "customer", "data": { "label": "Place order", "status": "active", "kind": "step", "icon": "user", "sub": "cart → checkout" } }, - { "id": "confirm", "type": "change", "group": "customer", "data": { "label": "Order confirmed", "status": "done", "kind": "step", "icon": "check", "meta": "email + SMS" } }, - - { "id": "cart", "type": "change", "group": "frontend", "data": { "label": "Cart API", "status": "info", "kind": "fn", "sub": "POST /cart" } }, - { "id": "submit", "type": "change", "group": "frontend", "data": { "label": "Submit order", "status": "active", "kind": "fn", "sub": "POST /orders", "meta": "p95 180ms" } }, - { "id": "status", "type": "change", "group": "frontend", "data": { "label": "Order status", "status": "done", "kind": "fn", "sub": "GET /orders/:id" } }, - - { "id": "validate", "type": "change", "group": "backend", "data": { "label": "Validate + price", "status": "active", "kind": "svc", "sub": "tax, promos, stock" } }, - { "id": "pay", "type": "change", "group": "backend", "data": { "label": "Charge payment", "status": "warn", "kind": "svc", "icon": "bolt", "sub": "Stripe", "meta": "1.2% decline" } }, - { "id": "persist", "type": "change", "group": "backend", "data": { "label": "Write order", "status": "success", "kind": "db", "icon": "db", "sub": "orders table" } }, - { "id": "emit", "type": "change", "group": "backend", "data": { "label": "Emit OrderPlaced", "status": "info", "kind": "event", "icon": "bolt", "sub": "Kafka" } }, - - { "id": "pick", "type": "change", "group": "ops", "data": { "label": "Pick + pack", "status": "active", "kind": "task", "sub": "warehouse WMS" } }, - { "id": "ship", "type": "change", "group": "ops", "data": { "label": "Ship", "status": "done", "kind": "task", "icon": "arrow", "meta": "carrier handoff" } }, - { "id": "retry", "type": "change", "group": "ops", "data": { "label": "Retry payment", "status": "error", "kind": "task", "icon": "warn", "sub": "dunning queue" } } - ], - "edges": [ - { "source": "browse", "target": "checkout", "style": { "stroke": "#22c55e" }, "markerEnd": { "type": "arrowclosed", "color": "#22c55e" } }, - { "source": "checkout", "target": "cart", "style": { "stroke": "#22c55e" }, "markerEnd": { "type": "arrowclosed", "color": "#22c55e" } }, - { "source": "cart", "target": "submit", "style": { "stroke": "#22c55e" }, "markerEnd": { "type": "arrowclosed", "color": "#22c55e" } }, - { "source": "submit", "target": "validate", "style": { "stroke": "#22c55e" }, "markerEnd": { "type": "arrowclosed", "color": "#22c55e" } }, - { "source": "validate", "target": "pay", "style": { "stroke": "#22c55e" }, "markerEnd": { "type": "arrowclosed", "color": "#22c55e" } }, - { "source": "pay", "target": "persist", "style": { "stroke": "#22c55e" }, "markerEnd": { "type": "arrowclosed", "color": "#22c55e" } }, - { "source": "pay", "target": "retry", "label": "declined", "style": { "stroke": "#ef4444", "strokeDasharray": "6 4" }, "markerEnd": { "type": "arrowclosed", "color": "#ef4444" } }, - { "source": "retry", "target": "pay", "style": { "stroke": "#ef4444", "strokeDasharray": "6 4" }, "markerEnd": { "type": "arrowclosed", "color": "#ef4444" } }, - { "source": "persist", "target": "emit", "style": { "stroke": "#22c55e" }, "markerEnd": { "type": "arrowclosed", "color": "#22c55e" } }, - { "source": "persist", "target": "status", "style": { "stroke": "#22c55e" }, "markerEnd": { "type": "arrowclosed", "color": "#22c55e" } }, - { "source": "status", "target": "confirm", "style": { "stroke": "#22c55e" }, "markerEnd": { "type": "arrowclosed", "color": "#22c55e" } }, - { "source": "emit", "target": "pick", "animated": true, "style": { "stroke": "#a855f7", "strokeDasharray": "6 4" }, "markerEnd": { "type": "arrowclosed", "color": "#a855f7" } }, - { "source": "pick", "target": "ship", "style": { "stroke": "#22c55e" }, "markerEnd": { "type": "arrowclosed", "color": "#22c55e" } } - ] -} +{"lanes":true,"direction":"LR","groups":[{"id":"customer","label":"Customer","color":"#60a5fa"},{"id":"frontend","label":"Storefront","color":"#a855f7"},{"id":"backend","label":"Order Service","color":"#fbbf24"},{"id":"ops","label":"Fulfillment / Ops","color":"#22c55e"}],"legend":[{"label":"happy path","color":"#22c55e"},{"label":"async / event","color":"#a855f7","dash":true},{"label":"failure / retry","color":"#ef4444"}],"nodes":[{"id":"browse","type":"change","group":"customer","data":{"label":"Browse catalog","status":"info","kind":"step","icon":"search","sub":"product pages"}},{"id":"checkout","type":"change","group":"customer","data":{"label":"Place order","status":"active","kind":"step","icon":"user","sub":"cart → checkout"}},{"id":"confirm","type":"change","group":"customer","data":{"label":"Order confirmed","status":"done","kind":"step","icon":"check","meta":"email + SMS"}},{"id":"cart","type":"change","group":"frontend","data":{"label":"Cart API","status":"info","kind":"fn","sub":"POST /cart"}},{"id":"submit","type":"change","group":"frontend","data":{"label":"Submit order","status":"active","kind":"fn","sub":"POST /orders","meta":"p95 180ms"}},{"id":"status","type":"change","group":"frontend","data":{"label":"Order status","status":"done","kind":"fn","sub":"GET /orders/:id"}},{"id":"validate","type":"change","group":"backend","data":{"label":"Validate + price","status":"active","kind":"svc","sub":"tax, promos, stock"}},{"id":"pay","type":"change","group":"backend","data":{"label":"Charge payment","status":"warn","kind":"svc","icon":"bolt","sub":"Stripe","meta":"1.2% decline"}},{"id":"persist","type":"change","group":"backend","data":{"label":"Write order","status":"success","kind":"db","icon":"db","sub":"orders table"}},{"id":"emit","type":"change","group":"backend","data":{"label":"Emit OrderPlaced","status":"info","kind":"event","icon":"bolt","sub":"Kafka"}},{"id":"pick","type":"change","group":"ops","data":{"label":"Pick + pack","status":"active","kind":"task","sub":"warehouse WMS"}},{"id":"ship","type":"change","group":"ops","data":{"label":"Ship","status":"done","kind":"task","icon":"arrow","meta":"carrier handoff"}},{"id":"retry","type":"change","group":"ops","data":{"label":"Retry payment","status":"error","kind":"task","icon":"warn","sub":"dunning queue"}}],"edges":[{"source":"browse","target":"checkout","style":{"stroke":"#22c55e"},"markerEnd":{"type":"arrowclosed","color":"#22c55e"}},{"source":"checkout","target":"cart","style":{"stroke":"#22c55e"},"markerEnd":{"type":"arrowclosed","color":"#22c55e"}},{"source":"cart","target":"submit","style":{"stroke":"#22c55e"},"markerEnd":{"type":"arrowclosed","color":"#22c55e"}},{"source":"submit","target":"validate","style":{"stroke":"#22c55e"},"markerEnd":{"type":"arrowclosed","color":"#22c55e"}},{"source":"validate","target":"pay","style":{"stroke":"#22c55e"},"markerEnd":{"type":"arrowclosed","color":"#22c55e"}},{"source":"pay","target":"persist","style":{"stroke":"#22c55e"},"markerEnd":{"type":"arrowclosed","color":"#22c55e"}},{"source":"pay","target":"retry","label":"declined","style":{"stroke":"#ef4444","strokeDasharray":"6 4"},"markerEnd":{"type":"arrowclosed","color":"#ef4444"}},{"source":"retry","target":"pay","style":{"stroke":"#ef4444","strokeDasharray":"6 4"},"markerEnd":{"type":"arrowclosed","color":"#ef4444"}},{"source":"persist","target":"emit","style":{"stroke":"#22c55e"},"markerEnd":{"type":"arrowclosed","color":"#22c55e"}},{"source":"persist","target":"status","style":{"stroke":"#22c55e"},"markerEnd":{"type":"arrowclosed","color":"#22c55e"}},{"source":"status","target":"confirm","style":{"stroke":"#22c55e"},"markerEnd":{"type":"arrowclosed","color":"#22c55e"}},{"source":"emit","target":"pick","animated":true,"style":{"stroke":"#a855f7","strokeDasharray":"6 4"},"markerEnd":{"type":"arrowclosed","color":"#a855f7"}},{"source":"pick","target":"ship","style":{"stroke":"#22c55e"},"markerEnd":{"type":"arrowclosed","color":"#22c55e"}}]} diff --git a/plugin/skills/diagram-recipes/examples/system-architecture.flow.json b/plugin/skills/diagram-recipes/examples/system-architecture.flow.json index 31109c6..a164b62 100644 --- a/plugin/skills/diagram-recipes/examples/system-architecture.flow.json +++ b/plugin/skills/diagram-recipes/examples/system-architecture.flow.json @@ -1,45 +1 @@ -{ - "direction": "LR", - "legend": [ - { "label": "edge / ingress", "color": "#a1a1aa" }, - { "label": "service", "color": "#fbbf24" }, - { "label": "datastore", "color": "#22c55e" }, - { "label": "async (animated)", "color": "#60a5fa", "dash": true } - ], - "nodes": [ - { "id": "client", "type": "change", "data": { "label": "Browser / Mobile", "status": "info", "icon": "user" } }, - { "id": "cdn", "type": "change", "data": { "label": "CDN / Edge", "status": "neutral", "kind": "edge" } }, - { "id": "lb", "type": "change", "data": { "label": "Load Balancer", "status": "neutral", "kind": "edge" } }, - { "id": "web", "type": "change", "data": { "label": "Web App", "status": "active", "kind": "frontend" } }, - { "id": "gw", "type": "change", "data": { "label": "API Gateway", "status": "info", "kind": "gateway" } }, - { "id": "auth", "type": "change", "data": { "label": "Auth Service", "status": "active", "kind": "service" } }, - { "id": "orders", "type": "change", "data": { "label": "Orders Service", "status": "active", "kind": "service" } }, - { "id": "inventory", "type": "change", "data": { "label": "Inventory Service", "status": "active", "kind": "service" } }, - { "id": "payments", "type": "change", "data": { "label": "Payments Service", "status": "active", "kind": "service" } }, - { "id": "pg", "type": "change", "data": { "label": "PostgreSQL", "status": "success", "icon": "db", "kind": "datastore" } }, - { "id": "redis", "type": "change", "data": { "label": "Redis Cache", "status": "warn", "icon": "db", "kind": "datastore" } }, - { "id": "kafka", "type": "change", "data": { "label": "Kafka", "status": "info", "icon": "bolt", "kind": "async" } }, - { "id": "worker", "type": "change", "data": { "label": "Fulfillment Worker", "status": "active", "kind": "service" } }, - { "id": "s3", "type": "change", "data": { "label": "Object Store", "status": "neutral", "icon": "db", "kind": "datastore" } } - ], - "edges": [ - { "source": "client", "target": "cdn" }, - { "source": "cdn", "target": "lb" }, - { "source": "lb", "target": "web" }, - { "source": "web", "target": "gw" }, - { "source": "gw", "target": "auth" }, - { "source": "gw", "target": "orders" }, - { "source": "gw", "target": "inventory" }, - { "source": "gw", "target": "payments" }, - { "source": "auth", "target": "redis", "style": { "stroke": "#22c55e" }, "markerEnd": { "type": "arrowclosed", "color": "#22c55e" } }, - { "source": "orders", "target": "pg", "style": { "stroke": "#22c55e" }, "markerEnd": { "type": "arrowclosed", "color": "#22c55e" } }, - { "source": "orders", "target": "kafka", "animated": true, "style": { "stroke": "#60a5fa" }, "markerEnd": { "type": "arrowclosed", "color": "#60a5fa" } }, - { "source": "inventory", "target": "pg", "style": { "stroke": "#22c55e" }, "markerEnd": { "type": "arrowclosed", "color": "#22c55e" } }, - { "source": "inventory", "target": "redis", "style": { "stroke": "#22c55e" }, "markerEnd": { "type": "arrowclosed", "color": "#22c55e" } }, - { "source": "payments", "target": "pg", "style": { "stroke": "#22c55e" }, "markerEnd": { "type": "arrowclosed", "color": "#22c55e" } }, - { "source": "kafka", "target": "worker", "animated": true, "style": { "stroke": "#60a5fa" }, "markerEnd": { "type": "arrowclosed", "color": "#60a5fa" } }, - { "source": "worker", "target": "pg", "style": { "stroke": "#22c55e" }, "markerEnd": { "type": "arrowclosed", "color": "#22c55e" } }, - { "source": "worker", "target": "s3", "style": { "stroke": "#22c55e" }, "markerEnd": { "type": "arrowclosed", "color": "#22c55e" } }, - { "source": "payments", "target": "s3", "style": { "stroke": "#22c55e" }, "markerEnd": { "type": "arrowclosed", "color": "#22c55e" } } - ] -} +{"direction":"LR","legend":[{"label":"edge / ingress","color":"#a1a1aa"},{"label":"service","color":"#fbbf24"},{"label":"datastore","color":"#22c55e"},{"label":"async (animated)","color":"#60a5fa","dash":true}],"nodes":[{"id":"client","type":"change","data":{"label":"Browser / Mobile","status":"info","icon":"user"}},{"id":"cdn","type":"change","data":{"label":"CDN / Edge","status":"neutral","kind":"edge"}},{"id":"lb","type":"change","data":{"label":"Load Balancer","status":"neutral","kind":"edge"}},{"id":"web","type":"change","data":{"label":"Web App","status":"active","kind":"frontend"}},{"id":"gw","type":"change","data":{"label":"API Gateway","status":"info","kind":"gateway"}},{"id":"auth","type":"change","data":{"label":"Auth Service","status":"active","kind":"service"}},{"id":"orders","type":"change","data":{"label":"Orders Service","status":"active","kind":"service"}},{"id":"inventory","type":"change","data":{"label":"Inventory Service","status":"active","kind":"service"}},{"id":"payments","type":"change","data":{"label":"Payments Service","status":"active","kind":"service"}},{"id":"pg","type":"change","data":{"label":"PostgreSQL","status":"success","icon":"db","kind":"datastore"}},{"id":"redis","type":"change","data":{"label":"Redis Cache","status":"warn","icon":"db","kind":"datastore"}},{"id":"kafka","type":"change","data":{"label":"Kafka","status":"info","icon":"bolt","kind":"async"}},{"id":"worker","type":"change","data":{"label":"Fulfillment Worker","status":"active","kind":"service"}},{"id":"s3","type":"change","data":{"label":"Object Store","status":"neutral","icon":"db","kind":"datastore"}}],"edges":[{"source":"client","target":"cdn"},{"source":"cdn","target":"lb"},{"source":"lb","target":"web"},{"source":"web","target":"gw"},{"source":"gw","target":"auth"},{"source":"gw","target":"orders"},{"source":"gw","target":"inventory"},{"source":"gw","target":"payments"},{"source":"auth","target":"redis","style":{"stroke":"#22c55e"},"markerEnd":{"type":"arrowclosed","color":"#22c55e"}},{"source":"orders","target":"pg","style":{"stroke":"#22c55e"},"markerEnd":{"type":"arrowclosed","color":"#22c55e"}},{"source":"orders","target":"kafka","animated":true,"style":{"stroke":"#60a5fa"},"markerEnd":{"type":"arrowclosed","color":"#60a5fa"}},{"source":"inventory","target":"pg","style":{"stroke":"#22c55e"},"markerEnd":{"type":"arrowclosed","color":"#22c55e"}},{"source":"inventory","target":"redis","style":{"stroke":"#22c55e"},"markerEnd":{"type":"arrowclosed","color":"#22c55e"}},{"source":"payments","target":"pg","style":{"stroke":"#22c55e"},"markerEnd":{"type":"arrowclosed","color":"#22c55e"}},{"source":"kafka","target":"worker","animated":true,"style":{"stroke":"#60a5fa"},"markerEnd":{"type":"arrowclosed","color":"#60a5fa"}},{"source":"worker","target":"pg","style":{"stroke":"#22c55e"},"markerEnd":{"type":"arrowclosed","color":"#22c55e"}},{"source":"worker","target":"s3","style":{"stroke":"#22c55e"},"markerEnd":{"type":"arrowclosed","color":"#22c55e"}},{"source":"payments","target":"s3","style":{"stroke":"#22c55e"},"markerEnd":{"type":"arrowclosed","color":"#22c55e"}}]} diff --git a/plugin/skills/diagram-recipes/examples/task-checklist.component.json b/plugin/skills/diagram-recipes/examples/task-checklist.component.json index 2c0dfb1..bdfc176 100644 --- a/plugin/skills/diagram-recipes/examples/task-checklist.component.json +++ b/plugin/skills/diagram-recipes/examples/task-checklist.component.json @@ -1,81 +1 @@ -{ - "type": "Stack", - "props": { "gap": "md" }, - "children": [ - { - "type": "Stack", - "props": { "gap": 2 }, - "children": [ - { - "type": "Title", - "props": { "order": 3 }, - "children": "Release 2.4 — deploy checklist" - }, - { - "type": "Text", - "props": { "c": "dimmed", "size": "sm" }, - "children": "The agent ticks items as it completes them; you approve the gated steps yourself." - } - ] - }, - { - "type": "Card", - "props": { "withBorder": true, "padding": "md", "radius": "md" }, - "children": [ - { - "type": "Group", - "props": { "gap": "xs", "mb": "xs" }, - "children": [ - { "type": "ThemeIcon", "props": { "color": "blue", "variant": "light", "size": "sm", "radius": "xl" }, "children": "1" }, - { "type": "Text", "props": { "fw": 600, "size": "sm" }, "children": "Automated — agent-driven progress (read-only)" } - ] - }, - { - "type": "Checklist", - "props": { - "id": "ci", - "editable": false, - "items": [ - { "id": "lint", "label": "Lint + type-check pass", "checked": true }, - { "id": "unit", "label": "Unit tests pass (248)", "checked": true }, - { "id": "build", "label": "Build image + push to registry", "checked": true }, - { "id": "e2e", "label": "E2E suite green", "checked": false } - ] - } - } - ] - }, - { - "type": "Card", - "props": { "withBorder": true, "padding": "md", "radius": "md" }, - "children": [ - { - "type": "Group", - "props": { "gap": "xs", "mb": "xs" }, - "children": [ - { "type": "ThemeIcon", "props": { "color": "grape", "variant": "light", "size": "sm", "radius": "xl" }, "children": "2" }, - { "type": "Text", "props": { "fw": 600, "size": "sm" }, "children": "Your sign-off — tick to approve (editable)" } - ] - }, - { - "type": "Checklist", - "props": { - "id": "approval", - "editable": true, - "items": [ - { "id": "changelog", "label": "Changelog reviewed", "checked": false }, - { "id": "staging", "label": "Verified on staging", "checked": false }, - { "id": "rollback", "label": "Rollback plan confirmed", "checked": false }, - { "id": "ship", "label": "Approve production deploy", "checked": false } - ] - } - } - ] - }, - { - "type": "Alert", - "props": { "color": "blue", "variant": "light", "title": "Two-way, durable state" }, - "children": "Either side can toggle: the agent uses `termchart patch --check ci/e2e`; you tap the boxes. Each change persists in the spec, so `termchart pull` reads back exactly what's ticked — and every connected screen stays in sync." - } - ] -} +{"type":"Stack","props":{"gap":"md"},"children":[{"type":"Stack","props":{"gap":2},"children":[{"type":"Title","props":{"order":3},"children":"Release 2.4 — deploy checklist"},{"type":"Text","props":{"c":"dimmed","size":"sm"},"children":"The agent ticks items as it completes them; you approve the gated steps yourself."}]},{"type":"Card","props":{"withBorder":true,"padding":"md","radius":"md"},"children":[{"type":"Group","props":{"gap":"xs","mb":"xs"},"children":[{"type":"ThemeIcon","props":{"color":"blue","variant":"light","size":"sm","radius":"xl"},"children":"1"},{"type":"Text","props":{"fw":600,"size":"sm"},"children":"Automated — agent-driven progress (read-only)"}]},{"type":"Checklist","props":{"id":"ci","editable":false,"items":[{"id":"lint","label":"Lint + type-check pass","checked":true},{"id":"unit","label":"Unit tests pass (248)","checked":true},{"id":"build","label":"Build image + push to registry","checked":true},{"id":"e2e","label":"E2E suite green","checked":false}]}}]},{"type":"Card","props":{"withBorder":true,"padding":"md","radius":"md"},"children":[{"type":"Group","props":{"gap":"xs","mb":"xs"},"children":[{"type":"ThemeIcon","props":{"color":"grape","variant":"light","size":"sm","radius":"xl"},"children":"2"},{"type":"Text","props":{"fw":600,"size":"sm"},"children":"Your sign-off — tick to approve (editable)"}]},{"type":"Checklist","props":{"id":"approval","editable":true,"items":[{"id":"changelog","label":"Changelog reviewed","checked":false},{"id":"staging","label":"Verified on staging","checked":false},{"id":"rollback","label":"Rollback plan confirmed","checked":false},{"id":"ship","label":"Approve production deploy","checked":false}]}}]},{"type":"Alert","props":{"color":"blue","variant":"light","title":"Two-way, durable state"},"children":"Either side can toggle: the agent uses `termchart patch --check ci/e2e`; you tap the boxes. Each change persists in the spec, so `termchart pull` reads back exactly what's ticked — and every connected screen stays in sync."}]} diff --git a/plugin/skills/diagram-recipes/examples/task-tracker.component.json b/plugin/skills/diagram-recipes/examples/task-tracker.component.json index bd85b68..db4a677 100644 --- a/plugin/skills/diagram-recipes/examples/task-tracker.component.json +++ b/plugin/skills/diagram-recipes/examples/task-tracker.component.json @@ -1,461 +1 @@ -{ - "type": "Stack", - "props": { - "gap": "md" - }, - "children": [ - { - "type": "Group", - "props": { - "justify": "space-between", - "align": "flex-end" - }, - "children": [ - { - "type": "Stack", - "props": { - "gap": 2 - }, - "children": [ - { - "type": "Title", - "props": { - "order": 3 - }, - "children": "STR · Release 2.4 status" - }, - { - "type": "Text", - "props": { - "c": "dimmed", - "size": "sm" - }, - "children": "Sprint 12 · 3 days to code freeze · 8 of 12 tasks done" - } - ] - }, - { - "type": "RingProgress", - "props": { - "size": 76, - "thickness": 8, - "roundCaps": true, - "sections": [ - { - "value": 67, - "color": "green" - } - ], - "label": { - "type": "Text", - "props": { - "ta": "center", - "size": "sm", - "fw": 700 - }, - "children": "67%" - } - } - } - ] - }, - { - "type": "SimpleGrid", - "props": { - "cols": 4, - "spacing": "sm" - }, - "children": [ - { - "type": "Card", - "props": { - "withBorder": true, - "padding": "sm", - "radius": "md" - }, - "children": [ - { - "type": "Text", - "props": { - "size": "xs", - "c": "dimmed" - }, - "children": "Done" - }, - { - "type": "Text", - "props": { - "fw": 700, - "size": "xl", - "c": "green" - }, - "children": "8" - } - ] - }, - { - "type": "Card", - "props": { - "withBorder": true, - "padding": "sm", - "radius": "md" - }, - "children": [ - { - "type": "Text", - "props": { - "size": "xs", - "c": "dimmed" - }, - "children": "In progress" - }, - { - "type": "Text", - "props": { - "fw": 700, - "size": "xl", - "c": "blue" - }, - "children": "2" - } - ] - }, - { - "type": "Card", - "props": { - "withBorder": true, - "padding": "sm", - "radius": "md" - }, - "children": [ - { - "type": "Text", - "props": { - "size": "xs", - "c": "dimmed" - }, - "children": "Blocked" - }, - { - "type": "Text", - "props": { - "fw": 700, - "size": "xl", - "c": "red" - }, - "children": "1" - } - ] - }, - { - "type": "Card", - "props": { - "withBorder": true, - "padding": "sm", - "radius": "md" - }, - "children": [ - { - "type": "Text", - "props": { - "size": "xs", - "c": "dimmed" - }, - "children": "To do" - }, - { - "type": "Text", - "props": { - "fw": 700, - "size": "xl" - }, - "children": "1" - } - ] - } - ] - }, - { - "type": "Divider" - }, - { - "type": "Table", - "props": { - "withTableBorder": true, - "striped": true, - "verticalSpacing": "xs", - "horizontalSpacing": "md" - }, - "children": [ - { - "type": "Table.Thead", - "children": [ - { - "type": "Table.Tr", - "children": [ - { - "type": "Table.Th", - "children": "Task" - }, - { - "type": "Table.Th", - "children": "Owner" - }, - { - "type": "Table.Th", - "children": "Status" - }, - { - "type": "Table.Th", - "children": "Due" - } - ] - } - ] - }, - { - "type": "Table.Tbody", - "children": [ - { - "type": "Table.Tr", - "children": [ - { - "type": "Table.Td", - "children": "Auth: rotate signing keys" - }, - { - "type": "Table.Td", - "children": "A. Rivera" - }, - { - "type": "Table.Td", - "children": { - "type": "Badge", - "props": { - "color": "green", - "variant": "light", - "size": "sm" - }, - "children": "done" - } - }, - { - "type": "Table.Td", - "children": [ - { - "type": "Text", - "props": { - "size": "sm", - "c": "dimmed" - }, - "children": "Mon" - } - ] - } - ] - }, - { - "type": "Table.Tr", - "children": [ - { - "type": "Table.Td", - "children": "Billing: proration fix" - }, - { - "type": "Table.Td", - "children": "S. Kim" - }, - { - "type": "Table.Td", - "children": { - "type": "Badge", - "props": { - "color": "green", - "variant": "light", - "size": "sm" - }, - "children": "done" - } - }, - { - "type": "Table.Td", - "children": [ - { - "type": "Text", - "props": { - "size": "sm", - "c": "dimmed" - }, - "children": "Tue" - } - ] - } - ] - }, - { - "type": "Table.Tr", - "children": [ - { - "type": "Table.Td", - "children": "Search: relevance v2" - }, - { - "type": "Table.Td", - "children": "J. Patel" - }, - { - "type": "Table.Td", - "children": { - "type": "Badge", - "props": { - "color": "blue", - "variant": "light", - "size": "sm" - }, - "children": "in progress" - } - }, - { - "type": "Table.Td", - "children": [ - { - "type": "Text", - "props": { - "size": "sm", - "c": "dimmed" - }, - "children": "Thu" - } - ] - } - ] - }, - { - "type": "Table.Tr", - "children": [ - { - "type": "Table.Td", - "children": "Onboarding redesign" - }, - { - "type": "Table.Td", - "children": "M. Diaz" - }, - { - "type": "Table.Td", - "children": { - "type": "Badge", - "props": { - "color": "blue", - "variant": "light", - "size": "sm" - }, - "children": "in progress" - } - }, - { - "type": "Table.Td", - "children": [ - { - "type": "Text", - "props": { - "size": "sm", - "c": "dimmed" - }, - "children": "Fri" - } - ] - } - ] - }, - { - "type": "Table.Tr", - "children": [ - { - "type": "Table.Td", - "children": "Data export (GDPR)" - }, - { - "type": "Table.Td", - "children": "L. Chen" - }, - { - "type": "Table.Td", - "children": { - "type": "Badge", - "props": { - "color": "red", - "variant": "light", - "size": "sm" - }, - "children": "blocked" - } - }, - { - "type": "Table.Td", - "children": [ - { - "type": "Text", - "props": { - "size": "sm", - "c": "dimmed" - }, - "children": "Fri" - } - ] - } - ] - }, - { - "type": "Table.Tr", - "children": [ - { - "type": "Table.Td", - "children": "Mobile: offline cache" - }, - { - "type": "Table.Td", - "children": "—" - }, - { - "type": "Table.Td", - "children": { - "type": "Badge", - "props": { - "color": "gray", - "variant": "light", - "size": "sm" - }, - "children": "to do" - } - }, - { - "type": "Table.Td", - "children": [ - { - "type": "Text", - "props": { - "size": "sm", - "c": "dimmed" - }, - "children": "next" - } - ] - } - ] - } - ] - } - ] - }, - { - "type": "Alert", - "props": { - "color": "red", - "variant": "light", - "title": "1 blocker — Data export (GDPR)" - }, - "children": "Waiting on the legal sign-off for the retention window; owner pinged. At risk for the freeze unless resolved by Wed." - } - ] -} +{"type":"Stack","props":{"gap":"md"},"children":[{"type":"Group","props":{"justify":"space-between","align":"flex-end"},"children":[{"type":"Stack","props":{"gap":2},"children":[{"type":"Title","props":{"order":3},"children":"STR · Release 2.4 status"},{"type":"Text","props":{"c":"dimmed","size":"sm"},"children":"Sprint 12 · 3 days to code freeze · 8 of 12 tasks done"}]},{"type":"RingProgress","props":{"size":76,"thickness":8,"roundCaps":true,"sections":[{"value":67,"color":"green"}],"label":{"type":"Text","props":{"ta":"center","size":"sm","fw":700},"children":"67%"}}}]},{"type":"SimpleGrid","props":{"cols":4,"spacing":"sm"},"children":[{"type":"Card","props":{"withBorder":true,"padding":"sm","radius":"md"},"children":[{"type":"Text","props":{"size":"xs","c":"dimmed"},"children":"Done"},{"type":"Text","props":{"fw":700,"size":"xl","c":"green"},"children":"8"}]},{"type":"Card","props":{"withBorder":true,"padding":"sm","radius":"md"},"children":[{"type":"Text","props":{"size":"xs","c":"dimmed"},"children":"In progress"},{"type":"Text","props":{"fw":700,"size":"xl","c":"blue"},"children":"2"}]},{"type":"Card","props":{"withBorder":true,"padding":"sm","radius":"md"},"children":[{"type":"Text","props":{"size":"xs","c":"dimmed"},"children":"Blocked"},{"type":"Text","props":{"fw":700,"size":"xl","c":"red"},"children":"1"}]},{"type":"Card","props":{"withBorder":true,"padding":"sm","radius":"md"},"children":[{"type":"Text","props":{"size":"xs","c":"dimmed"},"children":"To do"},{"type":"Text","props":{"fw":700,"size":"xl"},"children":"1"}]}]},{"type":"Divider"},{"type":"Table","props":{"withTableBorder":true,"striped":true,"verticalSpacing":"xs","horizontalSpacing":"md"},"children":[{"type":"Table.Thead","children":[{"type":"Table.Tr","children":[{"type":"Table.Th","children":"Task"},{"type":"Table.Th","children":"Owner"},{"type":"Table.Th","children":"Status"},{"type":"Table.Th","children":"Due"}]}]},{"type":"Table.Tbody","children":[{"type":"Table.Tr","children":[{"type":"Table.Td","children":"Auth: rotate signing keys"},{"type":"Table.Td","children":"A. Rivera"},{"type":"Table.Td","children":{"type":"Badge","props":{"color":"green","variant":"light","size":"sm"},"children":"done"}},{"type":"Table.Td","children":[{"type":"Text","props":{"size":"sm","c":"dimmed"},"children":"Mon"}]}]},{"type":"Table.Tr","children":[{"type":"Table.Td","children":"Billing: proration fix"},{"type":"Table.Td","children":"S. Kim"},{"type":"Table.Td","children":{"type":"Badge","props":{"color":"green","variant":"light","size":"sm"},"children":"done"}},{"type":"Table.Td","children":[{"type":"Text","props":{"size":"sm","c":"dimmed"},"children":"Tue"}]}]},{"type":"Table.Tr","children":[{"type":"Table.Td","children":"Search: relevance v2"},{"type":"Table.Td","children":"J. Patel"},{"type":"Table.Td","children":{"type":"Badge","props":{"color":"blue","variant":"light","size":"sm"},"children":"in progress"}},{"type":"Table.Td","children":[{"type":"Text","props":{"size":"sm","c":"dimmed"},"children":"Thu"}]}]},{"type":"Table.Tr","children":[{"type":"Table.Td","children":"Onboarding redesign"},{"type":"Table.Td","children":"M. Diaz"},{"type":"Table.Td","children":{"type":"Badge","props":{"color":"blue","variant":"light","size":"sm"},"children":"in progress"}},{"type":"Table.Td","children":[{"type":"Text","props":{"size":"sm","c":"dimmed"},"children":"Fri"}]}]},{"type":"Table.Tr","children":[{"type":"Table.Td","children":"Data export (GDPR)"},{"type":"Table.Td","children":"L. Chen"},{"type":"Table.Td","children":{"type":"Badge","props":{"color":"red","variant":"light","size":"sm"},"children":"blocked"}},{"type":"Table.Td","children":[{"type":"Text","props":{"size":"sm","c":"dimmed"},"children":"Fri"}]}]},{"type":"Table.Tr","children":[{"type":"Table.Td","children":"Mobile: offline cache"},{"type":"Table.Td","children":"—"},{"type":"Table.Td","children":{"type":"Badge","props":{"color":"gray","variant":"light","size":"sm"},"children":"to do"}},{"type":"Table.Td","children":[{"type":"Text","props":{"size":"sm","c":"dimmed"},"children":"next"}]}]}]}]},{"type":"Alert","props":{"color":"red","variant":"light","title":"1 blocker — Data export (GDPR)"},"children":"Waiting on the legal sign-off for the retention window; owner pinged. At risk for the freeze unless resolved by Wed."}]} diff --git a/plugin/skills/diagram-recipes/examples/trace-waterfall.vegalite.json b/plugin/skills/diagram-recipes/examples/trace-waterfall.vegalite.json index df6b692..b1ed44c 100644 --- a/plugin/skills/diagram-recipes/examples/trace-waterfall.vegalite.json +++ b/plugin/skills/diagram-recipes/examples/trace-waterfall.vegalite.json @@ -1,155 +1 @@ -{ - "$schema": "https://vega.github.io/schema/vega-lite/v6.json", - "title": { - "text": "Distributed trace — POST /checkout", - "subtitle": "one request · 840ms total · spans by start time, colored by service", - "anchor": "start" - }, - "width": "container", - "height": 320, - "data": { - "values": [ - { - "span": "GET /checkout", - "service": "edge", - "start": 0, - "end": 840, - "dur": 840 - }, - { - "span": "auth.verify", - "service": "auth", - "start": 12, - "end": 60, - "dur": 48 - }, - { - "span": "session.load", - "service": "auth", - "start": 16, - "end": 52, - "dur": 36 - }, - { - "span": "orders.create", - "service": "orders", - "start": 70, - "end": 520, - "dur": 450 - }, - { - "span": "db.insert orders", - "service": "postgres", - "start": 90, - "end": 300, - "dur": 210 - }, - { - "span": "kafka.publish", - "service": "kafka", - "start": 305, - "end": 360, - "dur": 55 - }, - { - "span": "inventory.reserve", - "service": "orders", - "start": 365, - "end": 510, - "dur": 145 - }, - { - "span": "payments.charge", - "service": "payments", - "start": 530, - "end": 810, - "dur": 280 - }, - { - "span": "stripe.api", - "service": "stripe", - "start": 540, - "end": 790, - "dur": 250 - }, - { - "span": "receipt.render", - "service": "edge", - "start": 812, - "end": 838, - "dur": 26 - } - ] - }, - "mark": { - "type": "bar", - "cornerRadius": 2, - "height": { - "band": 0.7 - } - }, - "encoding": { - "y": { - "field": "span", - "type": "nominal", - "sort": { - "field": "start", - "order": "ascending" - }, - "title": null, - "axis": { - "labelLimit": 200 - } - }, - "x": { - "field": "start", - "type": "quantitative", - "title": "time (ms)", - "scale": { - "domain": [ - 0, - 840 - ] - }, - "axis": { - "grid": true - } - }, - "x2": { - "field": "end" - }, - "color": { - "field": "service", - "type": "nominal", - "title": "service", - "scale": { - "scheme": "category10" - }, - "legend": { - "orient": "top" - } - }, - "tooltip": [ - { - "field": "span", - "type": "nominal", - "title": "span" - }, - { - "field": "service", - "type": "nominal", - "title": "service" - }, - { - "field": "start", - "type": "quantitative", - "title": "start (ms)" - }, - { - "field": "dur", - "type": "quantitative", - "title": "duration (ms)" - } - ] - } -} +{"$schema":"https://vega.github.io/schema/vega-lite/v6.json","title":{"text":"Distributed trace — POST /checkout","subtitle":"one request · 840ms total · spans by start time, colored by service","anchor":"start"},"width":"container","height":320,"data":{"values":[{"span":"GET /checkout","service":"edge","start":0,"end":840,"dur":840},{"span":"auth.verify","service":"auth","start":12,"end":60,"dur":48},{"span":"session.load","service":"auth","start":16,"end":52,"dur":36},{"span":"orders.create","service":"orders","start":70,"end":520,"dur":450},{"span":"db.insert orders","service":"postgres","start":90,"end":300,"dur":210},{"span":"kafka.publish","service":"kafka","start":305,"end":360,"dur":55},{"span":"inventory.reserve","service":"orders","start":365,"end":510,"dur":145},{"span":"payments.charge","service":"payments","start":530,"end":810,"dur":280},{"span":"stripe.api","service":"stripe","start":540,"end":790,"dur":250},{"span":"receipt.render","service":"edge","start":812,"end":838,"dur":26}]},"mark":{"type":"bar","cornerRadius":2,"height":{"band":0.7}},"encoding":{"y":{"field":"span","type":"nominal","sort":{"field":"start","order":"ascending"},"title":null,"axis":{"labelLimit":200}},"x":{"field":"start","type":"quantitative","title":"time (ms)","scale":{"domain":[0,840]},"axis":{"grid":true}},"x2":{"field":"end"},"color":{"field":"service","type":"nominal","title":"service","scale":{"scheme":"category10"},"legend":{"orient":"top"}},"tooltip":[{"field":"span","type":"nominal","title":"span"},{"field":"service","type":"nominal","title":"service"},{"field":"start","type":"quantitative","title":"start (ms)"},{"field":"dur","type":"quantitative","title":"duration (ms)"}]}} diff --git a/plugin/skills/diagram-recipes/examples/user-journey.flow.json b/plugin/skills/diagram-recipes/examples/user-journey.flow.json index 4bc3f2f..94cfa90 100644 --- a/plugin/skills/diagram-recipes/examples/user-journey.flow.json +++ b/plugin/skills/diagram-recipes/examples/user-journey.flow.json @@ -1,131 +1 @@ -{ - "direction": "TB", - "legend": [ - { - "label": "entry", - "color": "#60a5fa" - }, - { - "label": "step", - "color": "#fbbf24" - }, - { - "label": "success", - "color": "#22c55e" - }, - { - "label": "error", - "color": "#ef4444", - "dash": true - } - ], - "nodes": [ - { - "id": "in", - "type": "change", - "data": { - "label": "Request in", - "kind": "entry", - "status": "info", - "icon": "search", - "sub": "POST /checkout" - } - }, - { - "id": "val", - "type": "change", - "data": { - "label": "Validate", - "kind": "step", - "status": "active", - "icon": "code", - "sub": "schema + auth" - } - }, - { - "id": "proc", - "type": "change", - "data": { - "label": "Process order", - "kind": "step", - "status": "active", - "icon": "bolt", - "meta": "~120ms" - } - }, - { - "id": "ok", - "type": "change", - "data": { - "label": "Confirmed", - "kind": "result", - "status": "done", - "icon": "check", - "sub": "200 · email sent" - } - }, - { - "id": "err", - "type": "change", - "data": { - "label": "Rejected", - "kind": "result", - "status": "error", - "icon": "x", - "sub": "4xx / 5xx" - } - } - ], - "edges": [ - { - "id": "j1", - "source": "in", - "target": "val", - "markerEnd": { - "type": "arrowclosed", - "color": "#60a5fa" - }, - "style": { - "stroke": "#60a5fa" - } - }, - { - "id": "j2", - "source": "val", - "target": "proc", - "markerEnd": { - "type": "arrowclosed", - "color": "#fbbf24" - }, - "style": { - "stroke": "#fbbf24" - } - }, - { - "id": "j3", - "source": "proc", - "target": "ok", - "markerEnd": { - "type": "arrowclosed", - "color": "#22c55e" - }, - "style": { - "stroke": "#22c55e", - "strokeWidth": 2 - } - }, - { - "id": "j4", - "source": "proc", - "target": "err", - "markerEnd": { - "type": "arrowclosed", - "color": "#ef4444" - }, - "style": { - "stroke": "#ef4444", - "strokeDasharray": "6 4" - } - } - ] -} +{"direction":"TB","legend":[{"label":"entry","color":"#60a5fa"},{"label":"step","color":"#fbbf24"},{"label":"success","color":"#22c55e"},{"label":"error","color":"#ef4444","dash":true}],"nodes":[{"id":"in","type":"change","data":{"label":"Request in","kind":"entry","status":"info","icon":"search","sub":"POST /checkout"}},{"id":"val","type":"change","data":{"label":"Validate","kind":"step","status":"active","icon":"code","sub":"schema + auth"}},{"id":"proc","type":"change","data":{"label":"Process order","kind":"step","status":"active","icon":"bolt","meta":"~120ms"}},{"id":"ok","type":"change","data":{"label":"Confirmed","kind":"result","status":"done","icon":"check","sub":"200 · email sent"}},{"id":"err","type":"change","data":{"label":"Rejected","kind":"result","status":"error","icon":"x","sub":"4xx / 5xx"}}],"edges":[{"id":"j1","source":"in","target":"val","markerEnd":{"type":"arrowclosed","color":"#60a5fa"},"style":{"stroke":"#60a5fa"}},{"id":"j2","source":"val","target":"proc","markerEnd":{"type":"arrowclosed","color":"#fbbf24"},"style":{"stroke":"#fbbf24"}},{"id":"j3","source":"proc","target":"ok","markerEnd":{"type":"arrowclosed","color":"#22c55e"},"style":{"stroke":"#22c55e","strokeWidth":2}},{"id":"j4","source":"proc","target":"err","markerEnd":{"type":"arrowclosed","color":"#ef4444"},"style":{"stroke":"#ef4444","strokeDasharray":"6 4"}}]} From 265a096592d0c5108f58a5123a7202ad95d41004 Mon Sep 17 00:00:00 2001 From: Ivan Cheung Date: Sun, 7 Jun 2026 18:52:15 -0400 Subject: [PATCH 6/6] feat(experiments): validate OpenCode runner + runner-agnostic success gate - entrypoint_cell.sh: OpenCode provider config (openai baseURL->proxy, --model openai/shared-model); capture runner exit code - cell_record.py: success = clean exit + >=1 model call (runner-agnostic; OpenCode emits text not Claude JSON) - README: runner status table (Claude Code + OpenCode working; AGY deferred - no custom base-URL to share the proxy/model) + matrix command Refs #142 --- scripts/experiments/README.md | 17 +++++++++++++ scripts/experiments/cell_record.py | 25 +++++++++++-------- scripts/experiments/podman/entrypoint_cell.sh | 21 ++++++++++++---- 3 files changed, 47 insertions(+), 16 deletions(-) diff --git a/scripts/experiments/README.md b/scripts/experiments/README.md index e02e24b..1e12be0 100644 --- a/scripts/experiments/README.md +++ b/scripts/experiments/README.md @@ -72,6 +72,23 @@ python3 scripts/experiments/aggregate.py 'out/**/agent.jsonl' --out out/aggregat - **Cost metrics are compared over *successful* diagrams only**; `success_rate` is tracked separately so a fix can't "win" by degrading quality (plan §5/§9 gate). +## Runner status (validated locally, Vertex Gemini 2.5 Flash) + +| Runner | Headless | Routed via proxy | Status | +|---|---|---|---| +| Claude Code | `claude -p … --permission-mode bypassPermissions` | `ANTHROPIC_BASE_URL` → LiteLLM | ✅ working (runs as non-root `node`; refuses `--bypass` as root) | +| OpenCode | `opencode run … --model openai/shared-model` | `openai` provider `baseURL` → LiteLLM | ✅ working | +| AGY (Antigravity) | `agy -p … --dangerously-skip-permissions` | — | ⛔ deferred: no custom base-URL flag, so it can't share the proxy/model or be token-measured uniformly; also needs `ANTIGRAVITY_API_KEY` | + +## Run the matrix (local podman) + +```bash +RUNNERS=claude-code,opencode CONDITIONS=baseline,c1 \ +TASKS=terminal-er-orders,component-plan-comparison,flow-auth-callgraph REPS=2 \ + scripts/experiments/podman/run_local.sh +# -> out/podman/aggregate/aggregate.md (clear out/podman/cache via `podman volume rm tc-cache` when a condition's ref changes) +``` + ## Status / what's stubbed for P1 - `agent_run.invoke_runner` (real mode) captures wall-clock + proxy usage and a coarse diff --git a/scripts/experiments/cell_record.py b/scripts/experiments/cell_record.py index 50c2eba..b095639 100755 --- a/scripts/experiments/cell_record.py +++ b/scripts/experiments/cell_record.py @@ -32,22 +32,25 @@ def main() -> int: rid = f"{cond}:{runner}:{task}:r{rep}" agg = metrics.parse_proxy_log_slice(spend, start) + runner_rc = int(_env("RUNNER_RC", "1") or 1) - # Runner self-reported outcome (Claude Code JSON: {is_error, result, modelUsage}). - result_ok = False; err = "" + # Runner self-reported outcome (Claude Code emits JSON {is_error, result}); other + # runners just print text, so this is supplementary detail, not the gate. + err = "" rj = os.path.join(out, "runner.json") - if os.path.exists(rj): + if os.path.exists(rj) and os.path.getsize(rj) > 0: try: d = json.load(open(rj, encoding="utf-8")) - result_ok = (not d.get("is_error")) and bool(d.get("result")) - except Exception as e: # malformed/empty -> failure, captured - err = f"runner output parse: {e}" - else: - err = "no runner output" + if d.get("is_error"): + err = "runner reported is_error" + except (json.JSONDecodeError, ValueError): + pass # non-JSON output (e.g. OpenCode) is expected + elif runner_rc != 0: + err = f"runner exit {runner_rc}" - # Correctness (coarse, P0): runner succeeded AND it actually hit the model. - # The rubric LLM-judge + rendered-diagram check land in P1. - success = bool(result_ok and agg["llm_calls"] > 0) + # Correctness (coarse, P0, runner-agnostic): the runner exited cleanly AND + # actually hit the model. Rubric LLM-judge + rendered-diagram check land in P1. + success = bool(runner_rc == 0 and agg["llm_calls"] > 0 and not err) rec = RunRecord( run_id=rid, runner=runner, condition=cond, task_id=task, diff --git a/scripts/experiments/podman/entrypoint_cell.sh b/scripts/experiments/podman/entrypoint_cell.sh index 6172633..aa70414 100755 --- a/scripts/experiments/podman/entrypoint_cell.sh +++ b/scripts/experiments/podman/entrypoint_cell.sh @@ -39,16 +39,27 @@ COMMON_ENV="HOME=/home/node ANTHROPIC_BASE_URL=${PROXY_URL} ANTHROPIC_API_KEY=${ ANTHROPIC_MODEL=shared-model ANTHROPIC_SMALL_FAST_MODEL=shared-model \ OPENAI_BASE_URL=${PROXY_URL}/v1 OPENAI_API_KEY=${PROXY_KEY}" case "$RUNNER" in - claude-code) RUN_CMD="claude -p \"\$TASK_PROMPT\" --permission-mode bypassPermissions --output-format json" ;; - opencode) RUN_CMD="opencode run \"\$TASK_PROMPT\" --model shared-model" ;; - *) log "runner '$RUNNER' not wired"; echo '{"is_error":true,"result":""}' >"$AW/runner.json" ; RUN_CMD="true" ;; + claude-code) + RUN_CMD="claude -p \"\$TASK_PROMPT\" --permission-mode bypassPermissions --output-format json" ;; + opencode) + # OpenCode needs an explicit OpenAI-compatible provider pointing at the proxy. + mkdir -p /home/node/.config/opencode + printf '{"$schema":"https://opencode.ai/config.json","provider":{"openai":{"options":{"baseURL":"%s/v1","apiKey":"%s"},"models":{"shared-model":{"name":"shared-model"}}}}}\n' \ + "$PROXY_URL" "$PROXY_KEY" >/home/node/.config/opencode/opencode.json + chown -R node:node /home/node/.config + RUN_CMD="opencode run \"\$TASK_PROMPT\" --model openai/shared-model" ;; + *) + log "runner '$RUNNER' not wired"; echo '{"is_error":true,"result":""}' >"$AW/runner.json"; RUN_CMD="true" ;; esac T0=$(date +%s%3N) -su node -c "cd $AW && export $COMMON_ENV TASK_PROMPT=\"$TASK_PROMPT\" && $RUN_CMD >$AW/runner.json 2>$AW/runner.err" || true +set +e +su node -c "cd $AW && export $COMMON_ENV TASK_PROMPT=\"$TASK_PROMPT\" && $RUN_CMD >$AW/runner.json 2>$AW/runner.err" +RUNNER_RC=$? +set -e T1=$(date +%s%3N) cp "$AW/runner.json" "$AW/runner.err" "$OUT/" 2>/dev/null || true cp "$AW"/*.txt "$AW"/*.mmd "$OUT/" 2>/dev/null || true # 4. emit the RunRecord (reads proxy spend-log slice for ground-truth tokens). -WALL_MS=$((T1 - T0)) python3 "$HARNESS/cell_record.py" +WALL_MS=$((T1 - T0)) RUNNER_RC=$RUNNER_RC python3 "$HARNESS/cell_record.py"