Skip to content

fix(web): collection cards show real child-item progress; child-progress endpoint (BUG-1509)#710

Merged
xarmian merged 3 commits into
mainfrom
fix/bug-1509-child-progress
Jun 6, 2026
Merged

fix(web): collection cards show real child-item progress; child-progress endpoint (BUG-1509)#710
xarmian merged 3 commits into
mainfrom
fix/bug-1509-child-progress

Conversation

@xarmian

@xarmian xarmian commented Jun 6, 2026

Copy link
Copy Markdown
Collaborator

Summary

Non-plan collection cards showed markdown-checkbox counts (label "done") while the item page's Children section showed linked child items — two unrelated data sources, so a task could show "0/5" on its card with no visible subtasks on the item page (or the inverse). Fixes docapp BUG-1509, root cause documented in its comments.

Changes:

  1. New GET /workspaces/{ws}/collections/{collSlug}/child-progress endpoint generalizing the plans-progress logic (shared collectionChildrenProgress helper; /plans-progress unchanged, now a thin wrapper).
  2. Collection page prefers real child-item progress (label "tasks") per item, falls back to checkbox progress (label "done").
  3. ChildItems.svelte error state was unreachable behind the loading || children.length > 0 gate — a failed /children fetch silently hid the section; now error renders.

Plus (architectural decisions)

  • Visibility/guest-grant filtering semantics preserved exactly and now shared between plans-progress and child-progress (restricted callers get the same empty shape — covered by a no-leak test).
  • include_archived query param threaded end-to-end including childrenDoneFiltersForCollection's discovery query (codex r3 found the discovery/aggregation inconsistency; the regression test was proven load-bearing by reverting the fix).

Known limitation

The restricted-visibility recompute path issues one GetChildItems query per parent item (pre-existing trade-off from plans-progress, now generalized to potentially larger collections). Deferred; needs a bulk-join recompute.

Test plan

  • go test ./... SQLite
  • make test-pg Postgres
  • golangci-lint run ./... 0 issues
  • npm run check 0 errors
  • npm run build
  • restricted-member no-leak test
  • archived-parent include_archived test (load-bearing, fails on revert)

Refs

Fixes docapp BUG-1509. Review: 4 codex rounds (r1 CLEAN, r2 ×2 findings fixed, r3 ×1 finding fixed, r4 CLEAN — converged).

xarmian added 3 commits June 6, 2026 14:06
Backend: extract collectionChildrenProgress helper from handlePlansProgress
and expose it at GET /collections/{collSlug}/child-progress with identical
visibility/guest-grant filtering. handlePlansProgress refactored to delegate
to the shared helper (no duplication). Route registered in the existing
/{collSlug} subrouter alongside checkbox-progress.

Frontend: +page.svelte fetches child-progress + checkbox-progress in parallel
for non-plans collections; per-item merge prefers child-progress (label
"tasks") when total>0, falls back to checkbox counts (label "done"). ItemCard
extended to render progress.label when present. ChildItems.svelte render gate
fixed to include error state so a failed /children fetch surfaces instead of
silently vanishing.

Tests: TestCollectionChildProgress covers happy path (linked children counted
correctly), zero-children items (present with total=0), 404 for unknown
collection, and restricted-member visibility gate (empty response for hidden
collection, not a data leak).
…ex r2)

P1: GetAllItemProgress now accepts includeArchived bool; the parent-row
filter (AND p.deleted_at IS NULL) is conditioned on it, mirroring
CollectionCheckboxProgress. handleCollectionChildrenProgress reads
?include_archived=true and threads it through. handlePlansProgress
hardcodes false — no contract change there. collectionChildProgress()
client method gains opts?: { includeArchived? } with qs() serialisation.
Both call sites in +page.svelte (loadCollection and refreshProgress) now
pass includeArchived to the child-progress fetch.

P2: refreshProgress plans branch now sets progressLabel = 'tasks' so a
sync-triggered refresh after a failed initial plans load renders with the
correct label. progressLabel = 'done' moved inside the non-plans try block
(symmetric with plans) so a thrown fetch leaves the label in whatever
state the previous collection set, not silently desync'd.

Tests: TestCollectionChildProgress extended — archives parentA, confirms
it drops from default response and reappears with include_archived=true.
…(codex r3)

GetAllItemProgress conditionally drops the p.deleted_at IS NULL parent
filter when includeArchived=true, but the filter-discovery call at the
top of the function — childrenDoneFiltersForCollection — still had the
filter hardcoded. If a child collection's only parent links pointed to
archived parents, that collection was absent from the done-semantics map,
and those children fell back to default status terminals rather than the
collection's configured done field — producing wrong done counts.

Fix: childrenDoneFiltersForCollection gains an includeArchived bool param;
the JOIN on items p conditions p.deleted_at IS NULL on it, exactly mirroring
the main query. GetAllItemProgress passes includeArchived through. The only
other caller of this helper (GetItemProgress via childrenDoneFiltersForParent)
is unaffected — that path is a separate function and never surfaces archived
parents.

Test: TestCollectionChildProgress extended with a "Widgets" collection whose
done field is `state` (terminal: "shipped") — not the default `status` field.
An archived task parent links two widget children (one shipped, one open); no
live task parent links into widgets, so the filter-discovery bug would drop
the collection from the map and produce done=0. The test asserts done=1 and
was verified to fail on the pre-fix code.
@xarmian
xarmian merged commit 35cc26d into main Jun 6, 2026
4 checks passed
@xarmian
xarmian deleted the fix/bug-1509-child-progress branch June 6, 2026 15:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant