claude-dev-env 8.88.7 → 8.90.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/.agents/skills/pr-visual/README.md +29 -0
  2. package/.agents/skills/pr-visual/SKILL.md +62 -0
  3. package/.agents/skills/pr-visual/assets/icons.symbols +27 -0
  4. package/.agents/skills/pr-visual/assets/viewer.html +147 -0
  5. package/.agents/skills/pr-visual/fixtures/bar.scene.json +193 -0
  6. package/.agents/skills/pr-visual/fixtures/flow.scene.json +201 -0
  7. package/.agents/skills/pr-visual/fixtures/head/metrics.json +16 -0
  8. package/.agents/skills/pr-visual/fixtures/integrated.scene.json +239 -0
  9. package/.agents/skills/pr-visual/fixtures/line.scene.json +193 -0
  10. package/.agents/skills/pr-visual/fixtures/scatter.scene.json +193 -0
  11. package/.agents/skills/pr-visual/fixtures/snapshot.json +21 -0
  12. package/.agents/skills/pr-visual/references/evidence.md +9 -0
  13. package/.agents/skills/pr-visual/references/formats.md +30 -0
  14. package/.agents/skills/pr-visual/references/scene-contract.md +48 -0
  15. package/.agents/skills/pr-visual/references/visual-contract.md +23 -0
  16. package/.agents/skills/pr-visual/scripts/collect.py +111 -0
  17. package/.agents/skills/pr-visual/scripts/metrics.py +99 -0
  18. package/.agents/skills/pr-visual/scripts/render.py +133 -0
  19. package/.agents/skills/pr-visual/scripts/test_collect.py +58 -0
  20. package/.agents/skills/pr-visual/scripts/test_metrics.py +47 -0
  21. package/.agents/skills/pr-visual/scripts/test_render.py +77 -0
  22. package/.agents/skills/pr-visual/scripts/verify.py +218 -0
  23. package/.agents/skills/pr-visual/scripts/visual_config/config/constants.py +104 -0
  24. package/bin/codex-skill-load-block.mjs +0 -25
  25. package/bin/codex-skill-load-block.test.mjs +5 -17
  26. package/bin/codex-trimmed-sol.test.mjs +0 -1
  27. package/bin/cursor-native-hooks.test.mjs +3 -3
  28. package/bin/ever-shipped-skills.mjs +1 -0
  29. package/bin/install-constants.mjs +3 -0
  30. package/bin/install-deepstack-plugin.mjs +42 -25
  31. package/bin/install-deepstack-plugin.test.mjs +37 -11
  32. package/bin/install-pstack-plugin.mjs +58 -11
  33. package/bin/install-pstack-plugin.test.mjs +54 -21
  34. package/bin/install.agents-home.test.mjs +0 -1
  35. package/bin/install.codex-rules.test.mjs +0 -2
  36. package/bin/install.cursor-rules.test.mjs +96 -21
  37. package/bin/install.mjs +48 -44
  38. package/bin/install.pr-visual.test.mjs +40 -0
  39. package/bin/install.prompt-context.test.mjs +3 -4
  40. package/bin/install.prune.test.mjs +0 -1
  41. package/bin/install.question-presentation.test.mjs +0 -4
  42. package/bin/install.settings-defaults.test.mjs +0 -1
  43. package/bin/install.shared-settings.test.mjs +0 -1
  44. package/bin/install.test.mjs +14 -44
  45. package/bin/install.transaction.test.mjs +0 -8
  46. package/bin/install.uninstall-transaction.test.mjs +0 -2
  47. package/bin/install.update-keeps-hooks.test.mjs +0 -1
  48. package/docs/BDD_SCENARIO_QUALITY.md +1 -1
  49. package/docs/BDD_TEST_LAYOUT.md +1 -1
  50. package/docs/CODE_RULES.md +1 -1
  51. package/docs/rule-guides/deepstack-wording.md +3 -5
  52. package/docs/rule-guides/session-title.md +5 -4
  53. package/hooks/advisory/owned_pr_review_loop.py +365 -0
  54. package/hooks/advisory/test_owned_pr_review_loop.py +369 -0
  55. package/hooks/blocking/session_title_turn_gate.py +287 -0
  56. package/hooks/blocking/test_session_title_format_gate.py +5 -0
  57. package/hooks/blocking/test_session_title_turn_gate.py +264 -0
  58. package/hooks/features/bash-dispatchers.md +3 -0
  59. package/hooks/features/conduct-gates.md +3 -0
  60. package/hooks/features/session-context.md +0 -4
  61. package/hooks/hooks.json +15 -5
  62. package/hooks/hooks_constants/mod_handoff_constants.py +0 -2
  63. package/hooks/hooks_constants/owned_pr_review_loop_constants.py +120 -0
  64. package/hooks/hooks_constants/session_title_constants.py +41 -3
  65. package/hooks/lifecycle/nested_project_hooks.py +2 -2
  66. package/hooks/lifecycle/test_nested_project_hooks.py +2 -2
  67. package/hooks/session/compaction_user_messages.py +2 -2
  68. package/package.json +2 -2
  69. package/rules/deepstack-wording.md +3 -3
  70. package/scripts/ci/install-playtest.mjs +58 -314
  71. package/scripts/ci/install-playtest.test.mjs +24 -6
  72. package/scripts/policy_lint/adapter_configuration.py +1 -0
  73. package/scripts/policy_lint/config/constants.py +1 -0
  74. package/scripts/test_deepstack_review_criterion.py +2 -3
  75. package/scripts/test_deepstack_wording_rule.py +6 -4
  76. package/scripts/tests/test_adapter_configuration.py +8 -0
  77. package/.agents/skills/deepen/SKILL.md +0 -26
  78. package/commands/deepen.md +0 -14
  79. package/docs/decisions/deepstack-gate.md +0 -32
  80. package/hooks/hooks_constants/deepen_first_prompt_constants.py +0 -187
  81. package/hooks/session/deepen_default_rewrite.py +0 -128
  82. package/hooks/session/deepen_first_prompt.py +0 -383
  83. package/hooks/session/deepen_role_gate.py +0 -171
  84. package/hooks/session/deepen_turn_marker.py +0 -75
  85. package/hooks/session/fixtures/deepstack_replay.json +0 -15
  86. package/hooks/session/grade_deepstack_replay.py +0 -78
  87. package/hooks/session/test_deepen_default_rewrite.py +0 -90
  88. package/hooks/session/test_deepen_first_prompt.py +0 -452
  89. package/hooks/session/test_deepen_role_gate.py +0 -83
  90. package/hooks/session/test_deepen_turn_marker.py +0 -59
  91. package/hooks/session/test_grade_deepstack_replay.py +0 -47
@@ -0,0 +1,29 @@
1
+ # PR visual
2
+
3
+ A portable agent skill with Python tools. The agent reviews the PR summary and code, authors a source-backed scene, and returns an interactive HTML visual. Claude Code, Codex, and other agents can follow SKILL.md; no model API is embedded.
4
+
5
+ Requires Python 3.11+, authenticated GitHub CLI for collection, and Playwright plus Chromium for browser verification. Collection and rendering use the standard library. Optional browser setup: `python -m pip install playwright`, then `python -m playwright install chromium`.
6
+
7
+ ```sh
8
+ python scripts/collect.py https://github.com/OWNER/REPO/pull/NUMBER --out RUN/evidence
9
+ python scripts/render.py SCENE.json --out RUN/visual.html --links-only
10
+ python scripts/verify.py RUN/visual.html --out RUN/proof
11
+ ```
12
+
13
+ Flow diagrams, decision trees, dependency graphs, comparisons, bar charts, line charts, and scatter plots share the same source details. Numerical chart coordinates resolve JSON pointers against captured source files. The renderer does not supply an independent semantic code review.
14
+
15
+ Try the synthetic fixture gallery from this directory:
16
+
17
+ ```sh
18
+ python scripts/render.py fixtures/flow.scene.json fixtures/bar.scene.json fixtures/line.scene.json fixtures/scatter.scene.json --out /tmp/pr-visual.html
19
+ python scripts/verify.py /tmp/pr-visual.html --out /tmp/pr-visual-proof
20
+ python -m pytest scripts/test_render.py
21
+ ```
22
+
23
+ Add `--browser-path PATH` when Chromium is outside the usual locations. `--no-sandbox` is available for isolated containers that cannot run Chromium's sandbox. Private evidence and generated HTML belong outside the skill package.
24
+
25
+ The npm installer ships this skill through `.agents/skills` in full and core installs. Claude's skills directory points to the same installed agent skill tree. Release automation publishes the npm package after merge.
26
+
27
+ When Data is available, the skill can use its chart-selection and quantitative-review workflow and plan integrated visuals for distributions, heatmaps, composition, and uncertainty. These additional families require that workflow's supported renderer.
28
+
29
+ Reviews start with compact PR selectors. Charts live inside relevant flow-step cards and their expansions. Render `fixtures/integrated.scene.json` to exercise that layout. Source examples can read numeric Python literals without executing the PR.
@@ -0,0 +1,62 @@
1
+ ---
2
+ name: pr-visual
3
+ description: Read a pull request's summary and code, trace the changed behavior, and return an interactive visual. Use for "visualize this PR", "draw this change", "PR visual", "chart this PR", "graph this change", or a PR URL with a request for a visual. Accept any accessible GitHub PR.
4
+ ---
5
+
6
+ # PR visual
7
+
8
+ Turn a PR into a visual explanation of what its code changes. Return the displayed visual.
9
+
10
+ ## Input and result
11
+
12
+ Accept a PR URL. Resolve a bare PR number only when the repository is known.
13
+
14
+ Read the description, changed code, relevant callers, and tests. Build an interactive diagram with short labels, visual drilldowns, source details, and a summary-to-code comparison.
15
+
16
+ Use the current agent for interpretation. The collector and renderer run locally. The shell command alone does not review code. Complete the whole workflow when this skill is invoked.
17
+
18
+ ## Workflow
19
+
20
+ 1. **Capture the PR.** Run `python <skill-root>/scripts/collect.py <PR-URL> --out <run-dir>/evidence`. It saves the PR description, open or closed state, base and head commits, changed paths, patches, and full base/head changed-file source. Use an authorized GitHub connector if gh is unavailable. Preserve the same snapshot fields. If capture fails or a patch is incomplete, fetch the missing source before interpretation. Do not invent a complete review from a partial diff.
21
+ 2. **Trace the behavior.** Read the snapshot description and diff. Follow the code from its entry point to its output, including decisions, side effects, failure paths, and material callers. Fetch unchanged callers at the captured commit when needed. Read repository review instructions. Compare the summary with the code. Record material extra scope, contradictions, and limitations. Treat prose claims and past test reports as claims until supported by current source or observed execution.
22
+ 3. **Choose the format.** Read [the visual contract](references/visual-contract.md). Pick the structure the change needs. Use a state loop for a sampler, a decision tree for an admission gate, split lanes for two job paths, or an angle loop for capture. Keep the top dashboard to compact PR selectors. Embed quantitative charts inside the relevant flow-step cards and their expansions. Keep the review, charts, and evidence together on one Page. Use a chart when captured numerical data answers the question: bars for categories, lines for ordered observations, scatter for relationships. Read [format selection](references/formats.md). Keep the cream background, teal line icons, green progress paths, orange conditional paths, and generous spacing. Give every node a source-backed mini-diagram in a docked detail panel, related-step navigation, and collapsed exact explanation and sources. Keep the main map usable while the panel is open. Do not copy the shipping instructions into unrelated content.
23
+ 4. **Render the scene.** Write a scene JSON using [the scene contract](references/scene-contract.md). Attach immutable file-and-line evidence to every node. Render it with `python <skill-root>/scripts/render.py <scene.json> --out <run-dir>/visual.html`. For hosted display, add `--links-only` to omit full PR bodies and source excerpts while retaining the diagrams and immutable links. For a batch, pass multiple scene files to create a compact selector dashboard and flow reviews. Avoid a detached chart-demo gallery. Compare the generated diagram with the source once more. Fix misleading arrows, labels, implied guarantees, or omitted branches before continuing.
24
+ 5. **Verify and display.** Run `python <skill-root>/scripts/verify.py <run-dir>/visual.html --out <run-dir>/proof` with Playwright and Chromium available. Inspect the resulting screenshots. Exercise every mini-diagram, related-step jump, collapsed source disclosure, keyboard navigation, summary comparison, review notes, and zoom. Check the desktop side panel, mobile stacked panel, and two widgets sharing a host. Confirm the PR's latest head and state. If the head moved, recapture and revise; otherwise label the observed state and time. Display the HTML through the host's supported visualization tool. For Pages, read or create the destination and call `create_page_visualization`, then inspect its receipt and read back the embed. Return the visual or its direct Page link. A local HTML file is the fallback when no visualization host is available.
25
+
26
+ ## Evidence and review
27
+
28
+ Follow [the evidence rules](references/evidence.md) for source review, CI claims, privacy, and coverage limits.
29
+
30
+ ## Optional Data workflow
31
+
32
+ When the host provides Data, use its `visualize-data` skill for chart selection and quantitative review. Follow [format selection and Data integration](references/formats.md). The bundled renderer runs independently.
33
+
34
+ ## Tool behavior
35
+
36
+ Use one read-only snapshot per captured head. Read GitHub and write local artifacts. Do not run PR code, post comments, publish reviews, change labels, dispatch workflows, enqueue, or merge as part of this skill.
37
+
38
+ Use a new run directory for an independent invocation. Reusing an immutable snapshot is fine after checking its head and state. Stop with a concrete access error when source is unavailable.
39
+
40
+ ## Package
41
+
42
+ `scripts/collect.py` captures source through authenticated gh. `scripts/render.py` validates evidence and produces self-contained HTML. `scripts/verify.py` exercises the browser output. `assets/viewer.html` owns presentation and interaction. Synthetic fixtures ship with the tool. Private PR snapshots stay in the run directory.
43
+
44
+ ## Reply
45
+
46
+ Return the displayed visual and a short link to the saved result. Mention a material coverage gap when one remains. Keep the workflow narrative inside the artifact.
47
+
48
+ ## Layout
49
+
50
+ | Path | Purpose |
51
+ | --- | --- |
52
+ | `scripts/collect.py` | Capture immutable PR evidence through gh |
53
+ | `scripts/render.py` | Validate and render scenes |
54
+ | `scripts/metrics.py` | Resolve chart coordinates from JSON and Python literals |
55
+ | `scripts/verify.py` | Verify browser interactions and layout |
56
+ | `scripts/visual_config/` | Shared constants |
57
+ | `assets/` | Standalone viewer and icon symbol definitions |
58
+ | `references/` | Scene, visual, and format contracts |
59
+ | `fixtures/` | Synthetic flow, bar, line, and scatter examples |
60
+ | `scripts/test_render.py` | Evidence and numerical validation cases |
61
+ | `scripts/test_collect.py` | Capture consistency and stale-head cases |
62
+ | `scripts/test_metrics.py` | Embedded-chart provenance and passive literal extraction |
@@ -0,0 +1,27 @@
1
+
2
+ <symbol id="apps" viewBox="0 0 48 48"><rect x="5" y="5" width="15" height="15" rx="4"/><rect x="28" y="5" width="15" height="15" rx="4"/><rect x="5" y="28" width="15" height="15" rx="4"/><rect x="28" y="28" width="15" height="15" rx="4"/></symbol>
3
+ <symbol id="phone" viewBox="0 0 48 48"><rect x="11" y="3" width="26" height="42" rx="5"/><path d="M19 9h10M21 39h6"/><circle cx="19" cy="19" r="1"/><circle cx="29" cy="19" r="1"/><circle cx="19" cy="27" r="1"/><circle cx="29" cy="27" r="1"/></symbol>
4
+ <symbol id="code" viewBox="0 0 48 48"><path d="m15 10-13 14 13 14m18-28 13 14-13 14M29 4 19 44"/></symbol>
5
+ <symbol id="queue" viewBox="0 0 48 48"><rect x="7" y="13" width="27" height="31" rx="3"/><path d="M14 13V8h26v31h-6M21 8V3h25v30h-6M13 23h15M13 30h15M13 37h10"/></symbol>
6
+ <symbol id="wrench" viewBox="0 0 48 48"><path d="M29 4a13 13 0 0 0-15 17L3 33a7 7 0 0 0 10 10l12-12A13 13 0 0 0 43 16L31 24l-8-8z"/><circle cx="9" cy="37" r="2"/></symbol>
7
+ <symbol id="eye" viewBox="0 0 48 48"><path d="M2 24s8-13 22-13 22 13 22 13-8 13-22 13S2 24 2 24Z"/><circle cx="24" cy="24" r="6"/></symbol>
8
+ <symbol id="flask" viewBox="0 0 48 48"><path d="M17 3h14M20 3v17L6 39q-4 6 4 6h28q8 0 4-6L28 20V3M13 31h23M21 36l3 3"/></symbol>
9
+ <symbol id="list" viewBox="0 0 48 48"><rect x="8" y="4" width="33" height="40" rx="4"/><path d="m3 13 4 4 7-9m4 6h16M18 25h16M18 35h16M11 25h1M11 35h1"/></symbol>
10
+ <symbol id="branches" viewBox="0 0 48 48"><circle cx="10" cy="8" r="5"/><circle cx="38" cy="8" r="5"/><circle cx="24" cy="40" r="5"/><path d="M10 13v6q0 10 14 10v6M38 13v6q0 10-14 10"/></symbol>
11
+ <symbol id="check" viewBox="0 0 48 48"><circle cx="24" cy="24" r="21"/><path d="m11 24 9 9 18-19"/></symbol>
12
+ <symbol id="github" viewBox="0 0 48 48"><path d="M15 38c-8 2-8-4-12-4M18 45v-8c-14 0-19-17-9-23-1-4 0-9 0-9s6 0 10 5a30 30 0 0 1 11 0c4-5 10-5 10-5s2 6 0 9c10 6 5 23-8 23v8"/></symbol>
13
+ <symbol id="lock" viewBox="0 0 48 48"><rect x="7" y="20" width="34" height="25" rx="4"/><path d="M14 20V12a10 10 0 0 1 20 0v8M24 29v7"/></symbol>
14
+ <symbol id="clock" viewBox="0 0 48 48"><circle cx="24" cy="25" r="20"/><path d="M24 12v13l9 6M17 2h14"/></symbol>
15
+ <symbol id="person" viewBox="0 0 48 48"><circle cx="24" cy="11" r="8"/><path d="M7 43V33q0-12 17-12t17 12v10M16 32v11M32 32v11"/></symbol>
16
+ <symbol id="merge" viewBox="0 0 48 48"><circle cx="12" cy="7" r="5"/><circle cx="12" cy="40" r="5"/><circle cx="37" cy="7" r="5"/><path d="M12 12v23M37 12v5q0 11-25 11"/></symbol>
17
+ <symbol id="flag" viewBox="0 0 48 48"><path d="M7 46V3M7 4q10-6 20 0t16 0v23q-8 5-16 0t-20 0"/></symbol>
18
+ <symbol id="screen" viewBox="0 0 48 48"><rect x="3" y="5" width="42" height="30" rx="4"/><path d="M18 35v8M30 35v8M11 43h26m-22-22 6 6 12-14"/></symbol>
19
+ <symbol id="shield" viewBox="0 0 48 48"><path d="M24 2 4 10v14q0 16 20 23 20-7 20-23V10Z"/><path d="m12 24 8 8 17-18"/></symbol>
20
+ <symbol id="ledger" viewBox="0 0 48 48"><rect x="9" y="4" width="35" height="41" rx="4"/><path d="M17 4v41M4 12h9M4 23h9M4 35h9M24 14h13M24 24h13M24 34h9"/></symbol>
21
+ <symbol id="cpu" viewBox="0 0 48 48"><rect x="10" y="10" width="28" height="28" rx="3"/><rect x="18" y="18" width="12" height="12" rx="1"/><path d="M17 2v8M31 2v8M17 38v8M31 38v8M2 17h8M2 31h8M38 17h8M38 31h8"/></symbol>
22
+ <symbol id="pause" viewBox="0 0 48 48"><rect x="10" y="7" width="8" height="34" rx="2"/><rect x="30" y="7" width="8" height="34" rx="2"/></symbol>
23
+ <symbol id="refresh" viewBox="0 0 48 48"><path d="M42 17A19 19 0 0 0 7 11L2 18m0-12v12h12M6 31a19 19 0 0 0 35 6l5-7m0 12V30H34"/></symbol>
24
+
25
+ <marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0 0 10 5 0 10z" fill="#294954"/></marker>
26
+ <marker id="arrowGreen" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0 0 10 5 0 10z" fill="#217757"/></marker>
27
+ <marker id="arrowOrange" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0 0 10 5 0 10z" fill="#b76531"/></marker>
@@ -0,0 +1,147 @@
1
+ <!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>PR visual review</title><style>
2
+ [data-pr-visual]{font-family:Arial,Helvetica,sans-serif;color:#17353f;background:#f6f5ef;width:100%;max-width:1600px;margin:0 auto;isolation:isolate;color-scheme:light}
3
+ [data-pr-visual] *{box-sizing:border-box;color:inherit}[data-pr-visual] [hidden]{display:none!important}
4
+ [data-pr-visual] button,[data-pr-visual] a{font:inherit}[data-pr-visual] button{cursor:pointer;opacity:1;text-shadow:none}
5
+ [data-pr-visual] .toolbar{display:flex;align-items:center;gap:8px;padding:13px 22px;background:#17353f;color:#fff}
6
+ [data-pr-visual] .brand{font-size:17px;font-weight:800;margin-right:auto}
7
+ [data-pr-visual] .toolbar button{border:1px solid #6f8488;border-radius:6px;background:#2b4a54;color:#fff;padding:7px 11px;font-size:13px}
8
+ [data-pr-visual] .tabs{display:grid;grid-template-columns:repeat(5,minmax(150px,1fr));gap:10px;padding:16px 22px;background:#edf0e6;overflow:auto;border-bottom:1px solid #d5dcd2}
9
+ [data-pr-visual] .tabs button{text-align:left;white-space:nowrap;border:1px solid #c5d1c1;border-radius:8px;background:#f9faf3;color:#17353f;padding:12px 14px;min-width:0}
10
+ [data-pr-visual] .tabs button[aria-selected=true]{background:#17353f;border-color:#17353f;color:#fff}
11
+ [data-pr-visual] .tab-number{font-size:11px;letter-spacing:.8px;opacity:.7;display:block;margin-bottom:5px}
12
+ [data-pr-visual] .tab-name{font-size:15px;font-weight:700;display:block}
13
+ [data-pr-visual] .workspace{display:grid;grid-template-columns:minmax(0,1fr);height:clamp(640px,78vw,1040px);overflow:hidden}
14
+ [data-pr-visual] .workspace.open{grid-template-columns:minmax(0,1fr) min(440px,43%)}
15
+ [data-pr-visual] .stage{overflow:auto;min-width:0;min-height:0;overscroll-behavior:contain;background:#f6f5ef}
16
+ [data-pr-visual] .canvas{min-width:1040px;max-width:1540px;margin:auto}
17
+ [data-pr-visual] svg{width:100%;display:block}[data-pr-visual] svg text{font-family:Arial,Helvetica,sans-serif;fill:#17353f}
18
+ [data-pr-visual] .title{font-size:36px;font-weight:900;letter-spacing:-1px}[data-pr-visual] .subtitle{font-size:17px;fill:#5e7171}
19
+ [data-pr-visual] .kicker{font-size:12px;font-weight:800;letter-spacing:1.5px}[data-pr-visual] .micro{font-size:13px;fill:#64746e}
20
+ [data-pr-visual] .wire{fill:none;stroke:#294954;stroke-width:3;stroke-linecap:round;stroke-linejoin:round}[data-pr-visual] .wire.green{stroke:#217757;stroke-width:3.5}[data-pr-visual] .wire.orange{stroke:#b76531}[data-pr-visual] .wire.dotted{stroke:#94a298;stroke-dasharray:5 8;stroke-width:2}
21
+ [data-pr-visual] .disc{fill:#fffef8;stroke:#b9c8bd;stroke-width:2}[data-pr-visual] .disc.blue{fill:#d9e9ee;stroke:#8faeb9}[data-pr-visual] .disc.green{fill:#e4f0df;stroke:#9db996}[data-pr-visual] .disc.lime{fill:#d8f687;stroke:#a4c357}[data-pr-visual] .disc.orange,[data-pr-visual] .diamond{fill:#fae5cf;stroke:#ca955e;stroke-width:2}
22
+ [data-pr-visual] .icon{fill:none;stroke:#17353f;stroke-width:2.8;stroke-linecap:round;stroke-linejoin:round}
23
+ [data-pr-visual] .node{cursor:pointer;outline:none;pointer-events:bounding-box}[data-pr-visual] .node:hover .disc,[data-pr-visual] .node:focus .disc,[data-pr-visual] .node[aria-expanded=true] .disc,[data-pr-visual] .node:hover .diamond{stroke:#17353f;stroke-width:3}
24
+ [data-pr-visual] .node-label{font-size:18px;font-weight:750}[data-pr-visual] .edge-label{font-size:12px;letter-spacing:1px;font-weight:800;fill:#217757}[data-pr-visual] .edge-label.orange{fill:#a45d2c}
25
+ [data-pr-visual] .group{fill:#edf0e5;stroke:#b7c7b2;stroke-width:1.5}[data-pr-visual] .panel-label{font-size:12px;font-weight:800;letter-spacing:1.4px;fill:#64746e}
26
+ [data-pr-visual] .step-card{fill:#fffef8;stroke:#aabdac;stroke-width:1.8}[data-pr-visual] .node:hover .step-card,[data-pr-visual] .node:focus .step-card,[data-pr-visual] .node[aria-expanded=true] .step-card{stroke:#217757;stroke-width:3}
27
+ [data-pr-visual] .step-title{font-size:16px;font-weight:750}[data-pr-visual] .step-value{font-size:14px;font-weight:700}[data-pr-visual] .step-caption{font-size:11px;fill:#64746e}[data-pr-visual] .step-category{font-size:12px;fill:#52695f}
28
+ [data-pr-visual] .foot-label{font-size:12px;font-weight:800;letter-spacing:1.4px}[data-pr-visual] .foot-text{font-size:16px}[data-pr-visual] .stamp{font-size:12px}
29
+ [data-pr-visual] .drawer{padding:22px;background:#fffef7;border-left:1px solid #c8d1c4;display:flex;flex-direction:column;min-height:0;min-width:0;overflow:hidden}
30
+ [data-pr-visual] .drawer header{display:flex;align-items:flex-start;gap:10px;flex:none}[data-pr-visual] .drawer h2{font-size:24px;line-height:1.25;margin:8px 0 18px;overflow-wrap:anywhere}
31
+ [data-pr-visual] .drawer .close{margin-left:auto;flex:none;border:1px solid #879b8e;border-radius:6px;background:#fff;color:#17353f;padding:7px 10px;font-size:13px}
32
+ [data-pr-visual] .drawer .close:hover{background:#e4f0df;color:#17353f}
33
+ [data-pr-visual] .content{overflow:auto;min-height:0;overscroll-behavior:contain;padding-right:6px}[data-pr-visual] .drawer h3{font-size:14px;line-height:1.4;color:#496357;margin:16px 0 10px}[data-pr-visual] .drawer p{font-size:14px;line-height:1.6;overflow-wrap:anywhere}
34
+ [data-pr-visual] .drawer pre{font:12px/1.6 monospace;white-space:pre-wrap;overflow-wrap:anywhere;background:#edf0e5;padding:12px;border-radius:6px}[data-pr-visual] .drawer a{display:block;color:#167550;overflow-wrap:anywhere;font-size:13px;margin:10px 0}
35
+ [data-pr-visual] .embedded-chart{margin:0 0 22px;padding:0 0 18px;border-bottom:1px solid #d5dcd2}[data-pr-visual] .embedded-chart h3{font-size:17px;margin:0 0 10px;color:#17353f}[data-pr-visual] .embedded-chart svg text{font-size:14px}[data-pr-visual] .chart-basis{display:block;font-size:10px;letter-spacing:1px;font-weight:800;color:#7c5b32;margin-top:8px}[data-pr-visual] .embedded-chart figcaption{font-size:12px;line-height:1.6;color:#64746e;margin-top:6px}
36
+ [data-pr-visual] .detail-map .mini-label{font-size:12px;font-weight:700}[data-pr-visual] .detail-map .mini-edge{font-size:10px;fill:#64746e}
37
+ [data-pr-visual] .detail-jumps{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:7px;margin-bottom:18px}[data-pr-visual] .detail-jumps button{display:flex;flex-direction:column;align-items:center;gap:7px;background:#f2f5ea;border:1px solid #879b8e;border-radius:6px;padding:9px 4px;color:#17353f;font-size:11px;line-height:1.3}[data-pr-visual] .detail-jumps button span{color:#17353f}[data-pr-visual] .detail-jumps svg{width:24px;height:24px}
38
+ [data-pr-visual] .exact-source{border-top:1px solid #d5dcd2;padding-top:14px;margin:18px 0}[data-pr-visual] .exact-source summary{font-size:13px;font-weight:700;color:#52695f;cursor:pointer}
39
+ [data-pr-visual] .sr-only{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0,0,0,0)}
40
+ [data-pr-visual] button:focus-visible,[data-pr-visual] summary:focus-visible,[data-pr-visual] a:focus-visible{outline:3px solid #17353f;outline-offset:3px}
41
+ [data-pr-visual] .toolbar button:focus-visible{outline-color:#d8f687}
42
+ @media(min-width:1100px){[data-pr-visual] .workspace.open .canvas{min-width:0}}
43
+ @media(max-width:700px){[data-pr-visual] .toolbar{padding:12px;flex-wrap:wrap}[data-pr-visual] .brand{flex-basis:100%;margin-bottom:4px}[data-pr-visual] .tabs{padding:12px;grid-template-columns:repeat(5,155px);gap:8px}[data-pr-visual] .tabs button{padding:10px 12px}[data-pr-visual] .workspace.open{grid-template-columns:minmax(0,1fr);grid-template-rows:430px minmax(0,1fr)}[data-pr-visual] .drawer{grid-row:1;border-left:0;border-bottom:1px solid #c8d1c4;padding:16px}[data-pr-visual] .stage{grid-row:2}[data-pr-visual] .workspace:not(.open) .stage{grid-row:1}}
44
+ @media print{[data-pr-visual] .toolbar,[data-pr-visual] .tabs,[data-pr-visual] .drawer{display:none!important}[data-pr-visual] .workspace{display:block;height:auto;overflow:visible}[data-pr-visual] .stage{overflow:visible}[data-pr-visual] .canvas{min-width:0;max-width:none}}
45
+ </style></head><body><main data-pr-visual>
46
+ <div class="toolbar"><span class="brand">PR visual review</span><button data-minus aria-label="Zoom out">&#8722;</button><button data-plus aria-label="Zoom in">+</button><button data-fit>Fit</button><button data-compare>Summary vs code</button><button data-review>Review notes</button></div>
47
+ <nav class="tabs" role="tablist" aria-label="Pull request diagrams"></nav>
48
+ <div class="workspace"><div class="stage"><div class="canvas" role="tabpanel"></div></div><aside class="drawer" aria-label="Diagram details" hidden><header><h2 data-detail-title></h2><button class="close" aria-label="Close details">Close</button></header><div class="content"></div></aside></div>
49
+ <script type="application/json" data-scenes>__SCENES__</script><script>
50
+ (()=>{
51
+ const root=document.currentScript.closest('[data-pr-visual]'),$=s=>root.querySelector(s),all=s=>[...root.querySelectorAll(s)],ns='http://www.w3.org/2000/svg';
52
+ const scenes=JSON.parse($('[data-scenes]').textContent),icons=`__ICONS__`,prefix='prv-'+Math.random().toString(36).slice(2)+'-';
53
+ const canvas=$('.canvas'),tabs=$('.tabs'),drawer=$('.drawer'),workspace=$('.workspace'),content=$('.content');let active=0,zoom=1,lastFocus;
54
+ canvas.id=prefix+'diagram';drawer.id=prefix+'details';
55
+ function el(tag,attrs={},text){const n=document.createElementNS(ns,tag);Object.entries(attrs).forEach(([k,v])=>{if(k==='href'&&v.startsWith('#'))v='#'+prefix+v.slice(1);if(k==='marker-end')v=v.replace('#','#'+prefix);n.setAttribute(k,String(v))});if(text!==undefined)n.textContent=text;return n}
56
+ function dom(tag,text,cls){const n=document.createElement(tag);if(text!==undefined)n.textContent=text;if(cls)n.className=cls;return n}
57
+ function text(svg,x,y,value,cls,anchor='start'){svg.append(el('text',{x,y,class:cls,'text-anchor':anchor},value))}
58
+ function lines(svg,x,y,values,cls,anchor='start',gap=23){(Array.isArray(values)?values:[values]).forEach((v,i)=>text(svg,x,y+i*gap,v,cls,anchor))}
59
+ function mini(flow){const rows=Math.ceil(flow.steps.length/3),height=rows*125,svg=el('svg',{viewBox:`0 0 350 ${height}`,class:'detail-map',role:'img','aria-label':flow.title});
60
+ const positions=flow.steps.map((_,i)=>{const row=Math.floor(i/3),col=row%2?2-i%3:i%3;return {x:55+120*col,y:38+125*row}});
61
+ const edges=flow.edges||flow.steps.slice(1).map((_,i)=>({from:i,to:i+1}));
62
+ edges.forEach(e=>{const a=positions[e.from],b=positions[e.to],dx=b.x-a.x,dy=b.y-a.y,length=Math.hypot(dx,dy),ux=dx/length,uy=dy/length,kind=e.kind||'green';svg.append(el('path',{d:`M${a.x+ux*25} ${a.y+uy*25}L${b.x-ux*28} ${b.y-uy*28}`,class:`wire ${kind}`,'stroke-width':2,'marker-end':`url(#${kind==='orange'?'arrowOrange':'arrowGreen'})`}));if(e.label)text(svg,(a.x+b.x)/2+(!dx?32:0),(a.y+b.y)/2-8,e.label,'mini-edge','middle')});
63
+ flow.steps.forEach((step,i)=>{const {x,y}=positions[i];svg.append(el('circle',{cx:x,cy:y,r:24,class:`disc ${step.kind||'green'}`}));svg.append(el('use',{href:'#'+step.icon,x:x-15,y:y-15,width:30,height:30,class:'icon'}));lines(svg,x,y+43,step.label,'mini-label','middle',15)});return svg}
64
+ function embeddedChart(chart){
65
+ const figure=dom('figure',undefined,'embedded-chart');figure.dataset.embeddedChart=chart.kind;
66
+ const svg=el('svg',{viewBox:'0 0 420 245',role:'img','aria-label':`${chart.title}. ${chart.caption}`}),left=42,right=398,top=36,bottom=181;
67
+ const points=chart.points,ys=points.map(p=>p.values.y),low=Math.min(0,...ys),high=Math.max(0,...ys),span=high-low||1,y=v=>bottom-(v-low)/span*(bottom-top);
68
+ const xs=points.map(p=>p.values.x),xmin=Math.min(...xs),xspan=Math.max(...xs)-xmin||1,isBar=chart.kind==='bar';
69
+ const x=(v,i)=>isBar?left+(i+.5)*(right-left)/points.length:left+(v-xmin)/xspan*(right-left);
70
+ for(let i=0;i<=4;i++){const v=low+span*i/4,yy=y(v);svg.append(el('line',{x1:left,y1:yy,x2:right,y2:yy,stroke:'#d9dfd2'}));text(svg,left-10,yy+4,Number(v.toPrecision(3)).toString(),'micro','end')}
71
+ svg.append(el('line',{x1:left,y1:y(0),x2:right,y2:y(0),stroke:'#64746e'}));
72
+ if(chart.kind==='line'){const sorted=points.map((p,i)=>({x:x(p.values.x,i),y:y(p.values.y)})).sort((a,b)=>a.x-b.x);svg.append(el('path',{d:sorted.map((p,i)=>`${i?'L':'M'}${p.x} ${p.y}`).join(' '),class:'wire green'}))}
73
+ points.forEach((p,i)=>{const xx=x(p.values.x,i),yy=y(p.values.y),width=Math.min(74,(right-left)/points.length*.55);svg.append(isBar?el('rect',{x:xx-width/2,y:Math.min(yy,y(0)),width,height:Math.max(2,Math.abs(yy-y(0))),rx:4,fill:'#9abd9b'}):el('circle',{cx:xx,cy:yy,r:5,fill:'#217757'}));
74
+ text(svg,xx,yy-9,String(p.values.y),'micro','middle');text(svg,xx,205,isBar?p.label:String(p.values.x),'micro','middle')});
75
+ text(svg,4,18,chart.y_label,'micro');text(svg,(left+right)/2,237,chart.x_label,'micro','middle');figure.append(svg);
76
+ figure.append(dom('span',chart.basis==='source-example'?'SOURCE EXAMPLE':'CAPTURED DATA','chart-basis'),dom('figcaption',chart.caption));return figure
77
+ }
78
+ function stepCard(group,node){
79
+ const {x,y}=node,chart=node.chart,left=x-78,right=x+78,top=y-8,bottom=y+69,points=chart.points;
80
+ group.append(el('rect',{x:x-106,y:y-80,width:212,height:201,rx:16,class:'step-card'}));
81
+ group.append(el('use',{href:'#'+node.icon,x:x-87,y:y-63,width:27,height:27,class:'icon'}));
82
+ text(group,x-47,y-43,node.label.join(' '),'step-title');
83
+ const ys=points.map(p=>p.values.y),low=Math.min(0,...ys),span=Math.max(0,...ys)-low||1,yy=v=>bottom-(v-low)/span*(bottom-top);
84
+ const xs=points.map(p=>p.values.x),xmin=Math.min(...xs),xspan=Math.max(...xs)-xmin||1;
85
+ const xx=(v,i)=>chart.kind==='bar'?left+(i+.5)*(right-left)/points.length:left+(v-xmin)/xspan*(right-left);
86
+ group.append(el('line',{x1:left,y1:yy(0),x2:right,y2:yy(0),stroke:'#ccd5c7'}));
87
+ const marks=el('g',{'data-step-chart':chart.kind});
88
+ if(chart.kind==='line'){marks.append(el('path',{d:[...points].sort((a,b)=>a.values.x-b.values.x).map((p,i)=>`${i?'L':'M'}${xx(p.values.x,i)} ${yy(p.values.y)}`).join(' '),class:'wire green'}))}
89
+ points.forEach((p,i)=>{const px=xx(p.values.x,i),py=yy(p.values.y),width=Math.min(30,(right-left)/points.length*.55);marks.append(chart.kind==='bar'?el('rect',{x:px-width/2,y:Math.min(py,yy(0)),width,height:Math.max(2,Math.abs(yy(0)-py)),rx:3,fill:'#91b78e'}):el('circle',{cx:px,cy:py,r:4,fill:'#217757'}));if(chart.kind==='bar'){text(marks,px,py-7,String(p.values.y),'step-value','middle');text(marks,px,bottom+18,p.label,'step-category','middle')}});
90
+ group.append(marks);text(group,x,y+108,(chart.unit||'Chart')+' \u00b7 '+(chart.basis==='source-example'?'source example':'captured data'),'step-caption','middle')
91
+ }
92
+ function cardArrow(svg,path,node,kind){
93
+ const length=path.getTotalLength();let position=length,point=path.getPointAtLength(position);
94
+ while(position>0&&point.x>=node.x-109&&point.x<=node.x+109&&point.y>=node.y-83&&point.y<=node.y+124){position-=2;point=path.getPointAtLength(Math.max(0,position))}
95
+ const before=path.getPointAtLength(Math.max(0,position-12));svg.append(el('path',{d:`M${before.x} ${before.y}L${point.x} ${point.y}`,class:`wire ${kind}`,'marker-end':`url(#${kind==='green'?'arrowGreen':kind==='orange'?'arrowOrange':'arrow'})`}))
96
+ }
97
+ function resetSelection(){all('[data-node]').forEach(n=>{n.setAttribute('aria-expanded','false');n.setAttribute('aria-controls',drawer.id)})}
98
+ function close(restore=true){drawer.hidden=true;workspace.classList.remove('open');resetSelection();if(restore&&lastFocus?.isConnected)lastFocus.focus({preventScroll:true})}
99
+ function showPanel(title,flow,paragraphs,sources=[],raw,related=[],trigger,chart){lastFocus=trigger||root.ownerDocument.activeElement;content.replaceChildren();$('[data-detail-title]').textContent=title;
100
+ if(chart){const figure=embeddedChart(chart);figure.prepend(dom('h3',chart.title));content.append(figure)}
101
+ content.append(dom('h3',flow.title),mini(flow));
102
+ if(related.length){content.append(dom('h3','Related steps'));const jumps=dom('div',undefined,'detail-jumps');related.forEach(n=>{const b=dom('button');b.dataset.detailTarget=n.id;const icon=el('svg',{viewBox:'0 0 48 48','aria-hidden':true});icon.append(el('use',{href:'#'+n.icon,width:48,height:48,class:'icon'}));b.append(icon,dom('span',n.label.join(' ')));b.onclick=()=>showNode(n.id);jumps.append(b)});content.append(jumps)}
103
+ const exact=dom('details',undefined,'exact-source');exact.append(dom('summary','Explanation and sources'));paragraphs.forEach(v=>exact.append(dom('p',v)));
104
+ sources.forEach(s=>{const a=dom('a',`${s.path} \u00b7 lines ${s.line}\u2013${s.end||s.line}`);a.href=s.url;a.target='_blank';a.rel='noopener noreferrer';exact.append(a);if(s.excerpt)exact.append(dom('pre',s.excerpt))});
105
+ if(raw)exact.append(dom('h3','PR description at capture'),dom('pre',raw));content.append(exact);content.scrollTop=0;drawer.hidden=false;workspace.classList.add('open');$('.close').focus({preventScroll:true})}
106
+ function showNode(id){const s=scenes[active],n=s.nodes.find(n=>n.id===id);resetSelection();const trigger=all('[data-node]').find(g=>g.dataset.node===id);trigger.setAttribute('aria-expanded','true');const neighbors=new Set(s.edges.filter(e=>e.from===id||e.to===id).flatMap(e=>[e.from,e.to]));neighbors.delete(id);showPanel(n.label.join(' '),n.drilldown,n.detail,n.sources,undefined,s.nodes.filter(n=>neighbors.has(n.id)),trigger,n.chart)}
107
+ $('.close').onclick=()=>close();root.addEventListener('keydown',e=>{if(e.key==='Escape'&&!drawer.hidden){e.preventDefault();close()}});
108
+ function chartNode(svg,n,x,y,bar){
109
+ const g=el('g',{class:'node','data-node':n.id,tabindex:0,role:'button','aria-expanded':'false','aria-controls':drawer.id,'aria-label':`${n.label.join(' ')}: ${n.values.y}`});
110
+ g.append(el('title',{},`${n.label.join(' ')}: ${n.values.y}. Open source evidence.`));
111
+ g.append(bar||el('circle',{cx:x,cy:y,r:10,class:'disc green'}));
112
+ text(g,x,y-18,String(n.values.y),'node-label','middle');
113
+ if(bar)lines(g,x,768,n.label,'micro','middle',17);
114
+ const show=()=>showNode(n.id);g.onclick=show;g.onkeydown=e=>{if(e.key==='Enter'||e.key===' '){e.preventDefault();show()}};svg.append(g)
115
+ }
116
+ function drawChart(svg,s){
117
+ const chart=s.chart,isBar=chart.kind==='bar',left=160,right=1300,top=255,bottom=720;
118
+ const ys=s.nodes.map(n=>n.values.y),low=Math.min(0,...ys),high=Math.max(0,...ys),span=high-low||1;
119
+ const y=v=>bottom-(v-low)/span*(bottom-top),xs=s.nodes.map((n,i)=>isBar?i:n.values.x),xmin=Math.min(...xs),xspan=Math.max(...xs)-xmin||1;
120
+ const x=(v,i)=>isBar?left+(i+.5)*(right-left)/s.nodes.length:left+(v-xmin)/xspan*(right-left);
121
+ for(let i=0;i<=5;i++){const v=low+span*i/5,yy=y(v);svg.append(el('line',{x1:left,y1:yy,x2:right,y2:yy,stroke:'#d5dcd2'}));text(svg,left-18,yy+5,Number(v.toPrecision(4)).toString(),'micro','end')}
122
+ text(svg,left,214,chart.y_label,'kicker');text(svg,(left+right)/2,818,chart.x_label,'kicker','middle');
123
+ svg.append(el('line',{x1:left,y1:y(0),x2:right,y2:y(0),stroke:'#17353f','stroke-width':2}));
124
+ if(!isBar){for(let i=0;i<=5;i++){const v=xmin+xspan*i/5;text(svg,x(v),750,Number(v.toPrecision(4)).toString(),'micro','middle')}}
125
+ const points=s.nodes.map((n,i)=>({n,x:x(xs[i],i),y:y(n.values.y)}));
126
+ if(chart.kind==='line'){const sorted=[...points].sort((a,b)=>a.n.values.x-b.n.values.x);svg.append(el('path',{d:sorted.map((p,i)=>`${i?'L':'M'}${p.x} ${p.y}`).join(' '),class:'wire green'}))}
127
+ points.forEach(p=>{const width=Math.min(150,(right-left)/points.length*.6),bar=isBar?el('rect',{x:p.x-width/2,y:Math.min(p.y,y(0)),width,height:Math.max(2,Math.abs(y(0)-p.y)),rx:5,class:'disc green'}):undefined;chartNode(svg,p.n,p.x,p.y,bar)})
128
+ }
129
+ function draw(index){close(false);active=index;const s=scenes[index];[...tabs.children].forEach((b,i)=>{b.setAttribute('aria-selected',i===index);b.tabIndex=i===index?0:-1});canvas.replaceChildren();const svg=el('svg',{viewBox:'0 0 1440 1020',role:'img','aria-label':`${s.title}. ${s.subtitle}`});const desc=el('desc',{},s.alt);svg.append(desc);const defs=el('defs');defs.innerHTML=icons.replace(/id="([^"]+)"/g,(_,id)=>`id="${prefix}${id}"`);svg.append(defs,el('rect',{width:1440,height:1020,fill:'#f6f5ef'}));text(svg,40,54,s.title,'title');text(svg,42,86,s.subtitle,'subtitle');text(svg,1398,49,`PR #${s.pr.number} \u00b7 ${s.pr.merged?'MERGED':s.pr.state==='closed'?'CLOSED':s.pr.draft?'DRAFT':'OPEN'}`,'kicker','end');
130
+ text(svg,42,128,s.before,'micro');text(svg,42,155,s.after,'micro');text(svg,1398,128,`${s.pr.changed_files} CHANGED FILES`,'kicker','end');
131
+ if(s.chart){drawChart(svg,s)}else{
132
+ (s.groups||[]).forEach(g=>{svg.append(el('rect',{x:g.x,y:g.y,width:g.w,height:g.h,rx:20,class:'group'}));text(svg,g.x+g.w/2,g.y+27,g.label,'panel-label','middle')});
133
+ const chartArrows=[];s.edges.forEach(e=>{const cls=e.kind||'',target=s.nodes.find(n=>n.id===e.to),path=el('path',{d:e.path,class:`wire ${cls}`});if(target.chart)chartArrows.push({path,target,cls});else path.setAttribute('marker-end',`url(#${prefix}${cls==='green'?'arrowGreen':cls==='orange'?'arrowOrange':'arrow'})`);svg.append(path);if(e.label)text(svg,e.x,e.y,e.label,`edge-label ${cls}`,'middle')});
134
+ s.nodes.forEach(n=>{const g=el('g',{class:'node','data-node':n.id,tabindex:0,role:'button','aria-expanded':'false','aria-controls':drawer.id,'aria-label':n.label.join(' ')});g.append(el('title',{},'Open code evidence'));if(n.chart){stepCard(g,n)}else if(n.kind==='decision'){g.append(el('path',{d:`M${n.x} ${n.y-54} L${n.x+62} ${n.y} L${n.x} ${n.y+54} L${n.x-62} ${n.y} Z`,class:'diamond'}));lines(g,n.x,n.y-3,n.label,'node-label','middle',21)}else{g.append(el('circle',{cx:n.x,cy:n.y,r:n.r||43,class:`disc ${n.kind||''}`}));g.append(el('use',{href:`#${n.icon}`,x:n.x-22,y:n.y-22,width:44,height:44,class:'icon'}));lines(g,n.x,n.y+70,n.label,'node-label','middle')}if(n.sub&&!n.chart)lines(g,n.x,n.y+(n.label.length>1?121:99),n.sub,'micro','middle',19);const show=()=>showNode(n.id);g.onclick=show;g.onkeydown=e=>{if(e.key==='Enter'||e.key===' '){e.preventDefault();show()}};svg.append(g)});
135
+ chartArrows.forEach(({path,target,cls})=>cardArrow(svg,path,target,cls));
136
+ }
137
+ svg.append(el('line',{x1:40,y1:835,x2:1400,y2:835,stroke:'#d5dcd2','stroke-width':2}));s.notes.forEach((n,i)=>{const x=42+i*455;text(svg,x,876,n.label,'foot-label');lines(svg,x,907,n.text,'foot-text','start',23)});
138
+ const when=new Date(s.pr.observed_at).toLocaleString('en-US',{timeZone:'America/New_York',month:'short',day:'numeric',hour:'numeric',minute:'2-digit'});text(svg,42,991,`Code reviewed \u00b7 ${when} ET \u00b7 ${s.pr.head.slice(0,10)}`,'stamp');text(svg,1398,991,'Click any symbol for code and source links.','micro','end');canvas.append(svg);const a=document.createElement('a');a.className='sr-only';a.href=s.pr.url;a.textContent=`Open PR ${s.pr.number}`;canvas.append(a);setZoom(1,true)}
139
+
140
+ scenes.forEach((s,i)=>{const b=dom('button');b.dataset.reviewCard=String(i);b.append(dom('span',`PR #${s.pr.number}`,'tab-number'),dom('span',s.tab,'tab-name'));b.id=prefix+`tab-${i}`;b.role='tab';b.setAttribute('aria-controls',canvas.id);b.onclick=()=>draw(i);b.onkeydown=e=>{if(e.key==='ArrowRight'||e.key==='ArrowLeft'){e.preventDefault();const next=(i+(e.key==='ArrowRight'?1:scenes.length-1))%scenes.length;draw(next);tabs.children[next].focus()}};tabs.append(b)});
141
+ function setZoom(value,fit=false){zoom=Math.max(.7,Math.min(2,value));canvas.style.width=fit?'':`${1440*zoom}px`;canvas.style.maxWidth=fit?'1540px':'none'}
142
+ $('[data-plus]').onclick=()=>setZoom(zoom+.2);$('[data-minus]').onclick=()=>setZoom(zoom-.2);$('[data-fit]').onclick=()=>setZoom(1,true);
143
+ $('[data-compare]').onclick=e=>showPanel('Summary vs code',{title:'Compare the claim with the implementation',steps:[{label:['PR','description'],icon:'list'},{label:['Trace the','code'],icon:'code'},{label:['Compare','behavior'],icon:'eye'}]},scenes[active].comparison,[],scenes[active].summary,scenes[active].nodes,e.currentTarget);
144
+ $('[data-review]').onclick=e=>showPanel('Review notes',{title:'Scope and evidence',steps:[{label:['Captured','source'],icon:'ledger'},{label:['Reviewed','path'],icon:'branches'},{label:['Scope and','limitations'],icon:'shield'}]},scenes[active].review,[],undefined,scenes[active].nodes,e.currentTarget);
145
+ draw(0);
146
+ })();
147
+ </script></main></body></html>
@@ -0,0 +1,193 @@
1
+ {
2
+ "snapshot": "snapshot.json",
3
+ "tab": "Bar",
4
+ "title": "Bar chart with source evidence",
5
+ "subtitle": "Synthetic fixture: source-backed views of the same data.",
6
+ "before": "BEFORE: Measurements stored as JSON.",
7
+ "after": "AFTER: Inspect each sample and its source.",
8
+ "alt": "Three synthetic samples; click a sample to inspect its captured JSON.",
9
+ "nodes": [
10
+ {
11
+ "id": "sample-0",
12
+ "x": 300,
13
+ "y": 430,
14
+ "label": [
15
+ "Sample 1"
16
+ ],
17
+ "icon": "ledger",
18
+ "kind": "green",
19
+ "detail": [
20
+ "Synthetic fixture data, not a performance claim."
21
+ ],
22
+ "sources": [
23
+ {
24
+ "path": "metrics.json",
25
+ "line": 4,
26
+ "end": 5
27
+ }
28
+ ],
29
+ "drilldown": {
30
+ "title": "Inspect the recorded measurement",
31
+ "steps": [
32
+ {
33
+ "label": [
34
+ "Captured",
35
+ "JSON"
36
+ ],
37
+ "icon": "code"
38
+ },
39
+ {
40
+ "label": [
41
+ "Measured",
42
+ "duration"
43
+ ],
44
+ "icon": "ledger"
45
+ }
46
+ ]
47
+ },
48
+ "metrics": {
49
+ "x": {
50
+ "source": 0,
51
+ "pointer": "/samples/0/size"
52
+ },
53
+ "y": {
54
+ "source": 0,
55
+ "pointer": "/samples/0/duration_ms"
56
+ }
57
+ }
58
+ },
59
+ {
60
+ "id": "sample-1",
61
+ "x": 700,
62
+ "y": 430,
63
+ "label": [
64
+ "Sample 2"
65
+ ],
66
+ "icon": "ledger",
67
+ "kind": "green",
68
+ "detail": [
69
+ "Synthetic fixture data, not a performance claim."
70
+ ],
71
+ "sources": [
72
+ {
73
+ "path": "metrics.json",
74
+ "line": 8,
75
+ "end": 9
76
+ }
77
+ ],
78
+ "drilldown": {
79
+ "title": "Inspect the recorded measurement",
80
+ "steps": [
81
+ {
82
+ "label": [
83
+ "Captured",
84
+ "JSON"
85
+ ],
86
+ "icon": "code"
87
+ },
88
+ {
89
+ "label": [
90
+ "Measured",
91
+ "duration"
92
+ ],
93
+ "icon": "ledger"
94
+ }
95
+ ]
96
+ },
97
+ "metrics": {
98
+ "x": {
99
+ "source": 0,
100
+ "pointer": "/samples/1/size"
101
+ },
102
+ "y": {
103
+ "source": 0,
104
+ "pointer": "/samples/1/duration_ms"
105
+ }
106
+ }
107
+ },
108
+ {
109
+ "id": "sample-2",
110
+ "x": 1100,
111
+ "y": 430,
112
+ "label": [
113
+ "Sample 3"
114
+ ],
115
+ "icon": "ledger",
116
+ "kind": "green",
117
+ "detail": [
118
+ "Synthetic fixture data, not a performance claim."
119
+ ],
120
+ "sources": [
121
+ {
122
+ "path": "metrics.json",
123
+ "line": 12,
124
+ "end": 13
125
+ }
126
+ ],
127
+ "drilldown": {
128
+ "title": "Inspect the recorded measurement",
129
+ "steps": [
130
+ {
131
+ "label": [
132
+ "Captured",
133
+ "JSON"
134
+ ],
135
+ "icon": "code"
136
+ },
137
+ {
138
+ "label": [
139
+ "Measured",
140
+ "duration"
141
+ ],
142
+ "icon": "ledger"
143
+ }
144
+ ]
145
+ },
146
+ "metrics": {
147
+ "x": {
148
+ "source": 0,
149
+ "pointer": "/samples/2/size"
150
+ },
151
+ "y": {
152
+ "source": 0,
153
+ "pointer": "/samples/2/duration_ms"
154
+ }
155
+ }
156
+ }
157
+ ],
158
+ "edges": [],
159
+ "notes": [
160
+ {
161
+ "label": "SOURCE",
162
+ "text": [
163
+ "Captured JSON",
164
+ "Immutable source links"
165
+ ]
166
+ },
167
+ {
168
+ "label": "INTERACTION",
169
+ "text": [
170
+ "Select a sample",
171
+ "Inspect its source"
172
+ ]
173
+ },
174
+ {
175
+ "label": "SCOPE",
176
+ "text": [
177
+ "Synthetic data only",
178
+ "No benchmark claim"
179
+ ]
180
+ }
181
+ ],
182
+ "comparison": [
183
+ "The fixture description and rendered samples use the same captured JSON."
184
+ ],
185
+ "review": [
186
+ "This fixture checks the renderer, not a PR correctness verdict."
187
+ ],
188
+ "chart": {
189
+ "kind": "bar",
190
+ "x_label": "Sample size (items)",
191
+ "y_label": "Duration (ms)"
192
+ }
193
+ }