From d134a56abe00e699df0dd35b54612d1a3cd9be35 Mon Sep 17 00:00:00 2001 From: m0rph3us1987 Date: Wed, 22 Jul 2026 09:23:36 +0000 Subject: [PATCH] AI Implementation feature(879): Authenticated Shell, Quick Tabs and Change Password 1.03 (#18) --- .kilo/plans/878.md | 221 ------------------ .kilo/plans/879.md | 178 ++++++++++++++ docs/architecture/frontend-structure.md | 4 +- docs/guides/authenticated-shell.md | 12 +- docs/index.md | 2 +- .../shell/header/shell-header.component.ts | 16 ++ tests/frontend/shell-led.spec.ts | 54 +++++ 7 files changed, 255 insertions(+), 232 deletions(-) delete mode 100644 .kilo/plans/878.md create mode 100644 .kilo/plans/879.md create mode 100644 tests/frontend/shell-led.spec.ts diff --git a/.kilo/plans/878.md b/.kilo/plans/878.md deleted file mode 100644 index 7f3d8c8..0000000 --- a/.kilo/plans/878.md +++ /dev/null @@ -1,221 +0,0 @@ -# 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 `` - 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-` 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 `` (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('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 `` 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. diff --git a/.kilo/plans/879.md b/.kilo/plans/879.md new file mode 100644 index 0000000..b24bd24 --- /dev/null +++ b/.kilo/plans/879.md @@ -0,0 +1,178 @@ +# Implementation Plan: Authenticated Shell — Component-Scoped LED Fix (Job 879, iteration 1.03) + +## Status + +The Job 879 reproduction shows that the LED `` +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 `` +carries `led` plus the correct `led-` 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 `` 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-]` 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-` 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 `` + 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-` 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. diff --git a/docs/architecture/frontend-structure.md b/docs/architecture/frontend-structure.md index 442c4d7..48f6a5c 100644 --- a/docs/architecture/frontend-structure.md +++ b/docs/architecture/frontend-structure.md @@ -3,7 +3,7 @@ type: architecture title: Frontend Structure description: Angular routes, components, services, guards, interceptors, and authenticated SSE transport. tags: [architecture, frontend, angular, shell, sse] -timestamp: 2026-07-21T23:25:00Z +timestamp: 2026-07-22T09:22:34Z --- # Routes @@ -25,7 +25,7 @@ Routes live in `frontend/src/app/app.routes.ts`: | Symbol | Path | Responsibility | |---|---|---| | `HomeComponent` | `frontend/src/app/features/home/home.component.ts` | Smart shell; owns navigation signals, user hydration, change-password flow, and event-stream lifecycle. | -| `ShellHeaderComponent` | `frontend/src/app/features/shell/header/shell-header.component.ts` | Renders title, active section, event LED/countdown, and user menu. | +| `ShellHeaderComponent` | `frontend/src/app/features/shell/header/shell-header.component.ts` | Renders title, active section, event LED/countdown, and user menu; contains component-scoped LED styling. | | `QuickTabsComponent` | `frontend/src/app/features/shell/tabs/quick-tabs.component.ts` | Emits navigation events for Challenges, Scoreboard, and Blog. | | `EventStatusStore` | `frontend/src/app/core/services/event-status.store.ts` | Applies event state frames and computes countdown text. | | `AuthenticatedEventSourceService` | `frontend/src/app/core/services/authenticated-event-source.service.ts` | Opens authenticated SSE over `fetch`, parsing streamed frames into `EventSourceLike` events. | diff --git a/docs/guides/authenticated-shell.md b/docs/guides/authenticated-shell.md index efa39b8..81e4d8f 100644 --- a/docs/guides/authenticated-shell.md +++ b/docs/guides/authenticated-shell.md @@ -3,7 +3,7 @@ type: guide title: Authenticated Shell description: How a signed-in user experiences the post-login shell, including authenticated event streaming. tags: [guide, shell, header, sse, navigation, tester] -timestamp: 2026-07-22T09:03:53Z +timestamp: 2026-07-22T09:22:34Z --- # When this view is available @@ -35,11 +35,7 @@ before the shell mounts. # Event LED colors -The LED is rendered by `ShellHeaderComponent` as a `` -with one of four state classes attached via `[class.led-]`. The -visual styling lives in `frontend/src/styles.css` and is driven entirely -by the existing theme color tokens — there is no per-state color in -TypeScript. +The LED is rendered by `ShellHeaderComponent` as a `` with one of four state classes attached via `[class.led-]`. Its component-scoped styling is declared in `frontend/src/app/features/shell/header/shell-header.component.ts` and is driven entirely by the existing theme color tokens — there is no per-state color in TypeScript. | Event state | CSS class | Color token | Approx. hex | Visual | |-------------------|-----------------------|-----------------------|-------------|--------| @@ -81,8 +77,8 @@ error event. The shell retains the last event state until a later connection suc | `frontend/src/app/features/home/home.component.ts` | Hosts the shell and starts/stops the authenticated event stream. | | `frontend/src/app/core/services/authenticated-event-source.service.ts` | Fetch-based authenticated SSE transport with abort and frame parsing. | | `frontend/src/app/core/services/event-status.store.ts` | Applies event frames and derives the live countdown. | -| `frontend/src/app/features/shell/header/shell-header.component.ts` | Renders event LED, countdown, navigation, and user menu. | -| `frontend/src/styles.css` | Defines the `.led` base shape plus the four `.led-{state}` background-color rules keyed on theme tokens. | +| `frontend/src/app/features/shell/header/shell-header.component.ts` | Renders event LED, countdown, navigation, and user menu; owns the component-scoped LED shape and state color rules. | +| `frontend/src/styles.css` | Defines the shared theme color tokens consumed by the shell LED. | | `frontend/src/app/features/shell/tabs/quick-tabs.component.ts` | Renders the main navigation tabs. | | `tests/frontend/authenticated-event-source.spec.ts` | Verifies authorization headers, SSE frame delivery, and anonymous header omission. | diff --git a/docs/index.md b/docs/index.md index 28459fa..b689d13 100644 --- a/docs/index.md +++ b/docs/index.md @@ -11,7 +11,7 @@ scoreboard, an event window with a public countdown, theming, and admin controls. The docs below are organized by purpose so agents can pull just the slice -they need. Last regenerated 2026-07-22T09:03:53Z. +they need. Last regenerated 2026-07-22T09:22:34Z. # Architecture diff --git a/frontend/src/app/features/shell/header/shell-header.component.ts b/frontend/src/app/features/shell/header/shell-header.component.ts index f48af34..2e6a06a 100644 --- a/frontend/src/app/features/shell/header/shell-header.component.ts +++ b/frontend/src/app/features/shell/header/shell-header.component.ts @@ -7,6 +7,22 @@ import { EventState } from '../../../core/services/event-status.store'; standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule], + styles: [` + :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); } + `], template: `