mirror of
https://github.com/PerpetualSoftware/pad.git
synced 2026-09-22 18:43:45 +00:00
c59132ad35
* feat(web): collection page search uses localSearch (TASK-1364)
Phase 3b of the local-first read model (PLAN-1343 / DOC-1342):
replace the collection page's debounced `api.search` round-trip with
`localSearch.search`, making the search box sub-millisecond.
- `handleSearchChange` now runs `localSearch.search(wsSlug, query,
{ collection, includeArchived, limit })` synchronously on every
keystroke. No debounce — local results are <1ms.
- `body:` / `content:` prefix routes the query back through the server
FTS endpoint (200ms debounce preserved) so the rich-text body stays
searchable. The local index intentionally excludes content per
DOC-1342 decision #4 — content lives on the server.
- A small `$effect` re-runs the local search when `showArchived` or
`indexReady` flip mid-typing, so cold-load + already-typed queries
surface results once the index hydrates, and the archived toggle
re-evaluates the active query.
- FilterBar input gets a `title=` tooltip documenting the `body:`
prefix UX.
- The existing client-side substring fallback (used when
`searchResultIds === null` and a query is typed) now covers two
narrow cases: server-FTS in-flight, and cold-load bootstrap window.
Parent: PLAN-1343. Phase 3b acceptance: keystroke → first results
<50ms P95 — local search runs synchronously inline with the
keystroke handler.
* fix(web): unified search effect handles URL-load body: queries per Codex review (round 1)
Codex round 1 P2: shared/reloaded URLs like `?q=body:foo` previously
only set `searchQuery` via `loadUrlFilters` without triggering the
dispatch, so the local fallback would search the literal string
`body:foo` against titles + fields instead of routing to server FTS.
Refactor: `handleSearchChange` is now a pure setter for
`searchQuery` + URL state. A single reactive `$effect` watches
`searchQuery`, `showArchived`, and `indexReady` and dispatches:
body-prefix → server FTS (debounced 200ms), else → synchronous
MiniSearch. This covers every entry point that mutates `searchQuery`
(typed input, URL load, programmatic clear) with one code path.
* fix(web): snapshot wsSlug/collSlug in search effect per Codex review (round 2)
Codex round 2 P2: navigating between workspaces or collections while a
`body:` query was in flight could land the old response on the new
route. Snapshot `wsSlug` / `collSlug` at effect-run time, route
`api.search` through the snapshots, and include them in the
stale-response guard alongside the query string.
* fix(web): refresh active search on localSearch mutations per Codex review (round 3)
Codex round 3 P2: while a search query was active, SSE-driven
`localIndex.upsert` / `applyDelta` would refresh the items list and
the underlying MiniSearch index, but the page's `searchResultIds`
Set stayed pinned to the original keystroke's results. A matching
row created after the query stayed hidden; an edited row that no
longer matched stayed visible until the user retyped.
Add a reactive per-workspace mutation epoch to `localSearch`:
`epoch(ws)` returns a SvelteMap-backed counter bumped by every
successful `rebuild` / `upsert` / `remove`. The collection page's
search-dispatch `$effect` reads it as a tracked dependency so the
search re-runs whenever the index changes. Server-FTS body queries
also benefit because the same effect path covers them.
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