Files
pad/web
xarmian 9768655103 feat(web): virtualize BoardView cards via content-visibility (TASK-1347) (#489)
* feat(web): virtualize BoardView cards via content-visibility (TASK-1347)

Per-column virtualization for the kanban board, mirroring the
approach landed for ListView in TASK-1346. Adds
`content-visibility: auto; contain-intrinsic-size: auto 80px;` to
`.card-wrapper` so the browser skips layout/style/paint work for
cards that have scrolled out of their column's viewport.

`.column-cards` is itself `overflow-y: auto`, so content-visibility's
near-viewport check uses the column as its frame — naturally
per-column. Cards stay mounted so:

  - svelte-dnd-action keeps every drop target in the DOM for
    drag-between-columns + drop-into-empty-column hit-testing
  - column horizontal scroll + column reorder (native HTML5 DnD on
    `.kanban-column`) are unaffected
  - keyboard focus on an off-screen card still resolves via
    querySelector and scrollIntoView rehydrates paint

Intrinsic size is `80px` (vs. ListView's `60px`) because board cards
render with `compact={true}` — they stack status + tags taller than
the list row's single-line layout. The `auto` keyword caches the
real measured height after first paint so subsequent scrolls don't
reflow.

Parent: PLAN-1343.

* fix(web): explicit overflow:visible + spec citation on board card-wrapper per Codex review (round 1)

Codex round 1 [P2] worried that `content-visibility: auto` on
`.card-wrapper` would apply paint containment that clips ItemCard's
`.pr-badge` (positioned at `right: -6px`, deliberately protruding
past the card's right edge).

Per CSS Containment Module Level 2 §4 ("content-visibility"),
`content-visibility: auto` applies paint containment ONLY when the
element is "not relevant to the user" — off-screen, when the badge
isn't being painted anyway. On-screen elements receive only layout
containment, which does not clip ink overflow.

Even so, declaring `overflow: visible` explicitly is the cheapest
defense against a future style sweep that might silently add
`overflow: hidden` to wrappers, and pairs naturally with the
in-source comment citing the spec. Codex's concern is now
documented, addressed, and auditable.

No behavior change for spec-compliant browsers — the badge
already rendered correctly on-screen. The explicit declaration
makes the contract self-describing.

* fix(web): use overflow-clip-margin:6px to preserve PR badge protrusion per Codex review (round 2)

Codex round 2 [P2] correctly pushed back on round 1's spec reading:
per CSS Containment L2 §3.4 / §4, `content-visibility: auto` applies
paint containment continuously (including on-screen), and paint
containment clips ink overflow regardless of an explicit
`overflow: visible` declaration (overflow:visible is treated like
overflow:clip at used-value time when paint containment is active).

The proper fix is `overflow-clip-margin: 6px` — a CSS property
specifically designed to extend the paint-clip rectangle past the
element's content box by a fixed margin, without affecting layout.
6px matches `.pr-badge`'s outward offset (`right: -6px`) exactly,
so the badge renders unchanged from the pre-virtualization layout.

Browser support for overflow-clip-margin is identical to
content-visibility:auto (Chrome 90+, Firefox 102+, Safari 16.4+) —
every browser that ships the virtualization also ships the escape
hatch. Older browsers ignore both properties and render
unvirtualized (which is also correct).

Comment in the file now cites the actual spec section and the
correct mental model — no more "auto applies paint only off-screen"
misreading.

* fix(web): grow overflow-clip-margin to 12px for badge shadow + hover per Codex review (round 3)

Codex round 3 [P3] caught that 6px covered the badge's border-box
offset (`right: -6px`) but not the ink overflow from
`box-shadow: 0 1px 3px` (~3px blur) and the hover
`transform: scale(1.05)` (~2px growth at typical badge widths).
12px covers offset + shadow + hover with a small safety margin.
2026-05-11 10:51:31 -04:00
..
2026-03-26 01:52:36 +00:00
2026-03-26 01:52:36 +00:00
2026-03-26 01:52:36 +00:00
2026-03-26 01:52:36 +00:00
2026-03-26 01:52:36 +00:00
2026-03-26 01:52:36 +00:00

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