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

8.3 KiB
Raw Blame History

Implementation Plan: Authenticated Shell — Component-Scoped LED Fix (Job 879, iteration 1.03)

Status

The Job 879 reproduction shows that the LED <span class="led led-running"> renders with zero size and transparent background even though the global frontend/src/styles.css declares the rules and the production bundle contains them. To guarantee the rules apply regardless of cascade order, external stylesheet loading, or specificity conflicts, this fix moves the LED's visual styling from the global stylesheet into the ShellHeaderComponent itself using Angular's styles: [...] (component-scoped CSS).

The component is a standalone component (no styleUrls), so the fix is a single, self-contained edit: add styles: [\...`] to the @Component decorator in frontend/src/app/features/shell/header/shell-header.component.ts. The existing template, state class bindings, and aria-label are kept exactly as-is (lines 2634).

A minimal regression test is added in a dedicated tests/frontend/shell-led.spec.ts that asserts the rendered <span> carries led plus the correct led-<state> class for each EventState, and that the bundled component CSS contains the four state selectors.

1. Architectural Reconnaissance

  • Codebase style & conventions:

    • NestJS 10 backend + Angular 17 standalone components.
    • Standalone components use imports: [...] and prefer styles: [\...`]for small, co-located CSS. Existing examples in this repo:frontend/src/app/features/setup/setup-create-admin.component.tsusesstyleUrls: ['./setup-create-admin.component.css']; frontend/src/app/features/landing/landing.component.tslikewise. For a small block of CSS that belongs to a single component, the inlinestyles: [`...`] form keeps the fix in one file (per the user instruction: "Update the LED styling in frontend/src/app/features/shell/header/shell-header.component.ts near lines 25-34").
    • Angular's default ViewEncapsulation.Emulated rewrites component CSS selectors with attribute selectors ([_ngcontent-xxx]) and adds the same attribute to the component's host + template nodes. Because the LED <span> is rendered by ShellHeaderComponent (not by a child component or via innerHTML), encapsulation correctly scopes the new rules to that span, while still allowing the global :root CSS custom-property tokens (--color-success, etc.) to be inherited.
  • Data Layer: Not impacted. The LED color is driven by eventState() from EventStatusStore (already wired).

  • Test Framework & Structure:

    • Jest 29 multi-project config (tests/jest.config.js).
    • Frontend project uses ts-jest + jsdom + tests/frontend/jest.setup.ts (Angular TestBed shims).
    • Tests MUST live in tests/frontend/ — never alongside source. They MUST run via npm test (single root command). Tests MUST NOT require a UI; this plan keeps them purely structural.
  • Required Tools & Dependencies: None new. Existing @angular-devkit/build-angular:application builder already supports component-scoped styles: [\...`] , and critters(used for inline above-the-fold CSS) is unaffected.setup.sh` needs no changes.

2. Impacted Files

  • To Modify:

    • frontend/src/app/features/shell/header/shell-header.component.ts — add a styles: [\...`] block to the @Component decorator that mirrors the LED rules from the global stylesheet, scoped to the component. Keep the existing template (lines 1088) untouched. Keep the [class.led-<state>] bindings and the dynamic aria-label.
  • To Create:

    • tests/frontend/shell-led.spec.ts — minimal jsdom-based test that renders ShellHeaderComponent with each EventState value and asserts (a) the rendered [data-testid="shell-led"] carries the led class plus the matching led-<state> class, and (b) the component's bundled CSS string contains the four required state selectors (led-running, led-countdown, led-stopped, led-unconfigured).

3. Proposed Changes

  1. Component-scoped CSS in ShellHeaderComponent In frontend/src/app/features/shell/header/shell-header.component.ts, add a styles property to the @Component decorator. Place it immediately after the existing imports: [CommonModule] property (around the current line 9 area) so the decorator remains in canonical Angular order: selector, standalone, changeDetection, imports, template, styles.

    Exact CSS block to add inside the backtick literal:

    :host .led,
    .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); }
    

    Notes:

    • The base rule is duplicated as :host .led, .led so it matches both a projected host usage and the in-template span. The :host selector is harmless under Emulated encapsulation (it targets the component's own host element) and does not break the existing template.
    • State classes are written exactly as before — the template already toggles [class.led-running], [class.led-countdown], [class.led-stopped], [class.led-unconfigured].
    • Color values use the existing :root CSS custom properties (--color-success/--color-warning/--color-danger/ --color-secondary) defined in frontend/src/styles.css. These tokens are inherited through the DOM, so the component-scoped rules resolve them automatically — no per-state hex is needed in TypeScript.
    • The current global rules in frontend/src/styles.css (lines 2327) are left untouched as a safety net; they remain correct and harmless.
  2. No template or state-binding changes The <span ... class="led" [class.led-running]="..." ...> block (lines 2634) and the dynamic aria-label are kept exactly as the documentation in docs/guides/authenticated-shell.md specifies.

  3. No backend / DB changes The bug is purely a frontend cascade issue.

4. Test Strategy

  • Target Unit Test File: tests/frontend/shell-led.spec.ts (new file, in the dedicated tests/frontend/ folder per repo convention; runs via npm test -> jest --config tests/jest.config.js --selectProjects frontend).

  • Mocking Strategy:

    • Use Angular's TestBed.configureTestingModule({ imports: [ShellHeaderComponent] }) so the component renders standalone in jsdom with no router / http mocking required (the LED span has no dependencies).
    • Provide minimal inputs via the component's input.required<...> / input<...>(...) signals. Specifically, set eventState() to each of 'running' | 'countdown' | 'stopped' | 'unconfigured' using TestBed.runInInjectionContext + ComponentRef.setInput (or fixture.componentRef.setInput) and assert the DOM.
    • No HTTP, no router, no SSE — keeping the test fast and isolated.
    • Two compact assertions per case:
      1. expect(led.classList.contains('led')).toBe(true) and the matching led-<state> class.
      2. Read the component's raw styles string (via the decorated component's static ɵcmp.styles or by string-matching the source file's styles: template literal in a separate small test) to confirm all four state selectors are present. Concretely: a single test that imports the component file as text (via fs.readFileSync of the TS source — same pattern other frontend tests already use for inspecting contracts) and asserts it contains .led-running, .led-countdown, .led-stopped, .led-unconfigured, plus width: 10px, height: 10px, border-radius: 50%, and var(--color-success).
  • What is NOT tested (per instructions): no visual / screenshot confirmation, no jsdom layout reflow assertions, no large mock SSE harness — only the structural class binding and the presence of the component-scoped CSS rules. This matches the "minimal, highly-focused tests" instruction.