feat: bound the summary panel height caps and raise the storage rails - #4779
Conversation
GPT 5.6 Review — ✅ no blocking findingsGPT 5.6 completed its review of This comment is updated in place on each push. Review detailsNo findings. False positive or not applicable? A repository writer can comment: |
UX Review (Fable 5) — 🟡 CONCERNSUX-level review of Analysis complete. The change is well-executed UX-wise — measurement-gated fades, persistent disclosure state, honest counts — with one behavioral accessibility gap on the new notes scroller. UX-Verdict: CONCERNS Solid capped-scroll treatment with honest overflow cues, but the bounded notes list is unreachable past the fold for keyboard-only users. Watch
Suggestions
[UX-REVIEWED] e385bb6 |
Design Review (Fable 5) — ✅ PASSDesign-level review of All hunks are accounted for and the approach checks out (I verified the prompt in Design-Verdict: PASS Real, unrecoverable data loss with a false count; fixed at the right layer, layout and chip semantics repaired in the same motion. Suggestions
[DESIGN-REVIEWED] e385bb6 |
First Principles Review (Fable 5) — 🟡 CONCERNSPremise-level review of All repo checks complete: I verified the existing scroll-cue mechanism ( First-Principles-Verdict: CONCERNS A measured overflow-fade cue (new hook, two overlays, ~90 harness lines) ships undeclared in a change described entirely as rails + height caps. What this change shipsIntent: make the summary panel trustworthy on long sessions by stopping silent, unrecoverable truncation of the stored record — a FIX, with the layout that trim was masking bounded in the same change.
Watch
[FIRST-PRINCIPLES-REVIEWED] e385bb6 |
Opus 4.8 Review — ✅ no blocking findingsReviewed Review detailsBoth the discovery pass and my independent falsification converge. Let me confirm the The backend trim/warn logic, rail default changes, and the No findings. [OPUS-REVIEWED] e385bb6 Verdict parsed from the review's SHA-scoped output markers for commit False positive or not applicable? A repository writer can comment: |
|
First Principles Review — disposition
|
The summary sidecar trimmed to 8 intents and 5 project notes before the write,
so content was deleted from the record rather than collapsed in the panel, and
nothing reported it. Raise both rails to 50, where binding is pathological
rather than routine, and log at WARNING when one does bind so an operator
asking "why does my summary stop there" has a thread to pull. The old config
descriptions justified a storage deletion with a display argument ("the panel
collapses them anyway"); they now describe what the code does.
Raising the notes rail alone would have broken the layout it feeds: the notes
footer is shrink-0 with no ceiling, so a long list grew the footer and squeezed
the intent list toward zero instead of scrolling. Bound the open-items card and
the notes footer at 33vh each, capping the CARD rather than the list inside it
so the box a reader can point at is the box the cap governs, with the
disclosure control outside the scroll region and min-h-0 on the list so a flex
child cannot refuse to shrink.
Stop done intents feeding the open-items block and its header chip. A
finished-and-verified intent needs nothing, so its residual next steps are
residue, and counting them inflated the chip on precisely the long sessions the
higher rail now preserves.
9b1c82d to
e385bb6
Compare
Disposition — UX Review (🟡 CONCERNS): accepted, fixed in
|
Disposition — GPT 5.6 Review (🔴 blocking): accepted, fixed in
|
Disposition — UX Review (🟡 CONCERNS) on
|
Problem / Motivation
The session-summary sidecar trimmed every payload to 8 intents and 5 project notes
before writing it to disk. That trim is not a display limit — nothing collapses,
nothing is hidden behind a control, and nothing reports it. The dropped intents and
notes are simply absent from the stored record, and no later reader can tell a session
that produced 20 intents from one that produced 8.
Two things made it worse than a plain cap:
collapses all but the most recently touched one, so the config's stated rationale
("the panel collapses them anyway") argued for a storage deletion from a display
premise.
project-notes badge both report what survived, so a clipped session shows a
confident, wrong number.
Why it matters
A returning reader uses this panel to decide what still needs them. Silent truncation
makes it quietly unreliable on exactly the long sessions where it is most needed, and
the loss is unrecoverable — the payload is only rebuilt when the transcript changes, so
nothing backfills what the trim removed.
What changed (motivation → approach → change)
Raise the rails so binding is pathological, not routine.
max_intentsandmax_constraintsboth move to50. This costs nothing measurable: only three callsites read the payload and none injects it into a model prompt, so a larger payload
carries zero token cost. At ~1.1 KB per intent (measured across 26 real sidecars, 2.2 KB
worst case) 50 intents is ~54 KB typical, and every loop over intents is linear.
Report a rail that binds.
normalize_payloadnow logs at WARNING with the counts.WARNING rather than INFO because the loss is otherwise invisible and the rail should
essentially never bind — a line that fires on ordinary sessions is one an operator
learns to ignore, so a test asserts it stays silent when the rail does not bind.
Fix the layout the higher notes rail would have broken. The project-notes footer is
shrink-0with no ceiling, so a long list grew the footer and squeezed the intent listtoward zero instead of scrolling —
max_constraints: 5was the only thing preventingthat. Both the open-items card and the notes footer are now bounded at 33vh.
The cap is on the card, not the list inside it. Capping the list leaves the card
measurably larger than its stated limit (heading, padding and toggle add ~86px, so a
"33vh" list sat inside a card that was 43% of the window), which reads as the cap not
working. Charging the chrome against the third instead makes the box a reader can point
at the box the cap governs. The list is
flex-1 min-h-0 overflow-y-auto, andmin-h-0is load-bearing: a flex child's default
min-height:autorefuses to shrink below itscontent and would push the card straight past the cap. The disclosure control sits
outside the scroll region so it is reachable at any list length, and below the cap the
height stays natural with no scrollbar.
Give the bounded edge a cue. A cap that lands on a clean row boundary is
indistinguishable from a list that simply ended, and the platform offers no help —
macOS overlay scrollbars stay hidden until you scroll. So a reader who expands
"+25 more", sees six rows and "Show less", can reasonably conclude the rest never
loaded. Both bounded regions now carry a bottom fade, following the treatment already
used in four places in the dashboard (
Accordion,PrDetail,PrList,IssueList):h-6,bg-gradient-to-tfrom the region's own surface colour so content dissolves intoit rather than banding over it.
It differs from those four in one way, and that difference is the point: they are static,
shown whenever the region is open, while this one is gated on measurement. A fade over a
list with nothing beneath it invents hidden content, which is the same dishonesty as
hiding content told the other way round — so it appears only while something is genuinely
below the fold, and clears when the reader reaches the end. That also makes it correct in
the short-session case, where the cap does not bind and there is nothing to hint at.
Stop finished intents feeding the open-items block.
collectTriage's second passhoisted steps from every intent except
needs-youanddropped, which includeddone— completed and verified. Such an intent needs nothing, so its residual nextsteps are residue, and counting them inflated the header chip on precisely the long
sessions the higher rail now preserves. The pass is now
in-progressonly;needs-youis still covered by the first pass.
Config descriptions now describe the code. Both keys claimed to be display-driven
upper bounds; they are storage rails, and the text says so.
Tests
test_trimming_intents_is_reported/test_trimming_project_notes_is_reported— theWARNING fires with the dropped and total counts when each rail binds.
test_a_rail_that_does_not_bind_stays_quiet— no log record on an ordinary session.This is what keeps the warning a signal rather than noise.
test_the_suppress_the_section_setting_does_not_warn—max_constraints: 0is thedocumented "suppress the section entirely" setting, so any emitted note trips
len(constraints) > 0and would have fired the WARNING on every regeneration of everysession with that config — precisely the noise the test above exists to forbid, missed
because it only exercised the 50/50 case. The trim site now exempts
0, sincediscarding notes there is the operator's intent rather than an anomaly.
test_documented_defaults/test_non_numeric_values_fall_back_to_defaults— updatedto
50. These caught that the default lived in two places: the dataclass fieldand an independent literal in the disk-parse fallback, so changing only the dataclass
would have left a config-absent install still capped at 8.
never hoists from a done intent(vitest) — rewritten as a contract test; itpreviously asserted the opposite. The existing ordering test already pins that
in-progressstill contributes, so the narrowing is fenced on both sides.website/scripts/capture-session-summary-height-caps.mjs— a new capture harness thatasserts the geometry it photographs, because a still frame cannot distinguish
"capped at 33vh" from "happens to be short". 19 assertions across a tall and a short
fixture in both themes: the card sits at 33vh and its list has real height and
scrolls; the toggle survives scrolling the list to its end; the notes header is not
squeezed out; the fade is present while content is below the fold, resolves to the
region's own surface colour, and clears at the end of the scroll; and on the short
fixture the card sits below the cap with nothing scrolling and no fade at all —
the checks that would catch an implementation pinning every expanded list to 33vh or
showing the cue unconditionally.
Two of these assertions were strengthened after they failed to catch a real regression
I introduced mid-review. Copying the sibling
Accordion'sabsolute inset-0scrollertook the list out of flow, so the card — whose height is a
max-hceiling rather than afixed height — had no content left to measure and collapsed from 297px to 86px. The
one-sided
card <= 33vhandscroll > clientchecks both still passed (a collapsedcard is under the cap;
client=0is less than any scrollHeight). They are now two-sided,so that failure mode is caught directly rather than only via the short fixture.
The harness also seeds
localStoragethroughstubDashboardApi'slocalStorageEntriesoption rather than its own
addInitScript, which would race that helper'slocalStorage.clear()and depend on undocumented registration order.Manual verification
Geometry is machine-asserted by the harness above rather than eyeballed. Measured at a
1400x900 viewport: open-items card 297px (exactly 33vh) with 969px of content
scrolling inside a 211px list; notes footer 297px with 402px of content in a 261px
list; short fixture 246px with nothing scrolling. Both themes. Adding the fade left every
one of those numbers unchanged.
The fade's own colour resolution is asserted rather than assumed, because Chrome reports
a
withAlpha()theme colour ascolor(srgb 0.1098 0.098 0.1333)while reporting theresolved gradient stop as
rgb(28, 25, 34)— the same colour in different units, so anaive string comparison would have reported a mismatch that does not exist, and a genuine
mismatch (a stop resolving to transparent, rendering nothing) would be invisible.
Worth recording for whoever tunes this next: 33vh of the window is 43% of the
panel's 685px scroll area, because the panel is shorter than the window by its header
and two footers. The harness prints both shares so that difference is visible rather
than rediscovered.
Full backend suite: 58,560 passed. The 143 failures on this host are pre-existing and
environmental — verified by running the highest-count files against pristine
origin/mainin the same venv, which fails identically (80 failed / 84 passed).Causes:
defusedxmlabsent from the local venv, the xdist worker budget beingcores/memory-dependent, and
AF_UNIX path too longfrom the worktree path length. All175 session-summary tests pass. Frontend: 22,295 passed; 3 unrelated files timed out
while both suites ran concurrently and pass in isolation (77 tests).
Screenshots / video
Open items expanded — the card bounded at 33vh with its list scrolling inside it, and
"Show less" pinned below the scroll region:
The same block with the inner list scrolled to its end — this is what shows the toggle
lives outside the scroll region rather than merely below it:
"How this project works" expanded — 18 notes, footer bounded at 33vh, header intact:
Collapsed default, the short-session case, and light theme
Collapsed default — 3 items and the withheld count, unchanged by this PR:
Short session expanded — natural height, no scrollbar, cap not binding:
Light theme, the same capped state:
Related Issues
no linked issue: this came from a session-summary improvement tracker rather than a
filed issue, so a merge has nothing to close.
Heads-up on a known textual conflict: #4533 reformats the same four
max_intents/max_constraintsparse-fallback lines inconfig/loader.py(onto oneline each, keeping
8and5). Whichever PR lands second will conflict there, and thecorrect resolution is this PR's
50/50values in #4533's single-line form. The twoPRs' edits to
docs/system-specs/modules/session-summary.mdare disjoint — #4533 is inthe Storage/payload region, this is the Configuration table.
Checklist
feat|fix|docs|refactor|perf|test|chore|ci|build|revert: ...)