Skip to content

web: responsive shell, accessibility pass, and content security policy #232

Description

@rghvgrv

Parent

#196 — PRD: Angular web client for SubVora (parity minus reminders)

What to build

The shell-level polish pass: the app usable from 360px to a wide desktop, reachable entirely by keyboard and screen reader, and locked down with a content security policy. Grouped as one slice because all three are cross-cutting passes over the shell and the finished screens rather than separate user-visible features.

Implementation Steps

  1. Responsive shell — make the side nav a persistent rail above ~900px and a drawer below it; verify every screen down to 360px, with tables scrolling inside their own overflow-x: auto container so the page body never scrolls sideways.
  2. Narrow-screen tables — collapse the subscriptions table to stacked cards (or hide secondary columns) at narrow widths so the primary information — name, cost, due state — stays readable without horizontal scrolling.
  3. Keyboard and screen reader — every control labelled; visible focus rings throughout; row menus and dialogs reachable and dismissable by keyboard with focus returned to the trigger; snackbars announced via aria-live; the overdue state conveyed by text, not colour alone.
  4. Content security policy — add a strict CSP <meta> to index.html: default-src 'self', the API origin in connect-src, the catalog logo host in img-src, and no third-party script sources. [HITL] — confirm the catalogLogoUrl host before locking img-src, or logos silently stop loading.
  5. Verify the budget — confirm the production build still fits the bundle budget from slice 3 and record the resulting sizes on the issue.
  6. Manual sweep — walk every screen once by keyboard only at 360px and at desktop width, and note anything unreachable on the issue.

Agent Routing

agent_routing:
  complexity_hint: medium
  required_capability: balanced
  parallel_safe: false
  cost_preference: balanced
  speed_preference: balanced
  ownership_scope:
    - src/SubVora.Web/src/app/layout/shell.component.ts
    - src/SubVora.Web/src/app/features/**
    - src/SubVora.Web/src/index.html
    - src/SubVora.Web/src/styles.scss
  verification:
    - cd src/SubVora.Web && npx ng build --configuration production
    - cd src/SubVora.Web && npx ng test --no-watch

Technical Context Snapshot

Current stack in scope

  • UI: Angular (20+) standalone components with signals and built-in control flow, Angular Material (Material 3) as the only component library, SCSS. Static SPA — no SSR, no service worker.
  • State: signal-backed injectable stores, one per domain area, mirroring the MAUI ViewModel split in src/SubVora.Mobile/ViewModels one-to-one. No NgRx.
  • API access: hand-written models plus one service per API controller under src/SubVora.Web/src/app/core/api. Enums travel as JSON strings (JsonStringEnumConverter in Program.cs), so TypeScript string-literal unions are exact.
  • Backend consumed unchanged: ASP.NET Core net10.0, /api/v1/, JWT bearer in the Authorization header, tokens in JSON bodies (no cookies).
  • Tests: Angular CLI unit-test builder (Vitest runner; Karma is deprecated) with HttpTestingController. Stores, interceptors, mappers and utils only — no component-DOM or browser automation.

Dependencies in scope

  • Reuse: @angular/*, @angular/material, rxjs, and the utilities already added under src/SubVora.Web/src/app/core. No chart library, no date library, no HTTP wrapper library.
  • New dependency additions allowed for this slice: no. If a dependency looks unavoidable, stop and raise it on the issue rather than adding it.

Architecture alignment

  • Preserve the repo's load-bearing rules (CLAUDE.md): burn-rate maths is server-side and counts cycles, never days; currency conversion is a read-time projection and stored amounts are never overwritten; nothing advances next_billing_date on a timer; provider matching stays one SQL query; the mobile SQLite cache stays a read-only mirror.
  • There is deliberately no shared DTO project. Web models mirror the API's JSON contract by convention — a contract change means editing both sides.
  • create-git-issue provides routing hints only and assigns no concrete agent or model.
  • run-with-it remains the final runtime routing authority.

Integration touchpoints

  • The CSP must allow the API origin for XHR and the catalog logo host for images; getting either wrong breaks the app silently in production.
  • No new endpoints or contracts; presentation and headers only.
  • Accessibility is a baseline requirement, not a nice-to-have: labels, focus, keyboard reachability and non-colour-only status are in scope even though an automated axe gate is not.

Acceptance criteria

  • Every screen is usable from 360px to wide desktop; wide content scrolls within its own container, never the page body.
  • Every screen is fully operable by keyboard with visible focus and announced status messages; overdue state is conveyed by text as well as colour.
  • A strict CSP is in place, the API and logo hosts still work under it, and the production build fits the declared bundle budget.

Blocked by

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestready-for-agentReady for autonomous agent execution

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions