diff --git a/docs/dashboard-tables.md b/docs/dashboard-tables.md new file mode 100644 index 0000000..72226f6 --- /dev/null +++ b/docs/dashboard-tables.md @@ -0,0 +1,70 @@ +# Dashboard tables + +How every data table on the dashboard is laid out, and the rules a new table +must follow so it never grows a horizontal scrollbar on a desktop screen. +Everything here is plain CSS in `src/web/tailwind.css` plus four cell classes; +there is no table library and no JavaScript involved. + +## The problem this solves + +A table used to be `min-width: max-content` inside a `.scroll-x` wrapper, and +most cells were `white-space: nowrap`. Any table whose natural width exceeded +the page column (80rem, ~1355px at the site's 110% scale) became a horizontal +scroller, even on a 2560px monitor, and the columns that mattered were hidden +behind the scrollbar. Wrapping columns were squeezed to their minimum while the +nowrap ones kept everything. + +## The three rules + +1. **Tables get the whole viewport, everything else keeps the page column.** + `.dashboard` is a CSS grid with two named column spans: `content` (at most + 80rem minus the page gutters, exactly what the header uses) and `wide` (the + viewport minus the same gutters). Every direct child sits in `content`; a + direct-child `.scroll-x` sits in `wide`. The table inside a wide wrapper is + `width: auto` (shrink-to-fit) with `min-width` equal to the content column + and `max-width: 100%`, centred. A table that fits the page column therefore + looks exactly as before; a table that needs more grows, centred, up to the + viewport edge; the page itself never scrolls sideways. + +2. **Columns wrap before the table scrolls.** The `min-width: max-content` rule + is gone. A table shrinks by wrapping its text columns down to their floors, + and only when the floors alone no longer fit (phones, mostly) does the + `.scroll-x` wrapper scroll. Headers always wrap. + +3. **A cell declares what it holds, and nothing else decides its width.** + There are exactly four cell classes: + + | class | use it for | behaviour | + | ------------ | -------------------------------------------------------------- | ---------------------------------------------------------------------- | + | *(none)* | short prose, names, tokens, badges | wraps at spaces | + | `num` | numbers, money, counts | right-aligned tabular figures; `td.num` never wraps, `th.num` may | + | `cell-tight` | timestamps, dates, ids, a single short token that must not split | `white-space: nowrap`. Nothing longer than ~20 characters | + | `cell-wrap` | titles, URLs, notes, messages, badge lists, anything free-form | floor 10rem, ceiling 32rem, `overflow-wrap: anywhere` | + + Anything long that must not wrap gets an inner block with a Tailwind clamp + (`
`) inside a `cell-wrap` cell; the cell + ceiling bounds the column, the clamp bounds the row height. Do not put + `truncate`/`line-clamp-*` on a `td` itself (a table cell cannot be a + `-webkit-box`), and do not use `table-fixed` on a page-level table (fixed + layout needs a definite width and is silently ignored with `width: auto`). + +## Checklist for a new table + +- Wrap it in `
` (add `tall` for a 70vh vertical cap) and + make that wrapper a **direct child** of `
` if the + table has more than a handful of columns. Tables inside `.card`, `.cards` or + `details` stay inside their box and just wrap/scroll there. +- Give every `td` one of the four classes above according to its content. + When in doubt, leave it unclassed: wrapping is the safe default. +- Never `cell-tight` a name, a title, a reason sentence or a free-text field. +- Check the page at 390, 1280 and 2560px. At 1280 and above the wrapper's + `scrollWidth` must equal its `clientWidth`; at 390 a wide table is allowed to + scroll. + +## Measuring + +`docs/plans/briefs/dashboard-tables/measure.mjs` logs in as the dev-seed admin, +visits every dashboard page at several widths and prints, per page, whether the +document overflows and which table wrappers scroll. Run it against a seeded +dev server (`cargo run --example seed_dev_db -- ./dev`) before and after a CSS +change; the "scrolling" count for widths ≥ 1280 should be zero. diff --git a/docs/plans/briefs/dashboard-tables/00-shared.md b/docs/plans/briefs/dashboard-tables/00-shared.md new file mode 100644 index 0000000..e35f4f6 --- /dev/null +++ b/docs/plans/briefs/dashboard-tables/00-shared.md @@ -0,0 +1,50 @@ +# Shared brief — dashboard tables (2026-09-20) + +The operator sees horizontal scrollbars on most dashboard tables, even on an +ultrawide monitor, because every table is capped at the 80rem page column and +`min-width: max-content` forbids wrapping. The fix is a small, reusable table +framework described in **`docs/dashboard-tables.md` — read it first; it is the +spec.** Two tasks run in parallel in separate git worktrees: + +- `01-css.md` — the CSS side (`src/web/tailwind.css`, rebuilt `app.css`). +- `02-templates.md` — the template audit (every `td` gets the right cell class). + +## Ground rules + +- Work only in the worktree named in your brief. **Never touch the main checkout** + at `/home/thallada/workspace/the-daily-epub`. **Do not `git commit`** (the sandbox + cannot write `.git/worktrees`); leave the work in the tree, the orchestrator + commits. +- Styling lives in **`src/web/tailwind.css`** (Tailwind v4, `@apply` inside + `@layer components`, semantic colour tokens). After every CSS change run + `npm run css` (writes `src/web/static/app.css`, which the binary embeds with + `include_str!`). `npm run css:check` must print no diff at the end. +- CSP forbids inline `