Skip to content

Graph: the y-axis minimum belongs to the plot, not the time axis - #70

Merged
ralyodio merged 1 commit into
mainfrom
worktree-graph-axis-collision
Sep 8, 2026
Merged

Graph: the y-axis minimum belongs to the plot, not the time axis#70
ralyodio merged 1 commit into
mainfrom
worktree-graph-axis-collision

Conversation

@ralyodio

@ralyodio ralyodio commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Closes #69.

The bug

With both axis and timeAxis, the y-axis minimum was written at height - 1 — the row the time axis had already claimed. The two labels landed side by side with nothing between them:

before                          after
│   $008-10        08-14        │   $0
│                               │    08-10        08-14

Visible today in the CoinPay frame on hqtui.com/apps.

The minimum marks the bottom of the plot, and a time axis takes that row away, so it belongs one row higher whenever one is present. Decided before the labels are written rather than after, because by then the surface has already been subdivided and the row is no longer addressable.

Fixed everywhere it is implemented

Port
TypeScript reference
Rust, Go, Python, Zig, C++ reimplement the widget — each fixed
COBOL renders through the TypeScript library and uses neither option — inherits the fix

Why no fixture caught it

Because none covered the combination. graph-axis and graph-timeaxis each existed alone, and timeAxis appears nowhere in the demo either — so regenerating the fixtures against the buggy code produced zero drift. The bug was invisible to the entire conformance system.

A graph-axis-timeaxis scene is added. That is what now holds the five ports to this, and it is the part of this change that matters beyond the one-line fix.

The harnesses did earn their keep: each of the five refused the new fixture outright — "no Rust scene for widget fixture", "no Go scene", and so on — rather than silently skipping it. Matching scenes by name is what makes an unwired port loud instead of quiet.

Checks

  • 6 new tests in graph-axis.test.ts. Reverting just the fix fails 2 of them, so they test the behaviour rather than describe it.
  • 238 pass under bun test and node --test (was 232).
  • Rust cargo test, Go go test ./..., Python unittest, Zig zig build test, C++ ctest (9/9) — all green, each comparing 54 widget scenes over 5584 cells.

One test-writing note: my first assertion used /^\s*0*08-10/, which matches the correct output too because 0* allows none. It is /\d08-10/ now — a digit directly against the label is the actual signature of the fusion.

🤖 Generated with Claude Code

https://claude.ai/code/session_017Df2FNu5DhinMV2soRz3cy

Closes #69.

With both `axis` and `timeAxis`, the minimum was written at the bottom
row -- which the time axis had already claimed. The two labels ended up
side by side with nothing between them, so a chart rendered "$0" and
"08-10" as "$008-10". It is visible today in the CoinPay frame on
hqtui.com/apps.

The minimum marks the bottom of the *plot*, and a time axis takes that
row away, so it belongs one row higher whenever one is present. Decided
before the labels are written rather than after, because by then the
surface has already been subdivided.

Fixed in the TypeScript reference and in every port that reimplements
the widget: Rust, Go, Python, Zig and C++. The COBOL port renders
through the TypeScript library and uses neither option, so it inherits
the fix rather than needing one.

## Why no fixture caught it

Because none covered the combination. `graph-axis` and `graph-timeaxis`
each existed alone, and `timeAxis` appears nowhere in the demo either,
so regenerating produced no drift at all. A `graph-axis-timeaxis` scene
is added, which is what now holds the five ports to this.

The conformance harnesses earned their keep on the way: each refused the
new fixture outright rather than silently skipping it, which is the
whole point of matching scenes by name.

238 pass under Bun and Node; Rust, Go, Python, Zig and C++ suites green,
each comparing 54 widget scenes over 5584 cells.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Df2FNu5DhinMV2soRz3cy
@ralyodio
ralyodio merged commit f577922 into main Sep 8, 2026
18 checks passed
@ralyodio
ralyodio deleted the worktree-graph-axis-collision branch September 8, 2026 16:13
ralyodio added a commit that referenced this pull request Sep 8, 2026
…ministic (#71)

* Recapture the CoinPay frame with the axis fix

#70 moved the y-axis minimum off the time-axis row. The published
screenshot still showed the old collision, since the gallery serves
committed images rather than rendering live.

"$0" now sits on the plot's bottom row and "08-10" has the time axis to
itself.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Df2FNu5DhinMV2soRz3cy

* Recapture nixamp and tsbb, now that both frames are deterministic

Regenerating the whole gallery showed these two changing on every run,
which I had claimed would not happen without checking. Both were real
defects in the fixtures rather than in the capture:

  nixamp  anoisesrc defaults to seed=-1, so the analyser was measuring
          different noise each time. Seeded upstream.

  tsbb    the view renders ages through relative(), which compares
          against Date.now(). Pinning the fixture timestamps to a fixed
          date made the rendered labels drift by an hour every hour.
          Anchored to capture time upstream instead.

Two consecutive captures now produce byte-identical files for all nine.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Df2FNu5DhinMV2soRz3cy

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

Graph: y-axis minimum and the first timeAxis label collide

1 participant