safety.viz — consolidated charting library

Design document for Requirement #1 · obot.roadmap

STATUS: Signed off 2026-07-04 · drafted 2026-07-02, ported to obot.roadmap 2026-07-03, updated 2026-07-04 (qcthat alignment) · drafted by Claude Code using Fable 5, reviewed by @jwildfire

Context and goals

#1 consolidates the nine legacy safetyGraphics renderers into one modern Chart.js library at jwildfire/safety.viz, consumed by Widget_*.R htmlwidgets in gsm.safety — mirroring how gsm.kri consumes gsm.viz. This document settles the repo layout, module contract, stack, traceability approach, and the distribution decision.

Non-goals: no shared-utility layer until the second renderer forces one; no npm publication in the first iteration; no visual-regression or accessibility hardening in the pilot (tracked as explicit follow-ups).

Architecture overview

safety.agent matrices          safety.viz                          consumers
(requirement IDs)   ──keys──►  src/<chart>/ modules   ──esbuild──► dist/safety.viz-{version}/
                               Vitest (unit)                        ├─► gsm.safety inst/htmlwidgets/lib/   (vendored)
                               Playwright (browser evidence)        └─► examples/ static demo pages

Repo layout

Mirrors gsm.viz's structure, with the test stack modernized:

safety.viz/
├── src/
│   ├── main.js               # public module collection; registers shared Chart.js plugins
│   ├── histogram.js          # public entrypoint for one chart (one pair per renderer)
│   ├── histogram/
│   │   ├── checkInputs.js    # validate data/config against JSON schemas
│   │   ├── configure.js      # defaults + metadata + user-config merge
│   │   ├── structureData.js  # input records → Chart.js datasets
│   │   ├── getPlugins.js     # tooltips, annotations, custom plugins
│   │   ├── getScales.js      # axes
│   │   ├── updateData.js     # update lifecycle
│   │   └── updateConfig.js
│   └── data/schema/          # JSON schemas — machine-readable data contracts
├── tests/
│   ├── unit/                 # Vitest, requirement-ID-keyed describe/it names
│   └── e2e/                  # Playwright specs + fixtures + demo test pages
├── examples/                 # static example pages (GitHub Pages later)
├── dist/                     # committed esbuild bundles (see Distribution)
├── package.json · vitest.config.js · playwright.config.js
└── .github/workflows/ci.yml

Module contract

Every renderer module exposes the lifecycle API proven by the histogram pilot as its public surface — this is what widget bindings and test harnesses call:

MethodBehavior
init(el, data, settings)Mount into a container element; validate inputs (checkInputs) and render.
setData(data) / setSettings(settings)Replace inputs; re-derive config and datasets.
render() / resize()Redraw; respond to container size changes.
destroy()Tear down the Chart.js instance and listeners; container returns to pre-init state.

Internally each module follows the gsm.viz flow (checkInputs → configure → structureData → getPlugins/getScales → new Chart(...)), so the file layout stays recognizably parallel to the reference implementation even though the public API is the pilot's lifecycle shape.

Stack

ChoiceWhatRationale / deltas from references
ChartingChart.js v4 (+ plugins as needed)Pilot already runs 4.4.x; gsm.viz is on v3 — we take the newer major from the start.
BundleresbuildSame as gsm.viz. The pilot used rollup; esbuild replaces it at extraction time (single-tool, fast, ESM-first).
Unit testsVitestgsm.viz uses Jest 29 + jsdom; Vitest is the ESM-native equivalent with the same assertion style.
Browser evidencePlaywrightCarries over the pilot's 10-spec harness pattern; produces the browser-verifiable evidence the migration workflow requires.
FormattingPrettierSame as gsm.viz and the pilot.

Traceability and evidence

qcthat alignment: the gsm R packages use qcthat for QC reporting — GitHub issues document requirements, tests reference issue numbers in their names, and qcthat compiles the qualification matrix (milestone → issues → tests). qcthat does not support JS test frameworks today, so safety.viz adopts the issue-linked naming convention now to future-proof its evidence; full qcthat-compatible reporting for the JS stack is tracked as backlog Requirement #15.

Distribution — the decision this document settles

Recommendation: vendored, versioned dist/ directory; defer npm.

CI

One GitHub Actions workflow on PRs and main: npm ci → build → Vitest → Playwright (with browser install step). A drift check fails the build if dist/ doesn't match a fresh build of src/.

Dependencies and sequencing

Open questions

Sign-off: recorded on #1 (2026-07-04). Implementation is tracked in the issue's Tasks section — scaffold: safety.viz#1.