@selvajs/ui 5.0.0 → 6.0.0-beta.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.
Files changed (49) hide show
  1. package/dist/components/compute/ComputeApp.svelte +15 -7
  2. package/dist/components/compute/ComputeApp.svelte.d.ts +1 -1
  3. package/dist/components/primitives/alert/alert.svelte.d.ts +8 -8
  4. package/dist/components/primitives/badge/badge.svelte.d.ts +14 -14
  5. package/dist/components/primitives/button/button.svelte.d.ts +41 -41
  6. package/dist/components/primitives/button-group/button-group.svelte.d.ts +8 -8
  7. package/dist/components/primitives/field/field.svelte.d.ts +5 -5
  8. package/dist/components/primitives/textarea/textarea.svelte +1 -2
  9. package/dist/components/viewer/SceneManager.svelte +75 -160
  10. package/dist/components/viewer/SceneManager.svelte.d.ts +8 -3
  11. package/dist/components/viewer/Viewer.svelte +20 -3
  12. package/dist/compute/useSolveSession.svelte.d.ts +12 -0
  13. package/dist/compute/useSolveSession.svelte.js +76 -0
  14. package/dist/external/storage.d.ts +1 -15
  15. package/dist/external/storage.js +4 -54
  16. package/dist/index.d.ts +4 -3
  17. package/dist/index.js +7 -4
  18. package/dist/public.d.ts +4 -3
  19. package/dist/public.js +9 -5
  20. package/package.json +17 -15
  21. package/src/lib/components/compute/ComputeApp.svelte +15 -7
  22. package/src/lib/components/primitives/textarea/textarea.svelte +1 -2
  23. package/src/lib/components/viewer/SceneManager.svelte +75 -160
  24. package/src/lib/components/viewer/Viewer.svelte +20 -3
  25. package/src/lib/compute/mesh-policy-wiring.test.ts +72 -0
  26. package/src/lib/compute/useSolveSession.svelte.ts +83 -0
  27. package/src/lib/external/storage.ts +12 -64
  28. package/src/lib/index.ts +15 -5
  29. package/src/lib/public.ts +17 -6
  30. package/dist/compute/computeThrottle.svelte.d.ts +0 -24
  31. package/dist/compute/computeThrottle.svelte.js +0 -82
  32. package/dist/compute/createSolveSession.svelte.d.ts +0 -66
  33. package/dist/compute/createSolveSession.svelte.js +0 -159
  34. package/dist/compute/solve-session-core.d.ts +0 -54
  35. package/dist/compute/solve-session-core.js +0 -87
  36. package/dist/compute/solveMemo.d.ts +0 -22
  37. package/dist/compute/solveMemo.js +0 -124
  38. package/dist/types/solveFn.d.ts +0 -25
  39. package/dist/types/solveFn.js +0 -1
  40. package/src/lib/compute/computeThrottle.svelte.ts +0 -109
  41. package/src/lib/compute/computeThrottle.test.ts +0 -106
  42. package/src/lib/compute/createSolveSession.svelte.ts +0 -239
  43. package/src/lib/compute/createSolveSession.test.ts +0 -168
  44. package/src/lib/compute/solve-session-core.test.ts +0 -153
  45. package/src/lib/compute/solve-session-core.ts +0 -122
  46. package/src/lib/compute/solveMemo.test.ts +0 -220
  47. package/src/lib/compute/solveMemo.ts +0 -140
  48. package/src/lib/external/storage.test.ts +0 -99
  49. package/src/lib/types/solveFn.ts +0 -29
@@ -12,7 +12,7 @@
12
12
  type MeasureTool,
13
13
  type ViewPreset,
14
14
  type Grid
15
- } from '@selvajs/compute/visualization';
15
+ } from '@selvajs/visualization/render';
16
16
  import {
17
17
  Maximize,
18
18
  Minimize,
@@ -30,8 +30,10 @@
30
30
  Spline
31
31
  } from '@lucide/svelte';
32
32
  import { DropdownMenu } from 'bits-ui';
33
+ import { SvelteSet } from 'svelte/reactivity';
33
34
  import type * as THREE from 'three';
34
35
  import type { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
36
+ import { createSceneOutliner, type SceneOutliner } from '@selvajs/visualization/scene';
35
37
  import SceneManager from './SceneManager.svelte';
36
38
  import MeshMetadataDialog from './MeshMetadataDialog.svelte';
37
39
  import * as Resizable from '../primitives/resizable/index.js';
@@ -127,6 +129,15 @@
127
129
  let sceneVersion = $state(0);
128
130
  let hideButton = $state(false);
129
131
  let sceneManagerOpen = $state(false);
132
+
133
+ // The scene outliner lives here rather than inside <SceneManager> because that component is
134
+ // mounted only while its panel is open. Hidden objects must stay hidden when the panel is
135
+ // closed — and must be re-hidden after each solve, which nothing would do if the state
136
+ // unmounted with the panel.
137
+ const hiddenObjects = new SvelteSet<string>();
138
+ const selectedObjects = new SvelteSet<string>();
139
+ const collapsedLayers = new SvelteSet<string>();
140
+ let outliner: SceneOutliner | null = $state(null);
130
141
  let projection: CameraProjection = $state('perspective');
131
142
  let measureActive = $state(false);
132
143
  let gridVisible = $state(false);
@@ -195,6 +206,9 @@
195
206
 
196
207
  const init = initThree(canvas, opts);
197
208
  scene = init.scene;
209
+ outliner = createSceneOutliner(init.scene, {
210
+ sets: { hidden: hiddenObjects, selected: selectedObjects, collapsed: collapsedLayers }
211
+ });
198
212
  camera = init.camera;
199
213
  controls = init.controls;
200
214
  cameraController = init.cameraController;
@@ -272,6 +286,9 @@
272
286
  if (edgesVisible) applyEdges?.(scene!);
273
287
  // Rescale the grid to the new content's extent so cells and fade match the part size.
274
288
  updateGridScale?.();
289
+ // The rebuild above also un-hid everything the user had hidden. Re-hide it: the outliner
290
+ // keys that state on Grasshopper identity, not on the instances just discarded.
291
+ outliner?.applyTo();
275
292
  sceneVersion++;
276
293
  // New solve content — repaint now rather than on the render loop's safety interval.
277
294
  invalidate?.();
@@ -546,10 +563,10 @@
546
563
  </Resizable.Pane>
547
564
 
548
565
  <!-- Scene Manager Pane -->
549
- {#if sceneManagerOpen && scene}
566
+ {#if sceneManagerOpen && scene && outliner}
550
567
  <Resizable.Handle withHandle />
551
568
  <Resizable.Pane defaultSize={15} minSize={8} maxSize={30}>
552
- <SceneManager {scene} {sceneVersion} />
569
+ <SceneManager {outliner} {sceneVersion} />
553
570
  </Resizable.Pane>
554
571
  {/if}
555
572
  </Resizable.PaneGroup>
@@ -0,0 +1,12 @@
1
+ import type { SolveSession, SolveSessionArgs } from '@selvajs/solve/client';
2
+ /**
3
+ * Wraps a Solve Session so its state reads reactively inside Svelte components.
4
+ *
5
+ * Returns the same `SolveSession` surface — every method delegates untouched, and every
6
+ * getter additionally depends on the version counter. Callers use it exactly like the
7
+ * session it wraps.
8
+ *
9
+ * Must be called during component initialization (it uses `$effect` to manage the
10
+ * subscription, so teardown follows the owning component's lifecycle).
11
+ */
12
+ export declare function useSolveSession(args: SolveSessionArgs): SolveSession;
@@ -0,0 +1,76 @@
1
+ // The Svelte binding for a Solve Session.
2
+ //
3
+ // The session itself lives in `@selvajs/solve/client` and is framework-free: it
4
+ // exposes plain getters plus a `subscribe()` seam. That makes it usable headless, but a
5
+ // plain getter read inside Svelte markup is NOT reactive — nothing tells the component to
6
+ // re-run. This adapter closes that gap: it subscribes once, bumps a `$state` version
7
+ // counter on every notification, and reads that counter inside each getter so any
8
+ // component touching one re-runs when the session changes.
9
+ //
10
+ // The counter (rather than mirroring each field into its own `$state`) keeps this a pure
11
+ // republish: no field list to keep in sync as the session grows, and no risk of a mirrored
12
+ // copy drifting from the source of truth.
13
+ import { createSolveSession } from '@selvajs/solve/client';
14
+ /**
15
+ * Wraps a Solve Session so its state reads reactively inside Svelte components.
16
+ *
17
+ * Returns the same `SolveSession` surface — every method delegates untouched, and every
18
+ * getter additionally depends on the version counter. Callers use it exactly like the
19
+ * session it wraps.
20
+ *
21
+ * Must be called during component initialization (it uses `$effect` to manage the
22
+ * subscription, so teardown follows the owning component's lifecycle).
23
+ */
24
+ export function useSolveSession(args) {
25
+ const session = createSolveSession(args);
26
+ // Bumped on every session notification. Reading it inside a getter is what registers
27
+ // the dependency; the value itself is never meaningful.
28
+ let version = $state(0);
29
+ $effect(() => {
30
+ // Re-read on mount and unsubscribe on teardown. The session outlives no component
31
+ // here — it is created alongside this adapter — so dropping the subscription is the
32
+ // whole cleanup.
33
+ return session.subscribe(() => {
34
+ version += 1;
35
+ });
36
+ });
37
+ /** Registers the reactive dependency, then returns the live value. */
38
+ function track(read) {
39
+ void version;
40
+ return read();
41
+ }
42
+ return {
43
+ get values() {
44
+ return track(() => session.values);
45
+ },
46
+ get error() {
47
+ return track(() => session.error);
48
+ },
49
+ get computeErrors() {
50
+ return track(() => session.computeErrors);
51
+ },
52
+ get computeWarnings() {
53
+ return track(() => session.computeWarnings);
54
+ },
55
+ get meshes() {
56
+ return track(() => session.meshes);
57
+ },
58
+ get hasPendingChanges() {
59
+ return track(() => session.hasPendingChanges);
60
+ },
61
+ get hasNeverSolved() {
62
+ return track(() => session.hasNeverSolved);
63
+ },
64
+ get isSolving() {
65
+ return track(() => session.isSolving);
66
+ },
67
+ setValue: (id, value, forceSolve) => session.setValue(id, value, forceSolve),
68
+ solve: () => session.solve(),
69
+ loadValues: (incoming) => session.loadValues(incoming),
70
+ rebuild: (schema, scopeKey) => session.rebuild(schema, scopeKey),
71
+ report: (result) => session.report(result),
72
+ reportError: (message) => session.reportError(message),
73
+ subscribe: (listener) => session.subscribe(listener),
74
+ notify: () => session.notify()
75
+ };
76
+ }
@@ -1,15 +1 @@
1
- import type { UISchema } from '@selvajs/schemas';
2
- export interface ExternalValueRef {
3
- scopeKey: string;
4
- inputId: string;
5
- }
6
- export declare function writeExternalValue(args: ExternalValueRef & {
7
- value: unknown;
8
- }): void;
9
- export declare function readExternalValue(ref: ExternalValueRef): unknown | undefined;
10
- export declare function clearExternalValue(ref: ExternalValueRef): void;
11
- export interface ExternalInput {
12
- paramId: string;
13
- displayName: string;
14
- }
15
- export declare function getExternalInputs(schema: UISchema): ExternalInput[];
1
+ export { writeExternalValue, readExternalValue, clearExternalValue, getExternalInputs, type ExternalValueRef, type ExternalInput } from '@selvajs/solve/client';
@@ -1,54 +1,4 @@
1
- // Client-supplied input transit storage.
2
- //
3
- // When an input has source.kind === 'client', a producer route writes the produced
4
- // value here, and the solver route reads it back. Scoped per (scopeKey, inputId) so
5
- // values for one solver/input don't bleed into another. The scope key is whatever
6
- // uniquely identifies the solver context — sessionId in plugin-ui/preview,
7
- // definition guid in selva/library, etc.
8
- //
9
- // inputId is the Grasshopper parameter instance GUID (LayoutItem.paramId / SchemaInput.id).
10
- import { getInputItems } from '@selvajs/schemas';
11
- const STORAGE_PREFIX = 'external';
12
- function makeKey(scopeKey, inputId) {
13
- return `${STORAGE_PREFIX}:${scopeKey}:${inputId}`;
14
- }
15
- export function writeExternalValue(args) {
16
- const { scopeKey, inputId, value } = args;
17
- if (!scopeKey || !inputId)
18
- return;
19
- if (typeof sessionStorage === 'undefined')
20
- return;
21
- sessionStorage.setItem(makeKey(scopeKey, inputId), JSON.stringify(value));
22
- }
23
- export function readExternalValue(ref) {
24
- const { scopeKey, inputId } = ref;
25
- if (!scopeKey || !inputId)
26
- return undefined;
27
- if (typeof sessionStorage === 'undefined')
28
- return undefined;
29
- const raw = sessionStorage.getItem(makeKey(scopeKey, inputId));
30
- if (raw === null)
31
- return undefined;
32
- try {
33
- return JSON.parse(raw);
34
- }
35
- catch {
36
- return undefined;
37
- }
38
- }
39
- export function clearExternalValue(ref) {
40
- const { scopeKey, inputId } = ref;
41
- if (!scopeKey || !inputId)
42
- return;
43
- if (typeof sessionStorage === 'undefined')
44
- return;
45
- sessionStorage.removeItem(makeKey(scopeKey, inputId));
46
- }
47
- export function getExternalInputs(schema) {
48
- return getInputItems(schema)
49
- .filter((item) => item.source?.kind === 'client')
50
- .map((item) => ({
51
- paramId: item.paramId,
52
- displayName: item.displayName ?? item.paramId
53
- }));
54
- }
1
+ // Moved to `@selvajs/solve/client`, where the Solve Session that hydrates from it
2
+ // now lives. Re-exported here to keep the published `@selvajs/ui/external` sub-path — and
3
+ // the pre-step producer routes that import it working unchanged.
4
+ export { writeExternalValue, readExternalValue, clearExternalValue, getExternalInputs } from '@selvajs/solve/client';
package/dist/index.d.ts CHANGED
@@ -9,13 +9,14 @@ export * from './schema/defaults';
9
9
  export * from './schema/dynamic-value-list';
10
10
  export * from './schema/traversal';
11
11
  export * from './compute/solving.svelte';
12
- export { createSolveSession, createRequestResponseDriver, type SolveSession, type SolveSessionArgs, type SolveDriver, type SolveReporter } from './compute/createSolveSession.svelte';
13
- export * from './external/storage';
12
+ export { useSolveSession } from './compute/useSolveSession.svelte';
13
+ export { createSolveSession, createRequestResponseDriver, type SolveSession, type SolveSessionArgs, type SolveDriver, type SolveReporter } from '@selvajs/solve/client';
14
+ export { writeExternalValue, readExternalValue, clearExternalValue, getExternalInputs, type ExternalValueRef, type ExternalInput } from '@selvajs/solve/client';
14
15
  export * from './contexts/footerContext.svelte';
15
16
  export * from './contexts/clientSlotContext.svelte';
16
17
  export * from './composables/useFooterItem.svelte';
17
18
  export * from './utils';
18
19
  export { randomId } from './utils/randomId';
19
20
  export type { ActionButton } from './types/actionButton';
20
- export type { SolveFn, SolveResult } from './types/solveFn';
21
+ export type { SolveFn, SolveResult } from '@selvajs/solve/shared';
21
22
  export { DEFAULT_PRESET_LABELS, type PresetLabels } from './types/presetLabels';
package/dist/index.js CHANGED
@@ -15,12 +15,15 @@ export * from './schema/defaults';
15
15
  export * from './schema/dynamic-value-list';
16
16
  export * from './schema/traversal';
17
17
  export * from './compute/solving.svelte';
18
- // Solve Session seam (transport-agnostic value/lifecycle state machine + its driver
19
- // interface). Exported so transports outside this package e.g. plugin-ui's WebSocket
18
+ // Solve Session seam. The session itself now lives in `@selvajs/solve/client`
19
+ // (framework-free); `useSolveSession` is this package's Svelte binding, which republishes
20
+ // the session's subscribe() notifications as rune state so its getters read reactively in
21
+ // markup. Re-exported so transports outside this package — e.g. plugin-ui's WebSocket
20
22
  // driver — can satisfy SolveDriver and drive a session. See CONTEXT.md.
21
- export { createSolveSession, createRequestResponseDriver } from './compute/createSolveSession.svelte';
23
+ export { useSolveSession } from './compute/useSolveSession.svelte';
24
+ export { createSolveSession, createRequestResponseDriver } from '@selvajs/solve/client';
22
25
  // External-input transit storage (used by routes that wire pre-step producers)
23
- export * from './external/storage';
26
+ export { writeExternalValue, readExternalValue, clearExternalValue, getExternalInputs } from '@selvajs/solve/client';
24
27
  // Contexts & Composables
25
28
  export * from './contexts/footerContext.svelte';
26
29
  export * from './contexts/clientSlotContext.svelte';
package/dist/public.d.ts CHANGED
@@ -5,12 +5,13 @@ export type { Locale, ViewerMessages } from './i18n/messages';
5
5
  export { VIEWER_MESSAGES, DEFAULT_LOCALE, messagesFor } from './i18n/messages';
6
6
  export { setLocaleContext, getLocaleContext, type LocaleContext } from './i18n/localeContext.svelte';
7
7
  export { default as ErrorScreen } from './components/ErrorScreen.svelte';
8
- export { createSolveSession, createRequestResponseDriver, type SolveSession, type SolveSessionArgs, type SolveDriver, type SolveReporter } from './compute/createSolveSession.svelte';
8
+ export { useSolveSession } from './compute/useSolveSession.svelte';
9
+ export { createSolveSession, createRequestResponseDriver, type SolveSession, type SolveSessionArgs, type SolveDriver, type SolveReporter } from '@selvajs/solve/client';
9
10
  export type { ClientSlotArgs, ClientSlot } from './contexts/clientSlotContext.svelte';
10
- export * from './external/storage';
11
+ export { writeExternalValue, readExternalValue, clearExternalValue, getExternalInputs, type ExternalValueRef, type ExternalInput } from '@selvajs/solve/client';
11
12
  export * from './schema/defaults';
12
13
  export * from './schema/traversal';
13
14
  export * from './schema/dynamic-value-list';
14
15
  export type { ActionButton } from './types/actionButton';
15
- export type { SolveFn, SolveResult } from './types/solveFn';
16
+ export type { SolveFn, SolveResult } from '@selvajs/solve/shared';
16
17
  export { DEFAULT_PRESET_LABELS, type PresetLabels } from './types/presetLabels';
package/dist/public.js CHANGED
@@ -8,7 +8,8 @@
8
8
  // Scope: the compute-app SDK — everything an external host app needs to embed a
9
9
  // Grasshopper-driven app (ComputeApp), drive solves, and wire pre-step
10
10
  // producers. Verified against real external host apps: they import
11
- // ComputeApp + its types, the solve seam, and external/storage. Nothing else.
11
+ // ComputeApp + its types, the solve seam, and the external-input storage
12
+ // helpers. Nothing else.
12
13
  //
13
14
  // Deliberately NOT public: design-system primitives (Button, Card, Dialog, …),
14
15
  // page-chrome layout (AppShell, SideNav, …), toast/Toaster, ThemeSwitcher,
@@ -30,11 +31,14 @@ export { setLocaleContext, getLocaleContext } from './i18n/localeContext.svelte'
30
31
  // Full-screen states a host app renders
31
32
  export { default as ErrorScreen } from './components/ErrorScreen.svelte';
32
33
  // Solve Session seam (transport-agnostic value/lifecycle state machine + its
33
- // driver interface). Exported so transports outside this package can satisfy
34
- // SolveDriver and drive a session. See CONTEXT.md.
35
- export { createSolveSession, createRequestResponseDriver } from './compute/createSolveSession.svelte';
34
+ // driver interface). The session lives in `@selvajs/solve/client` and is
35
+ // framework-free; `useSolveSession` is the Svelte binding that makes its getters
36
+ // read reactively inside components. A host embedding <ComputeApp> needs neither —
37
+ // both are re-exported for hosts driving a session themselves. See CONTEXT.md.
38
+ export { useSolveSession } from './compute/useSolveSession.svelte';
39
+ export { createSolveSession, createRequestResponseDriver } from '@selvajs/solve/client';
36
40
  // Pre-step producer transit storage (host apps wire producers via these).
37
- export * from './external/storage';
41
+ export { writeExternalValue, readExternalValue, clearExternalValue, getExternalInputs } from '@selvajs/solve/client';
38
42
  // Schema utilities a ComputeApp host reasonably needs to read/shape values.
39
43
  export * from './schema/defaults';
40
44
  export * from './schema/traversal';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@selvajs/ui",
3
- "version": "5.0.0",
3
+ "version": "6.0.0-beta.0",
4
4
  "description": "Shared UI components and utilities for Selva applications",
5
5
  "license": "MIT",
6
6
  "author": "VektorNode",
@@ -58,10 +58,12 @@
58
58
  "@sveltejs/kit": "^2",
59
59
  "bits-ui": "^2.18.0",
60
60
  "svelte": "^5",
61
- "tailwind-variants": "^3.2.2",
62
- "three": "^0.184.0",
63
- "@selvajs/compute": "^3.1.0",
64
- "@selvajs/schemas": "^4.7.0"
61
+ "tailwind-variants": "^3.3.0",
62
+ "three": "^0.185.1",
63
+ "@selvajs/compute": "^4.0.0-beta.0",
64
+ "@selvajs/schemas": "^4.7.0",
65
+ "@selvajs/solve": "^0.2.0-beta.0",
66
+ "@selvajs/visualization": "^1.0.0-beta.0"
65
67
  },
66
68
  "peerDependenciesMeta": {
67
69
  "three": {
@@ -69,9 +71,9 @@
69
71
  }
70
72
  },
71
73
  "dependencies": {
72
- "@floating-ui/dom": "^1.7.6",
74
+ "@floating-ui/dom": "^1.8.0",
73
75
  "@iconify/svelte": "^5.2.1",
74
- "@lucide/svelte": "^1.11.0",
76
+ "@lucide/svelte": "^1.27.0",
75
77
  "clsx": "^2.1.1",
76
78
  "mode-watcher": "^1.1.0",
77
79
  "paneforge": "^1.0.2",
@@ -81,17 +83,17 @@
81
83
  },
82
84
  "devDependencies": {
83
85
  "@internationalized/date": "^3.12.1",
84
- "@sveltejs/kit": "2.69.1",
86
+ "@sveltejs/kit": "2.70.1",
85
87
  "@sveltejs/vite-plugin-svelte": "^7.2.0",
86
- "@types/three": "^0.184.0",
88
+ "@types/three": "^0.185.1",
87
89
  "bits-ui": "^2.18.0",
88
- "rhino3dm": "8.17.0",
90
+ "rhino3dm": "8.32.1",
89
91
  "rimraf": "^6.0.1",
90
- "svelte": "5.56.4",
91
- "tailwind-variants": "^3.2.2",
92
- "vitest": "^3.2.7",
93
- "@selvajs/schemas": "4.7.0",
94
- "@selvajs/config": "0.0.3"
92
+ "svelte": "5.56.8",
93
+ "tailwind-variants": "^3.3.0",
94
+ "vitest": "^4.1.10",
95
+ "@selvajs/config": "0.0.3",
96
+ "@selvajs/schemas": "4.7.0"
95
97
  },
96
98
  "scripts": {
97
99
  "predev": "node ../../scripts/sync-shared-assets.js",
@@ -3,13 +3,12 @@
3
3
  import { page } from '$app/state';
4
4
  import type { UISchema, ParameterPreset } from '@selvajs/schemas';
5
5
  import type { ActionButton } from '../../types/actionButton';
6
- import type { SolveFn } from '../../types/solveFn';
6
+ import type { SolveFn } from '@selvajs/solve/shared';
7
7
  import type { PresetLabels } from '../../types/presetLabels';
8
8
  import { createSolvingIndicator } from '../../compute/solving.svelte';
9
- import {
10
- createSolveSession,
11
- createRequestResponseDriver
12
- } from '../../compute/createSolveSession.svelte';
9
+ import { createRequestResponseDriver } from '@selvajs/solve/client';
10
+ import { meshPolicy } from '@selvajs/visualization/parse';
11
+ import { useSolveSession } from '../../compute/useSolveSession.svelte';
13
12
  import { useFooterItem } from '../../composables/useFooterItem.svelte';
14
13
  import { hexToOklch } from '../../utils/color';
15
14
  import AppShell from '../layout/AppShell.svelte';
@@ -115,10 +114,19 @@
115
114
  // reads the reporter lazily so it can capture the session it's wired into.
116
115
  // svelte-ignore state_referenced_locally
117
116
  const driver = createRequestResponseDriver(onSolve, () => session, {
118
- timeout: solveTimeoutMs
117
+ timeout: solveTimeoutMs,
118
+ // The driver's result memo caches whole solve results, meshes included — and the viewer
119
+ // disposes what it renders on the next scene update. `@selvajs/solve` keeps meshes opaque,
120
+ // so the three.js clone/dispose rules are injected from the renderer that owns them
121
+ // (audit C1). Without this a memo hit serves an already-disposed mesh.
122
+ meshPolicy,
123
+ // `session.isSolving` forwards to the driver, which the session can't observe on its
124
+ // own — republish so the spinner and disabled states track it. Deferred into a
125
+ // callback, so it reads `session` after initialization rather than during it.
126
+ onChange: () => session.notify()
119
127
  });
120
128
  // svelte-ignore state_referenced_locally
121
- const session = createSolveSession({
129
+ const session = useSolveSession({
122
130
  schema,
123
131
  scopeKey: externalScopeKey || definitionKey || schema?.id || '',
124
132
  driver
@@ -19,5 +19,4 @@
19
19
  className
20
20
  )}
21
21
  bind:value
22
- {...restProps}
23
- ></textarea>
22
+ {...restProps}></textarea>