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

194 lines
6.6 KiB
Markdown

# 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<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
```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.