Skip to content

pwa(concurrency): make multi-tab editing safe with Web Locks, BroadcastChannel & lifecycle ownership #480

Description

@qnbs

Parent

Part of #478 — PWA first-class excellence/hardening.

Why this can exceed native behavior

The PWA can have several independent same-origin tabs/windows/workers alive at once. That is often treated as a browser nuisance, but it can become a product advantage if WorldScript makes concurrency explicit and safe.

Browser-native primitives provide origin-wide coordination without inventing a native IPC daemon:

  • Web Locks API for exclusive/shared ownership of critical resources;
  • BroadcastChannel for low-latency same-origin presence/state notifications;
  • Service Worker / workers as additional coordinated participants where appropriate;
  • browser lifecycle semantics that release locks when a context actually dies.

The goal is not real-time collaborative editing between a user's own tabs. The goal is to make accidental or intentional multi-window usage safe, comprehensible, and useful.

Current opportunity

The repository already has sophisticated IndexedDB encryption-migration admission concepts (#338/#360 lineage), but there is no dedicated general PWA multi-tab ownership contract. Search of the current codebase did not find a generic BroadcastChannel or PWA-level navigator.locks coordination layer.

Proposed architecture

WorldScript origin
    │
    ├── Tab / Installed window A ─┐
    ├── Tab / Installed window B ─┼── BroadcastChannel: presence/events
    ├── Worker / local-AI owner ──┤
    └── Service Worker ───────────┘
                 │
                 ▼
          Web Locks ownership
                 │
                 ▼
       IndexedDB / OPFS authority

Lock classes

Define named, versioned lock namespaces rather than ad-hoc strings:

worldscript:project:<id>:writer
worldscript:project:<id>:migration
worldscript:project:<id>:backup
worldscript:local-ai:model:<id>:init
worldscript:duckdb:migration
worldscript:pwa:update-activation

Only introduce a lock when a real shared-resource race exists. Do not serialize unrelated work globally.

Project writing ownership

Recommended default:

  • first context opening a project obtains the active writer lease/lock;
  • a second context may open safely in read-only / observer mode rather than silently becoming another writer;
  • the secondary context clearly identifies the active writer context;
  • user may request Take over editing;
  • takeover flushes/coordinates current dirty state before transferring ownership where possible;
  • if the original tab is dead, lock loss permits deterministic recovery;
  • never use wall-clock-only leases as the sole source of truth.

Do not make the entire application single-instance: multiple projects should remain usable in multiple tabs/windows if their resource ownership does not conflict.

BroadcastChannel protocol

BroadcastChannel is advisory coordination, not durable truth. Persisted state remains in IndexedDB/OPFS.

Suggested typed messages:

HELLO / GOODBYE
PROJECT_OPENED
PROJECT_WRITER_ACQUIRED
PROJECT_WRITER_RELEASED
DIRTY_GENERATION_ADVANCED
SAVE_STARTED / SAVE_COMMITTED / SAVE_FAILED
MIGRATION_STARTED / FINISHED / RECOVERY_REQUIRED
UPDATE_AVAILABLE / UPDATE_ACTIVATION_REQUESTED
LOCAL_AI_INIT_STARTED / READY / FAILED

Every message should include a protocol version, context ID, timestamp/monotonic sequence where useful, and bounded non-sensitive metadata. Never broadcast manuscript text, prompts, tokens, API keys, or encryption material merely for coordination.

Lifecycle behavior

Handle:

  • normal close;
  • crash/kill;
  • browser discard/freeze/background;
  • reload/navigation;
  • installed PWA + normal browser tab simultaneously;
  • service-worker replacement;
  • suspend/resume where exposed;
  • stale presence records.

Presence heartbeats may improve UX but must not become a correctness substitute for Web Locks / durable state.

Local-AI resource ownership

A major PWA-specific win is preventing multiple tabs from independently loading the same multi-GB local model or initializing WebGPU simultaneously.

Use per-model/per-backend locks to:

  • deduplicate initialization;
  • route/coordinate requests where architecture permits;
  • prevent model-download duplication;
  • avoid GPU-memory storms;
  • allow safe cancellation/recovery if the owning context dies.

Coordinate with the dedicated PWA local-AI issue under #478.

Migration / encryption relationship

Do not replace the existing security-specific migration-admission invariants with generic UI locking.

Instead:

Failure UX

Never show a generic "database locked" error when the app can explain the situation.

Examples:

  • “This project is currently being edited in another WorldScript window.”
  • “A storage migration is finishing. Editing will resume when it is safe.”
  • “Local model X is already loading in another WorldScript window.”

Provide safe actions: focus existing window where possible, retry, open read-only, or explicit takeover.

Testing

Automated/browser-matrix tests should cover:

  • two tabs open same project concurrently;
  • two tabs open different projects;
  • active writer crashes;
  • active writer reloads;
  • dirty secondary tries takeover;
  • migration starts while writer active;
  • local model init from two tabs;
  • installed PWA and normal browser tab coexist;
  • BroadcastChannel unavailable/fails;
  • Web Locks unavailable;
  • background/frozen context recovers;
  • service-worker update while multiple dirty clients exist.

Fallback must remain safe when a browser lacks one of the APIs; unsupported capability must not silently re-enable unsafe last-writer-wins behavior.

Acceptance criteria

  • Typed same-origin coordination protocol exists.
  • Same project cannot be silently written by two independent PWA contexts.
  • Read-only/observer secondary-open UX is clear.
  • Explicit safe takeover exists or is deliberately deferred with clear UX.
  • Dead-tab recovery does not require manual storage deletion.
  • BroadcastChannel is never treated as durable authority.
  • Critical shared operations use Web Locks or an equivalent proven admission primitive.
  • Local-AI/model initialization can be deduplicated across contexts where practical.
  • Existing encryption migration admission remains fail-closed.
  • No sensitive content is broadcast as coordination metadata.
  • Multi-tab lifecycle tests exist.
  • Unsupported browsers degrade safely and explicitly.

Non-goals

  • building Google-Docs-style collaboration between the user's own tabs;
  • introducing a server requirement;
  • forcing single-instance application behavior;
  • using BroadcastChannel as persistence;
  • weakening encryption migration locks for convenience.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions