grist-widget-sdk 0.2.15 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/README.md +2 -2
- package/dist/advanced.js +3 -2
- package/dist/advanced.js.map +1 -1
- package/dist/{chunk-ER6UWHZA.js → chunk-7GXVEZ2J.js} +2 -2
- package/dist/{chunk-KOVYBNPJ.js → chunk-D5GBS2NT.js} +8 -31
- package/dist/chunk-D5GBS2NT.js.map +1 -0
- package/dist/chunk-N3X46JW2.js +37 -0
- package/dist/chunk-N3X46JW2.js.map +1 -0
- package/dist/emulator/devtools.cjs +37 -0
- package/dist/emulator/devtools.cjs.map +1 -1
- package/dist/emulator/devtools.js +28 -0
- package/dist/emulator/devtools.js.map +1 -1
- package/dist/emulator/testing.cjs.map +1 -1
- package/dist/emulator/testing.js +2 -1
- package/dist/emulator/testing.js.map +1 -1
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/dist/chunk-KOVYBNPJ.js.map +0 -1
- /package/dist/{chunk-ER6UWHZA.js.map → chunk-7GXVEZ2J.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/sdk/helpers/grist-mappings.ts","../src/sdk/internal/handshake/types.ts","../src/sdk/internal/handshake/derive.ts","../src/sdk/internal/handshake/actions.ts","../src/sdk/internal/handshake/mapping-resolver.ts","../src/sdk/internal/handshake/reducer.ts","../src/sdk/helpers/grist-widget-interaction.ts","../src/sdk/internal/ready-singleton.ts","../src/sdk/internal/subscriptions.ts","../src/sdk/internal/handshake/effects/detect.ts","../src/sdk/internal/handshake/effects/negotiate.ts","../src/sdk/internal/handshake/effects/subscriptions.ts","../src/sdk/internal/handshake/effects/mappings.ts","../src/sdk/internal/handshake/effects/heartbeat.ts","../src/sdk/internal/handshake/authz-from-options-cache.ts","../src/sdk/internal/handshake/environment.ts","../src/sdk/internal/handshake/manager.ts","../src/sdk/internal/embedded-handshake-manager.ts"],"sourcesContent":["import type { GristColumnsToMap, GristColumnToMap, GristWidgetColumnMap } from \"../types\"\n\nexport type ValidateColumnMappingsResult = {\n ok: boolean\n /**\n * `true` while the host has not yet reported column mappings (e.g. between\n * `grist.ready()` resolving and the first `onRecord` / `onRecords` callback,\n * or before `sectionApi.mappings()` returns). Callers should not gate UI or\n * emit mapping alerts while pending.\n */\n pending?: boolean\n /** Logical names of required columns the user has not yet mapped. */\n missing: string[]\n /** Logical names of `allowMultiple` columns mapped to an empty list. */\n emptyMultiples: string[]\n}\n\n/**\n * Merges mapping payloads from `onRecord`, `onRecords`, and `sectionApi.mappings()`.\n * Later sources win; `undefined` sources are skipped; `null` means \"host reported\n * no mappings\" but still counts as a known (empty) payload.\n */\nexport function mergeWidgetColumnMaps(\n ...sources: (GristWidgetColumnMap | null | undefined)[]\n): GristWidgetColumnMap | null | undefined {\n let merged: GristWidgetColumnMap = {}\n let sawDefined = false\n for (const src of sources) {\n if (src === undefined) continue\n sawDefined = true\n if (src === null) continue\n merged = { ...merged, ...src }\n }\n if (!sawDefined) return undefined\n return merged\n}\n\nfunction asColumnSpec(entry: string | GristColumnToMap): GristColumnToMap {\n return typeof entry === \"string\" ? { name: entry } : entry\n}\n\n/**\n * Verifies that the current Grist mappings cover every required column declared\n * in `grist.ready({ columns })`.\n *\n * Use this before reading `mappedRecord` so you can render a \"please configure\n * the widget\" prompt instead of a confusing `null`.\n */\nexport function validateColumnMappings(\n spec: GristColumnsToMap | undefined | null,\n mappings: GristWidgetColumnMap | null | undefined\n): ValidateColumnMappingsResult {\n const missing: string[] = []\n const emptyMultiples: string[] = []\n if (!spec) return { ok: true, pending: false, missing, emptyMultiples }\n\n const m = mappings ?? {}\n for (const raw of spec) {\n const col = asColumnSpec(raw)\n if (col.optional) continue\n const value = m[col.name]\n if (col.allowMultiple) {\n if (value === undefined) {\n missing.push(col.name)\n } else if (!Array.isArray(value) || value.length === 0) {\n emptyMultiples.push(col.name)\n }\n } else {\n if (value === undefined || value === null || value === \"\") missing.push(col.name)\n }\n }\n return {\n ok: missing.length === 0 && emptyMultiples.length === 0,\n pending: false,\n missing,\n emptyMultiples,\n }\n}\n\n/** Status while waiting for the host to report mappings (see `pending`). */\nexport const pendingColumnMappingStatus: ValidateColumnMappingsResult = {\n ok: true,\n pending: true,\n missing: [],\n emptyMultiples: [],\n}\n","/**\n * Pure type contracts for the handshake / synchronization state machine.\n *\n * The machine models five orthogonal axes that together describe the widget's\n * relationship with the Grist host:\n *\n * 1. `LIFECYCLE` — protocol handshake (one-shot via `grist.ready`).\n * 2. `LINK` — RPC channel health (continuous, post-online).\n * 3. `AUTHZ` — effective access level granted by the host.\n * 4. `CONFIG` — column mapping contract with the end-user.\n * 5. `SYNC` — freshness of each event stream and ancillary RPCs.\n *\n * The legacy public `status` (\"booting\" / \"ready\" / \"unavailable\" / \"error\")\n * is a **pure projection** of the snapshot, computed in `./derive.ts`.\n *\n * No runtime side effects in this file — types only.\n */\n\nimport type { GristColumnsToMap, GristRequiredAccess, GristWidgetColumnMap } from \"../../types\"\n\n// ---------------------------------------------------------------------------\n// Branded primitives\n// ---------------------------------------------------------------------------\n\n/** Monotone counter bumped on reload / restart / link recovery / access widening. */\nexport type GristGeneration = number & { readonly __brand: \"GristGeneration\" }\n\nexport const ZERO_GENERATION = 0 as GristGeneration\n\nexport function nextGeneration(current: GristGeneration): GristGeneration {\n return (current + 1) as GristGeneration\n}\n\n// ---------------------------------------------------------------------------\n// 1. LIFECYCLE — handshake protocol\n// ---------------------------------------------------------------------------\n\nexport type GristLifecyclePhase =\n | \"idle\"\n | \"detecting\"\n | \"negotiating\"\n | \"online\"\n | \"terminated\"\n\nexport type GristTerminationReason =\n | { kind: \"script_timeout\"; attempts: number }\n | { kind: \"not_embedded\" }\n | { kind: \"ready_failed\"; message: string }\n | { kind: \"ready_timeout\"; afterMs: number }\n | { kind: \"host_rejected\"; message: string }\n | { kind: \"manual_terminate\" }\n\nexport type GristLifecycle =\n | { phase: \"idle\" }\n | { phase: \"detecting\"; attempts: number; startedAtMs: number }\n | { phase: \"negotiating\"; access: GristRequiredAccess; startedAtMs: number }\n | { phase: \"online\"; sinceMs: number; declaredAccess: GristRequiredAccess }\n | { phase: \"terminated\"; reason: GristTerminationReason; atMs: number }\n\n// ---------------------------------------------------------------------------\n// 2. LINK — RPC channel health\n// ---------------------------------------------------------------------------\n\nexport type GristLinkState = \"connected\" | \"stale\" | \"lost\"\n\nexport type GristLink = {\n state: GristLinkState\n /** ms timestamp of last successful RPC (or heartbeat). `null` before any. */\n lastSuccessMs: number | null\n /** Consecutive RPC failures since the last success. */\n missedPings: number\n}\n\n// ---------------------------------------------------------------------------\n// 3. AUTHZ — effective access\n// ---------------------------------------------------------------------------\n\nexport type GristAuthz =\n | { state: \"pending\"; declared: GristRequiredAccess }\n | {\n state: \"granted\"\n /** Effective level granted by the host. May differ from `declared`. */\n level: GristRequiredAccess\n declared: GristRequiredAccess\n /** True if granted level >= a previously granted level for the same widget. */\n widened: boolean\n }\n | { state: \"denied\"; declared: GristRequiredAccess; message?: string }\n\n// ---------------------------------------------------------------------------\n// 4. CONFIG — column-mapping contract\n// ---------------------------------------------------------------------------\n\n/**\n * Source that produced a mapping payload, used for deterministic merge by\n * {@link MappingResolver} (see `./mapping-resolver.ts`).\n */\nexport type GristMappingSource =\n | \"section_api\"\n | \"stream_record\"\n | \"stream_records\"\n | \"stream_new_record\"\n\n/**\n * Static priority order for mapping sources (lower index = higher priority).\n * `section_api` is the host's source-of-truth when available; stream payloads\n * are reactive fallbacks.\n */\nexport const MAPPING_SOURCE_PRIORITY: readonly GristMappingSource[] = [\n \"section_api\",\n \"stream_record\",\n \"stream_records\",\n \"stream_new_record\",\n] as const\n\nexport type GristMappingComplete = {\n state: \"complete\"\n declared: GristColumnsToMap\n mappings: GristWidgetColumnMap\n /** `\"merged\"` is used internally when multiple sources contributed. */\n source: GristMappingSource | \"merged\"\n reportedAtMs: number\n}\n\nexport type GristMappingIncomplete = {\n state: \"incomplete\"\n declared: GristColumnsToMap\n mappings: GristWidgetColumnMap\n missing: readonly string[]\n emptyMultiples: readonly string[]\n source: GristMappingSource | \"merged\"\n reportedAtMs: number\n}\n\nexport type GristMappingState =\n /** Widget did not declare any `columns` in `grist.ready`. */\n | { state: \"not_declared\" }\n /** Columns declared but no host report yet received. */\n | { state: \"pending\"; declared: GristColumnsToMap; declaredAtMs: number }\n | GristMappingIncomplete\n | GristMappingComplete\n /** User just modified the mapping; previous payload preserved to avoid UI flicker. */\n | {\n state: \"invalidating\"\n previous: GristMappingIncomplete | GristMappingComplete\n sinceMs: number\n }\n /** Declared but no host report after a timeout. Soft state — payload may still arrive. */\n | { state: \"unreported\"; declared: GristColumnsToMap; afterMs: number }\n\nexport type GristConfig = {\n mappings: GristMappingState\n}\n\n// ---------------------------------------------------------------------------\n// 5. SYNC — stream freshness\n// ---------------------------------------------------------------------------\n\nexport type GristStreamFreshness =\n | { state: \"cold\" }\n | { state: \"warming\"; sinceMs: number }\n | { state: \"hot\"; lastEventMs: number; generation: GristGeneration; payloadHash: string }\n | {\n state: \"stale\"\n lastEventMs: number\n reason: \"link_lost\" | \"tab_hidden\" | \"host_quiet\"\n }\n\nexport type GristCurrentTableState =\n | { state: \"unknown\" }\n | { state: \"known\"; tableId: string; refreshing: boolean }\n /** Local error (e.g. `selectedTable.getTableId()` rejected) — does NOT propagate to global status. */\n | { state: \"errored\"; message: string; sinceMs: number }\n\nexport type GristSync = {\n record: GristStreamFreshness\n records: GristStreamFreshness\n options: GristStreamFreshness\n newRecord: GristStreamFreshness\n currentTable: GristCurrentTableState\n}\n\n// ---------------------------------------------------------------------------\n// SNAPSHOT (aggregate)\n// ---------------------------------------------------------------------------\n\nexport type GristWidgetSnapshot = {\n generation: GristGeneration\n lifecycle: GristLifecycle\n link: GristLink\n authz: GristAuthz\n config: GristConfig\n sync: GristSync\n}\n\n// ---------------------------------------------------------------------------\n// PROJECTION — legacy 4-state status + capabilities\n// ---------------------------------------------------------------------------\n\n/** Mirrors the long-standing public type, kept for compat. */\nexport type GristWidgetStatus = \"booting\" | \"ready\" | \"unavailable\" | \"error\"\n\n/**\n * Pre-computed booleans that widget code should consume directly instead of\n * re-deriving from `status` + mapping flags + writes flags.\n *\n * Every field is a **pure function of the snapshot** and is referentially\n * stable across renders that don't change the snapshot's relevant slices.\n */\nexport type GristCapabilities = {\n /** `lifecycle.online` + `link != lost` + `authz.state === \"granted\"` with read or higher. */\n canRead: boolean\n /** `canRead` + (`mappings.state` is `complete` or `not_declared`). */\n canRender: boolean\n /** `canRender` + `authz.level === \"full\"`. Required before any write. */\n canWriteRecords: boolean\n /** `canWriteRecords` + `sync.currentTable.state === \"known\"`. */\n canWriteSchema: boolean\n /** `canRead` + `link === \"connected\"`. */\n canFetchTable: boolean\n /** `canRead` + `sync.record.state === \"hot\"` + mappings not invalidating. */\n hasFreshSelection: boolean\n /** `mappings.state === \"complete\" || \"not_declared\"`. */\n hasUsableMappings: boolean\n /** Logical column names still missing from user mapping (incomplete only). */\n missingMappings: readonly string[]\n /** `allowMultiple` columns mapped to `[]` (incomplete only). */\n emptyMultipleMappings: readonly string[]\n}\n\n// ---------------------------------------------------------------------------\n// Initial snapshot factory\n// ---------------------------------------------------------------------------\n\nexport function initialGristSnapshot(): GristWidgetSnapshot {\n return {\n generation: ZERO_GENERATION,\n lifecycle: { phase: \"idle\" },\n link: { state: \"connected\", lastSuccessMs: null, missedPings: 0 },\n authz: { state: \"pending\", declared: \"read table\" },\n config: { mappings: { state: \"not_declared\" } },\n sync: {\n record: { state: \"cold\" },\n records: { state: \"cold\" },\n options: { state: \"cold\" },\n newRecord: { state: \"cold\" },\n currentTable: { state: \"unknown\" },\n },\n }\n}\n","/**\n * Pure projections from a {@link GristWidgetSnapshot}:\n *\n * - {@link deriveStatus} → legacy 4-state `status` (compat).\n * - {@link deriveCapabilities} → ready-to-bind booleans for widget UI.\n *\n * Both are deterministic and side-effect free. Callers (hooks, Boundary)\n * memoize them by reference equality of `snapshot`.\n */\n\nimport { ACCESS_RANK } from \"./access\"\nimport {\n pendingColumnMappingStatus,\n type ValidateColumnMappingsResult,\n} from \"../../helpers/grist-mappings\"\nimport type { GristColumnsToMap, GristWidgetColumnMap } from \"../../types\"\nimport type {\n GristCapabilities,\n GristMappingState,\n GristWidgetSnapshot,\n GristWidgetStatus,\n} from \"./types\"\n\n// ---------------------------------------------------------------------------\n// Status projection\n// ---------------------------------------------------------------------------\n\nexport function deriveStatus(snapshot: GristWidgetSnapshot): GristWidgetStatus {\n const { lifecycle, authz, link } = snapshot\n\n if (lifecycle.phase === \"idle\" || lifecycle.phase === \"detecting\") return \"booting\"\n\n if (lifecycle.phase === \"negotiating\") return \"booting\"\n\n if (lifecycle.phase === \"terminated\") {\n switch (lifecycle.reason.kind) {\n case \"script_timeout\":\n case \"not_embedded\":\n return \"unavailable\"\n case \"host_rejected\":\n case \"ready_failed\":\n case \"ready_timeout\":\n case \"manual_terminate\":\n return \"error\"\n }\n }\n\n // lifecycle.phase === \"online\"\n if (authz.state === \"denied\") return \"error\"\n if (link.state === \"lost\") return \"error\"\n return \"ready\"\n}\n\n/** Extract a single human-readable error message, if any, from the snapshot. */\nexport function deriveErrorMessage(snapshot: GristWidgetSnapshot): string | null {\n if (snapshot.lifecycle.phase === \"terminated\") {\n switch (snapshot.lifecycle.reason.kind) {\n case \"ready_failed\":\n return snapshot.lifecycle.reason.message\n case \"ready_timeout\":\n return `Grist handshake timed out after ${snapshot.lifecycle.reason.afterMs}ms`\n case \"host_rejected\":\n return snapshot.lifecycle.reason.message\n case \"script_timeout\":\n return `Grist plugin API never loaded (${snapshot.lifecycle.reason.attempts} attempts)`\n case \"not_embedded\":\n return \"This page is not embedded in a Grist iframe\"\n case \"manual_terminate\":\n return null\n }\n }\n if (snapshot.authz.state === \"denied\") {\n return (\n snapshot.authz.message ??\n `Grist denied requested access (${snapshot.authz.declared})`\n )\n }\n if (snapshot.lifecycle.phase === \"online\" && snapshot.link.state === \"lost\") {\n return `Grist link lost (${snapshot.link.missedPings} consecutive failures)`\n }\n return null\n}\n\n// ---------------------------------------------------------------------------\n// Capabilities derivation\n// ---------------------------------------------------------------------------\n\nexport function deriveCapabilities(snapshot: GristWidgetSnapshot): GristCapabilities {\n const { lifecycle, authz, link, config, sync } = snapshot\n const online = lifecycle.phase === \"online\"\n const granted = authz.state === \"granted\"\n const grantedReadOrMore =\n granted && ACCESS_RANK[authz.level] >= ACCESS_RANK[\"read table\"]\n const grantedFull = granted && authz.level === \"full\"\n const linkUsable = link.state !== \"lost\"\n\n const canRead = online && linkUsable && grantedReadOrMore\n const canFetchTable = canRead && link.state === \"connected\"\n\n const mappingsUsable = hasUsableMappings(config.mappings)\n const missing = mappingsMissing(config.mappings)\n const emptyMultiples = mappingsEmptyMultiples(config.mappings)\n\n const canRender = canRead && mappingsUsable\n const canWriteRecords = canRender && grantedFull\n const canWriteSchema =\n canWriteRecords && sync.currentTable.state === \"known\"\n\n const hasFreshSelection =\n canRead &&\n sync.record.state === \"hot\" &&\n config.mappings.state !== \"invalidating\"\n\n return {\n canRead,\n canRender,\n canWriteRecords,\n canWriteSchema,\n canFetchTable,\n hasFreshSelection,\n hasUsableMappings: mappingsUsable,\n missingMappings: missing,\n emptyMultipleMappings: emptyMultiples,\n }\n}\n\n// ---------------------------------------------------------------------------\n// Mapping helpers\n// ---------------------------------------------------------------------------\n\nfunction hasUsableMappings(state: GristMappingState): boolean {\n if (state.state === \"not_declared\") return true\n if (state.state === \"complete\") return true\n if (state.state === \"invalidating\") {\n return state.previous.state === \"complete\"\n }\n return false\n}\n\nfunction mappingsMissing(state: GristMappingState): readonly string[] {\n if (state.state === \"incomplete\") return state.missing\n if (state.state === \"invalidating\" && state.previous.state === \"incomplete\") {\n return state.previous.missing\n }\n return EMPTY\n}\n\nfunction mappingsEmptyMultiples(state: GristMappingState): readonly string[] {\n if (state.state === \"incomplete\") return state.emptyMultiples\n if (state.state === \"invalidating\" && state.previous.state === \"incomplete\") {\n return state.previous.emptyMultiples\n }\n return EMPTY\n}\n\nconst EMPTY: readonly string[] = Object.freeze([])\n\n// ---------------------------------------------------------------------------\n// Column-mapping projection (public `columnMappingStatus` shape)\n// ---------------------------------------------------------------------------\n\n/** Resolved mappings from the FSM config axis, if any. */\nexport function extractResolvedMappings(\n state: GristMappingState\n): GristWidgetColumnMap | null {\n switch (state.state) {\n case \"complete\":\n case \"incomplete\":\n return state.mappings\n case \"invalidating\":\n if (state.previous.state === \"complete\" || state.previous.state === \"incomplete\") {\n return state.previous.mappings\n }\n return null\n default:\n return null\n }\n}\n\n/**\n * Project {@link GristMappingState} into the legacy\n * {@link ValidateColumnMappingsResult} consumed by `useGrist().columnMappingStatus`.\n */\nexport function deriveColumnMappingStatus(\n mappingState: GristMappingState,\n columns?: GristColumnsToMap\n): ValidateColumnMappingsResult {\n if (!columns?.length) {\n return { ok: true, pending: false, missing: [], emptyMultiples: [] }\n }\n switch (mappingState.state) {\n case \"not_declared\":\n return { ok: true, pending: false, missing: [], emptyMultiples: [] }\n case \"pending\":\n return pendingColumnMappingStatus\n case \"unreported\":\n return pendingColumnMappingStatus\n case \"complete\":\n return { ok: true, pending: false, missing: [], emptyMultiples: [] }\n case \"incomplete\":\n return {\n ok: false,\n pending: false,\n missing: [...mappingState.missing],\n emptyMultiples: [...mappingState.emptyMultiples],\n }\n case \"invalidating\":\n return deriveColumnMappingStatus(mappingState.previous, columns)\n }\n}\n","/**\n * Action union for the handshake state machine.\n *\n * Every action that can mutate the snapshot is listed here as a discriminated\n * union — exhaustiveness checks in the reducer rely on it.\n *\n * Actions either:\n * - target a single axis (e.g. `RPC_SUCCESS` → link), OR\n * - target multiple axes via a single transition (e.g. `NEGOTIATE_SUCCESS`\n * advances lifecycle AND authz at once).\n *\n * Generation-carrying actions can be **dropped** by the reducer if their\n * `generation` is older than the snapshot's current generation; this is the\n * primary defence against stale events arriving after a reload/restart.\n */\n\nimport type { GristColumnsToMap, GristRequiredAccess, GristWidgetColumnMap } from \"../../types\"\nimport type {\n GristGeneration,\n GristMappingSource,\n GristTerminationReason,\n} from \"./types\"\n\n// ---------------------------------------------------------------------------\n// Lifecycle actions\n// ---------------------------------------------------------------------------\n\nexport type GristLifecycleAction =\n | { type: \"DETECT_START\"; atMs: number }\n | { type: \"DETECT_TICK\"; attempts: number; atMs: number }\n | { type: \"DETECT_AVAILABLE\"; atMs: number }\n | { type: \"DETECT_TIMEOUT\"; attempts: number; atMs: number }\n | { type: \"NOT_EMBEDDED\"; atMs: number }\n | { type: \"NEGOTIATE_START\"; access: GristRequiredAccess; atMs: number }\n | {\n type: \"NEGOTIATE_SUCCESS\"\n access: GristRequiredAccess\n grantedLevel: GristRequiredAccess\n atMs: number\n }\n | { type: \"NEGOTIATE_FAIL\"; reason: GristTerminationReason; atMs: number }\n | { type: \"TERMINATE\"; reason: GristTerminationReason; atMs: number }\n\n// ---------------------------------------------------------------------------\n// Link actions\n// ---------------------------------------------------------------------------\n\nexport type GristLinkAction =\n | { type: \"RPC_SUCCESS\"; atMs: number }\n | { type: \"RPC_FAILURE\"; atMs: number; kind: \"timeout\" | \"rejected\" | \"other\" }\n | { type: \"HEARTBEAT_OK\"; atMs: number }\n | { type: \"HEARTBEAT_FAIL\"; atMs: number }\n\n// ---------------------------------------------------------------------------\n// Authz actions\n// ---------------------------------------------------------------------------\n\nexport type GristAuthzAction = {\n type: \"AUTHZ_REPORT\"\n /** Effective access level reported by Grist (`onOptions` / `interaction.access_level`). */\n level: GristRequiredAccess\n atMs: number\n generation: GristGeneration\n}\n\n// ---------------------------------------------------------------------------\n// Generation control\n// ---------------------------------------------------------------------------\n\nexport type GristGenerationAction = {\n type: \"BUMP_GENERATION\"\n atMs: number\n reason: \"reload\" | \"restart\" | \"link_recovery\" | \"widening\"\n}\n\n// ---------------------------------------------------------------------------\n// Config / mapping actions\n// ---------------------------------------------------------------------------\n\nexport type GristMappingAction =\n | {\n type: \"MAPPING_DECLARE\"\n declared: GristColumnsToMap\n atMs: number\n generation: GristGeneration\n }\n | {\n type: \"MAPPING_REPORT\"\n source: GristMappingSource\n payload: GristWidgetColumnMap | null\n atMs: number\n generation: GristGeneration\n }\n | { type: \"MAPPING_INVALIDATE\"; atMs: number; generation: GristGeneration }\n | { type: \"MAPPING_INVALIDATION_RESOLVE\"; atMs: number; generation: GristGeneration }\n | { type: \"MAPPING_TIMEOUT\"; atMs: number; generation: GristGeneration }\n\n// ---------------------------------------------------------------------------\n// Sync actions\n// ---------------------------------------------------------------------------\n\nexport type GristSyncStream = \"record\" | \"records\" | \"options\" | \"newRecord\"\n\nexport type GristSyncAction =\n | {\n type: \"STREAM_RECEIVED\"\n stream: GristSyncStream\n payloadHash: string\n atMs: number\n generation: GristGeneration\n }\n | { type: \"STREAM_WARMING\"; stream: GristSyncStream; atMs: number }\n | {\n type: \"STREAM_STALE\"\n stream: GristSyncStream\n atMs: number\n reason: \"link_lost\" | \"tab_hidden\" | \"host_quiet\"\n }\n | { type: \"CURRENT_TABLE_KNOWN\"; tableId: string; atMs: number }\n | { type: \"CURRENT_TABLE_REFRESH_START\"; atMs: number }\n | { type: \"CURRENT_TABLE_ERROR\"; message: string; atMs: number }\n\n// ---------------------------------------------------------------------------\n// Top-level union\n// ---------------------------------------------------------------------------\n\nexport type GristAction =\n | GristLifecycleAction\n | GristLinkAction\n | GristAuthzAction\n | GristGenerationAction\n | GristMappingAction\n | GristSyncAction\n\n/**\n * Type guard: actions that carry a `generation` field and can therefore be\n * dropped by the reducer if `action.generation < snapshot.generation`.\n */\nexport function hasGenerationStamp(\n action: GristAction\n): action is Extract<GristAction, { generation: GristGeneration }> {\n return \"generation\" in action && typeof action.generation === \"number\"\n}\n","/**\n * Deterministic merge of column-mapping payloads across multiple sources.\n *\n * Why this exists\n * ---------------\n * Grist reports column mappings from several places, possibly in different\n * orders depending on network timing and event scheduling:\n *\n * - `grist.sectionApi.mappings()` (one-shot promise),\n * - `grist.onRecord` (stream),\n * - `grist.onRecords` (stream, occasionally diverges from `onRecord`),\n * - `grist.onNewRecord` (stream, only on the \"new\" placeholder row).\n *\n * A \"later wins\" merge would let arrival order dictate the final mappings —\n * unstable and surprising. This resolver applies a fixed priority order\n * (see {@link MAPPING_SOURCE_PRIORITY}) so the resolution is a **pure function\n * of the set of received payloads**, independent of the order in which they\n * arrived.\n *\n * Generation safety\n * -----------------\n * Each payload is ingested with the {@link GristGeneration} that was active\n * when the source produced it. Payloads with `generation` strictly less than\n * the resolver's current generation are silently dropped — this is how the\n * machine recovers from reloads / restarts without ever applying a stale\n * mapping over a fresh one.\n */\n\nimport { validateColumnMappings } from \"../../helpers/grist-mappings\"\nimport type { GristColumnsToMap, GristWidgetColumnMap } from \"../../types\"\nimport {\n MAPPING_SOURCE_PRIORITY,\n type GristGeneration,\n type GristMappingComplete,\n type GristMappingIncomplete,\n type GristMappingSource,\n type GristMappingState,\n} from \"./types\"\n\nexport type MappingPayload = {\n source: GristMappingSource\n /** `null` is a known empty payload (host explicitly reported no mappings). */\n payload: GristWidgetColumnMap | null\n receivedAtMs: number\n generation: GristGeneration\n}\n\nexport type ResolveOutcome =\n | {\n kind: \"no_change\"\n reason: \"stale_generation\" | \"duplicate_payload\"\n }\n | {\n kind: \"resolved\"\n state: GristMappingComplete | GristMappingIncomplete\n }\n | { kind: \"still_pending\" }\n\n/**\n * Stateful resolver. One instance per declaration of `columns` (i.e. per\n * widget lifetime, modulo `reset()` on declare-change or restart).\n *\n * The resolver does NOT own the higher-level {@link GristMappingState} —\n * it returns concrete `complete` / `incomplete` payloads that the reducer\n * folds into the snapshot.\n */\nexport class MappingResolver {\n private declared: GristColumnsToMap\n private currentGeneration: GristGeneration\n /** Latest accepted payload per source. */\n private payloads = new Map<GristMappingSource, MappingPayload>()\n /** Last resolved state, used to detect duplicate ingests. */\n private lastResolvedHash: string | null = null\n\n constructor(declared: GristColumnsToMap, generation: GristGeneration) {\n this.declared = declared\n this.currentGeneration = generation\n }\n\n /**\n * Replace the declared columns (e.g. user passed a new `columns` array to\n * the provider). Clears accumulated payloads — they were validated against\n * the previous declaration.\n */\n redeclare(declared: GristColumnsToMap, atGeneration: GristGeneration): void {\n this.declared = declared\n this.currentGeneration = atGeneration\n this.payloads.clear()\n this.lastResolvedHash = null\n }\n\n /** Bump the active generation. Payloads from older generations are dropped. */\n bumpGeneration(next: GristGeneration): void {\n if (next <= this.currentGeneration) return\n this.currentGeneration = next\n // Drop any cached payloads from older generations.\n for (const [src, p] of this.payloads) {\n if (p.generation < next) this.payloads.delete(src)\n }\n this.lastResolvedHash = null\n }\n\n /** Forget every payload and reset the hash. The declaration is preserved. */\n reset(): void {\n this.payloads.clear()\n this.lastResolvedHash = null\n }\n\n /** Cheap, deterministic snapshot for tests/debug. */\n knownSources(): readonly GristMappingSource[] {\n return [...this.payloads.keys()]\n }\n\n /**\n * Ingest one payload. Returns a {@link ResolveOutcome}:\n * - `still_pending` — no payloads accepted yet.\n * - `no_change/stale_*` — input generation is older than current.\n * - `no_change/duplicate_payload` — input did not move the resolution.\n * - `resolved` — new resolution differs from the previous one.\n *\n * Call {@link current} to read the latest resolution without affecting\n * the duplicate-detection hash.\n */\n ingest(input: MappingPayload): ResolveOutcome {\n if (input.generation < this.currentGeneration) {\n return { kind: \"no_change\", reason: \"stale_generation\" }\n }\n if (input.generation > this.currentGeneration) {\n this.bumpGeneration(input.generation)\n }\n this.payloads.set(input.source, input)\n\n const computed = this.compute()\n if (computed === null) return { kind: \"still_pending\" }\n\n const hash = computeMappingHash(\n computed.mappings,\n computed.state === \"complete\",\n computed.source\n )\n if (hash === this.lastResolvedHash) {\n return { kind: \"no_change\", reason: \"duplicate_payload\" }\n }\n this.lastResolvedHash = hash\n return { kind: \"resolved\", state: computed }\n }\n\n /**\n * Return the resolver's current resolution (pure read). Equivalent to\n * `ingest(...)` without mutating any state — used by tests and by the\n * reducer when re-applying after `MAPPING_INVALIDATION_RESOLVE`.\n */\n current(): ResolveOutcome {\n const computed = this.compute()\n if (computed === null) return { kind: \"still_pending\" }\n return { kind: \"resolved\", state: computed }\n }\n\n /**\n * Compute the current best mapping from accumulated payloads (pure).\n *\n * Priority: higher-priority sources **override** specific keys from\n * lower-priority sources. Keys absent from a higher-priority source fall\n * back to lower-priority sources. This matches the production behaviour\n * where `sectionApi.mappings()` is authoritative for the keys it reports,\n * but stream payloads can fill in keys the section_api hasn't reported yet.\n */\n private compute(): GristMappingComplete | GristMappingIncomplete | null {\n if (this.payloads.size === 0) return null\n\n const merged: GristWidgetColumnMap = {}\n let latestReportedAt = 0\n const contributors: GristMappingSource[] = []\n\n for (const src of [...MAPPING_SOURCE_PRIORITY].reverse()) {\n const p = this.payloads.get(src)\n if (!p) continue\n contributors.push(src)\n if (p.receivedAtMs > latestReportedAt) latestReportedAt = p.receivedAtMs\n if (p.payload === null) continue\n for (const [k, v] of Object.entries(p.payload)) {\n merged[k] = v\n }\n }\n\n if (contributors.length === 0) return null\n\n const validation = validateColumnMappings(this.declared, merged)\n const source: GristMappingSource | \"merged\" =\n contributors.length === 1 ? (contributors[0] as GristMappingSource) : \"merged\"\n\n if (validation.ok) {\n return {\n state: \"complete\",\n declared: this.declared,\n mappings: merged,\n source,\n reportedAtMs: latestReportedAt,\n }\n }\n\n return {\n state: \"incomplete\",\n declared: this.declared,\n mappings: merged,\n missing: validation.missing,\n emptyMultiples: validation.emptyMultiples,\n source,\n reportedAtMs: latestReportedAt,\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Stable hash for a {complete|incomplete} mapping payload. Deterministic\n * across re-resolutions of identical inputs so the resolver can de-duplicate.\n */\nexport function computeMappingHash(\n mappings: GristWidgetColumnMap,\n ok: boolean,\n source: GristMappingSource | \"merged\"\n): string {\n const keys = Object.keys(mappings).sort()\n const body = keys.map((k) => {\n const v = mappings[k]\n if (Array.isArray(v)) return `${k}=[${v.join(\",\")}]`\n return `${k}=${v ?? \"\"}`\n }).join(\"|\")\n return `${ok ? \"OK\" : \"KO\"}::${source}::${body}`\n}\n\n/**\n * Decide whether to treat a newly arrived payload as a graceful invalidation\n * of the previous mapping (user just edited the section's mapping settings).\n *\n * Returns `true` if `previous` was `complete`/`incomplete` and `next` differs\n * by at least one key. Pure function, no side effects.\n */\nexport function shouldInvalidate(\n previous: GristMappingState,\n nextMappings: GristWidgetColumnMap\n): boolean {\n if (previous.state !== \"complete\" && previous.state !== \"incomplete\") return false\n const a = previous.mappings\n const aKeys = Object.keys(a)\n const bKeys = Object.keys(nextMappings)\n if (aKeys.length !== bKeys.length) return true\n for (const k of aKeys) {\n const av = a[k]\n const bv = nextMappings[k]\n if (Array.isArray(av) && Array.isArray(bv)) {\n if (av.length !== bv.length) return true\n for (let i = 0; i < av.length; i++) if (av[i] !== bv[i]) return true\n } else if (av !== bv) {\n return true\n }\n }\n return false\n}\n","/**\n * Pure reducer for the handshake state machine.\n *\n * Contract\n * --------\n * - **Pure**: `(snapshot, action, ctx) => nextSnapshot`. No I/O, no globals,\n * no Date.now() — callers pass timestamps via `action.atMs` and (optionally)\n * a context object for the {@link MappingResolver}.\n * - **Total**: every (snapshot.lifecycle.phase × action.type) pair returns\n * either an updated snapshot or the identity snapshot.\n * - **Idempotent for replays**: same generation-stamped action applied twice\n * leaves the snapshot unchanged (`payloadHash` short-circuit on streams,\n * resolver hash short-circuit on mappings).\n * - **Generation-safe**: any action carrying a `generation` lower than the\n * current snapshot generation is dropped silently — this is what makes\n * the SDK robust to late events after reload/restart.\n *\n * The reducer never throws. Invariant violations are surfaced via\n * `./invariants.ts` if the caller opts in.\n */\n\nimport {\n hasGenerationStamp,\n type GristAction,\n type GristMappingAction,\n type GristSyncAction,\n type GristSyncStream,\n} from \"./actions\"\nimport { MappingResolver } from \"./mapping-resolver\"\nimport {\n ZERO_GENERATION,\n nextGeneration,\n type GristGeneration,\n type GristLifecycle,\n type GristLink,\n type GristMappingComplete,\n type GristMappingIncomplete,\n type GristMappingState,\n type GristStreamFreshness,\n type GristSync,\n type GristTerminationReason,\n type GristWidgetSnapshot,\n} from \"./types\"\nimport {\n ACCESS_RANK,\n formatInsufficientAccessMessage,\n isAccessSufficient,\n} from \"./access\"\nimport type { GristRequiredAccess } from \"../../types\"\n\n/**\n * Per-machine mutable companion to the snapshot. The reducer reads from it\n * (and lets the resolver mutate it in-place) so the snapshot itself can stay\n * a deeply-frozen, immutable value.\n */\nexport type GristReducerContext = {\n resolver: MappingResolver | null\n}\n\nexport function createReducerContext(): GristReducerContext {\n return { resolver: null }\n}\n\n/**\n * Tuning knobs. Pass `Partial<ReducerConfig>` to override defaults; values\n * are read by the reducer at each step.\n */\nexport type ReducerConfig = {\n /** Stale-pings threshold before link transitions to `stale`. */\n staleAfterMissed: number\n /** Stale-pings threshold before link transitions to `lost`. */\n lostAfterMissed: number\n}\n\nexport const DEFAULT_REDUCER_CONFIG: ReducerConfig = {\n staleAfterMissed: 1,\n lostAfterMissed: 3,\n}\n\n// ---------------------------------------------------------------------------\n// Reducer entry point\n// ---------------------------------------------------------------------------\n\nexport function gristReducer(\n snapshot: GristWidgetSnapshot,\n action: GristAction,\n ctx: GristReducerContext,\n config: ReducerConfig = DEFAULT_REDUCER_CONFIG\n): GristWidgetSnapshot {\n // Generation gate — applied to every action that carries a generation.\n if (hasGenerationStamp(action) && action.generation < snapshot.generation) {\n return snapshot\n }\n\n switch (action.type) {\n // -- Lifecycle --------------------------------------------------------\n case \"DETECT_START\":\n return reduceDetectStart(snapshot, action.atMs)\n case \"DETECT_TICK\":\n return reduceDetectTick(snapshot, action.attempts)\n case \"DETECT_AVAILABLE\":\n return reduceDetectAvailable(snapshot, action.atMs)\n case \"DETECT_TIMEOUT\":\n return reduceTerminate(snapshot, {\n kind: \"script_timeout\",\n attempts: action.attempts,\n }, action.atMs)\n case \"NOT_EMBEDDED\":\n return reduceTerminate(snapshot, { kind: \"not_embedded\" }, action.atMs)\n case \"NEGOTIATE_START\":\n return reduceNegotiateStart(snapshot, action.access, action.atMs)\n case \"NEGOTIATE_SUCCESS\":\n return reduceNegotiateSuccess(snapshot, action.access, action.grantedLevel, action.atMs)\n case \"NEGOTIATE_FAIL\":\n return reduceTerminate(snapshot, action.reason, action.atMs)\n case \"TERMINATE\":\n return reduceTerminate(snapshot, action.reason, action.atMs)\n case \"AUTHZ_REPORT\":\n return reduceAuthzReport(snapshot, action.level, action.atMs)\n\n // -- Link -------------------------------------------------------------\n case \"RPC_SUCCESS\":\n case \"HEARTBEAT_OK\":\n return reduceLinkSuccess(snapshot, action.atMs)\n case \"RPC_FAILURE\":\n case \"HEARTBEAT_FAIL\":\n return reduceLinkFailure(snapshot, action.atMs, config)\n\n // -- Generation -------------------------------------------------------\n case \"BUMP_GENERATION\":\n return reduceBumpGeneration(snapshot, ctx)\n\n // -- Config / mappings -----------------------------------------------\n case \"MAPPING_DECLARE\":\n case \"MAPPING_REPORT\":\n case \"MAPPING_INVALIDATE\":\n case \"MAPPING_INVALIDATION_RESOLVE\":\n case \"MAPPING_TIMEOUT\":\n return reduceMapping(snapshot, action, ctx)\n\n // -- Sync -------------------------------------------------------------\n case \"STREAM_RECEIVED\":\n case \"STREAM_WARMING\":\n case \"STREAM_STALE\":\n return reduceStream(snapshot, action)\n case \"CURRENT_TABLE_KNOWN\":\n return updateSync(snapshot, {\n ...snapshot.sync,\n currentTable: { state: \"known\", tableId: action.tableId, refreshing: false },\n })\n case \"CURRENT_TABLE_REFRESH_START\":\n if (snapshot.sync.currentTable.state !== \"known\") return snapshot\n return updateSync(snapshot, {\n ...snapshot.sync,\n currentTable: { ...snapshot.sync.currentTable, refreshing: true },\n })\n case \"CURRENT_TABLE_ERROR\":\n return updateSync(snapshot, {\n ...snapshot.sync,\n currentTable: { state: \"errored\", message: action.message, sinceMs: action.atMs },\n })\n\n default: {\n // Exhaustiveness check — TypeScript guarantees this is `never`.\n const _exhaustive: never = action\n void _exhaustive\n return snapshot\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Lifecycle transitions\n// ---------------------------------------------------------------------------\n\nfunction reduceDetectStart(snapshot: GristWidgetSnapshot, atMs: number): GristWidgetSnapshot {\n if (snapshot.lifecycle.phase !== \"idle\") return snapshot\n return {\n ...snapshot,\n lifecycle: { phase: \"detecting\", attempts: 0, startedAtMs: atMs },\n }\n}\n\nfunction reduceDetectTick(snapshot: GristWidgetSnapshot, attempts: number): GristWidgetSnapshot {\n if (snapshot.lifecycle.phase !== \"detecting\") return snapshot\n if (attempts === snapshot.lifecycle.attempts) return snapshot\n return {\n ...snapshot,\n lifecycle: { ...snapshot.lifecycle, attempts },\n }\n}\n\nfunction reduceDetectAvailable(\n snapshot: GristWidgetSnapshot,\n atMs: number\n): GristWidgetSnapshot {\n if (snapshot.lifecycle.phase !== \"detecting\" && snapshot.lifecycle.phase !== \"idle\") {\n return snapshot\n }\n return {\n ...snapshot,\n lifecycle: {\n phase: \"negotiating\",\n access: snapshot.authz.declared,\n startedAtMs: atMs,\n },\n }\n}\n\nfunction reduceNegotiateStart(\n snapshot: GristWidgetSnapshot,\n access: GristWidgetSnapshot[\"authz\"][\"declared\"],\n atMs: number\n): GristWidgetSnapshot {\n // Allowed from `detecting` (first negotiate), `negotiating` (re-arm),\n // or `online` (access widening triggers re-negotiation in place).\n if (snapshot.lifecycle.phase === \"terminated\") return snapshot\n return {\n ...snapshot,\n authz: { state: \"pending\", declared: access },\n lifecycle:\n snapshot.lifecycle.phase === \"online\"\n ? snapshot.lifecycle\n : { phase: \"negotiating\", access, startedAtMs: atMs },\n }\n}\n\nfunction authzDeclared(snapshot: GristWidgetSnapshot): GristRequiredAccess {\n return snapshot.authz.declared\n}\n\nfunction reduceAuthzReport(\n snapshot: GristWidgetSnapshot,\n level: GristRequiredAccess,\n _atMs: number\n): GristWidgetSnapshot {\n if (snapshot.lifecycle.phase !== \"online\") return snapshot\n\n const declared = authzDeclared(snapshot)\n\n if (isAccessSufficient(level, declared)) {\n const previousLevel =\n snapshot.authz.state === \"granted\" ? snapshot.authz.level : null\n const widened =\n previousLevel !== null && ACCESS_RANK[level] > ACCESS_RANK[previousLevel]\n const nextAuthz = {\n state: \"granted\" as const,\n level,\n declared,\n widened,\n }\n if (\n snapshot.authz.state === \"granted\" &&\n snapshot.authz.level === nextAuthz.level &&\n snapshot.authz.declared === nextAuthz.declared &&\n snapshot.authz.widened === nextAuthz.widened\n ) {\n return snapshot\n }\n return { ...snapshot, authz: nextAuthz }\n }\n\n const message = formatInsufficientAccessMessage(declared, level)\n const nextAuthz = { state: \"denied\" as const, declared, message }\n if (\n snapshot.authz.state === \"denied\" &&\n snapshot.authz.declared === nextAuthz.declared &&\n snapshot.authz.message === nextAuthz.message\n ) {\n return snapshot\n }\n return { ...snapshot, authz: nextAuthz }\n}\n\nfunction reduceNegotiateSuccess(\n snapshot: GristWidgetSnapshot,\n access: GristWidgetSnapshot[\"authz\"][\"declared\"],\n grantedLevel: GristWidgetSnapshot[\"authz\"][\"declared\"],\n atMs: number\n): GristWidgetSnapshot {\n if (snapshot.lifecycle.phase === \"terminated\") return snapshot\n\n const previousLevel =\n snapshot.authz.state === \"granted\" ? snapshot.authz.level : null\n const widened =\n previousLevel !== null && ACCESS_RANK[grantedLevel] > ACCESS_RANK[previousLevel]\n\n return {\n ...snapshot,\n authz: {\n state: \"granted\",\n level: grantedLevel,\n declared: access,\n widened,\n },\n lifecycle:\n snapshot.lifecycle.phase === \"online\"\n ? snapshot.lifecycle\n : { phase: \"online\", sinceMs: atMs, declaredAccess: access },\n link: { state: \"connected\", lastSuccessMs: atMs, missedPings: 0 },\n }\n}\n\nfunction reduceTerminate(\n snapshot: GristWidgetSnapshot,\n reason: GristTerminationReason,\n atMs: number\n): GristWidgetSnapshot {\n if (snapshot.lifecycle.phase === \"terminated\") return snapshot\n const lifecycle: GristLifecycle = { phase: \"terminated\", reason, atMs }\n return { ...snapshot, lifecycle }\n}\n\n// ---------------------------------------------------------------------------\n// Link transitions\n// ---------------------------------------------------------------------------\n\nfunction reduceLinkSuccess(snapshot: GristWidgetSnapshot, atMs: number): GristWidgetSnapshot {\n if (snapshot.lifecycle.phase !== \"online\") return snapshot\n const link: GristLink = { state: \"connected\", lastSuccessMs: atMs, missedPings: 0 }\n if (\n snapshot.link.state === link.state &&\n snapshot.link.lastSuccessMs === link.lastSuccessMs &&\n snapshot.link.missedPings === link.missedPings\n ) {\n return snapshot\n }\n return { ...snapshot, link }\n}\n\nfunction reduceLinkFailure(\n snapshot: GristWidgetSnapshot,\n _atMs: number,\n config: ReducerConfig\n): GristWidgetSnapshot {\n if (snapshot.lifecycle.phase !== \"online\") return snapshot\n const missed = snapshot.link.missedPings + 1\n let state: GristLink[\"state\"] = snapshot.link.state\n if (missed >= config.lostAfterMissed) state = \"lost\"\n else if (missed >= config.staleAfterMissed) state = \"stale\"\n return {\n ...snapshot,\n link: { state, lastSuccessMs: snapshot.link.lastSuccessMs, missedPings: missed },\n }\n}\n\n// ---------------------------------------------------------------------------\n// Generation\n// ---------------------------------------------------------------------------\n\nfunction reduceBumpGeneration(\n snapshot: GristWidgetSnapshot,\n ctx: GristReducerContext\n): GristWidgetSnapshot {\n const next = nextGeneration(snapshot.generation)\n ctx.resolver?.bumpGeneration(next)\n return {\n ...snapshot,\n generation: next,\n // Marking streams as warming again ensures hooks treat the data as\n // \"in transit\" until a fresh event arrives under the new generation.\n sync: {\n ...snapshot.sync,\n record: degradeForRefresh(snapshot.sync.record, snapshot.generation),\n records: degradeForRefresh(snapshot.sync.records, snapshot.generation),\n options: degradeForRefresh(snapshot.sync.options, snapshot.generation),\n newRecord: degradeForRefresh(snapshot.sync.newRecord, snapshot.generation),\n },\n }\n}\n\nfunction degradeForRefresh(\n freshness: GristStreamFreshness,\n prevGeneration: GristGeneration\n): GristStreamFreshness {\n if (freshness.state === \"hot\" && freshness.generation === prevGeneration) {\n return { state: \"stale\", lastEventMs: freshness.lastEventMs, reason: \"host_quiet\" }\n }\n return freshness\n}\n\n// ---------------------------------------------------------------------------\n// Mappings (config)\n// ---------------------------------------------------------------------------\n\nfunction reduceMapping(\n snapshot: GristWidgetSnapshot,\n action: GristMappingAction,\n ctx: GristReducerContext\n): GristWidgetSnapshot {\n switch (action.type) {\n case \"MAPPING_DECLARE\": {\n if (action.declared.length === 0) {\n // Empty declaration → revert to `not_declared`.\n if (snapshot.config.mappings.state === \"not_declared\") return snapshot\n ctx.resolver = null\n return {\n ...snapshot,\n config: { mappings: { state: \"not_declared\" } },\n }\n }\n if (!ctx.resolver) {\n ctx.resolver = new MappingResolver(action.declared, snapshot.generation)\n } else {\n ctx.resolver.redeclare(action.declared, snapshot.generation)\n }\n return {\n ...snapshot,\n config: {\n mappings: {\n state: \"pending\",\n declared: action.declared,\n declaredAtMs: action.atMs,\n },\n },\n }\n }\n case \"MAPPING_REPORT\": {\n if (!ctx.resolver) return snapshot\n const outcome = ctx.resolver.ingest({\n source: action.source,\n payload: action.payload,\n receivedAtMs: action.atMs,\n generation: action.generation,\n })\n if (outcome.kind !== \"resolved\") return snapshot\n return applyResolved(snapshot, outcome.state)\n }\n case \"MAPPING_INVALIDATE\": {\n const previous = snapshot.config.mappings\n if (previous.state !== \"complete\" && previous.state !== \"incomplete\") {\n return snapshot\n }\n return {\n ...snapshot,\n config: {\n mappings: {\n state: \"invalidating\",\n previous: previous as GristMappingComplete | GristMappingIncomplete,\n sinceMs: action.atMs,\n },\n },\n }\n }\n case \"MAPPING_INVALIDATION_RESOLVE\": {\n if (snapshot.config.mappings.state !== \"invalidating\") return snapshot\n if (!ctx.resolver) {\n // Resolver lost (e.g. declaration cleared) — fall back to `previous`.\n return {\n ...snapshot,\n config: { mappings: snapshot.config.mappings.previous },\n }\n }\n const outcome = ctx.resolver.current()\n if (outcome.kind !== \"resolved\") {\n return {\n ...snapshot,\n config: { mappings: snapshot.config.mappings.previous },\n }\n }\n return applyResolved(snapshot, outcome.state)\n }\n case \"MAPPING_TIMEOUT\": {\n const previous = snapshot.config.mappings\n if (previous.state !== \"pending\") return snapshot\n return {\n ...snapshot,\n config: {\n mappings: {\n state: \"unreported\",\n declared: previous.declared,\n afterMs: action.atMs - previous.declaredAtMs,\n },\n },\n }\n }\n }\n}\n\nfunction applyResolved(\n snapshot: GristWidgetSnapshot,\n resolved: GristMappingComplete | GristMappingIncomplete\n): GristWidgetSnapshot {\n const existing = snapshot.config.mappings\n if (\n (existing.state === \"complete\" || existing.state === \"incomplete\") &&\n sameResolved(existing, resolved)\n ) {\n return snapshot\n }\n return { ...snapshot, config: { mappings: resolved } }\n}\n\nfunction sameResolved(\n a: GristMappingState,\n b: GristMappingComplete | GristMappingIncomplete\n): boolean {\n if (a.state !== b.state) return false\n if (a.state !== \"complete\" && a.state !== \"incomplete\") return false\n if (a.source !== b.source) return false\n const ak = Object.keys(a.mappings)\n const bk = Object.keys(b.mappings)\n if (ak.length !== bk.length) return false\n for (const k of ak) {\n const av = a.mappings[k]\n const bv = b.mappings[k]\n if (Array.isArray(av) && Array.isArray(bv)) {\n if (av.length !== bv.length) return false\n for (let i = 0; i < av.length; i++) if (av[i] !== bv[i]) return false\n } else if (av !== bv) {\n return false\n }\n }\n return true\n}\n\n// ---------------------------------------------------------------------------\n// Streams (sync)\n// ---------------------------------------------------------------------------\n\nfunction reduceStream(snapshot: GristWidgetSnapshot, action: GristSyncAction): GristWidgetSnapshot {\n if (action.type === \"CURRENT_TABLE_KNOWN\" || action.type === \"CURRENT_TABLE_REFRESH_START\" || action.type === \"CURRENT_TABLE_ERROR\") {\n return snapshot\n }\n const stream = action.stream\n const current = snapshot.sync[stream]\n\n if (action.type === \"STREAM_WARMING\") {\n if (current.state === \"warming\" || current.state === \"hot\") return snapshot\n return updateStream(snapshot, stream, { state: \"warming\", sinceMs: action.atMs })\n }\n\n if (action.type === \"STREAM_STALE\") {\n if (current.state !== \"hot\") return snapshot\n return updateStream(snapshot, stream, {\n state: \"stale\",\n lastEventMs: current.lastEventMs,\n reason: action.reason,\n })\n }\n\n // STREAM_RECEIVED — idempotent on (generation, hash).\n if (\n current.state === \"hot\" &&\n current.generation === action.generation &&\n current.payloadHash === action.payloadHash\n ) {\n return snapshot\n }\n return updateStream(snapshot, stream, {\n state: \"hot\",\n lastEventMs: action.atMs,\n generation: action.generation,\n payloadHash: action.payloadHash,\n })\n}\n\nfunction updateStream(\n snapshot: GristWidgetSnapshot,\n stream: GristSyncStream,\n next: GristStreamFreshness\n): GristWidgetSnapshot {\n return {\n ...snapshot,\n sync: { ...snapshot.sync, [stream]: next },\n }\n}\n\nfunction updateSync(snapshot: GristWidgetSnapshot, sync: GristSync): GristWidgetSnapshot {\n return { ...snapshot, sync }\n}\n\n// ---------------------------------------------------------------------------\n// Re-exports (convenience)\n// ---------------------------------------------------------------------------\n\nexport { ZERO_GENERATION, nextGeneration }\n","import type { GristWidgetInteraction, GristWidgetLinkingInfo } from \"../types\"\n\n/**\n * Parse `InteractionOptions.linking` from a Grist `onOptions` settings payload.\n */\nexport function parseGristWidgetLinkingInfo(raw: unknown): GristWidgetLinkingInfo | undefined {\n if (!raw || typeof raw !== \"object\" || !(\"asTarget\" in raw)) return undefined\n const linking = raw as { asTarget: unknown; asSource?: unknown }\n return {\n asTarget: linking.asTarget == null ? null : String(linking.asTarget),\n asSource: Boolean(linking.asSource),\n }\n}\n\n/**\n * Normalize the second argument to `grist.onOptions` (Grist `InteractionOptions`).\n * Hosts use `accessLevel` (camelCase); the emulator uses `access_level`.\n */\nexport function normalizeWidgetInteraction(\n interaction: GristWidgetInteraction | null\n): GristWidgetInteraction | null {\n if (!interaction || typeof interaction !== \"object\") return interaction\n const raw = interaction as Record<string, unknown>\n const access_level =\n typeof raw.access_level === \"string\"\n ? raw.access_level\n : typeof raw.accessLevel === \"string\"\n ? raw.accessLevel\n : undefined\n const linking = parseGristWidgetLinkingInfo(raw.linking)\n return {\n ...interaction,\n ...(access_level ? { access_level } : {}),\n ...(linking ? { linking } : {}),\n }\n}\n","/**\n * Single-call wrapper around `grist.ready()`.\n *\n * Grist's contract is \"call ready once\". Multiple `useGristReady` calls (or\n * `useGristWidget` from sibling components) would otherwise race. We:\n *\n * - Coalesce concurrent calls into one in-flight promise.\n * - Re-call `grist.ready` only when `requiredAccess` widens (so an action-capable\n * hook can upgrade a previously read-only handshake).\n */\n\nimport type { GristColumnsToMap, GristReadyOptions, GristRequiredAccess } from \"../types\"\n\nconst ACCESS_RANK: Record<GristRequiredAccess, number> = {\n none: 0,\n \"read table\": 1,\n full: 2,\n}\n\nlet inFlight: Promise<void> | null = null\nlet lastAccess: GristRequiredAccess | null = null\nlet lastColumnNames: ReadonlySet<string> | null = null\n\nfunction columnNamesOf(columns: GristColumnsToMap | undefined): Set<string> {\n return new Set((columns ?? []).map((c) => (typeof c === \"string\" ? c : c.name)))\n}\n\n/**\n * Re-init whenever the new payload asks for strictly more than what's\n * already cached: a higher access level, or a column name the cached\n * payload didn't declare. Prevents a bare fallback caller (e.g. a status\n * chip with no declared columns) from permanently sticking a widget's real,\n * richer requirements to the host once it happens to win the initial race.\n */\nfunction shouldReinit(next: GristRequiredAccess, nextColumns: ReadonlySet<string>): boolean {\n if (!lastAccess) return true\n if (ACCESS_RANK[next] > ACCESS_RANK[lastAccess]) return true\n for (const name of nextColumns) {\n if (!lastColumnNames?.has(name)) return true\n }\n return false\n}\n\n/**\n * Calls `grist.ready` at most once per page (re-called only if access widens\n * or previously-undeclared columns are requested).\n * Returns the same promise for all concurrent callers.\n */\nexport function ensureGristReady(payload: GristReadyOptions): Promise<void> {\n const access = payload.requiredAccess ?? \"read table\"\n const columns = columnNamesOf(payload.columns)\n if (inFlight && !shouldReinit(access, columns)) return inFlight\n\n lastAccess = access\n lastColumnNames = columns\n const next = Promise.resolve().then(() => {\n const result = grist.ready(payload)\n return result instanceof Promise ? result : Promise.resolve()\n })\n\n inFlight = next.catch((err) => {\n inFlight = null\n lastAccess = null\n lastColumnNames = null\n throw err\n })\n return inFlight\n}\n\n/**\n * Forget the cached ready state. Subsequent calls to {@link ensureGristReady}\n * will re-issue `grist.ready` even at the same access level.\n *\n * Production uses: the handshake manager's `reload()` clears the singleton so\n * a soft retry triggers a fresh `grist.ready` (otherwise the cached promise\n * would short-circuit and the user-visible reload would be a no-op).\n *\n * Tests use this between test cases when swapping `window.grist`.\n */\nexport function resetGristReadySingleton(): void {\n inFlight = null\n lastAccess = null\n lastColumnNames = null\n}\n\n/** @deprecated Use {@link resetGristReadySingleton}. Kept for test back-compat. */\nexport const resetGristReadyCacheForTests = resetGristReadySingleton\n","/**\n * Module-level subscription manager for Grist's `on*` events.\n *\n * Why this exists: `grist.onRecord`, `onRecords`, `onOptions`, and `onNewRecord`\n * are add-only — there is no `off`. Subscribing in every hook instance leaks\n * handlers for the page lifetime. This module subscribes to each Grist event\n * **at most once** and fans out to React listeners with proper teardown.\n *\n * Last-known values are cached so late subscribers immediately receive the\n * current state (analogous to a BehaviorSubject).\n *\n * Fixed-defaults contract\n * -----------------------\n * The underlying `grist.onRecord` / `onRecords` calls accept a\n * `FetchSelectedOptions` parameter (e.g. `keepEncoded`, `format`), but\n * because we bind **once** at the first subscribe, those options would be\n * \"first-hook-wins\" across the page — a subtle footgun for agents.\n *\n * This module therefore binds with Grist's **public defaults** and does not\n * accept per-subscriber options:\n * - `onRecord` → decoded rows (`keepEncoded: false`)\n * - `onRecords` → decoded rows (`format: \"rows\"`, `keepEncoded: false`)\n *\n * If you need encoded values, use the one-shot readers\n * (`useGrist().fetchSelectedTable({ keepEncoded: true })`) instead of\n * a subscription — that call-site controls its own options.\n */\n\nimport { normalizeWidgetInteraction } from \"../helpers/grist-widget-interaction\"\nimport type { GristWidgetColumnMap, GristWidgetInteraction } from \"../types\"\n\ntype Listener<T> = (value: T) => void\n\nclass GristEvent<T> {\n private listeners = new Set<Listener<T>>()\n private hasValue = false\n private last: T | undefined\n private bind: (() => void) | null = null\n\n constructor(private readonly bindOnce: (emit: (value: T) => void) => void) {}\n\n subscribe(listener: Listener<T>): () => void {\n this.listeners.add(listener)\n if (!this.bind) {\n this.bind = () => {\n // Mark as bound regardless of throw, so we don't retry every render.\n this.bind = () => {}\n try {\n this.bindOnce((value) => {\n this.hasValue = true\n this.last = value\n for (const l of this.listeners) l(value)\n })\n } catch (err) {\n if (typeof console !== \"undefined\") {\n console.error(\"[lib/grist] failed to subscribe to Grist event:\", err)\n }\n }\n }\n this.bind()\n }\n if (this.hasValue && this.last !== undefined) {\n listener(this.last)\n }\n return () => {\n this.listeners.delete(listener)\n }\n }\n\n /** Last cached value (or `undefined` if no event has fired yet). */\n peek(): T | undefined {\n return this.last\n }\n\n /** True once at least one event has fired. */\n get ready(): boolean {\n return this.hasValue\n }\n\n /**\n * Test helper: fan out a payload without calling `grist.on*`.\n * Used to simulate hosts that reuse one record object and mutate it in place.\n */\n deliverForTests(value: T): void {\n this.hasValue = true\n this.last = value\n for (const l of this.listeners) l(value)\n }\n\n /**\n * Test helper: forget the bound state and cached value. Lets a test swap\n * `window.grist` (e.g. via the emulator's `dispose()` + `createGristEmulator()`)\n * and re-bind cleanly on the next subscribe.\n */\n _resetForTests(): void {\n this.listeners.clear()\n this.hasValue = false\n this.last = undefined\n this.bind = null\n }\n}\n\nexport type GristRecordPayload = {\n record: Record<string, unknown> | null\n mappings: GristWidgetColumnMap | null\n}\n\nexport type GristRecordsPayload = {\n records: Record<string, unknown>[]\n mappings: GristWidgetColumnMap | null\n}\n\nexport type GristOptionsPayload = {\n options: Record<string, unknown> | null\n interaction: GristWidgetInteraction | null\n}\n\nexport type GristNewRecordPayload = {\n mappings: GristWidgetColumnMap | null\n}\n\n/**\n * The singletons. These are lazy: nothing is registered with `grist` until\n * the first `subscribe()` call.\n */\ntype GristHostMessage = {\n tableId?: string\n rowId?: number | \"new\"\n dataChange?: boolean\n mappingsChange?: boolean\n}\n\nexport const recordEvent = new GristEvent<GristRecordPayload>((emit) => {\n const deliver = (record: Record<string, unknown> | null, mappings: GristWidgetColumnMap | null) => {\n emit({ record, mappings })\n }\n\n // Pin `keepEncoded: false` so mapped records contain JS-native values for\n // widget code instead of raw cell tuples.\n grist.onRecord(\n (record, mappings) => {\n deliver(record, (mappings as GristWidgetColumnMap | null) ?? null)\n },\n { keepEncoded: false }\n )\n\n // Mirror grist-plugin-api's `onRecord` message handler: Grist's RecordNotifier\n // posts `{ rowId, dataChange: false }` on cursor moves. `onRecord` should\n // handle that, but refetch here when `rowId` changes so widgets never stick on\n // the first row if the callback path is skipped or reordered.\n const fetchSelectedRecord = grist.fetchSelectedRecord ?? grist.docApi?.fetchSelectedRecord\n if (typeof grist.on !== \"function\" || typeof fetchSelectedRecord !== \"function\") {\n return\n }\n let lastRowId: number | undefined\n grist.on(\"message\", async (msg: unknown) => {\n if (!msg || typeof msg !== \"object\") return\n const host = msg as GristHostMessage\n if (!host.tableId || typeof host.rowId !== \"number\") return\n if (host.rowId === lastRowId) return\n lastRowId = host.rowId\n try {\n const record = await fetchSelectedRecord(host.rowId, { keepEncoded: false })\n deliver(record, recordEvent.peek()?.mappings ?? null)\n } catch (err) {\n if (typeof console !== \"undefined\") {\n console.error(\"[grist-widget-sdk] fetchSelectedRecord on cursor message failed:\", err)\n }\n }\n })\n})\n\nexport const recordsEvent = new GristEvent<GristRecordsPayload>((emit) => {\n // Pin `format: \"rows\"` + `keepEncoded: false` so callers always receive\n // row-shaped decoded objects. (GristView would otherwise default to\n // `format: \"columns\"` + `keepEncoded: true`.)\n grist.onRecords(\n (records, mappings) => {\n emit({ records, mappings: (mappings as GristWidgetColumnMap | null) ?? null })\n },\n { keepEncoded: false, format: \"rows\" }\n )\n})\n\nexport const optionsEvent = new GristEvent<GristOptionsPayload>((emit) => {\n grist.onOptions((options, interaction) => {\n const normalized = normalizeWidgetInteraction(\n (interaction as GristWidgetInteraction | null) ?? null\n )\n emit({\n options: options ? { ...options } : null,\n interaction: normalized,\n })\n })\n})\n\nexport const newRecordEvent = new GristEvent<GristNewRecordPayload>((emit) => {\n if (typeof grist.onNewRecord !== \"function\") return\n grist.onNewRecord((mappings) => {\n emit({ mappings: (mappings as GristWidgetColumnMap | null) ?? null })\n })\n})\n\n/**\n * Test-only: reset every event singleton. Call between tests when swapping\n * `window.grist`. Production code never imports this.\n */\nexport function resetGristEventsForTests(): void {\n recordEvent._resetForTests()\n recordsEvent._resetForTests()\n optionsEvent._resetForTests()\n newRecordEvent._resetForTests()\n}\n\n/** Vitest-only: push a record payload through {@link recordEvent} subscribers. */\nexport function deliverRecordForTests(payload: GristRecordPayload): void {\n recordEvent.deliverForTests(payload)\n}\n","/**\n * Detect effect — poll for `window.grist` with exponential backoff inside a\n * finite, optionally adaptive budget.\n *\n * Outputs (dispatched to the manager):\n * - DETECT_START (once, at start)\n * - DETECT_TICK (each unsuccessful poll, with attempts count)\n * - DETECT_AVAILABLE (grist global present AND embedded)\n * - NOT_EMBEDDED (grist global present but `self === top`)\n * - DETECT_TIMEOUT (budget consumed without detection)\n *\n * The effect aborts on `signal.aborted` and clears its pending timer.\n *\n * Pure of `window`: relies on the {@link GristEnvironment} adapter so the\n * effect is fully unit-testable with a virtual clock.\n */\n\nimport type { GristEnvironment, TimerHandle } from \"../environment\"\nimport type { GristAction } from \"../actions\"\n\n/**\n * Default poll sequence (ms). Caps at 5 000 ms — most production widgets see\n * `grist-plugin-api.js` load in < 1 s, but slow 3G can stretch to 20–30 s.\n */\nexport const DEFAULT_POLL_SEQUENCE_MS: readonly number[] = [\n 200, 200, 400, 400, 800, 1600, 3200, 5000, 5000, 5000, 5000, 5000, 5000, 5000,\n] as const\n\n/** Effective budget, adaptive on `navigator.connection.effectiveType`. */\nexport function defaultBudgetMs(hint: ReturnType<GristEnvironment[\"effectiveTypeHint\"]>): number {\n if (hint === \"slow-2g\" || hint === \"2g\") return 120_000\n if (hint === \"3g\") return 60_000\n return 30_000\n}\n\nexport type DetectOptions = {\n /** Custom poll sequence in ms. Index past end uses last value. */\n pollSequenceMs?: readonly number[]\n /** Total wall-clock budget across all polls. */\n budgetMs?: number\n}\n\nexport type DetectHandle = {\n cancel(): void\n}\n\n/**\n * Start the detect effect. The manager owns the cancellation handle and is\n * responsible for calling `cancel()` on `restart()` / `stop()`.\n */\nexport function startDetectEffect(\n env: GristEnvironment,\n dispatch: (action: GristAction) => void,\n signal: AbortSignal,\n options: DetectOptions = {}\n): DetectHandle {\n const sequence = options.pollSequenceMs ?? DEFAULT_POLL_SEQUENCE_MS\n const budgetMs = options.budgetMs ?? defaultBudgetMs(env.effectiveTypeHint())\n\n const startedAt = env.now()\n let attempts = 0\n let timer: TimerHandle | null = null\n let cancelled = false\n\n signal.addEventListener(\"abort\", () => {\n cancelled = true\n if (timer !== null) env.clearTimeout(timer)\n })\n\n dispatch({ type: \"DETECT_START\", atMs: startedAt })\n\n const tick = () => {\n if (cancelled) return\n timer = null\n\n const probe = env.probeGrist()\n if (probe !== null) {\n if (probe.embedded) {\n dispatch({ type: \"DETECT_AVAILABLE\", atMs: env.now() })\n } else {\n dispatch({ type: \"NOT_EMBEDDED\", atMs: env.now() })\n }\n return\n }\n\n attempts += 1\n dispatch({ type: \"DETECT_TICK\", attempts, atMs: env.now() })\n\n const elapsed = env.now() - startedAt\n if (elapsed >= budgetMs) {\n dispatch({ type: \"DETECT_TIMEOUT\", attempts, atMs: env.now() })\n return\n }\n\n const next = sequence[Math.min(attempts - 1, sequence.length - 1)] ?? 1000\n const remaining = budgetMs - elapsed\n timer = env.setTimeout(tick, Math.max(0, Math.min(next, remaining)))\n }\n\n // Probe immediately at t=0 so widgets where grist is already global don't\n // wait for the first interval.\n tick()\n\n return {\n cancel() {\n cancelled = true\n if (timer !== null) {\n env.clearTimeout(timer)\n timer = null\n }\n },\n }\n}\n","/**\n * Negotiate effect — issue `grist.ready(payload)` race-d against a timeout\n * and an external `AbortSignal`.\n *\n * Dispatches:\n * - NEGOTIATE_START\n * - NEGOTIATE_SUCCESS (grist.ready resolved AND `docApi` is present)\n * - NEGOTIATE_FAIL (reason: ready_failed | ready_timeout | manual_terminate)\n *\n * The effect never throws to the caller. Aborts before resolution silently\n * stop dispatching.\n */\n\nimport type { GristEnvironment, GristLike } from \"../environment\"\nimport type { GristAction } from \"../actions\"\nimport type { GristReadyOptions, GristRequiredAccess } from \"../../../types\"\nimport type { GristTerminationReason } from \"../types\"\n\nexport type NegotiateOptions = {\n /** Max ms to wait for `grist.ready` to resolve. */\n timeoutMs?: number\n /**\n * Custom implementation of `grist.ready(payload)`. Defaults to calling\n * `grist.ready` directly via the env. Production callers (the provider)\n * inject a wrapper that routes through `ensureGristReady()` so the\n * page-level singleton dedupes concurrent ready calls.\n */\n readyImpl?: (payload: GristReadyOptions) => Promise<void> | void\n}\n\nexport const DEFAULT_NEGOTIATE_TIMEOUT_MS = 30_000\n\nexport type NegotiateHandle = {\n cancel(): void\n}\n\nexport function startNegotiateEffect(\n env: GristEnvironment,\n dispatch: (action: GristAction) => void,\n signal: AbortSignal,\n payload: GristReadyOptions,\n options: NegotiateOptions = {}\n): NegotiateHandle {\n const timeoutMs = options.timeoutMs ?? DEFAULT_NEGOTIATE_TIMEOUT_MS\n const access: GristRequiredAccess = payload.requiredAccess ?? \"read table\"\n let settled = false\n let timer: ReturnType<GristEnvironment[\"setTimeout\"]> | null = null\n\n const settle = (next: () => void) => {\n if (settled) return\n settled = true\n if (timer !== null) env.clearTimeout(timer)\n if (signal.aborted) return\n next()\n }\n\n dispatch({ type: \"NEGOTIATE_START\", access, atMs: env.now() })\n\n const grist = env.getGrist()\n if (!grist) {\n settle(() =>\n dispatch({\n type: \"NEGOTIATE_FAIL\",\n reason: { kind: \"ready_failed\", message: \"grist global missing at negotiate\" },\n atMs: env.now(),\n })\n )\n return { cancel: () => settle(() => {}) }\n }\n\n // Schedule timeout first; ready() may resolve synchronously and trigger\n // `settle` before this fires.\n timer = env.setTimeout(() => {\n if (settled) return\n settle(() =>\n dispatch({\n type: \"NEGOTIATE_FAIL\",\n reason: { kind: \"ready_timeout\", afterMs: timeoutMs },\n atMs: env.now(),\n })\n )\n }, timeoutMs)\n\n const handleSuccess = (g: GristLike) => {\n if (!g.docApi) {\n dispatch({\n type: \"NEGOTIATE_FAIL\",\n reason: { kind: \"ready_failed\", message: \"grist.docApi missing after ready\" },\n atMs: env.now(),\n })\n return\n }\n dispatch({\n type: \"NEGOTIATE_SUCCESS\",\n access,\n grantedLevel: access, // host doesn't report effective level; widen later via authz events\n atMs: env.now(),\n })\n }\n\n const handleFailure = (err: unknown) => {\n const message = err instanceof Error ? err.message : String(err)\n const reason: GristTerminationReason = { kind: \"ready_failed\", message }\n dispatch({ type: \"NEGOTIATE_FAIL\", reason, atMs: env.now() })\n }\n\n const readyImpl = options.readyImpl ?? ((p) => grist.ready(p))\n try {\n const result = readyImpl(payload)\n if (result && typeof (result as Promise<void>).then === \"function\") {\n ;(result as Promise<void>).then(\n () => settle(() => handleSuccess(env.getGrist() ?? grist)),\n (err) => settle(() => handleFailure(err))\n )\n } else {\n settle(() => handleSuccess(env.getGrist() ?? grist))\n }\n } catch (err) {\n settle(() => handleFailure(err))\n }\n\n signal.addEventListener(\"abort\", () => settle(() => {}))\n\n return { cancel: () => settle(() => {}) }\n}\n","/**\n * Subscriptions effect — bind Grist event streams once and feed the handshake\n * machine with generation-stamped, hash-deduplicated dispatches.\n *\n * Why a dedicated effect\n * ----------------------\n * The SDK already has a {@link \"../../subscriptions\"} module that owns the\n * one-time binding to `grist.on*` and the BehaviorSubject-style cache. We do\n * NOT re-bind here — instead, we install a single listener per stream that\n * forwards into the handshake reducer.\n *\n * Generation discipline: every dispatched action carries the CURRENT manager\n * generation read at the moment the event arrives. The reducer enforces\n * `action.generation >= snapshot.generation`, so events arriving after a\n * `reload()` / `restart()` that bumped generation are dropped automatically.\n *\n * Hash: payloads are reduced to a compact, deterministic string so duplicates\n * (e.g. emulator triple-replay of initial state) become no-ops at the reducer.\n */\n\nimport type { GristAction, GristSyncStream } from \"../actions\"\nimport { parseGristRequiredAccess } from \"../access\"\nimport type { GristGeneration } from \"../types\"\nimport {\n newRecordEvent,\n optionsEvent,\n recordEvent,\n recordsEvent,\n} from \"../../subscriptions\"\n\nexport type SubscriptionsHandle = {\n cancel(): void\n}\n\n/**\n * Pluggable binder type. The default {@link defaultSubscriptionsBinder}\n * subscribes to the SDK's singleton {@link GristEvent} instances. Tests can\n * supply a stub that never touches the real `grist` global.\n */\nexport type SubscriptionsBinder = (ctx: SubscriptionsContext) => SubscriptionsHandle\n\nexport type SubscriptionsContext = {\n dispatch: (action: GristAction) => void\n /**\n * Snapshot generation provider. Must be read lazily (inside the callback)\n * because `generation` is monotone but mutates over the lifetime of the\n * subscription.\n */\n getGeneration: () => GristGeneration\n /** Hook for the mapping effect: receive payloads in arrival order. */\n onRecordPayload?: (mappings: Record<string, unknown> | null | undefined) => void\n onRecordsPayload?: (mappings: Record<string, unknown> | null | undefined) => void\n onOptionsPayload?: (options: Record<string, unknown> | null) => void\n onNewRecordPayload?: (mappings: Record<string, unknown> | null | undefined) => void\n}\n\nexport const bindSubscriptions: SubscriptionsBinder = defaultSubscriptionsBinder\n\nexport function defaultSubscriptionsBinder(ctx: SubscriptionsContext): SubscriptionsHandle {\n const offs: Array<() => void> = []\n\n offs.push(\n recordEvent.subscribe((payload) => {\n const hash = quickHash(\"record\", payload)\n dispatchStream(ctx, \"record\", hash)\n ctx.onRecordPayload?.(payload.mappings ?? null)\n })\n )\n\n offs.push(\n recordsEvent.subscribe((payload) => {\n const hash = quickHash(\"records\", payload)\n dispatchStream(ctx, \"records\", hash)\n ctx.onRecordsPayload?.(payload.mappings ?? null)\n })\n )\n\n offs.push(\n optionsEvent.subscribe((payload) => {\n const hash = quickHash(\"options\", payload)\n dispatchStream(ctx, \"options\", hash)\n const level = parseGristRequiredAccess(payload.interaction?.access_level)\n if (level !== null) {\n ctx.dispatch({\n type: \"AUTHZ_REPORT\",\n level,\n atMs: Date.now(),\n generation: ctx.getGeneration(),\n })\n }\n ctx.onOptionsPayload?.(payload.options ?? null)\n })\n )\n\n offs.push(\n newRecordEvent.subscribe((payload) => {\n const hash = quickHash(\"newRecord\", payload)\n dispatchStream(ctx, \"newRecord\", hash)\n ctx.onNewRecordPayload?.(payload.mappings ?? null)\n })\n )\n\n return {\n cancel() {\n for (const off of offs.splice(0)) off()\n },\n }\n}\n\n/**\n * No-op binder for tests that don't care about event streams. Returned handle's\n * `cancel()` is a no-op. Use {@link defaultSubscriptionsBinder} in production.\n */\nexport const noopSubscriptionsBinder: SubscriptionsBinder = () => ({\n cancel: () => {},\n})\n\nfunction dispatchStream(\n ctx: SubscriptionsContext,\n stream: GristSyncStream,\n payloadHash: string\n): void {\n ctx.dispatch({\n type: \"STREAM_RECEIVED\",\n stream,\n payloadHash,\n atMs: Date.now(),\n generation: ctx.getGeneration(),\n })\n}\n\n/**\n * Deterministic, low-cost hash of an event payload.\n *\n * We do NOT need cryptographic strength — only equality on identical inputs\n * for idempotence. A short, stable serialization is enough.\n */\nexport function quickHash(kind: string, payload: unknown): string {\n return `${kind}::${stableSerialize(payload)}`\n}\n\nfunction stableSerialize(value: unknown): string {\n if (value === null) return \"null\"\n if (value === undefined) return \"undefined\"\n const t = typeof value\n if (t === \"number\" || t === \"string\" || t === \"boolean\") return JSON.stringify(value)\n if (Array.isArray(value)) {\n return `[${value.map(stableSerialize).join(\",\")}]`\n }\n if (t === \"object\") {\n const obj = value as Record<string, unknown>\n const keys = Object.keys(obj).sort()\n const body = keys.map((k) => `${JSON.stringify(k)}:${stableSerialize(obj[k])}`).join(\",\")\n return `{${body}}`\n }\n return String(value)\n}\n","/**\n * Mappings effect — feed column-mapping payloads into the reducer from the\n * three Grist sources (`sectionApi.mappings()`, `onRecord`, `onRecords`,\n * `onNewRecord`) and trigger the mapping timeout if nothing arrives in time.\n *\n * Inputs:\n * - `declared` — `columns` from the provider options.\n * - `getGeneration()` — current snapshot generation (for stale-drop).\n * - `getGrist()` — environment accessor to `grist.sectionApi.mappings()`.\n *\n * Outputs:\n * - `MAPPING_DECLARE` (once at start when `declared.length > 0`)\n * - `MAPPING_REPORT` (one per source payload received)\n * - `MAPPING_TIMEOUT` (if no payload after `timeoutMs`)\n *\n * The effect does NOT subscribe to grist events directly — it consumes them\n * via the {@link SubscriptionsContext} payload callbacks. This keeps the\n * single-binding contract of `subscriptions.ts` intact (one `grist.onRecord`\n * binding per page).\n */\n\nimport type { GristEnvironment } from \"../environment\"\nimport type { GristAction } from \"../actions\"\nimport type { GristColumnsToMap, GristWidgetColumnMap } from \"../../../types\"\nimport type { GristGeneration } from \"../types\"\n\nexport type MappingsOptions = {\n /**\n * Time after `MAPPING_DECLARE` before falling back to `unreported` state.\n * Default 5 000 ms.\n */\n timeoutMs?: number\n /**\n * If true, dispatch `MAPPING_INVALIDATE` whenever a payload differs from\n * the last applied mappings. Default false — graceful invalidation requires\n * a `mappingsChange: true` envelope flag the SDK doesn't currently surface,\n * so we leave it opt-in for tests.\n */\n invalidateOnDiff?: boolean\n}\n\nexport const DEFAULT_MAPPING_TIMEOUT_MS = 5_000\n\nexport type MappingsHandle = {\n /** Ingest a payload from `onRecord` / `onRecords` / `onNewRecord`. */\n ingestStreamPayload(\n source: \"stream_record\" | \"stream_records\" | \"stream_new_record\",\n payload: unknown\n ): void\n /** Force a re-fetch of `sectionApi.mappings()` (e.g. on link recovery). */\n refetchSectionApi(): Promise<void>\n /** Cancel all pending timers. */\n cancel(): void\n}\n\n/**\n * Start the mapping effect. Returns a handle the manager wires into the\n * subscriptions effect's `on*Payload` callbacks.\n *\n * The effect is a no-op when `declared.length === 0` (the reducer's\n * `MAPPING_DECLARE` action turns it into `not_declared`).\n */\nexport function startMappingsEffect(\n env: GristEnvironment,\n dispatch: (action: GristAction) => void,\n signal: AbortSignal,\n declared: GristColumnsToMap,\n getGeneration: () => GristGeneration,\n options: MappingsOptions = {}\n): MappingsHandle {\n let timeoutHandle: ReturnType<GristEnvironment[\"setTimeout\"]> | null = null\n let cancelled = false\n\n const startMs = env.now()\n dispatch({ type: \"MAPPING_DECLARE\", declared, atMs: startMs, generation: getGeneration() })\n\n // No declaration → no timeout, no section_api fetch, no further work.\n if (declared.length === 0) {\n return {\n ingestStreamPayload: () => {},\n refetchSectionApi: async () => {},\n cancel: () => {},\n }\n }\n\n // Arm the unreported timeout.\n const timeoutMs = options.timeoutMs ?? DEFAULT_MAPPING_TIMEOUT_MS\n timeoutHandle = env.setTimeout(() => {\n if (cancelled || signal.aborted) return\n dispatch({ type: \"MAPPING_TIMEOUT\", atMs: env.now(), generation: getGeneration() })\n }, timeoutMs)\n\n signal.addEventListener(\"abort\", () => {\n cancelled = true\n if (timeoutHandle !== null) env.clearTimeout(timeoutHandle)\n })\n\n const ingestPayload = (\n source: \"section_api\" | \"stream_record\" | \"stream_records\" | \"stream_new_record\",\n raw: unknown\n ) => {\n if (cancelled || signal.aborted) return\n const payload = coerceMappingsPayload(raw)\n dispatch({\n type: \"MAPPING_REPORT\",\n source,\n payload,\n atMs: env.now(),\n generation: getGeneration(),\n })\n }\n\n // Kick off `sectionApi.mappings()` once.\n const refetchSectionApi = async () => {\n if (cancelled || signal.aborted) return\n const grist = env.getGrist()\n const api = grist?.sectionApi\n if (!api || typeof api.mappings !== \"function\") return\n try {\n const payload = await api.mappings()\n ingestPayload(\"section_api\", payload)\n } catch {\n ingestPayload(\"section_api\", null)\n }\n }\n void refetchSectionApi()\n\n return {\n ingestStreamPayload: (source, payload) => ingestPayload(source, payload),\n refetchSectionApi,\n cancel() {\n cancelled = true\n if (timeoutHandle !== null) env.clearTimeout(timeoutHandle)\n },\n }\n}\n\nfunction coerceMappingsPayload(raw: unknown): GristWidgetColumnMap | null {\n if (raw === null || raw === undefined) return null\n if (typeof raw !== \"object\") return null\n return raw as GristWidgetColumnMap\n}\n","/**\n * Heartbeat effect — periodically verify the RPC channel is alive.\n *\n * Strategy\n * --------\n * 1. Schedule a ping every `intervalMs` (default 30 s).\n * 2. The ping calls a cheap RPC (default `grist.docApi.getDocName()`).\n * 3. Success → `HEARTBEAT_OK`. Failure / timeout → `HEARTBEAT_FAIL`.\n * 4. Any externally-observed RPC success (the caller invokes\n * {@link HeartbeatHandle.recordRpcSuccess}) coalesces with the heartbeat\n * and reschedules the next ping — we don't burn a request when natural\n * traffic already proves the link.\n * 5. Tab hidden? Pause via `env.onVisibilityChange`. Resume on `visible`.\n *\n * The effect is **purely an observer**: it never mutates application state\n * directly — it only dispatches actions the reducer consumes.\n */\n\nimport type { GristEnvironment, TimerHandle } from \"../environment\"\nimport type { GristAction } from \"../actions\"\n\nexport type HeartbeatOptions = {\n /** How often to issue a probe when no natural RPC traffic occurred. */\n intervalMs?: number\n /** Max time to wait for the probe before counting it as a failure. */\n pingTimeoutMs?: number\n /** If true, pause the heartbeat when the document becomes hidden. */\n pauseWhenHidden?: boolean\n /**\n * Probe used when the interval elapses. Defaults to\n * `() => env.getGrist().docApi.getDocName()`. Tests pass a controlled stub.\n */\n probe?: () => Promise<unknown>\n}\n\nexport const DEFAULT_HEARTBEAT_INTERVAL_MS = 30_000\nexport const DEFAULT_HEARTBEAT_PING_TIMEOUT_MS = 10_000\n\nexport type HeartbeatHandle = {\n /** Stop scheduling and cancel any pending probe. Idempotent. */\n cancel(): void\n /**\n * Coalesce: tell the heartbeat that an unrelated RPC just succeeded. The\n * heartbeat treats it as a free `HEARTBEAT_OK` and pushes the next probe\n * out by a full interval.\n */\n recordRpcSuccess(): void\n /**\n * Coalesce: an unrelated RPC failed. The heartbeat dispatches\n * `HEARTBEAT_FAIL` without firing its own probe, so the link signal\n * degrades immediately rather than waiting for the interval.\n */\n recordRpcFailure(): void\n /** Force a probe now (e.g. on `online` event). */\n pingNow(): void\n}\n\nexport function startHeartbeatEffect(\n env: GristEnvironment,\n dispatch: (action: GristAction) => void,\n signal: AbortSignal,\n options: HeartbeatOptions = {}\n): HeartbeatHandle {\n const intervalMs = options.intervalMs ?? DEFAULT_HEARTBEAT_INTERVAL_MS\n const pingTimeoutMs = options.pingTimeoutMs ?? DEFAULT_HEARTBEAT_PING_TIMEOUT_MS\n const pauseWhenHidden = options.pauseWhenHidden ?? true\n const probe = options.probe ?? defaultProbe(env)\n\n let cancelled = false\n let paused = false\n let intervalTimer: TimerHandle | null = null\n let inFlight = false\n let inFlightTimeoutTimer: TimerHandle | null = null\n\n const clearInterval = () => {\n if (intervalTimer !== null) {\n env.clearTimeout(intervalTimer)\n intervalTimer = null\n }\n }\n const clearInFlightTimeout = () => {\n if (inFlightTimeoutTimer !== null) {\n env.clearTimeout(inFlightTimeoutTimer)\n inFlightTimeoutTimer = null\n }\n }\n\n const scheduleNext = (delay = intervalMs) => {\n clearInterval()\n if (cancelled || paused) return\n intervalTimer = env.setTimeout(ping, delay)\n }\n\n const ping = () => {\n if (cancelled || paused) return\n intervalTimer = null\n if (inFlight) {\n // A previous probe still in flight; let it resolve before scheduling.\n return\n }\n inFlight = true\n\n // Timeout guard.\n inFlightTimeoutTimer = env.setTimeout(() => {\n if (!inFlight) return\n inFlight = false\n clearInFlightTimeout()\n if (!cancelled) {\n dispatch({ type: \"HEARTBEAT_FAIL\", atMs: env.now() })\n scheduleNext()\n }\n }, pingTimeoutMs)\n\n const handleOk = () => {\n if (!inFlight) return\n inFlight = false\n clearInFlightTimeout()\n if (cancelled) return\n dispatch({ type: \"HEARTBEAT_OK\", atMs: env.now() })\n scheduleNext()\n }\n const handleFail = () => {\n if (!inFlight) return\n inFlight = false\n clearInFlightTimeout()\n if (cancelled) return\n dispatch({ type: \"HEARTBEAT_FAIL\", atMs: env.now() })\n scheduleNext()\n }\n\n // Call probe synchronously so we collapse one microtask round. If it\n // throws synchronously or returns a non-thenable, settle immediately.\n let result: unknown\n try {\n result = probe()\n } catch {\n handleFail()\n return\n }\n if (\n result &&\n typeof (result as { then?: unknown }).then === \"function\"\n ) {\n ;(result as Promise<unknown>).then(handleOk, handleFail)\n } else {\n handleOk()\n }\n }\n\n // Visibility wiring.\n const offVisibility =\n pauseWhenHidden && env.onVisibilityChange\n ? env.onVisibilityChange((visible) => {\n if (cancelled) return\n if (visible) {\n if (paused) {\n paused = false\n // Refresh immediately on resume — a hidden tab might have\n // missed a long stale window.\n ping()\n }\n } else {\n paused = true\n clearInterval()\n // Note: an in-flight probe is allowed to settle naturally; we\n // just don't schedule another one.\n }\n })\n : null\n\n // Online recovery — when the network reports it's back, ping immediately.\n const offOnline =\n env.onOnline?.(() => {\n if (cancelled || paused) return\n ping()\n }) ?? null\n\n signal.addEventListener(\"abort\", () => {\n cancelled = true\n clearInterval()\n clearInFlightTimeout()\n inFlight = false\n offVisibility?.()\n offOnline?.()\n })\n\n // Boot: first probe after `intervalMs`. We don't ping at t=0 because the\n // very transition that started the heartbeat (e.g. NEGOTIATE_SUCCESS) just\n // proved the link is alive.\n scheduleNext()\n\n return {\n cancel() {\n cancelled = true\n clearInterval()\n clearInFlightTimeout()\n inFlight = false\n offVisibility?.()\n offOnline?.()\n },\n recordRpcSuccess() {\n if (cancelled || paused) return\n dispatch({ type: \"RPC_SUCCESS\", atMs: env.now() })\n scheduleNext()\n },\n recordRpcFailure() {\n if (cancelled || paused) return\n dispatch({ type: \"RPC_FAILURE\", atMs: env.now(), kind: \"other\" })\n // Don't reschedule — a real failure should keep the interval close so\n // we re-probe quickly to confirm.\n scheduleNext(Math.min(intervalMs, 1_000))\n },\n pingNow() {\n if (cancelled) return\n ping()\n },\n }\n}\n\nfunction defaultProbe(env: GristEnvironment): () => Promise<unknown> {\n return async () => {\n const g = env.getGrist()\n if (!g) throw new Error(\"grist global missing\")\n const docApi = g.docApi\n if (!docApi || typeof docApi.getDocName !== \"function\") {\n throw new Error(\"grist.docApi.getDocName unavailable\")\n }\n return docApi.getDocName()\n }\n}\n","/**\n * Re-apply the cached `grist.onOptions` interaction after `reload()`.\n *\n * `reload()` re-runs `grist.ready` (optimistic grant) but Grist often does not\n * send a fresh `onOptions`. The subscription singleton still holds the last\n * `access_level`, which must be checked once the handshake is `online` again.\n *\n * Only runs after `reload()` — not on the initial connect — so a stale\n * pre-`ready` cache cannot deny a first successful handshake.\n */\n\nimport { isAccessSufficient, parseGristRequiredAccess } from \"./access\"\nimport type { GristAction } from \"./actions\"\nimport type { GristEnvironment } from \"./environment\"\nimport type { GristWidgetSnapshot } from \"./types\"\nimport { optionsEvent } from \"../subscriptions\"\n\nexport function denyAuthzFromCachedOptionsIfInsufficient(\n snapshot: GristWidgetSnapshot,\n dispatch: (action: GristAction) => void,\n env: GristEnvironment\n): void {\n if (snapshot.lifecycle.phase !== \"online\") return\n if (snapshot.authz.state === \"denied\") return\n\n const payload = optionsEvent.peek()\n const level = parseGristRequiredAccess(payload?.interaction?.access_level)\n if (level === null) return\n\n const declared = snapshot.authz.declared\n if (isAccessSufficient(level, declared)) return\n\n dispatch({\n type: \"AUTHZ_REPORT\",\n level,\n atMs: env.now(),\n generation: snapshot.generation,\n })\n}\n","/**\n * Environment abstraction for the handshake effects layer.\n *\n * Why an abstraction\n * ------------------\n * The detect / negotiate / heartbeat effects need access to:\n * - the `grist` global (or absence thereof),\n * - `window.self !== window.top` to detect iframe embedding,\n * - timers (`setTimeout`, `clearTimeout`),\n * - the wall clock (`performance.now()` or `Date.now()`),\n * - `navigator.connection?.effectiveType` for adaptive policy.\n *\n * Putting these behind a small interface lets us test the manager and effects\n * **without touching `window`**, using deterministic fake timers and stubbed\n * detectors / negotiators.\n *\n * Production code calls {@link createBrowserEnvironment()} which returns an\n * adapter against `globalThis`. Tests pass {@link createTestEnvironment} or a\n * hand-rolled stub.\n */\n\nimport type { GristDocApi, GristReadyOptions, GristWidgetColumnMap } from \"../../types\"\n\n// ---------------------------------------------------------------------------\n// Public env interface\n// ---------------------------------------------------------------------------\n\nexport type GristEnvironment = {\n /** Wall-clock ms (monotonic preferred but not required). */\n now(): number\n /** Schedule a callback. */\n setTimeout(cb: () => void, ms: number): TimerHandle\n /** Cancel a timer. */\n clearTimeout(handle: TimerHandle): void\n\n /**\n * Probe whether the `grist` global is present in this realm.\n * Returns `{ embedded }` when the global exists; `null` otherwise.\n */\n probeGrist(): { embedded: boolean } | null\n\n /**\n * Snapshot of the `grist` global. Effects use this once detection has\n * succeeded; callers must NOT cache the result across `restart()`.\n */\n getGrist(): GristLike | null\n\n /** Adaptive detection budget hint (ms). `null` if unknown. */\n effectiveTypeHint(): EffectiveTypeHint | null\n\n /** Add a listener for `visibilitychange` / `online` / `focus`. Returns cleanup. */\n onVisibilityChange?(listener: (visible: boolean) => void): () => void\n onOnline?(listener: () => void): () => void\n}\n\nexport type TimerHandle = ReturnType<typeof setTimeout> | number | { __opaque: true }\n\nexport type EffectiveTypeHint = \"slow-2g\" | \"2g\" | \"3g\" | \"4g\" | \"5g\"\n\n/**\n * Subset of `window.grist` actually used by handshake effects. Mirrors the\n * declared shape in `grist-global.d.ts` but only the pieces we need.\n */\nexport type GristLike = {\n ready: (options?: GristReadyOptions) => Promise<void> | void\n docApi: GristDocApi | null | undefined\n onRecord?: (\n cb: (\n record: Record<string, unknown> | null,\n mappings?: GristWidgetColumnMap | null\n ) => void,\n options?: { keepEncoded?: boolean }\n ) => void\n onRecords?: (\n cb: (\n records: Record<string, unknown>[],\n mappings?: GristWidgetColumnMap | null\n ) => void,\n options?: { keepEncoded?: boolean; format?: string }\n ) => void\n onOptions?: (\n cb: (options: Record<string, unknown> | null, interaction: unknown) => void\n ) => void\n onNewRecord?: (cb: (mappings: GristWidgetColumnMap | null) => void) => void\n sectionApi?: {\n mappings: () => Promise<GristWidgetColumnMap | null>\n }\n}\n\n// ---------------------------------------------------------------------------\n// Browser environment factory\n// ---------------------------------------------------------------------------\n\nexport function createBrowserEnvironment(): GristEnvironment {\n return {\n now: () => (typeof performance !== \"undefined\" ? performance.now() : Date.now()),\n setTimeout: (cb, ms) => globalThis.setTimeout(cb, ms),\n clearTimeout: (h) => globalThis.clearTimeout(h as ReturnType<typeof setTimeout>),\n\n probeGrist() {\n if (typeof globalThis === \"undefined\") return null\n const g = (globalThis as { grist?: unknown }).grist\n if (typeof g === \"undefined\") return null\n const embedded =\n typeof window === \"undefined\" ? false : window.self !== window.top\n return { embedded }\n },\n\n getGrist() {\n const g = (globalThis as { grist?: GristLike }).grist\n return g ?? null\n },\n\n effectiveTypeHint() {\n if (typeof navigator === \"undefined\") return null\n const conn = (navigator as { connection?: { effectiveType?: string } }).connection\n const t = conn?.effectiveType\n if (t === \"slow-2g\" || t === \"2g\" || t === \"3g\" || t === \"4g\" || t === \"5g\") return t\n return null\n },\n\n onVisibilityChange(listener) {\n if (typeof document === \"undefined\") return () => {}\n const handler = () => listener(document.visibilityState === \"visible\")\n document.addEventListener(\"visibilitychange\", handler)\n return () => document.removeEventListener(\"visibilitychange\", handler)\n },\n\n onOnline(listener) {\n if (typeof window === \"undefined\") return () => {}\n const handler = () => listener()\n window.addEventListener(\"online\", handler)\n return () => window.removeEventListener(\"online\", handler)\n },\n }\n}\n\n// ---------------------------------------------------------------------------\n// Test environment factory — fake timers, stubbed grist\n// ---------------------------------------------------------------------------\n\nexport type TestEnvironmentOptions = {\n /**\n * Provider for the `grist` stub. Called every `probeGrist()` / `getGrist()`.\n * Returning `null` simulates \"script not loaded yet\".\n */\n grist?: () => GristLike | null\n embedded?: () => boolean\n effectiveType?: EffectiveTypeHint\n}\n\nexport type TestEnvironment = GristEnvironment & {\n /** Manually advance virtual time. Fires due timers in order. */\n advance(ms: number): void\n /** Currently scheduled timers. */\n pendingTimers(): readonly { dueAt: number; cb: () => void }[]\n /** Trigger visibilitychange listeners. */\n emitVisibility(visible: boolean): void\n /** Trigger online listeners. */\n emitOnline(): void\n}\n\nexport function createTestEnvironment(opts: TestEnvironmentOptions = {}): TestEnvironment {\n let clock = 0\n const timers: Array<{ id: number; dueAt: number; cb: () => void }> = []\n let nextId = 1\n const visListeners = new Set<(v: boolean) => void>()\n const onlineListeners = new Set<() => void>()\n\n function advance(ms: number) {\n const target = clock + ms\n while (true) {\n timers.sort((a, b) => a.dueAt - b.dueAt)\n const due = timers.find((t) => t.dueAt <= target)\n if (!due) break\n timers.splice(timers.indexOf(due), 1)\n clock = due.dueAt\n due.cb()\n }\n clock = target\n }\n\n return {\n now: () => clock,\n setTimeout(cb, ms) {\n const id = nextId++\n timers.push({ id, dueAt: clock + ms, cb })\n return id as unknown as TimerHandle\n },\n clearTimeout(handle) {\n const id = handle as unknown as number\n const idx = timers.findIndex((t) => t.id === id)\n if (idx >= 0) timers.splice(idx, 1)\n },\n\n probeGrist() {\n const g = opts.grist?.() ?? null\n if (!g) return null\n const embedded = opts.embedded?.() ?? true\n return { embedded }\n },\n getGrist: () => opts.grist?.() ?? null,\n effectiveTypeHint: () => opts.effectiveType ?? null,\n\n onVisibilityChange(listener) {\n visListeners.add(listener)\n return () => visListeners.delete(listener)\n },\n onOnline(listener) {\n onlineListeners.add(listener)\n return () => onlineListeners.delete(listener)\n },\n\n advance,\n pendingTimers: () => timers.map((t) => ({ dueAt: t.dueAt, cb: t.cb })),\n emitVisibility(visible) {\n for (const l of visListeners) l(visible)\n },\n emitOnline() {\n for (const l of onlineListeners) l()\n },\n }\n}\n","/**\n * GristHandshakeManager — single owner of the handshake snapshot.\n *\n * Responsibilities\n * ----------------\n * 1. Hold the snapshot and the {@link GristReducerContext}.\n * 2. Run effects (detect → negotiate → subscriptions + mappings) in order.\n * 3. Bump generation on `reload()` / `restart()` and propagate cancellation\n * through a per-generation {@link AbortController}.\n * 4. Notify subscribers on each snapshot change (interface compatible with\n * React's `useSyncExternalStore`).\n *\n * The manager is a **class**, not a hook. React hooks subscribe to it via the\n * standard `subscribe` / `getSnapshot` pair so unit tests can drive the\n * manager directly with a virtual clock — no React needed.\n */\n\nimport {\n DEFAULT_REDUCER_CONFIG,\n createReducerContext,\n gristReducer,\n initialGristSnapshot,\n nextGeneration,\n type GristAction,\n type GristReducerContext,\n type GristWidgetSnapshot,\n type ReducerConfig,\n} from \"./index\"\nimport { startDetectEffect, type DetectHandle, type DetectOptions } from \"./effects/detect\"\nimport {\n startNegotiateEffect,\n type NegotiateHandle,\n type NegotiateOptions,\n} from \"./effects/negotiate\"\nimport {\n defaultSubscriptionsBinder,\n type SubscriptionsBinder,\n type SubscriptionsHandle,\n} from \"./effects/subscriptions\"\nimport {\n startMappingsEffect,\n type MappingsHandle,\n type MappingsOptions,\n} from \"./effects/mappings\"\nimport {\n startHeartbeatEffect,\n type HeartbeatHandle,\n type HeartbeatOptions,\n} from \"./effects/heartbeat\"\nimport { denyAuthzFromCachedOptionsIfInsufficient } from \"./authz-from-options-cache\"\nimport {\n createBrowserEnvironment,\n type GristEnvironment,\n} from \"./environment\"\nimport type { GristColumnsToMap, GristReadyOptions } from \"../../types\"\n\nexport type ManagerOptions = GristReadyOptions & {\n detect?: DetectOptions\n negotiate?: NegotiateOptions\n mappings?: MappingsOptions\n heartbeat?: HeartbeatOptions | false\n reducerConfig?: Partial<ReducerConfig>\n /**\n * Where to read `grist`, timers, and platform hints from. Defaults to a\n * browser adapter. Tests pass {@link createTestEnvironment}.\n */\n env?: GristEnvironment\n /**\n * Binder for grist event streams. Defaults to\n * {@link defaultSubscriptionsBinder} which wires the SDK's singleton\n * {@link \"../../subscriptions\"} module. Tests can pass `noopSubscriptionsBinder`\n * to avoid touching the real `grist` global.\n */\n subscriptionsBinder?: SubscriptionsBinder\n /**\n * Called when the manager catches an unexpected internal error.\n * Telemetry hook — does not affect reducer state.\n */\n onError?: (err: unknown, where: string) => void\n /**\n * Called from inside {@link ManagerControls.reload} BEFORE generation bump.\n * Production callers route `resetGristReadySingleton()` through here so\n * the page-level ready singleton is cleared and the next negotiate effect\n * issues a fresh `grist.ready` instead of re-using the cached promise.\n */\n onBeforeReload?: () => void\n}\n\nexport type ManagerControls = {\n /** Start the effects pipeline. Idempotent after `start()`. */\n start(): void\n /** Cancel all running effects and reset to `idle`. */\n stop(): void\n /**\n * Soft retry: bumps generation, cancels current effects, re-runs detect/\n * negotiate. Subscriptions are NOT re-bound — the underlying singletons\n * survive (and re-deliver cached values).\n */\n reload(): void\n /**\n * Hard reload of the host iframe. Browser-only — in the test env this is a\n * no-op. Production code may also call `window.location.reload()` directly.\n */\n restart(): void\n subscribe(listener: () => void): () => void\n getSnapshot(): GristWidgetSnapshot\n /**\n * Test/internal: inject an action directly. Used by integration tests that\n * want to simulate authz events, link failures, etc.\n */\n dispatch(action: GristAction): void\n /**\n * Coalesce: an external RPC succeeded — tell the heartbeat to skip the\n * next scheduled probe and reset the missed-pings counter. Returns\n * silently when the heartbeat is disabled.\n */\n recordRpcSuccess(): void\n /**\n * Coalesce: an external RPC failed — degrade the link signal immediately.\n * Returns silently when the heartbeat is disabled.\n */\n recordRpcFailure(): void\n}\n\nexport function createGristHandshakeManager(options: ManagerOptions = {}): ManagerControls {\n const env = options.env ?? createBrowserEnvironment()\n const reducerConfig: ReducerConfig = {\n ...DEFAULT_REDUCER_CONFIG,\n ...(options.reducerConfig ?? {}),\n }\n\n let snapshot = initialGristSnapshot()\n const ctx: GristReducerContext = createReducerContext()\n const listeners = new Set<() => void>()\n\n let started = false\n let abort: AbortController | null = null\n let detectHandle: DetectHandle | null = null\n let negotiateHandle: NegotiateHandle | null = null\n let subscriptionsHandle: SubscriptionsHandle | null = null\n let mappingsHandle: MappingsHandle | null = null\n let heartbeatHandle: HeartbeatHandle | null = null\n /** Set by `reload()`; cleared after one post-online cache check. */\n let recheckAuthzFromCacheAfterOnline = false\n\n const declared: GristColumnsToMap = options.columns ?? []\n const subscriptionsBinder = options.subscriptionsBinder ?? defaultSubscriptionsBinder\n\n const notify = () => {\n for (const l of listeners) {\n try {\n l()\n } catch (err) {\n options.onError?.(err, \"listener\")\n }\n }\n }\n\n const dispatch = (action: GristAction) => {\n const prev = snapshot\n const next = gristReducer(prev, action, ctx, reducerConfig)\n if (next !== prev) {\n snapshot = next\n // Run side-effects that depend on phase transitions BEFORE notifying.\n onTransition(prev, next)\n notify()\n }\n }\n\n /**\n * Effect-runner glue: react to lifecycle transitions inside the manager\n * (without polluting the pure reducer).\n *\n * Rationale: the reducer cannot start a `grist.ready()` because that's\n * impure. The reducer instead emits the transition (DETECT_AVAILABLE →\n * `lifecycle.phase === \"negotiating\"`) and the manager observes the\n * transition here to launch the next effect.\n */\n const onTransition = (prev: GristWidgetSnapshot, next: GristWidgetSnapshot) => {\n if (prev.lifecycle.phase !== \"negotiating\" && next.lifecycle.phase === \"negotiating\") {\n launchNegotiate()\n // Bind `grist.onRecord` before `grist.ready` resolves so the host's\n // post-ready cursor/data push is not dropped (iframe transport).\n launchSubscriptions()\n }\n if (prev.lifecycle.phase !== \"online\" && next.lifecycle.phase === \"online\") {\n launchMappings()\n launchHeartbeat()\n if (recheckAuthzFromCacheAfterOnline) {\n recheckAuthzFromCacheAfterOnline = false\n denyAuthzFromCachedOptionsIfInsufficient(next, dispatch, env)\n }\n }\n if (prev.lifecycle.phase === \"online\" && next.lifecycle.phase !== \"online\") {\n // Leaving the online phase — stop probing immediately.\n heartbeatHandle?.cancel()\n heartbeatHandle = null\n }\n }\n\n const launchDetect = () => {\n if (!abort) return\n detectHandle?.cancel()\n detectHandle = startDetectEffect(env, dispatch, abort.signal, options.detect)\n }\n\n const launchNegotiate = () => {\n if (!abort) return\n negotiateHandle?.cancel()\n negotiateHandle = startNegotiateEffect(\n env,\n dispatch,\n abort.signal,\n buildReadyPayload(options),\n options.negotiate\n )\n }\n\n const launchSubscriptions = () => {\n if (!abort) return\n if (subscriptionsHandle) return\n subscriptionsHandle = subscriptionsBinder({\n dispatch,\n getGeneration: () => snapshot.generation,\n onRecordPayload: (m) =>\n mappingsHandle?.ingestStreamPayload(\"stream_record\", m),\n onRecordsPayload: (m) =>\n mappingsHandle?.ingestStreamPayload(\"stream_records\", m),\n onNewRecordPayload: (m) =>\n mappingsHandle?.ingestStreamPayload(\"stream_new_record\", m),\n })\n }\n\n const launchMappings = () => {\n if (!abort) return\n launchSubscriptions()\n if (mappingsHandle) return\n mappingsHandle = startMappingsEffect(\n env,\n dispatch,\n abort.signal,\n declared,\n () => snapshot.generation,\n options.mappings\n )\n }\n\n const launchHeartbeat = () => {\n if (!abort) return\n if (options.heartbeat === false) return\n if (heartbeatHandle) return\n heartbeatHandle = startHeartbeatEffect(\n env,\n dispatch,\n abort.signal,\n options.heartbeat ?? {}\n )\n }\n\n const cancelAllEffects = () => {\n detectHandle?.cancel()\n detectHandle = null\n negotiateHandle?.cancel()\n negotiateHandle = null\n mappingsHandle?.cancel()\n mappingsHandle = null\n heartbeatHandle?.cancel()\n heartbeatHandle = null\n if (abort && !abort.signal.aborted) abort.abort()\n }\n\n const controls: ManagerControls = {\n start() {\n if (started) return\n started = true\n abort = new AbortController()\n launchDetect()\n },\n stop() {\n if (!started) return\n started = false\n cancelAllEffects()\n subscriptionsHandle?.cancel()\n subscriptionsHandle = null\n // Reset snapshot to `idle` so a subsequent `start()` is clean.\n snapshot = initialGristSnapshot()\n notify()\n },\n reload() {\n if (!started) {\n controls.start()\n return\n }\n recheckAuthzFromCacheAfterOnline = true\n try {\n options.onBeforeReload?.()\n } catch (err) {\n options.onError?.(err, \"onBeforeReload\")\n }\n cancelAllEffects()\n // Bump generation so any lingering microtasks / RPC resolutions get dropped.\n dispatch({ type: \"BUMP_GENERATION\", atMs: env.now(), reason: \"reload\" })\n // Reset snapshot to `idle` for the lifecycle restart.\n snapshot = {\n ...initialGristSnapshot(),\n generation: nextGeneration(snapshot.generation),\n }\n ctx.resolver = null\n abort = new AbortController()\n launchDetect()\n notify()\n },\n restart() {\n controls.reload()\n // Hard reload only makes sense in a real browser realm.\n if (typeof window !== \"undefined\" && typeof window.location?.reload === \"function\") {\n try {\n window.location.reload()\n } catch (err) {\n options.onError?.(err, \"restart\")\n }\n }\n },\n subscribe(listener) {\n listeners.add(listener)\n return () => listeners.delete(listener)\n },\n getSnapshot: () => snapshot,\n dispatch,\n recordRpcSuccess() {\n heartbeatHandle?.recordRpcSuccess()\n },\n recordRpcFailure() {\n heartbeatHandle?.recordRpcFailure()\n },\n }\n\n return controls\n}\n\nfunction buildReadyPayload(options: ManagerOptions): GristReadyOptions {\n const payload: GristReadyOptions = {\n requiredAccess: options.requiredAccess ?? \"read table\",\n columns: options.columns ?? [],\n }\n if (options.allowSelectBy !== undefined) payload.allowSelectBy = options.allowSelectBy\n if (options.hasCustomOptions !== undefined) payload.hasCustomOptions = options.hasCustomOptions\n if (options.onEditOptions) payload.onEditOptions = options.onEditOptions\n return payload\n}\n","/**\n * Page-level embedded handshake manager for widgets that call `useGrist()` /\n * `useGristCore()` without mounting `<GristWidgetProvider>`.\n *\n * A single manager is shared across all hook instances on the page (ref-counted)\n * so mid-level hooks (`useGristActions`, `useGristRowsFromTable`, …) do not\n * spawn competing `grist.ready` handshakes.\n */\n\nimport {\n createGristHandshakeManager,\n type ManagerControls,\n type ManagerOptions,\n} from \"./handshake/manager\"\n\ntype EmbeddedSlot = {\n manager: ManagerControls\n refCount: number\n}\n\nlet embedded: EmbeddedSlot | null = null\n\nexport function acquireEmbeddedHandshakeManager(options: ManagerOptions): ManagerControls {\n if (!embedded) {\n embedded = {\n manager: createGristHandshakeManager(options),\n refCount: 0,\n }\n embedded.manager.start()\n }\n embedded.refCount += 1\n return embedded.manager\n}\n\nexport function releaseEmbeddedHandshakeManager(): void {\n if (!embedded) return\n embedded.refCount -= 1\n if (embedded.refCount <= 0) {\n embedded.manager.stop()\n embedded = null\n }\n}\n\n/** Test isolation — tear down the page-level embedded manager between cases. */\nexport function resetEmbeddedHandshakeManagerForTests(): void {\n if (embedded) {\n embedded.manager.stop()\n embedded = null\n }\n}\n"],"mappings":"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","names":["nextAuthz","ACCESS_RANK","grist","grist","inFlight","embedded"]}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// src/sdk/internal/handshake/access.ts
|
|
2
|
+
var ACCESS_RANK = {
|
|
3
|
+
none: 0,
|
|
4
|
+
"read table": 1,
|
|
5
|
+
full: 2
|
|
6
|
+
};
|
|
7
|
+
var GRIST_ACCESS_LEVELS = ["none", "read table", "full"];
|
|
8
|
+
function parseGristRequiredAccess(raw) {
|
|
9
|
+
if (!raw) return null;
|
|
10
|
+
return GRIST_ACCESS_LEVELS.includes(raw) ? raw : null;
|
|
11
|
+
}
|
|
12
|
+
function isAccessSufficient(granted, required) {
|
|
13
|
+
return ACCESS_RANK[granted] >= ACCESS_RANK[required];
|
|
14
|
+
}
|
|
15
|
+
function gristAccessLevelLabel(level) {
|
|
16
|
+
switch (level) {
|
|
17
|
+
case "none":
|
|
18
|
+
return "No document access";
|
|
19
|
+
case "read table":
|
|
20
|
+
return "Read selected table";
|
|
21
|
+
case "full":
|
|
22
|
+
return "Full document access";
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function formatInsufficientAccessMessage(required, _granted) {
|
|
26
|
+
const need = gristAccessLevelLabel(required);
|
|
27
|
+
return `Set Access level to at least "${need}" in the Custom widget settings.`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export {
|
|
31
|
+
ACCESS_RANK,
|
|
32
|
+
parseGristRequiredAccess,
|
|
33
|
+
isAccessSufficient,
|
|
34
|
+
gristAccessLevelLabel,
|
|
35
|
+
formatInsufficientAccessMessage
|
|
36
|
+
};
|
|
37
|
+
//# sourceMappingURL=chunk-N3X46JW2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/sdk/internal/handshake/access.ts"],"sourcesContent":["/**\n * Access-level helpers used by the handshake state machine.\n *\n * Kept in a tiny dedicated file because the same ranking is needed by the\n * reducer (`widened` flag, future re-negotiation logic) and potentially by\n * effects (decide whether to re-issue `grist.ready`).\n */\n\nimport type { GristRequiredAccess } from \"../../types\"\n\n/** Strictly-ordered ranking. Higher rank = stronger access. */\nexport const ACCESS_RANK: Record<GristRequiredAccess, number> = {\n none: 0,\n \"read table\": 1,\n full: 2,\n}\n\nconst GRIST_ACCESS_LEVELS: readonly GristRequiredAccess[] = [\"none\", \"read table\", \"full\"]\n\n/** Parse `interaction.access_level` / `accessLevel` from Grist `onOptions`. */\nexport function parseGristRequiredAccess(\n raw: string | undefined\n): GristRequiredAccess | null {\n if (!raw) return null\n return (GRIST_ACCESS_LEVELS as readonly string[]).includes(raw)\n ? (raw as GristRequiredAccess)\n : null\n}\n\n/** True when the host-granted level meets what the widget declared in `grist.ready`. */\nexport function isAccessSufficient(\n granted: GristRequiredAccess,\n required: GristRequiredAccess\n): boolean {\n return ACCESS_RANK[granted] >= ACCESS_RANK[required]\n}\n\n/** Creator-panel label for a Grist access level (for user-facing copy). */\nexport function gristAccessLevelLabel(level: GristRequiredAccess): string {\n switch (level) {\n case \"none\":\n return \"No document access\"\n case \"read table\":\n return \"Read selected table\"\n case \"full\":\n return \"Full document access\"\n }\n}\n\nexport function formatInsufficientAccessMessage(\n required: GristRequiredAccess,\n _granted: GristRequiredAccess\n): string {\n const need = gristAccessLevelLabel(required)\n return `Set Access level to at least \"${need}\" in the Custom widget settings.`\n}\n\nexport function isAccessWidening(\n previous: GristRequiredAccess | null,\n next: GristRequiredAccess\n): boolean {\n if (previous === null) return true\n return ACCESS_RANK[next] > ACCESS_RANK[previous]\n}\n"],"mappings":";AAWO,IAAM,cAAmD;AAAA,EAC9D,MAAM;AAAA,EACN,cAAc;AAAA,EACd,MAAM;AACR;AAEA,IAAM,sBAAsD,CAAC,QAAQ,cAAc,MAAM;AAGlF,SAAS,yBACd,KAC4B;AAC5B,MAAI,CAAC,IAAK,QAAO;AACjB,SAAQ,oBAA0C,SAAS,GAAG,IACzD,MACD;AACN;AAGO,SAAS,mBACd,SACA,UACS;AACT,SAAO,YAAY,OAAO,KAAK,YAAY,QAAQ;AACrD;AAGO,SAAS,sBAAsB,OAAoC;AACxE,UAAQ,OAAO;AAAA,IACb,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,EACX;AACF;AAEO,SAAS,gCACd,UACA,UACQ;AACR,QAAM,OAAO,sBAAsB,QAAQ;AAC3C,SAAO,iCAAiC,IAAI;AAC9C;","names":[]}
|
|
@@ -42,6 +42,18 @@ function gristRemoveRecordAction(tableId, rowId) {
|
|
|
42
42
|
return ["RemoveRecord", tableId, rowId];
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
// src/sdk/internal/handshake/access.ts
|
|
46
|
+
function gristAccessLevelLabel(level) {
|
|
47
|
+
switch (level) {
|
|
48
|
+
case "none":
|
|
49
|
+
return "No document access";
|
|
50
|
+
case "read table":
|
|
51
|
+
return "Read selected table";
|
|
52
|
+
case "full":
|
|
53
|
+
return "Full document access";
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
45
57
|
// src/emulator/core/mappings.ts
|
|
46
58
|
function resolveMappings(state) {
|
|
47
59
|
if (state.selection.mappings !== void 0 && state.selection.mappings !== null) {
|
|
@@ -90,6 +102,7 @@ function eventTag(event) {
|
|
|
90
102
|
|
|
91
103
|
// src/emulator/devtools/inspector.tsx
|
|
92
104
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
105
|
+
var ACCESS_LEVELS = ["none", "read table", "full"];
|
|
93
106
|
function EmulatorInspector({ emulator, className, showTimeline = true }) {
|
|
94
107
|
const { state, history } = useEmulatorSnapshot(emulator);
|
|
95
108
|
const tableIds = [...state.tables.keys()];
|
|
@@ -110,6 +123,10 @@ function EmulatorInspector({ emulator, className, showTimeline = true }) {
|
|
|
110
123
|
const onEditCell = (rowId, colId, value) => {
|
|
111
124
|
emulator.apply([gristUpdateRecordAction(selectedTableId, rowId, { [colId]: value })]);
|
|
112
125
|
};
|
|
126
|
+
const grantedAccessLevel = state.widgetInteraction.access_level ?? "read table";
|
|
127
|
+
const onSetAccessLevel = (level) => {
|
|
128
|
+
emulator.setInteraction({ ...state.widgetInteraction, access_level: level });
|
|
129
|
+
};
|
|
113
130
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className, style: baseStyle, children: [
|
|
114
131
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: headerStyle, children: [
|
|
115
132
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: badgeStyle, children: "EMULATOR" }),
|
|
@@ -197,6 +214,26 @@ function EmulatorInspector({ emulator, className, showTimeline = true }) {
|
|
|
197
214
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(KV, { k: "mappings", v: JSON.stringify(resolveMappings(state)) }),
|
|
198
215
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(KV, { k: "configuration", v: JSON.stringify(state.configuration) })
|
|
199
216
|
] }) }),
|
|
217
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Section, { title: "Access level", children: [
|
|
218
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { display: "flex", gap: 6, marginBottom: 6, flexWrap: "wrap" }, children: ACCESS_LEVELS.map((level) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
219
|
+
"button",
|
|
220
|
+
{
|
|
221
|
+
type: "button",
|
|
222
|
+
onClick: () => onSetAccessLevel(level),
|
|
223
|
+
style: level === grantedAccessLevel ? tabActiveStyle : tabStyle,
|
|
224
|
+
title: gristAccessLevelLabel(level),
|
|
225
|
+
children: level
|
|
226
|
+
},
|
|
227
|
+
level
|
|
228
|
+
)) }),
|
|
229
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { style: mutedStyle, children: [
|
|
230
|
+
"Granted to the widget via ",
|
|
231
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { children: "access_level" }),
|
|
232
|
+
" \u2014 ",
|
|
233
|
+
gristAccessLevelLabel(grantedAccessLevel),
|
|
234
|
+
"."
|
|
235
|
+
] })
|
|
236
|
+
] }),
|
|
200
237
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Section, { title: "Widget options", children: [
|
|
201
238
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("pre", { style: preStyle, children: JSON.stringify(state.widgetOptions, null, 2) }),
|
|
202
239
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(KV, { k: "interaction", v: JSON.stringify(state.widgetInteraction) })
|