7.2 KiB
type, title, description, tags, timestamp
| type | title | description | tags | timestamp | ||||||
|---|---|---|---|---|---|---|---|---|---|---|
| guide | Authenticated Shell | How a signed-in user experiences the post-login shell, including authenticated event streaming. |
|
2026-07-22T09:35:00Z |
When this view is available
The authenticated shell is the post-login UI rendered by HomeComponent
(frontend/src/app/features/home/home.component.ts). It is gated by the client
authGuard and the JWT-protected /api/v1/events/status stream.
A user who is not signed in, or whose session has expired, is sent to /login
before the shell mounts.
How to access (tester steps)
- Ensure the instance is initialized. If not, follow First-Run Bootstrap.
- Sign in via
/loginas an admin or player. - Confirm the browser lands on
/challengesand renders the shell.
Expected behavior
Header
| Element | Selector | Expected |
|---|---|---|
| Page title | [data-testid="shell-title"] |
Shows the bootstrap pageTitle; clicking navigates to /challenges. |
| Active section | [data-testid="shell-active-section"] |
Shows Challenges, Scoreboard, Blog, or Admin based on the URL. |
| Event LED | [data-testid="shell-led"] |
A 10×10 circular dot whose color reflects the current event state. See Event LED colors below. |
| Countdown | [data-testid="shell-countdown"] |
Displays DD:HH:mm, Event ended, or empty for an unconfigured event. |
| User menu | [data-testid="user-menu-trigger"] |
Toggles rank, change-password, optional admin, and logout entries. |
Event LED colors
The LED is rendered by ShellHeaderComponent as a <span class="led"> with one of four state classes attached via [class.led-<state>] and a [style.background-color] bound directly from a pure TypeScript helper.
| Event state | CSS class | Color token ([style.background-color] value) |
Approx. hex | Visual |
|---|---|---|---|---|
countdown |
led led-countdown |
var(--color-warning) |
#f59e0b |
Yellow filled dot |
running |
led led-running |
var(--color-success) |
#10b981 |
Green filled dot |
stopped |
led led-stopped |
var(--color-danger) |
#ef4444 |
Red filled dot |
unconfigured |
led led-unconfigured |
var(--color-secondary) |
#64748b |
Neutral gray filled dot |
The mapping is defined in frontend/src/app/core/services/event-status.pure.ts as the constant LED_COLOR_BY_STATE and exposed through the helper ledBackgroundColor(state). ShellHeaderComponent exposes it on the DOM via a computed signal called ledColor(), so the active theme token resolves at runtime and the LED is never painted with transparent.
The base .led rule paints a 10×10 circular inline-block, so the LED
is always the same size and shape — only the background color changes
between states. The element also carries
aria-label="Event status: <state>" for assistive tech.
What the tester should see
- The 10×10 LED is visible (not transparent, not invisible) in every state.
- The LED color matches the table above for the active
EventState. - Switching the event window (countdown → running → stopped → unconfigured) flips the dot color in real time.
- The
aria-labelupdates toEvent status: <state>and never relies on color alone.
Quick tabs
The tab strip [data-testid="quick-tabs"] contains Challenges, Scoreboard, and
Blog. Clicking a tab navigates to its matching route; the active tab is marked
with the active class.
Live event window
HomeComponent.ngOnInit()startsEventStatusStorewithAuthenticatedEventSourceService.open('/api/v1/events/status').- The transport uses
fetch, sendsAccept: text/event-stream, includesAuthorization: Bearer <access-token>when available, and sends credentials. - It parses SSE
event:anddata:lines, buffers frames until a message listener is registered, and dispatchesMessageEventpayloads. - The store applies each state frame and advances countdown values with a local one-second tick between server pushes.
- Leaving the shell closes the source, aborts the fetch, and clears the tick.
A failed HTTP response, missing stream body, or streaming failure dispatches an error event. The shell retains the last event state until a later connection succeeds.
Architecture map
| File | Responsibility |
|---|---|
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; 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. |
Tester flows
Live event window
- Configure a future start time and confirm the countdown LED and text appear.
- Move the start time into the past and the end time into the future; confirm the LED changes to running and the countdown counts toward the end.
- Move the end time into the past; confirm
Event endedand the stopped LED. - Remove either setting; confirm the unconfigured LED and empty countdown.
Navigation and logout
- Click each quick tab and confirm the matching route and active styling.
- Open the user menu and click Logout.
- Confirm navigation to
/loginand that the event stream is closed. - With two open tabs of the same browser profile both on authenticated
routes, log out in one. Confirm the other tab also navigates to
/loginwithout any user interaction. See Cross-Tab Authenticated Shell Invalidation for the full tester matrix (peer logout, SSE unauthorized, negative cases).
Unauthorized SSE response
- Sign in once and open the authenticated shell.
- Trigger an unauthorized response on
/api/v1/events/status(e.g. by revoking the refresh cookie via an admin tool or by expiring the session server-side). - Confirm the shell clears, the event stream is closed, and the router
navigates to
/login. The same flow in another open tab is propagated via the cross-tab broadcast — see Cross-Tab Authenticated Shell Invalidation.