Initial Commit
This commit is contained in:
193
openspec/changes/enterprise-browser-pwa/proposal.md
Normal file
193
openspec/changes/enterprise-browser-pwa/proposal.md
Normal file
@@ -0,0 +1,193 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user