Skip to content

Tables tab: columns collapse by the list's own width, stacked rows (#2555) - #2577

Merged
jh-RLI merged 1 commit into
developfrom
feature-2555-tables-collapse
Oct 2, 2026
Merged

jh-RLI merged 1 commit into
developfrom
feature-2555-tables-collapse

Conversation

@jh-RLI

@jh-RLI jh-RLI commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

Closes #2555. Slice 3 of spec #2551.

The tables tab now fits its width from a wide screen to a phone without sideways scrolling. Columns collapse by the list's own width, using container queries on .dash. As the list narrows, Topics drops first (and Created, once it exists), then Review and Datasets. On the narrowest lists each row stacks into two lines: title and ⋯, then Status, Publishable and Modified. Where the filter bar does not fit on one line, everything but Search folds behind "Filters (n)". Stacked rows have no column headers, so a "Sort by" select takes their place.

What changed

  • login/listing.py (generic, knows nothing about Tables):
    • Sort gains ascending/descending words.
    • ListPage.sort_options lists every sort in both directions, with the current one selected.
    • ListPage.folded_count counts the active filters behind the toggle (every one but free text).
  • login/tables_tab.py: the four sorts declare their words: "Table: A to Z", "Status: drafts first", "Review: not reviewed first", "Datasets: most first", and so on. Ascending stays "least done first".
  • list_sort_select.html (new, generic): the select, inside the swapped region, so it always shows the sort on screen. tables_tab.js rewrites its request the way it rewrites a filter's: every filter kept, page dropped.
  • user_tables.html:
    • The "Filters (n)" toggle and one fold panel that holds every control but Search.
    • On a wide list the panel is display: contents, so the controls sit in the row as before.
    • The toggle only flips aria-expanded, and the CSS reads it. A fold left open in a narrow window therefore hides nothing in a wide one.
  • tables_tab.css: the container queries, with the measurements beside them. Organization chips are capped at 11rem with an ellipsis; the full name is in title, and every holder is already in the link's aria-label. That bounds the Access column whatever an Organization is called (names may be up to 150 characters).
  • benchmarks/tables_tab/ (new): seed.py ports the WF-06 prototype's generator (accounts p90 = 130, max = 2,068 mixed, maxreal = 2,068 as measured). run.py measures the row cost; widths.mjs measures the column widths in headless Chrome.

Measurement 1: thresholds, on the real row partial, sidebar shown

Taken in headless Chrome 152. Each number is the narrowest list a set of columns fits, on the worst of the first six pages of each account. The page uses the system font stack, which falls back to DejaVu Sans on Linux. That is one of the widest fonts, so the numbers are conservative; I re-measured this slice's row with Segoe UI injected as a web font.

The thresholds are set for the row as it will be once #2560 and #2561 are in too. Both are built and open. Together they make the row 50 px wider: #2561 adds the ⋯ menu, and #2560 makes the Publishable header sortable. I measured that on a trial merge of all three branches (see "Merge note"). With thresholds set for this slice's row alone, a 1280 screen would scroll the table by 16 px as soon as both land.

columns this slice, DejaVu this slice, Segoe UI with #2560 + #2561, DejaVu threshold
every column 980 912 1,030 1,050
without Topics 863 803 913 930
also without Review, Datasets 651 617 701 720
filter bar on one line 958 902 958 960 (folds below)

What that gives with the sidebar:

screen list columns filter bar
1920 1,392 every column one line
1440 976 without Topics (Review kept) one line
1280 896 also without Review and Datasets folded
1024 640 stacked + Sort by folded
800 416 stacked + Sort by folded
390 342 stacked + Sort by folded

No sideways scrolling at 1920, 1440, 1280, 1024, 800 and 390 px. I checked each width on all three accounts, first six pages each, in both fonts, on this branch and on the trial merge with #2560 + #2561. The table never scrolls. The page scrolls at 1024 only, by 90–126 px in DejaVu and 0–3 px in Segoe UI, and that is the site navbar (see "Found, not fixed").

Departures from the issue, for you to rule on

  1. The thresholds are not the issue's 1,360 / 1,100 / 900. Those came from the prototype, whose cells were wider: 1,340 px for the whole row, against about 1,050 here. Keeping 900 would have stacked a 1280 laptop that has room for columns.
  2. The filter bar folds at its own width (960), not together with the stacking (720). On a single line the bar would wrap at 1280 while the rows stay columns. Folding by what the bar itself needs keeps it one line at 1440, as Tables tab: Review, Access, Topic, Tags and Dataset filters with chips and Reset #2556 tuned it, and folds it at 1280, where Tables tab: Review, Access, Topic, Tags and Dataset filters with chips and Reset #2556 expected the fold.
  3. User story 104 (seven columns, Review kept, at 1280–1440) holds at 1440 only. At 1280 the sidebar leaves the list 896 px. That is less than the 913 px the row needs with Store the Publish gate result: Publishable filter and sort #2560 + Tables tab: row action pipeline, with publish and unpublish from the row #2561, so Review and Datasets drop there.
  4. The thresholds are not set for the complete row. ☐ (Tables tab: selection, bulk bar and bulk publish/unpublish #2564), Modified (Tables tab: a real Modified date (data and metadata), default sort and range filter #2557) and Created (Tables tab: an honest Created date ('before Nov 2025' for older tables) #2559) do not exist yet. Measured with stand-ins for them, the complete row needs 1,294 / 1,047 / 835. Setting those now would leave four data columns at 1440 until those slices land. So each of them re-runs benchmarks/tables_tab/widths.mjs and moves the numbers; the CSS comment says so. Once they are in, Review also drops at 1440. Keeping it there would mean trimming about 90 px from the row, for example cell padding, the header sort arrows and the Table column's 13rem minimum. That is a design call, so it is not in this PR.

Measurement 2: server cost per row

Measured with python -m benchmarks.tables_tab.run, in the model-factsheet benchmark's style (throwaway database). Values are medians of 7 over the first 6 pages. Every request is 7 queries at every account size.

oemetadata per Table per row (marginal) building the rows rendering
6 KB 0.8–1.2 ms 0.6–1.0 ms 0.34–0.47 ms
60 KB 1.4–2.2 ms 1.3–2.1 ms 0.34–0.47 ms
500 KB 5.8–7.5 ms 4.9–8.1 ms 0.34–0.47 ms

The old cards cost 6–10 ms each. A row stays below 10 ms, so nothing was trimmed. The rendering cost is constant. What grows is the oemetadata the live Publish gate decodes in the page query: real documents range from a few KB to 500 KB. If that ever matters, the lever is to fetch only the license part of the document rather than all of it.

Checked in a real browser (headless Chrome, real htmx, sidebar shown)

On the p90 account at 390 px, starting on page 3:

  • Opening "Filters" reveals the controls.
  • Choosing "Not reviewed" drops page=3, and the toggle reads "Filters (1)".
  • "Sort by: published first" keeps the filter and focus returns to the select. Back to the default, sort leaves the URL.
  • The back button restores the earlier state.
  • Widening to 1920 with the fold left open hides nothing.
  • A header sort at 1920 shows in the select after narrowing to 800.

Merge note: #2560 (PR #2576) and #2561 (PR #2575)

I trial-merged both onto this branch and resolved them, as a throwaway branch that is not pushed. Whichever of the three lands later will see these conflicts:

On the merged tree: vitest is green (69 in login), the ⋯ menu opens in a stacked row at 390 px without overflow, and the widths are the "with #2560 + #2561" column above.

Found, not fixed (both predate this branch)

  • The navbar overflows at 1024 px, on every profile tab, not only this one: the user menu and "Sign Out" (.ms-auto) stick out 90–126 px. That belongs to the navbar rework (Feature 2025 rework oep homepage #2352).
  • The back button logs SyntaxError: Identifier 'reverseApiBaseUrl' has already been declared. htmx restores the page from its history cache and re-runs the inline const in base/templates/base/reverseUrl.html. The restored state is correct. Develop shows the same error on the same steps.

Tests

  • login/tests/test_tables_narrow.py (11 tests): the Sort by options, labels and selection, including an unknown sort; the select riding in the region; the order of controls behind the toggle; folded_count (stale values and Search do not count); data-folded on an htmx swap.
  • tables_tab.test.js (5 tests added): the Sort by request rewrite, focus back on the select, the toggle, "Filters (n)" following the region, and an open fold surviving a swap.
  • login: 248 tests green. vitest: 135 green. QueryCountTests and FilterQueryCountTests are unchanged: still 7 unfiltered, 12 with every option filter named.
  • Full suite on an isolated test DB: 1,142 run, 105 skipped (no Fuseki), one failure: the known oekg.tests.test_iri_resolution.LightImportTest, which cannot find the isolated settings module from its subprocess.
  • The thresholds have no automated test: happy-dom has no layout. They are measured, and the numbers sit beside the queries.

🤖 Generated with Claude Code

The list now fits its width without sideways scrolling. Container queries
on `.dash` measure the list, not the screen, so the profile sidebar's
share does not matter: Topics (and Created, once it exists) drop first,
then Review and Datasets, and on the narrowest lists each row stacks into
two lines (title and ⋯, then Status, Publishable and Modified). The filter
bar folds everything but Search behind "Filters (n)" where it does not fit
on one line; the toggle only flips aria-expanded and the CSS shows the
panel from it, so a fold left open by a narrow window hides nothing on a
wide one.

A stacked row has no column headers, so the region carries a "Sort by"
select: ListPage.sort_options, every sort in both directions with the
words each Sort declares for them ("Status: drafts first"). The module
rewrites its request like a filter's, keeping every filter and returning
to page 1. ListPage.folded_count counts the filters behind the toggle
(every one but free text) for the region's data-folded.

The thresholds are measured, not the spec's provisional 1,360 / 1,100 /
900: in headless Chrome on the real row partial, worst of the first six
pages of accounts with 130 and 2,068 Tables, sidebar shown, in DejaVu
Sans (the widest fallback of the system font stack; Segoe UI needs about
7 % less). This slice's row needs 980 / 863 / 651 px; with #2560's
sortable Publishable header and #2561's ⋯ menu, both built and open, it
needs 1,030 / 913 / 701, so the thresholds are 1,050 / 930 / 720 and
nothing scrolls whichever lands first. The bar needs 958 on one line and
folds below 960. So: every column at 1920, without Topics at 1440, also
without Review and Datasets at 1280, stacked at 1024 and below, bar
folded at 1280 and below. With stand-ins for the columns still to come
(☐, Modified, Created) the row needs 1,294 / 1,047 / 835; a slice adding
one re-measures with benchmarks/tables_tab/widths.mjs. Organization chips
are capped at 11rem with an ellipsis (full name in the title), so the
Access column is bounded whatever an Organization is called.

Server cost per row, measured with benchmarks/tables_tab (the factsheet
benchmark's style, throwaway database): 0.8-1.2 ms with 6 KB of metadata
per Table, 1.4-2.2 ms with 60 KB, 5.8-7.5 ms with 500 KB, against 6-10 ms
per old card. Rendering is 0.34-0.47 ms per row at any size; what grows is
the oemetadata the live Publish gate decodes. Still 7 queries per request.

Closes #2555

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@jh-RLI
jh-RLI merged commit 3f732d2 into develop Oct 2, 2026
5 checks passed
@jh-RLI
jh-RLI deleted the feature-2555-tables-collapse branch October 2, 2026 15:38
jh-RLI added a commit that referenced this pull request Oct 2, 2026
Brings in #2560 (stored Publish gate, PR #2576) and #2555 (collapse by
the list's own width, PR #2577).

Conflicts, all additive, both sides kept:
- tables_tab.css: #2555's container queries after this branch's menu,
  dialog and toast rules, so the stacked layout still overrides the
  sticky ⋯ cell.
- tables_tab.js: both sets of new constants; the header's bullet list
  joined into one.
- tables_region.html: both halves of the docstring; #2555's data-folded
  beside this branch's hx-get/hx-trigger.
- changelog: #2555 and #2560 first, then #2561.

Follow-up the merge required, beyond the conflicts:
- PUBLISH_GATE moved to dataedit.publish_gate, so the action service
  imports publish_checks from there instead of from login.tables_tab.
- The preflight now reads the stored verdict, as the spec asks: a stored
  pass is not validated again (publishing still validates live, and a
  Table failing there refuses the whole request, now naming that Table
  only); a stored fail or no verdict yet runs the checks live, which
  names the failed check and agrees with the Publishable cell, where live
  wins. Two tests pin both cases.

Full suite green bar the known LightImportTest artefact of the isolated
settings (green on its own); vitest 69 green.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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.

Tables tab: columns collapse by the list's own width, stacked rows on small screens

1 participant