Make WebUI pages live-update in Chrome on ChromeOS (Chromebook) #12

Closed
opened 2026-05-31 22:19:10 +02:00 by Brummel · 2 comments
Owner

The WebUI does not live-update its pages on a Chromebook (Chrome browser). On a CachyOS desktop the same pages update as expected. Other devices and browsers are not yet tested.

Reported symptom (not yet reproduced in-tree)

  • Pages in the WebUI do not refresh on a Chromebook running Chrome; the open page stays stale until a manual reload.
  • Claim (unverified): the exact stale surface (case list, case page, recordings page) was not pinned down by the reporter.
  • The same flow works on a CachyOS desktop. The browser/engine used there is not yet recorded.

How auto-refresh is supposed to work (verified from source)

Live updates are driven by Server-Sent Events, not polling:

  • GET /events streams text/event-stream, one connection per tab, with a 15 s keep-alive comment — server/src/routes/events.rs:21-55.
  • The page templates open new EventSource('/events') and, on a case event, call location.reload() after a 300 ms debounce:
    • server/templates/my_cases.html:247-249
    • server/templates/case_page.html:806-808
    • server/templates/case_recordings.html:419

If the EventSource connection never opens, is buffered, or is dropped, the page never reloads — consistent with the symptom.

Hypotheses (all unverified)

  1. Reverse-proxy SSE buffering. app.doctate.de terminates TLS at an OpenResty proxy in front of the server (app.doctate.de -> minerva:3000). The /events response sets a 15 s keep-alive but no X-Accel-Buffering: no header (server/src/routes/events.rs:50-54); nginx/OpenResty buffers text/event-stream by default, which would delay or swallow events. This only explains the desktop/Chromebook difference if the desktop reaches the server by a different path (for example minerva.lan:3000 directly, bypassing the proxy) — not confirmed.
  2. Chrome on ChromeOS suspending or capping the EventSource connection (background-tab throttling, network handoff, or device sleep), so the stream never delivers.
  3. Stale-page caching specific to the Chromebook. No per-page Cache-Control is set on the HTML routes, so an intermediary or ChromeOS could serve cached HTML.

What would disambiguate (to gather)

  • The exact URL each device opens: app.doctate.de (through the OpenResty proxy) versus minerva.lan:3000 (direct). If only the proxied path fails, hypothesis 1 is most likely.
  • The browser/engine on the working CachyOS desktop (Chrome versus Firefox).
  • On the Chromebook, the DevTools Network panel state of the /events request: whether it opens and stays pending (streaming), errors, or closes immediately, and whether case events arrive.

Acceptance

  • Root cause identified and recorded here with evidence.
  • WebUI pages live-update in Chrome on ChromeOS without a manual reload.
  • Regression guard appropriate to the cause (for example proxy config checked in, or an X-Accel-Buffering: no header on /events with a test asserting it).
The WebUI does not live-update its pages on a Chromebook (Chrome browser). On a CachyOS desktop the same pages update as expected. Other devices and browsers are not yet tested. ## Reported symptom (not yet reproduced in-tree) - Pages in the WebUI do not refresh on a Chromebook running Chrome; the open page stays stale until a manual reload. - Claim (unverified): the exact stale surface (case list, case page, recordings page) was not pinned down by the reporter. - The same flow works on a CachyOS desktop. The browser/engine used there is not yet recorded. ## How auto-refresh is supposed to work (verified from source) Live updates are driven by Server-Sent Events, not polling: - `GET /events` streams `text/event-stream`, one connection per tab, with a 15 s keep-alive comment — `server/src/routes/events.rs:21-55`. - The page templates open `new EventSource('/events')` and, on a `case` event, call `location.reload()` after a 300 ms debounce: - `server/templates/my_cases.html:247-249` - `server/templates/case_page.html:806-808` - `server/templates/case_recordings.html:419` If the EventSource connection never opens, is buffered, or is dropped, the page never reloads — consistent with the symptom. ## Hypotheses (all unverified) 1. Reverse-proxy SSE buffering. `app.doctate.de` terminates TLS at an OpenResty proxy in front of the server (`app.doctate.de` -> `minerva:3000`). The `/events` response sets a 15 s keep-alive but no `X-Accel-Buffering: no` header (`server/src/routes/events.rs:50-54`); nginx/OpenResty buffers `text/event-stream` by default, which would delay or swallow events. This only explains the desktop/Chromebook difference if the desktop reaches the server by a different path (for example `minerva.lan:3000` directly, bypassing the proxy) — not confirmed. 2. Chrome on ChromeOS suspending or capping the EventSource connection (background-tab throttling, network handoff, or device sleep), so the stream never delivers. 3. Stale-page caching specific to the Chromebook. No per-page `Cache-Control` is set on the HTML routes, so an intermediary or ChromeOS could serve cached HTML. ## What would disambiguate (to gather) - The exact URL each device opens: `app.doctate.de` (through the OpenResty proxy) versus `minerva.lan:3000` (direct). If only the proxied path fails, hypothesis 1 is most likely. - The browser/engine on the working CachyOS desktop (Chrome versus Firefox). - On the Chromebook, the DevTools Network panel state of the `/events` request: whether it opens and stays pending (streaming), errors, or closes immediately, and whether `case` events arrive. ## Acceptance - [ ] Root cause identified and recorded here with evidence. - [ ] WebUI pages live-update in Chrome on ChromeOS without a manual reload. - [ ] Regression guard appropriate to the cause (for example proxy config checked in, or an `X-Accel-Buffering: no` header on `/events` with a test asserting it).
Brummel added the bug label 2026-05-31 22:19:10 +02:00
Author
Owner

Diagnosis update follows below.

Diagnosis update follows below.
Author
Owner

Differential confirmed by the reporter: the Chromebook is the only device reaching the WebUI through the public hostname app.doctate.de; the working devices hit minerva.lan:3000 directly. The failure tracks the proxied path exactly, placing the root cause in the reverse-proxy hop — not in the app's SSE logic and not in Chrome itself. Hypothesis 1 stands; 2 and 3 are demoted.

Proxy identified: the TLS reverse proxy is Nginx Proxy Manager (plain nginx, admin UI at 192.168.178.102:81), not OpenResty as the issue body's hypothesis 1 assumed. nginx buffers text/event-stream by default, which holds back the SSE frames the WebUI relies on for location.reload(). The buffering setting itself was not read out of the proxy config — flagged as inference from the proxied-versus-direct differential plus the documented nginx default.

Two fix paths:

  • In-repo (preferred): emit X-Accel-Buffering: no (plus Cache-Control: no-cache) on the /events response in server/src/routes/events.rs. nginx honors an upstream X-Accel-Buffering: no response header to disable buffering for that single response, so it unblocks SSE without any proxy-side change, travels with the code, and is unit-testable.
  • Proxy-side: add proxy_buffering off; to the doctate proxy host's Advanced config in Nginx Proxy Manager. Effective but lives in the proxy GUI, not version control.

Regression guard for the in-repo path: an integration test asserting GET /events carries Content-Type: text/event-stream and X-Accel-Buffering: no.

Differential confirmed by the reporter: the Chromebook is the only device reaching the WebUI through the public hostname `app.doctate.de`; the working devices hit `minerva.lan:3000` directly. The failure tracks the proxied path exactly, placing the root cause in the reverse-proxy hop — not in the app's SSE logic and not in Chrome itself. Hypothesis 1 stands; 2 and 3 are demoted. Proxy identified: the TLS reverse proxy is Nginx Proxy Manager (plain nginx, admin UI at `192.168.178.102:81`), not OpenResty as the issue body's hypothesis 1 assumed. nginx buffers `text/event-stream` by default, which holds back the SSE frames the WebUI relies on for `location.reload()`. The buffering setting itself was not read out of the proxy config — flagged as inference from the proxied-versus-direct differential plus the documented nginx default. Two fix paths: - In-repo (preferred): emit `X-Accel-Buffering: no` (plus `Cache-Control: no-cache`) on the `/events` response in `server/src/routes/events.rs`. nginx honors an upstream `X-Accel-Buffering: no` response header to disable buffering for that single response, so it unblocks SSE without any proxy-side change, travels with the code, and is unit-testable. - Proxy-side: add `proxy_buffering off;` to the doctate proxy host's Advanced config in Nginx Proxy Manager. Effective but lives in the proxy GUI, not version control. Regression guard for the in-repo path: an integration test asserting `GET /events` carries `Content-Type: text/event-stream` and `X-Accel-Buffering: no`.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Brummel/doctate#12