mirror of
https://github.com/PerpetualSoftware/pad.git
synced 2026-09-11 13:28:57 +00:00
7aef246dcd
GitHub #1228. Reviewing an agent-written doc meant many small comments, and every one cost a trip to the Activity tab and back. TASK-2294's own spec put an activity preview on the Details panel; it never shipped, and the comments being tab-only is the half that was left. Dave ruled the full move. One component cannot render in two DOM locations, so ItemTimeline stays the SINGLE owner — one fetch, one SSE subscription, one composer — mounted under the content on Details rendering comments, and mirrors its feed out through a new bindable `feed` prop. The Activity and Versions panels render that same feed through a second TimelineEntryList. - The mirror publishes the WHOLE feed, not the owner's rendered slice. The owner renders comments only, so publishing `visibleEntries` would leave both tabs permanently empty with nothing to report. Tested, and the one-word mutation fails it. - `loadMore` rides in the mirror: pagination is a property of the ONE feed, and a tab that can show older entries but not ask for them is a dead end. - The kind partition is three shared constants with an exhaustiveness check, not literals at the mount sites. A kind in none of them renders NOWHERE — which is how note/decision shipped invisible the first time (BUG-2301). Adding a kind to TimelineEntry without routing it is now a build error or a failing test rather than a silent hole. - The comments section carries its own {#key itemSlug}: it left the block that used to provide that remount, and dropping the guard would have been invisible. It wraps only the timeline — the collab editor must never be keyed. Five e2e specs asserted comments behind the Activity tab and are updated. attachment-lifecycle's tab round-trip is preserved deliberately: its claim is that the panel is CSS-hidden rather than unmounted, so it now goes out to Activity and BACK rather than asserting against a hidden panel. Gates: 117 files / 1987 tests pass, svelte-check 0 errors. Both new properties have negative controls — publishing the rendered slice fails 1, unrouting a kind fails 2. Claude-Session: https://claude.ai/code/session_011Q4b1iHtJtSyMs7BA2ySxo
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
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