@weasel-js/labkit 1.4.2 → 1.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/README.md +20 -2
  2. package/dist/_dts/{CanvasStackContext-kjILVnPj.d.ts → CanvasStackContext-Dde8_1my.d.ts} +1 -1
  3. package/dist/_dts/{PrefsForm.d-CgxUequc.d.ts → PrefsForm.d-DDHFANkc.d.ts} +1 -1
  4. package/dist/_dts/{frac-A4R6v4ld.d.ts → frac-B5mB8LsJ.d.ts} +7 -4
  5. package/dist/_dts/{index-CsU9WhjM.d.ts → index-DelFfeV0.d.ts} +15 -7
  6. package/dist/_dts/types-B9_zrHmb.d.ts +166 -0
  7. package/dist/_dts/{types-BbXpvQa8.d.ts → types-BP2OCcpg.d.ts} +21 -3
  8. package/dist/_dts/types-x92Kfeme.d.ts +62 -0
  9. package/dist/_dts/{useTrialState-DKjqpv20.d.ts → useTrialState-D6Vb3T-g.d.ts} +7 -7
  10. package/dist/canvas/index.d.ts +9 -8
  11. package/dist/canvas/index.js +1 -2
  12. package/dist/chrome/index.d.ts +7 -6
  13. package/dist/chrome/index.js +5 -5
  14. package/dist/{chunk-UTOEDPNU.js → chunk-2RQPGOQF.js} +7 -6
  15. package/dist/chunk-2RQPGOQF.js.map +1 -0
  16. package/dist/{chunk-ULDW42CR.js → chunk-5TROQVQ2.js} +25 -21
  17. package/dist/chunk-5TROQVQ2.js.map +1 -0
  18. package/dist/{chunk-XKENZTNE.js → chunk-ASWKLRKJ.js} +65 -49
  19. package/dist/chunk-ASWKLRKJ.js.map +1 -0
  20. package/dist/{chunk-W2FJR5FF.js → chunk-C42T6DJT.js} +74 -76
  21. package/dist/chunk-C42T6DJT.js.map +1 -0
  22. package/dist/chunk-E44SU6XS.js +50 -0
  23. package/dist/chunk-E44SU6XS.js.map +1 -0
  24. package/dist/{chunk-WS6ZRV75.js → chunk-FQMJUVHJ.js} +3 -3
  25. package/dist/{chunk-WS6ZRV75.js.map → chunk-FQMJUVHJ.js.map} +1 -1
  26. package/dist/{chunk-FJG4PHTL.js → chunk-I6JCVE24.js} +3 -3
  27. package/dist/chunk-I6JCVE24.js.map +1 -0
  28. package/dist/{chunk-NSOVI3AZ.js → chunk-ISCOFAUO.js} +6 -6
  29. package/dist/{chunk-NSOVI3AZ.js.map → chunk-ISCOFAUO.js.map} +1 -1
  30. package/dist/chunk-MHAGC6VD.js +7320 -0
  31. package/dist/chunk-MHAGC6VD.js.map +1 -0
  32. package/dist/{chunk-UDXOYZEC.js → chunk-QAGAHJKZ.js} +4 -4
  33. package/dist/{chunk-UDXOYZEC.js.map → chunk-QAGAHJKZ.js.map} +1 -1
  34. package/dist/{chunk-TN7YSJVU.js → chunk-RS3HRQWU.js} +3 -3
  35. package/dist/{chunk-TN7YSJVU.js.map → chunk-RS3HRQWU.js.map} +1 -1
  36. package/dist/{chunk-D5KQ5OY6.js → chunk-TJ7QY3OC.js} +3 -3
  37. package/dist/{chunk-D5KQ5OY6.js.map → chunk-TJ7QY3OC.js.map} +1 -1
  38. package/dist/controls/index.d.ts +5 -4
  39. package/dist/controls/index.js +3 -3
  40. package/dist/dragdrop/index.d.ts +6 -5
  41. package/dist/dragdrop/index.js +1 -2
  42. package/dist/index.d.ts +90 -23
  43. package/dist/index.js +199 -117
  44. package/dist/index.js.map +1 -1
  45. package/dist/layers/index.d.ts +7 -6
  46. package/dist/layers/index.js +2 -3
  47. package/dist/loupe/index.d.ts +8 -7
  48. package/dist/loupe/index.js +1 -2
  49. package/dist/passthrough/weasel-canvas.d.ts +1 -3
  50. package/dist/passthrough/weasel-canvas.js +1 -1
  51. package/dist/passthrough/weasel-canvas.js.map +1 -1
  52. package/dist/passthrough/weasel-ui.d.ts +88 -20
  53. package/dist/passthrough/weasel-ui.js +1 -2
  54. package/dist/primitives/index.js +3 -4
  55. package/dist/state/index.d.ts +6 -3
  56. package/dist/state/index.js +3 -2
  57. package/dist/state/index.js.map +1 -1
  58. package/dist/styles.css +2 -2
  59. package/dist/surface/index.js +1 -2
  60. package/dist/ui/layers/index.js +1 -2
  61. package/dist/undo/index.d.ts +6 -6
  62. package/package.json +7 -7
  63. package/src/annotations/ExportMenu.test.tsx +16 -0
  64. package/src/annotations/ExportMenu.tsx +0 -5
  65. package/src/config/builder.test.ts +56 -2
  66. package/src/config/builder.ts +73 -13
  67. package/src/config/index.ts +16 -1
  68. package/src/config/path.test.ts +109 -0
  69. package/src/config/path.ts +76 -0
  70. package/src/config/resolve.test.ts +93 -2
  71. package/src/config/resolve.ts +72 -29
  72. package/src/config/types.ts +99 -9
  73. package/src/config/visible.ts +6 -4
  74. package/src/controls/ControlPanel.stories.tsx +38 -4
  75. package/src/controls/ControlPanel.test.tsx +114 -0
  76. package/src/controls/ControlPanel.tsx +99 -50
  77. package/src/index.ts +16 -1
  78. package/src/instrument/serializers.ts +36 -0
  79. package/src/instrument/types.ts +4 -2
  80. package/src/lab/Lab.test.tsx +29 -1
  81. package/src/lab/Lab.tsx +10 -1
  82. package/src/lab/LabShell.test.tsx +8 -0
  83. package/src/lab/LabShell.tsx +3 -1
  84. package/src/primitives/ZoomControl.less +6 -2
  85. package/src/primitives/ZoomControl.tsx +1 -0
  86. package/src/state/helpers.ts +1 -1
  87. package/src/state/store.test.ts +155 -1
  88. package/src/state/store.ts +30 -24
  89. package/src/state/types.ts +24 -3
  90. package/src/state/useTrialState.ts +1 -1
  91. package/src/trial/Trial.config.test.tsx +44 -0
  92. package/src/trial/Trial.tsx +3 -3
  93. package/src/trial/TrialChrome.tsx +10 -5
  94. package/src/trial/trialOps.ts +4 -8
  95. package/dist/_dts/types-lg4TSCb2.d.ts +0 -164
  96. package/dist/_dts/weasel-canvas-FJTFi3ZZ.d.ts +0 -5041
  97. package/dist/chunk-54ZWZ5FQ.js +0 -29997
  98. package/dist/chunk-54ZWZ5FQ.js.map +0 -1
  99. package/dist/chunk-FJG4PHTL.js.map +0 -1
  100. package/dist/chunk-ISSVF5PT.js +0 -7263
  101. package/dist/chunk-ISSVF5PT.js.map +0 -1
  102. package/dist/chunk-ULDW42CR.js.map +0 -1
  103. package/dist/chunk-UTOEDPNU.js.map +0 -1
  104. package/dist/chunk-W2FJR5FF.js.map +0 -1
  105. package/dist/chunk-XKENZTNE.js.map +0 -1
package/dist/index.d.ts CHANGED
@@ -2,28 +2,30 @@ export { LayerStack, LayerStackItem, LayerStackProps } from './ui/layers/index.j
2
2
  import * as react_jsx_runtime from 'react/jsx-runtime';
3
3
  import * as react from 'react';
4
4
  import { ReactNode } from 'react';
5
- import { P as PropertyMetricProps, a as PropertyListPack } from './_dts/index-CsU9WhjM.js';
6
- export { C as CheckboxRow, b as CheckboxRowProps, c as ColorRow, d as ColorRowProps, e as ControlPanel, N as NumberRow, f as NumberRowProps, g as PropertyList, h as PropertyListProps, i as PropertyOption, j as PropertyPanel, k as PropertyPanelProps, l as PropertyRow, m as PropertyRowLayout, n as PropertyRowProps, o as PropertyRowVariant, p as PropertySpan, q as PropertySpanProps, S as SelectRow, r as SelectRowProps, s as SliderRow, t as SliderRowProps, T as TextRow, u as TextRowProps, v as ToggleRow, w as ToggleRowProps } from './_dts/index-CsU9WhjM.js';
7
- import { D as DrawCommand, N as Node, V as View, P as Path, F as FillStyle, b as StyledRun, c as TextStyle, d as Stroke, B as BuiltinToolId, T as ToolPrefLeaf } from './_dts/weasel-canvas-FJTFi3ZZ.js';
8
- import { d as WorldRect, A as AnnotationData, e as AnnotationMeaning, M as MarkScene, C as CaptureSource, f as CaptureOptions, g as CaptureResult, h as AnnotationTarget, i as AnnotationsApi, j as AnnotationsCapability, T as TrialTool, k as AnnotationKind, V as ViewTransform, I as Instrument, l as InstrumentList, m as TrialContribution } from './_dts/frac-A4R6v4ld.js';
9
- export { n as Annotation, o as AnnotationInit, p as AnnotationPatch, q as AnnotationQuery, r as AnnotationStatus, s as AnnotationStoreOptions, t as AnnotationTargetInfo, u as CanvasCapability, v as CanvasLayer, w as CaptureDeps, x as DEFAULT_FRAME, b as DragDropCapability, D as DragFeedback, F as FracPoint, y as FracRect, H as HitResult, z as IconComponent, L as LOUPE_DEFAULTS, B as LayerCapability, E as LayerDescriptor, G as LoupeCapability, J as LoupeDeclaration, K as LoupeRenderArgs, P as PaletteItem, a as Point, R as RenderContext, N as ResolvedLoupe, S as SerializedAnnotations, O as SidebarSection, Q as StatusReadout, U as SystemEvent, X as ToolCapability, Y as ToolItem, Z as ToolbarItem, _ as TrialChromeContext, $ as TrialRegion, a0 as UndoCapability, a1 as ViewportControl, a2 as ViewportSize, W as WorldFrame, c as WorldSpec, a3 as annotationsFromJSON, a4 as applyCamera, a5 as createAnnotationScene, a6 as createAnnotationStore, a7 as fracContains, a8 as fracIntersects, a9 as fracToWorld, aa as resolveFrame, ab as resolveLoupe, ac as roundFrac, ad as worldToFrac } from './_dts/frac-A4R6v4ld.js';
10
- import { i as TrialInfo, a as StorageAdapter, h as LabMode, T as TrialRecord, c as SavedSnapshot } from './_dts/types-BbXpvQa8.js';
11
- export { C as CreateLabStoreOptions, L as LabDocument, b as LabStoreState, S as SerializedTrial, d as TrialStateHandle, U as UndoStack, e as UndockedPanel, f as UndockedPanels, g as dockPanel, p as panelKey, u as undockPanel } from './_dts/types-BbXpvQa8.js';
5
+ import { P as PropertyMetricProps, a as PropertyListPack } from './_dts/index-DelFfeV0.js';
6
+ export { C as CheckboxRow, b as CheckboxRowProps, c as ColorRow, d as ColorRowProps, e as ControlPanel, N as NumberRow, f as NumberRowProps, g as PropertyList, h as PropertyListProps, i as PropertyOption, j as PropertyPanel, k as PropertyPanelProps, l as PropertyRow, m as PropertyRowLayout, n as PropertyRowProps, o as PropertyRowVariant, p as PropertySpan, q as PropertySpanProps, S as SelectRow, r as SelectRowProps, s as SliderRow, t as SliderRowProps, T as TextRow, u as TextRowProps, v as ToggleRow, w as ToggleRowProps } from './_dts/index-DelFfeV0.js';
7
+ import { DrawCommand, SceneNode, View, Path, FillStyle, StyledRun, TextStyle, Stroke, BuiltinToolId, ToolPrefLeaf, ToolPrefGroup } from '@weasel-js/core';
8
+ import { d as WorldRect, A as AnnotationData, e as AnnotationMeaning, M as MarkScene, C as CaptureSource, f as CaptureOptions, g as CaptureResult, h as AnnotationTarget, i as AnnotationsApi, j as AnnotationsCapability, T as TrialTool, k as AnnotationKind, V as ViewTransform, I as Instrument, l as InstrumentList, m as TrialContribution } from './_dts/frac-B5mB8LsJ.js';
9
+ export { n as Annotation, o as AnnotationInit, p as AnnotationPatch, q as AnnotationQuery, r as AnnotationStatus, s as AnnotationStoreOptions, t as AnnotationTargetInfo, u as CanvasCapability, v as CanvasLayer, w as CaptureDeps, x as DEFAULT_FRAME, b as DragDropCapability, D as DragFeedback, F as FracPoint, y as FracRect, H as HitResult, z as IconComponent, L as LOUPE_DEFAULTS, B as LayerCapability, E as LayerDescriptor, G as LoupeCapability, J as LoupeDeclaration, K as LoupeRenderArgs, P as PaletteItem, a as Point, R as RenderContext, N as ResolvedLoupe, S as SerializedAnnotations, O as SidebarSection, Q as StatusReadout, U as SystemEvent, X as ToolCapability, Y as ToolItem, Z as ToolbarItem, _ as TrialChromeContext, $ as TrialRegion, a0 as UndoCapability, a1 as ViewportControl, a2 as ViewportSize, W as WorldFrame, c as WorldSpec, a3 as annotationsFromJSON, a4 as applyCamera, a5 as createAnnotationScene, a6 as createAnnotationStore, a7 as fracContains, a8 as fracIntersects, a9 as fracToWorld, aa as resolveFrame, ab as resolveLoupe, ac as roundFrac, ad as worldToFrac } from './_dts/frac-B5mB8LsJ.js';
10
+ import { i as TrialInfo, a as StorageAdapter, h as LabMode, T as TrialRecord, c as SavedSnapshot } from './_dts/types-BP2OCcpg.js';
11
+ export { C as CreateLabStoreOptions, L as LabDocument, b as LabStoreState, S as SerializedTrial, d as TrialStateHandle, U as UndoStack, e as UndockedPanel, f as UndockedPanels, g as dockPanel, p as panelKey, u as undockPanel } from './_dts/types-BP2OCcpg.js';
12
12
  export { CanvasStack, CanvasStackProps, OrbitHandlers, OrbitView, PITCH_LIMIT, PanZoomHandlers, UseOrbitOptions, UsePanZoomOptions, Vec3, ZoomAtOptions, centerOn, clampPitch, orbitAfterDrag, orbitAfterWheel, screenToWorld, useOrbit, usePanZoom, worldToScreen, wrapYaw, zoomAt } from './canvas/index.js';
13
- export { C as CanvasLayerDescriptor, a as CanvasStackContext, b as CanvasStackContextValue, c as CanvasStackSurface } from './_dts/CanvasStackContext-kjILVnPj.js';
13
+ export { C as CanvasLayerDescriptor, a as CanvasStackContext, b as CanvasStackContextValue, c as CanvasStackSurface } from './_dts/CanvasStackContext-Dde8_1my.js';
14
14
  export { PaletteRegion, PaletteRegionProps, SidebarRegion, SidebarRegionProps, StatusRegion, StatusRegionProps, TitleBarPlacement, TitleBarRegion, TitleBarRegionProps, ToolbarRegion, ToolbarRegionProps, ViewportRegion, ViewportRegionProps, builtinContributions, mergeContributions, suppressContributions } from './chrome/index.js';
15
- import { b as ConfigNode, A as Annotations, N as NodeOptions, c as ControlRenderer, d as ConfigOption, a as ConfigSchema, I as InferConfig, C as ConfigField, R as ResolvedConfig, e as ConfigRule, L as LeafPatch, f as ConfigRuleContext } from './_dts/types-lg4TSCb2.js';
16
- export { g as CheckboxField, h as ColorField, i as ConfigFieldBase, j as ConfigFieldType, k as ConfigOf, l as NodeValue, m as NumberField, S as SectionSpec, n as SelectField, o as SelectOption, p as SliderField, T as TextField } from './_dts/types-lg4TSCb2.js';
15
+ import { b as ConfigNode, A as Annotations, N as NodeOptions, c as ControlRenderer, d as ConfigOption, e as ConfigShape, f as ConfigBranch, B as BranchAnnotations, g as BranchOptions, a as ConfigSchema, I as InferConfig, h as ConfigEntry, R as ResolvedConfig, i as ConfigRule, L as LeafPatch, j as ConfigRuleContext } from './_dts/types-B9_zrHmb.js';
16
+ export { k as ConfigOf, C as ConfigPath, E as EntryValue, l as NodeValue, S as SectionSpec, V as ValueAtPath } from './_dts/types-B9_zrHmb.js';
17
+ import { C as ConfigField } from './_dts/types-x92Kfeme.js';
18
+ export { a as CheckboxField, b as ColorField, c as ConfigFieldBase, d as ConfigFieldType, N as NumberField, S as SelectField, e as SelectOption, f as SliderField, T as TextField } from './_dts/types-x92Kfeme.js';
17
19
  import { J as JobHandle } from './_dts/types-DJ79Tg5J.js';
18
20
  export { a as JobCapability, b as JobEvent, c as JobFailure, d as JobStatus } from './_dts/types-DJ79Tg5J.js';
19
21
  export { UseJobOptions, useJob } from './job/index.js';
20
22
  export { LayerList, LayerListProps, LayerTreeNode } from './layers/index.js';
21
23
  export { CanvasLoupe, CanvasLoupeProps, DomLoupe, DomLoupeProps, LensCamera, LoupeBubble, LoupeBubbleProps, LoupeState, SourceRect, TrialLoupe, TrialLoupeProps, UseLoupeOptions, drawCanvasLens, lensCamera, lensSourceRect, sampleStack, useLoupe } from './loupe/index.js';
22
24
  export { FloatingPanel, FloatingPanelProps, FpsMeter, JobProgress, JobProgressProps, Legend, LegendEntry, LegendMark, LegendProps, ScaleIndicator, ScaleIndicatorProps, Sidebar, SidebarProps, StatusBar, StatusBarProps, StatusBarSectionProps, Toolbar, ToolbarButtonProps, ToolbarGroupProps, ToolbarProps, ZoomControl, ZoomControlProps } from './primitives/index.js';
23
- export { C as CURRENT_DOCUMENT_VERSION, b as LabStoreContext, c as LabStoreProvider, T as TrialIdContext, d as TrialIdProvider, f as createMemoryAdapter, l as labDocumentKey, g as localStorageAdapter, n as noneAdapter, q as quarantineKey, s as sessionStorageAdapter, u as urlHashAdapter, h as useLabStore, i as useTrialId, j as useTrialState } from './_dts/useTrialState-DKjqpv20.js';
25
+ export { C as CURRENT_DOCUMENT_VERSION, b as LabStoreContext, c as LabStoreProvider, T as TrialIdContext, d as TrialIdProvider, f as createMemoryAdapter, l as labDocumentKey, g as localStorageAdapter, n as noneAdapter, q as quarantineKey, s as sessionStorageAdapter, u as urlHashAdapter, h as useLabStore, i as useTrialId, j as useTrialState } from './_dts/useTrialState-D6Vb3T-g.js';
24
26
  export { Box, Rect, SurfaceCanvasContext, SurfaceContext, SurfaceFrame, SurfaceHandle, TilePainter, UseTiledSurfaceOptions, composeRects, rectsEqual, toDeviceRect, useSurface, useSurfaceCanvas, useSurfaceOptional, useSurfaceTile, useTileId, useTiledSurface } from './surface/index.js';
25
27
  export { EventBus, EventListener, clearUndo, createEventBus, emptyStack, pushSnapshot, redo, undo } from './undo/index.js';
26
- import './_dts/PrefsForm.d-CgxUequc.js';
28
+ import './_dts/PrefsForm.d-DDHFANkc.js';
27
29
  import 'windease';
28
30
  import 'zustand/vanilla';
29
31
 
@@ -215,7 +217,7 @@ interface MarkDrawOptions {
215
217
  * export that resolves a status colour or a stale dash differently from the
216
218
  * screen produces a picture nobody was looking at.
217
219
  */
218
- declare function createMarkDrawOne(opts: MarkDrawOptions): (node: Node<AnnotationData, 'marks', WorldRect>, pose: WorldRect, view: View) => DrawCommand[];
220
+ declare function createMarkDrawOne(opts: MarkDrawOptions): (node: SceneNode<AnnotationData, 'marks', WorldRect>, pose: WorldRect, view: View) => DrawCommand[];
219
221
  /** How a mark looks, as opposed to where it is. Separate from the callback
220
222
  * above because an SVG export resolves the same appearance without ever
221
223
  * building a draw command. */
@@ -459,8 +461,11 @@ interface SvgTextNode {
459
461
  * in `data.fill` / `data.stroke`. Not `SvgPaint`, which is the path
460
462
  * nodes' shape — a text paint reaches the renderer through the runs, and
461
463
  * `StyledRun.fill` / `.stroke` override these per range.
464
+ *
465
+ * `null` is SVG's `fill="none"`: outline-only text, painted by its stroke
466
+ * alone. Absent takes the default black.
462
467
  */
463
- fill?: FillStyle;
468
+ fill?: FillStyle | null;
464
469
  stroke?: Stroke;
465
470
  /** Element-level opacity (`opacity="..."`), 0..1. */
466
471
  opacity?: number;
@@ -600,6 +605,8 @@ declare function fromWeaselView(v: View): ViewTransform;
600
605
  */
601
606
  declare function fitView(content: ContentSize, pane: PaneSize): ViewTransform;
602
607
 
608
+ /** Whether a schema entry is a branch rather than a leaf. */
609
+ declare function isConfigBranch(entry: ConfigEntry): entry is ConfigBranch;
603
610
  /** Shared chaining surface. Every method clones, so a node can be reused as a
604
611
  * base for several leaves without one bleeding into the next. */
605
612
  declare abstract class BaseNode<T> implements ConfigNode<T> {
@@ -621,8 +628,11 @@ declare abstract class BaseNode<T> implements ConfigNode<T> {
621
628
  hidden(): this;
622
629
  /** Pair with a sibling sharing this id, side-by-side on one row. */
623
630
  pair(pair: string): this;
624
- /** Render under a named section heading. */
625
- section(section: string): this;
631
+ /** Render under a named section heading. `collapsed` opens the section
632
+ * folded — say it on any one of the section's leaves. */
633
+ section(label: string, opts?: {
634
+ collapsed?: boolean;
635
+ }): this;
626
636
  /** Show this row only while the predicate holds. Presentational — the value
627
637
  * stays in config and the instrument still reads it. */
628
638
  showIf(predicate: (config: Record<string, unknown>) => boolean): this;
@@ -676,6 +686,31 @@ declare class CustomNode<T> extends BaseNode<T> {
676
686
  constructor(kind: string, defaultValue: T, annotations?: Readonly<Annotations>, options?: Readonly<NodeOptions>);
677
687
  protected with(annotations: Annotations, options: NodeOptions): this;
678
688
  }
689
+ /**
690
+ * A branch of the schema: its children nest under its key, so a leaf inside
691
+ * one is read and written at a dotted path. Distinct from `.section()`, which
692
+ * puts a heading over sibling leaves and leaves their paths alone.
693
+ */
694
+ declare class GroupNode<S extends ConfigShape> implements ConfigBranch<S> {
695
+ readonly children: S;
696
+ readonly annotations: Readonly<BranchAnnotations>;
697
+ readonly options: Readonly<BranchOptions>;
698
+ constructor(children: S, annotations?: Readonly<BranchAnnotations>, options?: Readonly<BranchOptions>);
699
+ private with;
700
+ /** Heading for the group's rows. Defaults to the key, title-cased. */
701
+ label(name: string): GroupNode<S>;
702
+ /** Longer help text. Carried on the resolved `PrefGroup`, where weasel-ui's
703
+ * `PrefsForm` draws it under the heading; `ControlPanel` has no place for
704
+ * it yet and shows the heading alone. */
705
+ describe(description: string): GroupNode<S>;
706
+ /** Render this whole group under a named section heading. */
707
+ section(label: string, opts?: {
708
+ collapsed?: boolean;
709
+ }): GroupNode<S>;
710
+ /** Show this group only while the predicate holds. Presentational — every
711
+ * value beneath it stays in config and the instrument still reads it. */
712
+ showIf(predicate: (config: Record<string, unknown>) => boolean): GroupNode<S>;
713
+ }
679
714
  /**
680
715
  * Builds an instrument's config: the values, their types, and how each is
681
716
  * edited, in one declaration.
@@ -684,6 +719,8 @@ declare class CustomNode<T> extends BaseNode<T> {
684
719
  * const config = f.schema({
685
720
  * showGrid: f.boolean(true),
686
721
  * cellSize: f.number(20).range(5, 80).step(5).label('Grid spacing'),
722
+ * // Nested: read and written at `grid.color`.
723
+ * grid: f.group({ color: f.color('#ffffff') }),
687
724
  * })
688
725
  * ```
689
726
  */
@@ -699,10 +736,14 @@ declare const f: {
699
736
  /** A leaf with no declared kind: the rule chain decides, which is how a lab
700
737
  * states a convention like "every `*Color` key is a color picker". */
701
738
  value: <T>(def: T) => ValueNode<T>;
739
+ /** A branch: its children nest under this key, so `grid: f.group({ size })`
740
+ * puts the value at `config.grid.size` and addresses it as `'grid.size'`.
741
+ * Groups nest as deeply as the schema wants. */
742
+ group: <const S extends ConfigShape>(children: S) => GroupNode<S>;
702
743
  /** A leaf of a kind a lab supplies the control for, through `controls`. */
703
744
  custom: <T>(kind: string, def: T, validate?: (leaf: ToolPrefLeaf) => string[]) => CustomNode<T>;
704
- /** Collect leaves into an instrument's config. */
705
- schema<S extends Record<string, ConfigNode>>(nodes: S): ConfigSchema<InferConfig<S>>;
745
+ /** Collect leaves and groups into an instrument's config. */
746
+ schema<S extends ConfigShape>(nodes: S): ConfigSchema<InferConfig<S>>;
706
747
  };
707
748
 
708
749
  /**
@@ -714,12 +755,37 @@ declare const f: {
714
755
  */
715
756
  declare function fromConfigFields(fields: readonly ConfigField[]): ResolvedConfig;
716
757
 
758
+ /** The value at a dotted path. `undefined` when a segment is missing or the
759
+ * walk hits something that is not a record. */
760
+ declare function valueAtPath(config: unknown, path: string): unknown;
761
+ /** Whether a dotted path is actually present, which a `undefined` value is
762
+ * not enough to tell you. */
763
+ declare function hasConfigPath(config: unknown, path: string): boolean;
764
+ /** A copy of `config` with `value` written at a dotted path. Every record on
765
+ * the way down is copied and the input is left alone; a missing — or
766
+ * non-record — intermediate is replaced with the branch the path implies. */
767
+ declare function withValueAtPath<T>(config: T, path: string, value: unknown): T;
768
+ /**
769
+ * `stored` with every gap filled from `defaults`, down the whole tree.
770
+ *
771
+ * This is how a config written before its schema grew a branch still loads:
772
+ * the branch arrives at its defaults instead of `undefined`, and a key the
773
+ * defaults no longer mention is kept rather than dropped, so nothing is lost
774
+ * to a schema change the author has not finished making.
775
+ */
776
+ declare function fillConfigDefaults<T>(stored: unknown, defaults: T): T;
777
+ /** A node in a resolved schema tree, by dotted path. Structural rather than
778
+ * `isPrefLeaf` so this stays free of a runtime import. */
779
+ declare function schemaNodeAtPath(group: ToolPrefGroup, path: string): ToolPrefLeaf | ToolPrefGroup | undefined;
780
+
717
781
  /**
718
782
  * Resolve a schema into the vocabulary weasel-ui renders, running each leaf
719
783
  * through the consumer's rules and then labkit's own.
720
784
  *
721
- * The group is flat — every leaf is a direct child — so a leaf's path is its
722
- * config key and both `ControlPanel` and `PrefsForm` address it identically.
785
+ * The group mirrors the schema's own nesting: an `f.group` becomes a nested
786
+ * `PrefGroup`, so a leaf's dotted path within the tree is the path its value
787
+ * is written at, and both `ControlPanel` and `PrefsForm` address it
788
+ * identically.
723
789
  */
724
790
  declare function resolveConfigSchema<TC>(schema: ConfigSchema<TC>, rules?: readonly ConfigRule[]): ResolvedConfig;
725
791
 
@@ -750,9 +816,10 @@ declare function applyRules(seed: LeafPatch, ctx: Omit<ConfigRuleContext, 'leaf'
750
816
  declare function useConfigSchema(instrument: Instrument<any, any, any>): ResolvedConfig;
751
817
 
752
818
  /**
753
- * Whether a leaf's row should be drawn right now.
819
+ * Whether the row at a dotted path should be drawn right now. A group answers
820
+ * for its whole subtree — hiding one hides everything under it.
754
821
  *
755
- * `hidden` is the leaf's own static flag; `showIf` is the schema's predicate
822
+ * `hidden` is the node's own static flag; `showIf` is the schema's predicate
756
823
  * over the live config. Both are presentational — a hidden leaf keeps its
757
824
  * value and the instrument still reads it.
758
825
  */
@@ -1116,5 +1183,5 @@ interface TrialChromeProps {
1116
1183
  * each region its slice. */
1117
1184
  declare function TrialChrome({ trialId, record, instrument, isLastTrial, undoBindings, job, loupe, trialChrome, chrome, suppress, activeToolId, setActiveTool, children, }: TrialChromeProps): react_jsx_runtime.JSX.Element;
1118
1185
 
1119
- export { ANNOTATION_TOOLS, ANNOTATION_WEASEL_TOOLS, AnnotationData, AnnotationKind, AnnotationMeaning, AnnotationOverlay, AnnotationTarget, AnnotationTargets, Annotations, AnnotationsApi, AnnotationsCapability, AnnotationsContext, CaptureOptions, CaptureResult, CaptureSource, ConfigField, ConfigNode, ConfigOption, ConfigRule, ConfigRuleContext, ConfigSchema, ControlRenderer, CurveField, DEFAULT_VIEW, EffectCard, EffectCardList, ExportMenu, InferConfig, Instrument, InstrumentList, JobHandle, Lab, LabContext, LabMode, LabPalette, LabShell, LeafPatch, MarkList, MarkScene, NodeOptions, PropertyGroup, PropertyListPack, ResolvedConfig, SavedSnapshot, StorageAdapter, Subpanel, Trial, TrialBody, TrialChrome, TrialContribution, TrialInfo, TrialRecord, TrialTool, ViewTransform, Workspace, WorldRect, addTrial, annotationToolInfo, applyRules, as2DView, builtinRules, capturePlan, captureTarget, cloneTrial, closeTrial, composeCaptureSvg, createMarkDrawOne, defineInstrument, f, fitView, fromConfigFields, fromWeaselView, interstellarTheme, isLeafVisible, isStale, markCommands, markSvgNodes, reorderTrials, resetTrial, resolveConfigSchema, resolveMarkStyle, seenFrom, titleCase, toWeaselView, useAnnotations, useAnnotationsOptional, useConfigSchema, useLabContext, validateConfigSchema };
1186
+ export { ANNOTATION_TOOLS, ANNOTATION_WEASEL_TOOLS, AnnotationData, AnnotationKind, AnnotationMeaning, AnnotationOverlay, AnnotationTarget, AnnotationTargets, Annotations, AnnotationsApi, AnnotationsCapability, AnnotationsContext, BranchAnnotations, BranchOptions, CaptureOptions, CaptureResult, CaptureSource, ConfigBranch, ConfigEntry, ConfigField, ConfigNode, ConfigOption, ConfigRule, ConfigRuleContext, ConfigSchema, ConfigShape, ControlRenderer, CurveField, DEFAULT_VIEW, EffectCard, EffectCardList, ExportMenu, InferConfig, Instrument, InstrumentList, JobHandle, Lab, LabContext, LabMode, LabPalette, LabShell, LeafPatch, MarkList, MarkScene, NodeOptions, PropertyGroup, PropertyListPack, ResolvedConfig, SavedSnapshot, StorageAdapter, Subpanel, Trial, TrialBody, TrialChrome, TrialContribution, TrialInfo, TrialRecord, TrialTool, ViewTransform, Workspace, WorldRect, addTrial, annotationToolInfo, applyRules, as2DView, builtinRules, capturePlan, captureTarget, cloneTrial, closeTrial, composeCaptureSvg, createMarkDrawOne, defineInstrument, f, fillConfigDefaults, fitView, fromConfigFields, fromWeaselView, hasConfigPath, interstellarTheme, isConfigBranch, isLeafVisible, isStale, markCommands, markSvgNodes, reorderTrials, resetTrial, resolveConfigSchema, resolveMarkStyle, schemaNodeAtPath, seenFrom, titleCase, toWeaselView, useAnnotations, useAnnotationsOptional, useConfigSchema, useLabContext, validateConfigSchema, valueAtPath, withValueAtPath };
1120
1187
  export type { AddTrialOptions, AnnotationOverlayProps, AnnotationTargetsProps, AnnotationToolInfo, CaptureArgs, CapturePlan, ComposeSvgArgs, ContentSize, CurveFieldProps, CurveMark, EffectCardListItem, EffectCardListProps, EffectCardProps, InstrumentSpec, LabContextValue, LabPaletteProps, LabProps, LabShellProps, MarkDrawOptions, MarkListProps, MarkStyle, PaintableMark, PaneSize, PanelDescriptor, PropertyGroupProps, SubpanelProps, TrialBodyProps, TrialChromeProps, TrialLayout, TrialProps, UndoBindings, ValidationResult, ViewTransform2D, WorkspaceProps };