Skip to content

Review: the rail shows every intent, the code column keeps a floor, and both ends of the bar stay legible - #12

Merged
jbachorik merged 4 commits into
mainfrom
fix/review_layout_and_rail
Aug 5, 2026
Merged

Review: the rail shows every intent, the code column keeps a floor, and both ends of the bar stay legible#12
jbachorik merged 4 commits into
mainfrom
fix/review_layout_and_rail

Conversation

@jbachorik

Copy link
Copy Markdown
Contributor

Four fixes, found by running a comprehensive visual pass over Review against
docs/superpowers/specs/2026-08-03-review-ui-correctness-and-orientation-design.md
— the real app driven through the shot: diag harness at eleven window
widths, against a six-item queue and a clean repository.

The intent rail drew one card of seven

Cards were ~1100px tall in an 851px rail, so the first filled it and the
rest sat below the viewport: a 26-file branch looked like it had a single
intent, and ] moved the diff while the rail kept card 1 highlighted. A
Button sizes its graphic by asking prefHeight(-1), which a wrapping
Label answers with its height at minimum width — one word per line.

A regression from #11, which added the two setWrapText(true) calls that
turned a latent binding into an unbounded height. It reached a screenshot
rather than a test because the existing tests assert on the row model, and
the row model was always right.

The code column had no claim on space (plan Task 10)

Each rail collapsed on its own width threshold. Measured at seven view
widths, four put the code column under the 560px the spec gives it — 526,
522, 493, 524 — and it was not monotonic: widening 1150 → 1210 took it from
624px to 522px as the queue rail came back.

RailLayout replaces the four thresholds with one rule: rails narrow, then
give up their width margin-first and queue-last, until the code column
clears its floor. Pure arithmetic in its own class. solve(1210) predicts
778px; the app then rendered 778px.

This is Task 10 of the 2026-08-03 plan, the one part never built.

The verdict bar truncated its own actions

At the floor the three actions want 327px of a 532px budget, so HBox
elided every label: "Approv…", "Request c…", "Ask the agent …". With every
rail collapsed the bar is the only surface left, so what yields is now
decided — actions keep their width, the title yields with a tooltip, and
the redundant hint goes first.

An empty Review didn't say what it looked at

"Nothing to review" without its scope is indistinguishable from Review being
pointed at the wrong repositories. It now carries "Scanned drydock and
btrace", naming up to three and counting the rest. Completes P3's "what was
scanned, and that it found nothing".


1186 tests, zero failures. Each commit compiles standalone. Cherry-picked
onto main rather than pushed to fix/more_reviews, whose three commits are
already in main as the #11 squash.

🤖 Generated with Claude Code

jbachorik and others added 4 commits August 5, 2026 14:20
A card was ~1100px tall in an 851px rail, so the first one filled it and
the other six sat below the viewport. The rail's own header read 0/7 over
a single card, and pressing ] moved the diff to intent 2 while the rail
kept intent 1 highlighted -- a 26-file branch looked like it had one
intent, at every window width.

A Button sizes its graphic by asking prefHeight(-1), and a wrapping Label
answers that with the height it needs at its MINIMUM width: one word per
line. The graphic now answers at the width it is actually given, and that
width is bound to the cards column -- whose width the ScrollPane hands
down -- rather than to the button, which is the cycle that made the
question unanswerable in the first place.

The two setWrapText(true) calls that turned a latent binding into an
unbounded height came from the commit that made the rail categorised, so
this is that commit's regression. It reached a screenshot rather than a
test because the existing tests assert on the row model, and the row
model was always right.

Measured in the running app before and after: h=[1132 877 1135 802 1045
865 841] -> h=[67 67 67 67 79 67 67], 6722px of content -> 506px. The
mark verb now prints those numbers, which is what made it legible.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Each rail decided its own collapse from its own width threshold, and the
code column -- the only thing anyone opens Review to read -- had no claim
on space at all. Measured through the diag harness at seven view widths,
four of them put it under the 560px the spec gives it: 526 at 1330, 522
at 1210, 493 at 1181, 524 at 1050. It was not even monotonic: widening
from 1150 to 1210 took the column from 624px to 522px, because the queue
rail came back at full width on the way up.

RailLayout replaces the four thresholds with one rule. Rails narrow
first, then give up their width margin-first and queue-last, until the
code column clears its floor. Narrowing before collapsing is what makes
the result monotonic in the number of rails: there is no width at which
widening the window loses you one.

Pure arithmetic in its own class, so the rule is testable without a
scene, and the eleven widths that were measured wrong are a test.
Verified in the app afterwards at the same widths -- solve(1210) predicts
778px and the app rendered 778px.

The code column's own width is deliberately not monotonic: at 1270 the
margin re-opens and takes its 286px back, which is the rails returning in
the reverse of the order they went. The floor is what holds across it.

Implements Task 10 of docs/superpowers/plans/2026-08-03-review-ui-
correctness-and-orientation.md, the one part of that plan never built.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
With every rail collapsed the bar is the only surface left, and a review
is read, settled and advanced entirely from it -- so it has to work at
560px, the width the column is now guaranteed. It did not: the three
actions want 327px of a 532px budget, HBox shrank every label equally,
and they rendered as "Approv...", "Request c..." and "Ask the agent ...".
A button whose own label is elided is not a standing action.

What gives way is decided rather than proportional. The actions keep
their width. The title yields, with a tooltip carrying what the ellipsis
takes -- it is context, and the actions are the point. The hint goes
first of all: "3 left" already appears in the progress line and on the
Submit button, and the key it names lives in the shortcuts overlay, so
at the floor it is the only thing on the bar that costs nothing.

Two things the implementation turns on. The decision runs in
layoutChildren, not a width listener: the first width the row is handed
arrives before CSS, so every preferred width read there is a bare
unstyled label's and the row looks roomy. And it is computed from the
row's width counting the hint's own slot whether or not the hint is
showing -- deciding from the laid-out result oscillates, because hiding
the hint frees exactly the width that says to show it.

The test pins the stage at the floor in start() and restores it after
each case. TestFX reuses the stage across classes, so a scene built at
560 came up at whatever the previous class left it at, and the
truncation assertions passed without measuring anything.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
"Nothing to review" stated a conclusion without its scope, which is
indistinguishable from Review being pointed at the wrong repositories --
the exact failure this destination has already had once, when a rail
showed another scope's files. The empty surface now carries a third
line: "Scanned drydock and btrace", naming up to three and counting the
rest, present tense while a scan is in flight, and absent entirely when
there is nothing to name.

That needed the names, so the view takes them instead of a bare count:
setItems(assembly, List<String>) and showScanning(List<String>). The
count is names.size() rather than a second parameter that can disagree
with the first. The scanning state is raised before there is any
assembly, so the view holds them rather than reading them off one.

The line gets its own quiet style. The mono slot it would otherwise have
borrowed is a code chip, and it belongs to the checkout gate's commands
-- things the reader is meant to run, which this is not.

Completes P3's "what was scanned, and that it found nothing" from
docs/superpowers/specs/2026-08-03-review-ui-correctness-and-orientation-
design.md.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@jbachorik
jbachorik merged commit 347d8bd into main Aug 5, 2026
1 check 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.

1 participant