Files
suaveplan/openspec/changes/enterprise-browser-pwa/proposal.md
2026-05-27 23:29:53 +01:00

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.