Skip to content

Fix paginated table height and mobile scrolling - #3871

Draft
laurakwhit wants to merge 3 commits into
mainfrom
fix/table-height
Draft

Fix paginated table height and mobile scrolling#3871
laurakwhit wants to merge 3 commits into
mainfrom
fix/table-height

Conversation

@laurakwhit

@laurakwhit laurakwhit commented Aug 29, 2026

Copy link
Copy Markdown
Collaborator

Description & motivation 💭

Paginated tables now fill the available viewport height on desktop and scroll with the page on mobile

Previously

PaginatedTable sized itself with max-height: calc(100vh - var(--layout-pt) - ${tableOffset}px), where tableOffset = tableContainer.offsetTop + 32.

  1. offsetTop was a plain DOM read inside $derived, so it was measured once at mount and never recomputed — not on resize, not when the filter bar wrapped
    to a second line.

  2. It compared against 100vh instead of the actual scroll container.

  3. + 32 hardcoded the desktop p-8 bottom padding, which was p-4 on smaller screens.

  4. height: calc(100% - var(--table-header-h)) on the empty state resolve to auto, so empty states collapsed to content height.

What changed

Height comes from flexbox now, not JS. The chain from the app shell down to the table container is a definite-height flex column, so the browser does the arithmetic and reflows on resize for free.

Empty states fill the table. The original calc() works now that the container has a definite height, and consumers' h-full empty wrappers fill the page.

Mobile scrolls the page, not the table. The table grows to its content and the page scrolls, while horizontal scroll stays inside the table for wide column sets.

Footer controls wrap. Page size and action icons share a row, pagination gets its own.

verticalScroll prop replaces the implicit maxHeight="100%" signal for the maximized view. 'table' means the body scrolls and the footer pins; the default 'responsive' scrolls the page on mobile and the body from md up.

Top nav height matches its token. The nav is now sized by --top-nav-height instead of the token merely describing it. This also fixes three pre-existing sticky sub-headers (workflow-history-layout,workflow-timeline-layout, workflow-family-tree) that offset by the token and were sitting 1px high, leaving a sliver of scrolled content under the nav.

Inline style became style: directives. stylelint --fix silently corrupts a Svelte style attribute that begins
with an interpolation, appending the property name on every run and killing the declaration after it. It had already eaten scroll-padding-bottom. Any .svelte file with style="{... is exposed.

Known tradeoffs

  • Sticky column headers are lost on mobile. sticky anchors to the nearest scrollport, and the overflow-auto we need for horizontal scroll makes the container one.
  • The footer isn't vertically sticky on mobile. A sticky one collides with the fixed bottom nav.

Screenshots (if applicable) 📸

Design Considerations 🎨

Testing 🧪

How was this tested 👻

  • Manual testing
  • E2E tests added
  • Unit tests added

Steps for others to test: 🚶🏽‍♂️🚶🏽‍♀️

Checklists

Draft Checklist

Merge Checklist

Issue(s) closed

Docs

Any docs updates needed?

laurakwhit and others added 2 commits August 28, 2026 20:19
The table sized itself with
`calc(100vh - var(--layout-pt) - ${tableContainer.offsetTop + 32}px)`.
`offsetTop` is a plain DOM read inside `$derived`, so it was measured once at
mount and never recomputed on resize or when the filter bar wrapped; it
compared against `100vh` rather than the scroll container; and the `+ 32`
hardcoded the desktop `p-8` padding. The residual left a permanent 1px page
scrollbar. The same indefinite-height chain made the empty state's
`calc(100% - var(--table-header-h))` resolve to `auto`, so empty states and
callers' `h-full` empty wrappers collapsed to content height.

Give the chain from the app shell to the table container a definite height so
flexbox does the arithmetic and reflows on resize for free, and drop the
measurement code.

- Fill the available height on desktop; free space now falls between the last
  row and the pagination controls rather than below them.
- Scroll the page rather than the table on mobile, while keeping horizontal
  scroll inside the table for wide column sets. The footer scrolls with the
  page instead of colliding with the fixed bottom nav, but stays pinned
  horizontally so it cannot scroll out of view sideways.
- Wrap the footer controls: page size and actions share a row, pagination takes
  its own, with nothing clipped down to 320px.
- Add a `verticalScroll` prop to replace the implicit `maxHeight="100%"` signal
  for the maximized view. `maxHeight` is a plain passthrough again.
- Use `style:` directives instead of an inline `style` attribute. `stylelint
  --fix` corrupts a Svelte `style` attribute that begins with an interpolation,
  appending the property name on each run, which had already killed
  `scroll-padding-bottom`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 29, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
holocene Ready Ready Preview Aug 29, 2026 6:34am

Request Review

@laurakwhit laurakwhit changed the title Fix/table height Fix paginated table height and mobile scrolling Aug 29, 2026
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