mirror of
https://github.com/PerpetualSoftware/pad.git
synced 2026-10-26 22:37:03 +00:00
4ac7f16084
* feat(web): gapped sort keys, so a reorder is one write (TASK-3525) Lane order was dense integers, so a drop between two cards renumbered one side of the lane: up to ~510 rows (and ~510 'reordered' activity rows) for a middle drop in a 1,020-card lane. The planner now keeps the longest run of cards already in order and writes every other card into the gap between its kept neighbours: 1,024 past it at an end, spread evenly in the middle. Moving one card in a gapped lane is one write. With no room (a dense lane from before this, a never-dragged lane of 0s, or a gap closed by repeated drops in one spot) it shifts the shorter side as before, but 1,024 apart, so the next drops are single writes again. A plan leaving +/-2^30 re-spaces the lane around 0. Client only: the server stores whatever integers it is sent, so no schema, API or MCP change, and API writers keep working with any values that sort. The roles board shares the planner and gets the same behaviour. Also: the timeline says "moved up/down" instead of raw sort values; the share page's comment about its order is corrected (it is ListItems' default, not sort_order); CLAUDE.md says a copy does not carry sort_order; the CLI's --sort-order help says it is a key, not a position. Claude-Session: https://claude.ai/code/session_01Q9WssCjFuHjSfLABoW4tmS * fix(web): a reorder writes the card the user moved when that is enough (TASK-3525) A one-write plan can often write either card of a swap, and each written card gets the 'reordered' activity row. The board, list, table, child list and roles board now hand the planner the moved card's id, and it is written alone whenever every other card already sorts and its gap has room. The identity-gate rows for handleReorder and handleDndFinalize are re-reviewed: the id is a synchronous planner argument, nothing moved. Claude-Session: https://claude.ai/code/session_01Q9WssCjFuHjSfLABoW4tmS * fix(web): the reorder row shows a direction only for two differing numbers (TASK-3525, codex) Claude-Session: https://claude.ai/code/session_01Q9WssCjFuHjSfLABoW4tmS
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