9.3 KiB
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.tsalready renders<span class="led" [class.led-running]="..." [class.led-countdown]="..." [class.led-stopped]="..." [class.led-unconfigured]="..." data-testid="shell-led">with the correctaria-label.- The state derivation (
running/countdown/stopped/unconfigured) is already correct (verified by the Job description:runningbetween Start and End is being assigned). frontend/src/styles.cssdefines 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 (
ShellHeaderComponentisstandalone: true,ChangeDetectionStrategy.OnPush). Global styles live infrontend/src/styles.cssand 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.eventStateinput. - Test Framework & Structure: Jest via
tests/jest.config.jswith two projects (backend,frontend). Frontend tests usejsdom+ts-jestandtests/frontend/jest.setup.ts(providesmatchMediashim). Frontend specs live undertests/frontend/*.spec.tsand import directly fromfrontend/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.ledrule, 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 assertsShellHeaderComponent.ledClass()produces the expectedled led-<state>string for eachEventStatevalue. 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:
.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 defaultdisplay: inlineon the<span>(which the bug report explicitly calls out aswidth: 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-secondarywas the only remaining neutral token available). - No
::before/::aftercontent is used, so nocontentproperty 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 inshell-header.component.tsalready 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.
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<EventState>('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:
// pure helper, exported for tests
export function ledClassForState(state: EventState): string {
return `led led-${state}`;
}
And have ledClass delegate to it:
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:
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 vianpm testfrom the repo root, which the project's package.json already wires tojest --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
TestBedconfiguration are needed. The existingtests/frontend/jest.setup.tsalready shimsmatchMediafor any incidental DOM usage. - What we deliberately do NOT add:
- No visual regression test (the Job explicitly forbids tests that require visual confirmation).
- No
TestBedrendering test forShellHeaderComponent(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:
- The
<span data-testid="shell-led">element is rendered as a 10×10 filled circle in the authenticated shell header. - 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
- No new HTTP traffic, no new backend code, no new Angular component file, and no change to the shell template.
npm testcontinues to pass.