CreditCalc устанавливается как Progressive Web App. PWA остаётся статическим приложением: расчёты выполняются локально, backend и сетевое хранилище пользовательских данных не появляются.
manifest.webmanifest использует переносимые start_url: "./" и scope: "./". Service worker регистрируется относительно import.meta.env.BASE_URL, поэтому поддерживаются оба production-варианта:
- GitHub Pages:
/CreditCalc/; - переносимый release-архив:
./.
Worker обрабатывает только same-origin GET-запросы, путь которых находится внутри его registration scope. Namespace Cache Storage дополнительно содержит хеш scope, поэтому сборки под разными путями на одном origin не удаляют кеши друг друга.
index.htmlи стартовый URL — network-first с таймаутом 5 секунд и fallback на последнюю успешную страницу или precache при сетевой ошибке и HTTP 4xx/5xx; runtime-кеш обновляется только успешным HTML-ответом;- хешированные JS/CSS, Web Workers, иконки, manifest и локальные шрифты — precache и cache-first;
- неизвестная навигация без сети — статический
offline.html; - cross-origin, POST и другие запросы вне allowlist не кешируются.
В Cache Storage попадает только production shell. Zustand state, JSON-экспорт, shared-ссылки и другие пользовательские данные worker не читает и не кеширует. Это проверяется browser E2E с приватным sentinel-значением.
В Chromium-клиентах кнопка «Установить приложение» появляется только после beforeinstallprompt. На iOS интерфейс показывает поддерживаемый браузером ручной путь через «Поделиться» → «На экран Домой». В уже установленном standalone-режиме предложение скрывается. Напоминание можно отложить на семь дней или отключить навсегда через отдельный локальный флаг; если системная установка доступна, её кнопка остаётся в разделе «Параметры» независимо от напоминания.
В нативной Android-сборке Capacitor PWA-установка, индикатор сети, service worker и запрос browser persistent storage отключены: приложение уже установлено, работает из встроенного dist-android и использует локальное WebView-хранилище.
Новый worker остаётся в состоянии waiting. Приложение показывает «Доступна новая версия», но не вызывает skipWaiting и не перезагружает страницу автоматически. Только кнопка «Обновить» отправляет SKIP_WAITING; reload выполняется после controllerchange. При проблемном статусе localStorage рядом предлагается сначала скачать JSON.
Финансовое состояние остаётся в localStorage под версионированной persisted-схемой 11. Каждая запись получает epoch, монотонную revision и writer ID; старые версии мигрируют, повреждённые записи проходят карантин. Переход на IndexedDB не входит в текущую архитектуру: это отдельная миграция данных, которую следует проектировать с dual-read/dual-write, rollback и проверкой восстановления.
Storage API persist() запрашивается только по кнопке в настройках. Успешный результат означает защиту origin от некоторых сценариев автоматического eviction, но не создаёт backup и не защищает от ручной очистки, удаления браузерного профиля или потери устройства. JSON-экспорт остаётся способом резервного восстановления.
После production-сборки:
pnpm build
pnpm test:pwa
pnpm test:e2etest:pwa проверяет manifest, обязательные файлы, внедрение precache-манифеста, хешированные assets и границы scope. Playwright устанавливает worker, проверяет Cache Storage, перезагружает стартовый экран без сети и открывает offline fallback. Workflow GitHub Pages повторяет E2E с E2E_BASE_PATH=/CreditCalc/.