@selvajs/ui 6.0.0-beta.8 → 6.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.
Files changed (94) hide show
  1. package/README.md +5 -7
  2. package/dist/components/compute/AppLayout.svelte +2 -8
  3. package/dist/components/compute/AppLayout.svelte.d.ts +0 -2
  4. package/dist/components/compute/ComputeApp.svelte +25 -43
  5. package/dist/components/compute/ComputeApp.svelte.d.ts +8 -24
  6. package/dist/components/compute/ParameterPresetManager.svelte +4 -9
  7. package/dist/components/compute/ParameterPresetManager.svelte.d.ts +1 -1
  8. package/dist/components/layout/AppShell.svelte +5 -13
  9. package/dist/components/layout/AppShell.svelte.d.ts +1 -2
  10. package/dist/components/layout/EmptyState.svelte +2 -3
  11. package/dist/components/layout/PageFooter.svelte +0 -4
  12. package/dist/components/layout/index.js +0 -1
  13. package/dist/components/preview/Group.svelte +2 -5
  14. package/dist/components/preview/ImageOutput.svelte +5 -4
  15. package/dist/components/preview/InputControl.svelte +27 -28
  16. package/dist/components/preview/InputControl.svelte.d.ts +3 -3
  17. package/dist/components/preview/OutputDisplay.svelte +3 -6
  18. package/dist/components/preview/TabLayout.svelte +3 -3
  19. package/dist/components/preview/inputs/FileInput.svelte +9 -12
  20. package/dist/components/primitives/field/field-error.svelte +1 -3
  21. package/dist/components/primitives/index.js +0 -1
  22. package/dist/components/viewer/SceneManager.svelte +9 -20
  23. package/dist/components/viewer/SceneManager.svelte.d.ts +3 -3
  24. package/dist/components/viewer/Viewer.svelte +36 -61
  25. package/dist/components/viewer/Viewer.svelte.d.ts +6 -19
  26. package/dist/composables/useFooterItem.svelte.d.ts +3 -4
  27. package/dist/composables/useFooterItem.svelte.js +3 -3
  28. package/dist/compute/solving.svelte.d.ts +6 -6
  29. package/dist/compute/solving.svelte.js +6 -6
  30. package/dist/compute/useSolveSession.svelte.d.ts +5 -7
  31. package/dist/compute/useSolveSession.svelte.js +11 -24
  32. package/dist/constants.js +4 -4
  33. package/dist/contexts/clientSlotContext.svelte.d.ts +3 -5
  34. package/dist/contexts/footerContext.svelte.d.ts +6 -8
  35. package/dist/external/storage.js +2 -3
  36. package/dist/i18n/localeContext.svelte.d.ts +5 -7
  37. package/dist/i18n/localeContext.svelte.js +8 -15
  38. package/dist/i18n/messages.d.ts +0 -2
  39. package/dist/i18n/messages.js +3 -8
  40. package/dist/index.js +7 -13
  41. package/dist/public.js +14 -30
  42. package/dist/schema/defaults.js +2 -3
  43. package/dist/schema/dynamic-value-list.js +13 -15
  44. package/dist/schema/param-exporter.d.ts +7 -14
  45. package/dist/schema/param-exporter.js +6 -12
  46. package/dist/schema/traversal.js +3 -4
  47. package/dist/schema/visibility-rules.d.ts +4 -8
  48. package/dist/schema/visibility-rules.js +4 -8
  49. package/dist/types/actionButton.d.ts +1 -25
  50. package/dist/types/presetLabels.d.ts +2 -3
  51. package/dist/utils/color.js +6 -9
  52. package/dist/utils/debounce.d.ts +1 -11
  53. package/dist/utils/debounce.js +1 -11
  54. package/dist/utils/loadScript.d.ts +2 -7
  55. package/dist/utils/loadScript.js +2 -7
  56. package/package.json +16 -16
  57. package/src/lib/components/compute/AppLayout.svelte +2 -8
  58. package/src/lib/components/compute/ComputeApp.svelte +25 -43
  59. package/src/lib/components/compute/ParameterPresetManager.svelte +4 -9
  60. package/src/lib/components/layout/AppShell.svelte +5 -13
  61. package/src/lib/components/layout/EmptyState.svelte +2 -3
  62. package/src/lib/components/layout/PageFooter.svelte +0 -4
  63. package/src/lib/components/layout/index.ts +0 -1
  64. package/src/lib/components/preview/Group.svelte +2 -5
  65. package/src/lib/components/preview/ImageOutput.svelte +5 -4
  66. package/src/lib/components/preview/InputControl.svelte +27 -28
  67. package/src/lib/components/preview/OutputDisplay.svelte +3 -6
  68. package/src/lib/components/preview/TabLayout.svelte +3 -3
  69. package/src/lib/components/preview/inputs/FileInput.svelte +9 -12
  70. package/src/lib/components/primitives/field/field-error.svelte +1 -3
  71. package/src/lib/components/primitives/index.ts +0 -2
  72. package/src/lib/components/viewer/SceneManager.svelte +9 -20
  73. package/src/lib/components/viewer/Viewer.svelte +36 -61
  74. package/src/lib/composables/useFooterItem.svelte.ts +3 -4
  75. package/src/lib/compute/solving.svelte.ts +6 -6
  76. package/src/lib/compute/useSolveSession.svelte.ts +11 -24
  77. package/src/lib/constants.ts +4 -4
  78. package/src/lib/contexts/clientSlotContext.svelte.ts +7 -12
  79. package/src/lib/contexts/footerContext.svelte.ts +6 -8
  80. package/src/lib/external/storage.ts +2 -3
  81. package/src/lib/i18n/localeContext.svelte.ts +8 -17
  82. package/src/lib/i18n/messages.ts +3 -10
  83. package/src/lib/index.ts +8 -13
  84. package/src/lib/public.ts +22 -40
  85. package/src/lib/schema/defaults.ts +2 -3
  86. package/src/lib/schema/dynamic-value-list.ts +13 -15
  87. package/src/lib/schema/param-exporter.ts +9 -16
  88. package/src/lib/schema/traversal.ts +3 -4
  89. package/src/lib/schema/visibility-rules.ts +4 -8
  90. package/src/lib/types/actionButton.ts +1 -25
  91. package/src/lib/types/presetLabels.ts +2 -3
  92. package/src/lib/utils/color.ts +6 -9
  93. package/src/lib/utils/debounce.ts +1 -11
  94. package/src/lib/utils/loadScript.ts +2 -7
@@ -1,12 +1,10 @@
1
1
  import type { SolveSession, SolveSessionArgs } from '@selvajs/solve/client';
2
2
  /**
3
- * Wraps a Solve Session so its state reads reactively inside Svelte components.
3
+ * Wraps a Solve Session so its state reads reactively inside Svelte components. Use this in
4
+ * a component, never `createSolveSession` directly: the raw session's getters return correct
5
+ * values but never re-render.
4
6
  *
5
- * Returns the same `SolveSession` surfaceevery 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).
7
+ * Must be called during component initialization the subscription is managed by `$effect`,
8
+ * so teardown follows the owning component's lifecycle.
11
9
  */
12
10
  export declare function useSolveSession(args: SolveSessionArgs): SolveSession;
@@ -1,35 +1,22 @@
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.
1
+ // A version counter, rather than mirroring each field into its own `$state`, keeps this a
2
+ // pure republish: no field list to keep in sync as the session grows, and no mirrored copy
3
+ // that can drift from the source of truth.
13
4
  import { createSolveSession } from '@selvajs/solve/client';
14
5
  /**
15
- * Wraps a Solve Session so its state reads reactively inside Svelte components.
6
+ * Wraps a Solve Session so its state reads reactively inside Svelte components. Use this in
7
+ * a component, never `createSolveSession` directly: the raw session's getters return correct
8
+ * values but never re-render.
16
9
  *
17
- * Returns the same `SolveSession` surfaceevery 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).
10
+ * Must be called during component initialization the subscription is managed by `$effect`,
11
+ * so teardown follows the owning component's lifecycle.
23
12
  */
24
13
  export function useSolveSession(args) {
25
14
  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.
15
+ // Reading this inside a getter is what registers the dependency; the value never matters.
28
16
  let version = $state(0);
29
17
  $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.
18
+ // The session is created alongside this adapter and outlives no other component, so
19
+ // dropping the subscription is the whole cleanup.
33
20
  return session.subscribe(() => {
34
21
  version += 1;
35
22
  });
package/dist/constants.js CHANGED
@@ -1,10 +1,10 @@
1
1
  export const APP_DEFAULTS = {
2
2
  // File upload limits
3
3
  // TEMP (dev): raised 150 MB → 300 MB so large dev file-widget inputs aren't
4
- // blocked client-side. The server request cap (COMPUTE_REQUEST_MAX_BYTES) was
5
- // bumped to 300 MB to match, but base64 inflates a raw file by ~4/3, so a
6
- // full 300 MB upload still won't fit the 300 MB request body. Revert to 150
7
- // before release.
4
+ // blocked client-side. The server request cap (COMPUTE_REQUEST_MAX_BYTES)
5
+ // still defaults to 210 MB, and base64 inflates a raw file by ~4/3, so
6
+ // anything over ~157 MB is rejected with a 413 after the client accepts it.
7
+ // Revert to 150 before release.
8
8
  FILE_UPLOAD: {
9
9
  MAX_SIZE_MB: 300,
10
10
  MAX_SIZE_BYTES: 300 * 1024 * 1024
@@ -4,13 +4,11 @@ export interface ClientSlotArgs {
4
4
  /** Grasshopper parameter instance GUID (LayoutItem.paramId / SchemaInput.id). */
5
5
  inputId: string;
6
6
  displayName: string;
7
- /** The current value held for this input (e.g. the prefilled JSON), if any. */
8
7
  value: unknown;
9
8
  /**
10
- * Commit a value for this input. Identical channel to a built-in widget's change
11
- * the value lands in the solve session and is sent to Compute on the next solve.
12
- * `forceSolve` requests a solve even in manual-solve mode (system reconciliation).
13
- * Lets a slot be an interactive control (a custom picker), not just a display cell.
9
+ * Commit a value for this input the same channel a built-in widget's change uses, so a
10
+ * slot can be an interactive control rather than just a display cell. `forceSolve`
11
+ * requests a solve even in manual-solve mode.
14
12
  */
15
13
  onValueChange: (value: SupportedTypes, forceSolve?: boolean) => void;
16
14
  }
@@ -1,19 +1,17 @@
1
1
  import { type Component } from 'svelte';
2
2
  import { SvelteMap } from 'svelte/reactivity';
3
3
  /**
4
- * A footer item registered by a descendant of the root layout. `component` + `position`
5
- * are fixed at registration; the renderer re-invokes `getProps` reactively, so return
6
- * reactive state from it (e.g. `() => ({ status: myState.status })`) to keep the footer
7
- * in sync.
8
- *
9
- * Generic over the component's props `P` so registration is type-checked at the call
10
- * site. The store holds a heterogeneous mix of items, so its map widens `P` to
4
+ * Generic over the component's props `P` so registration is type-checked at the call site.
5
+ * The store holds a heterogeneous mix of items, so its map widens `P` to
11
6
  * `Record<string, unknown>` at the boundary.
12
7
  */
13
8
  export interface FooterItem<P extends Record<string, unknown> = Record<string, unknown>> {
14
9
  id: string;
15
10
  component: Component<P>;
16
- /** Called on every render — return reactive state to keep footer in sync. */
11
+ /**
12
+ * Re-invoked on every render. Return reactive state — `() => ({ status: s.status })` —
13
+ * or the footer never updates.
14
+ */
17
15
  getProps: () => P;
18
16
  position: 'left' | 'right';
19
17
  priority: number;
@@ -1,4 +1,3 @@
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.
1
+ // Moved to @selvajs/solve/client, beside the Solve Session that hydrates from it.
2
+ // Re-exported to keep the published `@selvajs/ui/external` sub-path working unchanged.
4
3
  export { writeExternalValue, readExternalValue, clearExternalValue, getExternalInputs } from '@selvajs/solve/client';
@@ -1,18 +1,16 @@
1
1
  import { type Locale, type ViewerMessages } from './messages';
2
2
  export interface LocaleContext {
3
- /** Current locale. Called reactively — return reactive state to enable live switching. */
4
3
  readonly locale: Locale;
5
- /** Resolved message catalog for the current locale. */
6
4
  readonly messages: ViewerMessages;
7
5
  }
8
6
  /**
9
- * Provide the locale to descendants. Pass a getter so a reactive source (a
10
- * `$state`, a store, the app's Paraglide locale) keeps consumers in sync.
7
+ * Provide the locale to descendants. Pass a getter, not a value: it is re-read on every
8
+ * consumer render, so a reactive source (a `$state`, the app's Paraglide locale) switches
9
+ * the language live.
11
10
  */
12
11
  export declare function setLocaleContext(getLocale: () => Locale | undefined): void;
13
12
  /**
14
- * Read the locale context. Falls back to an English-only context when no
15
- * provider exists (e.g. a primitive used in isolation), so consumers never
16
- * need a null check.
13
+ * Falls back to an English-only context when no provider exists (a primitive used in
14
+ * isolation), so consumers never need a null check.
17
15
  */
18
16
  export declare function getLocaleContext(): LocaleContext;
@@ -1,13 +1,7 @@
1
1
  import { getContext, setContext } from 'svelte';
2
2
  import { messagesFor, DEFAULT_LOCALE } from './messages';
3
- // ============================================================================
4
- // Viewer locale context
5
- // ============================================================================
6
- //
7
- // Carries the current UI locale down to the viewer and its panels without
8
- // threading a `lang` prop through every layer. The value is a getter so the
9
- // host can back it with reactive state — flip the language and the viewer
10
- // re-renders live.
3
+ // Carries the UI locale down to the viewer and its panels without threading a
4
+ // `lang` prop through every layer.
11
5
  //
12
6
  // Resolution order for any consuming component:
13
7
  // explicit `lang` prop → nearest locale context → English default
@@ -15,12 +9,12 @@ import { messagesFor, DEFAULT_LOCALE } from './messages';
15
9
  // Two ways to provide it:
16
10
  // - Standalone: <Viewer lang="de" /> — Viewer provides the context itself.
17
11
  // - In an app: call setLocaleContext(() => app.locale) once at the root; the
18
- // viewer (and anything else) reads it. selva later wires its Paraglide
19
- // locale in here.
12
+ // viewer (and anything else) reads it.
20
13
  const LOCALE_CONTEXT_KEY = Symbol('viewer-locale-context');
21
14
  /**
22
- * Provide the locale to descendants. Pass a getter so a reactive source (a
23
- * `$state`, a store, the app's Paraglide locale) keeps consumers in sync.
15
+ * Provide the locale to descendants. Pass a getter, not a value: it is re-read on every
16
+ * consumer render, so a reactive source (a `$state`, the app's Paraglide locale) switches
17
+ * the language live.
24
18
  */
25
19
  export function setLocaleContext(getLocale) {
26
20
  const ctx = {
@@ -34,9 +28,8 @@ export function setLocaleContext(getLocale) {
34
28
  setContext(LOCALE_CONTEXT_KEY, ctx);
35
29
  }
36
30
  /**
37
- * Read the locale context. Falls back to an English-only context when no
38
- * provider exists (e.g. a primitive used in isolation), so consumers never
39
- * need a null check.
31
+ * Falls back to an English-only context when no provider exists (a primitive used in
32
+ * isolation), so consumers never need a null check.
40
33
  */
41
34
  export function getLocaleContext() {
42
35
  return (getContext(LOCALE_CONTEXT_KEY) ?? {
@@ -7,9 +7,7 @@ export interface ViewerMessages {
7
7
  views: string;
8
8
  measure: string;
9
9
  grid: string;
10
- /** "Display" submenu label — groups render style + edges. */
11
10
  display: string;
12
- /** Edges show/hide toggle label. */
13
11
  edges: string;
14
12
  sceneManager: string;
15
13
  screenshot: string;
@@ -2,14 +2,9 @@
2
2
  // Viewer message catalog (library-owned strings)
3
3
  // ============================================================================
4
4
  //
5
- // These are the strings @selvajs/ui renders itself in the 3D viewer and its
6
- // panelstool menu, view presets, scene manager, metadata dialog. They are
7
- // NOT the strings that come from a Grasshopper definition (mesh/layer/metadata
8
- // names): those live in the user's .gh file and can't be translated here.
9
- //
10
- // The library ships English + German. A host app can switch locale at runtime
11
- // via the locale context (see ./localeContext.svelte.ts) — e.g. selva feeds its
12
- // own Paraglide locale in. With no provider, components fall back to English.
5
+ // Only the strings @selvajs/ui renders itself. Names coming from a Grasshopper
6
+ // definitionmeshes, layers, metadata keys live in the user's .gh file and
7
+ // cannot be translated here.
13
8
  const en = {
14
9
  toolsMenu: 'Viewer tools',
15
10
  switchTo2D: 'Switch to 2D',
package/dist/index.js CHANGED
@@ -1,34 +1,28 @@
1
- // Layout components (page chrome: shell, header, footer, nav)
1
+ // The in-monorepo barrel: everything public.ts publishes, plus the design system,
2
+ // page chrome, contexts and utils that never ship to npm. Reached via the
3
+ // "selva-source" export condition.
2
4
  export * from './components/layout';
3
- // Compute app (schema + viewer + solve controls composed into a runnable app)
4
5
  export { default as AppLayout } from './components/compute/AppLayout.svelte';
5
6
  export { default as ComputeApp } from './components/compute/ComputeApp.svelte';
6
- // Error screen
7
7
  export { default as ErrorScreen } from './components/ErrorScreen.svelte';
8
8
  // Design-system primitives (shadcn-svelte + custom)
9
9
  export * from './components/primitives';
10
10
  export { default as StateDisplay } from './components/primitives/StateDisplay.svelte';
11
- // 3D viewer
12
11
  export { default as Viewer } from './components/viewer/Viewer.svelte';
13
- // Utilities
14
12
  export * from './schema/defaults';
15
13
  export * from './schema/dynamic-value-list';
16
14
  export * from './schema/traversal';
17
15
  export * from './compute/solving.svelte';
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
22
- // driver — can satisfy SolveDriver and drive a session. See CONTEXT.md.
16
+ // Solve Session seam. Re-exported so transports outside this package — e.g.
17
+ // plugin-ui's WebSocket driver can satisfy SolveDriver and drive a session.
18
+ // See CONTEXT.md.
23
19
  export { useSolveSession } from './compute/useSolveSession.svelte';
24
20
  export { createSolveSession, createRequestResponseDriver } from '@selvajs/solve/client';
25
- // External-input transit storage (used by routes that wire pre-step producers)
21
+ // Pre-step producer transit storage
26
22
  export { writeExternalValue, readExternalValue, clearExternalValue, getExternalInputs } from '@selvajs/solve/client';
27
- // Contexts & Composables
28
23
  export * from './contexts/footerContext.svelte';
29
24
  export * from './contexts/clientSlotContext.svelte';
30
25
  export * from './composables/useFooterItem.svelte';
31
- // Utils (cn function)
32
26
  export * from './utils';
33
27
  export { randomId } from './utils/randomId';
34
28
  export { DEFAULT_PRESET_LABELS } from './types/presetLabels';
package/dist/public.js CHANGED
@@ -1,46 +1,30 @@
1
- // ============================================================================
2
- // Published public API of @selvajs/ui
3
- // ============================================================================
1
+ // The only surface npm consumers of @selvajs/ui see. package.json maps the "."
2
+ // export's `svelte`/`types` conditions at this file's build output.
4
3
  //
5
- // This is the ONLY surface npm consumers of @selvajs/ui see (package.json maps
6
- // the "." export's `svelte`/`types` conditions at this file's build output).
7
- //
8
- // Scope: the compute-app SDK everything an external host app needs to embed a
9
- // Grasshopper-driven app (ComputeApp), drive solves, and wire pre-step
10
- // producers. Verified against real external host apps: they import
11
- // ComputeApp + its types, the solve seam, and the external-input storage
12
- // helpers. Nothing else.
13
- //
14
- // Deliberately NOT public: design-system primitives (Button, Card, Dialog, …),
15
- // page-chrome layout (AppShell, SideNav, …), toast/Toaster, ThemeSwitcher,
16
- // DataTable, contexts/composables, cn/randomId. These remain importable from
17
- // the full barrel (./index.ts) INSIDE the monorepo via the "@selvajs/source"
18
- // export condition, but never ship to npm. If an external consumer ever needs a
19
- // primitive directly, promote it here explicitly rather than re-exporting the
20
- // whole primitives barrel.
21
- // Compute app (schema + viewer + solve controls composed into a runnable app)
4
+ // Design-system primitives, page chrome, toasts, DataTable, contexts and small
5
+ // utils stay out on purpose: they remain importable from ./index.ts inside the
6
+ // monorepo via the "selva-source" export condition, but never ship to npm.
7
+ // Promote a symbol here explicitly rather than re-exporting a whole barrel.
8
+ // Compute app: schema + viewer + solve controls composed into a runnable app.
22
9
  export { default as AppLayout } from './components/compute/AppLayout.svelte';
23
10
  export { default as ComputeApp } from './components/compute/ComputeApp.svelte';
24
- // Standalone 3D viewer. Render Grasshopper meshes on their own, outside a
25
- // ComputeApp host — external apps drive it directly with a `meshes` array and
26
- // an optional `viewerConfig`. Pass `lang` to localize its chrome, or provide a
27
- // locale context (setLocaleContext) at the host root to drive it app-wide.
11
+ // Standalone 3D viewer: renders Grasshopper meshes outside a ComputeApp host,
12
+ // driven directly with a `meshes` array and an optional `viewerConfig`.
28
13
  export { default as Viewer } from './components/viewer/Viewer.svelte';
29
14
  export { appSource, isHostOwned, isOwnedBy, pointerToNdc } from '@selvajs/visualization/render';
30
15
  export { VIEWER_MESSAGES, DEFAULT_LOCALE, messagesFor } from './i18n/messages';
31
16
  export { setLocaleContext, getLocaleContext } from './i18n/localeContext.svelte';
32
- // Full-screen states a host app renders
17
+ // Full-screen states a host app renders.
33
18
  export { default as ErrorScreen } from './components/ErrorScreen.svelte';
34
- // Solve Session seam (transport-agnostic value/lifecycle state machine + its
35
- // driver interface). The session lives in `@selvajs/solve/client` and is
19
+ // Solve Session seam, for hosts driving a session themselves rather than
20
+ // embedding <ComputeApp>. The session lives in `@selvajs/solve/client` and is
36
21
  // framework-free; `useSolveSession` is the Svelte binding that makes its getters
37
- // read reactively inside components. A host embedding <ComputeApp> needs neither —
38
- // both are re-exported for hosts driving a session themselves. See CONTEXT.md.
22
+ // read reactively inside components. See CONTEXT.md.
39
23
  export { useSolveSession } from './compute/useSolveSession.svelte';
40
24
  export { createSolveSession, createRequestResponseDriver } from '@selvajs/solve/client';
41
25
  // Pre-step producer transit storage (host apps wire producers via these).
42
26
  export { writeExternalValue, readExternalValue, clearExternalValue, getExternalInputs } from '@selvajs/solve/client';
43
- // Schema utilities a ComputeApp host reasonably needs to read/shape values.
27
+ // Schema utilities a ComputeApp host needs to read/shape values.
44
28
  export * from './schema/defaults';
45
29
  export * from './schema/traversal';
46
30
  export * from './schema/dynamic-value-list';
@@ -1,4 +1,3 @@
1
- // Moved to @selvajs/schemas so server-side callers can share it without pulling
2
- // in the UI package. Re-exported here to keep existing `@selvajs/ui` importers
3
- // working.
1
+ // Moved to @selvajs/schemas so server-side callers can share it without pulling in the
2
+ // UI package. Re-exported to keep existing @selvajs/ui importers working.
4
3
  export { getDefaultValue } from '@selvajs/schemas';
@@ -27,12 +27,11 @@ function coercePayload(value) {
27
27
  return null;
28
28
  }
29
29
  // Memoizes string-payload coercion. In compute mode the payload arrives as a JSON
30
- // string — potentially several MB for a large options list — and the options map is
31
- // derived from `values` (TabLayout), so it recomputes on EVERY value change. Without
32
- // memoization each keystroke/output-merge re-parses megabytes and allocates a fresh
33
- // options object, whose new identity re-renders the entire dropdown subtree; with a
34
- // 6.4 MB payload this was measured driving a tab out of memory. Map keys use
35
- // SameValueZero, so an identical response string from a later solve also hits.
30
+ // string — several MB for a large options list — and the options map is derived from
31
+ // `values` (TabLayout), so it recomputes on EVERY value change. Unmemoized, each
32
+ // keystroke re-parses megabytes and allocates a fresh options object whose new
33
+ // identity re-renders the whole dropdown subtree; a 6.4 MB payload drove a tab OOM.
34
+ // Map keys use SameValueZero, so an identical string from a later solve also hits.
36
35
  const coerceCache = new Map();
37
36
  const COERCE_CACHE_MAX = 8;
38
37
  function coercePayloadMemo(value) {
@@ -48,10 +47,9 @@ function coercePayloadMemo(value) {
48
47
  }
49
48
  const parseStart = performance.now();
50
49
  const parsed = coercePayload(value);
51
- // Diagnostic: an expensive parse should happen ONCE per distinct solve result.
52
- // If this line storms in the console, payload memoization is being defeated
53
- // (e.g. payload strings differing per recompute) — the pre-memoization churn
54
- // pattern that could OOM a tab.
50
+ // An expensive parse should happen ONCE per distinct solve result. If this line
51
+ // storms the console, memoization is being defeated (payload strings differing
52
+ // per recompute) — the churn pattern that could OOM a tab.
55
53
  if (value.length > 256 * 1024) {
56
54
  const optionCount = parsed?.options ? Object.keys(parsed.options).length : 0;
57
55
  console.info(`[DVL] parsed ${(value.length / (1024 * 1024)).toFixed(1)} MB options payload ` +
@@ -68,11 +66,11 @@ function coercePayloadMemo(value) {
68
66
  /**
69
67
  * Every dynamicValueList output reference in the schema.
70
68
  *
71
- * Canonical location is `schema.outputs[]` — the plugin's SchemaSynchronizer enforces that every
72
- * dynamicValueList layout item is mirrored there (see CanonicalizeDynamicValueListOutputs). We ALSO
73
- * scan the layout purely as back-compat defense for schemas persisted by an older plugin that lacked
74
- * that invariant; for current schemas the layout pass finds nothing new.
75
- * Deduped by id, outputs[] winning so the canonical record's targetInputId takes precedence.
69
+ * Canonical location is `schema.outputs[]` — the plugin's SchemaSynchronizer mirrors every
70
+ * dynamicValueList layout item there (`CanonicalizeDynamicValueListOutputs`). The layout scan is
71
+ * back-compat defense for schemas persisted by an older plugin that lacked that invariant; on
72
+ * current schemas it finds nothing new. Deduped by id, `outputs[]` winning so the canonical
73
+ * record's targetInputId takes precedence.
76
74
  */
77
75
  function collectDynamicValueListSources(schema) {
78
76
  const byId = new Map();
@@ -11,29 +11,22 @@ export declare function validateSavedState(savedState: ParameterPreset, currentS
11
11
  canLoad: boolean;
12
12
  };
13
13
  /**
14
- * The subset of a preset's parameters that can safely be applied to the current schema:
15
- * every param that still exists as an input. A param that no longer exists is dropped
16
- * (it's the same condition `validateSavedState` flags as an error), so this derives its
17
- * own "loadable" rule from the schema rather than re-reading validation issues — no
18
- * coupling to how sentinel ids are encoded.
14
+ * Params the current schema no longer has are dropped. That's the same condition
15
+ * `validateSavedState` flags as an error, but the rule is re-derived from the schema here
16
+ * rather than read back off the issue list nothing couples to how sentinel ids are encoded.
19
17
  */
20
18
  export declare function extractLoadableValues(savedState: ParameterPreset, currentSchema: UISchema): Record<string, unknown>;
21
- /** The full outcome of loading a preset: what to apply, plus the diagnostics to surface. */
22
19
  export interface PresetLoadResult {
23
- /** Values safe to apply now (params that still exist). */
20
+ /** Safe to apply now — the params that still exist in the schema. */
24
21
  values: Record<string, unknown>;
25
- /** All validation issues (errors + warnings) for the load dialog. */
22
+ /** Errors and warnings both, for the load dialog. */
26
23
  issues: ValidationIssueMessage[];
27
24
  /** No issues at all — load silently. */
28
25
  isValid: boolean;
29
- /** No blocking errors — load is allowed (warnings are fine). */
26
+ /** No errors — warnings alone still allow the load. */
30
27
  canLoad: boolean;
31
28
  }
32
- /**
33
- * Single entry point for applying a preset: validates against the current schema and, in
34
- * the same pass, computes the loadable values. Callers get one object instead of
35
- * threading a validation result back into a separate extraction step.
36
- */
29
+ /** Validates and computes the loadable values in one pass, so callers thread one object. */
37
30
  export declare function loadPreset(savedState: ParameterPreset, currentSchema: UISchema): PresetLoadResult;
38
31
  export declare function exportStateAsJson(savedState: ParameterPreset): void;
39
32
  export declare function importStateFromJson(file: File): Promise<ParameterPreset>;
@@ -75,11 +75,9 @@ export function validateSavedState(savedState, currentSchema) {
75
75
  return { isValid: issues.length === 0, issues, canLoad: !hasErrors };
76
76
  }
77
77
  /**
78
- * The subset of a preset's parameters that can safely be applied to the current schema:
79
- * every param that still exists as an input. A param that no longer exists is dropped
80
- * (it's the same condition `validateSavedState` flags as an error), so this derives its
81
- * own "loadable" rule from the schema rather than re-reading validation issues — no
82
- * coupling to how sentinel ids are encoded.
78
+ * Params the current schema no longer has are dropped. That's the same condition
79
+ * `validateSavedState` flags as an error, but the rule is re-derived from the schema here
80
+ * rather than read back off the issue list nothing couples to how sentinel ids are encoded.
83
81
  */
84
82
  export function extractLoadableValues(savedState, currentSchema) {
85
83
  const values = {};
@@ -91,11 +89,7 @@ export function extractLoadableValues(savedState, currentSchema) {
91
89
  }
92
90
  return values;
93
91
  }
94
- /**
95
- * Single entry point for applying a preset: validates against the current schema and, in
96
- * the same pass, computes the loadable values. Callers get one object instead of
97
- * threading a validation result back into a separate extraction step.
98
- */
92
+ /** Validates and computes the loadable values in one pass, so callers thread one object. */
99
93
  export function loadPreset(savedState, currentSchema) {
100
94
  const validation = validateSavedState(savedState, currentSchema);
101
95
  return {
@@ -111,8 +105,8 @@ export function exportStateAsJson(savedState) {
111
105
  const date = savedState.timestamp.split('T')[0].replace(/-/g, '_');
112
106
  const link = document.createElement('a');
113
107
  link.href = URL.createObjectURL(blob);
114
- // .slvp = Selva parameter preset. Import accepts legacy .sps files too the content is
115
- // identical JSON, only the writer's extension changed.
108
+ // .slvp = Selva parameter preset. Import still accepts the pre-rename .sps — same JSON,
109
+ // only the writer's extension changed.
116
110
  link.download = `${safeName}_${date}.slvp`;
117
111
  link.click();
118
112
  URL.revokeObjectURL(link.href);
@@ -1,5 +1,4 @@
1
- // Schema layout traversal now lives in @selvajs/schemas (next to the types it walks) so
2
- // any package depending on the schema can traverse it without pulling in @selvajs/ui.
3
- // Re-exported here to keep it part of @selvajs/ui's published surface for existing
4
- // consumers.
1
+ // Moved to @selvajs/schemas, next to the types it walks, so any package depending on the
2
+ // schema can traverse it without pulling in @selvajs/ui. Re-exported to keep existing
3
+ // @selvajs/ui importers working.
5
4
  export { getGroups, getLayoutItems, getInputItems } from '@selvajs/schemas';
@@ -7,16 +7,12 @@ export interface VisibilityResult {
7
7
  export declare function evaluateRule(rule: VisibilityRule, values: Record<string, unknown>): boolean;
8
8
  export declare function evaluateCondition(condition: VisibilityCondition | GroupVisibilityCondition, values: Record<string, unknown>): boolean;
9
9
  export declare function evaluateVisibility(item: LayoutItem, values: Record<string, unknown>): VisibilityResult;
10
- /**
11
- * Stable key for a layout item: `paramId` for inputs/outputs, `id` for linebreaks.
12
- * Mirrors the key the renderers use in their `{#each}` blocks.
13
- */
10
+ /** Must stay identical to the key the renderers use in their `{#each}` blocks. */
14
11
  export declare function itemKey(item: LayoutItem): string;
15
12
  /**
16
- * Evaluates every item's visibility once against the current values, keyed by itemKey.
17
- * Callers that touch the same items more than once per render (column layout + cell
18
- * render, plus the default-value sweep) read from this single map instead of
19
- * re-evaluating per access — one source of truth for "what's visible right now".
13
+ * Evaluates each item once per render. Callers that touch the same item several times
14
+ * (column layout, then cell render, then the default-value sweep) read this map instead of
15
+ * re-evaluating, so they can't disagree about what's visible mid-render.
20
16
  */
21
17
  export declare function buildVisibilityMap(items: LayoutItem[], values: Record<string, unknown>): Record<string, VisibilityResult>;
22
18
  export declare function evaluateGroupVisibility(group: {
@@ -57,18 +57,14 @@ export function evaluateVisibility(item, values) {
57
57
  const actionFn = ACTIONS[action] ?? ACTIONS.show;
58
58
  return actionFn(met, defaultValue);
59
59
  }
60
- /**
61
- * Stable key for a layout item: `paramId` for inputs/outputs, `id` for linebreaks.
62
- * Mirrors the key the renderers use in their `{#each}` blocks.
63
- */
60
+ /** Must stay identical to the key the renderers use in their `{#each}` blocks. */
64
61
  export function itemKey(item) {
65
62
  return item.type === 'linebreak' ? item.id : item.paramId;
66
63
  }
67
64
  /**
68
- * Evaluates every item's visibility once against the current values, keyed by itemKey.
69
- * Callers that touch the same items more than once per render (column layout + cell
70
- * render, plus the default-value sweep) read from this single map instead of
71
- * re-evaluating per access — one source of truth for "what's visible right now".
65
+ * Evaluates each item once per render. Callers that touch the same item several times
66
+ * (column layout, then cell render, then the default-value sweep) read this map instead of
67
+ * re-evaluating, so they can't disagree about what's visible mid-render.
72
68
  */
73
69
  export function buildVisibilityMap(items, values) {
74
70
  const map = {};
@@ -1,31 +1,7 @@
1
- /**
2
- * Defines an action button for use in the UI.
3
- *
4
- * @example
5
- * ```typescript
6
- * const saveButton: ActionButton = {
7
- * id: 'save-btn',
8
- * label: 'Save Changes',
9
- * icon: SaveIcon,
10
- * variant: 'default',
11
- * size: 'lg',
12
- * onclick: async () => {
13
- * await saveData();
14
- * showNotification('Saved!');
15
- * }
16
- * };
17
- * ```
18
- *
19
- * @property id - Unique identifier for this button
20
- * @property label - Display text shown on the button
21
- * @property icon - Optional icon component to display alongside the label
22
- * @property variant - Button styling variant (controls color/appearance)
23
- * @property size - Button size preset
24
- * @property onclick - Callback function executed when clicked; can be async
25
- */
26
1
  export interface ActionButton {
27
2
  id: string;
28
3
  label: string;
4
+ /** A Svelte component, rendered before the label. */
29
5
  icon?: any;
30
6
  variant?: 'default' | 'outline' | 'destructive' | 'secondary' | 'ghost';
31
7
  size?: 'default' | 'sm' | 'lg';
@@ -1,7 +1,6 @@
1
1
  /**
2
- * Overridable UI strings for the parameter-preset manager (Save/Load flow) and
3
- * footer copyright text. All optional — unset keys fall back to the English
4
- * defaults in `DEFAULT_PRESET_LABELS`. Pass a partial object to localize.
2
+ * Overridable UI strings for the parameter-preset manager. Pass a partial object to localize;
3
+ * unset keys fall back to `DEFAULT_PRESET_LABELS`.
5
4
  */
6
5
  export interface PresetLabels {
7
6
  saveButton: string;