# OpenSpec Change: enterprise-browser-pwa - **Change ID**: enterprise-browser-pwa - **Package**: `@suaveplan/pwa` - **Category**: browser - **Target path**: `browser/platform/pwa` - **Status**: IN PROGRESS - **Date**: 2026-04-24 - **Reference spec**: `/mnt/s/web/suaveplan/enterprise/pwa.md` --- ## Summary Deliver `@suaveplan/pwa` on the genesis branch at enterprise quality. The package provides `ServiceWorkerManager`, `SyncQueue`, `PushNotificationManager`, and `InstallPromptManager`. Genesis removes all `vi.stubGlobal` usage, migrates `SyncQueue` persistence from `localStorage` to a pluggable backend (default: IndexedDB via `@suaveplan/cache-browser`), adds a declarative caching-strategy DSL for SW route registration, Zod schemas, typed errors via `@suaveplan/error`, OTel metrics, splits `service-worker.test.ts` (587 lines) below 500, and adds Playwright E2E proving SW install → update → skipWaiting. --- ## Current-state audit (genesis) Fresh build — does not exist on genesis yet. --- ## Available workspace deps on genesis | Package | Role | |---|---| | `@suaveplan/cache-browser` (`workspace:^`) | IndexedDB-backed `SyncQueue` | | `@suaveplan/error` (`workspace:^`) | Typed PWA errors | | `@suaveplan/types` (`workspace:^`) | `LoggerLike` | | `@suaveplan/logger` (`workspace:^`) | Structured logging | | `@suaveplan/schema-registry` (`workspace:^`) | `SyncQueueEntry` schema | | `zod` (`catalog:forms`) | `SyncQueueEntry` Zod schema | | `@opentelemetry/api` (`catalog:observability`) | SW + push + sync metrics | | `tslib` (`catalog:build`) | Runtime helpers | Dev: `vitest`, `@vitest/coverage-istanbul`, `@playwright/test`, `@suaveplan/testing`. Zero runtime framework deps — no React. --- ## Preserved public API surface ```ts class ServiceWorkerManager implements IServiceWorkerManager { register(options?: SwRegistrationOptions): Promise unregister(): Promise update(): Promise skipWaiting(): Promise getState(): SwState onUpdate(cb: () => void): () => void } class SyncQueue implements ISyncQueue { constructor(config: SyncQueueConfig) enqueue(entry: SyncQueueEntry): Promise flush(): Promise clear(): Promise getDepth(): number } class PushNotificationManager implements IPushNotificationManager { getPermission(): PushPermission requestPermission(): Promise subscribe(options: PushSubscriptionOptions): Promise unsubscribe(): Promise sendNotification(options: PushNotificationOptions): Promise } class InstallPromptManager implements IInstallPromptManager { prompt(): Promise getState(): InstallPromptState onStateChange(cb: (s: InstallPromptState) => void): () => void } // SW route registration DSL (new) function swRoute(pattern: string | RegExp, strategy: CacheStrategy, options?: RouteOptions): SwRoute function networkFirst(options?: NetworkFirstOptions): CacheStrategy function cacheFirst(options?: CacheFirstOptions): CacheStrategy function staleWhileRevalidate(options?: StaleWhileRevalidateOptions): CacheStrategy ``` Subpath exports: `"."`, `"./core/service-worker/service-worker"`, `"./core/sync-queue/sync-queue"`, `"./core/push-notifications/push-notifications"`, `"./core/install-prompt/install-prompt"`, `"./core/routing/routing"`. --- ## New additions for genesis ### 1. No `vi.stubGlobal` All tests use real happy-dom globals and typed adapters implementing the browser interfaces (`Notification`, `PushManager`, `fetch`). No `@ts-expect-error` in tests. ### 2. Pluggable `SyncQueue` persistence `SyncQueueConfig` accepts a `storage?: StorageBackend` interface; default is `IndexedDBSyncStorage` backed by `@suaveplan/cache-browser`. `localStorage` fallback available for environments without IDB. ### 3. `SyncQueueEntry` Zod schema `SyncQueueEntrySchema` registered in `@suaveplan/schema-registry`. Validated on `enqueue()`. ### 4. Declarative caching-strategy DSL `src/core/routing/` — `swRoute`, `networkFirst`, `cacheFirst`, `staleWhileRevalidate`. Register routes in SW setup; reduces boilerplate for asset precache patterns. ### 5. `service-worker.test.ts` split Split at 587 lines into: `service-worker-registration.test.ts`, `service-worker-update.test.ts`, `service-worker-lifecycle.test.ts`, `service-worker-events.test.ts`, each ≤ 500 lines. ### 6. Error codes `SW_REGISTRATION_FAILED`, `SW_UPDATE_FAILED`, `PUSH_PERMISSION_DENIED`, `PUSH_UNSUPPORTED`, `SYNC_QUEUE_EXCEEDED`. ### 7. OTel metrics Counter `pwa.sw_install_total`, counter `pwa.sw_activate_total`, counter `pwa.sync_queue_flushed_total`, counter `pwa.push_subscriptions_total`, histogram `pwa.sync_flush_ms`, histogram `pwa.install_prompt_latency_ms`. --- ## File layout ```text browser/platform/pwa/ package.json tsconfig.json vitest.config.ts src/ index.ts error-codes.ts core/ service-worker/ service-worker.ts service-worker-registration.test.ts (≤500 lines) service-worker-update.test.ts (≤500 lines) service-worker-lifecycle.test.ts (≤500 lines) service-worker-events.test.ts (≤500 lines) service-worker.md service-worker.types.ts index.ts sync-queue/ sync-queue.ts sync-queue.test.ts sync-queue.md sync-queue.types.ts storage/ storage.ts (StorageBackend interface + IndexedDBSyncStorage + LocalStorageSyncStorage) storage.test.ts storage.md index.ts index.ts push-notifications/ push-notifications.ts push-notifications.test.ts push-notifications.md push-notifications.types.ts index.ts install-prompt/ install-prompt.ts install-prompt.test.ts install-prompt.md install-prompt.types.ts index.ts routing/ routing.ts (swRoute, networkFirst, cacheFirst, staleWhileRevalidate) routing.test.ts routing.md index.ts schema/ sync-queue-schema/ sync-queue-schema.ts sync-queue-schema.md index.ts types/ common.ts common.md constants/ index.ts tests/ integration.test.ts e2e/ pwa.spec.ts (Chromium/WebKit/Firefox: SW install → update → skipWaiting) ``` --- ## Impact **Downstream**: all Suaveplan PWA browser apps. **Breaking changes**: `SyncQueue` default persistence changes from `localStorage` to IndexedDB; `localStorage` fallback available via `LocalStorageSyncStorage`. No API surface change. **Prerequisites**: `@suaveplan/cache-browser`, `@suaveplan/error`, `@suaveplan/schema-registry` on Verdaccio.