feat(ui): in-session maintenance overlay on the edge 503 marker #171

Merged
developer merged 2 commits from feature/maintenance-overlay into development 2026-07-03 20:52:05 +00:00
Owner

Что

Оверлей «технические работы» в самом SPA — закрывает зазор, который статичная caddy-страница не ловит: юзер, УЖЕ сидящий в приложении, её не видит, у него просто начинают падать запросы. Комплемент к гейту из #170.

Механика:

  • Детект строго по X-Scrabble-Maintenance: 1 (не по любому unavailable — транзиент уже покрыт индикатором «Подключение…») на СЫРОМ ConnectError в двух catch-сайтах transport (unary exec + live subscribe), ДО того как toGatewayError срежет заголовки. Чистый maintenanceRetryMs() — юнит-тест.
  • Неубираемый затемнённый оверлей (клон Splash-шелла, в стиле caddy-страницы: тайл «Э» + текст). Гаснет сам: capped-backoff поллинг (дешёвый read, зеркало connection.svelte.ts) снимает на первом успехе; кнопка «Повторить» форсит проверку. Залипнуть не может.
  • fail-fast: при детекте read-петля не жжёт 6 ретраев, сразу отдаёт управление оверлею.
  • i18n RU «Технические работы» / EN; хук window.__maint (мок реального 503 не даёт) + Playwright-спек.

Проверено локально

pnpm check 0 ошибок, unit 402 (incl. 6 новых детектора), build ок, e2e 186 (Chromium+WebKit, incl. 2 новых — оверлей рендерится/гаснет по __maint).

От тебя

  • Визуальный ревью на контуре после деплоя (я pnpm/localhost тебе показать не могу). Для реального 503: подними флаг на тест-caddy (docker run --rm -v <SCRABBLE_CONFIG_DIR>/caddy:/x alpine touch /x/on) — увидишь и статичную страницу (свежая загрузка), и оверлей (если уже в SPA); сними rm /x/on.

Заметка

Прод same-origin (VITE_GATEWAY_URL пустой) → заголовок-маркер читаем без CORS Access-Control-Expose-Headers. Если когда-нибудь разнесём origin SPA/gateway — добавить его в caddy-503 (одна строка).

## Что Оверлей «технические работы» в самом SPA — закрывает зазор, который статичная caddy-страница не ловит: юзер, УЖЕ сидящий в приложении, её не видит, у него просто начинают падать запросы. Комплемент к гейту из #170. **Механика:** - **Детект** строго по `X-Scrabble-Maintenance: 1` (не по любому `unavailable` — транзиент уже покрыт индикатором «Подключение…») на СЫРОМ `ConnectError` в двух catch-сайтах transport (unary `exec` + live `subscribe`), ДО того как `toGatewayError` срежет заголовки. Чистый `maintenanceRetryMs()` — юнит-тест. - **Неубираемый затемнённый оверлей** (клон Splash-шелла, в стиле caddy-страницы: тайл «Э» + текст). Гаснет сам: capped-backoff поллинг (дешёвый read, зеркало `connection.svelte.ts`) снимает на первом успехе; кнопка «Повторить» форсит проверку. Залипнуть не может. - **fail-fast:** при детекте read-петля не жжёт 6 ретраев, сразу отдаёт управление оверлею. - i18n RU «Технические работы» / EN; хук `window.__maint` (мок реального 503 не даёт) + Playwright-спек. ## Проверено локально `pnpm check` 0 ошибок, unit **402** (incl. 6 новых детектора), build ок, **e2e 186** (Chromium+WebKit, incl. 2 новых — оверлей рендерится/гаснет по `__maint`). ## От тебя - **Визуальный ревью на контуре** после деплоя (я pnpm/localhost тебе показать не могу). Для реального 503: подними флаг на тест-caddy (`docker run --rm -v <SCRABBLE_CONFIG_DIR>/caddy:/x alpine touch /x/on`) — увидишь и статичную страницу (свежая загрузка), и оверлей (если уже в SPA); сними `rm /x/on`. ## Заметка Прод same-origin (`VITE_GATEWAY_URL` пустой) → заголовок-маркер читаем без CORS `Access-Control-Expose-Headers`. Если когда-нибудь разнесём origin SPA/gateway — добавить его в caddy-503 (одна строка).
developer added 1 commit 2026-07-03 20:41:16 +00:00
feat(ui): in-session maintenance overlay on the edge 503 marker
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Successful in 1m4s
CI / conformance (pull_request) Successful in 11s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m39s
d67e582c03
The edge caddy 503 carries X-Scrabble-Maintenance during a deploy window, but a user
already in the running SPA only sees their calls start failing — the static caddy page
catches only a fresh load. Detect that marker (strictly — not any transient
'unavailable', which the Connecting indicator already covers) on the raw ConnectError at
the two transport catch sites (unary exec + the live subscribe stream), before
toGatewayError discards the response headers, and raise a non-dismissable dimmed overlay
that mirrors the static caddy page. It self-clears: a capped-backoff poll (a cheap read,
mirroring connection.svelte.ts) lifts it on the first success, and a manual "retry"
button forces an immediate re-check — so it can never get stuck. On detection the read
retry loop fails fast, so a window doesn't burn the retry budget on every call.

- pure detector maintenance.ts (maintenanceRetryMs / parseRetryAfterMs) + unit tests;
  the store + self-clearing poll in maintenance.svelte.ts (mirrors connection.svelte.ts)
- MaintenanceOverlay.svelte (clones Splash's fixed/inset/dimmed shell, non-dismissable),
  mounted app-global in App.svelte after Coachmark
- transport.ts detects + reports at both catch sites, clears on any successful read
- i18n RU "Технические работы" / EN "Under maintenance"; a window.__maint mock hook
  (the mock can't emit a real 503) + a Playwright spec

Prod is same-origin (VITE_GATEWAY_URL empty) so the marker header is readable without a
CORS expose-header. Verified: pnpm check (0), unit (402), build, e2e (186, Chromium+WebKit).
developer added 1 commit 2026-07-03 20:46:55 +00:00
feat(ui): reload the SPA on maintenance recovery
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Successful in 1m6s
CI / conformance (pull_request) Successful in 9s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m47s
7dcd62fdd7
The deploy that ends a maintenance window may ship a client-incompatible change
(wire/schema bump), and the in-session bundle is the old one. On recovery, reload to
pick up the fresh client instead of just hiding the overlay. Ordering is safe: the edge
maintenance flag (deploy/prod-deploy.sh) spans the WHOLE roll and clears only at script
exit — after the gateway (which serves the embedded SPA) has rolled — so the edge 503s
everything until the entire deploy is live; recovery therefore always serves the new SPA.
A window.__maint.recover() hook + e2e cover the reload.
owner approved these changes 2026-07-03 20:50:58 +00:00
developer merged commit 01a9249002 into development 2026-07-03 20:52:05 +00:00
developer deleted branch feature/maintenance-overlay 2026-07-03 20:52:05 +00:00
Sign in to join this conversation.
No Reviewers
No Label
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: developer/scrabble-game#171