Skip to content

web: light/dark theme following the system preference #230

Description

@rghvgrv

Parent

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

What to build

Light and dark themes from one Material 3 token set: follow the operating system by default, with a toggle that overrides and remembers the choice. A spend dashboard is a page people leave open, so dark mode is not decoration.

Implementation Steps

  1. Palette — extend theme.scss so both light and dark palettes come from the same M3 seed colour; define colours as tokens so no component hard-codes a hex value. [HITL] — the brand seed colour, if the mobile app has one to match.
  2. Theme service — core/theme/theme.service.ts: a mode signal (system | light | dark), initialised from localStorage (try/catch) and otherwise from prefers-color-scheme, applying the choice as a data-theme attribute on the document element and persisting changes.
  3. React to the OS — while in system mode, follow live changes to prefers-color-scheme via a media-query listener, so switching the OS theme updates the app without a reload.
  4. Toggle — a three-state control in the shell toolbar (and mirrored in the settings appearance card) bound to the service.
  5. Sweep for hard-coded colours — check the screens built so far, especially the dashboard bars and the overdue chip, and confirm they read theme tokens and keep AA contrast in both themes.
  6. Spec — theme.service.spec.ts: no stored preference follows the media query; an explicit choice wins and persists; a throwing localStorage still applies the theme for the session.

Agent Routing

agent_routing:
  complexity_hint: easy
  required_capability: balanced
  parallel_safe: true
  cost_preference: low
  speed_preference: balanced
  ownership_scope:
    - src/SubVora.Web/src/theme.scss
    - src/SubVora.Web/src/styles.scss
    - src/SubVora.Web/src/app/core/theme/**
    - src/SubVora.Web/src/app/layout/shell.component.ts
  verification:
    - cd src/SubVora.Web && npx ng test --no-watch
    - cd src/SubVora.Web && npx ng build --configuration production

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

Acceptance criteria

  • The app follows the OS theme by default and reacts to OS changes live while in system mode.
  • An explicit light/dark choice overrides it and survives a reload.
  • Overdue chips and breakdown bars remain legible and AA-contrast in both themes, with no hard-coded colours.

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