mirror of
https://github.com/PerpetualSoftware/pad.git
synced 2026-09-22 18:43:45 +00:00
1af63e3a47
* feat(web): wire ContentSkeleton into item detail loading + collab-sync (TASK-1375)
Two gaps where the item detail page would show a blank body:
1. HTTP-load gap — `{#if loading}` rendered the literal string
"Loading..." while loadData()'s GET was in flight. Now renders
<ContentSkeleton variant="page" />.
2. Collab-sync gap — for editable items, the Editor mounted on
`ydoc !== null` but content lives in the Y.Doc which starts
empty until the WS replays the op-log. On slow/broken
connections this looked indistinguishable from a genuinely
empty item. The {:else if ydoc} branch now renders
<ContentSkeleton variant="inline" /> while
`collabProvider.state === 'connecting' && !hasEverSynced`.
The `hasEverSynced` latch (split into two $effects per CONVE-606
— route-change reset on item.id vs reactive-state-sync on
collabProvider.synced) ensures mid-session `reconnecting` does
NOT re-show the skeleton over already-rendered content. The
reset on item navigation handles SvelteKit's reuse of
+page.svelte across [slug] changes.
Error UI and retry (offline state, stuck-connecting timeout) is
TASK-1376's scope — this PR is skeleton-only.
Parent: PLAN-1373. Resolves part of BUG-1372.
* fix(web): reset hasEverSynced on provider change, not just item.id (TASK-1375 round 1)
Per Codex review: the original `void item?.id` reset dependency
missed two cases where the same item gets a fresh unsynced Y.Doc:
1. Raw -> Rich mode toggle (collabKey derives from rawMode, so
the collab $effect tears down + rebuilds the provider but
item.id is unchanged).
2. forceRefreshNonce bump (server force_refresh or a future
TASK-1376 retryCollabSync), which rebuilds the provider
against the same item.
In both cases hasEverSynced=true survived the rebuild, the
skeleton gate bypassed, and the editor mounted on the new
unsynced Y.Doc showing blank — re-opening the very pre-sync
empty window the skeleton was meant to close.
Fix: depend on `collabProvider` (the reactive variable, not its
fields). Any provider instance change — navigation, mode toggle,
nonce bump, cleanup-to-null — fires the reset. Strictly more
correct than item.id since it also covers same-item rebuilds.
Codex round 1.
* fix(web): null editorInstance on provider change (TASK-1375 round 2)
Per Codex review round 2: `editorInstance` is set by the Editor's
`onEditor` callback on mount but is NOT re-nulled on unmount. During
the connecting-skeleton window for a same-item provider rebuild
(rawMode toggle, force_refresh), the old <Editor> unmounts but
`editorInstance` still points at the previous (now-destroyed)
instance.
If an `applier_request` frame arrives on the new provider during
that window, `onApplierRequest` would call `setContent` on the
WRONG editor (or a destroyed one) instead of returning false and
letting the server fall back to a direct items.content write.
Fix: null `editorInstance` in the same reset $effect that resets
`hasEverSynced`. The new editor's onEditor callback re-populates
the reference once it mounts after the skeleton phase.
Codex round 2.
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