Files
pad/web
xarmian c9f16a04ea feat(web): key/label split + slugification in collection modals (TASK-595) (#134)
* feat(web): add key/label split + slugification to collection modals

Closes TASK-595 in PLAN-593.

Replace the Create modal's stripped-down row form with the shared
FieldEditor component from TASK-594 and introduce a proper key/label
split with auto-slugification, duplicate detection, and reserved-key
validation.

New UI
- FieldEditor shows a muted monospace Key input under the Label input
  for new (unsaved) fields only. The key auto-syncs from the label
  via slugify(). Once the user edits the key manually (keyTouched),
  auto-sync stops and an inline hint explains that keys are immutable
  after save.
- Inline error message + red outline on the key input when the key is
  invalid or collides with another field. Create/Save buttons are
  disabled (with a reason tooltip) while any field has a blocking
  error.

Shared helpers in field-editor-types.ts
- slugifyKey(): lowercase, strip non-[a-z0-9_\s-], collapse whitespace
  and hyphens to underscores, trim, max 40 chars.
- RESERVED_FIELD_KEYS: UI-side reserved list mirroring top-level Item
  JSON fields (id, slug, ref, title, content, created_at, etc.) that
  would shadow core item properties and cause confusion.
- validateFieldKey(): structural validation (non-empty, starts with a
  letter, only lowercase+digits+underscore, not reserved, length).
- fieldFromDef(): hydrate an EditableField from a FieldDef preserving
  the key verbatim — used when loading templates or existing fields
  so slugify doesn't overwrite already-valid keys.

CreateCollectionModal
- Drops the { key, type, options: string } row form.
- Now renders fields via FieldEditor bound to EditableField[].
- Template selection marks loaded fields keyTouched=true so template
  keys stay intact. Duplicate / reserved / empty keys disable Create
  with a hover tooltip reason.
- Save uses field.key directly (no longer derives key from label at
  submit time).

EditCollectionModal
- New fields gain the same key/label split + validation. Duplicate
  detection runs against existing field keys + other new-field keys.
- hasNewFieldBlockingErrors gates the Save button with a tooltip.
- Existing-field behavior is unchanged: label editable, key frozen,
  no key row rendered.

Behavior / regression notes
- User-visible outcome for a typed-and-submitted collection is now:
  label "Target Date" -> key "target_date" (was "Target Date" in
  pre-T2 behavior). This is the intended fix.
- Templates continue to ship with their existing keys verbatim.
- Alignment of the type dropdown against the taller new-field card
  will be revisited in TASK-598 (visual redesign pass).

* fix(web): persist terminal_options on newly-created status fields

Codex P2 (PR #134): the Create modal and EditCollectionModal's new-
fields serialization paths copied key/label/type/options into FieldDef
but not terminal_options. Users could toggle terminal markings on a
status field in the FieldEditor, click Create (or Save), and have
those choices silently dropped — making terminal-dependent behavior
fall back to defaults and misclassify statuses.

Mirror the existing-fields branch that already handles this in
EditCollectionModal: when the field key is "status" and terminalOptions
is non-empty, filter to the options that survived in the saved set and
write them to def.terminal_options.

Key === "status" gating matches the current FieldEditor UI. T4
(TASK-597) will generalize terminal options to any select field and
the gate can be removed there.
2026-04-17 13:18:02 -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
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