# `plot` — documentation and a coverage floor **Goal:** Give the library a README and a user-facing documentation set, make the test coverage reproducible from one command, raise every library source above 80% line coverage, and add a floor check so it cannot silently regress. ## Measured baseline (this is the starting point, not an estimate) `lcov` over `src/**`, built with `--coverage -O0 -g`, running the full 274-case suite: | | | |---|---| | Aggregate | **93.0% lines** (3206/3447), 97.8% functions (404/413) | | Files captured | 23 | | Below 80% | **`gl/loader.cpp` — 65.4% lines, 77.8% functions** | | Near the line | `render/vertex_array.cpp` 83.1%, `gl/handle.cpp` 83.6% | | At or near 100% | `data/*`, `math/transform`, `plot/axis`, `plot/measure`, `render/embedded_mono_font` | | No instrumented lines | `core/status.cpp` (19 non-comment lines), `render/shaders.cpp` (82, string literals only), `text/stb_truetype_impl.cpp` (0) | | Adapters | 2 files captured / 57 lines at 100% — the widget and host *bodies* need a window, so they are covered by the demo runs, not by unit tests; their testable seams (pointer anchors, row routing) are unit-tested | The uncovered lines are, in every case, **error paths and "loader unavailable" branches** — that is the honest description of the gap, and it is what the new tests must exercise. ## Global Constraints - `plot_core` and every public header under `include/plot/` stay free of third-party code, and the public API must not change to make something testable. - Public headers self-contained; the configure-time umbrella and self-containment checks keep passing. - C++20; our targets clean under `-Wall -Wextra -Wpedantic -Wconversion -Wshadow`. - No exceptions across the public API boundary. - The existing 274 cases keep passing; no assertion is relaxed. - **Tests must fail toward a false alarm, never toward a silent pass.** A coverage test that cannot fail is worse than none. - **Never leave process-global GL state poisoned for later cases.** `plot::gl::load()` is one-way and process-wide; tests that need the loader unavailable must run in a **forked child** (the suite already has that pattern) or restore exactly what they stubbed. - Documentation claims must be **checked against the code**, not written from memory: every API name, signature, default value and file path in the docs is verified before it is committed. - Commit as soon as the tree builds; several agents in this phase have died of turn exhaustion. - Every task ends with `cmake --preset linux-debug && cmake --build build/debug && ctest --test-dir build/debug --output-on-failure` green and warning-clean. --- ### Task 1: Reproducible coverage, with a floor **Files:** - Modify: `CMakePresets.json`, `CMakeLists.txt` - Create: `tools/coverage.sh`, `tools/coverage_floor.py` (or an equivalent small checker) - Create: `docs/coverage.md` (the measured table, regenerated by the script) **Interfaces:** - Consumes: `ctest`, `lcov`, `genhtml`, the presets. - Produces: a `linux-coverage` preset; `tools/coverage.sh` producing a summary, an HTML report and a machine-readable file; a floor check that exits non-zero when any `src/**` file is below 80% lines or the aggregate is below the configured target. - [ ] **Step 1: The preset** Add `linux-coverage` (Ninja, Debug, `--coverage -O0 -g` on compile and link, `binaryDir` `build/cov`). It must not affect the other presets, and `PLOT_BUILD_TESTS=ON` is required. Confirm `cmake --preset linux-coverage && cmake --build build/cov && ctest --test-dir build/cov` works from clean. - [ ] **Step 2: One command** `tools/coverage.sh` runs the suite, captures with `lcov`, writes `build/cov/coverage.info`, generates `build/cov/html/index.html` with `genhtml`, prints the per-file table for `src/**`, and **exits non-zero if the floor check fails**. It must be idempotent: a second run without changes gives the same numbers (say how you guarantee that — stale `.gcda` files are the usual cause). - [ ] **Step 3: The floor check** `tools/coverage_floor.py` reads the lcov file and fails if any file under `src/` is below **80% lines**, or the `src/` aggregate is below **90%**. It must be able to fail: prove it by temporarily raising the per-file floor above a file's actual coverage and showing the non-zero exit. State the numbers it enforces in `docs/coverage.md` so the contract is visible. - [ ] **Step 4: Wire it so it cannot rot** Decide how the check is reachable: a `coverage` custom CMake target, a documented command, or a ctest case that only registers in the coverage preset. Whichever you choose, it must not slow or destabilise the normal debug/asan/release runs, and the README's build section (Task 4) must name the exact command. Report what you chose. - [ ] **Step 5: Verify and commit** Report from a clean tree: the aggregate and per-file table, the floor check's exit status, and the deliberate-failure demonstration. Append the table to `docs/coverage.md`. ```bash git add -A && git commit -m "test(coverage): a reproducible coverage preset, report and floor check" ``` --- ### Task 2: Close the error-path gaps in the GL layer **Files:** - Modify: `tests/test_gl_context.cpp`, `tests/test_render_smoke.cpp` (or a new `tests/test_gl_errors.cpp`) - Modify: `src/gl/loader.cpp`, `src/render/vertex_array.cpp`, `src/gl/handle.cpp` **only if** a test requires a change, and then only a non-behavioural one (state it in the report) **Interfaces:** - Consumes: the suite's existing stub-and-restore pattern for entry points (`RefuseGetIntegerv`, `RefuseMapBufferRange`, `RefuseFenceSync`), its fork-based death-test pattern, `plot::gl::available()`, `caps()`. - Produces: tests, not new API. The public surface must not change. - [ ] **Step 1: `loader.cpp` — the extension scanner and the missing-symbol path (28 uncovered lines)** The uncovered lines are `has_token` (the whole function, including the `at_start`/`at_end` boundary), `has_extension`'s indexed path, the "no current context" return, and the post-failure `PLOT_ASSERT`. Reach `has_token` **through the public path** by stubbing `glGetString` so `GL_VERSION` reports a `2.1` context and `GL_EXTENSIONS` returns a crafted string — then assert `caps().buffer_storage`/`persistent_map` both ways: extension present (`...GL_ARB_buffer_storage ...`) and a **prefix-only near-miss** (`GL_ARB_buffer_storageX`, which must be false). That near-miss is the boundary the function exists for, so it is the assertion that matters. Reach the indexed path by stubbing `glGetStringi` to return `nullptr` and to return a matching name. For the missing-symbol path: run it in a **forked child** that calls `load()` with a stub loader returning `nullptr` for one required entry point, asserts `load()` returns false and `available()` is false, and exits with a status the parent checks. Never do this in the parent — `load()` is one-way and process-wide. - [ ] **Step 2: `handle.cpp` — the `kNoLoader` and "returned 0" families (42 uncovered lines)** The uncovered lines are the failure returns for `glCreateShader`/`glGenBuffers`/`glGenVertexArrays`/`glCreateProgram` returning 0, the `kNoLoader` returns, the program-link-failure log path, and two early returns. Cover the "returned 0" family by stubbing the specific entry point to return 0 (save and restore it), then asserting the constructor returns the documented `Status` **and leaves a usable object** (id 0, destructor safe). Cover the `kNoLoader` family in the forked child from Step 1, where `available()` is genuinely false. Cover the link-failure log path by compiling a program whose fragment shader is valid GLSL but fails to link against the vertex shader (a mismatched varying/interface is the usual way) and asserting the failure is reported with a non-empty log. - [ ] **Step 3: `vertex_array.cpp` — the failure and empty paths (10 uncovered lines)** The uncovered lines are the loader-unavailable returns, `vao_.reset()` on the failure path, and the `instances_ = 0` early returns for an empty batch. Cover the empty-batch returns through the public staging API (stage nothing, upload, draw — assert nothing is drawn and no GL error), and the failure returns through the forked child and the stub pattern. - [ ] **Step 4: Re-measure and report** Run `tools/coverage.sh` and report the new per-file table. **Every file under `src/` must be at or above 80% lines**, and the aggregate at or above 90%; if either fails, keep going rather than lowering the floor. Also report the aggregate change from 93.0% and name any line you could not reach and why (an unreachable line is worth stating, not hiding). - [ ] **Step 5: Commit** ```bash git add -A && git commit -m "test(gl): cover the loader's extension scanner and the handle and vertex-array failure paths" ``` --- ### Task 3: The user guide under `docs/` **Files:** - Create: `docs/getting-started.md`, `docs/architecture.md`, `docs/embedding.md`, `docs/data-and-performance.md`, `docs/interaction.md`, `docs/styling.md`, `docs/building-and-testing.md` **Interfaces:** - Consumes: the headers, the specs under `docs/superpowers/specs/`, the demos under `examples/`, the coverage table from Task 1. - Produces: prose documentation for a *user* of the library. The `docs/superpowers/` tree stays as the design record and is not user documentation — say so once, in `docs/architecture.md`. - [ ] **Step 1: Write the pages, verifying every claim** For each page, the claims must come from the code: read the header and the relevant `src/` file before writing a sentence about it. Specifically: - **getting-started** — prerequisites, the build/install/consume commands, and a minimal GLFW program that opens a window and draws one trace. The program must be **compiled** as part of the verification (a scratch build is fine; do not add it to `examples/` unless it duplicates nothing). - **architecture** — the layering (`core`/`math` → `gl` → `data` → `render` → `plot` → `input`), what each layer owns, the dependency rule, and where the design record lives. - **embedding** — one section per host (GLFW, ImGui, Qt6, GTK4), each with the exact `initialize`/`paint`/`resize`/event-translation steps the shipped adapter performs, the DPI/`ui_scale` rule, and the fact that the library never owns the context or the event loop. Include the per-host headless invocation used by the project's own verification. - **data-and-performance** — `Series`/`ArraySeries`/`StreamSeries`, the SPSC contract and what a producer may and may not do, the min/max LOD pyramid and why it makes cost O(pixels), the exact-vs-interpolated query distinction (`reduce`/`sum`/`sumsq` exact over a range; the crosshair interpolated), and the measured frame times with the standing caveat that they are llvmpipe under Xvfb. - **interaction** — cursors (two orientations), regions, measurement band, legend, crosshair, the `Controller` gesture table exactly as implemented, live follow with relative zoom and mouse panning, and the `XFollow`/`set_x_range` reinterpretation with its clamp. - **styling** — every `Style` field with its default and whether it is logical or device, the `ui_scale` rule, the grid classes, the crosshair dash, and the `Legend`/`CrosshairStyle` knobs. - **building-and-testing** — the presets, the options (`PLOT_BUILD_*`), how to run the suite, the headless demo modes, and the coverage command with the Task 1 floor. - [ ] **Step 2: No invented API** Any symbol you mention must exist with that exact name and signature. Check by grepping the headers, and for the getting-started program by compiling it. Report the check you ran per page. - [ ] **Step 3: Commit** ```bash git add -A && git commit -m "docs: a user guide covering getting started, architecture, embedding, data, interaction, styling and testing" ``` --- ### Task 4: The README, and a final accuracy pass **Files:** - Create: `README.md` - Modify: `docs/*.md` only to fix anything the final pass finds wrong **Interfaces:** - Consumes: everything above, including Task 3's pages and Task 1's coverage table. - Produces: the repository front door. - [ ] **Step 1: The README** Keep it to what a newcomer needs, in this order: what the library is in two sentences; the four host integrations and the headline capability (real-time, high sample rate, O(pixels) rendering) with one measured number; a short feature list; build and install; a ~20-line minimal embedding example; a link per doc page under `docs/`; the test and coverage commands with the current numbers; licence and third-party provenance (Adwaita Mono under OFL 1.1, vendored stb_truetype v1.26, and the `plot_core` zero-dependency rule); and a short, honest limitations list. Do not market: no claims without a number or a file behind them. - [ ] **Step 2: The final accuracy pass** Walk every code snippet in the README and in `docs/` and compile or at least syntax-check it against the real headers. Fix what does not compile. Grep every mentioned symbol and confirm it exists. Report the list of symbols checked and any correction made. - [ ] **Step 3: Commit** ```bash git add -A && git commit -m "docs: README with build, embedding and verification entry points" ```