6.6 KiB
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
class ServiceWorkerManager implements IServiceWorkerManager {
register(options?: SwRegistrationOptions): Promise<SwRegistrationResult>
unregister(): Promise<boolean>
update(): Promise<void>
skipWaiting(): Promise<void>
getState(): SwState
onUpdate(cb: () => void): () => void
}
class SyncQueue implements ISyncQueue {
constructor(config: SyncQueueConfig)
enqueue(entry: SyncQueueEntry): Promise<void>
flush(): Promise<void>
clear(): Promise<void>
getDepth(): number
}
class PushNotificationManager implements IPushNotificationManager {
getPermission(): PushPermission
requestPermission(): Promise<PushPermission>
subscribe(options: PushSubscriptionOptions): Promise<PushSubscriptionResult>
unsubscribe(): Promise<boolean>
sendNotification(options: PushNotificationOptions): Promise<void>
}
class InstallPromptManager implements IInstallPromptManager {
prompt(): Promise<void>
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
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.