Skip to content

Render usage-gate skip rows as first-class timeline events on the metrics page - #154

Merged
thedavidmeister merged 3 commits into
masterfrom
2026-07-31-render-skip-rows
Jul 31, 2026
Merged

thedavidmeister merged 3 commits into
masterfrom
2026-07-31-render-skip-rows

Conversation

@thedavidmeister

@thedavidmeister thedavidmeister commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Closes #152

The metrics page draws its timeline from issue-pr-cron's metrics/runs.jsonl. The producer now emits a row for each tick its usage gate paused (rainlanguage/issue-pr-cron#163): "outcome":"skipped" (the typed discriminant) alongside "skipped":"usage-gate" and "skipReason":"<the gate's PAUSE line verbatim>", with time carried by runId as on every row. This PR renders those rows as first-class, visually distinct events instead of the dead stretches — or, with the row's zeroed run fields, phantom zero-work "runs" — they would otherwise produce.

What changed (site/metrics.html)

  • A skip draws as its own visual class: a thin muted vertical tick at the baseline, at the row's own timestamp (runId). The cue is shape (runs are dots on lines; a skip is a tick), not colour alone, and holds in light and dark. Never a bar or point that could read as a completed run with zero work.
  • Tooltip carries the verbatim skipReason (the gate's PAUSE line), built as text nodes per the page's no-markup-sink rule. The legend names the mark ("skipped tick") whenever skips are on the plot — including proportion mode, which gains a legend only then. A caption sentence appears only when skips exist.
  • Structural aggregate exclusion: one predicate (isSkip: outcome === "skipped" primary, skipped presence as the fallback arm; both fields are absent, not null, on ordinary rows) and one partition (pmPartition) split records before any renderer runs. Tiles, controls, y-scale and every series receive real runs only; no chart re-tests skip-ness. This matters concretely for the real row shape — stage:"final" with startupPct: 0.0, durationMs: 0, outcome:"skipped" — which read as a run would drag the median, dive the total line to the baseline, and paint an errored run.
  • Fail-open on display: an unknown skipped value still renders as a generic skip (marker + tooltip naming the kind it declares) rather than being dropped — and by the partition it is still never counted as a run. Same for a row carrying only the typed outcome.
  • Zero/some/all correctness: files with no skip rows render exactly as before (pinned by test — no historical back-fill exists); an all-skips window still charts the pause and dates the axis by the skips. Skips draw in both unit modes (an event is not a measurement, so no toggle can make it meaningless), and skip timestamps join the x-domain so a pause at either end stretches the axis.

Hand-rolled vanilla JS matching the page's existing idioms (the framework question is #150 and out of scope here).

Screenshots

Same fixture both sides: the live runs.jsonl plus three real-shape usage-gate skip rows after the last producer run. On master the skip rows inflate "runs recorded" to 82, put a red "outcome skipped" in the caption as if the latest run degraded, and leave stray zero-duration marks; on the branch they are muted baseline ticks with a legend entry and the caption sentence, excluded from every stat (79 runs recorded).

before (master) after (branch)
light before light after light
dark before dark after dark

QA

  • Discriminating tests: 20 new deno tests in test/dashboard.test.js — predicate contract (typed outcome primary, gate-field fallback, run outcomes and absent fields never match); typed-outcome-only row routes to skips and renders generically; pmRecords admits a gate-fields-only row (no startupPct/bootMs) and keeps vetter skips out; partition split; tiles unmoved by a skip (median and run-count pinned); the emitter's REAL row (zeroed run fields, outcome:"skipped") partitions as an event, moves no tile, draws no dot; marker is a line at its own timestamp, vertical and short, never a circle; draws in pct mode; unknown skipped renders generically, never dropped, never a run; all-skips file still charts and dates the axis; a skip's stray numbers never reach the y-scale; zero-skip regression pin (no marker, no legend entry, same dots/lines/labels in both modes); legend naming in both modes; verbatim-reason tooltip unit + hover integration; caption on/off; hostile-input tests for skipReason/skipped/runId (unit + hover, markup-node sweep).
  • Mutations applied: from committed baseline b9a9fb2 — (1) isSkipreturn false (drop the predicate): 7 tests failed; (2) pmPartition runs side → unfiltered recs (count skips into aggregates): 4 tests failed, including the tiles-aggregate and y-scale tests. From committed baseline acecb01 — (3) drop the typed-outcome arm: 2 tests failed; (4) drop the gate-field fallback arm: 7 tests failed. Each restore verified with git status clean and the full suite back green before the next mutant; head suite is 187/187.
  • Oracle: the pinned row contract for metrics page: render usage-gate skip rows as first-class events — pauses currently draw as dead stretches #152 as finalized by Record usage-gate pauses as typed skip rows in metrics/runs.jsonl, published hourly issue-pr-cron#163 (typed outcome:"skipped" + skipped/skipReason verbatim; time via runId; both skip fields absent on ordinary rows; no back-fill), plus the page's own rules — CLAUDE.md "Rendering untrusted data" (DOM nodes only) and the chart's "hue means error-ness" convention (skips are muted and shape-cued, no new hue).
  • Category check: the category is "rows that are not runs", not just skipped:"usage-gate" — covered by the two-arm predicate (typed outcome alone, gate fields alone, unknown kinds) and the stray-numbers test (a skip row carrying run-shaped fields still moves no aggregate); both unit modes, both themes (skip styles use vars defined in all three theme blocks), zero/some/all skip-row files, single-event and unparseable-id x-scale fallbacks all exercised.

🤖 Generated with Claude Code

thedavidmeister and others added 2 commits July 31, 2026 10:35
The producer now emits a row with skipped/skipReason for ticks its usage
gate paused, so the metrics chart no longer shows those pauses as
unexplained dead stretches. A skip draws as a thin muted baseline tick —
a shape no run mark uses, so it cannot read as a completed run — with
the gate's PAUSE line verbatim in its tooltip, a legend entry, and a
caption sentence, in both unit modes and both themes.

A skip is an event, never a run: isSkip is the page's one discriminant
and pmPartition splits records once, so tiles, controls and every chart
aggregate see real runs only — no renderer re-tests skip-ness. Unknown
skipped values still render as a generic skip (fail-open on display) and
still never count as runs. Files with zero skip rows render exactly as
before; all-skip files chart the pause on its own.

Closes #152

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
issue-pr-cron#160's actual row is a stage:"final" record with zeroed run
fields (startupPct 0.0, durationMs 0, outcome "skipped") plus the skip
pair. Read as a run it drags the median, dives the total line and paints
an errored run; the partition defuses all three, and this pins it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@thedavidmeister thedavidmeister self-assigned this Jul 31, 2026
@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@thedavidmeister, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 23 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 3cfa1f63-ed44-44bc-8ffe-927dc042f363

📥 Commits

Reviewing files that changed from the base of the PR and between b618355 and acecb01.

📒 Files selected for processing (2)
  • site/metrics.html
  • test/dashboard.test.js

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

thedavidmeister added a commit that referenced this pull request Jul 31, 2026
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
issue-pr-cron#163's final row shape carries outcome:"skipped" as the
typed discriminant beside skipped/skipReason, and ordinary rows omit
both skip fields entirely. isSkip now reads outcome === "skipped" first
with the gate-field presence arm kept for rows lacking the typed
outcome; run outcomes (ok/error/session-limit) and absent fields never
match. Classification is by type, not by string-matching a reason.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@thedavidmeister

Copy link
Copy Markdown
Contributor Author

Reviewed acecb01: ready — skip rows as baseline ticks with verbatim reasons via text nodes, one typed isSkip predicate partitioning once so every aggregate sees runs only, phantom-zero-run hazard defused with the producer's real row shape pinned by test, zero-skip page pixel-identical, 187/187 with four mutants killed from committed baselines, screenshots verified.

@thedavidmeister
thedavidmeister merged commit 162f5b7 into master Jul 31, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

metrics page: render usage-gate skip rows as first-class events — pauses currently draw as dead stretches

1 participant