feat(pwa): надёжное обновление + пер-кооп имя/иконка установочника #93

Merged
ant merged 3 commits from feat/pwa-reliable-update into dev 2026-06-05 09:27:07 +00:00
Owner

Что и зачем

Три связанных улучшения PWA-установочника кооператива. Зонтичный PR, по коммиту на блок.

1. Надёжное пользовательское обновление (убирает «подвисание между релизами»)

Было: Workbox GenerateSW с skipWaiting+clientsClaim → новый SW активировался в фоне сам → гонка controllerchangeupdated(). При сбое тайминга страница оставалась старой, а SW — новым: рассинхрон версий, «подвисли между релизами». Внезапный авто-reload.

Стало: skipWaiting/clientsClaim=false → новый SW ждёт в waiting. updated() шлёт window-событие sw:update-available; boot pwa-update показывает канон-тост в левом нижнем углу (UpdateAlert, timeout:0, кнопки «Обновить»/«Позже»). По «Обновить» → window.applyUpdate()SKIP_WAITINGcontrollerchangereload. Один детерминированный путь, без фонового свопа. Бонус: при skipWaiting:false Workbox сам инжектит listener SKIP_WAITING.

2. Имя установочника = имя кооператива

Было: образ generic, кооп задаётся env контейнера (COOP_SHORT_NAME), а manifest пёкся на сборке → на домашнем экране всегда дефолтное «Цифровой Кооператив».

Стало: manifest отдаётся динамически SSR-middleware из env. Ловушка: serveStatic('/') регистрируется до user-middleware (ssr-prod-webserver), поэтому статический /manifest.json перехватил бы запрос. Отдаём по /manifest.webmanifest (статики там нет → fall-through) и переписываем <link rel=manifest> в HTML. Плюс рантайм-apple-mobile-web-app-title для iOS.

3. Стандартная иконка приложения

Было: PNG-иконки manifest'а физически отсутствовали (/icons/*.png отдавали HTML-фоллбэк) → дефолтная иконка на домашнем экране.

Стало: полный набор PNG из logo.svg (квадрат, белый фон, центр, maskable-safe) + в динамическом manifest основная иконка — масштабируемый /logo.svg (purpose: any maskable).

Режимы

SW-логика и тост — client-only (guard typeof window), в SSR серверно не исполняются. Динамический manifest — SSR-only; в чистом SPA/статике остаётся build-time fallback. SPA и SSR не затронуты.

Проверки перед мержем (среда CI)

  • Собранный service-worker.js содержит инжектнутый listener SKIP_WAITING (при skipWaiting:false Workbox должен добавить — подтвердить после сборки).
  • <link rel=manifest> в SSR-HTML реально переписывается на /manifest.webmanifest (порядок атрибутов rel/href — regex учитывает оба).
  • iOS: apple-touch-icon 180 ставится (PNG в наборе есть).

🤖 Generated with Claude Code

## Что и зачем Три связанных улучшения PWA-установочника кооператива. Зонтичный PR, по коммиту на блок. ### 1. Надёжное пользовательское обновление (убирает «подвисание между релизами») **Было:** Workbox GenerateSW с `skipWaiting`+`clientsClaim` → новый SW активировался в фоне сам → гонка `controllerchange`↔`updated()`. При сбое тайминга страница оставалась старой, а SW — новым: рассинхрон версий, «подвисли между релизами». Внезапный авто-reload. **Стало:** `skipWaiting`/`clientsClaim=false` → новый SW ждёт в `waiting`. `updated()` шлёт `window`-событие `sw:update-available`; boot `pwa-update` показывает канон-тост **в левом нижнем углу** (`UpdateAlert`, `timeout:0`, кнопки «Обновить»/«Позже»). По «Обновить» → `window.applyUpdate()` → `SKIP_WAITING` → `controllerchange` → `reload`. Один детерминированный путь, без фонового свопа. Бонус: при `skipWaiting:false` Workbox сам инжектит listener `SKIP_WAITING`. ### 2. Имя установочника = имя кооператива **Было:** образ generic, кооп задаётся env контейнера (`COOP_SHORT_NAME`), а manifest пёкся на сборке → на домашнем экране всегда дефолтное «Цифровой Кооператив». **Стало:** manifest отдаётся динамически SSR-middleware из env. Ловушка: `serveStatic('/')` регистрируется **до** user-middleware (`ssr-prod-webserver`), поэтому статический `/manifest.json` перехватил бы запрос. Отдаём по **`/manifest.webmanifest`** (статики там нет → fall-through) и переписываем `<link rel=manifest>` в HTML. Плюс рантайм-`apple-mobile-web-app-title` для iOS. ### 3. Стандартная иконка приложения **Было:** PNG-иконки manifest'а физически отсутствовали (`/icons/*.png` отдавали HTML-фоллбэк) → дефолтная иконка на домашнем экране. **Стало:** полный набор PNG из `logo.svg` (квадрат, белый фон, центр, maskable-safe) + в динамическом manifest основная иконка — масштабируемый `/logo.svg` (`purpose: any maskable`). ## Режимы SW-логика и тост — client-only (guard `typeof window`), в SSR серверно не исполняются. Динамический manifest — SSR-only; в чистом SPA/статике остаётся build-time fallback. SPA и SSR не затронуты. ## Проверки перед мержем (среда CI) - [ ] Собранный `service-worker.js` содержит инжектнутый listener `SKIP_WAITING` (при `skipWaiting:false` Workbox должен добавить — подтвердить после сборки). - [ ] `<link rel=manifest>` в SSR-HTML реально переписывается на `/manifest.webmanifest` (порядок атрибутов rel/href — regex учитывает оба). - [ ] iOS: apple-touch-icon 180 ставится (PNG в наборе есть). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
claude force-pushed feat/pwa-reliable-update from 6fe06c3ec8 to c3bedef6b2 2026-06-05 08:42:27 +00:00 Compare
claude force-pushed feat/pwa-reliable-update from c3bedef6b2 to 30e8e0a1b6 2026-06-05 08:50:10 +00:00 Compare
claude force-pushed feat/pwa-reliable-update from 30e8e0a1b6 to 949b007961 2026-06-05 08:58:03 +00:00 Compare
claude force-pushed feat/pwa-reliable-update from 949b007961 to 4471ea2fb9 2026-06-05 09:06:08 +00:00 Compare
claude added 3 commits 2026-06-05 09:21:21 +00:00
Новый SW больше не активируется в фоне сам (skipWaiting/clientsClaim=false) —
ждёт в waiting, убирая гонку «подвисание между релизами» (старая страница +
новый SW). updated() шлёт window-событие 'sw:update-available'; boot 'pwa-update'
ловит его и показывает канон-тост UpdateAlert в левом нижнем углу (timeout 0,
кнопки «Обновить»/«Позже»). По «Обновить» → window.applyUpdate() → SKIP_WAITING
→ controllerchange → reload. Детерминированно, без фонового свопа версий.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Образ desktop generic, кооп задаётся env контейнера (COOP_SHORT_NAME), а
manifest пёкся на сборке и коопа не знал → на домашнем экране всегда дефолтное
'Цифровой Кооператив'. Отдаём manifest динамически SSR-middleware из env по
пути /manifest.webmanifest (статический /manifest.json перехватывается
serveStatic ДО user-middleware, поэтому новый путь + переписываем <link
rel=manifest> в HTML). Плюс рантайм apple-mobile-web-app-title для iOS.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
feat(pwa): стандартная иконка приложения (SVG + PNG-набор)
Typecheck / desktop (pull_request) Successful in 12m56s
Typecheck / controller (pull_request) Successful in 12m31s
de974d336d
PNG-иконки manifest'а физически отсутствовали (на проде /icons/*.png отдавали
HTML-фоллбэк) → на домашнем экране дефолтная иконка. Генерим полный набор из
logo.svg (квадрат, белый фон, лого по центру, maskable-safe) + в динамическом
manifest основная иконка — масштабируемый /logo.svg (purpose any maskable).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
claude force-pushed feat/pwa-reliable-update from 4471ea2fb9 to de974d336d 2026-06-05 09:21:21 +00:00 Compare
ant approved these changes 2026-06-05 09:26:57 +00:00
ant merged commit eb9ff9d3b6 into dev 2026-06-05 09:27:07 +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: C9S/mono#93