mirror of
https://github.com/PerpetualSoftware/pad.git
synced 2026-09-24 11:26:34 +00:00
4b2fbe55df
Final full-diff review of PLAN-2392 phases 1/1b found four issues the per-task reviews structurally could not see. P2 — the strip fenced its delete mutation on the LOAD generation, which a Retry also bumps. A delete failure landing during a same-item Retry was therefore mistaken for an A→B item switch and silently swallowed: no rollback, no toast, no 404 broadcast. Split the single counter in two — `loadGeneration` (per request, bumped by every effect run including Retry) still fences load responses; a new `viewGeneration` (bumped only when the view actually changes, plus onDestroy) fences mutations. The effect cleanup deliberately bumps only the request generation, since it also runs before a Retry's re-run. View identity is the (workspace, item) pair, not the item: `wsSlug` is reactive and the strip survives a workspace change, so an item-only key would read that change as a same-item Retry. `viewKey()` now backs the retry marker, the painted-error owner, and the mutation fence alike. Exposed by the new test: the rollback splice was not idempotent. A reload that restored the row while the delete was in flight made it duplicate the id and the keyed each block threw. The rollback now skips the re-insert when the row is already back, and still toasts. P2 — Storage's Retry now performs the same attachment metadata-cache invalidation the strip's does, so a failed HEAD cached during the outage doesn't stay poisoned on chips and inline images after recovery. While there, loadList captures and re-checks its request workspace: the generation alone couldn't tell a superseded workspace from the current one, and both the rows and the cache keys are workspace-scoped. P3 — StorageTab's `anyFilterActive` re-listed the filter fields instead of reusing `selections()`; one projection now, so they cannot drift. P3 — the deletion tombstone set survives a Retry and rides a workspace-wide bus, so it is bounded like the list buffers, shedding oldest-first (the newest tombstone is the one still racing a response) and refreshing an id's age when it is re-announced. Claude-Session: https://claude.ai/code/session_01LmbFxQFDjcYKBLcTnor6DC
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