Files
pad/web
xarmian 89ae5369ae feat(web): settings page exports .tar.gz bundle (TASK-892) (#309)
* feat(web): settings page exports .tar.gz bundle (TASK-892)

Replace the legacy "Download JSON" button on the workspace
settings page with a single "Download .tar.gz" link that hits the
existing ?format=tar dispatch on handleExportWorkspace. The bundle
ships items + comments + version history + attachment blobs +
manifest in a single archive — same shape the CLI's
'pad workspace export' command produces.

Behavior:

- Field label changed from "Export" to "Export bundle"
- Button text changed from "Download JSON" to "Download .tar.gz"
- href appended ?format=tar
- download attribute changed from {slug}-export.json to
  {slug}-export.tar.gz
- Added a title= tooltip explaining the bundle contents and that
  it's re-importable via the Create Workspace dialog

No JSON-export UI surface remains in the settings page. The legacy
JSON path on the server side stays for back-compat (any operator
still hitting /export with no query keeps getting JSON).

Parent: PLAN-890. Sibling task TASK-893 will flip the import
modal to consume .tar.gz so the round-trip closes.

* feat(web): import workspace bundle (.tar.gz) in CreateWorkspaceModal (TASK-893)

Folded into the same PR as TASK-892 because Codex (correctly) flagged
that exporting .tar.gz while still importing JSON ships a half-baked
state — the settings page tooltip even tells users the bundle is
re-importable via this modal. Now it actually is.

Changes in CreateWorkspaceModal.svelte:

- importWorkspace() now calls api.workspaces.importBundle(file, name)
  instead of reading + JSON.parse-ing the file and POSTing through
  api.raw.post. The new method sets Content-Type: application/gzip
  and posts the raw File body, which the server's existing dispatch
  in handleImportWorkspace routes to the bundle path
  (handlers_workspaces.go:361).
- File picker accept attribute changed from ".json" to
  ".tar.gz,.tgz,application/gzip,application/x-gzip" — UI advertises
  only the new format.
- Drag-drop guard accepts .tar.gz, .tgz, AND .json (legacy
  back-compat — server still supports JSON imports for any operator
  with an old archive lying around, even though we don't advertise
  it).
- Drop-zone hint and import explanatory text updated to mention the
  bundle format and what's preserved (items, comments, attachments,
  version history).
- Auto-fill regex strips -export.tar.gz, .tar.gz, .tgz, AND .json
  suffixes when seeding the workspace name from the filename.

New api.workspaces.importBundle method in web/src/lib/api/client.ts:

- Bypasses the JSON-only `request` helper — sets Content-Type:
  application/gzip and posts the File body raw.
- Handles CSRF token, 401 redirect, and shaped error responses the
  same way `request` does.
- Mirrors the CLI's `pad workspace import <bundle.tar.gz>` flow.

Server-side: no changes — handleImportWorkspace dispatches on
Content-Type and the bundle path was already audited + hardened in
PR #308.

Parent: PLAN-890. Closes the import/export round-trip alongside
TASK-892. TASK-894 (Playwright e2e) covers the round-trip.

* fix(web): drop .json from import accept list per Codex review (round 2)

Codex P2 on PR #309: I left .json in the drag-drop guard
isAcceptedBundleFile, intending to be lenient for users with legacy
JSON exports. But api.workspaces.importBundle always POSTs as
Content-Type: application/gzip — so a dropped .json file would
route to the server's bundle path and fail with a gzip decode
error. Confusing UX.

Make the modal strictly tar.gz-only:

- isAcceptedBundleFile regex narrowed to /(\.tar\.gz|\.tgz)$/i
- name auto-fill regex narrowed to strip only -export.tar.gz, .tar.gz,
  .tgz suffixes
- Comment documents that operators with legacy JSON exports can
  still curl them against POST /workspaces/import directly — the
  server keeps the JSON dispatch for back-compat.

The file picker accept attribute was already strict (.tar.gz, .tgz,
application/gzip, application/x-gzip) — this commit makes the
drag-drop path consistent with it.

Parent: PLAN-890.
2026-04-29 20:34:42 -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