Files
pad/web
xarmian e01c1581d0 fix(web): wire New Collection dashboard card to CreateCollectionModal (BUG-1332) (#481)
* fix(web): wire New Collection dashboard card to CreateCollectionModal (BUG-1332)

The "+ New Collection" tile in the workspace dashboard's Collections grid
was a plain <a href=".../settings"> link, so clicking it navigated to the
workspace Settings page instead of starting the create-collection flow.

Swapped the anchor for a <button> that opens the existing
CreateCollectionModal (same pattern Sidebar.svelte already uses for its
"+" affordance). The oncreated handler refreshes the dashboard so the new
collection card appears immediately without a full reload. Added a small
scoped reset on button.coll-card-new (font, text-align, cursor, width) so
it's visually indistinguishable from the sibling <a> cards.

* fix(web): gate dashboard New Collection on isOwner; refresh collectionStore per Codex review (round 1)

Codex review of round 0 surfaced two findings, both addressed here:

P2 — Owner gating: the New Collection dashboard trigger and modal were
shown to all viewers. The server requires owner role for collection
create (handlers_collections.go:48), and the settings page already gates
this modal behind isOwner. Without the gate, non-owners could open the
full create flow and only learn it's forbidden on submit. Wrapped both
the trigger button and the modal mount in {#if isOwner}, mirroring the
settings page's pattern.

P3 — Sidebar staleness: oncreated only refreshed dashboard-local state
via load(), so the Sidebar and quick-add (which read from
collectionStore) didn't see new collections until another refresh path
ran. Added a collectionStore.loadCollections(wsSlug) call alongside the
existing load(), matching Sidebar.svelte's own create-flow pattern.

* fix(web): keep CreateCollectionModal mounted across isOwner flicker per Codex review (round 2)

Round 1 wrapped the dashboard's CreateCollectionModal in {#if isOwner},
which Codex round-2 caught as a regression: the dashboard's 30s poll
(and any sync signal) calls load() → workspaceStore.setCurrent(), which
transiently clears currentMembership before /me resolves. isOwner flips
false during that window, unmounting the modal mid-edit and dropping any
form state the owner had typed.

Switched the modal-level gate from {#if isOwner} to {#if wsSlug} — the
same pattern Sidebar.svelte uses. The trigger button stays owner-gated
(UX boundary), and handlers_collections.go:48 remains the security
boundary, so this regression-free path preserves both protections.

* fix(web): cache isOwner so New Collection trigger doesn't flicker per Codex review (round 3)

Round 2 fixed the modal unmount race but left the trigger button reading
workspaceStore.isOwner directly, which Codex round-3 caught as a related
regression: the button itself disappears/reappears every 30s as the
dashboard's silent poll calls workspaceStore.setCurrent() → clears
currentMembership before /me resolves. Drops focus on the CTA every
refresh.

Cached the page-local isOwner via two effects per CONVE-606 (split
reactive-state sync from route-change effects):

  1. On wsSlug change → reset cached isOwner to false (so we never leak
     the prior workspace's owner status into the new workspace's CTA,
     and never flash owner-only UI before /me confirms).
  2. On workspaceStore.currentMembership change → only update when
     non-null. The transient null windows during silent refreshes are
     ignored, so the cached value (and the trigger's visibility) stays
     stable.

Server-side enforcement (handlers_collections.go:48) remains the
security boundary; this is purely a UX stability fix.
2026-05-10 20:28:29 -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