Plan: Worker Portal Handler Remediation

On this page

Status

Step Description Status

1

Case detail: household tab — fetch members, address, certification from canopy-persons and canopy-renewals

Done (2026-04-07)

2

Case detail: income tab — fetch income from canopy-persons, IEVS data from canopy-snap

Done (2026-04-07)

3

Case detail: determination tab — fetch from canopy-eligibility and canopy-snap

Done (2026-04-07)

4

Case detail: notices tab — fetch from canopy-notices

Done (2026-04-07)

5

Case detail: appeals tab — fetch from canopy-appeals

Done (2026-04-07)

6

Case detail: activity tab — fetch from canopy-security

Done (2026-04-07)

7

Application processing — fetch application + eligibility result, wire POST approve/deny endpoints

Done (2026-04-07)

8

Renewal queue — fetch certifications due from canopy-renewals

Done (2026-04-07)

9

Dashboard — wire work queue aggregation and recent activity feed

Done (2026-04-07)

10

Fix case detail summary bar — dynamic values from upstream data (not hardcoded)

Done (2026-04-07)

11

Rewrite session_test.rs for session-based auth (not JWT)

Done (2026-04-07)

12

Add OIDC env vars to docker-compose.yml for canopy-web

Done (2026-04-07)

Epic: &43
Issues: TBD
Branch: fix/worker-portal-remediation
Labels: type::bug, priority::critical, program::snap, service::web

Context

An audit of the worker portal revealed that while the UI shell (templates, routing, theme, session auth) is complete and functional, the data-fetching handlers are largely stubbed. The ServiceClients struct with 8 HTTP clients exists but is ignored by most handlers. Templates receive empty data and render graceful "no data available" fallbacks — giving the appearance of working while returning nothing.

This was marked as complete in the worker-portal-snap plan but should not have been. The handlers need to call the upstream services using the existing ServiceClients and populate the templates with real data.

The seed tool (MR !36) has populated the databases with realistic test data. The upstream API services (canopy-persons, canopy-applications, canopy-snap, canopy-eligibility, canopy-enrollment, canopy-renewals, canopy-notices, canopy-appeals, canopy-security) all return this data when queried with a valid JWT. The canopy-web BFF must forward requests to these services to display the data.

Scope

In scope:

  • Wire all 6 case detail tab handlers to call upstream services via ServiceClients

  • Wire application processing handler to fetch real application and eligibility data

  • Implement POST /applications/{id}/approve and POST /applications/{id}/deny

  • Wire renewal queue to fetch from canopy-renewals

  • Wire dashboard work queue and activity feed

  • Replace all hardcoded values (benefit amounts, cert periods, household sizes) with upstream data

  • Rewrite session_test.rs integration tests for session-based auth

  • Add OIDC env vars to docker-compose.yml

Out of scope:

  • Keycloak OIDC flow (already implemented)

  • New UI pages or templates (existing templates are complete)

  • E2E Playwright tests (separate plan, depends on this)

Design

Each handler currently has this pattern:

fn render_household_tab(..., _clients: ...) -> ... {
    // _clients ignored, hardcoded empty data
    HouseholdTab { members: Vec::new(), ... }
}

The fix is straightforward: call the upstream service via the clients, parse the JSON response, and populate the template struct. Errors degrade gracefully (the templates already handle empty data).

fn render_household_tab(..., clients: ...) -> ... {
    let members = clients.persons.get(&format!("/v1/v1/households/{hh_id}"))
        .await
        .map(|r| r.json().await.unwrap_or_default())
        .unwrap_or_default();
    HouseholdTab { members, ... }
}
NOTE
The upstream services currently have double-nested routes (/v1/v1/persons). The BFF clients must use this path until the API services are fixed to not double-nest.

Steps

Step 1-6: Case detail tabs

Files: services/canopy-web/src/api/case_detail.rs

Each tab renderer calls the appropriate upstream service. Error handling: if the service returns an error, the tab shows its existing "no data" fallback.

Step 7: Application processing

Files: services/canopy-web/src/api/applications.rs, services/canopy-web/src/api/mod.rs

  • Wire get_process_application to fetch from canopy-applications and canopy-eligibility

  • Add POST /applications/{id}/approve and POST /applications/{id}/deny routes

  • Both POST routes call canopy-applications to record the determination

Step 8: Renewal queue

Files: services/canopy-web/src/api/renewals.rs

Wire to GET /v1/v1/renewals/snap/due?days={filter_days} on canopy-renewals.

Step 9: Dashboard aggregation

Files: services/canopy-web/src/api/dashboard.rs

Work queue: aggregate from applications (pending), renewals (due), appeals (pending). Activity feed: fetch recent audit events from canopy-security.

Step 10: Case detail summary bar

Files: services/canopy-web/src/api/case_detail.rs

Replace hardcoded $975.00/mo, Apr 2026 → Mar 2027, household size 4 with data from canopy-snap and canopy-enrollment.

Step 11: Rewrite session tests

Files: services/canopy-web/tests/session_test.rs

Tests need to use session cookies (not JWT Bearer) since the BFF now uses OIDC session auth. Either mock the OIDC flow or inject session data directly.

Step 12: Docker compose OIDC env vars

Files: docker-compose.yml

Add to canopy-web service:

CANOPY_WEB__KEYCLOAK_EXTERNAL_URL: "http://localhost:8180/realms/canopy"
CANOPY_WEB__KEYCLOAK_INTERNAL_URL: "http://keycloak:8080/realms/canopy"
CANOPY_WEB__KEYCLOAK_CLIENT_ID: "canopy-ui"
CANOPY_WEB__REDIRECT_URL: "http://localhost:8080/auth/callback"

Files Touched

File Change

services/canopy-web/src/api/case_detail.rs

Wire 6 tab renderers to upstream services

services/canopy-web/src/api/applications.rs

Wire data fetching, add POST approve/deny

services/canopy-web/src/api/mod.rs

Add POST routes for approve/deny

services/canopy-web/src/api/renewals.rs

Wire to canopy-renewals

services/canopy-web/src/api/dashboard.rs

Wire work queue and activity feed

services/canopy-web/tests/session_test.rs

Rewrite for session auth

docker-compose.yml

Add OIDC env vars for canopy-web

Verification

  1. cargo clippy --workspace --all-targets — -D warnings

  2. cargo nextest run -p canopy-web — all tests pass

  3. cargo xtask dev restart --shared-dbcargo xtask seed --seed 42 --households 50

  4. Browser: http://localhost:8080/ → Keycloak login → dashboard with real stats

  5. Search for a person → click result → case detail with populated tabs

  6. Application processing page shows real data, approve/deny buttons work

  7. Renewal queue shows certifications due

Documentation Updates

  • .claude/CLAUDE.md — update canopy-web status to reflect actual state

  • CHANGELOG.adoc — entry for handler wiring

  • docs/modules/ROOT/pages/plans/worker-portal-snap.adoc — update Step 2 status honestly

Edit this page · default