The stacked chart's caption gate budgets one legend line, but renderLegend wraps — so the unit caption reappears at heights where the grouped chart does not fit.
Follow-up to a must-fix review comment on #1061 (comment) that arrived 16 minutes after the PR merged and was never addressed. Live on main as of 31f14c4e.
The bug
authbridge/cmd/abctl/tui/usage_render.go:172-178
// stackedChartFloor: the same, plus the blank separator and at least one legend line.
// renderLegend emits one line per wrap and never zero, so one is its minimum.
stackedChartFloor = barChartFloor + 2
The comment is accurate about renderLegend's minimum. The gate then spends that minimum as if it were the actual cost. renderLegend (usage_stacked.go:481-537) flushes a line whenever plain+cost > width, so grouped by model at 66 columns it returns 2 lines at 3 series, 2 at 4, and 3 at 5. axisCaption therefore opens at a budget the stacked frame does not fit into, and the caption is the single row that tips the pane past the terminal.
This is the same defect #1061 set out to fix — a caption gated on height so it cannot overrun the pane — unfixed for the wrapping case.
Reproduction
Grouped by model with three realistic model names (claude-3-5-sonnet-20241022, gpt-4o-2024-11-20, gemini-1.5-pro-002), sweeping widths 66–140 × budgets 13–40 and counting only cases that fit without the caption and overflow with it — 30 cases:
w=66 budget=16 afford=16 withCaption=17 noCaption=16 legend=2
w=67 budget=16 afford=16 withCaption=17 noCaption=16 legend=2
w=68 budget=16 afford=16 withCaption=17 noCaption=16 legend=2
w=69 budget=16 afford=16 withCaption=17 noCaption=16 legend=2
On the composed pane that is 66x27 and 80x27. Reachable from ordinary user state: [b] cycles the grouping (README.md:564) and usage.group persists in settings, so a grouped chart survives a restart.
Why CI is green
TestUsageStackedChart_FitsTheChartBudget (layout_fit_test.go:321) tests the right property and is well constructed, but its fixture groups by status with "200" / "429" / "500" — three short labels that fit one legend line at every width it checks, so the wrapping case is unreachable from it. Same shape of blind spot as fitModel's snapshot being ungrouped, which #1061 diagnosed and fixed one layer down.
Suggested fix
Make the floor reflect the legend actually rendered rather than its lower bound: compute the legend before the caption decision and include its real line count in the height the gate checks. renderStackedBars already builds legendSeries and letters before it calls axisCaption, so the legend can be rendered first and its length passed in.
Add a case to TestUsageStackedChart_FitsTheChartBudget with long labels (model names) so the wrapping path is covered.
CodeRabbit's second comment on the same PR (here) points at the same spot from the renderer side.
The stacked chart's caption gate budgets one legend line, but
renderLegendwraps — so the unit caption reappears at heights where the grouped chart does not fit.Follow-up to a
must-fixreview comment on #1061 (comment) that arrived 16 minutes after the PR merged and was never addressed. Live onmainas of31f14c4e.The bug
authbridge/cmd/abctl/tui/usage_render.go:172-178The comment is accurate about
renderLegend's minimum. The gate then spends that minimum as if it were the actual cost.renderLegend(usage_stacked.go:481-537) flushes a line wheneverplain+cost > width, so grouped by model at 66 columns it returns 2 lines at 3 series, 2 at 4, and 3 at 5.axisCaptiontherefore opens at a budget the stacked frame does not fit into, and the caption is the single row that tips the pane past the terminal.This is the same defect #1061 set out to fix — a caption gated on height so it cannot overrun the pane — unfixed for the wrapping case.
Reproduction
Grouped by model with three realistic model names (
claude-3-5-sonnet-20241022,gpt-4o-2024-11-20,gemini-1.5-pro-002), sweeping widths 66–140 × budgets 13–40 and counting only cases that fit without the caption and overflow with it — 30 cases:On the composed pane that is 66x27 and 80x27. Reachable from ordinary user state:
[b]cycles the grouping (README.md:564) andusage.grouppersists in settings, so a grouped chart survives a restart.Why CI is green
TestUsageStackedChart_FitsTheChartBudget(layout_fit_test.go:321) tests the right property and is well constructed, but its fixture groups by status with"200"/"429"/"500"— three short labels that fit one legend line at every width it checks, so the wrapping case is unreachable from it. Same shape of blind spot asfitModel's snapshot being ungrouped, which #1061 diagnosed and fixed one layer down.Suggested fix
Make the floor reflect the legend actually rendered rather than its lower bound: compute the legend before the caption decision and include its real line count in the height the gate checks.
renderStackedBarsalready buildslegendSeriesandlettersbefore it callsaxisCaption, so the legend can be rendered first and its length passed in.Add a case to
TestUsageStackedChart_FitsTheChartBudgetwith long labels (model names) so the wrapping path is covered.CodeRabbit's second comment on the same PR (here) points at the same spot from the renderer side.