Skip to content

fix(frontend): make a section's wide table admit what it is hiding - #2465

Merged
dzarlax merged 6 commits into
constructorfabric:mainfrom
dzarlax:fix/section-table-affordances
Aug 12, 2026
Merged

fix(frontend): make a section's wide table admit what it is hiding#2465
dzarlax merged 6 commits into
constructorfabric:mainfrom
dzarlax:fix/section-table-affordances

Conversation

@dzarlax

@dzarlax dzarlax commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

A pass over the two table blocks at the top of a person's section
screen. Everything here is about the same mismatch: the table holds far
more than the box shows, and nothing on screen admitted it.

What was wrong

Before
Header Empty. The metric selector is chart-only and the plain heading needs a single metric, so a grouped multi-metric table matched neither branch.
Grand total, scrolled across Gone. Its cell spans every group, so its left edge is off-screen once the table scrolls; left-aligned inside it, the totals went with it and left the sticky label beside nothing.
Grand total, scrolled down Gone, for the ordinary reason a long table scrolls.
Edges Nothing said the table continued. The overlay scrollbar stays hidden until a scroll begins, so at first sight there is no affordance at all, and a column cut mid-group reads as a rendering fault.
Card height Fixed for both presentations, though only a chart needs it. A short table sat above a slab of empty card.

What it does now

  • The block is named by its grouping — the metrics are already named on the columns — and carries the group count. That count is load-bearing: a reader seeing three groups beside a grand total covering eleven is looking at numbers that do not add up, with nothing on screen to explain the difference.
  • The totals stick past the label column, and the footer is pinned to the foot of the box with a top edge, so it reads as a summary over more rows rather than as the end of the table.
  • Arrows appear on each side that still holds something, and page by just under a frame so the edge row or column stays on screen.
  • A table sizes to its rows, keeps the old figure as a ceiling, and offers to drop the ceiling when the rows overrun it. The offer appears only when it would do something, and is not persisted — a card remembered at full height would greet the next visit as a wall.

Two things deliberately not done

A metric picker on the table. The chart below has one, but that is a constraint rather than a feature: a stacked chart cannot draw two metrics at once. Several metrics side by side is what the table is for, so narrowing it to one would have made it a worse chart without touching the actual complaint.

An edge fade, and a forced-visible scrollbar. Both were built and removed. A fade suggests more by veiling what lies under it, and over a pale table whose edge cells are frequently empty there is nothing to veil. Forcing the scrollbar visible does not survive a platform that draws scrollbars as overlays — the CSS applies and the bar still takes no space.

Verification

Behaviour was checked in a browser by measurement rather than by eye: the totals' position at both ends of the horizontal scroll, the footer pinned while the box scrolls, the card shrinking to content and capping, and the arrows appearing per side against the measured overflow on each axis.

Tests cover the header and its count, the card's height per presentation, the sticky totals and the pinned footer. The arrows and the full-height control are not covered: they depend on measured overflow, and jsdom reports every dimension as zero, so both stay hidden there. Faking layout to assert them would test the fake. Full unit suite green (1091), typecheck and lint clean.

Summary by CodeRabbit

  • New Features

    • Added horizontal scroll controls for timeseries tables with directional paging and smooth navigation.
    • Added expand/collapse controls for tables with vertical overflow.
    • Grouped tables now display grouping labels and column counts.
    • Grand totals remain visible during horizontal scrolling, while footers stay fixed at the bottom.
  • Bug Fixes

    • Improved table and chart sizing for clearer, more consistent layouts.
    • Table navigation updates appropriately as content size and scrolling change.

Alexey Panfilov added 2 commits August 12, 2026 12:50
Three faults in the same table, all of them about a box narrower than
what it holds.

The grand-total cell spans every group, so its left edge sits wherever
the first group is — off-screen on a table wide enough to scroll, which
is the ordinary case. Left-aligned inside that cell, the one row
summarising the whole table scrolled away and left its sticky label
next to nothing. It now sticks past the label column.

The footer scrolled out of reach vertically for the same reason a long
table needs scrolling at all. It is pinned to the foot of the box, with
a top edge so the block reads as a summary sitting over more rows
rather than as the end of the table.

Nothing said the table continued past either edge. The platform's
overlay scrollbar stays hidden until a scroll begins, so at first sight
there was no affordance, and a column cut through the middle reads as a
rendering fault. Arrows now appear on each side that still holds
something and page by just under a frame.

Two earlier attempts are not here because they do not work: an edge
fade suggests more by hiding what is under it, and over a pale table
whose edge cells are often empty there is nothing to hide; forcing the
scrollbar visible does not survive a platform that draws it as an
overlay.

Signed-off-by: Alexey Panfilov <Alexey.Panfilov@constructor.tech>
The metric selector is chart-only and the plain heading needs a single
metric, so a grouped multi-metric table fell through both and rendered
an empty header — the largest block on the screen was the only
unlabelled one, and a reader inferred what it was from the column
headings. It is named by its grouping, since the metrics are already on
the columns, and carries the number of groups: such a table is
routinely wider than the frame, and three visible groups beside a grand
total covering eleven is a reader looking at numbers that do not add up
with nothing on screen to explain it.

The card also held both presentations to one fixed height, which only
the chart needs — it has no height of its own. A short table sat above
a slab of empty card. The table now sizes to its rows, keeps the same
figure as a ceiling, and offers to drop the ceiling entirely when the
rows overrun it. That offer appears only when it would do something:
whether the rows overflow depends on the height of a row, which depends
on whether the header is one level or two, so it is measured by the
table and reported up rather than guessed from a row count. It is
deliberately not persisted — a card remembered at full height would
greet the next visit as a wall.

Signed-off-by: Alexey Panfilov <Alexey.Panfilov@constructor.tech>
@dzarlax
dzarlax requested a review from a team as a code owner August 12, 2026 10:51
@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: bc9cc2ba-1428-4f0a-91a8-8995ae5bcfd5

📥 Commits

Reviewing files that changed from the base of the PR and between e9927f0 and 198f6b4.

📒 Files selected for processing (4)
  • src/frontend/src/components/widgets/metric-views/metric-timeseries-presentations.test.tsx
  • src/frontend/src/components/widgets/metric-views/metric-timeseries-table.tsx
  • src/frontend/src/components/widgets/metric-views/metric-timeseries-view.test.tsx
  • src/frontend/src/components/widgets/metric-views/metric-timeseries-view.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/frontend/src/components/widgets/metric-views/metric-timeseries-view.tsx
  • src/frontend/src/components/widgets/metric-views/metric-timeseries-table.tsx

📝 Walkthrough

Walkthrough

The timeseries table now detects horizontal and vertical overflow, provides directional paging, and keeps totals visible. The view tracks overflow, supports table expansion, displays grouped-table metadata, and applies separate chart and table sizing.

Changes

Timeseries overflow and layout

Layer / File(s) Summary
Table overflow controls and sticky totals
src/frontend/src/components/widgets/metric-views/metric-timeseries-table.tsx, src/frontend/src/components/widgets/metric-views/metric-timeseries-chrome.tsx, src/frontend/src/components/widgets/metric-views/metric-timeseries-presentations.test.tsx
The table measures horizontal and vertical overflow, reports vertical overflow, renders directional paging buttons, and supports RTL-aware smooth scrolling. The footer and grand-total content remain sticky. Tests cover paging and sticky total styles.
View expansion and presentation layout
src/frontend/src/components/widgets/metric-views/metric-timeseries-view.tsx, src/frontend/src/components/widgets/metric-views/metric-timeseries-view.test.tsx
The view tracks overflow, provides table expansion controls, resets expansion when the presentation changes, displays grouped-table metadata, and separates fixed chart height from capped table height. Tests cover sizing and grouped headings.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Suggested reviewers: aleksdotbar, hello1101n

Sequence Diagram(s)

sequenceDiagram
  participant MetricTimeseriesView
  participant TimeseriesBody
  participant MetricTimeseriesTable
  participant ResizeObserver
  MetricTimeseriesView->>TimeseriesBody: Render table with overflow callback
  TimeseriesBody->>MetricTimeseriesTable: Pass onVerticalOverflow
  MetricTimeseriesTable->>ResizeObserver: Observe table size
  ResizeObserver->>MetricTimeseriesTable: Report overflow state
  MetricTimeseriesTable->>MetricTimeseriesView: Report vertical overflow
  MetricTimeseriesView->>MetricTimeseriesTable: Apply expanded table state
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: exposing hidden content in a section's wide table through overflow controls.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@src/frontend/src/components/widgets/metric-views/metric-timeseries-table.tsx`:
- Around line 260-279: Update the SIDES.map rendering in the metric timeseries
table so each paging Button is rendered only when more[side] is true; remove the
hidden-button opacity and pointer-events handling while preserving the existing
button properties and page(side) behavior.
- Around line 217-230: Update the ResizeObserver setup in the useEffect callback
to observe the scroll container box as well as the inner table, so measure()
runs when either the table or viewport dimensions change. Preserve the existing
conditional handling when ResizeObserver or the table is unavailable, and ensure
the observer is cleaned up through the existing effect lifecycle.

In `@src/frontend/src/components/widgets/metric-views/metric-timeseries-view.tsx`:
- Around line 358-362: Update the presentation-change handling near overflowOf
so it also resets the table’s expanded state alongside setOverflows(false).
Ensure returning to table mode starts collapsed and respects max-h-96, while
preserving existing overflow detection behavior.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 022e907a-6621-4475-8727-c0a2d4c5a408

📥 Commits

Reviewing files that changed from the base of the PR and between dcc02e0 and 29a59ec.

📒 Files selected for processing (5)
  • src/frontend/src/components/widgets/metric-views/metric-timeseries-chrome.tsx
  • src/frontend/src/components/widgets/metric-views/metric-timeseries-presentations.test.tsx
  • src/frontend/src/components/widgets/metric-views/metric-timeseries-table.tsx
  • src/frontend/src/components/widgets/metric-views/metric-timeseries-view.test.tsx
  • src/frontend/src/components/widgets/metric-views/metric-timeseries-view.tsx

Comment thread src/frontend/src/components/widgets/metric-views/metric-timeseries-table.tsx Outdated
Comment thread src/frontend/src/components/widgets/metric-views/metric-timeseries-table.tsx Outdated

@aleksdotbar aleksdotbar left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean up the walls of comments please.
Most of the code is self-explanatory and doesnt need a comment.

Four points from review.

A paging arrow towards a side with nothing on it is no longer rendered
at all. Hiding it with opacity left a real button in the tab order and
the accessibility tree, so a keyboard reader sitting at the first
column could reach "Show earlier columns" and press it to no effect.
The fade goes with it; an invisible control that answers is the worse
trade.

The resize observer watches the scrolling box as well as the table.
Both sides of the comparison move: the table when columns or rows
arrive, the box when the window or the card does. A box narrowing
around an unchanged table left the arrows and the full-height offer
stale.

Expansion is cleared with the overflow flag when the presentation
changes. A table let out to full height, swapped for the chart and
swapped back came up uncapped, and offered to collapse a table that
already fitted.

The note on `onVerticalOverflow` loses its second half, which explained
why the fact travels up instead of a ref — a road not taken, and not
visible from the code it sat above.

Signed-off-by: Alexey Panfilov <Alexey.Panfilov@constructor.tech>
@dzarlax

dzarlax commented Aug 12, 2026

Copy link
Copy Markdown
Contributor Author

All four applied in e9927f0.

Arrows towards an empty side are no longer rendered. The opacity
guard was the wrong tool — the button stayed in the tab order and the
accessibility tree either way. This one is now covered by a test: with
nothing overflowing, no paging control exists in the DOM at all. It
fails without the fix, since the old code rendered four hidden ones.

The observer watches the box as well as the table. Agreed on the
reasoning: a box that narrows around an unchanged table is exactly the
case the old code missed.

Expansion is cleared with the overflow flag on a presentation
change, in the same render-phase reset.

The note on onVerticalOverflow is trimmed — thanks, it was
justifying an approach that is not in the code, which is noise for
anyone reading the prop.

Verified in a browser afterwards: with no horizontal overflow and 107px
of vertical, exactly one control is rendered — "Show later rows" — and
no hidden ones sit beside it.

Two of the four still have no unit test, for the reason already in the
description: they turn on measured overflow, and every dimension is
zero in jsdom, so neither the arrows nor the full-height control can be
exercised there.

Full suite green (1092), typecheck and lint clean.

Comments that restated the code or recorded how it got here, removed.
The history is in the preceding commits and in git blame, and it ages
badly there: a note about a render loop describes a bug that no longer
exists.

What is left names a constraint the code cannot hold and someone would
otherwise undo — `scrollLeft` is negative in RTL, `model` must stay out
of the effect deps, the reset happens during render because the lint
rule forbids an effect, the arrows are rendered rather than hidden so
they stay out of the tab order.

Two magic numbers became names instead of prose: EDGE_SLACK_PX for the
sub-pixel tolerance and NEARLY_A_FRAME for the paging step.

Signed-off-by: Alexey Panfilov <Alexey.Panfilov@constructor.tech>
@dzarlax

dzarlax commented Aug 12, 2026

Copy link
Copy Markdown
Contributor Author

@aleksdotbar your note generalised, in 062021c.

Measured before touching anything: 105 of 352 added non-blank lines
were comments — 30%
. It is now 36 of 267, so about 13%.

Three kinds went out:

  • History. "This was a render loop once", "a fade was tried and does
    not work". It is in the preceding commits and in git blame, and it
    ages worst of all — a note about a loop that no longer exists.
  • Restatements. Five lines above SIDES.filter(side => more[side])
    explaining that only available sides render. The filter says it. This
    was your line.
  • Rationale for roads not taken, including the one you flagged.

What stayed names a constraint the code cannot hold and that someone
would otherwise undo: scrollLeft is negative in RTL, model must
stay out of the effect deps, the reset happens during render because
the lint rule forbids an effect, the arrows are rendered rather than
hidden so they stay out of the tab order.

Two numbers became names rather than prose — EDGE_SLACK_PX for the
sub-pixel tolerance, NEARLY_A_FRAME for the paging step — which is
the better half of the fix: the comment was standing in for something
that wanted a name.

No behaviour change. Full suite green (1092), typecheck and lint clean.

The new-code coverage gate failed at 71.7%: the scroll effect, the
paging step and the expand toggle were all untested, because each one
turns on a measurement jsdom cannot make.

Supplying the geometry is enough. Defining the container's scroll and
client dimensions, then dispatching a scroll, lets the real decisions
run — which side still holds something, how far a page moves, which
sign it takes — with only the absent layout engine stood in for. The
same applies to the card: the mocked table now reports overflow, so the
control it gates can be exercised.

The observer stub is scoped to the two tests that need it rather than
the file: charts in here measure themselves, and a no-op observer left
them with nothing to draw.

What stays uncovered is three `if (!box) return` guards, unreachable
while the ref is attached.

Signed-off-by: Alexey Panfilov <Alexey.Panfilov@constructor.tech>
@dzarlax
dzarlax requested a review from aleksdotbar August 12, 2026 12:31
@dzarlax
dzarlax enabled auto-merge August 12, 2026 15:03
@dzarlax
dzarlax added this pull request to the merge queue Aug 12, 2026
Merged via the queue into constructorfabric:main with commit f076e23 Aug 12, 2026
55 of 56 checks passed
@dzarlax
dzarlax deleted the fix/section-table-affordances branch August 12, 2026 16:07
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.

3 participants