Files
pad/web
xarmian 57995c5898 fix(sync): moved-out tombstones for cross-visibility collection moves (BUG-1675) (#670)
* fix(sync): emit moved-out tombstones for cross-visibility collection moves (BUG-1675)

/items-changes filtered deltas by an item's CURRENT collection, so an
item moving from a collection a restricted member can see into one they
can't vanished with no eviction signal — the stale, now-unauthorized
row lingered in their local cache until a full rebootstrap.

Server:
- store.ListMovedOutSince: finds items that changed since the cursor,
  are now outside the caller's visible scope, and have a 'moved'
  activity FROM a collection the caller CAN see. Returns id+seq only —
  no destination data leaks (the caller has read access to the source).
- handleListItemsChanges merges these in as moved_out tombstones, then
  seq-sorts + caps the combined stream so pagination stays gap-free.
- Bulk collection moves now log a proper 'moved' activity with from/to
  collection slugs (mirroring handleMoveItem) — the signal the
  tombstone query reads. Previously they logged generic 'updated'.

Client:
- ItemChangeRow gains moved_out; applyDelta hard-evicts those ids from
  RAM + search and queues the IDB delete into the SAME atomic
  cursor-advance tx (persistDelta gains removeIds) so it can't
  resurrect on warm boot.

Full members (nil visibility) skip the extra query entirely — the path
only runs for restricted members/guests.

Tests: store-level matrix (ListMovedOutSince), end-to-end restricted
member /items-changes tombstone, bulk-move 'moved' activity logging.

* fix(sync): tie moved-out tombstone to the move event's seq per Codex review (round 1)

Keying the tombstone on the item's CURRENT seq meant any later change
while it sat in a hidden collection re-emitted a moved_out row — leaking
that an invisible item keeps mutating, and never settling. Stamp the
post-move seq into the 'moved' activity metadata (both single + bulk
move paths) and key the tombstone on THAT seq: it fires once, for the
move that crossed the visibility boundary, and the cursor settles past
it. Moves logged before the seq stamp are skipped (evict on rebootstrap)
rather than risk the re-fire.

Test: re-fire regression (a post-move hidden-collection update must not
re-emit the tombstone).

* fix(sync): page moved-out tombstones by move seq, not current seq per Codex review (round 2)

Ordering/capping candidates by the item's current seq could strand an
item that moved out early (low move seq) but later churned in the hidden
collection (high current seq): it fell past the limit while the cursor
advanced beyond its move seq, never to be emitted again. Collect all
eligible rows, keep the earliest qualifying move per item, sort by move
seq, then apply the limit at a move-seq boundary so dropped rows
re-fetch cleanly on the next poll.

Test: 3 items move out ascending; the earliest churns to a high current
seq; limit=2 must still return the two smallest move seqs, then the
third on the next page with no gap.

* fix(sync): durable item_collection_moves table for moved-out detection per Codex review (round 3)

Moved-out detection read the 'moved' activity row, which is written
after the move commits and best-effort (errors discarded) — so a delta
poll racing the audit write, or a failed write, could advance the cursor
past the move seq and strand the unauthorized item forever.

Record every cross-collection move in a new item_collection_moves table
inside the SAME transaction as the move (MoveItemWithPreCheck), carrying
the workspace seq the move assigned. ListMovedOutSince now reads that
table — fully SQL/indexed (from_collection_id IN visible, MIN(seq) for
multi-hop, current-collection NOT IN visible), no JSON parsing, no
best-effort dependency. The 'moved' activity stays for audit only.

Migration 066 adds the table + indexes. Tests updated to rely on the
durable record (MoveItem writes it) rather than hand-logged activity.

* fix(sync): add Postgres migration for item_collection_moves per Codex review (round 4)

Postgres reads the separate pgmigrations/ tree, so the SQLite-only
migration 066 left item_collection_moves absent on PG deploys — every
cross-collection move would fail at the in-tx insert and moved-out
queries would error. Add pgmigrations/045 with the equivalent table +
indexes.
2026-05-30 19:57:45 -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