MeridiansMeridians

14. Domain categories — src/lib/client/store/store.tsx

From the Meridians Wiki · Public · Maintained · joint

Three sets, exported for UI differentiation:

  • DOMAIN_PLAYGROUND_IDS — bundled public/playgrounds corpus.
  • DOMAIN_WORK_IDS — bundled public/works corpus.
  • DOMAIN_USER_GENERATED_IDS — created at runtime (analysis, wizard, extensions) = the complement of the two bundled sets.

Both bundled categories prepopulate from /public on hydration. DELETE_DOMAIN treats both bundled categories as reset-not-deleted (they re-seed); only USER_GENERATED is truly removable.

15. Game guards — src/lib/core/game/guards.ts

While a Scenario game is live, structural edits that would corrupt the board are hard-blocked in the reducer and surfaced with a toast (GAME_LOCK_MESSAGE):

  • Branch-scoped (isBranchGameLocked): DELETE_SCENE, branch-entry removal, CREATE_MERGE/REVERT_MERGE, REMOVE_STREAM — locked on a branch with a live game.
  • Domain-scoped (hasActiveGame): UPSERT_PERSPECTIVE — perspectives are shared across branches and a live board renders them to players, so they're locked on any active game.

The game's own RESOLVE generation runs through useScenario (not these guards). Abandoning the game (clearGameREMOVE_GAME_ROOM) is not guarded — it's the escape hatch and unlocks everything. Admins get equal control: isGM is actAsSeatId === null (not sync-role), so a client in the domain interface gets the full Director view and can abandon the game; control actions flow to the master like any edit. Game hosting (the Scenario broker) stays master-only (useScenarioLiveHost).

16. Edge cases handled

Edge caseHandling
Concurrent edits to different domains/branchesMaster applies each delta onto canonical state, serialized — all land
Concurrent edits to the same sceneLast applied wins; the loser adopts canonical (toast "Updated by host")
Action no longer applicable (edits a deleted scene)Master catches the throw → reject → client refreshes from canonical
Stale document write to the daemonwriteDocument forward-time guard → 409; the SSE echo converges the cache (Part A §3)
Stale-base write with a fresh clock (replica missed a headless commit, then edited)writeDocument collection-regression guard → 409 when it would drop a populated collection; the catch-up re-pulls the fuller record copy (Part A §2)
No master connectedPushes get 503 → "Host offline" toast; reads still work from the local replica
Master restart (broker globalThis reset)Broker backfills its shape; the client's SSE drops → lock releases event-driven, then re-pulls on reconnect
Client loses connection mid-pushSSE onerror unlocks immediately and requestReconciles the pending domain — no waiting on a timer
Two localhost master tabsBroker displaced frame puts the old tab inert (read-only) so it can't double-publish
Tunnel stoppeddisconnectAllClients tells clients to disconnect and clears the roster
Far-out-of-date clientEvery entries frame re-pulls exactly the stamped-stale domains (version ledger, §13) — reconnects clear the per-connection request guard so lost requests retry
Client boots before its replica decryptsStorm guard defers the reconcile; the warm-ledger replay re-runs it against the buffered latest entries (§13)
Aggregate view opens before its member Domains / sync handshake existReplica read-through queues until connection + identity, coalesces each Domain pull, and resolves from the authorized canonical frame
Headless commit while every window was CLOSEDThe version-gated sweep (catchUpEntries, Part A §5) re-pulls held docs the record moved past, on the next boot/reconnect
Headless commit to a background worldsaveDomainProjection for every adopted domain (Part A §5) + relayHeadlessDomain (§11) land it in IndexedDB and on clients
Client-created assetsUploaded to the master before the referencing domain is pushed (no dangling ref)
Large embedding sets over the tunnelBinary encoding + one bulk request + yielded master encoding
Asset bytes overwritten in placeAvoided by content-addressing; meridians:asset-changed re-resolves consumers

17. Toasts — feedback discipline

Toasts are the user-facing signal for every sync/lock action. The store fires meridians:toast window events (it sits above ToastProvider); the provider renders them bottom-right with solid backgrounds. They use the shared communications vocabulary in src/lib/core/comms/signal.ts, so the toast, console line, system log, and persisted API record describe an event in the same language. Its axes are orthogonal: tone controls urgency/colour; register says what kind of communication this is; failure tier names ownership; actor + operation code + HTTP status supply provenance.

  • Confirmed (normally success) — an action landed.
  • Intel (normally info) — neutral state or reporting; no response is required.
  • Advisory (normally warning) — a user-correctable condition, guard, or fallback.
  • Failure tier (error, or a persistent warning such as Billing) — Our bug, Provider, Billing, or Unclassified. Every red failure must carry a tier and a greppable operation code; use Unclassified when the boundary cannot decide blame rather than omitting the tag. Add a detail line that says what remains safe and what the operator should do next.

Input validation is Advisory, not a bug. Social relays carry the actor and suppress the machine register. Semantically identical on-screen messages dedupe; rich reporting and classified issues stay until dismissed. Rich cards expose a Copy report action containing the visible register or tier, operation code, HTTP status, headline, and next step. Register may be overridden independently of tone when meaning and urgency differ. Dedupe includes ownership, source, status, and actor, so equal headlines from distinct incidents remain distinct.

  • Client: Syncing… / Saved ✓ / Hold on — syncing your last change / Host offline — change not saved / Host didn't confirm / Couldn't apply — refreshing / Updated by host / Host stopped hosting / Host offline — start Meridians on the host machine.
  • Master: Client connected — N online / Client left — N online / All clients disconnected / Hosting moved to another tab.
  • Both: GAME_LOCK_MESSAGE whenever a guarded structural edit is blocked.

18. Known limitations / future

  • Permissions: clients are full admins today. Tightening is a small set of gates (isGM, the generate/host controls, the reducer guards).
  • Same-domain same-field concurrency resolves last-writer-wins (rare for admins).
  • Scene generation into an existing domain is master-only (bursts don't fit the one-in-flight lock); analysis/wizard/extension (whole-domain creates) work from clients.
  • Timing constants live in one place, src/lib/server/sync/constants.ts, with a header that states the rule: time-based handling is kept only where it's inherent — debounces coalesce publish bursts, network timeouts bound a wait when no event can report the answer. Everything else (lock release, reconnect catch-up, echo suppression, the projection echo) is event- or version-driven.
  • Asset relay timeouts are bounded (ASSET_PULL_TIMEOUT_MS / ASSET_UPLOAD_TIMEOUT_MS); a stalled master degrades to partial data + self-heal, never a hang.
  • Production build recommended for tunnel testingnext dev's on-demand route compilation is slow over a tunnel (allowedDevOrigins is set for ngrok to keep HMR from blocking, but pre-compiled next start is the real fix).