Uh oh!
There was an error while loading. Please reload this page.
feat(S7): Role-filtered task queue + task actions - #8
Merged
Conversation
Record PWA/responsive web as the locked GD4 decision in plan.md, prd.md, issues.md, and implementation-plan.md — the pre-work gate for Iteration 7 required this before any S7 code lands. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Plan review found Task carries no field distinguishing SDOH from clinical work today (category/code/reasonCode/businessStatus all unset), so A1's role filter had nothing to key off. Adds A0: Action Planner self-reports domain per task (reusing the existing ResourceDomain vocabulary), threaded through Task.category on write and mapTaskResource/getTasks on read. Legacy Tasks without category fail open (visible to every role). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds a per-task care domain ('clinical' | 'sdoh') so S7 A1 can role-filter
the task queue (Social Worker → SDOH-only). The Action Planner self-reports
domain in its plan_tasks structured output (it knows which upstream finding
each task synthesizes); it is threaded through ActionPlannerTaskInput and
written on the FHIR Task, read back via a shared extractTaskDomain helper
in mapTaskResource/getTasks, exposed as TaskSummary.domain, and carried on
the analysis SSE task event.
Storage: a second meta.tag coding (system .../fhir/task-domain), mirroring
CARESYNC_TASK_TAG — NOT Task.category, which the plan originally named but
which FHIR R4 Task has no element for (HAPI 7.2.0 silently drops it,
verified). External contract is unchanged: domain surfaces as
TaskSummary.domain.
Fail-open: Tasks predating this field carry no domain tag and map to
domain: undefined (never a default), so A1 can treat an uncategorized Task
as visible to every role.
Tests (Jest + real HAPI): createTask writes the domain tag (read-back by
id); mapTaskResource extracts it and returns undefined when absent; getTasks
maps the untagged seed Task to undefined. All existing ActionPlannerOutput
fixtures updated for the now-required domain field. 123/123 API tests pass
on a serial run (jest --runInBand).
Note: `npm run test:api` (parallel workers) intermittently times out
integration tests due to pre-existing shared-HAPI contention at the default
5s timeout — reproducible on suites A0 never touched, and green when run
serially. Flagged for a maxWorkers/timeout decision in a later task.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>Adds GET /api/tasks: Social Worker sees only sdoh-domain + uncategorized
Tasks (fail-open per A0), Coordinator/Director see everything. FhirReadService
.listTasks reads the fixed coordinator-demo-panel group (same panel
getAssignedPanel uses) via Patient/{id}/$everything rather than
Task?subject=... search — verified directly against the local HAPI that
search lags behind a just-written Task while $everything doesn't, which
matters here since this is a live read path, not just test cleanup.
TDD: tests written first (red on missing route, then red again on the HAPI
search-lag race before switching to $everything), green after. 126/126 API
tests pass serially (npx jest --runInBand).
Follow-up flagged, not fixed here: getAssignedPanel's taskCount still reads
via Task?subject=... search and has the same latent lag risk.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>Adds PATCH /api/tasks/:id/status handling complete/defer/escalate. FHIR R4 Task.status has no native "deferred"/"escalated" value, so: complete sets status=completed; defer sets status=on-hold + businessStatus="Deferred"; escalate leaves status as-is + businessStatus="Escalated" + priority=urgent. Read side (mapTaskResource/getTasks/listTasks) now prefers businessStatus.text over the generic status map so the queue shows the distinct labels. Authorization generalizes A1's per-task domain filter to a write: fetch the Task, check its domain against the actor's scope, fail-open on undefined domain — not a guard(actor, 'clinical', ...) gate, since that would wrongly block a Social Worker from their own sdoh tasks. TDD: tests written first (red on missing route), green after. 136/136 API tests pass serially (npx jest --runInBand); tsc --noEmit clean. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
New /tasks route (TaskQueue.tsx), built against reference-materials/caresync-mobile.html's phone-shell mockup. Social Worker's roleHome now points here instead of the /coming-soon placeholder; Coordinator reaches it via a new header nav link. Backend: listTasks (A1) extended with patientId/patientName/conditionTag per task — free, since the $everything bundle it already fetches contains the Patient and Condition resources too. Scoped to listTasks only; TaskSummary and getTasks/mapTaskResource are unchanged. Chrome scope (html-mockup-fidelity): phone-frame + decorative status bar kept for GD4 visual identity; segment tabs, bottom tab bar, bell/badge, back button, and the pinned risk-summary sheet omitted (no backing data/screens yet). "Done" (-> A2 complete) is the only wired card action — Call and Defer/Escalate stay scoped to B2's task-detail screen per the plan's own architecture note. New Playwright spec covers Social Worker (sdoh/uncategorized only, completes one via Done) and Coordinator (full unfiltered set via the nav link). social-worker-denied.spec.ts and coordinator-panel.spec.ts updated for the new route and nav link. API 137/137, web unit 116/116, Playwright 11/11 (serial), tsc clean in both apps. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
New /tasks/:id route (TaskDetail.tsx) showing justifying patient context, resolved citations, Complete/Defer/Escalate (A2), and a tel: Call link. Reachable from a task card in the queue (Done button uses stopPropagation so it keeps completing in place instead of also navigating). Citation persistence revisited (plan-review finding): S3's createTask deliberately did not persist citations onto the FHIR Task (SSE-only). B2 needs them readable later, so this reverses that call. task.fhirResources now writes as Task.input entries (type.text: 'citation', valueReference per citation) instead. Task.reasonReference was tried first and rejected: FHIR R4 defines it 0..1, and HAPI 7.2.0 silently keeps only the first entry of a multi-value array on write (verified by direct probe against local HAPI) -- exactly the silent data loss GD11 exists to prevent. Task.input (0..*, native valueReference support) was verified the same way to round-trip every entry intact. New GET /api/tasks/:id (getTaskDetail) resolves citations to display strings and returns the patient's phone (new Patient.telecom seed data -- fabricated demo numbers, all seed data here is synthetic) for the Call link. Reuses the same fail-open domain-scope rule as A1/A2 (factored into a shared guardTaskDomain helper). No mockup exists for M03; built to the design tokens/patterns already established by TaskQueue.tsx/PatientDetail.tsx. New Playwright spec opens a probe task with real citations from the queue, confirms they render, defers the task, and confirms a working tel: link. API 144/144, web unit 116/116, Playwright 12/12 (serial), tsc clean in both apps. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Scope correction found and resolved: plan.md's GD9 (locked) classifies screen W13 as a nav-only shell, not one of the 6 demo-critical fully-functional screens -- this B3 plan line's own text implied full functionality, contradicting that. Resolved by honoring GD9: /task-center (TaskCenter.tsx) is an honest placeholder matching ComingSoon.tsx's pattern, reachable via a Coordinator nav link. No task table or status actions were built into W13 itself. Cross-surface sync moved to PatientDetail.tsx instead (already demo-critical, already shows a patient's task list), since S7's own acceptance criteria still require it regardless of W13's tier. EventHub gained publishAll (broadcast to every connection); the Subscription webhook now fires a task-updated broadcast on every Task change, assigned or not, alongside the unchanged owner-scoped assignment event from S6. PatientDetail subscribes and invalidates its own query when the event's patientId matches, live-updating with no reload. New Playwright spec proves a direct status-transition PATCH (simulating a mobile-queue completion) live-updates an already-open patient tab. API 147/147, web unit 122/122, Playwright 13/13 (serial), tsc clean in both apps. This completes S7 Phase B (M02, M03, W13 shell + sync). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Final consolidated verification: fresh API 147/147 (serial), web unit 122/122, Playwright 13/13, tsc clean in both apps. Closes out stale C1/C2 and Definition-of-done checkboxes in implementation-plan.md Iteration 7. Gate outcome: PASS. See verification-s7.md for full evidence, the definition-of-done check against issues.md, and the two plan-review findings from this slice (Task.input citation persistence, GD9/W13 scope correction). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Standards: extracted the duplicated patient-context lookup in listTasks/ getTaskDetail into a shared patientContextFromBundle helper. Spec: fixed a stale doc comment in agent.ts still referencing Task.category, which A0 established doesn't exist in FHIR R4. Both fixed and re-verified (147/147 API tests, tsc clean) before this commit. No open findings on either axis. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
manjula25 added a commit
that referenced
this pull request
Jul 8, 2026
Three doc updates to match the now-published reality (vs the pre-smoke-test state when verification-s14.md + the changelog were first written): 1. verification-s14.md §6 — two new follow-ups: - #7 (FIXED in f8d0862): the mount-order regression the live smoke test caught. Records the methodology lesson (per-route unit tests are blind to mount-order bugs; future slices need an integration smoke test against npm run dev). - #8 (out of scope): requireAuth itself still rejects SMART-shape tokens, so SMART-token-only callers would 401 at requireAuth before smartAuth ever runs. Asymmetry left by the f8d0862 fix. 2. review-s14.md — added a "Post-review update (commit f8d0862)" paragraph that discloses the regression caught after the PR was open and the fix. 3. changelog — added commits 9-11 (changelog + regression fix), the "Live smoke tests" section, follow-up #7 + #8, and the verification count update (281/281 → 282/282 with the new pass-through test). PR description was also updated (separately, via gh pr edit) to include a "🚨 Regression caught post-PR (and fixed in f8d0862)" section + the new follow-ups + the live smoke-test evidence. Unrelated pre-existing uncommitted changes in apps/web/ (PatientDetail.tsx + MyPatients.test.tsx) are NOT part of this commit; they were left in the working tree from an earlier session.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
tel:Call action.plan.md's locked GD9 scope tier (not one of the 6 demo-critical screens); the "mobile completion syncs to web" requirement is instead satisfied on the already-demo-criticalPatientDetail.tsxvia a new broadcast relay event.Task.reasonReferencesilently truncates multi-value arrays on this HAPI version; switched toTask.input) and the GD9/W13 scope conflict noted above.Test plan
cd apps/api && npx jest --runInBand— 147/147cd apps/web && npx vitest run— 122/122cd apps/web && npx playwright test --workers=1— 13/13npx tsc --noEmitclean in bothapps/apiandapps/webnpm run lintclean in both apps (pre-existing warnings only, none in touched files)docs/plans/caresync-ai/verification-s7.md(Phase C gate: PASS) andreview-s7.md(Standards + Spec axes, both findings fixed pre-merge)🤖 Generated with Claude Code