194 lines
6.6 KiB
Markdown
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.
|