Files
pad/web
xarmian c59132ad35 feat(web): collection page search uses localSearch (TASK-1364) (#509)
* 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.
2026-05-11 22:58:09 -04:00
..
2026-03-26 01:52:36 +00:00
2026-03-26 01:52:36 +00:00
2026-03-26 01:52:36 +00:00
2026-03-26 01:52:36 +00:00
2026-03-26 01:52:36 +00:00
2026-03-26 01:52:36 +00:00

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