Клиент подписывает устройство на Web Push, а показывает уведомление service
worker — приложение при этом может быть и закрыто.
- `web/public/sw.js`: обработчики `push` (показывает уведомление, иначе браузер
может отозвать подписку) и `notificationclick` (фокусирует окно и переводит
его в комнату, иначе открывает новое). Путь из payload берётся только
внутренний: `//host` и абсолютные адреса отбрасываются;
- `web/src/lib/webPush.ts`: поддержка API, запрос разрешения, подписка и
отписка, преобразование ключей в base64url, слушатель перехода по клику
(`push-navigate` → `history.pushState` без перезагрузки);
- `web/src/api/push.ts`: `GET /push/config`, `GET|POST|DELETE /push/subscriptions`;
- раздел «Push-уведомления» в настройках: тумблер «Включить на этом
устройстве», состояние «включено», объяснение отказа браузера в разрешении и
честная деградация — «браузер не поддерживает» или «на инстансе не настроено»;
- владелец инстанса управляет ключами, клиент только спрашивает публичный.
Тесты: `web/tests/webPush.test.ts` (поддержка API, ключи, подписка, отписка,
отказ в разрешении, переход по клику) и `web/tests/pushSettings.test.tsx`
(раздел настроек: деградация, включение, отключение, ошибка загрузки).
- /.well-known/security.txt уходил в SPA-заглушку (200 text/html): добавлена
копия в web/public и явный тип text/plain в статике (RFC 9116, AGENT.md 11.3);
- make security дополнен `npm audit --omit=dev --audit-level=high`;
- новая цель `make sbom` — CycloneDX-отчёт зависимостей (trivy, если есть).
- internal/retention: истёкшие сессии, аудит старше RETENTION_AUDIT_DAYS и
файлы без ссылок старше RETENTION_ORPHAN_HOURS; сиротой файл считается
только если на него не ссылается ничего (вложения, аватары, оформление
сервера, комнат, ролей, эмодзи, звуки, вебхуки, приглашения)
- обслуживание запускается в приложении по RETENTION_INTERVAL_SECONDS;
команды glchat cleanup и glchat reindex делают то же вручную
- PWA: манифест с иконками и shortcuts, офлайн-страница, сервис-воркер
(кэш только /assets/*, приватные запросы не кэшируются)
- тесты: Go на чистку (сирота удаляется, используемые файлы и свежие сироты
остаются, истёкшая сессия уходит), Vitest на манифест и регистрацию воркера