feat: Worker portal Stage 5 — case detail (3 shell strategies + 13 section types)

Description

Reframe the case detail surface around 13 section types and 3 shell strategies (scroll / card-grid / tabs), driven by the Stage-3 composition loader and a rulesets/{jurisdiction}/composition/case-detail.toml. The current 6-tab implementation maps to the "tabs" shell with current section types; this issue generalizes to all 3 shells.

Largest single surface in this redesign.

Acceptance Criteria

  • Template at services/canopy-web/templates/case/detail.html reads ComposedSurface from composition loader and selects shell strategy from composition TOML per role (not per breakpoint)
  • 13 section types (one Askama partial each, using Stage 1 primitives):
    • Household summary, Persons, Income, Assets, Expenses, Verifications, Determinations, Notices, Appeals, Audit, Cross-program, Worker activity, Documents
  • 3 shell strategies:
    • scroll: all sections rendered in vertical document; in-page nav rail
    • card_grid: sections as cards in CSS grid; clicking a card opens its detail
    • tabs: classic horizontal tabs (current behavior preserved as default)
  • Per-role TOML shape supported (see Design answers below) — composition loader picks the strategy by the authenticated user's role, not by viewport width
  • Existing 30 caseworker actions preserved (per #392 (closed)/#393 (closed)/#394 (closed) work) — wired to whatever shell strategy the jurisdiction picks
  • Per-section four-state rendering (loading / error / empty / populated)
  • Default Georgia baseline composition/case-detail.toml ships tabs shell with current 6 section slugs (zero regression)
  • Playwright E2E: all 3 shells render; section nav works in each; actions fire in each
  • axe-core WCAG 2.1 AA clean for all 3 shells
  • CHANGELOG entry under === Changed

Design answers (2026-05-19)

  • Shell strategies are jurisdiction-selectable per role, not progressive enhancements. Same jurisdiction can run different shells per role, but a given user in a given context sees exactly one shell. No breakpoint-based shell-switching in v1.
  • Reasons: desktop-first product; runtime cost of two shells live (double templates, double htmx swap targets, ambiguous focus management mid-action); the responsive escape hatch is tabs (the most viewport-tolerant shell — Mississippi composition in the design package uses this).
  • TOML shape: per-role [shell.<role-slug>] tables in case-detail.toml:
[shell.eligibility_worker]
strategy = "scroll"

[shell.intake_screener]
strategy = "card_grid"
  • Section list and shell strategy are independent knobs. A jurisdiction on tabs can still reorder sections, mark some required = true, or exclude program-specific ones via the same TOML. Section list lives in sections = [...]; strategy lives in [shell.<role>].
  • Georgia migration default: the existing 6-tab implementation becomes strategy = "tabs" with the current section slugs in sections = [...]. scroll and card_grid ship as opt-in. No auto-migration — Studio admin switches when ready.
  • Section content shape is identical across shells. Only the chrome (in-page rail / CSS grid of cards / horizontal tab bar) changes per strategy. Section partials know nothing about which shell hosts them.

Blocked by

Context & References

Labels

type::feature, priority::medium, program::infrastructure, service::web, workflow::needs-spec, compliance::wcag-21-aa

Edited by Ghost User