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

222 lines
9.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 `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-<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:
```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 `<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`.
```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`:
```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 `<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.