@selvajs/solve 2.0.0-beta.9 → 2.0.0

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.
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","names":["finalizeResult","outcomeFromComputeMessages","decodeOutcome","makeKey","scopeKey","inputId","writeExternalValue","args","value","readExternalValue","ref","raw","clearExternalValue","getExternalInputs","getInputItems","schema","item","EMPTY_LIVE_SOLVE","reduceLiveSolve","base","event","state","next","payload","diagnostic","source","progress","report","readProgress","previous","num","v","fraction","done","total","nextFraction","stepMs","clamp","buildInitialValues","clientSet","values","input","stored","read","getDefaultValue","output","makeInitialFlags","manual","instanceSolve","applyValueChange","id","pickInputValues","picked","applySolveResult","result","commitSolveResult","needsAcknowledgement","blankOutputs","blank","_meshes","retained","solvePhase","isSolving","diagnostics","d","createSolveSession","currentSchema","flags","listeners","emit","l","settledListeners","waiters","ABORTED","nextSettlement","resolve","settle","waiting","settlement","listener","abandon","settleResult","live","stopEvents","solve","settled","dispatch","held","shouldSolve","forceSolve","incoming","f","message","createAsyncThrottle","options","isRunning","pendingValues","currentAbortController","setRunning","onChange","abortCurrent","execute","deadlineId","runDeadlineMs","run","signal","err","trigger","cancel","stableInputKey","serialize","obj","k","createSolveMemo","entries","release","meshPolicy","copy","evict","entry","key","hit","max","oldest","createRequestResponseDriver","memo","reportStopped","getReporter","solveDeadlineMs","throttle","cached","onSolve","stamped","events","mintStreamId","createSolveEventStream","streamId","doFetch","currentSolveId","open","url","solveId","parseServerTiming","out","header","part","name","params","s","dur","p","ms","defaultOnRateLimited","retryAfterSeconds","defaultOnSessionExpired","createComputeFetchSolveFn","debug","opts","log","cooldownUntil","remainingMs","solveStart","inputs","outputs","channel","versionId","res","redirectedToLogin","errorBody","retryAfter","ttfbMs","downloadStart","bodyText","downloadMs","bytes","solved","processor","GrasshopperResponseProcessor","outcome","rejected","meshes","resultOutputs","o","byId","valuesBytes","serverTiming","serverTotal","networkMs","sizeMB","size","mbps","heapMB","heap","cacheMsg"],"sources":["../src/client/external-storage.ts","../src/client/events/live-solve.ts","../src/client/session/solve-session-core.ts","../src/client/session/solve-session.ts","../src/client/async-throttle.ts","../src/client/solve-memo.ts","../src/client/drivers/request-response.ts","../src/client/events/solve-event-stream.ts","../src/client/compute-fetch-solve-fn.ts"],"sourcesContent":["// Transit storage for inputs with source.kind === 'client': a producer route writes the\n// value here, the solver route reads it back. Keyed by (scopeKey, inputId) so values\n// don't bleed across solver contexts — scopeKey is whatever uniquely identifies one\n// (sessionId in plugin-ui/preview, definition guid in selva/library).\n\nimport type { UISchema } from '@selvajs/schemas';\nimport { getInputItems } from '@selvajs/schemas';\n\nconst STORAGE_PREFIX = 'external';\n\nfunction makeKey(scopeKey: string, inputId: string): string {\n\treturn `${STORAGE_PREFIX}:${scopeKey}:${inputId}`;\n}\n\nexport interface ExternalValueRef {\n\tscopeKey: string;\n\tinputId: string;\n}\n\nexport function writeExternalValue(args: ExternalValueRef & { value: unknown }): void {\n\tconst { scopeKey, inputId, value } = args;\n\tif (!scopeKey || !inputId) return;\n\tif (typeof sessionStorage === 'undefined') return;\n\tsessionStorage.setItem(makeKey(scopeKey, inputId), JSON.stringify(value));\n}\n\nexport function readExternalValue(ref: ExternalValueRef): unknown | undefined {\n\tconst { scopeKey, inputId } = ref;\n\tif (!scopeKey || !inputId) return undefined;\n\tif (typeof sessionStorage === 'undefined') return undefined;\n\tconst raw = sessionStorage.getItem(makeKey(scopeKey, inputId));\n\tif (raw === null) return undefined;\n\ttry {\n\t\treturn JSON.parse(raw);\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nexport function clearExternalValue(ref: ExternalValueRef): void {\n\tconst { scopeKey, inputId } = ref;\n\tif (!scopeKey || !inputId) return;\n\tif (typeof sessionStorage === 'undefined') return;\n\tsessionStorage.removeItem(makeKey(scopeKey, inputId));\n}\n\nexport interface ExternalInput {\n\tparamId: string;\n\tdisplayName: string;\n}\n\nexport function getExternalInputs(schema: UISchema): ExternalInput[] {\n\treturn getInputItems(schema)\n\t\t.filter((item) => item.source?.kind === 'client')\n\t\t.map((item) => ({\n\t\t\tparamId: item.paramId,\n\t\t\tdisplayName: item.displayName ?? item.paramId\n\t\t}));\n}\n","// What the running (or most recent) solve has said over the live channel, folded into one state\n// a host can render. Pure: `reduceLiveSolve` returns a new state when an event changes something\n// and the same object when it does not, so a host can compare by reference.\n//\n// One case per known kind. An unknown kind is kept in `other`, untouched, so a newer plugin's\n// events reach a host that knows them without this package having to.\n\nimport type { ProgressPayload, SolveEvent } from '@selvajs/schemas';\nimport { decodeOutcome } from '../../shared/outcome.js';\nimport type { SolveDiagnostic } from '../../shared/solve-fn.js';\n\nexport interface LiveSolveState {\n\t/** The solve these events belong to; null before the first event. */\n\treadonly solveId: string | null;\n\t/** Between that solve's first event and its `solveEnded`. */\n\treadonly running: boolean;\n\t/** `diagnostic` events in arrival order. */\n\treadonly diagnostics: readonly SolveDiagnostic[];\n\t/** The newest `progress` per source. A report without a source is keyed by `''`. */\n\treadonly progress: ReadonlyMap<string, ProgressPayload>;\n\t/** `solveEnded`'s payload once it arrives: `ok`, `blocked`, `aborted` or `error`. */\n\treadonly ended: { readonly kind: string } | null;\n\t/** Events of kinds this package does not know, oldest first. */\n\treadonly other: readonly SolveEvent[];\n\t/** Highest `seq` applied for this solve. A late or repeated delivery at or below it is ignored. */\n\treadonly lastSeq: number;\n}\n\nexport const EMPTY_LIVE_SOLVE: LiveSolveState = Object.freeze({\n\tsolveId: null,\n\trunning: false,\n\tdiagnostics: [],\n\tprogress: new Map(),\n\tended: null,\n\tother: [],\n\tlastSeq: 0\n});\n\nexport function reduceLiveSolve(state: LiveSolveState, event: SolveEvent): LiveSolveState {\n\t// A new id starts a new solve: nothing the previous one said carries over.\n\tconst base: LiveSolveState =\n\t\tevent.solveId === state.solveId\n\t\t\t? state\n\t\t\t: { ...EMPTY_LIVE_SOLVE, solveId: event.solveId, running: true };\n\tif (event.seq <= base.lastSeq) return state;\n\tconst next = { ...base, lastSeq: event.seq };\n\tconst payload = (event.payload ?? {}) as Record<string, unknown>;\n\n\tswitch (event.type) {\n\t\tcase 'solveStarted':\n\t\t\treturn { ...next, running: true };\n\n\t\tcase 'solveEnded':\n\t\t\treturn {\n\t\t\t\t...next,\n\t\t\t\trunning: false,\n\t\t\t\tended: { kind: typeof payload.kind === 'string' ? payload.kind : 'ok' }\n\t\t\t};\n\n\t\tcase 'diagnostic': {\n\t\t\t// Same normalization as a finished solve's messages, so a live row and its final\n\t\t\t// counterpart are the same object shape.\n\t\t\tconst [diagnostic] = decodeOutcome({ diagnostics: [payload] })?.diagnostics ?? [];\n\t\t\treturn diagnostic ? { ...next, diagnostics: [...next.diagnostics, diagnostic] } : next;\n\t\t}\n\n\t\tcase 'progress': {\n\t\t\tconst source = typeof payload.source === 'string' ? payload.source : '';\n\t\t\tconst progress = new Map(next.progress);\n\t\t\tconst report = readProgress(payload);\n\t\t\t// A bar that jumps back reads as broken. Within one solve a source only moves forward.\n\t\t\tconst previous = next.progress.get(source)?.fraction;\n\t\t\tif (previous !== undefined && report.fraction !== undefined && report.fraction < previous) {\n\t\t\t\treport.fraction = previous;\n\t\t\t}\n\t\t\tprogress.set(source, report);\n\t\t\treturn { ...next, progress };\n\t\t}\n\n\t\tdefault:\n\t\t\treturn { ...next, other: [...next.other, event] };\n\t}\n}\n\nfunction readProgress(payload: Record<string, unknown>): ProgressPayload {\n\tconst num = (v: unknown) => (typeof v === 'number' && Number.isFinite(v) ? v : undefined);\n\tconst fraction = num(payload.fraction);\n\tconst done = num(payload.done);\n\tconst total = num(payload.total);\n\tconst nextFraction = num(payload.nextFraction);\n\tconst stepMs = num(payload.stepMs);\n\tconst clamp = (v: number) => Math.min(1, Math.max(0, v));\n\treturn {\n\t\t...(typeof payload.source === 'string' ? { source: payload.source } : {}),\n\t\t...(fraction !== undefined ? { fraction: clamp(fraction) } : {}),\n\t\t...(done !== undefined ? { done } : {}),\n\t\t...(total !== undefined ? { total } : {}),\n\t\t...(typeof payload.label === 'string' ? { label: payload.label } : {}),\n\t\t...(nextFraction !== undefined ? { nextFraction: clamp(nextFraction) } : {}),\n\t\t...(stepMs !== undefined && stepMs > 0 ? { stepMs } : {})\n\t};\n}\n","// Pure transition logic for a Solve Session.\n//\n// This module holds the value/lifecycle state machine with no transport and no state\n// ownership: how values are seeded (including client-sourced hydration), when a value\n// change should dispatch a solve vs. defer it, and what a reported solve result does to\n// the flags. `solve-session.ts` is a thin shell that owns the state object and the\n// subscriber set; every decision lives here, which is what makes it directly testable.\n\nimport type { UISchema } from '@selvajs/schemas';\nimport { getDefaultValue } from '@selvajs/schemas';\nimport { getExternalInputs, type ExternalValueRef } from '../external-storage.js';\nimport type { SolveDiagnostic, SolveResult } from '../../shared/solve-fn.js';\n\n/**\n * The slice of a reported result the session keeps addressable, so a host can commit exactly\n * what the viewer is showing — `source` and `values` included, already memo-correct.\n *\n * Mesh-free by construction. `SolveResult.meshes` are GPU-backed and the viewer disposes what\n * it renders on the next scene update, which is why the memo carries a `MeshPolicy`\n * clone/release contract at all. Retaining them here would hand out disposed instances after\n * the next solve, with no policy governing them and no owner. Everything left is inert JSON;\n * live meshes stay on `session.meshes`.\n */\nexport type RetainedSolveResult<TSource = unknown> = Omit<SolveResult<never, TSource>, 'meshes'>;\n\nexport interface SolveSessionState {\n\tvalues: Record<string, unknown>;\n\terror: string;\n\tcomputeErrors: string[];\n\tcomputeWarnings: string[];\n\t/**\n\t * The same messages with their level, source and `isGate`. What decides whether a solve\n\t * interrupts the user; the two string arrays above stay the plain display lists.\n\t */\n\tdiagnostics: SolveDiagnostic[];\n\t/**\n\t * The last solve was refused by a guard in the definition. Separate from `computeErrors`\n\t * being non-empty: a solve can report errors and still return usable outputs, and the UI\n\t * needs to tell \"solved, with complaints\" from \"would not solve\".\n\t */\n\tblocked: boolean;\n\t/**\n\t * The last solve raised messages the user has not acknowledged yet. While true the result\n\t * is withheld: `meshes` and the solve's outputs stay at their previous values until\n\t * `acknowledge()` releases them.\n\t */\n\tawaitingAck: boolean;\n\t/**\n\t * The withheld result, kept whole so `acknowledge()` can apply it without a re-solve.\n\t * Null whenever `awaitingAck` is false. A newer solve replaces it: the user is always\n\t * confirming the most recent result, never a stale one.\n\t */\n\theldResult: SolveResult | null;\n\tmeshes: unknown[];\n\t/** Values changed since the last solve, in manual (instanceSolve === false) mode. */\n\tpendingValues: Record<string, unknown>;\n\thasPendingChanges: boolean;\n\thasNeverSolved: boolean;\n\t/**\n\t * The last result reported, minus its meshes. Null before the first solve.\n\t *\n\t * Lags `values` by design: nothing updates it on `setValue` or `loadValues`, so between a\n\t * value change and the next report it describes the *previous* input set. That is what a\n\t * commit gate wants — what is on screen, not what is pending — so `lastResult.values`\n\t * disagreeing with `state.values` mid-scrub is the feature, not a bug.\n\t */\n\tlastResult: RetainedSolveResult | null;\n}\n\nexport type ExternalReader = (ref: ExternalValueRef) => unknown | undefined;\n\n/**\n * Client-sourced inputs are hydrated from `read` and left absent when no stored value\n * exists, so the missing-inputs panel can detect them. Other inputs fall back to their\n * declared default, then the paramType default.\n */\nexport function buildInitialValues(\n\tschema: UISchema,\n\tscopeKey: string,\n\tread: ExternalReader\n): Record<string, unknown> {\n\tconst clientSet = new Set(getExternalInputs(schema).map((e) => e.paramId));\n\tconst values: Record<string, unknown> = {};\n\tfor (const input of schema.inputs) {\n\t\tif (clientSet.has(input.id)) {\n\t\t\tconst stored = read({ scopeKey, inputId: input.id });\n\t\t\tif (stored !== undefined) values[input.id] = stored;\n\t\t\tcontinue;\n\t\t}\n\t\tvalues[input.id] = input.default ?? getDefaultValue(input.paramType);\n\t}\n\tfor (const output of schema.outputs) {\n\t\tvalues[output.id] = null;\n\t}\n\treturn values;\n}\n\n/** Manual-solve schemas (instanceSolve === false) start dirty; auto-solve schemas start clean. */\nexport function makeInitialFlags(instanceSolve: boolean | undefined): {\n\thasPendingChanges: boolean;\n\thasNeverSolved: boolean;\n} {\n\tconst manual = instanceSolve === false;\n\treturn { hasPendingChanges: manual, hasNeverSolved: manual };\n}\n\n/** Manual mode defers (records the pending value, raises the dirty flag) instead of solving. */\nexport function applyValueChange(\n\tstate: SolveSessionState,\n\tid: string,\n\tvalue: unknown,\n\tinstanceSolve: boolean | undefined\n): { state: SolveSessionState; shouldSolve: boolean } {\n\tstate.values[id] = value;\n\tif (instanceSolve === false) {\n\t\tstate.pendingValues[id] = value;\n\t\tstate.hasPendingChanges = true;\n\t\treturn { state, shouldSolve: false };\n\t}\n\treturn { state, shouldSolve: true };\n}\n\n/**\n * Solve outputs live in the same `values` map as inputs (so widgets like dynamic value\n * lists can read them) but must not be echoed back to the driver: a measured 6.4 MB\n * options-list output showed this can re-upload a multi-MB payload no backend reads.\n */\nexport function pickInputValues(\n\tschema: UISchema | undefined,\n\tvalues: Record<string, unknown>\n): Record<string, unknown> {\n\tif (!schema?.inputs) return values;\n\tconst picked: Record<string, unknown> = {};\n\tfor (const input of schema.inputs) {\n\t\tif (input.id in values) picked[input.id] = values[input.id];\n\t}\n\treturn picked;\n}\n\nexport function applySolveResult(state: SolveSessionState, result: SolveResult): SolveSessionState {\n\tstate.error = '';\n\tstate.computeErrors = result.errors ?? [];\n\tstate.computeWarnings = result.warnings ?? [];\n\tstate.diagnostics = result.diagnostics ?? [];\n\tstate.blocked = result.blocked ?? false;\n\n\t// A blocked solve is applied at once: there is no result to weigh, and the previous one must\n\t// not stay on screen looking like the answer to the current inputs. The caller blanks the\n\t// outputs (see `blankOutputs`); the empty mesh list clears the viewer.\n\tif (state.blocked) {\n\t\tstate.awaitingAck = false;\n\t\tstate.heldResult = null;\n\t\treturn commitSolveResult(state, result);\n\t}\n\n\t// The solve is finished either way — Grasshopper cannot be paused mid-solution — so what is\n\t// held back here is the *result*, not the computation. Everything the solve produced is kept\n\t// intact in `heldResult` and released verbatim by `acknowledge()`.\n\tif (needsAcknowledgement(state.diagnostics)) {\n\t\tstate.awaitingAck = true;\n\t\tstate.heldResult = result;\n\t\t// The input set stays dirty-free regardless: the values were sent and solved, and\n\t\t// leaving them pending would re-solve them on the next tick.\n\t\tstate.pendingValues = {};\n\t\tstate.hasPendingChanges = false;\n\t\treturn state;\n\t}\n\n\tstate.awaitingAck = false;\n\tstate.heldResult = null;\n\treturn commitSolveResult(state, result);\n}\n\n/**\n * Every schema output set to null. A blocked solve reports `outputs: {}`, and merging that into\n * `values` would leave the previous solve's outputs behind.\n */\nexport function blankOutputs(schema: UISchema | undefined): Record<string, null> {\n\tconst blank: Record<string, null> = {};\n\tfor (const output of schema?.outputs ?? []) blank[output.id] = null;\n\treturn blank;\n}\n\n/** Writes a result into the state. Shared by the clean path and by `acknowledge()`. */\nexport function commitSolveResult(\n\tstate: SolveSessionState,\n\tresult: SolveResult\n): SolveSessionState {\n\tstate.meshes = result.meshes ?? [];\n\tconst { meshes: _meshes, ...retained } = result;\n\tstate.lastResult = retained;\n\tObject.assign(state.values, result.outputs);\n\tstate.pendingValues = {};\n\tstate.hasPendingChanges = false;\n\tstate.hasNeverSolved = false;\n\treturn state;\n}\n\nexport type SolvePhase = 'idle' | 'solving' | 'review' | 'blocked';\n\n/**\n * One value for what a host should show. Solving wins: a new solve supersedes a held or refused\n * result. `isSolving` is passed in because the driver owns it.\n */\nexport function solvePhase(state: SolveSessionState, isSolving: boolean): SolvePhase {\n\tif (isSolving) return 'solving';\n\tif (state.awaitingAck) return 'review';\n\tif (state.blocked) return 'blocked';\n\treturn 'idle';\n}\n\n/**\n * Whether this solve's messages still need the user's acknowledgement.\n *\n * Only a Selva Message component gates. Grasshopper raises warnings constantly for reasons that\n * concern whoever is editing the definition, not whoever is using the app — a component\n * complaining about its own inputs is not something an end user can act on, and interrupting\n * them for it trains them to dismiss the one message that mattered.\n *\n * Every solve that gates asks again, by design — an acknowledgement covers the solve in front of\n * the user, never a future one. Under `instanceSolve` with a live warning that means a dialog\n * per solve, which is the intended checkpoint, not an oversight.\n *\n * Without `diagnostics` (a transport that reports no structure) nothing gates: the messages are\n * still shown, just without interrupting.\n */\nexport function needsAcknowledgement(diagnostics: SolveDiagnostic[] | undefined): boolean {\n\treturn (diagnostics ?? []).some((d) => d.isGate && d.level !== 'remark');\n}\n","// A framework-free shell over the pure transition logic in solve-session-core.ts: owns the\n// live values/flags, delegates every transition to the core, and drives solves through a\n// transport-agnostic SolveDriver.\n//\n// State is exposed as plain getters; it carries no framework reactivity of its own, so a\n// Svelte host must wrap it — see `useSolveSession.svelte.ts` in `@selvajs/ui`, which\n// republishes these getters as $state. Reading a getter without subscribing gives a\n// correct value but will not re-render.\n\nimport type { UISchema } from '@selvajs/schemas';\nimport { readExternalValue } from '../external-storage.js';\nimport type { SolveDiagnostic, SolveResult } from '../../shared/solve-fn.js';\nimport type { SolveDriver } from '../drivers/driver.js';\nimport { EMPTY_LIVE_SOLVE, reduceLiveSolve, type LiveSolveState } from '../events/live-solve.js';\nimport {\n\tbuildInitialValues,\n\tmakeInitialFlags,\n\tapplyValueChange,\n\tapplySolveResult,\n\tblankOutputs,\n\tcommitSolveResult,\n\tpickInputValues,\n\tsolvePhase,\n\ttype SolvePhase,\n\ttype SolveSessionState,\n\ttype RetainedSolveResult\n} from './solve-session-core.js';\n\n/**\n * How a solve ended, once nothing about it can change any more: a held result counts only\n * after the user accepts or rejects it, and a result a queued solve is about to replace never\n * counts. One per solve that lands, whether a host asked for it or an input change did.\n */\nexport type SolveSettlement =\n\t| { status: 'applied'; result: RetainedSolveResult; diagnostics: SolveDiagnostic[] }\n\t/** The user rejected a held result; the screen still shows the one before it. */\n\t| { status: 'discarded'; diagnostics: SolveDiagnostic[] }\n\t/** The definition refused the solve. */\n\t| { status: 'blocked'; diagnostics: SolveDiagnostic[] }\n\t/** Cancelled, or stopped in Grasshopper, before it produced a result. */\n\t| { status: 'aborted'; diagnostics: SolveDiagnostic[] }\n\t/** The transport failed or the solve ran past its deadline; `error` is what the user sees. */\n\t| { status: 'failed'; error: string };\n\nexport interface SolveSession {\n\treadonly values: Record<string, unknown>;\n\treadonly error: string;\n\treadonly computeErrors: string[];\n\treadonly computeWarnings: string[];\n\t/**\n\t * The last solve's messages with their level, source and `isGate`. Richer than the two\n\t * string arrays above and the list a message UI should render; they remain for hosts that\n\t * only want the text. Empty from a transport that reports no structure.\n\t */\n\treadonly diagnostics: SolveDiagnostic[];\n\t/**\n\t * The definition refused the last solve, or it was aborted. Applied at once, not held: the\n\t * outputs are blanked and the viewer cleared, and `computeErrors` holds the reason.\n\t */\n\treadonly blocked: boolean;\n\t/**\n\t * The last solve raised messages awaiting the user's confirmation, and its result is held\n\t * back until then. A host shows the messages and calls `acknowledge()` to release them.\n\t * Never true for a blocked solve.\n\t */\n\treadonly awaitingAck: boolean;\n\t/**\n\t * Releases a held result into the session. No-op when nothing is held. The solve already\n\t * ran — this applies what it produced, it does not re-solve.\n\t */\n\tacknowledge(): void;\n\t/**\n\t * Drops a held result instead of applying it. The viewer keeps what it was showing. No-op\n\t * when nothing is held, and never re-solves — like `acknowledge()`, it only decides the\n\t * fate of a result that already exists.\n\t */\n\tdiscard(): void;\n\t/**\n\t * What the running (or most recent) solve has said so far: its diagnostics, progress per\n\t * source, and how it ended. Reset when a solve with a new id starts, so a host renders \"what\n\t * this solve has said\", never a tail of the previous one. Stays empty when the driver has no\n\t * live channel.\n\t */\n\treadonly live: LiveSolveState;\n\t/**\n\t * Where the session is, as one value instead of four flags: `solving` while a solve runs,\n\t * `review` while a result waits for `acknowledge()`, `blocked` after a refused or aborted\n\t * solve, `idle` otherwise.\n\t */\n\treadonly phase: SolvePhase;\n\t/**\n\t * Asks the transport to stop the solve in flight. Cooperative on both paths — Grasshopper\n\t * stops at the next component boundary — so the result is a `solveEnded` (or nothing, if it\n\t * finished first), never an instant halt.\n\t */\n\tabort(): void;\n\treadonly meshes: unknown[];\n\t/**\n\t * The last reported result without its meshes — what the viewer is currently showing,\n\t * including the `source`/`values` pair a commit path needs. Null before the first solve,\n\t * and again after `rebuild()`. Survives `reportError`: a failed solve leaves the last good\n\t * geometry on screen, so a host committing what it sees should still see what produced it.\n\t */\n\treadonly lastResult: RetainedSolveResult | null;\n\treadonly hasPendingChanges: boolean;\n\treadonly hasNeverSolved: boolean;\n\treadonly isSolving: boolean;\n\t/** `forceSolve` overrides manual mode for system-initiated reconciliation (e.g. a dynamic value list pruning a vanished selection), so the output can't lag behind the new value. */\n\tsetValue(id: string, value: unknown, forceSolve?: boolean): void;\n\t/**\n\t * Solves the current inputs. Resolves with the settlement of the solve that lands, which is\n\t * a later one if the inputs change meanwhile: what is on screen when it resolves. Never\n\t * rejects; a failure is a status. A rebuild or dispose first resolves it as `aborted`.\n\t */\n\tsolve(): Promise<SolveSettlement>;\n\t/**\n\t * The result for the current inputs, solving only when it has to: right away from the result\n\t * on screen when nothing changed, after the running or held solve when there is one, else\n\t * after a new solve. For an action that needs a fresh result, such as an export.\n\t */\n\tensureSolved(): Promise<SolveSettlement>;\n\t/** Fires once per settled solve, the ones input changes start included. Returns the unsubscribe. */\n\tonSettled(listener: (settlement: SolveSettlement) => void): () => void;\n\t/** Merges incoming values, then solves (auto) or marks dirty (manual). */\n\tloadValues(incoming: Record<string, unknown>): void;\n\trebuild(schema: UISchema, scopeKey: string): void;\n\treport(result: SolveResult): void;\n\treportError(message: string): void;\n\treportAborted(): void;\n\t/** Fires after any mutation of the getters above, including driver-reported `isSolving` transitions. */\n\tsubscribe(listener: () => void): () => void;\n\t/** For driver-owned state the session only forwards: `isSolving` lives on the driver, so wire this to its `onChange`. */\n\tnotify(): void;\n\t/**\n\t * Stops listening to the driver's live events and drops every subscriber. Call when the\n\t * session is discarded: the event source can outlive it (the cloud stream belongs to the tab),\n\t * and would otherwise keep feeding a session nobody reads.\n\t */\n\tdispose(): void;\n}\n\nexport interface SolveSessionArgs {\n\tschema: UISchema;\n\tscopeKey: string;\n\tdriver: SolveDriver;\n}\n\nexport function createSolveSession(args: SolveSessionArgs): SolveSession {\n\tlet currentSchema = args.schema;\n\n\tconst flags = makeInitialFlags(currentSchema?.instanceSolve);\n\tconst state: SolveSessionState = {\n\t\tvalues: buildInitialValues(currentSchema, args.scopeKey, readExternalValue),\n\t\terror: '',\n\t\tcomputeErrors: [],\n\t\tcomputeWarnings: [],\n\t\tdiagnostics: [],\n\t\tblocked: false,\n\t\tawaitingAck: false,\n\t\theldResult: null,\n\t\tmeshes: [],\n\t\tpendingValues: {},\n\t\thasPendingChanges: flags.hasPendingChanges,\n\t\thasNeverSolved: flags.hasNeverSolved,\n\t\tlastResult: null\n\t};\n\n\tconst listeners = new Set<() => void>();\n\tconst emit = () => listeners.forEach((l) => l());\n\n\tconst settledListeners = new Set<(settlement: SolveSettlement) => void>();\n\tlet waiters: ((settlement: SolveSettlement) => void)[] = [];\n\tconst ABORTED: SolveSettlement = { status: 'aborted', diagnostics: [] };\n\n\tfunction nextSettlement(): Promise<SolveSettlement> {\n\t\treturn new Promise((resolve) => waiters.push(resolve));\n\t}\n\n\tfunction settle(settlement: SolveSettlement) {\n\t\tconst waiting = waiters;\n\t\twaiters = [];\n\t\twaiting.forEach((resolve) => resolve(settlement));\n\t\tsettledListeners.forEach((listener) => listener(settlement));\n\t}\n\n\t/** Resolves waiting callers without telling listeners: no solve ended, the session moved on. */\n\tfunction abandon() {\n\t\tconst waiting = waiters;\n\t\twaiters = [];\n\t\twaiting.forEach((resolve) => resolve(ABORTED));\n\t}\n\n\tfunction settleResult(result: SolveResult) {\n\t\tconst diagnostics = state.diagnostics;\n\t\tif (result.aborted) return settle({ status: 'aborted', diagnostics });\n\t\tif (result.blocked) return settle({ status: 'blocked', diagnostics });\n\t\tsettle({ status: 'applied', result: state.lastResult!, diagnostics });\n\t}\n\n\tlet live: LiveSolveState = EMPTY_LIVE_SOLVE;\n\t// A driver with no live channel never calls this; nothing else in the session depends on it.\n\tconst stopEvents = args.driver.onEvent?.((event) => {\n\t\tconst next = reduceLiveSolve(live, event);\n\t\tif (next === live) return;\n\t\tlive = next;\n\t\temit();\n\t});\n\n\tfunction solve(): Promise<SolveSettlement> {\n\t\tconst settled = nextSettlement();\n\t\tdispatch();\n\t\treturn settled;\n\t}\n\n\tfunction dispatch() {\n\t\t// Copy: state.values is the live map, and a driver may hold the payload across an\n\t\t// async solve — a later setValue must not mutate what's already in flight.\n\t\targs.driver.solve({ ...pickInputValues(currentSchema, state.values) });\n\t\temit();\n\t}\n\n\treturn {\n\t\tget values() {\n\t\t\treturn state.values;\n\t\t},\n\t\tget error() {\n\t\t\treturn state.error;\n\t\t},\n\t\tget computeErrors() {\n\t\t\treturn state.computeErrors;\n\t\t},\n\t\tget computeWarnings() {\n\t\t\treturn state.computeWarnings;\n\t\t},\n\t\tget diagnostics() {\n\t\t\treturn state.diagnostics;\n\t\t},\n\t\tget blocked() {\n\t\t\treturn state.blocked;\n\t\t},\n\t\tget awaitingAck() {\n\t\t\treturn state.awaitingAck;\n\t\t},\n\n\t\tacknowledge() {\n\t\t\tif (!state.awaitingAck) return;\n\t\t\tconst held = state.heldResult;\n\t\t\tstate.awaitingAck = false;\n\t\t\tstate.heldResult = null;\n\t\t\tif (held) commitSolveResult(state, held);\n\t\t\temit();\n\t\t\tif (held) settleResult(held);\n\t\t},\n\n\t\tdiscard() {\n\t\t\tif (!state.awaitingAck) return;\n\t\t\tstate.awaitingAck = false;\n\t\t\tstate.heldResult = null;\n\t\t\t// `values`, `meshes` and `lastResult` are untouched: the viewer keeps showing the last\n\t\t\t// result the user accepted. The messages stay on `computeErrors`/`computeWarnings` so\n\t\t\t// the footer still explains why nothing changed.\n\t\t\temit();\n\t\t\tsettle({ status: 'discarded', diagnostics: state.diagnostics });\n\t\t},\n\t\tget live() {\n\t\t\treturn live;\n\t\t},\n\t\tget phase() {\n\t\t\treturn solvePhase(state, args.driver.isSolving);\n\t\t},\n\t\tabort() {\n\t\t\targs.driver.cancel();\n\t\t},\n\t\tget meshes() {\n\t\t\treturn state.meshes;\n\t\t},\n\t\tget lastResult() {\n\t\t\treturn state.lastResult;\n\t\t},\n\t\tget hasPendingChanges() {\n\t\t\treturn state.hasPendingChanges;\n\t\t},\n\t\tget hasNeverSolved() {\n\t\t\treturn state.hasNeverSolved;\n\t\t},\n\t\tget isSolving() {\n\t\t\treturn args.driver.isSolving;\n\t\t},\n\n\t\tsetValue(id, value, forceSolve = false) {\n\t\t\tconst { shouldSolve } = applyValueChange(state, id, value, currentSchema?.instanceSolve);\n\t\t\tif (shouldSolve || forceSolve) {\n\t\t\t\tif (forceSolve && !shouldSolve) {\n\t\t\t\t\tstate.pendingValues = {};\n\t\t\t\t\tstate.hasPendingChanges = false;\n\t\t\t\t}\n\t\t\t\tdispatch();\n\t\t\t} else {\n\t\t\t\temit();\n\t\t\t}\n\t\t},\n\n\t\tsolve,\n\n\t\tensureSolved() {\n\t\t\tif (args.driver.isSolving || args.driver.hasQueued || state.awaitingAck) {\n\t\t\t\treturn nextSettlement();\n\t\t\t}\n\t\t\tif (state.hasPendingChanges || state.hasNeverSolved || state.error || !state.lastResult) {\n\t\t\t\treturn solve();\n\t\t\t}\n\t\t\tif (state.blocked) {\n\t\t\t\treturn Promise.resolve({ status: 'blocked', diagnostics: state.diagnostics });\n\t\t\t}\n\t\t\treturn Promise.resolve({\n\t\t\t\tstatus: 'applied',\n\t\t\t\tresult: state.lastResult,\n\t\t\t\tdiagnostics: state.diagnostics\n\t\t\t});\n\t\t},\n\n\t\tonSettled(listener) {\n\t\t\tsettledListeners.add(listener);\n\t\t\treturn () => settledListeners.delete(listener);\n\t\t},\n\n\t\tloadValues(incoming) {\n\t\t\tObject.assign(state.values, incoming);\n\t\t\tif (currentSchema?.instanceSolve !== false) {\n\t\t\t\tdispatch();\n\t\t\t} else {\n\t\t\t\tstate.hasPendingChanges = true;\n\t\t\t\temit();\n\t\t\t}\n\t\t},\n\n\t\trebuild(schema, scopeKey) {\n\t\t\t// A caller waiting on the old definition's solve would otherwise get the new one's.\n\t\t\tabandon();\n\t\t\tcurrentSchema = schema;\n\t\t\t// The new definition has its own input space; a matching input key from the prior\n\t\t\t// one must not serve that definition's cached result.\n\t\t\targs.driver.clearCache?.();\n\t\t\tstate.meshes = [];\n\t\t\t// Same reason as clearCache: a retained result belongs to the previous definition.\n\t\t\tstate.lastResult = null;\n\t\t\tstate.error = '';\n\t\t\tstate.computeErrors = [];\n\t\t\tstate.computeWarnings = [];\n\t\t\tstate.diagnostics = [];\n\t\t\tstate.blocked = false;\n\t\t\tstate.awaitingAck = false;\n\t\t\t// Belongs to the previous definition, same as lastResult.\n\t\t\tstate.heldResult = null;\n\t\t\tstate.pendingValues = {};\n\t\t\tstate.values = buildInitialValues(schema, scopeKey, readExternalValue);\n\t\t\tconst f = makeInitialFlags(schema?.instanceSolve);\n\t\t\tstate.hasPendingChanges = f.hasPendingChanges;\n\t\t\tstate.hasNeverSolved = f.hasNeverSolved;\n\t\t\tif (schema && Object.keys(state.values).length > 0 && schema.instanceSolve !== false) {\n\t\t\t\tdispatch();\n\t\t\t} else {\n\t\t\t\temit();\n\t\t\t}\n\t\t},\n\n\t\treport(result) {\n\t\t\t// `finalizeResult` already dropped a rejected solve's outputs; this also nulls the\n\t\t\t// ones still in `values` from the previous solve, which only the session can name.\n\t\t\tapplySolveResult(\n\t\t\t\tstate,\n\t\t\t\tresult.blocked || result.aborted\n\t\t\t\t\t? { ...result, outputs: blankOutputs(currentSchema), meshes: [] }\n\t\t\t\t\t: result\n\t\t\t);\n\t\t\temit();\n\t\t\t// A held result settles on acknowledge() or discard(); one a queued solve replaces, never.\n\t\t\tif (state.awaitingAck || args.driver.hasQueued) return;\n\t\t\tsettleResult(result);\n\t\t},\n\n\t\treportError(message) {\n\t\t\tstate.error = message;\n\t\t\temit();\n\t\t\tif (!args.driver.hasQueued) settle({ status: 'failed', error: message });\n\t\t},\n\n\t\treportAborted() {\n\t\t\temit();\n\t\t\tif (!args.driver.hasQueued) settle(ABORTED);\n\t\t},\n\n\t\tsubscribe(listener) {\n\t\t\tlisteners.add(listener);\n\t\t\treturn () => listeners.delete(listener);\n\t\t},\n\n\t\tnotify: emit,\n\n\t\tdispose() {\n\t\t\tstopEvents?.();\n\t\t\tlisteners.clear();\n\t\t\tsettledListeners.clear();\n\t\t\tabandon();\n\t\t}\n\t};\n}\n","/**\n * Single-in-flight, latest-wins throttle for any async operation: only one run at a\n * time, a newly triggered value overwrites the single pending slot (no queue), and a\n * stale run can be aborted via `AbortController` or timeout.\n *\n * Framework-free: `isRunning` is a plain getter, not a store. A reactive host must\n * subscribe via `onChange` (see `@selvajs/ui`'s rune adapter) — polling the getter\n * directly reads correct values but won't trigger a re-render.\n */\ninterface AsyncThrottleOptions {\n\t/**\n\t * How long one run may take before it is aborted (ms). Required and deliberately\n\t * undefaulted: this is a generic throttle, so it has no basis for guessing what\n\t * the caller's work costs. Callers driving a Selva solve pass the deadline the\n\t * server enforces (`COMPUTE_SOLVE_DEADLINE_MS`) — a shorter local guess would abort\n\t * solves the server would have finished.\n\t */\n\trunDeadlineMs: number;\n\tonChange?: () => void;\n}\n\nexport interface AsyncThrottle<T> {\n\ttrigger: (values: T) => void;\n\treadonly isRunning: boolean;\n\treadonly hasPending: boolean;\n\tcancel: () => void;\n}\n\nexport function createAsyncThrottle<T>(\n\trun: (values: T, signal: AbortSignal) => Promise<void>,\n\toptions: AsyncThrottleOptions\n): AsyncThrottle<T> {\n\tconst { runDeadlineMs, onChange } = options;\n\n\tlet isRunning = false;\n\tlet pendingValues: T | null = null;\n\tlet currentAbortController: AbortController | null = null;\n\n\tfunction setRunning(next: boolean) {\n\t\tif (isRunning === next) return;\n\t\tisRunning = next;\n\t\tonChange?.();\n\t}\n\n\tfunction abortCurrent() {\n\t\tcurrentAbortController?.abort();\n\t\tcurrentAbortController = null;\n\t}\n\n\tasync function execute(values: T) {\n\t\tabortCurrent();\n\n\t\tcurrentAbortController = new AbortController();\n\t\tconst { signal } = currentAbortController;\n\t\tconst deadlineId = setTimeout(() => {\n\t\t\t// Cleared in `finally` on every other path, so firing means a genuine timeout.\n\t\t\tconsole.warn(`[Solve/throttle] run exceeded ${runDeadlineMs}ms — aborting`);\n\t\t\t// The reason tells `run` a timeout from a cancel: one is a failure, the other not.\n\t\t\tcurrentAbortController?.abort(new DOMException('Run deadline exceeded', 'TimeoutError'));\n\t\t}, runDeadlineMs);\n\n\t\tsetRunning(true);\n\t\ttry {\n\t\t\tawait run(values, signal);\n\t\t} catch (err) {\n\t\t\t// AbortError is expected (timeout or cancel). Non-abort errors must be\n\t\t\t// handled inside `run` — re-throwing here would produce an unhandled\n\t\t\t// rejection because `execute` is always called fire-and-forget.\n\t\t\tif (!(err instanceof Error) || (err.name !== 'AbortError' && err.name !== 'TimeoutError')) {\n\t\t\t\tconsole.error('[Solve/throttle] unhandled error in run:', err);\n\t\t\t}\n\t\t} finally {\n\t\t\tclearTimeout(deadlineId);\n\t\t\tsetRunning(false);\n\t\t\tcurrentAbortController = null;\n\n\t\t\tif (pendingValues !== null) {\n\t\t\t\tconst next = pendingValues;\n\t\t\t\tpendingValues = null;\n\t\t\t\texecute(next);\n\t\t\t}\n\t\t}\n\t}\n\n\tfunction trigger(values: T) {\n\t\tif (isRunning) {\n\t\t\tif (pendingValues !== null) {\n\t\t\t\t// Latest-wins: the previously-queued values are dropped, not run.\n\t\t\t\t// `debug`, not `info`: a slider scrub fires this per frame, and it is a trace of\n\t\t\t\t// normal operation. Promoting it would spam a host's console on every drag.\n\t\t\t\t// eslint-disable-next-line no-console -- see above\n\t\t\t\tconsole.debug('[Solve/throttle] superseded pending run (latest-wins)');\n\t\t\t}\n\t\t\tpendingValues = values;\n\t\t} else {\n\t\t\texecute(values);\n\t\t}\n\t}\n\n\tfunction cancel() {\n\t\tpendingValues = null;\n\t\tabortCurrent();\n\t}\n\n\treturn {\n\t\ttrigger,\n\t\tget isRunning() {\n\t\t\treturn isRunning;\n\t\t},\n\t\tget hasPending() {\n\t\t\treturn pendingValues !== null;\n\t\t},\n\t\tcancel\n\t};\n}\n","// LRU cache of solve results keyed on a stable serialization of the inputs, so re-solving\n// something already seen this session (e.g. dragging a slider back) returns instantly\n// instead of round-tripping.\n//\n// A viewer typically takes ownership of every mesh it renders, disposing the previous\n// content on the next update. So the memo can't hand out its own stored instances (they'd\n// get disposed under it) or drop entries silently (their GPU buffers would leak): it keeps\n// private copies via an injected clone/release policy, which also keeps `three` out of this\n// package — `TMesh` stays opaque here.\n\nimport type { SolveResult } from '../shared/solve-fn.js';\n\n/**\n * Deterministic string key for a set of input values. Sorts object keys at every level so\n * two logically-equal inputs built in different key order collide.\n */\nexport function stableInputKey(values: Record<string, unknown>): string {\n\treturn serialize(values);\n}\n\nfunction serialize(value: unknown): string {\n\tif (value === null || typeof value !== 'object') return JSON.stringify(value) ?? 'null';\n\tif (Array.isArray(value)) return `[${value.map(serialize).join(',')}]`;\n\tconst obj = value as Record<string, unknown>;\n\tconst keys = Object.keys(obj).sort();\n\treturn `{${keys.map((k) => `${JSON.stringify(k)}:${serialize(obj[k])}`).join(',')}}`;\n}\n\n/**\n * Omit when results are mesh-free or nothing takes ownership of the meshes — the memo then\n * stores and serves them by reference, which is safe only because nothing frees them behind\n * its back.\n */\nexport interface MeshPolicy<TMesh> {\n\t/** Called on the way in and on every hit, so the stored entry and a served copy are never the same instance. */\n\tclone(meshes: TMesh[]): TMesh[];\n\t/** Must not free anything shared with live content (e.g. materials another object still uses). */\n\trelease(meshes: TMesh[]): void;\n}\n\nexport interface SolveMemoOptions<TMesh> {\n\t/** Entry cap, not a byte cap. Kept small on purpose: this targets the slider-scrub loop, not a durable cache. */\n\tmax?: number;\n\tmeshPolicy?: MeshPolicy<TMesh>;\n}\n\nexport interface SolveMemo<TMesh = unknown> {\n\tget(values: Record<string, unknown>): SolveResult<TMesh> | undefined;\n\tset(values: Record<string, unknown>, result: SolveResult<TMesh>): void;\n\tclear(): void;\n}\n\nexport function createSolveMemo<TMesh = unknown>(\n\toptions: SolveMemoOptions<TMesh> = {}\n): SolveMemo<TMesh> {\n\tconst { max = 16, meshPolicy } = options;\n\tconst entries = new Map<string, SolveResult<TMesh>>();\n\n\tfunction release(result: SolveResult<TMesh>): void {\n\t\tif (result.meshes?.length) meshPolicy?.release(result.meshes);\n\t}\n\n\tfunction copy(result: SolveResult<TMesh>): SolveResult<TMesh> {\n\t\tif (!meshPolicy || !result.meshes?.length) return result;\n\t\treturn { ...result, meshes: meshPolicy.clone(result.meshes) };\n\t}\n\n\tfunction evict(key: string): void {\n\t\tconst entry = entries.get(key);\n\t\tif (entry === undefined) return;\n\t\tentries.delete(key);\n\t\trelease(entry);\n\t}\n\n\treturn {\n\t\tget(values) {\n\t\t\tconst key = stableInputKey(values);\n\t\t\tconst hit = entries.get(key);\n\t\t\tif (hit === undefined) return undefined;\n\t\t\tentries.delete(key);\n\t\t\tentries.set(key, hit);\n\t\t\tconsole.info(`[Solve/memo] HIT — served from client memo (${entries.size}/${max})`);\n\t\t\treturn copy(hit);\n\t\t},\n\t\tset(values, result) {\n\t\t\tconst key = stableInputKey(values);\n\t\t\tevict(key);\n\t\t\tentries.set(key, copy(result));\n\t\t\twhile (entries.size > max) {\n\t\t\t\tconst oldest = entries.keys().next().value;\n\t\t\t\tif (oldest === undefined) break;\n\t\t\t\tevict(oldest);\n\t\t\t\tconsole.info(`[Solve/memo] evicted LRU entry (cap ${max})`);\n\t\t\t}\n\t\t},\n\t\tclear() {\n\t\t\tif (entries.size > 0) {\n\t\t\t\tconsole.info(`[Solve/memo] cleared ${entries.size} entries (definition changed)`);\n\t\t\t}\n\t\t\tentries.forEach(release);\n\t\t\tentries.clear();\n\t\t}\n\t};\n}\n","import { createAsyncThrottle } from '../async-throttle.js';\nimport { createSolveMemo, type MeshPolicy } from '../solve-memo.js';\nimport type { SolveFn } from '../../shared/solve-fn.js';\nimport type { SolveDriver, SolveEventSource, SolveReporter } from './driver.js';\n\nexport interface RequestResponseDriverOptions<TMesh> {\n\t/** How long one solve may take before the client aborts it (ms). Pass the server's `COMPUTE_SOLVE_DEADLINE_MS` so the two agree. */\n\tsolveDeadlineMs: number;\n\tonChange?: () => void;\n\t/** Mesh ownership policy for the result memo — see `MeshPolicy` (solve-memo.ts). */\n\tmeshPolicy?: MeshPolicy<TMesh>;\n\t/** Live events for the solves this driver runs; without it the session never sees any. */\n\tevents?: SolveEventSource;\n}\n\n/**\n * `getReporter` is lazy (`() => session`) because the session and driver reference each\n * other — the host builds the driver first, then the session, then can hand this back.\n */\nexport function createRequestResponseDriver<TMesh = unknown>(\n\tonSolve: SolveFn<TMesh>,\n\tgetReporter: () => SolveReporter<TMesh>,\n\toptions: RequestResponseDriverOptions<TMesh>\n): SolveDriver {\n\tconst { meshPolicy, solveDeadlineMs, onChange, events } = options;\n\n\t// Checked inside the throttled run, not before triggering it, so a hit only serves\n\t// after the throttle's latest-wins ordering has picked these values to run.\n\tconst memo = createSolveMemo<TMesh>({ meshPolicy });\n\n\t// A stopped run still ends in a report: a timeout is a failure the user should read, a\n\t// cancel is not. Either way a host awaiting the solve hears back.\n\tfunction reportStopped(signal: AbortSignal) {\n\t\tconst reason = signal.reason as { name?: string } | undefined;\n\t\tif (reason?.name === 'TimeoutError') {\n\t\t\tgetReporter().reportError(\n\t\t\t\t`The solve took longer than ${Math.round(solveDeadlineMs / 1000)} s and was stopped.`\n\t\t\t);\n\t\t} else {\n\t\t\tgetReporter().reportAborted();\n\t\t}\n\t}\n\n\tconst throttle = createAsyncThrottle<Record<string, unknown>>(\n\t\tasync (values, signal) => {\n\t\t\tconst cached = memo.get(values);\n\t\t\tif (cached !== undefined) {\n\t\t\t\tif (signal.aborted) return reportStopped(signal);\n\t\t\t\tgetReporter().report(cached);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst result = await onSolve(values, signal);\n\t\t\t\tif (signal.aborted) {\n\t\t\t\t\t// eslint-disable-next-line no-console -- normal during a slider scrub; not a warning\n\t\t\t\t\tconsole.debug('[Solve/driver] solve completed after abort — result discarded');\n\t\t\t\t\treturn reportStopped(signal);\n\t\t\t\t}\n\t\t\t\t// Stamped here rather than in the SolveFn: a memo hit never calls it, so values\n\t\t\t\t// captured inside would pair the served result with whatever solved last.\n\t\t\t\tconst stamped = { ...result, values };\n\t\t\t\tmemo.set(values, stamped);\n\t\t\t\tgetReporter().report(stamped);\n\t\t\t} catch (err) {\n\t\t\t\tif (signal.aborted) {\n\t\t\t\t\t// eslint-disable-next-line no-console -- normal during a slider scrub; not a warning\n\t\t\t\t\tconsole.debug('[Solve/driver] solve aborted (cancelled or timed out)');\n\t\t\t\t\treturn reportStopped(signal);\n\t\t\t\t}\n\t\t\t\tconsole.warn('[Solve/driver] solve failed:', err);\n\t\t\t\tgetReporter().reportError(err instanceof Error ? err.message : String(err));\n\t\t\t}\n\t\t},\n\t\t// The throttle is generic, so the solve deadline is what bounds one of its runs.\n\t\t{ runDeadlineMs: solveDeadlineMs, onChange }\n\t);\n\n\treturn {\n\t\tsolve(values) {\n\t\t\tthrottle.trigger(values);\n\t\t},\n\t\tcancel() {\n\t\t\t// Dropping the fetch alone stops the Compute child: the server closes the solve and the\n\t\t\t// plugin's next callback gets a 410. The explicit cancel is for a server that keeps the\n\t\t\t// solve open, and loses the race harmlessly (404) when the fetch abort closes it first.\n\t\t\tthrottle.cancel();\n\t\t\tevents?.cancelCurrent?.();\n\t\t},\n\t\tget isSolving() {\n\t\t\treturn throttle.isRunning;\n\t\t},\n\t\tget hasQueued() {\n\t\t\treturn throttle.hasPending;\n\t\t},\n\t\tclearCache() {\n\t\t\tmemo.clear();\n\t\t},\n\t\t...(events ? { onEvent: (listener) => events.subscribe(listener) } : {})\n\t} satisfies SolveDriver;\n}\n","// The cloud half of the live channel: one SSE stream per tab, carrying every solve this tab\n// runs. The stream id is minted here rather than by the server so a solve request can name it\n// before the stream has even connected.\n//\n// Opened when created, not on first subscribe: the server binds a solve to the stream only if\n// the stream is already registered, and a lazily opened one was rarely connected by the time\n// the first solve ran.\n\nimport type { SolveEvent } from '@selvajs/schemas';\nimport type { SolveEventSource } from '../drivers/driver.js';\n\nexport interface SolveEventStreamOptions {\n\t/** The SSE endpoint, e.g. `/api/v1/solve-events`. `streamId` is appended as a query param. */\n\tendpoint: string;\n\t/** Builds the cancel URL for a solve, e.g. `(id) => \\`/api/v1/solve/${id}/cancel\\``. */\n\tcancelEndpoint: (solveId: string) => string;\n\tfetch?: typeof fetch;\n}\n\nexport interface SolveEventStream extends SolveEventSource {\n\t/** Send this with every solve request so the server can route that solve's events here. */\n\treadonly streamId: string;\n\t/** The solve the server last reported as started and not yet ended; null between solves. */\n\treadonly currentSolveId: string | null;\n\tclose(): void;\n}\n\nfunction mintStreamId(): string {\n\tif (typeof crypto !== 'undefined' && 'randomUUID' in crypto) {\n\t\treturn crypto.randomUUID().replace(/-/g, '');\n\t}\n\treturn Array.from({ length: 32 }, () => Math.floor(Math.random() * 16).toString(16)).join('');\n}\n\nexport function createSolveEventStream(options: SolveEventStreamOptions): SolveEventStream {\n\tconst streamId = mintStreamId();\n\tconst listeners = new Set<(event: SolveEvent) => void>();\n\tconst doFetch = options.fetch ?? ((...args: Parameters<typeof fetch>) => fetch(...args));\n\tlet source: EventSource | null = null;\n\tlet currentSolveId: string | null = null;\n\n\tfunction open() {\n\t\tif (source || typeof EventSource === 'undefined') return;\n\t\tconst url = `${options.endpoint}${options.endpoint.includes('?') ? '&' : '?'}streamId=${streamId}`;\n\t\tsource = new EventSource(url);\n\t\tsource.addEventListener('solve', (raw) => {\n\t\t\tlet event: SolveEvent;\n\t\t\ttry {\n\t\t\t\tevent = JSON.parse((raw as MessageEvent<string>).data);\n\t\t\t} catch {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (event.type === 'solveStarted') currentSolveId = event.solveId;\n\t\t\telse if (event.type === 'solveEnded' && currentSolveId === event.solveId)\n\t\t\t\tcurrentSolveId = null;\n\t\t\tfor (const listener of listeners) listener(event);\n\t\t});\n\t\t// EventSource reconnects on its own; nothing to do on error beyond not tearing down.\n\t}\n\n\topen();\n\n\treturn {\n\t\tstreamId,\n\t\tget currentSolveId() {\n\t\t\treturn currentSolveId;\n\t\t},\n\t\tsubscribe(listener) {\n\t\t\tlisteners.add(listener);\n\t\t\topen();\n\t\t\treturn () => {\n\t\t\t\tlisteners.delete(listener);\n\t\t\t};\n\t\t},\n\t\tcancelCurrent() {\n\t\t\tconst solveId = currentSolveId;\n\t\t\tif (!solveId) return;\n\t\t\t// Fire-and-forget: the answer arrives as `solveEnded` on the stream, or not at all if\n\t\t\t// the solve finished first, and either way there is nothing to await here.\n\t\t\tvoid doFetch(options.cancelEndpoint(solveId), { method: 'POST', keepalive: true }).catch(\n\t\t\t\t() => {}\n\t\t\t);\n\t\t},\n\t\tclose() {\n\t\t\tsource?.close();\n\t\t\tsource = null;\n\t\t\tlisteners.clear();\n\t\t\tcurrentSolveId = null;\n\t\t}\n\t};\n}\n","/**\n * `createComputeFetchSolveFn` — a ready-made `SolveFn` that POSTs to a\n * `/api/compute`-shaped HTTP endpoint (the wire contract `runSolvePipeline` +\n * `SolveEngine` produce server-side) and unpacks the result.\n *\n * Ports the resilience behavior an interactive solve endpoint needs regardless\n * of app — 429 cooldown, session-expiry/redirect detection, non-JSON-response\n * guarding, and abort handling at every await point — so a new consumer gets it\n * for free instead of re-deriving it by hand. Mesh decoding is NOT built in:\n * this package stays renderer-agnostic (see `shared/solve-fn.ts`), so a\n * viewer-enabled consumer supplies its own `meshes.extract` hook (typically\n * `getThreeObjectsFromComputeResponse` from `@selvajs/visualization/parse`).\n */\n\nimport {\n\tGrasshopperResponseProcessor,\n\ttype GrasshopperComputeResponse\n} from '@selvajs/compute/grasshopper';\nimport type { SolveFn, SolveResult } from '../shared/solve-fn.js';\nimport { decodeOutcome, finalizeResult, outcomeFromComputeMessages } from '../shared/outcome.js';\n\nexport interface ComputeFetchSolveFnOptions<TMesh = unknown> {\n\t/** The compute endpoint to POST to, e.g. `/api/compute`. */\n\tendpoint: string;\n\t/** Definition URL (or `local:<guid>`) to solve — read per-solve so it can track a route param. */\n\tdefinitionUrl: () => string;\n\t/** Schema input params, sent as the request body's `inputs`. */\n\tinputs: () => Array<{ id: string }>;\n\t/** Schema output params — drives byId/byName output extraction after a solve. */\n\toutputs: () => Array<{ id: string; nickname?: string }>;\n\tchannel?: () => 'live' | 'draft' | undefined;\n\tversionId?: () => string | null | undefined;\n\t/** The tab's live-event stream id (`createSolveEventStream().streamId`), so the server routes this solve's events to it. */\n\tstreamId?: () => string | null | undefined;\n\t/** Omit entirely for a non-viewer consumer — `meshes` on the result stays `[]`. */\n\tmeshes?: {\n\t\textract: (\n\t\t\tresponse: GrasshopperComputeResponse,\n\t\t\topts: { debug: boolean }\n\t\t) => TMesh[] | Promise<TMesh[]>;\n\t};\n\t/** Gates ALL console telemetry (timing, cache verdicts, whales, heap). Default `false`. */\n\tdebug?: boolean;\n\t/** Called on a 429. Default: throws a cooldown-aware Error; the cooldown itself is tracked either way. */\n\tonRateLimited?: (retryAfterSeconds: number) => void;\n\t/** Called on a 401 or a login-page redirect. Default: throws an actionable \"reload to sign in again\" Error. */\n\tonSessionExpired?: () => void;\n}\n\nfunction parseServerTiming(header: string | null): Record<string, number> {\n\tconst out: Record<string, number> = {};\n\tif (!header) return out;\n\tfor (const part of header.split(',')) {\n\t\tconst [name, ...params] = part.split(';').map((s) => s.trim());\n\t\tconst dur = params.find((p) => p.startsWith('dur='));\n\t\tif (!name || !dur) continue;\n\t\tconst ms = Number(dur.slice(4));\n\t\tif (Number.isFinite(ms)) out[name] = ms;\n\t}\n\treturn out;\n}\n\nconst defaultOnRateLimited = (retryAfterSeconds: number): never => {\n\tthrow new Error(`Rate limit reached. Try again in ${Math.ceil(retryAfterSeconds)}s.`);\n};\n\nconst defaultOnSessionExpired = (): never => {\n\tthrow new Error(\n\t\t'Your session has expired. Sign in again in a new tab, then re-run — your inputs are preserved.'\n\t);\n};\n\nexport function createComputeFetchSolveFn<TMesh = unknown>(\n\topts: ComputeFetchSolveFnOptions<TMesh>\n): SolveFn<TMesh, GrasshopperComputeResponse> {\n\tconst debug = opts.debug ?? false;\n\tconst log = (...args: unknown[]) => {\n\t\tif (debug) console.info(...args);\n\t};\n\n\tlet cooldownUntil = 0;\n\n\treturn async (values, signal): Promise<SolveResult<TMesh, GrasshopperComputeResponse>> => {\n\t\tconst remainingMs = cooldownUntil - Date.now();\n\t\tif (remainingMs > 0) {\n\t\t\tthrow new Error(`Rate limit reached. Try again in ${Math.ceil(remainingMs / 1000)}s.`);\n\t\t}\n\n\t\tconst solveStart = performance.now();\n\t\tconst inputs = opts.inputs();\n\t\tconst outputs = opts.outputs();\n\t\tconst channel = opts.channel?.();\n\t\tconst versionId = opts.versionId?.();\n\t\tconst streamId = opts.streamId?.();\n\n\t\tconst payload = JSON.stringify({\n\t\t\tinputs,\n\t\t\tvalues,\n\t\t\tdefinitionUrl: opts.definitionUrl(),\n\t\t\t...(versionId ? { versionId } : channel === 'draft' ? { channel: 'draft' } : {}),\n\t\t\t...(streamId ? { streamId } : {})\n\t\t});\n\n\t\tlet res: Response;\n\t\ttry {\n\t\t\tres = await fetch(opts.endpoint, {\n\t\t\t\tmethod: 'POST',\n\t\t\t\theaders: { 'Content-Type': 'application/json' },\n\t\t\t\tbody: payload,\n\t\t\t\tsignal\n\t\t\t});\n\t\t} catch (err) {\n\t\t\tif (signal.aborted) {\n\t\t\t\tlog('[Compute] solve aborted during fetch — empty result discarded');\n\t\t\t\treturn { outputs: {} };\n\t\t\t}\n\t\t\tthrow new Error(\n\t\t\t\t'Request blocked — your session may have expired. Reload the page to sign in again.',\n\t\t\t\t{ cause: err }\n\t\t\t);\n\t\t}\n\n\t\tif (signal.aborted) {\n\t\t\tlog('[Compute] solve aborted after headers — empty result discarded');\n\t\t\treturn { outputs: {} };\n\t\t}\n\n\t\tconst redirectedToLogin = res.redirected && new URL(res.url).pathname === '/login';\n\t\tif (res.status === 401 || redirectedToLogin) {\n\t\t\t(opts.onSessionExpired ?? defaultOnSessionExpired)();\n\t\t}\n\n\t\tif (!res.ok) {\n\t\t\tconst errorBody = await res.text().catch(() => '');\n\t\t\tlet d: { message?: string; retryAfter?: number } = {};\n\t\t\ttry {\n\t\t\t\td = JSON.parse(errorBody);\n\t\t\t} catch {\n\t\t\t\tif (errorBody) {\n\t\t\t\t\tconsole.warn(\n\t\t\t\t\t\t`[Compute] non-JSON error body (HTTP ${res.status}):`,\n\t\t\t\t\t\terrorBody.slice(0, 300)\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (res.status === 503 || res.status === 504) {\n\t\t\t\t// The server names the failure class (unreachable, bad API key,\n\t\t\t\t// rate-limited, timed out) — show it rather than a blanket \"offline\".\n\t\t\t\tthrow new Error(\n\t\t\t\t\td.message || 'Compute server is offline or unreachable. Please try again later.'\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (res.status === 429) {\n\t\t\t\tconst retryAfter = Number(res.headers.get('Retry-After')) || Number(d.retryAfter) || 5;\n\t\t\t\tcooldownUntil = Date.now() + retryAfter * 1000;\n\t\t\t\t(opts.onRateLimited ?? defaultOnRateLimited)(retryAfter);\n\t\t\t}\n\t\t\tthrow new Error(d.message || `Compute error (HTTP ${res.status})`);\n\t\t}\n\n\t\tconst ttfbMs = performance.now() - solveStart;\n\n\t\tconst downloadStart = performance.now();\n\t\tconst bodyText = await res.text();\n\t\tif (signal.aborted) {\n\t\t\tlog('[Compute] solve aborted mid-download — empty result discarded');\n\t\t\treturn { outputs: {} };\n\t\t}\n\t\tconst downloadMs = performance.now() - downloadStart;\n\t\tconst bytes = bodyText.length;\n\n\t\tlet solved: GrasshopperComputeResponse;\n\t\ttry {\n\t\t\tsolved = JSON.parse(bodyText);\n\t\t} catch (err) {\n\t\t\tthrow new Error(\n\t\t\t\t'Received an invalid response from the server. Reload the page and try again.',\n\t\t\t\t{\n\t\t\t\t\tcause: err\n\t\t\t\t}\n\t\t\t);\n\t\t}\n\t\tconst processor = new GrasshopperResponseProcessor(solved);\n\n\t\t// The plugin's own verdict when the fork returned one; otherwise recovered from the\n\t\t// markers in Compute's flattened strings (stock server, or a plugin that predates it).\n\t\tconst outcome =\n\t\t\tdecodeOutcome(solved.selva?.outcome) ??\n\t\t\toutcomeFromComputeMessages(solved.errors ?? [], solved.warnings ?? []);\n\t\t// Decided before parsing: an older fork still ships a rejected solve's outputs, and the\n\t\t// geometry alone can be megabytes the client would only throw away.\n\t\tconst rejected = outcome.blocked || outcome.aborted;\n\n\t\tconst meshes =\n\t\t\topts.meshes && !rejected ? await opts.meshes.extract(processor.response, { debug }) : [];\n\n\t\tconst resultOutputs: Record<string, unknown> = {};\n\t\tfor (const o of rejected ? [] : outputs) {\n\t\t\tconst byId = processor.getValue({ byId: o.id }, { parseValues: true });\n\t\t\tresultOutputs[o.id] =\n\t\t\t\tbyId ??\n\t\t\t\t(o.nickname\n\t\t\t\t\t? processor.getValue({ byName: o.nickname }, { parseValues: true })\n\t\t\t\t\t: undefined);\n\t\t}\n\n\t\tif (debug) {\n\t\t\tconst valuesBytes = JSON.stringify(values).length;\n\t\t\tconst serverTiming = parseServerTiming(res.headers.get('Server-Timing'));\n\t\t\tconst serverTotal = serverTiming.total ?? 0;\n\t\t\tconst networkMs = Math.max(0, ttfbMs - serverTotal);\n\t\t\tconst sizeMB = bytes / (1024 * 1024);\n\t\t\tconst size = sizeMB >= 1 ? `${sizeMB.toFixed(2)} MB` : `${(bytes / 1024).toFixed(0)} KB`;\n\t\t\tconst mbps = downloadMs > 0 ? (sizeMB / (downloadMs / 1000)).toFixed(1) : '∞';\n\t\t\tconst heapMB = (performance as Performance & { memory?: { usedJSHeapSize: number } }).memory\n\t\t\t\t?.usedJSHeapSize;\n\t\t\tconst heap = heapMB !== undefined ? ` | heap=${(heapMB / (1024 * 1024)).toFixed(0)} MB` : '';\n\t\t\tlog(\n\t\t\t\t`[Compute] req=${(payload.length / 1024).toFixed(0)}KB (values ${(valuesBytes / 1024).toFixed(0)}KB) | ` +\n\t\t\t\t\t`ttfb=${ttfbMs.toFixed(0)}ms (network≈${networkMs.toFixed(0)} + server ${serverTotal.toFixed(0)}) | ` +\n\t\t\t\t\t`download=${downloadMs.toFixed(0)}ms (${size} @ ${mbps} MB/s)${heap}`\n\t\t\t);\n\t\t\tif (serverTiming.selva_cache !== undefined) {\n\t\t\t\tconst cacheMsg = serverTiming.selva_cache\n\t\t\t\t\t? 'HIT — served without calling compute'\n\t\t\t\t\t: serverTiming.rhino_cache\n\t\t\t\t\t\t? \"HIT — Rhino.Compute's result cache, solved nothing\"\n\t\t\t\t\t\t: 'miss — solved on Rhino.Compute';\n\t\t\t\tlog(`[Compute] └─ cache: ${cacheMsg}`);\n\t\t\t}\n\t\t}\n\n\t\treturn finalizeResult({ outputs: resultOutputs, meshes, source: solved }, outcome);\n\t};\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"client.js","names":[],"sources":["../src/client/external-storage.ts","../src/client/events/live-solve.ts","../src/client/session/solve-session-core.ts","../src/client/session/solve-session.ts","../src/client/async-throttle.ts","../src/client/solve-memo.ts","../src/client/drivers/request-response.ts","../src/client/events/solve-event-stream.ts","../src/client/compute-fetch-solve-fn.ts"],"sourcesContent":["// Transit storage for inputs with source.kind === 'client': a producer route writes the\n// value here, the solver route reads it back. Keyed by (scopeKey, inputId) so values\n// don't bleed across solver contexts — scopeKey is whatever uniquely identifies one.\n\nimport type { UISchema } from '@selvajs/schemas';\nimport { getInputItems } from '@selvajs/schemas';\n\nconst STORAGE_PREFIX = 'external';\n\nexport interface ExternalValueRef {\n\tscopeKey: string;\n\tinputId: string;\n}\n\n/** Null without a usable store. Merely reading `sessionStorage` throws when the browser blocks it. */\nfunction openSlot(ref: ExternalValueRef): { storage: Storage; key: string } | null {\n\tif (!ref.scopeKey || !ref.inputId) return null;\n\ttry {\n\t\tif (typeof sessionStorage === 'undefined') return null;\n\t\treturn {\n\t\t\tstorage: sessionStorage,\n\t\t\tkey: `${STORAGE_PREFIX}:${ref.scopeKey}:${ref.inputId}`\n\t\t};\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nexport function writeExternalValue(args: ExternalValueRef & { value: unknown }): void {\n\tconst slot = openSlot(args);\n\tif (!slot) return;\n\ttry {\n\t\tslot.storage.setItem(slot.key, JSON.stringify(args.value));\n\t} catch (err) {\n\t\t// Over quota. The input then reads as missing, which the panel reports.\n\t\tconsole.warn('[Solve/storage] could not store an external value:', err);\n\t}\n}\n\nexport function readExternalValue(ref: ExternalValueRef): unknown | undefined {\n\tconst slot = openSlot(ref);\n\tif (!slot) return undefined;\n\tconst raw = slot.storage.getItem(slot.key);\n\tif (raw === null) return undefined;\n\ttry {\n\t\treturn JSON.parse(raw);\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nexport function clearExternalValue(ref: ExternalValueRef): void {\n\tconst slot = openSlot(ref);\n\tslot?.storage.removeItem(slot.key);\n}\n\nexport interface ExternalInput {\n\tparamId: string;\n\tdisplayName: string;\n}\n\nexport function getExternalInputs(schema: UISchema): ExternalInput[] {\n\treturn getInputItems(schema)\n\t\t.filter((item) => item.source?.kind === 'client')\n\t\t.map((item) => ({\n\t\t\tparamId: item.paramId,\n\t\t\tdisplayName: item.displayName ?? item.paramId\n\t\t}));\n}\n","// What the running (or most recent) solve has said over the live channel, folded into one state\n// a host can render. Pure: `reduceLiveSolve` returns a new state when an event changes something\n// and the same object when it does not, so a host can compare by reference.\n//\n// One case per known kind. An unknown kind is kept in `other`, untouched, so a newer plugin's\n// events reach a host that knows them without this package having to.\n\nimport type { ProgressPayload, SolveEndedKind, SolveEvent } from '@selvajs/schemas';\nimport { decodeDiagnostic } from '../../shared/outcome.js';\nimport type { SolveDiagnostic } from '../../shared/solve-fn.js';\n\nexport interface LiveSolveState {\n\t/** The solve these events belong to; null before the first event. */\n\treadonly solveId: string | null;\n\t/** Between that solve's first event and its `solveEnded`. */\n\treadonly running: boolean;\n\t/** `diagnostic` events in arrival order. */\n\treadonly diagnostics: readonly SolveDiagnostic[];\n\t/** The newest `progress` per source. A report without a source is keyed by `''`. */\n\treadonly progress: ReadonlyMap<string, ProgressPayload>;\n\t/** `solveEnded`'s payload once it arrives. */\n\treadonly ended: { readonly kind: SolveEndedKind } | null;\n\t/** Events of kinds this package does not know, oldest first. */\n\treadonly other: readonly SolveEvent[];\n\t/** Highest `seq` applied for this solve. A late or repeated delivery at or below it is ignored. */\n\treadonly lastSeq: number;\n}\n\nexport const EMPTY_LIVE_SOLVE: LiveSolveState = Object.freeze({\n\tsolveId: null,\n\trunning: false,\n\tdiagnostics: [],\n\tprogress: new Map(),\n\tended: null,\n\tother: [],\n\tlastSeq: 0\n});\n\nfunction readEndedKind(payload: Record<string, unknown>): SolveEndedKind {\n\tconst { kind } = payload;\n\t// `error`, not `ok`: a payload this package can't read must not look like a clean finish.\n\treturn kind === 'ok' || kind === 'blocked' || kind === 'aborted' ? kind : 'error';\n}\n\nexport function reduceLiveSolve(state: LiveSolveState, event: SolveEvent): LiveSolveState {\n\t// A late end for a solve already replaced says nothing about the one on screen.\n\tif (event.type === 'solveEnded' && state.solveId !== null && event.solveId !== state.solveId) {\n\t\treturn state;\n\t}\n\t// A new id starts a new solve: nothing the previous one said carries over.\n\tconst base: LiveSolveState =\n\t\tevent.solveId === state.solveId\n\t\t\t? state\n\t\t\t: { ...EMPTY_LIVE_SOLVE, solveId: event.solveId, running: true };\n\tif (event.seq <= base.lastSeq) return state;\n\tconst next = { ...base, lastSeq: event.seq };\n\tconst payload = (event.payload ?? {}) as Record<string, unknown>;\n\n\tswitch (event.type) {\n\t\tcase 'solveStarted':\n\t\t\treturn { ...next, running: true };\n\n\t\tcase 'solveEnded':\n\t\t\treturn { ...next, running: false, ended: { kind: readEndedKind(payload) } };\n\n\t\tcase 'diagnostic': {\n\t\t\t// Same normalization as a finished solve's messages, so a live row and its final\n\t\t\t// counterpart are the same object shape.\n\t\t\tconst diagnostic = decodeDiagnostic(payload);\n\t\t\treturn diagnostic ? { ...next, diagnostics: [...next.diagnostics, diagnostic] } : next;\n\t\t}\n\n\t\tcase 'progress': {\n\t\t\tconst source = typeof payload.source === 'string' ? payload.source : '';\n\t\t\tconst progress = new Map(next.progress);\n\t\t\tconst report = readProgress(payload);\n\t\t\t// A bar that jumps back reads as broken. Within one solve a source only moves forward.\n\t\t\tconst previous = next.progress.get(source)?.fraction;\n\t\t\tif (previous !== undefined && (report.fraction ?? 0) < previous) report.fraction = previous;\n\t\t\tprogress.set(source, report);\n\t\t\treturn { ...next, progress };\n\t\t}\n\n\t\tdefault:\n\t\t\treturn { ...next, other: [...next.other, event] };\n\t}\n}\n\nfunction readProgress(payload: Record<string, unknown>): ProgressPayload {\n\tconst num = (v: unknown) => (typeof v === 'number' && Number.isFinite(v) ? v : undefined);\n\tconst fraction = num(payload.fraction);\n\tconst done = num(payload.done);\n\tconst total = num(payload.total);\n\tconst nextFraction = num(payload.nextFraction);\n\tconst stepMs = num(payload.stepMs);\n\tconst clamp = (v: number) => Math.min(1, Math.max(0, v));\n\treturn {\n\t\t...(typeof payload.source === 'string' ? { source: payload.source } : {}),\n\t\t...(fraction !== undefined ? { fraction: clamp(fraction) } : {}),\n\t\t...(done !== undefined ? { done } : {}),\n\t\t...(total !== undefined ? { total } : {}),\n\t\t...(typeof payload.label === 'string' ? { label: payload.label } : {}),\n\t\t...(nextFraction !== undefined ? { nextFraction: clamp(nextFraction) } : {}),\n\t\t...(stepMs !== undefined && stepMs > 0 ? { stepMs } : {})\n\t};\n}\n","// Pure transition logic for a Solve Session.\n//\n// This module holds the value/lifecycle state machine with no transport and no state\n// ownership: how values are seeded (including client-sourced hydration), when a value\n// change should dispatch a solve vs. defer it, and what a reported solve result does to\n// the flags. `solve-session.ts` is a thin shell that owns the state object and the\n// subscriber set; every decision lives here, which is what makes it directly testable.\n\nimport type { UISchema } from '@selvajs/schemas';\nimport { getDefaultValue } from '@selvajs/schemas';\nimport { getExternalInputs, type ExternalValueRef } from '../external-storage.js';\nimport type { SolveDiagnostic, SolveResult } from '../../shared/solve-fn.js';\n\n/**\n * The slice of a reported result the session keeps addressable, so a host can commit exactly\n * what the viewer is showing — `source` and `values` included, already memo-correct.\n *\n * Mesh-free by construction. `SolveResult.meshes` are GPU-backed and the viewer disposes what\n * it renders on the next scene update, which is why the memo carries a `MeshPolicy`\n * clone/release contract at all. Retaining them here would hand out disposed instances after\n * the next solve, with no policy governing them and no owner. Everything left is inert JSON;\n * live meshes stay on `session.meshes`.\n */\nexport type RetainedSolveResult<TSource = unknown> = Omit<SolveResult<never, TSource>, 'meshes'>;\n\nexport interface SolveSessionState {\n\tvalues: Record<string, unknown>;\n\terror: string;\n\tcomputeErrors: string[];\n\tcomputeWarnings: string[];\n\t/**\n\t * The same messages with their level, source and `isGate`. What decides whether a solve\n\t * interrupts the user; the two string arrays above stay the plain display lists.\n\t */\n\tdiagnostics: SolveDiagnostic[];\n\t/**\n\t * The last solve was refused by a guard in the definition. Separate from `computeErrors`\n\t * being non-empty: a solve can report errors and still return usable outputs, and the UI\n\t * needs to tell \"solved, with complaints\" from \"would not solve\".\n\t */\n\tblocked: boolean;\n\t/**\n\t * The result withheld until the user answers for it, kept whole so `acknowledge()` can apply\n\t * it without a re-solve. While set, `meshes` and the solve's outputs stay at their previous\n\t * values. A newer solve drops it: the user only ever confirms the most recent result.\n\t */\n\theldResult: SolveResult | null;\n\tmeshes: unknown[];\n\thasPendingChanges: boolean;\n\thasNeverSolved: boolean;\n\t/**\n\t * The last result reported, minus its meshes. Null before the first solve.\n\t *\n\t * Lags `values` by design: nothing updates it on `setValue` or `loadValues`, so between a\n\t * value change and the next report it describes the *previous* input set. That is what a\n\t * commit gate wants — what is on screen, not what is pending — so `lastResult.values`\n\t * disagreeing with `state.values` mid-scrub is the feature, not a bug.\n\t */\n\tlastResult: RetainedSolveResult | null;\n\t/**\n\t * Why the result on screen does not answer the current inputs, when nothing else says so: the\n\t * last solve was aborted, or the user discarded it. Nothing marks those inputs dirty, so\n\t * without this `ensureSolved` would hand out the older result as theirs.\n\t */\n\tunsolved: 'aborted' | 'discarded' | null;\n}\n\nexport type ExternalReader = (ref: ExternalValueRef) => unknown | undefined;\n\n/**\n * Client-sourced inputs are hydrated from `read` and left absent when no stored value\n * exists, so the missing-inputs panel can detect them. Other inputs fall back to their\n * declared default, then the paramType default.\n */\nexport function buildInitialValues(\n\tschema: UISchema,\n\tscopeKey: string,\n\tread: ExternalReader\n): Record<string, unknown> {\n\tconst clientSet = new Set(getExternalInputs(schema).map((e) => e.paramId));\n\tconst values: Record<string, unknown> = {};\n\tfor (const input of schema.inputs) {\n\t\tif (clientSet.has(input.id)) {\n\t\t\tconst stored = read({ scopeKey, inputId: input.id });\n\t\t\tif (stored !== undefined) values[input.id] = stored;\n\t\t\tcontinue;\n\t\t}\n\t\tvalues[input.id] = input.default ?? getDefaultValue(input.paramType);\n\t}\n\tfor (const output of schema.outputs) {\n\t\tvalues[output.id] = null;\n\t}\n\treturn values;\n}\n\n/** Manual-solve schemas (instanceSolve === false) start dirty; auto-solve schemas start clean. */\nexport function createInitialState(\n\tschema: UISchema,\n\tscopeKey: string,\n\tread: ExternalReader\n): SolveSessionState {\n\tconst manual = schema.instanceSolve === false;\n\treturn {\n\t\tvalues: buildInitialValues(schema, scopeKey, read),\n\t\terror: '',\n\t\tcomputeErrors: [],\n\t\tcomputeWarnings: [],\n\t\tdiagnostics: [],\n\t\tblocked: false,\n\t\theldResult: null,\n\t\tmeshes: [],\n\t\thasPendingChanges: manual,\n\t\thasNeverSolved: manual,\n\t\tlastResult: null,\n\t\tunsolved: null\n\t};\n}\n\n/**\n * Whether the change should dispatch. Manual mode defers and raises the dirty flag instead;\n * `force` overrides that, and what it solves is every pending edit.\n */\nexport function applyValueChange(\n\tstate: SolveSessionState,\n\tid: string,\n\tvalue: unknown,\n\tinstanceSolve: boolean | undefined,\n\tforce = false\n): boolean {\n\tstate.values[id] = value;\n\tif (instanceSolve !== false) return true;\n\tstate.hasPendingChanges = !force;\n\treturn force;\n}\n\n/** Same rule as `applyValueChange`, for a whole set. Whether it should dispatch. */\nexport function applyLoadedValues(\n\tstate: SolveSessionState,\n\tincoming: Record<string, unknown>,\n\tinstanceSolve: boolean | undefined\n): boolean {\n\tObject.assign(state.values, incoming);\n\tif (instanceSolve !== false) return true;\n\tstate.hasPendingChanges = true;\n\treturn false;\n}\n\n// Worth sending even unchanged: a dynamic list's options can change under the same selection, and\n// a file can be picked again after its parameter was cleared.\nconst RESEND_UNCHANGED = new Set(['dynamicValueList', 'file']);\n\n/**\n * Whether setting `value` would solve again for nothing: it is what the last solve sent and what\n * the session holds, and nothing failed or aborted since (`lastSent` is null then). A field that\n * reports on input and again on blur would otherwise solve twice, and Grasshopper re-applies every\n * dynamic list on each update, so the repeat is a real solve. Objects never count as repeats.\n */\nexport function isRepeatOfLastSolve(\n\tschema: UISchema,\n\tlastSent: Record<string, unknown> | null,\n\tvalues: Record<string, unknown>,\n\tid: string,\n\tvalue: unknown\n): boolean {\n\tif (!lastSent || !(id in lastSent)) return false;\n\tif (value !== null && typeof value === 'object') return false;\n\tif (!Object.is(lastSent[id], value) || !Object.is(values[id], value)) return false;\n\tconst paramType = schema.inputs.find((input) => input.id === id)?.paramType;\n\treturn !RESEND_UNCHANGED.has(paramType ?? '');\n}\n\n/**\n * Solve outputs live in the same `values` map as inputs (so widgets like dynamic value\n * lists can read them) but must not be echoed back to the driver: a measured 6.4 MB\n * options-list output showed this can re-upload a multi-MB payload no backend reads.\n */\nexport function pickInputValues(\n\tschema: UISchema,\n\tvalues: Record<string, unknown>\n): Record<string, unknown> {\n\tconst picked: Record<string, unknown> = {};\n\tfor (const input of schema.inputs) {\n\t\tif (Object.hasOwn(values, input.id)) picked[input.id] = values[input.id];\n\t}\n\treturn picked;\n}\n\n/** An input was edited after `sent` was dispatched, so a result for `sent` does not cover it. */\nexport function inputsChangedSince(\n\tschema: UISchema,\n\tvalues: Record<string, unknown>,\n\tsent: Record<string, unknown> | null\n): boolean {\n\tif (!sent) return false;\n\tconst current = pickInputValues(schema, values);\n\treturn Object.keys(current).some(\n\t\t(id) => !Object.hasOwn(sent, id) || !Object.is(current[id], sent[id])\n\t);\n}\n\n/**\n * `editedSinceSent` keeps the dirty flag for a value changed while the solve ran: its result\n * answers the older value, and clearing the flag would hide the Calculate prompt on a stale answer.\n */\nexport function applySolveResult(\n\tstate: SolveSessionState,\n\tresult: SolveResult,\n\teditedSinceSent: boolean\n): void {\n\tstate.error = '';\n\tstate.computeErrors = result.errors ?? [];\n\tstate.computeWarnings = result.warnings ?? [];\n\tstate.diagnostics = result.diagnostics ?? [];\n\tstate.blocked = result.blocked ?? false;\n\tstate.unsolved = result.aborted ? 'aborted' : null;\n\n\t// The solve is finished either way — Grasshopper cannot be paused mid-solution — so what is\n\t// held back here is the *result*, not the computation. Everything the solve produced is kept\n\t// intact in `heldResult` and released verbatim by `acknowledge()`.\n\t//\n\t// A blocked solve is applied at once: there is no result to weigh, and the previous one must\n\t// not stay on screen looking like the answer to the current inputs. The caller blanks the\n\t// outputs (see `blankOutputs`); the empty mesh list clears the viewer.\n\tif (!state.blocked && needsAcknowledgement(state.diagnostics)) {\n\t\tstate.heldResult = result;\n\t\tstate.hasPendingChanges &&= editedSinceSent;\n\t\treturn;\n\t}\n\n\tstate.heldResult = null;\n\tcommitSolveResult(state, result, editedSinceSent);\n}\n\n/**\n * Every schema output set to null. A blocked solve reports `outputs: {}`, and merging that into\n * `values` would leave the previous solve's outputs behind.\n */\nexport function blankOutputs(schema: UISchema): Record<string, null> {\n\tconst blank: Record<string, null> = {};\n\tfor (const output of schema.outputs) blank[output.id] = null;\n\treturn blank;\n}\n\n/** Writes a result into the state. Shared by the clean path and by `acknowledge()`. */\nexport function commitSolveResult(\n\tstate: SolveSessionState,\n\tresult: SolveResult,\n\teditedSinceSent: boolean\n): void {\n\tstate.meshes = result.meshes ?? [];\n\tconst { meshes: _meshes, ...retained } = result;\n\tstate.lastResult = retained;\n\tObject.assign(state.values, result.outputs);\n\tstate.hasPendingChanges &&= editedSinceSent;\n\tstate.hasNeverSolved = false;\n}\n\n/** What `ensureSolved` has to do for the current inputs. */\nexport type EnsureSolvedStep = 'wait' | 'solve' | 'discarded' | 'blocked' | 'applied';\n\n/** `busy`: the driver is solving or has a solve queued. */\nexport function ensureSolvedStep(state: SolveSessionState, busy: boolean): EnsureSolvedStep {\n\tif (busy || state.heldResult) return 'wait';\n\tif (state.hasPendingChanges) return 'solve';\n\t// Not re-solved: the same inputs would raise the same messages and ask the user again.\n\tif (state.unsolved === 'discarded') return 'discarded';\n\tif (state.unsolved || state.hasNeverSolved || state.error || !state.lastResult) return 'solve';\n\treturn state.blocked ? 'blocked' : 'applied';\n}\n\nexport type SolvePhase = 'idle' | 'solving' | 'review' | 'blocked';\n\n/**\n * One value for what a host should show. Solving wins: a new solve supersedes a held or refused\n * result. `isSolving` is passed in because the driver owns it.\n */\nexport function solvePhase(state: SolveSessionState, isSolving: boolean): SolvePhase {\n\tif (isSolving) return 'solving';\n\tif (state.heldResult) return 'review';\n\tif (state.blocked) return 'blocked';\n\treturn 'idle';\n}\n\n/**\n * Whether this solve's messages still need the user's acknowledgement.\n *\n * Only a Selva Message component gates. Grasshopper raises warnings constantly for reasons that\n * concern whoever is editing the definition, not whoever is using the app — a component\n * complaining about its own inputs is not something an end user can act on, and interrupting\n * them for it trains them to dismiss the one message that mattered.\n *\n * Every solve that gates asks again, by design — an acknowledgement covers the solve in front of\n * the user, never a future one. Under `instanceSolve` with a live warning that means a dialog\n * per solve, which is the intended checkpoint, not an oversight.\n *\n * A transport that reports no structure has no diagnostics, so nothing gates: the messages are\n * still shown, just without interrupting.\n */\nexport function needsAcknowledgement(diagnostics: SolveDiagnostic[]): boolean {\n\treturn diagnostics.some((d) => d.isGate && d.level !== 'remark');\n}\n","// A framework-free shell over the pure transition logic in solve-session-core.ts: owns the\n// live values/flags, delegates every transition to the core, and drives solves through a\n// transport-agnostic SolveDriver.\n//\n// State is exposed as plain getters; it carries no framework reactivity of its own, so a\n// Svelte host must wrap it — see `useSolveSession.svelte.ts` in `@selvajs/ui`, which\n// republishes these getters as $state. Reading a getter without subscribing gives a\n// correct value but will not re-render.\n\nimport type { UISchema } from '@selvajs/schemas';\nimport { readExternalValue } from '../external-storage.js';\nimport type { SolveDiagnostic, SolveResult } from '../../shared/solve-fn.js';\nimport type { SolveDriver } from '../drivers/driver.js';\nimport { EMPTY_LIVE_SOLVE, reduceLiveSolve, type LiveSolveState } from '../events/live-solve.js';\nimport {\n\tapplyLoadedValues,\n\tapplySolveResult,\n\tapplyValueChange,\n\tblankOutputs,\n\tcommitSolveResult,\n\tcreateInitialState,\n\tensureSolvedStep,\n\tinputsChangedSince,\n\tisRepeatOfLastSolve,\n\tpickInputValues,\n\tsolvePhase,\n\ttype SolvePhase,\n\ttype RetainedSolveResult\n} from './solve-session-core.js';\n\n/**\n * How a solve ended, once nothing about it can change any more: a held result counts only\n * after the user accepts or rejects it, and a result a queued solve is about to replace never\n * counts. One per solve that lands, whether a host asked for it or an input change did.\n */\nexport type SolveSettlement =\n\t| { status: 'applied'; result: RetainedSolveResult; diagnostics: SolveDiagnostic[] }\n\t/** A held result the user rejected or a newer solve replaced; the screen still shows the one before it. */\n\t| { status: 'discarded'; diagnostics: SolveDiagnostic[] }\n\t/** The definition refused the solve. */\n\t| { status: 'blocked'; diagnostics: SolveDiagnostic[] }\n\t/** Cancelled, or stopped in Grasshopper, before it produced a result. */\n\t| { status: 'aborted'; diagnostics: SolveDiagnostic[] }\n\t/** The transport failed or the solve ran past its deadline; `error` is what the user sees. */\n\t| { status: 'failed'; error: string };\n\nexport interface SolveSession {\n\treadonly values: Record<string, unknown>;\n\treadonly error: string;\n\treadonly computeErrors: string[];\n\treadonly computeWarnings: string[];\n\t/**\n\t * The last solve's messages with their level, source and `isGate`. Richer than the two\n\t * string arrays above and the list a message UI should render; they remain for hosts that\n\t * only want the text. Empty from a transport that reports no structure.\n\t */\n\treadonly diagnostics: SolveDiagnostic[];\n\t/**\n\t * The definition refused the last solve, or it was aborted. Applied at once, not held: the\n\t * outputs are blanked and the viewer cleared, and `computeErrors` holds the reason.\n\t */\n\treadonly blocked: boolean;\n\t/**\n\t * The last solve raised messages awaiting the user's confirmation, and its result is held\n\t * back until then. A host shows the messages and calls `acknowledge()` to release them.\n\t * Never true for a blocked solve.\n\t */\n\treadonly awaitingAck: boolean;\n\t/**\n\t * Releases a held result into the session. No-op when nothing is held. The solve already\n\t * ran — this applies what it produced, it does not re-solve.\n\t */\n\tacknowledge(): void;\n\t/**\n\t * Drops a held result instead of applying it. The viewer keeps what it was showing. No-op\n\t * when nothing is held, and never re-solves — like `acknowledge()`, it only decides the\n\t * fate of a result that already exists.\n\t */\n\tdiscard(): void;\n\t/**\n\t * What the running (or most recent) solve has said so far: its diagnostics, progress per\n\t * source, and how it ended. Reset when a solve with a new id starts, so a host renders \"what\n\t * this solve has said\", never a tail of the previous one. Stays empty when the driver has no\n\t * live channel.\n\t */\n\treadonly live: LiveSolveState;\n\t/**\n\t * Where the session is, as one value instead of four flags: `solving` while a solve runs,\n\t * `review` while a result waits for `acknowledge()`, `blocked` after a refused or aborted\n\t * solve, `idle` otherwise.\n\t */\n\treadonly phase: SolvePhase;\n\t/**\n\t * Asks the transport to stop the solve in flight. Cooperative on both paths — Grasshopper\n\t * stops at the next component boundary — so the result is a `solveEnded` (or nothing, if it\n\t * finished first), never an instant halt.\n\t */\n\tabort(): void;\n\treadonly meshes: unknown[];\n\t/**\n\t * What the viewer is currently showing, including the `source`/`values` pair a commit path\n\t * needs. Null before the first solve, and again after `rebuild()`. Survives `reportError`: a\n\t * failed solve leaves the last good geometry on screen, so a host committing what it sees\n\t * should still see what produced it.\n\t */\n\treadonly lastResult: RetainedSolveResult | null;\n\treadonly hasPendingChanges: boolean;\n\treadonly hasNeverSolved: boolean;\n\treadonly isSolving: boolean;\n\t/** `forceSolve` overrides manual mode for system-initiated reconciliation (e.g. a dynamic value list pruning a vanished selection), so the output can't lag behind the new value. */\n\tsetValue(id: string, value: unknown, forceSolve?: boolean): void;\n\t/**\n\t * Solves the current inputs. Resolves with the settlement of the solve that lands, which is\n\t * a later one if the inputs change meanwhile: what is on screen when it resolves. Never\n\t * rejects; a failure is a status. A rebuild or dispose first resolves it as `aborted`.\n\t */\n\tsolve(): Promise<SolveSettlement>;\n\t/**\n\t * The result for the current inputs, solving only when it has to: right away from the result\n\t * on screen when nothing changed, after the running or held solve when there is one, else\n\t * after a new solve. For an action that needs a fresh result, such as an export. Inputs whose\n\t * result the user just discarded settle as `discarded` rather than ask them again.\n\t */\n\tensureSolved(): Promise<SolveSettlement>;\n\t/** Fires once per settled solve, the ones input changes start included. Returns the unsubscribe. */\n\tonSettled(listener: (settlement: SolveSettlement) => void): () => void;\n\t/** Merges incoming values, then solves (auto) or marks dirty (manual). */\n\tloadValues(incoming: Record<string, unknown>): void;\n\trebuild(schema: UISchema, scopeKey: string): void;\n\treport(result: SolveResult): void;\n\treportError(message: string): void;\n\treportAborted(): void;\n\t/** Fires after any mutation of the getters above, including driver-reported `isSolving` transitions. */\n\tsubscribe(listener: () => void): () => void;\n\t/** For driver-owned state the session only forwards: `isSolving` lives on the driver, so wire this to its `onChange`. */\n\tnotify(): void;\n\t/**\n\t * Resets the driver, stops listening to its live events and drops every subscriber. Call when\n\t * the session is discarded: the event source can outlive it (the cloud stream belongs to the\n\t * tab), and would otherwise keep feeding a session nobody reads. Inert afterwards.\n\t */\n\tdispose(): void;\n}\n\nexport interface SolveSessionArgs {\n\tschema: UISchema;\n\tscopeKey: string;\n\tdriver: SolveDriver;\n}\n\n/** One listener's bug must not cost the others their update, or strand a solve mid-landing. */\nfunction notifyAll<T>(listeners: Iterable<(value: T) => void>, value: T) {\n\tfor (const listener of [...listeners]) {\n\t\ttry {\n\t\t\tlistener(value);\n\t\t} catch (err) {\n\t\t\tconsole.error('[Solve/session] listener threw:', err);\n\t\t}\n\t}\n}\n\nexport function createSolveSession(args: SolveSessionArgs): SolveSession {\n\tlet currentSchema = args.schema;\n\t// The inputs the last dispatch sent; null after a failure or abort, so the same values retry.\n\tlet lastSent: Record<string, unknown> | null = null;\n\t// Survives a failure, unlike `lastSent`: a queued solve may be running on exactly these inputs.\n\tlet lastDispatched: Record<string, unknown> | null = null;\n\n\tconst state = createInitialState(currentSchema, args.scopeKey, readExternalValue);\n\tlet disposed = false;\n\n\tconst listeners = new Set<() => void>();\n\tconst emit = () => notifyAll(listeners, undefined);\n\n\tconst settledListeners = new Set<(settlement: SolveSettlement) => void>();\n\tlet waiters: ((settlement: SolveSettlement) => void)[] = [];\n\tconst ABORTED: SolveSettlement = { status: 'aborted', diagnostics: [] };\n\n\tfunction nextSettlement(): Promise<SolveSettlement> {\n\t\tif (disposed) return Promise.resolve(ABORTED);\n\t\treturn new Promise((resolve) => waiters.push(resolve));\n\t}\n\n\tfunction settle(settlement: SolveSettlement) {\n\t\tconst waiting = waiters;\n\t\twaiters = [];\n\t\twaiting.forEach((resolve) => resolve(settlement));\n\t\tnotifyAll(settledListeners, settlement);\n\t}\n\n\t/** Resolves waiting callers without telling listeners: no solve ended, the session moved on. */\n\tfunction abandon() {\n\t\tconst waiting = waiters;\n\t\twaiters = [];\n\t\twaiting.forEach((resolve) => resolve(ABORTED));\n\t}\n\n\tfunction settlementOf(result: SolveResult): SolveSettlement {\n\t\tconst diagnostics = state.diagnostics;\n\t\tif (result.aborted) return { status: 'aborted', diagnostics };\n\t\tif (result.blocked) return { status: 'blocked', diagnostics };\n\t\treturn { status: 'applied', result: state.lastResult!, diagnostics };\n\t}\n\n\t/** One solve ended. Tells subscribers, then settles unless a queued solve is about to replace it. */\n\tfunction land(settlement: () => SolveSettlement) {\n\t\temit();\n\t\tif (!args.driver.hasQueued) settle(settlement());\n\t}\n\n\t/**\n\t * Waiters stay: `solve()` resolves with what lands, and that is now the newer solve. Only\n\t * listeners hear of it, so the dropped solve still settles exactly once.\n\t */\n\tfunction dropHeld() {\n\t\tif (!state.heldResult) return;\n\t\tstate.heldResult = null;\n\t\tnotifyAll(settledListeners, { status: 'discarded', diagnostics: state.diagnostics });\n\t}\n\n\tfunction editedSinceSent() {\n\t\treturn inputsChangedSince(currentSchema, state.values, lastDispatched);\n\t}\n\n\tlet live: LiveSolveState = EMPTY_LIVE_SOLVE;\n\t// A driver with no live channel never calls this; nothing else in the session depends on it.\n\tconst stopEvents = args.driver.onEvent?.((event) => {\n\t\tconst next = reduceLiveSolve(live, event);\n\t\tif (next === live) return;\n\t\tlive = next;\n\t\temit();\n\t});\n\n\tfunction solve(): Promise<SolveSettlement> {\n\t\tconst settled = nextSettlement();\n\t\tdispatch();\n\t\treturn settled;\n\t}\n\n\tfunction dispatch() {\n\t\tif (disposed) return;\n\t\tdropHeld();\n\t\t// Copy: state.values is the live map, and a driver may hold the payload across an\n\t\t// async solve — a later setValue must not mutate what's already in flight.\n\t\tlastSent = { ...pickInputValues(currentSchema, state.values) };\n\t\tlastDispatched = lastSent;\n\t\targs.driver.solve(lastSent);\n\t\temit();\n\t}\n\n\treturn {\n\t\tget values() {\n\t\t\treturn state.values;\n\t\t},\n\t\tget error() {\n\t\t\treturn state.error;\n\t\t},\n\t\tget computeErrors() {\n\t\t\treturn state.computeErrors;\n\t\t},\n\t\tget computeWarnings() {\n\t\t\treturn state.computeWarnings;\n\t\t},\n\t\tget diagnostics() {\n\t\t\treturn state.diagnostics;\n\t\t},\n\t\tget blocked() {\n\t\t\treturn state.blocked;\n\t\t},\n\t\tget awaitingAck() {\n\t\t\treturn state.heldResult !== null;\n\t\t},\n\n\t\tacknowledge() {\n\t\t\tconst held = state.heldResult;\n\t\t\tif (!held) return;\n\t\t\tstate.heldResult = null;\n\t\t\tcommitSolveResult(state, held, editedSinceSent());\n\t\t\tland(() => settlementOf(held));\n\t\t},\n\n\t\tdiscard() {\n\t\t\tif (!state.heldResult) return;\n\t\t\tstate.heldResult = null;\n\t\t\tstate.unsolved = 'discarded';\n\t\t\t// `values`, `meshes` and `lastResult` are untouched: the viewer keeps showing the last\n\t\t\t// result the user accepted. The messages stay on `computeErrors`/`computeWarnings` so\n\t\t\t// the footer still explains why nothing changed.\n\t\t\tland(() => ({ status: 'discarded', diagnostics: state.diagnostics }));\n\t\t},\n\t\tget live() {\n\t\t\treturn live;\n\t\t},\n\t\tget phase() {\n\t\t\treturn solvePhase(state, args.driver.isSolving);\n\t\t},\n\t\tabort() {\n\t\t\targs.driver.cancel();\n\t\t},\n\t\tget meshes() {\n\t\t\treturn state.meshes;\n\t\t},\n\t\tget lastResult() {\n\t\t\treturn state.lastResult;\n\t\t},\n\t\tget hasPendingChanges() {\n\t\t\treturn state.hasPendingChanges;\n\t\t},\n\t\tget hasNeverSolved() {\n\t\t\treturn state.hasNeverSolved;\n\t\t},\n\t\tget isSolving() {\n\t\t\treturn args.driver.isSolving;\n\t\t},\n\n\t\tsetValue(id, value, forceSolve = false) {\n\t\t\tif (!forceSolve && isRepeatOfLastSolve(currentSchema, lastSent, state.values, id, value))\n\t\t\t\treturn;\n\t\t\tif (applyValueChange(state, id, value, currentSchema.instanceSolve, forceSolve)) {\n\t\t\t\tdispatch();\n\t\t\t} else {\n\t\t\t\temit();\n\t\t\t}\n\t\t},\n\n\t\tsolve,\n\n\t\tensureSolved() {\n\t\t\tconst busy = args.driver.isSolving || args.driver.hasQueued === true;\n\t\t\tconst step = ensureSolvedStep(state, busy);\n\t\t\tif (step === 'wait') return nextSettlement();\n\t\t\tif (step === 'solve') return solve();\n\t\t\tif (step === 'applied') {\n\t\t\t\treturn Promise.resolve({\n\t\t\t\t\tstatus: step,\n\t\t\t\t\tresult: state.lastResult!,\n\t\t\t\t\tdiagnostics: state.diagnostics\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn Promise.resolve({ status: step, diagnostics: state.diagnostics });\n\t\t},\n\n\t\tonSettled(listener) {\n\t\t\tsettledListeners.add(listener);\n\t\t\treturn () => settledListeners.delete(listener);\n\t\t},\n\n\t\tloadValues(incoming) {\n\t\t\tif (applyLoadedValues(state, incoming, currentSchema.instanceSolve)) {\n\t\t\t\tdispatch();\n\t\t\t} else {\n\t\t\t\temit();\n\t\t\t}\n\t\t},\n\n\t\trebuild(schema, scopeKey) {\n\t\t\tif (disposed) return;\n\t\t\t// A caller waiting on the old definition's solve would otherwise get the new one's.\n\t\t\tabandon();\n\t\t\tcurrentSchema = schema;\n\t\t\tlastSent = null;\n\t\t\tlastDispatched = null;\n\t\t\t// The old definition's running solve and cached results must not reach the new one,\n\t\t\t// even where the two share input ids.\n\t\t\targs.driver.reset?.();\n\t\t\tObject.assign(state, createInitialState(schema, scopeKey, readExternalValue));\n\t\t\tif (Object.keys(state.values).length > 0 && schema.instanceSolve !== false) {\n\t\t\t\tdispatch();\n\t\t\t} else {\n\t\t\t\temit();\n\t\t\t}\n\t\t},\n\n\t\treport(result) {\n\t\t\tconst rejected = result.blocked || result.aborted;\n\t\t\t// An aborted solve answered nothing, so whatever was dirty stays dirty.\n\t\t\tconst edited = result.aborted === true || editedSinceSent();\n\t\t\tif (result.aborted) lastSent = null;\n\t\t\t// `finalizeResult` already dropped a rejected solve's outputs; this also nulls the\n\t\t\t// ones still in `values` from the previous solve, which only the session can name.\n\t\t\tapplySolveResult(\n\t\t\t\tstate,\n\t\t\t\trejected ? { ...result, outputs: blankOutputs(currentSchema), meshes: [] } : result,\n\t\t\t\tedited\n\t\t\t);\n\t\t\t// A result a queued solve replaces never asks the user anything, and never settles.\n\t\t\tif (args.driver.hasQueued) state.heldResult = null;\n\t\t\t// A held result settles on acknowledge() or discard().\n\t\t\tif (state.heldResult) return emit();\n\t\t\tland(() => settlementOf(result));\n\t\t},\n\n\t\treportError(message) {\n\t\t\tdropHeld();\n\t\t\tlastSent = null;\n\t\t\tstate.error = message;\n\t\t\tstate.unsolved = null;\n\t\t\tland(() => ({ status: 'failed', error: message }));\n\t\t},\n\n\t\treportAborted() {\n\t\t\tlastSent = null;\n\t\t\tstate.unsolved = 'aborted';\n\t\t\tland(() => ABORTED);\n\t\t},\n\n\t\tsubscribe(listener) {\n\t\t\tlisteners.add(listener);\n\t\t\treturn () => listeners.delete(listener);\n\t\t},\n\n\t\tnotify: emit,\n\n\t\tdispose() {\n\t\t\tdisposed = true;\n\t\t\tstopEvents?.();\n\t\t\tlisteners.clear();\n\t\t\tsettledListeners.clear();\n\t\t\targs.driver.reset?.();\n\t\t\tabandon();\n\t\t}\n\t};\n}\n","/**\n * Single-in-flight, latest-wins throttle for any async operation: only one run at a\n * time, a newly triggered value overwrites the single pending slot (no queue), and a\n * stale run can be aborted via `AbortController` or timeout.\n *\n * Framework-free: `isRunning` is a plain getter, not a store. A reactive host must\n * subscribe via `onChange` (see `@selvajs/ui`'s rune adapter) — polling the getter\n * directly reads correct values but won't trigger a re-render.\n */\ninterface AsyncThrottleOptions {\n\t/**\n\t * How long one run may take before it is aborted (ms). Required and deliberately\n\t * undefaulted: this is a generic throttle, so it has no basis for guessing what\n\t * the caller's work costs. Callers driving a Selva solve pass the deadline the\n\t * server enforces (the compute server's own timeout) — a shorter local guess would abort\n\t * solves the server would have finished.\n\t */\n\trunDeadlineMs: number;\n\tonChange?: () => void;\n}\n\nexport interface AsyncThrottle<T> {\n\ttrigger: (values: T) => void;\n\treadonly isRunning: boolean;\n\treadonly hasPending: boolean;\n\tcancel: () => void;\n}\n\n/** `run` must settle once its signal fires: the slot frees only then. */\nexport function createAsyncThrottle<T>(\n\trun: (values: T, signal: AbortSignal) => Promise<void>,\n\toptions: AsyncThrottleOptions\n): AsyncThrottle<T> {\n\tconst { runDeadlineMs, onChange } = options;\n\n\tlet isRunning = false;\n\tlet pendingValues: T | null = null;\n\tlet currentAbortController: AbortController | null = null;\n\n\tfunction setRunning(next: boolean) {\n\t\tif (isRunning === next) return;\n\t\tisRunning = next;\n\t\t// A host's bug must not strand `isRunning`, or throw out of `finally` below.\n\t\ttry {\n\t\t\tonChange?.();\n\t\t} catch (err) {\n\t\t\tconsole.error('[Solve/throttle] onChange threw:', err);\n\t\t}\n\t}\n\n\tasync function execute(values: T) {\n\t\tconst controller = new AbortController();\n\t\tcurrentAbortController = controller;\n\t\tconst deadlineId = setTimeout(() => {\n\t\t\tconsole.warn(`[Solve/throttle] run exceeded ${runDeadlineMs}ms — aborting`);\n\t\t\t// The reason tells `run` a timeout from a cancel: one is a failure, the other not.\n\t\t\tcontroller.abort(new DOMException('Run deadline exceeded', 'TimeoutError'));\n\t\t}, runDeadlineMs);\n\n\t\tsetRunning(true);\n\t\ttry {\n\t\t\tawait run(values, controller.signal);\n\t\t} catch (err) {\n\t\t\t// `execute` is called fire-and-forget, so re-throwing would be an unhandled rejection.\n\t\t\tif (!(err instanceof Error) || (err.name !== 'AbortError' && err.name !== 'TimeoutError')) {\n\t\t\t\tconsole.error('[Solve/throttle] unhandled error in run:', err);\n\t\t\t}\n\t\t} finally {\n\t\t\tclearTimeout(deadlineId);\n\t\t\t// Before `setRunning`: a subscriber can start the next run from inside it, and that\n\t\t\t// run's controller must survive this cleanup.\n\t\t\tif (currentAbortController === controller) currentAbortController = null;\n\t\t\tsetRunning(false);\n\n\t\t\tif (pendingValues !== null && !isRunning) {\n\t\t\t\tconst next = pendingValues;\n\t\t\t\tpendingValues = null;\n\t\t\t\texecute(next);\n\t\t\t}\n\t\t}\n\t}\n\n\tfunction trigger(values: T) {\n\t\tif (isRunning) {\n\t\t\tif (pendingValues !== null) {\n\t\t\t\t// eslint-disable-next-line no-console -- fires per frame during a slider scrub\n\t\t\t\tconsole.debug('[Solve/throttle] superseded pending run (latest-wins)');\n\t\t\t}\n\t\t\tpendingValues = values;\n\t\t} else {\n\t\t\texecute(values);\n\t\t}\n\t}\n\n\tfunction cancel() {\n\t\tpendingValues = null;\n\t\tcurrentAbortController?.abort();\n\t\tcurrentAbortController = null;\n\t}\n\n\treturn {\n\t\ttrigger,\n\t\tget isRunning() {\n\t\t\treturn isRunning;\n\t\t},\n\t\tget hasPending() {\n\t\t\treturn pendingValues !== null;\n\t\t},\n\t\tcancel\n\t};\n}\n","// LRU cache of solve results keyed on a stable serialization of the inputs, so re-solving\n// something already seen this session (e.g. dragging a slider back) returns instantly\n// instead of round-tripping.\n//\n// A viewer typically takes ownership of every mesh it renders, disposing the previous\n// content on the next update. So the memo can't hand out its own stored instances (they'd\n// get disposed under it) or drop entries silently (their GPU buffers would leak): it keeps\n// private copies via an injected clone/release policy, which also keeps `three` out of this\n// package — `TMesh` stays opaque here.\n\nimport type { SolveResult } from '../shared/solve-fn.js';\n\n/**\n * Deterministic string key for a set of input values. Sorts object keys at every level so\n * two logically-equal inputs built in different key order collide. Otherwise matches what\n * `JSON.stringify` sends: `toJSON` is honoured and an `undefined` property is absent, which a\n * server treats differently from `null`.\n *\n * Throws on a `bigint`, as `JSON.stringify` does.\n */\nexport function stableInputKey(input: unknown): string {\n\tconst hasToJSON =\n\t\tinput !== null &&\n\t\ttypeof input === 'object' &&\n\t\ttypeof (input as { toJSON?: unknown }).toJSON === 'function';\n\tconst value = hasToJSON ? (input as { toJSON(): unknown }).toJSON() : input;\n\n\tif (value === null || typeof value !== 'object') return JSON.stringify(value) ?? 'null';\n\tif (Array.isArray(value)) return `[${value.map(stableInputKey).join(',')}]`;\n\tconst obj = value as Record<string, unknown>;\n\tconst keys = Object.keys(obj)\n\t\t.filter((k) => obj[k] !== undefined)\n\t\t.sort();\n\treturn `{${keys.map((k) => `${JSON.stringify(k)}:${stableInputKey(obj[k])}`).join(',')}}`;\n}\n\n/**\n * Omit when results are mesh-free or nothing takes ownership of the meshes — the memo then\n * stores and serves them by reference, which is safe only because nothing frees them behind\n * its back.\n */\nexport interface MeshPolicy<TMesh> {\n\t/** Called on the way in and on every hit, so the stored entry and a served copy are never the same instance. */\n\tclone(meshes: TMesh[]): TMesh[];\n\t/** Must not free anything shared with live content (e.g. materials another object still uses). */\n\trelease(meshes: TMesh[]): void;\n}\n\nexport interface SolveMemoOptions<TMesh> {\n\t/** Entry cap, not a byte cap. Kept small on purpose: this targets the slider-scrub loop, not a durable cache. */\n\tmax?: number;\n\tmeshPolicy?: MeshPolicy<TMesh>;\n}\n\nexport interface SolveMemo<TMesh = unknown> {\n\tget(values: Record<string, unknown>): SolveResult<TMesh> | undefined;\n\tset(values: Record<string, unknown>, result: SolveResult<TMesh>): void;\n\tclear(): void;\n}\n\nexport function createSolveMemo<TMesh = unknown>(\n\toptions: SolveMemoOptions<TMesh> = {}\n): SolveMemo<TMesh> {\n\tconst { max = 16, meshPolicy } = options;\n\tconst entries = new Map<string, SolveResult<TMesh>>();\n\n\tfunction release(result: SolveResult<TMesh>): void {\n\t\tif (result.meshes?.length) meshPolicy?.release(result.meshes);\n\t}\n\n\tfunction copy(result: SolveResult<TMesh>): SolveResult<TMesh> {\n\t\tif (!meshPolicy || !result.meshes?.length) return result;\n\t\treturn { ...result, meshes: meshPolicy.clone(result.meshes) };\n\t}\n\n\tfunction evict(key: string): void {\n\t\tconst entry = entries.get(key);\n\t\tif (entry === undefined) return;\n\t\tentries.delete(key);\n\t\trelease(entry);\n\t}\n\n\treturn {\n\t\tget(values) {\n\t\t\tconst key = stableInputKey(values);\n\t\t\tconst hit = entries.get(key);\n\t\t\tif (hit === undefined) return undefined;\n\t\t\tentries.delete(key);\n\t\t\tentries.set(key, hit);\n\t\t\t// eslint-disable-next-line no-console -- fires per frame during a slider scrub\n\t\t\tconsole.debug(`[Solve/memo] HIT — served from client memo (${entries.size}/${max})`);\n\t\t\treturn copy(hit);\n\t\t},\n\t\tset(values, result) {\n\t\t\tconst key = stableInputKey(values);\n\t\t\tevict(key);\n\t\t\tentries.set(key, copy(result));\n\t\t\tfor (const oldest of entries.keys()) {\n\t\t\t\tif (entries.size <= max) break;\n\t\t\t\tevict(oldest);\n\t\t\t}\n\t\t},\n\t\tclear() {\n\t\t\tentries.forEach(release);\n\t\t\tentries.clear();\n\t\t}\n\t};\n}\n","import { createAsyncThrottle } from '../async-throttle.js';\nimport { createSolveMemo, type MeshPolicy } from '../solve-memo.js';\nimport { raceAbort } from '../../shared/abort.js';\nimport type { SolveFn, SolveResult } from '../../shared/solve-fn.js';\nimport type { SolveDriver, SolveEventSource, SolveReporter } from './driver.js';\n\nexport interface RequestResponseDriverOptions<TMesh> {\n\t/** How long one solve may take before the client aborts it (ms). Pass the deadline the solve's server enforces so the two agree. */\n\tsolveDeadlineMs: number;\n\tonChange?: () => void;\n\t/** Mesh ownership policy for the result memo — see `MeshPolicy` (solve-memo.ts). */\n\tmeshPolicy?: MeshPolicy<TMesh>;\n\t/** Live events for the solves this driver runs; without it the session never sees any. */\n\tevents?: SolveEventSource;\n}\n\n/**\n * `getReporter` is lazy (`() => session`) because the session and driver reference each\n * other — the host builds the driver first, then the session, then can hand this back.\n */\nexport function createRequestResponseDriver<TMesh = unknown>(\n\tonSolve: SolveFn<TMesh>,\n\tgetReporter: () => SolveReporter<TMesh>,\n\toptions: RequestResponseDriverOptions<TMesh>\n): SolveDriver {\n\tconst { meshPolicy, solveDeadlineMs, onChange, events } = options;\n\n\t// Checked inside the throttled run, not before triggering it, so a hit only serves\n\t// after the throttle's latest-wins ordering has picked these values to run.\n\tconst memo = createSolveMemo<TMesh>({ meshPolicy });\n\n\t// Bumped by `reset`. A run from an older generation belongs to a definition that is gone: it\n\t// must neither fill the memo nor report into the session that replaced it.\n\tlet generation = 0;\n\n\t// A stopped run still ends in a report: a timeout is a failure the user should read, a\n\t// cancel is not. Either way a host awaiting the solve hears back.\n\tfunction reportStopped(signal: AbortSignal) {\n\t\t// eslint-disable-next-line no-console -- normal during a slider scrub; not a warning\n\t\tconsole.debug('[Solve/driver] solve stopped (cancelled or timed out)');\n\t\tconst reason = signal.reason as { name?: string } | undefined;\n\t\tif (reason?.name === 'TimeoutError') {\n\t\t\tgetReporter().reportError(\n\t\t\t\t`The solve took longer than ${Math.round(solveDeadlineMs / 1000)} s and was stopped.`\n\t\t\t);\n\t\t} else {\n\t\t\tgetReporter().reportAborted();\n\t\t}\n\t}\n\n\tconst throttle = createAsyncThrottle<Record<string, unknown>>(\n\t\tasync (values, signal) => {\n\t\t\tconst startedIn = generation;\n\t\t\tlet result: SolveResult<TMesh>;\n\t\t\t// Only the solve is inside the `try`. A report that throws is a host bug, and landing\n\t\t\t// here would settle a good solve a second time, as failed.\n\t\t\ttry {\n\t\t\t\tconst cached = memo.get(values);\n\t\t\t\tif (cached !== undefined) {\n\t\t\t\t\tresult = cached;\n\t\t\t\t} else {\n\t\t\t\t\t// Raced: a `SolveFn` that ignores its signal must not pin the throttle's slot past\n\t\t\t\t\t// the deadline. Its late settlement is dropped.\n\t\t\t\t\tconst solved = await raceAbort(onSolve(values, signal), signal);\n\t\t\t\t\t// Stamped here rather than in the SolveFn: a memo hit never calls it, so values\n\t\t\t\t\t// captured inside would pair the served result with whatever solved last.\n\t\t\t\t\tresult = { ...solved, values };\n\t\t\t\t\t// An abort is not an answer for these inputs, so the same values must solve again.\n\t\t\t\t\tif (!result.aborted && startedIn === generation) memo.set(values, result);\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\tif (startedIn !== generation) return;\n\t\t\t\tif (signal.aborted) return reportStopped(signal);\n\t\t\t\tconsole.warn('[Solve/driver] solve failed:', err);\n\t\t\t\treturn getReporter().reportError(err instanceof Error ? err.message : String(err));\n\t\t\t}\n\t\t\tif (startedIn !== generation) return;\n\t\t\tif (signal.aborted) return reportStopped(signal);\n\t\t\tgetReporter().report(result);\n\t\t},\n\t\t// The throttle is generic, so the solve deadline is what bounds one of its runs.\n\t\t{ runDeadlineMs: solveDeadlineMs, onChange }\n\t);\n\n\tfunction cancel() {\n\t\t// Dropping the fetch alone stops the Compute child: the server closes the solve and the\n\t\t// plugin's next callback gets a 410. The explicit cancel is for a server that keeps the\n\t\t// solve open, and loses the race harmlessly (404) when the fetch abort closes it first.\n\t\tthrottle.cancel();\n\t\tevents?.cancelCurrent?.();\n\t}\n\n\treturn {\n\t\tsolve(values) {\n\t\t\tthrottle.trigger(values);\n\t\t},\n\t\tcancel,\n\t\tget isSolving() {\n\t\t\treturn throttle.isRunning;\n\t\t},\n\t\tget hasQueued() {\n\t\t\treturn throttle.hasPending;\n\t\t},\n\t\treset() {\n\t\t\tgeneration += 1;\n\t\t\tcancel();\n\t\t\tmemo.clear();\n\t\t},\n\t\t...(events ? { onEvent: (listener) => events.subscribe(listener) } : {})\n\t} satisfies SolveDriver;\n}\n","// The cloud half of the live channel: one SSE stream per tab, carrying every solve this tab\n// runs. The stream id is minted here rather than by the server so a solve request can name it\n// before the stream has even connected.\n//\n// Opened when created, not on first subscribe: the server binds a solve to the stream only if\n// the stream is already registered, and a lazily opened one was rarely connected by the time\n// the first solve ran.\n\nimport type { SolveEvent } from '@selvajs/schemas';\nimport type { SolveEventSource } from '../drivers/driver.js';\n\nconst MIN_RECONNECT_MS = 1_000;\nconst MAX_RECONNECT_MS = 30_000;\n\nexport interface SolveEventStreamOptions {\n\t/** The SSE endpoint, e.g. `/api/v1/solve-events`. `streamId` is appended as a query param. */\n\tendpoint: string;\n\t/** Builds the cancel URL for a solve, e.g. `(id) => \\`/api/v1/solve/${id}/cancel\\``. */\n\tcancelEndpoint: (solveId: string) => string;\n\tfetch?: typeof fetch;\n}\n\nexport interface SolveEventStream extends SolveEventSource {\n\t/** Send this with every solve request so the server can route that solve's events here. */\n\treadonly streamId: string;\n\t/** The solve the server last reported as started and not yet ended; null between solves. */\n\treadonly currentSolveId: string | null;\n\t/** Final: the stream does not reopen, and a later subscriber hears nothing. */\n\tclose(): void;\n}\n\nfunction mintStreamId(): string {\n\tif (typeof crypto !== 'undefined' && 'randomUUID' in crypto) {\n\t\treturn crypto.randomUUID().replace(/-/g, '');\n\t}\n\treturn Array.from({ length: 32 }, () => Math.floor(Math.random() * 16).toString(16)).join('');\n}\n\nexport function createSolveEventStream(options: SolveEventStreamOptions): SolveEventStream {\n\tconst streamId = mintStreamId();\n\tconst listeners = new Set<(event: SolveEvent) => void>();\n\tconst doFetch = options.fetch ?? ((...args: Parameters<typeof fetch>) => fetch(...args));\n\tlet source: EventSource | null = null;\n\tlet currentSolveId: string | null = null;\n\tlet reconnectMs = MIN_RECONNECT_MS;\n\tlet reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n\n\tfunction open() {\n\t\tif (source || typeof EventSource === 'undefined') return;\n\t\tconst url = `${options.endpoint}${options.endpoint.includes('?') ? '&' : '?'}streamId=${streamId}`;\n\t\tconst opened = new EventSource(url);\n\t\tsource = opened;\n\t\topened.addEventListener('open', () => {\n\t\t\treconnectMs = MIN_RECONNECT_MS;\n\t\t});\n\t\topened.addEventListener('solve', (raw) => {\n\t\t\tlet event: SolveEvent;\n\t\t\ttry {\n\t\t\t\tevent = JSON.parse((raw as MessageEvent<string>).data);\n\t\t\t} catch {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (event.type === 'solveStarted') currentSolveId = event.solveId;\n\t\t\telse if (event.type === 'solveEnded' && currentSolveId === event.solveId)\n\t\t\t\tcurrentSolveId = null;\n\t\t\tfor (const listener of listeners) listener(event);\n\t\t});\n\t\t// EventSource reconnects after a dropped connection on its own, but a non-200 closes it for\n\t\t// good: one 502 during a deploy would end progress and cancel until a reload.\n\t\topened.addEventListener('error', () => {\n\t\t\tif (opened.readyState !== EventSource.CLOSED || source !== opened) return;\n\t\t\tsource = null;\n\t\t\tcurrentSolveId = null;\n\t\t\treconnectTimer = setTimeout(open, reconnectMs);\n\t\t\treconnectMs = Math.min(reconnectMs * 2, MAX_RECONNECT_MS);\n\t\t});\n\t}\n\n\topen();\n\n\treturn {\n\t\tstreamId,\n\t\tget currentSolveId() {\n\t\t\treturn currentSolveId;\n\t\t},\n\t\tsubscribe(listener) {\n\t\t\tlisteners.add(listener);\n\t\t\treturn () => {\n\t\t\t\tlisteners.delete(listener);\n\t\t\t};\n\t\t},\n\t\tcancelCurrent() {\n\t\t\tconst solveId = currentSolveId;\n\t\t\tif (!solveId) return;\n\t\t\t// Fire-and-forget: the answer arrives as `solveEnded` on the stream, or not at all if\n\t\t\t// the solve finished first, and either way there is nothing to await here.\n\t\t\tvoid doFetch(options.cancelEndpoint(solveId), { method: 'POST', keepalive: true }).catch(\n\t\t\t\t() => {}\n\t\t\t);\n\t\t},\n\t\tclose() {\n\t\t\tif (reconnectTimer) clearTimeout(reconnectTimer);\n\t\t\tsource?.close();\n\t\t\tsource = null;\n\t\t\tlisteners.clear();\n\t\t\tcurrentSolveId = null;\n\t\t}\n\t};\n}\n","/**\n * A ready-made `SolveFn` that POSTs to a `/api/compute`-shaped endpoint (the wire contract\n * `SolveEngine` produces) and unpacks the result, with 429 cooldown, session-expiry detection,\n * a bad-response guard, and abort handling at every await.\n *\n * Mesh decoding is NOT built in: this package stays renderer-agnostic, so a viewer-enabled\n * consumer supplies `meshes.extract` (typically `getThreeObjectsFromComputeResponse` from\n * `@selvajs/visualization/parse`).\n */\n\nimport {\n\tGrasshopperResponseProcessor,\n\ttype GrasshopperComputeResponse\n} from '@selvajs/compute/grasshopper';\nimport type { SolveFn, SolveResult } from '../shared/solve-fn.js';\nimport { decodeOutcome, finalizeResult, outcomeFromComputeMessages } from '../shared/outcome.js';\n\nexport interface ComputeFetchSolveFnOptions<TMesh = unknown> {\n\t/** The compute endpoint to POST to, e.g. `/api/compute`. */\n\tendpoint: string;\n\t/** Definition URL (or `local:<guid>`) to solve — read per-solve so it can track a route param. */\n\tdefinitionUrl: () => string;\n\t/** Schema input params, sent as the request body's `inputs`. A stored definition's server ignores them. */\n\tinputs: () => Array<{ id: string }>;\n\t/** Schema output params — drives byId/byName output extraction after a solve. */\n\toutputs: () => Array<{ id: string; nickname?: string }>;\n\tchannel?: () => 'live' | 'draft' | undefined;\n\tversionId?: () => string | null | undefined;\n\t/** The tab's live-event stream id (`createSolveEventStream().streamId`), so the server routes this solve's events to it. */\n\tstreamId?: () => string | null | undefined;\n\t/** Omit entirely for a non-viewer consumer — `meshes` on the result stays `[]`. */\n\tmeshes?: {\n\t\textract: (\n\t\t\tresponse: GrasshopperComputeResponse,\n\t\t\topts: { debug: boolean; signal: AbortSignal }\n\t\t) => TMesh[] | Promise<TMesh[]>;\n\t};\n\t/** Gates ALL console telemetry (timing, cache verdicts, whales, heap). Default `false`. */\n\tdebug?: boolean;\n\t/** Called on a 429 and on a solve refused during its cooldown, before the rate-limit Error. */\n\tonRateLimited?: (retryAfterSeconds: number) => void;\n\t/** Called on a 401 or a login-page redirect, before the \"sign in again\" Error. */\n\tonSessionExpired?: () => void;\n\t/** Where the server redirects an expired session. Default `/login`. */\n\tloginPath?: string;\n}\n\n/** Applied to a 429 that names no wait. */\nconst DEFAULT_RETRY_AFTER_S = 5;\n/** A server's hint, not a lock: past this the client tries again anyway. */\nconst MAX_RETRY_AFTER_S = 3600;\n\nfunction parseServerTiming(header: string | null): Record<string, number> {\n\tconst out: Record<string, number> = {};\n\tif (!header) return out;\n\tfor (const part of header.split(',')) {\n\t\tconst [name, ...params] = part.split(';').map((s) => s.trim());\n\t\tconst dur = params.find((p) => p.startsWith('dur='));\n\t\tif (!name || !dur) continue;\n\t\tconst ms = Number(dur.slice(4));\n\t\tif (Number.isFinite(ms)) out[name] = ms;\n\t}\n\treturn out;\n}\n\nfunction rateLimitedError(retryAfterSeconds: number): Error {\n\treturn new Error(`Rate limit reached. Try again in ${Math.ceil(retryAfterSeconds)}s.`);\n}\n\nfunction sessionExpiredError(): Error {\n\treturn new Error(\n\t\t'Your session has expired. Sign in again in a new tab, then re-run — your inputs are preserved.'\n\t);\n}\n\n/** `Retry-After` in either RFC form. Null when it names no usable wait. */\nfunction parseRetryAfter(value: string): number | null {\n\tif (!value.trim()) return null;\n\tconst seconds = Number(value);\n\tif (!Number.isNaN(seconds)) return Number.isFinite(seconds) && seconds >= 0 ? seconds : null;\n\tconst at = Date.parse(value);\n\treturn Number.isNaN(at) ? null : Math.max(0, (at - Date.now()) / 1000);\n}\n\nfunction readRetryAfter(header: string | null, bodyHint: unknown): number | null {\n\tconst seconds =\n\t\t(header === null ? null : parseRetryAfter(header)) ??\n\t\t(typeof bodyHint === 'number' ? parseRetryAfter(String(bodyHint)) : null);\n\treturn seconds === null ? null : Math.min(seconds, MAX_RETRY_AFTER_S);\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction parseErrorBody(text: string): { message?: string; retryAfter?: unknown } {\n\ttry {\n\t\tconst body: unknown = JSON.parse(text);\n\t\tif (!isPlainObject(body)) return {};\n\t\treturn {\n\t\t\t...(typeof body.message === 'string' ? { message: body.message } : {}),\n\t\t\tretryAfter: body.retryAfter\n\t\t};\n\t} catch {\n\t\treturn {};\n\t}\n}\n\nexport function createComputeFetchSolveFn<TMesh = unknown>(\n\topts: ComputeFetchSolveFnOptions<TMesh>\n): SolveFn<TMesh, GrasshopperComputeResponse> {\n\tconst debug = opts.debug ?? false;\n\tconst log = (...args: unknown[]) => {\n\t\tif (debug) console.info(...args);\n\t};\n\n\tlet cooldownUntil = 0;\n\n\tfunction rateLimited(retryAfterSeconds: number): Error {\n\t\topts.onRateLimited?.(retryAfterSeconds);\n\t\treturn rateLimitedError(retryAfterSeconds);\n\t}\n\n\treturn async (values, signal): Promise<SolveResult<TMesh, GrasshopperComputeResponse>> => {\n\t\tconst remainingMs = cooldownUntil - Date.now();\n\t\tif (remainingMs > 0) throw rateLimited(remainingMs / 1000);\n\n\t\tconst solveStart = performance.now();\n\t\tconst inputs = opts.inputs();\n\t\tconst outputs = opts.outputs();\n\t\tconst channel = opts.channel?.();\n\t\tconst versionId = opts.versionId?.();\n\t\tconst streamId = opts.streamId?.();\n\n\t\tconst payload = JSON.stringify({\n\t\t\tinputs,\n\t\t\tvalues,\n\t\t\tdefinitionUrl: opts.definitionUrl(),\n\t\t\t...(versionId ? { versionId } : channel === 'draft' ? { channel: 'draft' } : {}),\n\t\t\t...(streamId ? { streamId } : {})\n\t\t});\n\n\t\tlet res: Response;\n\t\ttry {\n\t\t\tres = await fetch(opts.endpoint, {\n\t\t\t\tmethod: 'POST',\n\t\t\t\theaders: { 'Content-Type': 'application/json' },\n\t\t\t\tbody: payload,\n\t\t\t\tsignal\n\t\t\t});\n\t\t} catch (err) {\n\t\t\tif (signal.aborted) {\n\t\t\t\tlog('[Compute] solve aborted during fetch — empty result discarded');\n\t\t\t\treturn { outputs: {} };\n\t\t\t}\n\t\t\t// Also how a redirect to a cross-origin sign-in looks from here, so an expired session\n\t\t\t// stays a possibility.\n\t\t\tthrow new Error(\n\t\t\t\t'Could not reach the server. Check your connection, or reload the page if your session expired.',\n\t\t\t\t{ cause: err }\n\t\t\t);\n\t\t}\n\n\t\tif (signal.aborted) {\n\t\t\tlog('[Compute] solve aborted after headers — empty result discarded');\n\t\t\treturn { outputs: {} };\n\t\t}\n\n\t\tconst redirectedToLogin =\n\t\t\tres.redirected && new URL(res.url).pathname === (opts.loginPath ?? '/login');\n\t\tif (res.status === 401 || redirectedToLogin) {\n\t\t\topts.onSessionExpired?.();\n\t\t\tthrow sessionExpiredError();\n\t\t}\n\n\t\tif (!res.ok) {\n\t\t\tconst errorBody = await res.text().catch(() => '');\n\t\t\tconst { message, retryAfter: bodyHint } = parseErrorBody(errorBody);\n\t\t\tif (errorBody && message === undefined) {\n\t\t\t\tlog(`[Compute] unreadable error body (HTTP ${res.status}, ${errorBody.length} chars)`);\n\t\t\t}\n\t\t\t// Any status may ask for a wait: a shed 503 says the same thing as a 429, and ignoring it\n\t\t\t// would hit a saturated server hardest at the moment it asks clients to back off.\n\t\t\tconst retryAfter = readRetryAfter(res.headers.get('Retry-After'), bodyHint);\n\t\t\tif (retryAfter !== null) cooldownUntil = Date.now() + retryAfter * 1000;\n\t\t\tif (res.status === 503 || res.status === 504) {\n\t\t\t\t// The server names the failure class (unreachable, bad API key,\n\t\t\t\t// rate-limited, timed out) — show it rather than a blanket \"offline\".\n\t\t\t\tthrow new Error(\n\t\t\t\t\tmessage || 'Compute server is offline or unreachable. Please try again later.'\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (res.status === 429) {\n\t\t\t\tconst seconds = retryAfter ?? DEFAULT_RETRY_AFTER_S;\n\t\t\t\tcooldownUntil = Date.now() + seconds * 1000;\n\t\t\t\tthrow rateLimited(seconds);\n\t\t\t}\n\t\t\tthrow new Error(message || `Compute error (HTTP ${res.status})`);\n\t\t}\n\n\t\tconst ttfbMs = performance.now() - solveStart;\n\n\t\tconst downloadStart = performance.now();\n\t\tlet bodyText: string;\n\t\ttry {\n\t\t\tbodyText = await res.text();\n\t\t} catch (err) {\n\t\t\tif (signal.aborted) {\n\t\t\t\tlog('[Compute] solve aborted mid-download — empty result discarded');\n\t\t\t\treturn { outputs: {} };\n\t\t\t}\n\t\t\tthrow new Error('The connection dropped while receiving the result. Please try again.', {\n\t\t\t\tcause: err\n\t\t\t});\n\t\t}\n\t\tconst downloadMs = performance.now() - downloadStart;\n\t\tconst bodyChars = bodyText.length;\n\n\t\tconst invalidResponse = (cause?: unknown) =>\n\t\t\tnew Error('Received an invalid response from the server. Reload the page and try again.', {\n\t\t\t\tcause\n\t\t\t});\n\t\tlet parsed: unknown;\n\t\ttry {\n\t\t\tparsed = JSON.parse(bodyText);\n\t\t} catch (err) {\n\t\t\tthrow invalidResponse(err);\n\t\t}\n\t\tif (!isPlainObject(parsed)) throw invalidResponse();\n\t\tconst solved = parsed as unknown as GrasshopperComputeResponse;\n\t\tconst processor = new GrasshopperResponseProcessor(solved);\n\n\t\t// The plugin's own verdict when the fork returned one; otherwise recovered from the\n\t\t// markers in Compute's flattened strings (stock server, or a plugin that predates it).\n\t\tconst outcome =\n\t\t\tdecodeOutcome(solved.selva?.outcome) ??\n\t\t\toutcomeFromComputeMessages(solved.errors ?? [], solved.warnings ?? []);\n\t\t// Decided before parsing: an older fork still ships a rejected solve's outputs, and the\n\t\t// geometry alone can be megabytes the client would only throw away.\n\t\tconst rejected = outcome.blocked || outcome.aborted;\n\n\t\tconst meshes =\n\t\t\topts.meshes && !rejected\n\t\t\t\t? await opts.meshes.extract(processor.response, { debug, signal })\n\t\t\t\t: [];\n\n\t\tconst resultOutputs: Record<string, unknown> = {};\n\t\tfor (const o of rejected ? [] : outputs) {\n\t\t\tconst byId = processor.getValue({ byId: o.id }, { parseValues: true });\n\t\t\tresultOutputs[o.id] =\n\t\t\t\tbyId ??\n\t\t\t\t(o.nickname\n\t\t\t\t\t? processor.getValue({ byName: o.nickname }, { parseValues: true })\n\t\t\t\t\t: undefined);\n\t\t}\n\n\t\tif (debug) {\n\t\t\tconst valuesBytes = JSON.stringify(values).length;\n\t\t\tconst serverTiming = parseServerTiming(res.headers.get('Server-Timing'));\n\t\t\tconst serverTotal = serverTiming.total ?? 0;\n\t\t\tconst networkMs = Math.max(0, ttfbMs - serverTotal);\n\t\t\tconst sizeMB = bodyChars / (1024 * 1024);\n\t\t\tconst size = sizeMB >= 1 ? `${sizeMB.toFixed(2)} MB` : `${(bodyChars / 1024).toFixed(0)} KB`;\n\t\t\tconst mbps = downloadMs > 0 ? (sizeMB / (downloadMs / 1000)).toFixed(1) : '∞';\n\t\t\tconst heapBytes = (performance as Performance & { memory?: { usedJSHeapSize: number } })\n\t\t\t\t.memory?.usedJSHeapSize;\n\t\t\tconst heap =\n\t\t\t\theapBytes !== undefined ? ` | heap=${(heapBytes / (1024 * 1024)).toFixed(0)} MB` : '';\n\t\t\tlog(\n\t\t\t\t`[Compute] req=${(payload.length / 1024).toFixed(0)}KB (values ${(valuesBytes / 1024).toFixed(0)}KB) | ` +\n\t\t\t\t\t`ttfb=${ttfbMs.toFixed(0)}ms (network≈${networkMs.toFixed(0)} + server ${serverTotal.toFixed(0)}) | ` +\n\t\t\t\t\t`download=${downloadMs.toFixed(0)}ms (${size} @ ${mbps} MB/s)${heap}`\n\t\t\t);\n\t\t\tif (serverTiming.selva_cache !== undefined) {\n\t\t\t\tconst cacheMsg = serverTiming.selva_cache\n\t\t\t\t\t? 'HIT — served without calling compute'\n\t\t\t\t\t: serverTiming.rhino_cache\n\t\t\t\t\t\t? \"HIT — Rhino.Compute's result cache, solved nothing\"\n\t\t\t\t\t\t: 'miss — solved on Rhino.Compute';\n\t\t\t\tlog(`[Compute] └─ cache: ${cacheMsg}`);\n\t\t\t}\n\t\t}\n\n\t\treturn finalizeResult({ outputs: resultOutputs, meshes, source: solved }, outcome);\n\t};\n}\n"],"mappings":"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"}
@@ -1,28 +1,10 @@
1
1
  import { SolveDiagnostic, SolveOutcome } from "@selvajs/schemas";
2
- /**
3
- * The solve contract — what a solve returns and what a caller supplies to run one.
4
- *
5
- * Lives in `shared/` because both halves of the package speak it: `client/` calls a `SolveFn` and
6
- * stores its `SolveResult`; `server/` produces the payload one is built from.
7
- */
8
2
  /**
9
3
  * `TMesh` is deliberately opaque and defaults to `unknown`: this package never inspects meshes, and
10
4
  * typing them would drag a renderer dependency into a package whose whole point is not having one.
11
5
  * The app that owns assembly (parse → `THREE.Object3D[]`) is the only place that knows the concrete
12
6
  * type, and it narrows by writing `SolveResult<THREE.Object3D>` at its own seam.
13
7
  */
14
- /**
15
- * Marker the Grasshopper Message component prepends to a blocking error.
16
- *
17
- * Rhino.Compute flattens runtime messages to plain strings with no component attribution, so
18
- * this is the only way that path can tell a deliberate block from any other solve error. The
19
- * local WebSocket path carries `isGate` structurally and does not need it — but the component
20
- * writes the marker unconditionally, because the same definition runs on both.
21
- *
22
- * Changing this string breaks blocking on deployed Selva for every already-published
23
- * definition, since the text is baked into the .gh file's solve output, not the plugin.
24
- */
25
- declare const SOLVE_BLOCKED_MARKER = "[Selva:blocked]";
26
8
  interface SolveResult<TMesh = unknown, TSource = unknown> {
27
9
  outputs: Record<string, unknown>;
28
10
  meshes?: TMesh[];
@@ -70,23 +52,22 @@ interface SolveResult<TMesh = unknown, TSource = unknown> {
70
52
  /** Implementations should listen to `signal` and abort/clean up when it fires. */
71
53
  type SolveFn<TMesh = unknown, TSource = unknown> = (values: Record<string, unknown>, signal: AbortSignal) => Promise<SolveResult<TMesh, TSource>>;
72
54
  /**
73
- * Normalizes a wire outcome: unknown levels read as remarks, text is trimmed, empty and exactly
74
- * repeated messages are dropped, and `source`/`isGate` are omitted rather than null or false, so
75
- * the same verdict decodes to the same object from either transport. Null when `wire` is not an
76
- * outcome at all; the caller then falls back.
55
+ * Normalizes a wire outcome with `decodeDiagnostic`, dropping exact repeats, so the same verdict
56
+ * decodes to the same object from either transport. Null when `wire` is not an outcome at all;
57
+ * the caller then falls back. A verdict without a diagnostic list still counts: reading a refusal
58
+ * as "no outcome" would let the solve through.
77
59
  */
78
60
  declare function decodeOutcome(wire: unknown): SolveOutcome | null;
79
- /** The legacy verdict, read from Compute's flattened `errors`/`warnings` and their markers. */
80
- declare function outcomeFromComputeMessages(errors: readonly string[], warnings: readonly string[]): SolveOutcome;
81
61
  /**
82
62
  * Builds the result a driver reports. A blocked or aborted outcome is "no result" whatever the
83
- * transport sent along: no outputs, and an empty mesh list so the viewer clears. `errors` and
84
- * `warnings` are derived from the diagnostics for hosts that only want text.
63
+ * transport sent along: no outputs, no `source` (it holds the withheld outputs), and an empty mesh
64
+ * list so the viewer clears. `errors` and `warnings` are derived from the diagnostics for hosts
65
+ * that only want text.
85
66
  */
86
67
  declare function finalizeResult<TMesh, TSource>(partial: {
87
68
  outputs: Record<string, unknown>;
88
69
  meshes?: TMesh[];
89
70
  source?: TSource;
90
71
  }, outcome: SolveOutcome): SolveResult<TMesh, TSource>;
91
- export { SOLVE_BLOCKED_MARKER as a, SolveResult as c, outcomeFromComputeMessages as i, decodeOutcome as n, SolveDiagnostic as o, finalizeResult as r, SolveFn as s, SolveOutcome as t };
92
- //# sourceMappingURL=outcome-DsNmx27P.d.ts.map
72
+ export { SolveFn as a, SolveDiagnostic as i, decodeOutcome as n, SolveResult as o, finalizeResult as r, SolveOutcome as t };
73
+ //# sourceMappingURL=outcome-Bk9mnMi0.d.ts.map