Files
HIPCTF2/.kilo/plans/878.md
T

9.3 KiB
Raw Blame History

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 <span class="led" [class.led-running]="..." [class.led-countdown]="..." [class.led-stopped]="..." [class.led-unconfigured]="..." data-testid="shell-led"> 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 EventStatusStoreHomeComponentShellHeaderComponent.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-<state> 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:

.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 <span> (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.

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 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 <span data-testid="shell-led"> 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.