179 lines
8.3 KiB
Markdown
179 lines
8.3 KiB
Markdown
# 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 26–34).
|
||
|
||
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.ts`
|
||
uses `styleUrls: ['./setup-create-admin.component.css']`;
|
||
`frontend/src/app/features/landing/landing.component.ts` likewise.
|
||
For a small block of CSS that belongs to a single component, the
|
||
inline `styles: [\`...\`] 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 10–88) 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 23–27)
|
||
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 26–34) 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.
|