Skip to content

Feat: Frame events — PopupText popups and Banner notifications#644

Open
rgregg wants to merge 29 commits into
immichFrame:mainfrom
rgregg:rgregg-notification-ux
Open

Feat: Frame events — PopupText popups and Banner notifications#644
rgregg wants to merge 29 commits into
immichFrame:mainfrom
rgregg:rgregg-notification-ux

Conversation

@rgregg
Copy link
Copy Markdown
Contributor

@rgregg rgregg commented May 18, 2026

Summary

Adds a client-side event notification system to ImmichFrame, surfaced through a new POST /api/events endpoint. Two display modes:

  • PopupText — full-screen centered modal that pauses the slideshow until dismissed or auto-timeout.
  • Banner — passive top-of-screen overlay that does not pause the slideshow. Tap to dismiss; auto-dismiss on timeout; newer banners with the same category replace the older one.

Both modes can be active simultaneously (popup + banner coexist).

New settings

General:
  EventHostEnabled: false                # default off
  EventPollingIntervalSeconds: 2
  EventDefaultTimeoutMs: 15000

New API

  • POST /api/events — enqueue an event (validated; type must start with frame.ui.)
  • GET /api/events/next?deviceId=…&mode=… — kiosk poll endpoint; mode filter (PopupText | Banner) is optional
  • POST /api/events/{eventId}/ack?deviceId=… — kiosk reports Shown / Closed / Timeout / Dismissed / Error
  • GET /api/events/pending?deviceId=… — diagnostics

Example banner POST:

curl -X POST http://host:8080/api/events \
  -H 'Content-Type: application/json' \
  -d '{
    "deviceId": "<id>",
    "id": "calendar-reminder-…",
    "type": "frame.ui.banner",
    "mode": "Banner",
    "message": "Coffee with Sam in 15 minutes",
    "category": "banner.calendar",
    "timeoutMs": 8000
  }'

Implementation notes

  • In-memory queue (InMemoryFrameEventQueue) supports priority ordering, category-based replacement, expiry, and per-mode active tracking so a popup and a banner don't shadow each other in PeekNext.
  • Frontend (Svelte 5) splits the active-event store into activePopupEvent and activeBannerEvent; the home page polls both modes every interval and renders PopupTextOverlay + BannerOverlay from EventOverlayHost.
  • Slideshow pause behavior is gated on activePopupEvent only — banners never interrupt the slideshow.
  • Backwards-compatible: when EventHostEnabled is false (default), no new behavior surfaces.

Tests

  • 89 Core tests (incl. 8 new queue tests covering priority, category replacement, expiry, per-mode tracking)
  • 18 WebApi tests (incl. 5 new validator tests covering PopupText and Banner rules)
  • All passing

Design spec and implementation plan committed under docs/superpowers/specs/ and docs/superpowers/plans/ if useful for review context.

Test plan

  • dotnet test — 107/107 pass
  • cd immichFrame.Web && npm run check — zero errors
  • npm run build — succeeds
  • With EventHostEnabled: true, POST a Banner; verify it appears at top of frame and slideshow keeps advancing
  • POST a PopupText; verify it pauses the slideshow
  • POST both; verify they coexist; tap banner — only banner dismisses
  • With EventHostEnabled: false (default), POSTs return 404 — no behavior change for users who don't opt in

Summary by CodeRabbit

  • New Features

    • Added frame event system with configurable notification modes (popup text and banner notifications).
    • New event API endpoints: POST /api/events to enqueue events, GET /api/events/next to poll pending events, POST /api/events/{eventId}/ack to acknowledge events, GET /api/events/pending for diagnostics.
    • Added event configuration settings: EventHostEnabled, EventPollingIntervalSeconds, EventDefaultTimeoutMs.
    • Event notifications display with optional timeout-based auto-dismiss and user interaction support.
  • Tests

    • Added comprehensive test suites for event queue behavior, validation, and API endpoints.
  • Documentation

    • Added configuration documentation and implementation guides for the event notification system.

Review Change Stack

rgregg and others added 24 commits May 18, 2026 02:18
…TimeoutMs settings

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tifications

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Without this, FrameEventMode serializes as 0/1 instead of
"PopupText"/"Close", causing the frontend string comparison to fail.
The global converter was breaking asset type serialization (IMAGE as
string instead of int). Use [JsonConverter] on FrameEventMode and
FrameEventAckStatus instead.
The auto-generated API client type was missing the event properties,
so eventHostEnabled was never reaching the frontend config store.
Remove eventHostEnabled gate from startEventPolling — the poll loop
already checks it each iteration. This avoids timing issues where the
config store may not be populated when the function is first called.
Also remove dead $effect block and debug console.warn calls.
- Add seconds-remaining countdown indicator to the popup dismiss area
- Convert event components to Svelte 5 $props() syntax
- Fix weather temperature showing degree symbol twice by using
  weather.unit from the API instead of hardcoded degree symbol
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 18, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e7a71d1c-7413-492d-a273-4f3e12fb2e47

📥 Commits

Reviewing files that changed from the base of the PR and between 0ae6180 and 472eb5c.

📒 Files selected for processing (14)
  • ImmichFrame.Core.Tests/Events/InMemoryFrameEventQueueTests.cs
  • ImmichFrame.Core/Events/FrameEventAckStatus.cs
  • ImmichFrame.Core/Services/InMemoryFrameEventQueue.cs
  • ImmichFrame.WebApi.Tests/Events/FrameEventValidatorTests.cs
  • ImmichFrame.WebApi/Controllers/EventsController.cs
  • ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs
  • ImmichFrame.WebApi/Models/Events/FrameEventAckRequestDto.cs
  • ImmichFrame.WebApi/Models/ServerSettings.cs
  • ImmichFrame.WebApi/Services/FrameEventValidator.cs
  • docs/docs/getting-started/configuration.md
  • docs/superpowers/plans/2026-05-18-banner-notifications.md
  • docs/superpowers/specs/2026-05-18-banner-notifications-design.md
  • immichFrame.Web/src/lib/components/home-page/home-page.svelte
  • immichFrame.Web/src/lib/events/event-service.ts
🚧 Files skipped from review as they are similar to previous changes (11)
  • ImmichFrame.Core/Events/FrameEventAckStatus.cs
  • ImmichFrame.WebApi/Models/ServerSettings.cs
  • ImmichFrame.WebApi/Models/Events/FrameEventAckRequestDto.cs
  • docs/docs/getting-started/configuration.md
  • ImmichFrame.WebApi.Tests/Events/FrameEventValidatorTests.cs
  • ImmichFrame.WebApi/Services/FrameEventValidator.cs
  • ImmichFrame.Core.Tests/Events/InMemoryFrameEventQueueTests.cs
  • ImmichFrame.WebApi/Controllers/EventsController.cs
  • immichFrame.Web/src/lib/events/event-service.ts
  • ImmichFrame.Core/Services/InMemoryFrameEventQueue.cs
  • immichFrame.Web/src/lib/components/home-page/home-page.svelte

📝 Walkthrough

Walkthrough

Implements a complete frame event notification system with backend queue, REST API, and frontend Svelte components supporting PopupText and Banner modes, mode-scoped polling, acknowledgment tracking, configuration settings, and independent lifecycle management for each notification type.

Changes

Frame Event Notification System

Layer / File(s) Summary
Core domain models and queue interface
ImmichFrame.Core/Events/*, ImmichFrame.Core/Interfaces/IFrameEventQueue.cs, ImmichFrame.Core/Interfaces/IServerSettings.cs
Public FrameEvent, FrameEventMode (PopupText/Close/Banner), FrameEventAckStatus, FrameEventAction, FrameEventInput types; IFrameEventQueue interface defining enqueue, peek-by-mode, acknowledge, remove-by-category, and snapshot operations; IGeneralSettings extended with event host enable, polling interval, and default timeout.
In-memory event queue implementation and tests
ImmichFrame.Core/Services/InMemoryFrameEventQueue.cs, ImmichFrame.Core.Tests/Events/InMemoryFrameEventQueueTests.cs
Per-device concurrent queue with deduplication, priority-ordered peek, mode-scoped filtering, category-based removal, acknowledgment tracking, expiration by timeout, and 16 comprehensive test cases covering empty queue, duplicate rejection, category replacement, mode filtering, ack semantics, and cross-mode interactions.
Server settings and configuration
ImmichFrame.WebApi/Models/ServerSettings.cs, ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs, test and example resources
GeneralSettings and ServerSettingsV1 gain event host enable flag, polling interval (1–3600 seconds), and default timeout (100–300000 ms) with validation ranges; corresponding adapter extensions; test JSON/YAML and Docker example configurations.
Web API DTOs and request/response mapping
ImmichFrame.WebApi/Models/Events/*, ImmichFrame.WebApi/Models/ClientSettingsDto.cs
FrameEventRequestDto with ToDomain() conversion; FrameEventResponseDto with FromDomain() factory; FrameEventDiagnosticsDto and FrameEventStateDto for queue state; FrameEventAckRequestDto for acknowledgment; FrameEventActionDto and FrameEventInputDto sub-DTOs; ClientSettingsDto extended with event host fields.
Event validation and REST controller
ImmichFrame.WebApi/Services/FrameEventValidator.cs, ImmichFrame.WebApi/Controllers/EventsController.cs, ImmichFrame.WebApi/Program.cs, validation tests
FrameEventValidator enforces required fields, type prefix frame.ui., mode-specific message constraints, timeout bounds, and DTO-to-domain conversion; 14 test cases covering success paths and failure modes; EventsController exposes POST (enqueue), GET next (poll by device/mode), POST ack, and GET pending (diagnostics) endpoints; DI registration of queue and validator.
Frontend config store and event service
immichFrame.Web/src/lib/stores/config.store.ts, immichFrame.Web/src/lib/events/event-service.ts, immichFrame.Web/src/lib/immichFrameApi.ts
ClientSettingsWithUx extends ClientSettingsDto with event fields; config store adds patch() method and defaults eventHostEnabled to false; event service exports popup/banner stores, startEventPolling()/stopEventPolling(), concurrent mode-scoped polling loop, acknowledgeEvent() posting, and delay helper with abort support.
Svelte overlay components
immichFrame.Web/src/lib/components/events/PopupTextOverlay.svelte, BannerOverlay.svelte, EventOverlayHost.svelte
PopupTextOverlay renders title, message, action buttons, and countdown; supports backdrop/keyboard dismiss and auto-timeout. BannerOverlay displays top-of-screen fixed notification with animated progress bar. EventOverlayHost conditionally renders either overlay based on event mode and passes dismiss handlers.
Home page event integration
immichFrame.Web/src/lib/components/home-page/home-page.svelte
Subscribes to popup/banner event stores; manages independent lifecycle per mode including one-time Shown acknowledgment, per-event timeout (or default), and auto-dismissal; pauses slideshow on new popup only; clears events and timers on dismissal; mounts overlay conditionally when host enabled; comprehensive mount/unmount/destroy cleanup.
Configuration and design documentation
docs/docs/getting-started/configuration.md, docs/superpowers/specs/2026-05-18-banner-notifications-design.md, docs/superpowers/plans/2026-05-18-banner-notifications.md
Configuration guide describing event host setup, polling interval tuning, timeout defaults, mode-specific behaviors, and curl POST/GET examples; design spec covering API contracts, queue semantics, validation rules, frontend architecture, and test requirements; 11-task implementation plan with acceptance criteria.

Sequence Diagram(s)

sequenceDiagram
  participant Client as Client Device
  participant EventsAPI as /api/events
  participant Queue as Queue (per-device)
  participant Service as Event Service
  participant Store as Stores<br/>(popup/banner)
  participant HomePage as home-page.svelte
  participant Overlays as Overlays

  Client->>EventsAPI: POST /events (FrameEventRequestDto)
  EventsAPI->>Queue: EnqueueAsync(event)
  activate Queue
  Queue->>Queue: Deduplicate by ID<br/>Track category<br/>Order by priority
  deactivate Queue

  loop Every polling interval
    Service->>EventsAPI: GET /next?deviceId=X&mode=PopupText
    Service->>EventsAPI: GET /next?deviceId=X&mode=Banner (concurrent)
    EventsAPI->>Queue: PeekNextAsync(deviceId, mode)
    Queue-->>EventsAPI: FrameEvent | null
    EventsAPI-->>Service: FrameEventResponseDto | 204
  end

  Service->>Store: activePopupEvent = event
  activate Store
  Store-->>HomePage: subscribe
  deactivate Store

  HomePage->>HomePage: On new popup:<br/>Pause slideshow<br/>Ack Shown once<br/>Schedule auto-dismiss

  HomePage->>Overlays: Mount PopupTextOverlay/<br/>BannerOverlay
  activate Overlays
  Overlays->>Overlays: Show countdown<br/>Allow touch/key dismiss
  deactivate Overlays

  Overlays->>HomePage: onDismiss(Closed)
  HomePage->>EventsAPI: POST /{eventId}/ack?deviceId=X (status)
  EventsAPI->>Queue: AckAsync(eventId, Closed)
  Queue->>Queue: Remove event<br/>Clear active slot
  Service->>Store: activePopupEvent = null
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested labels

enhancement

Suggested reviewers

  • JW-CH

Poem

🐰 A frame now buzzes with banners so bright,
Popups pop up at just the right time,
Queue prioritizes with queue-ing insight,
Slideshow pauses—a notification prime!
Backend and frontend in harmony dance,
Events polled sweetly, a real-time romance! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Feat: Frame events — PopupText popups and Banner notifications' accurately and concisely summarizes the main change: adding a frame events notification system with two display modes (PopupText and Banner).
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Comment thread ImmichFrame.WebApi/Controllers/EventsController.cs Fixed
Comment thread ImmichFrame.WebApi/Controllers/EventsController.cs Fixed
Comment thread ImmichFrame.WebApi/Controllers/EventsController.cs Fixed
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

🧹 Nitpick comments (1)
docs/docs/getting-started/configuration.md (1)

195-207: ⚡ Quick win

Add an auth note to the curl example.

On Line 195, the example is easy to copy as-is, but setups with AuthenticationSecret enabled will fail without Authorization: Bearer .... Please add a one-line note (or alternate curl snippet) right under this example to prevent false-negative setup checks.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/docs/getting-started/configuration.md` around lines 195 - 207, The curl
example that posts to /api/events fails when AuthenticationSecret is enabled
because it lacks an Authorization header; update the documentation right under
the shown curl snippet (the POST to /api/events) to either add a one-line note
telling users to include Authorization: Bearer <token> when AuthenticationSecret
is enabled or provide an alternate curl snippet that includes the header
(Authorization: Bearer <your-auth-token>) so authenticated setups won't get
false-negative errors.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/superpowers/plans/2026-05-18-banner-notifications.md`:
- Around line 1177-1193: The plan doc currently embeds sensitive host/user
specifics in the shell snippets (e.g., the command string "ssh hass-hub-01.lan",
the systemd/user service "immich-frame", and the path "/home/ryan/.Xauthority"
and script "frame.sh"); replace those concrete values with neutral placeholders
(e.g., <HOSTNAME>, <USER>, <USER_HOME> or variables like $HOST, $USER_HOME) and
update the example commands to use those placeholders so the content no longer
contains internal hostnames or personal home paths while preserving the
instruction semantics.

In `@docs/superpowers/specs/2026-05-18-banner-notifications-design.md`:
- Line 8: The summary sentence contains a typo: change "renderered" to
"rendered" in the document describing the new Banner notification mode for
ImmichFrame; update the sentence that references Banner, PopupText,
FrameEventMode, and the new overlay component so it reads "...and rendered by a
new overlay component." ensuring the rest of the text and identifiers
(ImmichFrame, frame-event, FrameEventMode, PopupText) remain unchanged.

In `@ImmichFrame.Core/Events/FrameEventAckStatus.cs`:
- Around line 8-11: The enum FrameEventAckStatus is missing the Dismissed member
required by the event contract; update the enum declaration in
FrameEventAckStatus to include Dismissed (e.g., add Dismissed alongside Shown,
Closed, Timeout, Error) so the ack lifecycle can represent dismiss
acknowledgements correctly and avoid remapping/rejection of dismiss events.

In `@ImmichFrame.Core/Services/InMemoryFrameEventQueue.cs`:
- Around line 59-60: The category-based replacement currently ignores
FrameEventMode, causing an event of one mode (e.g., PopupText) to evict another
(e.g., Banner) when categories match; update InMemoryFrameEventQueue to scope
category lookups and replacements by mode—either by including the mode in the
category key (e.g., combine FrameEventMode with category) or by maintaining a
per-mode category dictionary, and then use that scoped mapping wherever
_byCategory is accessed/modified (references: _byCategory, _activeEventIdByMode,
and the add/remove logic around lines handling category replacement and the
blocks noted at 78-82 and 172-173) so replacements only affect events of the
same FrameEventMode.

In `@immichFrame.Web/src/lib/components/elements/clock.svelte`:
- Line 93: The temperature expression can produce "undefined°" when
weather.temperature is absent; update the template so the temperature and unit
are only rendered when temperature is present. Replace the inline expression in
the div with a conditional/guard that checks weather.temperature (e.g., in the
weather-temperature div or a small helper like a computed/derived value) and
render either the formatted toFixed(1) plus unit (weather.unit ?? '°') when
non-null, or an empty string/placeholder when missing to avoid showing
"undefined".

In `@immichFrame.Web/src/lib/components/home-page/home-page.svelte`:
- Around line 192-207: The early return in handlePopupEvent when
!$configStore.eventHostEnabled skips necessary cleanup (timers/state) so ensure
you run the same cleanup sequence before returning: call clearPopupTimer(), set
currentPopup = null, popupShownAcked = false, lastPopupId = null, and if
popupPausedSlideshow and progressBar then resume via progressBar.play() and set
popupPausedSlideshow = false; you can either move that cleanup block above the
if (!($configStore.eventHostEnabled ?? false)) check or extract it to a helper
(e.g., cleanupPopupState) and invoke it both before the early return and in the
existing null-event branch to avoid duplicated logic.

In `@immichFrame.Web/src/lib/events/event-service.ts`:
- Around line 125-143: The delay() function for polling attaches an 'abort'
listener that only auto-removes if the signal aborts, causing listener
accumulation; fix it in the delay(durationMs: number, signal: AbortSignal)
implementation by extracting the abort handler into a const (e.g., const onAbort
= () => { clearTimeout(timeout); resolve(); }) and registering it with
signal.addEventListener('abort', onAbort, { once: true }), then in the timeout
callback (the normal resolution path) call signal.removeEventListener('abort',
onAbort) after clearTimeout/resolve so the handler is removed when the timer
completes; ensure the same handler reference is used for both addEventListener
and removeEventListener to prevent leaks.
- Around line 93-103: The poll and ack fetch handlers currently only handle
200/204 and silently ignore 4xx/5xx responses; update the fetch result handling
for the /api/events/next call (the fetch with url
`/api/events/next?deviceId=${encodeURIComponent(deviceId)}&mode=${mode}`) and
the corresponding ack fetch so that non-2xx responses are handled explicitly:
detect response.ok === false, call the appropriate logger (or console.error)
with response.status and response.statusText and any returned error body, and
either set the store to an error/state indicating failure or throw an Error to
propagate the failure so callers can react instead of leaving stale UI state.
Ensure the same explicit handling is added to the ack flow so failed
acknowledgements are logged and surfaced.

In `@ImmichFrame.WebApi/Controllers/EventsController.cs`:
- Around line 69-77: AckEvent currently dereferences request.Status without
checking that the request body exists; add a null check for the
FrameEventAckRequestDto request at the start of AckEvent and return BadRequest
(e.g., new { message = "request body is required" }) if request is null before
calling _queue.AckAsync(deviceId, eventId, request.Status, cancellationToken),
ensuring you still validate deviceId and _settings.EventHostEnabled as before.

In `@ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs`:
- Around line 59-61: The V1 settings are forwarded without validation; add
bounds checks when mapping/using ServerSettingsV1.EventPollingIntervalSeconds
and ServerSettingsV1.EventDefaultTimeoutMs (and honor EventHostEnabled) in the
V1 adapter path or mapping function so bad legacy values can't create tight
loops or immediate timeouts; enforce reasonable ranges (e.g.
EventPollingIntervalSeconds >= 1 and <= 3600, EventDefaultTimeoutMs >= 1000 and
<= 300000), clamp out-of-range values to defaults or the nearest bound, and emit
a warning log mentioning ServerSettingsV1.EventPollingIntervalSeconds /
ServerSettingsV1.EventDefaultTimeoutMs when adjustments are made.

In `@ImmichFrame.WebApi/Models/Events/FrameEventAckRequestDto.cs`:
- Around line 8-9: The Status property on FrameEventAckRequestDto is currently a
non-nullable enum which prevents [Required] from detecting missing JSON; change
the type of Status to a nullable enum (FrameEventAckStatus?) so the model binder
can set null for omitted fields and [Required] will validate correctly, then
review any usages of FrameEventAckRequestDto.Status (e.g., code reading Status)
and handle the null case or validate before use.

In `@ImmichFrame.WebApi/Models/ServerSettings.cs`:
- Around line 75-77: ServerSettings currently exposes
EventPollingIntervalSeconds and EventDefaultTimeoutMs without bounds checks,
allowing <=0 values that break event hosting; add validation to enforce sensible
minima (e.g., EventPollingIntervalSeconds >= 1 and EventDefaultTimeoutMs >= 1 or
a practical lower bound like 100), either by applying DataAnnotations (e.g.,
Range) to the properties or by enforcing and normalizing values in the
ServerSettings constructor or property setters, and surface validation errors
(throw ArgumentOutOfRangeException or similar) so invalid configuration cannot
be used when EventHostEnabled is true.

In `@ImmichFrame.WebApi/Services/FrameEventValidator.cs`:
- Around line 19-22: The Validate method in FrameEventValidator currently
dereferences dto without a null check; add an explicit null guard at the start
of FrameEventValidator.Validate(FrameEventRequestDto dto) that throws a
ValidationException (e.g., "dto is required" or "request body is required") when
dto is null, before accessing dto.DeviceId (and any other fields), so callers
receive a ValidationException instead of a NullReferenceException.

---

Nitpick comments:
In `@docs/docs/getting-started/configuration.md`:
- Around line 195-207: The curl example that posts to /api/events fails when
AuthenticationSecret is enabled because it lacks an Authorization header; update
the documentation right under the shown curl snippet (the POST to /api/events)
to either add a one-line note telling users to include Authorization: Bearer
<token> when AuthenticationSecret is enabled or provide an alternate curl
snippet that includes the header (Authorization: Bearer <your-auth-token>) so
authenticated setups won't get false-negative errors.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4251090a-f90b-429b-b3f7-6b49a74d3a99

📥 Commits

Reviewing files that changed from the base of the PR and between 8e94c69 and 0ae6180.

📒 Files selected for processing (36)
  • ImmichFrame.Core.Tests/Events/InMemoryFrameEventQueueTests.cs
  • ImmichFrame.Core/Events/FrameEvent.cs
  • ImmichFrame.Core/Events/FrameEventAckStatus.cs
  • ImmichFrame.Core/Events/FrameEventAction.cs
  • ImmichFrame.Core/Events/FrameEventInput.cs
  • ImmichFrame.Core/Events/FrameEventMode.cs
  • ImmichFrame.Core/Interfaces/IFrameEventQueue.cs
  • ImmichFrame.Core/Interfaces/IServerSettings.cs
  • ImmichFrame.Core/Services/InMemoryFrameEventQueue.cs
  • ImmichFrame.WebApi.Tests/Events/FrameEventValidatorTests.cs
  • ImmichFrame.WebApi.Tests/Resources/TestV1.json
  • ImmichFrame.WebApi.Tests/Resources/TestV2.json
  • ImmichFrame.WebApi.Tests/Resources/TestV2.yml
  • ImmichFrame.WebApi/Controllers/EventsController.cs
  • ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs
  • ImmichFrame.WebApi/Models/ClientSettingsDto.cs
  • ImmichFrame.WebApi/Models/Events/FrameEventAckRequestDto.cs
  • ImmichFrame.WebApi/Models/Events/FrameEventDiagnosticsDto.cs
  • ImmichFrame.WebApi/Models/Events/FrameEventRequestDto.cs
  • ImmichFrame.WebApi/Models/Events/FrameEventResponseDto.cs
  • ImmichFrame.WebApi/Models/ServerSettings.cs
  • ImmichFrame.WebApi/Program.cs
  • ImmichFrame.WebApi/Services/FrameEventValidator.cs
  • docker/Settings.example.json
  • docker/Settings.example.yml
  • docs/docs/getting-started/configuration.md
  • docs/superpowers/plans/2026-05-18-banner-notifications.md
  • docs/superpowers/specs/2026-05-18-banner-notifications-design.md
  • immichFrame.Web/src/lib/components/elements/clock.svelte
  • immichFrame.Web/src/lib/components/events/BannerOverlay.svelte
  • immichFrame.Web/src/lib/components/events/EventOverlayHost.svelte
  • immichFrame.Web/src/lib/components/events/PopupTextOverlay.svelte
  • immichFrame.Web/src/lib/components/home-page/home-page.svelte
  • immichFrame.Web/src/lib/events/event-service.ts
  • immichFrame.Web/src/lib/immichFrameApi.ts
  • immichFrame.Web/src/lib/stores/config.store.ts

Comment thread docs/superpowers/plans/2026-05-18-banner-notifications.md
Comment thread docs/superpowers/specs/2026-05-18-banner-notifications-design.md Outdated
Comment thread ImmichFrame.Core/Events/FrameEventAckStatus.cs Outdated
Comment thread ImmichFrame.Core/Services/InMemoryFrameEventQueue.cs Outdated

<div class="weather-location">{weather.location},</div>
<div class="weather-temperature">{weather.temperature?.toFixed(1)}°</div>
<div class="weather-temperature">{weather.temperature?.toFixed(1)}{weather.unit ?? '°'}</div>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Avoid rendering undefined when temperature is missing.

weather.temperature?.toFixed(1) can render as undefined, producing undefined° (or undefined{unit}) in the UI. Add a fallback for missing temperature.

Proposed fix
-            <div class="weather-temperature">{weather.temperature?.toFixed(1)}{weather.unit ?? '°'}</div>
+            <div class="weather-temperature">
+                {weather.temperature == null ? '—' : `${weather.temperature.toFixed(1)}${weather.unit ?? '°'}`}
+            </div>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div class="weather-temperature">{weather.temperature?.toFixed(1)}{weather.unit ?? '°'}</div>
<div class="weather-temperature">
{weather.temperature == null ? '' : `${weather.temperature.toFixed(1)}${weather.unit ?? '°'}`}
</div>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@immichFrame.Web/src/lib/components/elements/clock.svelte` at line 93, The
temperature expression can produce "undefined°" when weather.temperature is
absent; update the template so the temperature and unit are only rendered when
temperature is present. Replace the inline expression in the div with a
conditional/guard that checks weather.temperature (e.g., in the
weather-temperature div or a small helper like a computed/derived value) and
render either the formatted toFixed(1) plus unit (weather.unit ?? '°') when
non-null, or an empty string/placeholder when missing to avoid showing
"undefined".

Comment thread ImmichFrame.WebApi/Controllers/EventsController.cs Outdated
Comment on lines +59 to +61
public bool EventHostEnabled { get; set; } = false;
public int EventPollingIntervalSeconds { get; set; } = 2;
public int EventDefaultTimeoutMs { get; set; } = 15000;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validate event timing values in the V1 adapter path as well.

These new values are forwarded directly through the adapter without bounds checks; invalid values can still trigger tight polling loops or immediate timeout behavior in legacy-config deployments (Line 145 currently no-op).

Proposed fix
     class GeneralSettingsV1Adapter(ServerSettingsV1 _delegate) : IGeneralSettings
     {
@@
-        public void Validate() { }
+        public void Validate()
+        {
+            if (_delegate.EventPollingIntervalSeconds <= 0)
+            {
+                throw new InvalidOperationException("EventPollingIntervalSeconds must be greater than 0.");
+            }
+
+            if (_delegate.EventDefaultTimeoutMs <= 0)
+            {
+                throw new InvalidOperationException("EventDefaultTimeoutMs must be greater than 0.");
+            }
+        }
     }

Also applies to: 141-143

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs` around lines 59 - 61,
The V1 settings are forwarded without validation; add bounds checks when
mapping/using ServerSettingsV1.EventPollingIntervalSeconds and
ServerSettingsV1.EventDefaultTimeoutMs (and honor EventHostEnabled) in the V1
adapter path or mapping function so bad legacy values can't create tight loops
or immediate timeouts; enforce reasonable ranges (e.g.
EventPollingIntervalSeconds >= 1 and <= 3600, EventDefaultTimeoutMs >= 1000 and
<= 300000), clamp out-of-range values to defaults or the nearest bound, and emit
a warning log mentioning ServerSettingsV1.EventPollingIntervalSeconds /
ServerSettingsV1.EventDefaultTimeoutMs when adjustments are made.

Comment thread ImmichFrame.WebApi/Models/Events/FrameEventAckRequestDto.cs Outdated
Comment on lines +75 to +77
public bool EventHostEnabled { get; set; } = false;
public int EventPollingIntervalSeconds { get; set; } = 2;
public int EventDefaultTimeoutMs { get; set; } = 15000;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add bounds validation for event timing settings.

EventPollingIntervalSeconds and EventDefaultTimeoutMs are unvalidated; invalid values (e.g., <= 0) can cause tight poll loops or immediate timeout behavior when event hosting is enabled (Line 79 is currently a no-op).

Proposed fix
 public class GeneralSettings : IGeneralSettings, IConfigSettable
 {
@@
-    public void Validate() { }
+    public void Validate()
+    {
+        if (EventPollingIntervalSeconds <= 0)
+        {
+            throw new InvalidOperationException("General.EventPollingIntervalSeconds must be greater than 0.");
+        }
+
+        if (EventDefaultTimeoutMs <= 0)
+        {
+            throw new InvalidOperationException("General.EventDefaultTimeoutMs must be greater than 0.");
+        }
+    }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ImmichFrame.WebApi/Models/ServerSettings.cs` around lines 75 - 77,
ServerSettings currently exposes EventPollingIntervalSeconds and
EventDefaultTimeoutMs without bounds checks, allowing <=0 values that break
event hosting; add validation to enforce sensible minima (e.g.,
EventPollingIntervalSeconds >= 1 and EventDefaultTimeoutMs >= 1 or a practical
lower bound like 100), either by applying DataAnnotations (e.g., Range) to the
properties or by enforcing and normalizing values in the ServerSettings
constructor or property setters, and surface validation errors (throw
ArgumentOutOfRangeException or similar) so invalid configuration cannot be used
when EventHostEnabled is true.

Comment thread ImmichFrame.WebApi/Services/FrameEventValidator.cs
rgregg added 3 commits May 18, 2026 20:59
- Add Dismissed to FrameEventAckStatus
- Scope queue category replacement by mode so Banner/PopupText do not evict each other
- Make FrameEventAckRequestDto.Status nullable so [Required] validates missing JSON
- Null-guard FrameEventValidator.Validate and EventsController.AckEvent
- Strip CR/LF from user-supplied log values
The 'show unit from API' change was bundled into commit fcf9c15 along with
unrelated popup countdown work. Split out to its own branch
(rgregg-weather-unit-fix) so this PR stays focused on the notification
system.
rgregg added 2 commits May 18, 2026 21:13
- delay() removes its abort listener on normal timeout path (no leak under long uptime)
- pollOne / acknowledgeEvent log non-2xx responses
- handlePopupEvent / handleBannerEvent run full cleanup before eventHostEnabled
  early-return so a mid-flight disable does not leave the slideshow paused
- Replace embedded hostnames / home paths in plan with placeholders
- Fix 'renderered' typo in spec
- Add Authorization-header note under the curl example
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants