Files
pad/internal/server
xarmian d6894def4f feat(web): collection page fetches via skinny /items-index endpoint (TASK-1349) (#491)
* feat(web): collection page fetches via skinny /items-index endpoint (TASK-1349)

Replaces every \`api.items.listByCollection(ws, coll)\` call in the
collection page with \`fetchSkinnyItems(ws, coll, includeArchived)\`,
which calls the local-first \`/items-index\` endpoint (TASK-1344)
through the typed client wrapper (TASK-1345). Items now ship
without the rich-text \`content\` body — the bulk of the per-row
wire size — until the user opens an item detail page, which still
goes through its existing full-item fetch.

Call sites updated:
  - loadCollection — primary load + plans-names lookup
  - SSE handler for item_created / item_archived / item_restored / item_updated
  - Sync coordinator's full-refresh fallback

The skinny rows are widened to \`Item[]\` at the boundary by setting
\`content: ''\` on each row. This keeps the existing view component
type contract unchanged and means existing call sites that read
\`item.content\` see an empty string — already a "nothing to do"
sentinel in the markdown-checklist progress branch.

Documented regression — out of scope for this task: non-plans
collections used to display checklist progress derived from item
content's markdown checkboxes. With \`content\` no longer fetched
for the list view, that progress no longer appears. Plans
progress is unaffected (uses /plans-progress, not content
parsing). Re-introducing the feature requires either server-side
progress on the index endpoint or a separate lazy fetch — a
follow-up rather than a blocker for the bandwidth win.

In-scope behavior preserved:
  - Item create/update flow: server still returns full items, dropped
    into the array as-is; sync coordinator's incremental updates
    similarly use the full-item type from the changes feed
  - Server-side FTS search via \`searchResultIds\`: still id-keyed,
    works against skinny rows
  - List / Board / Table view components: already only read fields
    present on the skinny row (title, fields, tags, sort_order…)
  - Detail page fetch: unchanged — still goes through
    \`api.items.get\` which returns the full Item with content

Parent: PLAN-1343.

* fix(api+web): add /collections/{coll}/checkbox-progress endpoint to preserve list-view checklist progress per Codex review (round 1)

Codex round 1 [P2] flagged that the original PR shipped a real
regression: non-plans collections used to compute markdown-checkbox
progress client-side from `item.content`, and the skinny
`/items-index` endpoint dropped `content` from the payload — so
list/board/table progress badges silently stopped appearing on
docs/tasks/custom collections.

This commit closes that gap with a new server endpoint that
computes the same `{item_id, total, done}` counts via
LENGTH/REPLACE arithmetic on the stored content, returning only
the small derived counts. No item bodies cross the wire.

Server (Go):
  - `store.CollectionCheckboxProgress(workspaceID, collectionID)` —
    SQL: `(LENGTH(content) - LENGTH(REPLACE(content, '- [ ]', '')))
    / 5 + (LENGTH(content) - LENGTH(REPLACE(content, '- [x]', '')))
    / 5` for total, the second clause alone for done. Same trick on
    SQLite and PostgreSQL.
  - `handleCollectionCheckboxProgress` — collection-visibility +
    item-grant filter so guests / restricted members can't enumerate
    items they shouldn't see. Mirrors `guestResourceFilter` exactly.
  - Route: `GET /api/v1/workspaces/{ws}/collections/{coll}/checkbox-progress`.
  - Test `TestCollectionCheckboxProgress` covers the math (open +
    done counts), zero-result rows are filtered, unknown collection
    → 404, empty result → 200 + `[]`.

Web:
  - `api.items.collectionCheckboxProgress(ws, coll)`
  - Both call sites in `+page.svelte` (initial `loadCollection`
    non-plans branch + `refreshProgress` non-plans branch) now
    pull from the endpoint instead of parsing `item.content`.
  - Drops the previous "documented regression" comment — the
    feature is fully preserved.

Sub-100-byte response per item (vs. the full content body) so the
bandwidth win from `/items-index` is preserved. The endpoint scans
content server-side, but doesn't transmit it — the original
listByCollection call both scanned AND transmitted content.

Parent: PLAN-1343.

* fix(api+web): plumb include_archived through checkbox-progress per Codex review (round 2)

Codex round 2 [P2] caught that the Archived toggle path lost
checklist progress badges: `CollectionCheckboxProgress` hard-coded
`deleted_at IS NULL`, but the page-side fetch is called with the
same `showArchived` flag that toggles whether archived items
render. With the toggle on, archived non-plan items appeared in
the list but had no `itemProgress` row — the old client-side parse
would have counted them.

Fix: thread `includeArchived` through the call chain.

  - store.CollectionCheckboxProgress(workspaceID, collectionID,
    includeArchived bool) — appends `AND deleted_at IS NULL` only
    when includeArchived is false. Default match the original
    archived-off behavior.
  - handleCollectionCheckboxProgress reads
    ?include_archived=true and forwards.
  - api.items.collectionCheckboxProgress(ws, coll, { includeArchived })
    on the client.
  - +page.svelte's two call sites pass `showArchived` /
    `includeArchived` exactly.

TestCollectionCheckboxProgress now archives one of the seeded
items and asserts:
  - default response excludes the archived item (1 row)
  - ?include_archived=true response includes it (2 rows)

Also clarified the const-doc on `checkboxCountSQL` to reflect the
dynamic deleted-at clause.
2026-05-11 11:51:06 -04:00
..