mirror of
https://github.com/PerpetualSoftware/pad.git
synced 2026-10-03 12:10:31 +00:00
f699d3480e
* feat(web): virtualize TableView rows via content-visibility (TASK-1348)
Flat-row variant of the approach landed for ListView in TASK-1346
and BoardView in TASK-1347. Adds
\`content-visibility: auto; contain-intrinsic-size: auto 36px;\`
to \`tbody tr\` so the browser skips layout/style/paint work for
rows that have scrolled out of the table's viewport.
CSS Containment L2 §4.4 historically treated layout/paint
containment as a no-op on table-row elements, but Chrome 122+
(March 2024) and follow-on Firefox / Safari releases lifted that
limitation for content-visibility specifically. The rule is
therefore opportunistic: modern browsers get virtualization,
older engines treat it as a no-op and render unchanged.
Preserved behavior:
- Sticky thead header (position: sticky; top: 0; on <th>) lives
in <thead> and is unaffected by per-row paint skipping.
- Column sort (toggleSort()) lives entirely in <thead> button
handlers — also untouched.
- No DnD on this view, so no drop-target preservation work.
- No protruding badges or absolute-positioned overflow content
inside rows, so no overflow-clip-margin escape hatch needed
(cf. PR #489's pr-badge handling on BoardView).
\`contain-intrinsic-size: auto 36px\` matches the actual data-row
height (var(--space-2) padding × 2 + ~20px line-height). The
\`auto\` keyword caches measured heights so rows with progress
bars or wrapped titles keep their natural sizing on re-entry.
Parent: PLAN-1343.
* fix(web): refactor TableView to CSS Grid so content-visibility actually applies per Codex review (round 1)
Codex round 1 [P2] correctly flagged that `content-visibility: auto`
on `<tr>` is a no-op: CSS Containment L2 §4.4 makes layout/paint
containment inactive on internal table boxes, and content-visibility
depends on size containment which is also no-op for table rows. So
the original PR shipped CSS that did nothing — the table-row branch
of CSS Containment defeats the trick that worked for ListView (PR
#488) and BoardView (PR #489).
Refactor: replace `<table>/<tr>/<td>` with `<div role="table">` /
`<div role="row">` / `<div role="cell">` and lay them out with CSS
Grid + subgrid. ARIA roles preserve assistive-tech semantics. Each
row is no longer an "internal table box," so content-visibility +
size containment apply normally.
The grid template is built dynamically because visibleFields depends
on the collection schema:
grid-template-columns: 70px minmax(200px, 1fr) auto* 90px
Each row sets `grid-template-columns: subgrid; grid-column: 1 / -1;`
so cells align across rows perfectly. Subgrid lands in Chrome 117+ /
Firefox 71+ / Safari 16+; an `@supports not (subgrid)` fallback
inherits the parent's grid template instead.
Preserved behavior:
- Sticky header — `.table-header { position: sticky; top: 0; }` is
on the first row (no `<thead>` anymore, but the role is the same).
- Column sort — `toggleSort()` logic unchanged.
- Column widths — fixed Ref (70px) / Updated (90px) bracket
minmax title + auto fields, matching the pre-refactor layout.
- Progress bar inside title cell — `.col-title` uses
`flex-direction: column` to stack title + progress.
- Hover state — `.table-row:not(.table-header):hover` keeps the
row-level hover behavior.
Virtualization rule (the actual point of the PR):
.table-row:not(.table-header) {
content-visibility: auto;
contain-intrinsic-size: auto 36px;
}
The header is excluded so sticky positioning isn't fought by paint
skipping.
Parent: PLAN-1343.
Pad Web UI
SvelteKit 2 + Svelte 5 frontend for Pad, compiled to static files and embedded into the Go binary.
Development
npm install
npm run dev # Dev server at localhost:5173 (proxies API to localhost:7777)
npm run build # Production build to build/
npm run check # Type checking with svelte-check
When developing, run the Go backend separately with make dev from the project root.
Building for Production
Do not build in isolation. Always use make build from the project root — this builds the web frontend, then compiles the Go binary with the build output embedded via //go:embed.
Stack
- Svelte 5 with runes (
$state,$derived,$effect) - SvelteKit 2 with
adapter-static(SPA mode) - Tiptap block editor with markdown round-trip
- svelte-dnd-action for drag-and-drop in board/list views
- SSE for real-time updates
- TypeScript throughout
Structure
src/
routes/ SvelteKit pages
+layout.svelte App shell (sidebar + main)
+page.svelte Landing/redirect
[workspace]/
+page.svelte Dashboard (collections, phases, activity)
+layout.svelte SSE connection per workspace
[collection]/
+page.svelte Collection view (board/list)
[collection]/[item]/
+page.svelte Item detail + editor
conventions/ Purpose-built conventions page
playbooks/ Purpose-built playbooks page
settings/ Workspace settings
lib/
api/client.ts HTTP API client
components/
layout/ Sidebar, navigation
editor/ Tiptap editor, raw markdown editor
fields/ FieldEditor, relation picker
items/ ItemCard, ItemDetail
collections/ BoardView, ListView
common/ StatusBadge, badges, modals
search/ CommandPalette
activity/ ActivityFeed
stores/ Svelte 5 reactive stores
workspace.svelte.ts Workspace state
collections.svelte.ts Collection + item state
ui.svelte.ts Sidebar, mobile state
types/index.ts TypeScript types and constants
app.css Global styles and design tokens