Worker Portal Mockups
On this page
Overview
The worker portal (services/canopy-web) is a server-rendered Askama + htmx
BFF for caseworker workflows. Eight distinct page surfaces plus the
Keycloak-mediated login: dashboard, case search, case detail (program-
dependent tabset), application process, renewal queue, applications list,
notices list, appeals list. Each section below captures the page’s layout
regions, Orchard token usage, primary htmx interactions, and the
caseworker actions exposed.
This is reference documentation for what’s in production today
(services/canopy-web/templates/); proposed redesigns belong in a
separate plan.
Orchard color tokens
Source of truth: rulesets/georgia/notices/components/orchard.typ:1-32.
| Token | Value | Where it appears |
|---|---|---|
|
|
App header, primary buttons, active tab underline, stat-card emphasis |
|
|
Secondary highlights (e.g. |
|
|
All body text |
|
|
Label text, helper copy, inactive tabs |
|
|
Card borders, table dividers, form-field borders |
Tokens surface in CSS as --color-orchard-primary, --color-orchard-accent,
etc. (see services/canopy-web/static/css/).
Page chrome (shared across all eight)
The header uses primary as its background with light-on-dark text; the
main content area uses a near-white surface with border-color dividers.
No persistent left sidebar — navigation is via the header links into the
five top-level pages (Dashboard / Cases / Applications / Renewals /
Appeals + Notices).
Login
Keycloak-mediated OIDC redirect; canopy-web is the relying party.
Caseworkers click "Sign in" → bounce to Keycloak realm canopy → return
with a session cookie minted by tower-sessions (PG-backed per ADR-009).
Orchard tokens: primary button background; accent for the "trouble
signing in?" link.
Caseworker actions: Sign in only. There is no in-app registration — worker accounts are provisioned by Keycloak realm admins.
Dashboard
Entry surface after login. Two horizontal grids: caseload-summary stat cards on top, program-breakdown stat cards below, then a recent-activity table.
Orchard tokens:
-
Stat cards:
border-colorborder,body-colortext,mutedlabel -
u-stat-accent(renewals-due card):accentforeground -
u-stat-error(interim-contacts-due card): semantic error red (not an Orchard token; uses Tailwind’s--color-error) -
Activity-table header:
primarybottom border
htmx interactions: none on the dashboard itself — it’s a static page
re-rendered on each navigation. Cards link via standard <a href> into
case search or per-program lists.
Source: services/canopy-web/templates/dashboard.html
Case Search
Filter-and-list page for active households. The form posts via
hx-get="/cases/search" and re-renders only the result table.
Orchard tokens:
-
Filter form:
border-colorfield borders,primary"Search" button -
Result rows:
border-colorseparators; hover state lightens toborder-color× 50%
htmx interactions: hx-get="/cases/search" hx-target="#results"
hx-swap="innerHTML". Result rows include <a href="/cases/{household_id}">
to navigate into case detail.
Source: services/canopy-web/templates/cases/search.html + _results.html
Case Detail
The largest surface. Header band with household summary; then a tablist
that shows a program-dependent subset of 16 tab partials in
services/canopy-web/templates/cases/tab_*.html. Tab content swaps via
htmx (hx-get="/cases/{id}/tab/{tab_id}").
Tabs rendered, by program (the source-of-truth cases.rs::render_program_tab):
| Program | Tab IDs |
|---|---|
SNAP |
|
TANF |
SNAP set + |
Medicaid |
SNAP set + |
CAPS |
SNAP set + |
WIC |
SNAP set + |
Synthetic IEVS-only rows on the income tab render an em-dash (—) in the
Actions column (income_id is None so there’s no PUT/DELETE target).
Orchard tokens:
-
Tab underline (active):
primary -
Tab text (inactive):
muted -
<details>action toggles (Edit / Remove / Add):border-colorborder,primaryconfirm button,muted"Cancel" -
Status badges in the header:
accentfor "Action Required",body-colorfor neutral -
Tab partials' card borders:
border-color
htmx interactions:
-
Tab clicks:
hx-get="/cases/{id}/tab/{tab_id}" hx-target="#tabpanel" hx-swap="innerHTML" -
Income actions (#409):
POST /actions/income/{add,edit,remove}→ 303 redirect to/cases/{household_id} -
SNAP caseworker actions (#411 + #392):
POST /actions/snap/{appeal,interim,change,abawd,discrepancy}and program-specific equivalents -
The
htmx-settlingclass shown during swap-in surfaces the brief loading state withborder-colorborder-pulse
Source: services/canopy-web/templates/cases/detail.html + 16 tab_*.html partials
Application Process
Application-detail / decision surface used by eligibility specialists to finalize a case. Shows application summary, rules-engine result, verification status, and decision actions (Approve / Deny).
Orchard tokens:
-
Cards:
border-colorborder,body-colortext,mutedlabel -
"Approve" button:
primarybackground -
"Deny" button: outline with
body-colorborder + text (no destructive red — denial requires a reason, not visual alarm) -
Result-status badges:
primaryfor approved,accentfor pending, semantic error for denied
htmx interactions: the form submits as a classic POST (full-page
reload) so the post-decision redirect lands on a clean case detail. No
htmx tab swap on this page.
Source: services/canopy-web/templates/applications/process.html
Renewal Queue
Three-button window filter (30 / 60 / 90 days) over the renewals list.
Each filter is an htmx swap targeting #renewal-list.
Orchard tokens:
-
Active filter button:
primarybackground, white text -
Inactive filter buttons:
body-colortext on white,border-colorborder -
List rows:
border-colorseparators; row text inbody-color -
"Days remaining" column tints
accentonce ≤ 14 days, semantic error once overdue
htmx interactions:
-
hx-get="/renewals?days={30|60|90}" hx-target="#renewal-list" hx-swap="innerHTML" -
Each row’s "Start renewal" action is a plain link into the case-detail household tab.
Source: services/canopy-web/templates/renewals/queue.html
Applications List
Filterable list of all applications regardless of program. Columns: application ID short, applicant, program list, status, received_at.
Orchard tokens: same shape as Renewal Queue — filter pills use
primary for active, muted for inactive; status cells use primary /
accent / semantic-error tinting.
htmx interactions: hx-get="/applications?…" with table swap into
#app-list. Row click navigates to /applications/{id}/process.
Source: services/canopy-web/templates/applications/list.html
Notices List
Per-household notice history. Columns: notice type, generated_at, delivery channel, status. Worker can re-generate or download a PDF from each row.
Orchard tokens: table-row scheme matches Applications List;
download/regenerate buttons in primary outline.
htmx interactions: "Re-generate" is htmx hx-post that swaps the row
in place with the updated status = 'queued' state; download is a plain
anchor that triggers a Typst-rendered PDF stream from canopy-notices.
Source: services/canopy-web/templates/notices/list.html
Appeals List
Open fair-hearings + IPV cases. Columns: case ID, household, type, filed_at, hearing_date, status.
Orchard tokens:
-
"Continued benefits" status badge:
accent(financial urgency signal per the continued-benefits 30-day clock) -
"Past 90-day clock" status: semantic error
-
Action-required rows tint the row background to
border-color× 25%
htmx interactions: hx-get="/appeals?status=…" hx-target="#appeals-list".
Row click navigates into the case-detail Appeals tab.
Source: services/canopy-web/templates/appeals/list.html
UAT review notes
When using these mockups for UAT:
-
Diagrams capture layout and interaction, not pixel fidelity. Caseworkers commenting on the diagrams should focus on whether the information regions are in the right order and whether the action set matches their workflow — not on font choices or spacing.
-
The 16 tab partials make Case Detail the heaviest review surface. If reviewers prefer to walk through one program at a time, the program → tab subset table above is the right entry point.
-
Color tokens are deliberately understated. The accent gold is for urgency signaling (renewals, continued benefits, action-required) — if reviewers see a surface that should signal urgency but doesn’t, flag the token assignment.
Future work
The Dioxus rewrite contemplated by ADR-008 is a constituent-facing surface (canopy-portal), not this worker portal. A separate worker-portal refresh would happen post-UAT, after caseworker feedback on these surfaces. This page is for documenting what exists today, not for proposing the refresh.