# Implementation Plan: Authenticated Shell — LED Color Rendering (Job 878, 1.02) ## 0. Already-Implemented Check After investigating the repository: - `frontend/src/app/features/shell/header/shell-header.component.ts` already renders `` with the correct `aria-label`. - The state derivation (`running` / `countdown` / `stopped` / `unconfigured`) is already correct (verified by the Job description: `running` between Start and End is being assigned). - `frontend/src/styles.css` defines the color tokens `--color-success`, `--color-warning`, `--color-danger`, but contains **no** rules for `.led`, `.led-running`, `.led-countdown`, `.led-stopped`, or `.led-unconfigured`. The bug is therefore isolated to missing CSS. The fix is purely a stylesheet addition. No TypeScript / Angular code changes are required; no backend, no state logic, and no template changes are required. This means most sections of this plan are intentionally short — there is nothing to migrate, no APIs to add, and no component logic to author. ## 1. Architectural Reconnaissance - **Codebase style & conventions:** TypeScript, Angular 17+ standalone components (`ShellHeaderComponent` is `standalone: true`, `ChangeDetectionStrategy.OnPush`). Global styles live in `frontend/src/styles.css` and are the single source of design tokens (`--color-*`, `--radius-*`, `--font-family`). All Angular component templates and styles are inlined per-component; nothing for the LED is defined at component scope. - **Data Layer:** Not applicable — this Job is a pure CSS fix. The event state itself already flows correctly via `EventStatusStore` → `HomeComponent` → `ShellHeaderComponent.eventState` input. - **Test Framework & Structure:** Jest via `tests/jest.config.js` with two projects (`backend`, `frontend`). Frontend tests use `jsdom` + `ts-jest` and `tests/frontend/jest.setup.ts` (provides `matchMedia` shim). Frontend specs live under `tests/frontend/*.spec.ts` and import directly from `frontend/src/...`. All tests run from the repo root with a single command: `npm test`. - **Required Tools & Dependencies:** None new. Existing stack (Node, Angular CLI / `npm --workspace frontend run build`, Jest 29, jsdom 29) is sufficient. ## 2. Impacted Files - **To Modify:** - `frontend/src/styles.css` — add the four `.led-{state}` rules plus the base `.led` rule, using the already-defined color tokens. One-line reason: the LED element currently has no dimensions and no color, so it renders as invisible content; the bug description confirms this exactly. - **To Create:** - `tests/frontend/led-class-mapping.spec.ts` — minimal regression test that asserts `ShellHeaderComponent.ledClass()` produces the expected `led led-` string for each `EventState` value. This verifies the class wiring that the new CSS rules depend on (small, pure, runnable without a UI). ## 3. Proposed Changes ### 3.1 Stylesheet (`frontend/src/styles.css`) Append the following rules to the bottom of `frontend/src/styles.css`, preserving the existing two-space indentation used in the file: ```css .led { display: inline-block; width: 10px; height: 10px; border-radius: 50%; border: 0; background-color: transparent; vertical-align: middle; } .led-running { background-color: var(--color-success); } .led-countdown { background-color: var(--color-warning); } .led-stopped { background-color: var(--color-danger); } .led-unconfigured { background-color: var(--color-secondary); } ``` Design rationale (each decision is required to satisfy the Job): - `display: inline-block` + `width/height: 10px` + `border-radius: 50%` are the minimum declarations needed to paint a visible circular dot and to override the browser's default `display: inline` on the `` (which the bug report explicitly calls out as `width: auto; height: auto; background-color: rgba(0,0,0,0)`). - The four state classes map directly to the four token colors already declared in `:root`, which is exactly the mapping the Job specifies (Green / Yellow / Red / secondary-gray for unconfigured, since `--color-secondary` was the only remaining neutral token available). - No `::before` / `::after` content is used, so no `content` property is needed (the bug report ruled these out as a fix path). - No changes to the template or component logic — the `[class.led-running]` etc. bindings in `shell-header.component.ts` already drive the right state class. ### 3.2 Regression test (`tests/frontend/led-class-mapping.spec.ts`) This test is purely defensive: it locks in the contract that `ShellHeaderComponent.ledClass` produces the class string the CSS rules are keyed on, so a future template refactor cannot silently break the visual fix. It uses the existing `jsdom` Jest project and the same import style as `tests/frontend/shell-active-section.spec.ts`. ```ts import { ShellHeaderComponent } from '../../frontend/src/app/features/shell/header/shell-header.component'; import { EventState } from '../../frontend/src/app/core/services/event-status.store'; describe('ShellHeaderComponent LED class mapping', () => { function build() { const c = new ShellHeaderComponent(); return c; } it.each([ ['running', 'led led-running'], ['countdown', 'led led-countdown'], ['stopped', 'led led-stopped'], ['unconfigured', 'led led-unconfigured'], ] as [EventState, string][])( 'maps event state %s to class string %s', (state, expected) => { const c = build(); c.eventState.set(state as any); // input signal set is a noop — handled below }, ); }); ``` Because `ShellHeaderComponent` exposes `eventState` as an Angular **input signal** (`input('unconfigured')`), and test-driving Angular signals without `TestBed` is fragile, the cleanest minimal test — and the one most consistent with the existing convention used by `shell-active-section.spec.ts` (which tests a pure helper) — is to mirror that pattern. We will instead add **one lightweight pure helper** export from `shell-header.component.ts`: ```ts // pure helper, exported for tests export function ledClassForState(state: EventState): string { return `led led-${state}`; } ``` And have `ledClass` delegate to it: ```ts readonly ledClass = computed(() => ledClassForState(this.eventState())); ``` This keeps the test pure-function-style (matches the `deriveActiveSectionFromUrl` precedent in `home.shell.ts`) and avoids any `TestBed` / `ChangeDetection` setup. The test then becomes: ```ts import { ledClassForState } from '../../frontend/src/app/features/shell/header/shell-header.component'; describe('ledClassForState', () => { it('returns the base + state class for each EventState', () => { expect(ledClassForState('running')) .toBe('led led-running'); expect(ledClassForState('countdown')) .toBe('led led-countdown'); expect(ledClassForState('stopped')) .toBe('led led-stopped'); expect(ledClassForState('unconfigured')) .toBe('led led-unconfigured'); }); }); ``` A single `describe` / single `it` keeps the suite minimal and instantly runnable, satisfying the "minimal, highly-focused tests" rule. If the implementer prefers to avoid touching `shell-header.component.ts` entirely, the alternative is to skip this test. The plan therefore lists this test as **optional / lowest priority**, and the Plan Implementer may omit it without affecting the bug fix. The bug fix does **not** require any code change to `shell-header.component.ts`. ## 4. Test Strategy - **Target Unit Test File:** `tests/frontend/led-class-mapping.spec.ts` (optional; see §3.2). Run via `npm test` from the repo root, which the project's package.json already wires to `jest --config tests/jest.config.js`. - **Mocking Strategy:** None required. The test, if added, exercises a pure exported function with no I/O, no Angular DI, and no DOM mutation, so no mocks, fakes, or `TestBed` configuration are needed. The existing `tests/frontend/jest.setup.ts` already shims `matchMedia` for any incidental DOM usage. - **What we deliberately do NOT add:** - No visual regression test (the Job explicitly forbids tests that require visual confirmation). - No `TestBed` rendering test for `ShellHeaderComponent` (would add infrastructure cost for no functional value, since the existing template + class bindings are unchanged). - No backend or integration test — the bug is purely CSS. ## 5. Acceptance Criteria The Job is considered fixed when, after rebuilding the Angular bundle: 1. The `` element is rendered as a 10×10 filled circle in the authenticated shell header. 2. Its background color matches the live event state: - `countdown` (now → startUtc) → `var(--color-warning)` (#f59e0b / yellow) - `running` (startUtc → endUtc) → `var(--color-success)` (#10b981 / green) - `stopped` (after endUtc) → `var(--color-danger)` (#ef4444 / red) - `unconfigured` (no Start/End) → `var(--color-secondary)` neutral 3. No new HTTP traffic, no new backend code, no new Angular component file, and no change to the shell template. 4. `npm test` continues to pass.