Skip to content

Context

Context is a free-form Record<string, unknown> that the client keeps in sync with the server. The autopilot reads it every turn — use it for everything that describes "where the user is right now": route, selection, app mode, theme, IDs.

Read

ts
const ctx = client.getContext(); // returns a deep copy

Write — three flavors

ts
client.setContext({ a: 1, b: 2 });            // replaces the entire context
client.updateContext({ b: 3, c: 4 });         // shallow merge → { a:1, b:3, c:4 }
client.mergeContext({ user: { name: 'Mo' } }); // deep merge

Which one to use:

  • setContext when you compute the full context from scratch on every render.
  • updateContext for the common case — "this one key changed".
  • mergeContext when you want to update a nested object without overwriting siblings.

Clear

ts
client.clearContext();

Sync model

All three writers feed the same pipeline:

your write  ──►  diff against last-synced snapshot  ──►  debounce 100 ms  ──►  send JSON Patch

Two consequences:

  • Bursty writers (a watch that fires on every keystroke, say) collapse into a single network message.
  • The server only sees the net change, not your intermediate states.

Size and depth limits

Every context sync is pre-flighted against the relay's limits before anything goes on the wire:

  • 64 KB cap. If the projected stored state (functions catalog + context) would exceed MAX_STATE_SIZE_BYTES (65536 bytes), the sync is aborted and a stateError event fires — e.g. context sync rejected: projected state size X bytes exceeds relay cap of 65536 bytes. The pending change is not discarded; shrink the context and it retries.
  • 10-level depth. The relay silently drops JSON-Patch ops whose path is deeper than 10 segments. The client catches this upfront and fires a stateError naming the offending path(s), so deep nesting fails loudly instead of diverging unnoticed.
ts
client.on('stateError', ({ error }) => {
  console.error('state rejected:', error.message); // shrink / flatten, then retry
});

Server snapshot reconciliation

When a server context snapshot arrives (the first connect, a relay echo of your own write, or a state_change from another client), the client reconciles it with any not-yet-synced local edits:

  • Unsent local edits are replayed on top of the server view — a write made between a flush and its echo is never lost.
  • If a local edit cannot replay onto the server view (the server retyped or removed a parent it touches), the server view is adopted wholesale and that one conflicting edit is dropped. This is rare and only happens after a structural change from elsewhere.

Forcing an immediate sync

ts
client.flushContextSync();

Use this before navigating away or closing the tab, when you cannot afford to wait the 100 ms.

ts
window.addEventListener('beforeunload', () => {
  client.flushContextSync();
});

Observing context changes

ts
client.on('contextUpdated', ({ context, patches }) => {
  console.log('patches sent:', patches);
});

The patches array is the RFC 6902 ops that were actually sent — useful for debugging "why did the server not see my update?".

With the Pinia store

If you are using useAutopilotStore(), its context property is a reactive object you can mutate directly: store.context.foo = 'bar'. A deep watcher auto-syncs mutations to the client (same 100 ms debounce). The convenience actions — store.setContextProperty('foo', 'bar') — still work and do the same thing. See the store reference.