5.3 KiB
phase, plan, subsystem, tags, dependency_graph, tech_stack, key_files, decisions, metrics
| phase | plan | subsystem | tags | dependency_graph | tech_stack | key_files | decisions | metrics | |||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 05-web-push-notifications | 03 | api/push-coalescer |
|
|
|
|
|
|
Phase 05 Plan 03: pushCoalescer — per-(list,actor) debounce — Summary
TDD RED→GREEN: pushCoalescer.ts implemented; per-(list,actor) sliding debounce collapses list-change bursts into a single dispatch call carrying (listId, actorId, count).
Tasks Executed
Task 1: RED — fix lint warning, add actorId assertion
Status: Completed. Commit: 7af827a
The existing RED scaffold in apps/api/tests/lib/pushCoalescer.test.ts (from Plan 05-01) had a lint warning: calledActorId was destructured in test 1 but never asserted. Added expect(calledActorId).toBe(actorId) to make the self-suppression assertion explicit in the burst-coalescing test as well (not only in the dedicated D-03 test).
Tests still fail after this change (RED preserved): Cannot find module '.../pushCoalescer.js'.
Task 2: GREEN — implement pushCoalescer.ts
Status: Completed. Commit: c1758de
Created apps/api/src/lib/pushCoalescer.ts:
coalesceListPush(listId, actorId, dispatch, windowMs=45000)
- Module-level
Map<string, {count, timer}>keyed by${listId}:${actorId} - First call in a burst: inserts entry with count=1, starts
setTimeout(windowMs) - Subsequent calls within window:
clearTimeout, increments count, resets timer (sliding debounce) - On timer fire: deletes map entry, calls
dispatch(listId, actorId, count)— self-deleting entries keep the map bounded (T-05-08) - dispatch errors caught and logged with
[pushCoalescer]prefix; never throws to caller
Verification
pnpm --filter @familysync/api exec vitest run tests/lib/pushCoalescer.test.ts
Test Files 1 passed (1)
Tests 3 passed (3)
pnpm --filter @familysync/api typecheck — passes.
TDD Gate Compliance
- RED:
test(05-03): add actorId assertion in burst test — fix unused var lint warning—7af827a - GREEN:
feat(05-03): implement pushCoalescer — per-(list,actor) sliding debounce (D-01/D-03)—c1758de
Deviations from Plan
Auto-fixed Issues
1. [Rule 1 - Bug] Lint warning — unused calledActorId in burst test
- Found during: Task 1 (RED)
- Issue:
calledActorIdwas destructured in test 1 but the assertion was missing, producing an unused-variable lint warning. - Fix: Added
expect(calledActorId).toBe(actorId)— the burst test now also asserts self-suppression, not just the dedicated D-03 test. - Files modified: apps/api/tests/lib/pushCoalescer.test.ts
- Commit:
7af827a
Dispatch signature simplification
The plan's <behavior> section describes dispatch(payload, actorId) where payload is a rich object {title, body, tag, navigate}. The existing RED scaffold (committed in Plan 05-01) uses dispatch(listId, actorId, count) — a simpler 3-argument form that defers notification copy construction to the caller.
The test is canonical; the implementation matches the test. The richer payload construction (D-02 generic copy: "${actorName} updated ${listName}", "${N} change(s)") is owned by the caller in Plan 05-05, which has the actorName/listName context from the DB row and passes a closure over dispatchPush.
Known Stubs
None. The coalescer is complete and testable. Plan 05-05 wires it into the list-change fan-out with actual notification copy.
Threat Flags
No new threat surface. pushCoalescer.ts is a pure in-memory utility module — no network endpoints, no auth paths, no file access.
T-05-06 (generic copy — no item text): mitigated by design — the coalescer passes only count, not item text; copy construction in Plan 05-05 will follow D-02.
T-05-07 (self-notification): mitigated — actorId threaded to dispatch so caller can apply WHERE userId != actorId.
T-05-08 (unbounded map): accepted — entries self-delete on timer fire; two-person household keeps keys bounded.
Self-Check
Files verified:
- apps/api/src/lib/pushCoalescer.ts — exists
- apps/api/tests/lib/pushCoalescer.test.ts — modified
Commits verified:
7af827a: test(05-03): add actorId assertion in burst test — fix unused var lint warning (RED gate)c1758de: feat(05-03): implement pushCoalescer — per-(list,actor) sliding debounce (D-01/D-03) (GREEN gate)