@weasel-js/routing 1.9.4 → 1.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,29 +1,20 @@
1
- import { View } from '@weasel-js/geom';
1
+ import { PoseLinear, View } from '@weasel-js/geom';
2
2
  import * as react from 'react';
3
3
  import { ReactNode } from 'react';
4
+ import { Emitter } from '@weasel-js/registry';
5
+ import { ClaimableGesture, GestureSpec, IngestItem, InputEvent, TargetSpec, KeyHeldSpec, TargetPredicate, KeySpec, ParsedModifiers, GestureName } from '@weasel-js/gestures';
4
6
  import { CapabilityTag, ActiveMode, ModeRegistry } from '@weasel-js/modes';
5
7
  import { CursorSpec } from '@weasel-js/cursor';
6
- import { ClaimableGesture, GestureSpec, InputEvent, TargetSpec, KeyHeldSpec, TargetPredicate, KeySpec, ParsedModifiers, GestureName, IngestItem } from '@weasel-js/gestures';
7
- import { Op } from '@weasel-js/history';
8
8
  import '@weasel-js/select';
9
9
 
10
10
  /**
11
11
  * The primitives the routing surface is typed in.
12
12
  *
13
- * Routing decides which action a gesture runs. To do that it has to name a
14
- * viewport, a rect, a node id and a selection — but it never constructs,
15
- * projects or renders any of them. So they are declared here as structural
16
- * interfaces with no dependencies, and `@weasel-js/core` re-exports each from
17
- * the path its own call sites have always used. Same arrangement as `Op`,
18
- * which is declared in `@weasel-js/history` and re-exported by
19
- * `core/ops/types`. The selection click policy's own vocabulary comes from
20
- * `@weasel-js/select`, where the rules that read it live.
21
- *
22
- * Authority for the *behavior* behind each of these stays in core: `View` is
23
- * what `core/viewport` maintains, `SelectionApi` is what `useSelection`
24
- * returns, `DeviceProfile` is what `resolveDeviceProfile` detects. Changing
25
- * one of these shapes is a change to core's contract that happens to be
26
- * spelled here.
13
+ * Routing has to name a viewport, a rect, a node id, and a selection, but it
14
+ * never constructs, projects, or renders any of them. So they are declared
15
+ * here as structural interfaces with no dependencies, and `@weasel-js/core`
16
+ * re-exports each. The behavior behind them stays in core: changing one of
17
+ * these shapes is a change to core's contract that happens to be spelled here.
27
18
  */
28
19
 
29
20
  /** An opaque scene-node identifier. Branded so a bare string cannot stand in. */
@@ -33,8 +24,9 @@ type NodeId = string & {
33
24
 
34
25
  /**
35
26
  * Axis-aligned rectangle in world space. The optional `rotation` (radians,
36
- * around the AABB center) lets chrome attach an orientation to an otherwise
37
- * axis-aligned rect without a parallel type.
27
+ * around the AABB center) and `transform` (inside it, about the same center)
28
+ * let chrome carry the frame a box is drawn in without a parallel type;
29
+ * `poseFrameOf` composes the two.
38
30
  */
39
31
  interface Bounds {
40
32
  x: number;
@@ -42,6 +34,7 @@ interface Bounds {
42
34
  width: number;
43
35
  height: number;
44
36
  rotation?: number;
37
+ transform?: PoseLinear;
45
38
  }
46
39
  /** The selection as routing reads and writes it. */
47
40
  interface SelectionApi {
@@ -65,6 +58,7 @@ interface SelectionApi {
65
58
  * Apply a click to the selection per the configured mode/extend key.
66
59
  * - `single`: replaces selection with `[id]`, regardless of modifiers.
67
60
  * - `multi`: with the extend key held, toggles `id` in/out of the selection;
61
+ * with the range key held, selects from the last pressed id through `id`;
68
62
  * otherwise replaces with `[id]`.
69
63
  */
70
64
  applyClick(id: NodeId, modifiers: {
@@ -126,8 +120,8 @@ type HitShape = {
126
120
  /** What a recorded viewport change was: a translation or a scale change. */
127
121
  type ViewportGestureKind = 'pan' | 'zoom';
128
122
  /**
129
- * Where a tool reports its internal hit math so the debug overlay can draw it.
130
- * Routing threads a sink through `ToolCtx` and never calls one.
123
+ * Where an action reports its hit math so the debug overlay can draw it. An
124
+ * action reaches the canvas's sink as its `debug` dep; routing never calls one.
131
125
  */
132
126
  interface DebugSink {
133
127
  recordHitbox(id: string, kind: 'body' | 'handle' | 'rotation' | 'anchor', shape: HitShape): void;
@@ -188,10 +182,11 @@ type ResizeAnchor = {
188
182
  * registration lives in the node it was made in, and a lookup starts at a leaf
189
183
  * and walks up, so one canvas never answers for another.
190
184
  */
185
+
191
186
  /** What every node of one tree shares: a version and the listeners to it. */
192
187
  interface ScopeTree {
193
188
  version: number;
194
- readonly listeners: Set<() => void>;
189
+ readonly listeners: Emitter;
195
190
  }
196
191
  declare class ScopeNode<N extends ScopeNode<N>> {
197
192
  readonly parent: N | null;
@@ -270,15 +265,12 @@ declare function ActiveToolContextProvider({ children, initialActive, }: ActiveT
270
265
  declare function useActiveToolContext(): ActiveToolContextValue;
271
266
  /**
272
267
  * Like `useActiveToolContext`, but returns `null` when no
273
- * `<ActiveToolContextProvider>` is in scope instead of throwing. Used by
274
- * `useStandardActions` to preserve its silent-no-op contract when no provider
275
- * is present.
268
+ * `<ActiveToolContextProvider>` is in scope instead of throwing.
276
269
  */
277
270
  declare function useOptionalActiveToolContext(): ActiveToolContextValue | null;
278
271
  /**
279
- * Conditional `<ActiveToolContextProvider>` wrapper. Mounts a provider only
280
- * when no parent provider is in scope — otherwise renders children unwrapped
281
- * so `<WeaselProvider>` defers to the host's existing scope.
272
+ * Mounts an `<ActiveToolContextProvider>` only when no parent provider is in
273
+ * scope; otherwise renders children unwrapped.
282
274
  */
283
275
  declare function ActiveToolContextProviderIfRoot({ children, }: {
284
276
  children: ReactNode;
@@ -456,25 +448,20 @@ declare const NEVER: Rule;
456
448
  * { when: function hasPath() { … } } → when(hasPath)
457
449
  * ```
458
450
  *
459
- * Never throws and always terminates — unlike `JSON.stringify`, which throws
460
- * on a cyclic value and renders the `when` arm as a useless `{}`.
451
+ * Never throws and always terminates.
461
452
  */
462
453
  declare function describeRule(rule: Rule): string;
463
454
  /** Evaluate a rule against a context. Pure, and cheap enough to run per
464
455
  * frame for every piece of chrome. */
465
456
  declare function evaluate(rule: Rule, ctx: RuleCtx): boolean;
466
457
  /**
467
- * Composable eligibility predicate with a fluent surface. Carries its
468
- * underlying `Rule` tree at `.rule` so the dispatcher's eligibility filter and
469
- * the chrome resolver can introspect and share trees.
470
- *
471
- * Callable form `cond(ctx)` evaluates the tree against ctx. The fluent methods
472
- * return new Conditions wrapping new trees.
458
+ * Composable eligibility predicate with a fluent surface. `cond(ctx)`
459
+ * evaluates its `Rule` tree, carried at `.rule`; the fluent methods return new
460
+ * Conditions wrapping new trees.
473
461
  *
474
462
  * **Chain semantics: strict left-to-right, no precedence.** `a.and(b).or(c)` is
475
- * `(a && b) || c`; `a.or(b).and(c)` is `(a || b) && c`. Mix `.and` and `.or`
476
- * only when you mean left-to-right evaluation. For grouped disjunction, name
477
- * the subexpression or use the top-level `or(...)`.
463
+ * `(a && b) || c`; `a.or(b).and(c)` is `(a || b) && c`. For grouped
464
+ * disjunction, name the subexpression or use the top-level `or(...)`.
478
465
  */
479
466
  interface Condition {
480
467
  (ctx: RuleCtx): boolean;
@@ -542,9 +529,6 @@ declare const DEFAULT_DEVICE_PROFILE: DeviceProfile;
542
529
  * An Action's `invoker` field describes how it's run. `immediate` invokers
543
530
  * fire once (delete, align, undo). `ongoing` invokers open a phase machine
544
531
  * driven by the dispatcher (move, resize, pinch-zoom).
545
- *
546
- * See `docs/superpowers/specs/2026-05-16-registry-unification-design.md`
547
- * § "Types" for the full design.
548
532
  */
549
533
 
550
534
  /** A 2D point in either world or screen coordinates. */
@@ -553,31 +537,20 @@ interface Point2 {
553
537
  y: number;
554
538
  }
555
539
  /**
556
- * Information about which UI affordance was hit at pointerdown.
557
- *
558
- * Populated by the dispatcher when the `affordanceAt` thunk is provided to
559
- * `useGestureDispatcher`. Tools / action invokers that only fire on a specific
560
- * affordance (e.g. a resize handle) use this field as a guard — if the
561
- * affordance is absent or is the wrong kind, they return `{}` and let other
562
- * bindings handle the drag.
540
+ * Which UI affordance was hit at pointerdown. Populated by the dispatcher when
541
+ * the `affordanceAt` thunk is provided to `useGestureDispatcher`.
563
542
  *
564
543
  * `kind` is a discriminator string:
565
544
  * - `'handle:top-left'` / `'handle:top-right'` / `'handle:bottom-left'` /
566
545
  * `'handle:bottom-right'` — corner resize handles.
567
546
  * - `'rotate-handle'` — the rotation affordance.
568
547
  * - `'anchor:N'` — a path anchor at index N.
569
- *
570
- * `fixedPoint` is the world-space point that should remain stationary during
571
- * the gesture. For resize handles this is the opposite (diagonally fixed)
572
- * corner; for rotate it is the pivot.
573
- *
574
- * `targetIds` are the node ids this affordance belongs to.
575
548
  */
576
549
  interface AffordanceHit {
577
550
  /** Discriminator string, e.g. `'handle:bottom-right'`. */
578
551
  kind: string;
579
552
  /** Id of whatever produced this hit — a kit affordance's `id`, or the
580
- * registered layer's id. Read only by the dispatcher's dead-claim warning today. */
553
+ * registered layer's id. */
581
554
  owner?: string;
582
555
  /** `'exclusive'` means no binding may act on this point unless its target
583
556
  * consults the affordance. `'shared'` (the default) competes on scope and
@@ -592,25 +565,18 @@ interface AffordanceHit {
592
565
  };
593
566
  /** Which nodes this affordance belongs to. */
594
567
  targetIds?: string[];
595
- /** Set when `kind` matches `'handle:*'`. Identifies which corner stays
596
- * fixed during a resize so consumers (resizeAction) don't re-parse `kind`.
597
- * Other affordance kinds (rotate-handle, anchor:N, controlIn:N, controlOut:N)
598
- * leave this undefined. */
568
+ /** Set only when `kind` matches `'handle:*'`: which corner stays fixed
569
+ * during a resize. */
599
570
  anchor?: ResizeAnchor;
600
- /** Cursor to show while the pointer hovers this affordance (no
601
- * gesture in flight). Consumed by the hover-cursor pump in
602
- * `useGestureDispatcher`; unset = the pump falls through to
603
- * action-cursor prediction, then to the active tool's cursor. */
571
+ /** Cursor to show while the pointer hovers this affordance (no gesture in
572
+ * flight). Unset falls through to action-cursor prediction, then to the
573
+ * active tool's cursor. */
604
574
  cursor?: CursorSpec;
605
575
  /**
606
576
  * Free-form payload from whatever produced the hit, carried through to the
607
- * matching action untouched.
608
- *
609
- * Kit affordances describe themselves fully in the fields above and leave
610
- * this unset. It exists for affordances the kit doesn't know the shape of —
577
+ * matching action untouched. Kit affordances leave it unset; it exists for
611
578
  * a registered layer's own chrome, where the hit-test already resolved
612
- * *which* of its pieces was hit and the action would otherwise have to
613
- * redo that work. `@weasel-js/hud` passes the hit widget here.
579
+ * *which* of its pieces was hit.
614
580
  */
615
581
  payload?: unknown;
616
582
  }
@@ -619,9 +585,7 @@ interface AffordanceHit {
619
585
  * stylus state the originating `PointerEvent` carried.
620
586
  *
621
587
  * The stylus fields are absent for mouse/touch on browsers that don't report
622
- * them, and for synthetic events. Consumers that want pressure-driven output
623
- * (e.g. `Stroke.vertexWidths` from a pencil stroke) read them off the samples
624
- * their `insert` dep receives — see `apps/site/demos/StrokeAndFillDemo.tsx`.
588
+ * them, and for synthetic events.
625
589
  */
626
590
  interface DragSample extends Point2 {
627
591
  /** 0..1. Mouse/touch report 0.5 while a button is held, per the spec. */
@@ -659,23 +623,19 @@ interface InvocationCtx {
659
623
  * mutates the viewport itself (pan, view-zoom), because world-space
660
624
  * deltas become self-referential as the view shifts mid-drag.
661
625
  *
662
- * Populated when the dispatcher received `clientX`/`clientY` on the
663
- * underlying pointer events. Absent for legacy callers that don't
664
- * provide them.
626
+ * Absent when the underlying pointer events carried no
627
+ * `clientX`/`clientY`.
665
628
  */
666
629
  screenDelta?: Point2;
667
630
  affordance?: AffordanceHit;
668
631
  /**
669
632
  * Full pointermove history for the current drag, in world space, with
670
633
  * per-sample stylus state when the browser reported it.
671
- * Accumulated by the dispatcher on every `pointermove` pump event.
672
634
  * Available only during `onMove` and `onEnd` calls (not on `start`).
673
635
  *
674
636
  * During `onMove` this is the dispatcher's live accumulator, and it grows
675
637
  * under you — read it, don't retain it. On `onEnd` it is a snapshot, so
676
638
  * that is where an action that keeps the trail should take it.
677
- * Used by `lassoSelectAction` to build its polygon vertex list and by
678
- * `insertAction`'s pencil kind to carry the freehand stroke.
679
639
  */
680
640
  points?: DragSample[];
681
641
  };
@@ -697,12 +657,13 @@ interface InvocationCtx {
697
657
  centroid: Point2;
698
658
  /** Distance between the two primary pointers, in CSS pixels. */
699
659
  spread: number;
660
+ /** Degrees the line between the two primary pointers has turned since
661
+ * the gesture began, clockwise positive. It keeps counting past a half
662
+ * turn. Zero where the events carry no `MultiTouchEvent.angle`. */
700
663
  rotation: number;
701
664
  /**
702
- * Pinch-zoom geometry. Populated by the dispatcher when a multitouch
703
- * handle is in flight and a pointermove-pump fires.
665
+ * Pinch-zoom geometry, populated while a multitouch handle is in flight.
704
666
  * `startSpread` is the spread at the moment the gesture began.
705
- * `currentSpread` is the spread at the current frame.
706
667
  */
707
668
  pinch?: {
708
669
  startSpread: number;
@@ -723,33 +684,27 @@ interface InvocationCtx {
723
684
  viewId?: string | null;
724
685
  /**
725
686
  * Per-invocation parameters. Populated by `ActionsRegistry.begin()` for
726
- * UI-driven ongoing actions (color picker, opacity slider) so handles can
727
- * read the current value on `start` and updated values on `onMove`. The
728
- * gesture dispatcher does not populate this field; gesture-driven actions
729
- * receive params via `BindingOpts.params` on `start` (the `opts` arg).
687
+ * UI-driven ongoing actions (color picker, opacity slider). The gesture
688
+ * dispatcher does not populate this field; gesture-driven actions receive
689
+ * params via `BindingOpts.params` on `start` (the `opts` arg).
730
690
  */
731
691
  params?: Record<string, unknown>;
732
692
  }
733
693
  /** Per-invocation options the dispatcher reads from a `GestureBinding`'s
734
- * `opts` field and passes to `OngoingInvoker.start`. Today carries
735
- * behaviors; extensible. */
694
+ * `opts` field and passes to `OngoingInvoker.start`. */
736
695
  interface BindingOpts {
737
696
  /** Gesture behaviors the action's own invoker runs. Routing carries the
738
697
  * array to the action and never reads an element, so the element type is
739
- * open here; `@weasel-js/core` narrows it to `ActionBehavior<unknown,
740
- * unknown, unknown>[]`, which is what a behavior is authored against. */
698
+ * open here; `@weasel-js/core` narrows it. */
741
699
  behaviors?: unknown[];
742
- /** Per-binding action parameters. The action's invoker reads
743
- * these via the second arg to `run` (or via InvocationCtx for ongoing
744
- * invokers, when needed). Loose typing (Record<string, unknown>) for
745
- * now; consider per-action typing later via BindingOpts<A>.
700
+ /** Per-binding action parameters, which an immediate invoker reads via the
701
+ * second arg to `run`.
746
702
  *
747
- * params may also be a thunk evaluated each time the
748
- * dispatcher (or invoker) needs the value. Thunks let tools close over
749
- * refs that mutate during a gesture (e.g. polygon `sides` adjusted
750
- * mid-drag via ArrowUp). For ongoing invokers that want the latest
751
- * values at commit, the invoker can re-call the thunk inside `onEnd`
752
- * via `resolveParams(opts?.params)`. */
703
+ * May also be a thunk evaluated each time the value is needed, so a tool
704
+ * can close over refs that mutate during a gesture (e.g. polygon `sides`
705
+ * adjusted mid-drag via ArrowUp). An ongoing invoker that wants the latest
706
+ * values at commit re-calls it inside `onEnd` via
707
+ * `resolveParams(opts?.params)`. */
753
708
  params?: Record<string, unknown> | (() => Record<string, unknown>);
754
709
  /**
755
710
  * The views this binding is live in, by id, where `null` is the surface's
@@ -761,6 +716,13 @@ interface BindingOpts {
761
716
  * drag bound inside a minimap view loses to the active tool's marquee.
762
717
  */
763
718
  views?: readonly (string | null)[];
719
+ /**
720
+ * Says this binding is declared ahead of another on the same input on
721
+ * purpose: its action declines through `enabled()` when the input is not its
722
+ * to take, and the next binding fires. The dispatcher does that for any
723
+ * binding; this tells the route-conflict check the order is the design.
724
+ */
725
+ yields?: boolean;
764
726
  }
765
727
  /** Resolve `BindingOpts.params` to a concrete record (calling the thunk if
766
728
  * needed). Returns `undefined` for absent params. */
@@ -792,12 +754,10 @@ type OverlayRole =
792
754
  * `OngoingHandle.overlay()` — a marquee rect, a lasso trail, the outline of a
793
755
  * shape being dragged out, the line a slice would cut along.
794
756
  *
795
- * Every variant is world geometry and nothing else. No `DrawCommand`, no
796
- * color, no coordinate space to choose: an action that reached for any of
797
- * those would be publishing core's own 2D vocabulary, and a surface rendering
798
- * the same gesture some other way could not read it. `resolveOverlays`
799
- * normalizes this into `ResolvedOverlay`, which is what a painter consumes —
800
- * `useDispatcherOverlayLayer` is core's, and it owns every style decision.
757
+ * Every variant is world geometry and nothing else — no `DrawCommand`, no
758
+ * color, no coordinate space to choose — so a surface rendering the same
759
+ * gesture some other way can still read it. The painter owns every style
760
+ * decision.
801
761
  */
802
762
  type OngoingOverlay = {
803
763
  kind: 'marquee';
@@ -823,13 +783,9 @@ type OngoingOverlay = {
823
783
  shiftHeld: boolean;
824
784
  } | {
825
785
  /**
826
- * An open run of world points the gesture is proposing — the slice
827
- * action's cut line, `@weasel-js/diagram`'s routed connector.
828
- *
829
- * Geometry only, like every other variant: `role` is the whole of what
830
- * an action says about appearance, and the painting surface is what
831
- * turns it into a stroke. An action that picked its own color would be
832
- * unpaintable by any renderer but the one it had in mind.
786
+ * An open run of world points the gesture is proposing — a cut line, a
787
+ * routed connector. `role` is the whole of what an action says about
788
+ * appearance.
833
789
  */
834
790
  kind: 'polyline';
835
791
  /** At least two points. Anything shorter is dropped in resolution. */
@@ -841,19 +797,14 @@ type OngoingOverlay = {
841
797
  role?: OverlayRole;
842
798
  } | {
843
799
  /**
844
- * Live insert-drag preview — dispatched by `insertAction` while the
845
- * user is dragging out a new shape. Pre-commit there is no scene node
846
- * to ghost via `previewIds()`/`previewPose()`, so insert paints its
847
- * preview through the dispatcher overlay layer instead.
800
+ * Live insert-drag preview. Pre-commit there is no scene node to ghost
801
+ * via `previewIds()`/`previewPose()`, so insert paints its preview
802
+ * through the overlay instead.
848
803
  *
849
- * `shape` is the kit's built-in insert kind. `bounds` is the AABB of
850
- * the current drag (start/current normalized). `extras` is the
851
- * per-kind extras the action already collected — the overlay
852
- * renderer rebuilds the shape using the same path builders the
853
- * commit factory uses, so the preview matches the eventual node.
854
- *
855
- * `extras` is opaque (`unknown`) at the union level; the overlay
856
- * renderer narrows on `shape` and casts the field shape it expects.
804
+ * `bounds` is the AABB of the current drag (start/current normalized).
805
+ * `extras` is the per-kind extras the action already collected; the
806
+ * overlay renderer narrows on `shape` and casts the field shape it
807
+ * expects.
857
808
  */
858
809
  kind: 'insertPreview';
859
810
  /** Which preview the dispatcher's overlay draws. The kit's own shape
@@ -867,15 +818,16 @@ type OngoingOverlay = {
867
818
  height: number;
868
819
  };
869
820
  extras: unknown;
870
- /** World-space point to paint a small "anchor" dot at. Sells the
871
- * click point as the drag's anchor — particularly useful for
872
- * radial shapes (polygon/star) where no vertex sits on the
873
- * click point, and for any shape in center mode where the dot
874
- * marks the center the shape grows around. */
821
+ /** World-space point to paint a small "anchor" dot at, marking the
822
+ * click point — for radial shapes (polygon/star), where no vertex
823
+ * sits on it, and for any shape in center mode. */
875
824
  anchorPoint?: {
876
825
  x: number;
877
826
  y: number;
878
827
  };
828
+ /** World affines the shape will also be drawn under once it lands, by
829
+ * the container it is drawn into: the preview is painted under each. */
830
+ copies?: readonly (readonly [number, number, number, number, number, number])[];
879
831
  };
880
832
  /** Handle returned from an `OngoingInvoker.start`. The dispatcher pumps
881
833
  * `onMove` on subsequent input events of the same gesture and calls
@@ -884,42 +836,29 @@ type OngoingOverlay = {
884
836
  interface OngoingHandle {
885
837
  /**
886
838
  * Optional logical action kind — a stable, human-readable tag the
887
- * dispatcher exposes via `getActiveAction()` for chrome-visibility
888
- * rules and any other surface that wants to react to "what action
889
- * is currently in flight" without inspecting handles directly.
890
- *
891
- * Examples: `'marquee'`, `'lasso'`, `'move'`, `'resize'`, `'rotate'`,
892
- * `'pan'`, `'pinch'`.
893
- *
894
- * Distinct from the dispatcher's internal `gestureId` (`pointer-mouse`,
895
- * `key-held-Space`, etc.) which keys per-pointer state and is not
896
- * meaningful to consumers.
839
+ * dispatcher exposes via `getActiveAction()`, e.g. `'marquee'`, `'move'`,
840
+ * `'resize'`, `'pan'`.
897
841
  *
898
- * When omitted, the action is "anonymous" — `getActiveAction().kind`
899
- * reports `null` even though a handle is in flight. This is fine for
900
- * actions that don't have visible chrome of their own.
842
+ * When omitted, `getActiveAction().kind` reports `null` even though a
843
+ * handle is in flight.
901
844
  */
902
845
  kind?: string;
846
+ /**
847
+ * The cursor this gesture shows now, read after every move. Wins over the
848
+ * action's `activeCursor`, for a cursor that follows the gesture — the
849
+ * rotate glyph turning about its pivot. `undefined` defers to the action.
850
+ */
851
+ cursor?(): CursorSpec | undefined;
903
852
  onMove?(ctx: InvocationCtx): void;
904
853
  onEnd?(ctx: InvocationCtx, reason: 'commit' | 'cancel'): void;
905
854
  /**
906
- * Optional preview surface — dispatcher-side ghost overlay.
907
- *
908
- * An ongoing-action implementation may populate `previewIds()` +
909
- * `previewPose(id)` to expose its in-flight preview state for the
910
- * canvas's preview-ghost layer (`usePreviewGhostLayer`) to render on
911
- * top of the committed scene during the gesture.
855
+ * Optional preview surface: the in-flight state the canvas's preview-ghost
856
+ * layer renders on top of the committed scene during the gesture.
912
857
  *
913
- * Returning `null` (or omitting the method entirely) means "no preview
914
- * this gesture" — the canvas will skip this handle as a source.
915
- *
916
- * Semantics mirror the tool-side `Tool.previewIds` / `Tool.previewPose`
917
- * pair: `previewIds()` enumerates the displaced node ids; `previewPose(id)`
918
- * returns the interim pose for one of those ids (shape opaque — the
919
- * canvas casts to its `TPose` parameter). The preview-ghost layer
920
- * merges all sources via first-non-null semantics, with tool-side
921
- * previews taking precedence over dispatcher-side (preserves
922
- * backwards-compat during the registry-unification migration).
858
+ * `previewIds()` enumerates the displaced node ids; `previewPose(id)`
859
+ * returns the interim pose for one of them (shape opaque — the canvas
860
+ * casts to its `TPose` parameter). Returning `null`, or omitting the
861
+ * method, means "no preview this gesture".
923
862
  */
924
863
  previewIds?(): Iterable<string> | null;
925
864
  previewPose?(id: string): unknown | null;
@@ -932,40 +871,22 @@ interface OngoingHandle {
932
871
  */
933
872
  previewOpaqueIds?(): Iterable<string> | null;
934
873
  /**
935
- * When `false`, the preview-ghost layer paints the ghost AND the
936
- * source node stays visible at its committed pose. Defaults to
937
- * `true` (move/resize/rotate semantics: ghost replaces the source
938
- * during the gesture). Clone overrides to `false` so the original
939
- * stays put and the ghost appears at the drag target.
874
+ * When `false`, the preview-ghost layer paints the ghost AND the source
875
+ * node stays visible at its committed pose, as a clone wants. Defaults to
876
+ * `true`: the ghost replaces the source during the gesture.
940
877
  */
941
878
  previewHidesSource?: boolean;
942
879
  /**
943
- * Optional per-id preview *data*. Falls back to the committed
944
- * `node.data` when null/absent. Use when the gesture mutates
945
- * `node.data` (e.g. anchor-edit on nodes that store the polygon on
946
- * `data.path`) rather than (or in addition to) the pose. The preview-
947
- * ghost layer assembles a synthetic node from `{ ...node, pose:
948
- * previewPose ?? node.pose, data: previewData ?? node.data }` before
949
- * calling the scene slot's `drawOne`.
950
- *
951
- * Sources compose first-non-null per axis: an action can emit only
952
- * `previewPose` (translation), only `previewData` (data-only edit),
953
- * or both (pose + data both change, e.g. anchor drag on a data.path
954
- * node where the bounds shift).
880
+ * Optional per-id preview *data*, for a gesture that mutates `node.data`
881
+ * (e.g. anchor-edit on nodes that store the polygon on `data.path`) rather
882
+ * than, or in addition to, the pose. Falls back to the committed
883
+ * `node.data` when null/absent.
955
884
  */
956
885
  previewData?(id: string): unknown | null;
957
886
  /**
958
- * Optional chrome surface — dispatcher-side overlay layer.
959
- *
960
- * An ongoing-action implementation may populate `overlay()` to expose a
961
- * non-ghost visual (marquee rectangle, lasso polyline) for the canvas's
962
- * `useDispatcherOverlayLayer` to paint while the gesture is in flight.
963
- * Returning `null` (or omitting the method) means "no overlay this
964
- * gesture" — the canvas will skip this handle as a chrome source.
965
- *
966
- * Distinct from the `previewIds()`/`previewPose(id)` ghost surface,
967
- * which paints displaced scene-node silhouettes. Marquee and lasso
968
- * gestures don't displace any node, but still need on-screen feedback.
887
+ * Optional chrome surface: a non-ghost visual (marquee rectangle, lasso
888
+ * polyline) painted while the gesture is in flight. Returning `null`, or
889
+ * omitting the method, means "no overlay this gesture".
969
890
  */
970
891
  overlay?(): OngoingOverlay | null;
971
892
  }
@@ -984,17 +905,13 @@ interface OngoingInvoker {
984
905
  timing: 'ongoing';
985
906
  start(ctx: InvocationCtx, opts?: BindingOpts): OngoingHandle;
986
907
  }
987
- /** Pluggable invocation strategy for an Action. Future variants
988
- * (`longPress`, `twoStage`, `modal`) extend this union without touching
989
- * the `Action` type. */
908
+ /** Pluggable invocation strategy for an Action. */
990
909
  type Invoker = ImmediateInvoker | OngoingInvoker;
991
910
 
992
911
  /**
993
912
  * GestureBinding — connects a GestureSpec to an Action id (with per-binding
994
913
  * options). Tools own arrays of these on their `bindings` field; ambient
995
914
  * gesture-bindings are registered globally.
996
- *
997
- * See `docs/superpowers/specs/2026-05-16-registry-unification-design.md`.
998
915
  */
999
916
 
1000
917
  /** An interaction: a gesture spec composed with the id of the action it
@@ -1006,7 +923,6 @@ interface GestureBinding {
1006
923
  opts?: BindingOpts;
1007
924
  }
1008
925
  /**
1009
- * @experimental
1010
926
  * A single entry in `Action.defaultBinding[]`. Either a bare `GestureSpec`
1011
927
  * (no per-binding opts) or an object form that pairs a spec with
1012
928
  * `BindingOpts` for parametric actions (e.g. `{ params: { axis: 'x' } }`).
@@ -1064,11 +980,9 @@ type ActionDisabledReason = (typeof ActionDisabledReason)[keyof typeof ActionDis
1064
980
  */
1065
981
  interface ActionPresentation {
1066
982
  label: string;
1067
- /** Inline-SVG icon for palette / toolbar surfaces. Mirrors
1068
- * `ToolPresentation.icon` so a generic `<ActionBar>` can render from
1069
- * action metadata the same way `<ToolPalette>` renders from tool
1070
- * metadata. May be a static `ReactNode` or a function (rare; useful
1071
- * for state-aware icons like a "lock" toggle). */
983
+ /** Inline-SVG icon for palette / toolbar surfaces. May be a static
984
+ * `ReactNode` or a function (rare; useful for state-aware icons like a
985
+ * "lock" toggle). */
1072
986
  icon?: ReactNode | (() => ReactNode);
1073
987
  /** Grouping key for palette/menu surfaces. Free-form string; the kit
1074
988
  * ships defaults for `'align'` (six edges/centers), `'distribute'`
@@ -1124,30 +1038,21 @@ interface ActionDispatch extends BindingSource {
1124
1038
  /**
1125
1039
  * @experimental
1126
1040
  * Optional predicate saying whether the action can run right now. Return
1127
- * `true` when it can, or a reason string (e.g. `'Selection required'`)
1128
- * when it cannot. The command palette greys out a disabled row, skips it
1129
- * in keyboard nav, ignores clicks, and shows the reason next to the label.
1130
- * The dispatcher skips a disabled action too, on a key as on a pointer, and
1131
- * falls through to the next binding that matches. `ActionsRegistry.trigger`
1132
- * does not ask, so the action's own `run` should still self-guard.
1041
+ * `true` when it can, or an `ActionDisabledReason` when it cannot. The
1042
+ * command palette greys out a disabled row, skips it in keyboard nav,
1043
+ * ignores clicks, and shows the reason next to the label. The dispatcher
1044
+ * skips a disabled action too, on a key as on a pointer, and falls through
1045
+ * to the next binding that matches. `ActionsRegistry.trigger` does not ask,
1046
+ * so the action's own `run` should still self-guard.
1133
1047
  *
1134
1048
  * **Contract:** must be pure (no side effects), fast (< 4ms in dev), and
1135
- * must not throw. If a call throws or exceeds the budget in dev mode,
1136
- * `evaluateEnabled` logs a one-time warning per action id; throws are
1137
- * caught and treated as disabled with reason `'(predicate threw)'`.
1049
+ * must not throw. A throw is caught and treated as disabled with reason
1050
+ * `ActionDisabledReason.PredicateThrew`.
1138
1051
  *
1139
- * Snapshot-on-open semantics: the palette evaluates `enabled` once when
1140
- * opened and does NOT re-evaluate on selection changes while open. Live
1141
- * reactive updates are deferred — palette is short-lived.
1052
+ * The palette evaluates `enabled` once when opened and does NOT re-evaluate
1053
+ * on selection changes while open.
1142
1054
  *
1143
- * The reason set is a closed enum — to add a new reason, edit
1144
- * `ActionDisabledReason` and the consumer's display map.
1145
- *
1146
- * The optional `deps` argument is the same bag passed to
1147
- * `ImmediateInvoker.run`; callers (`evaluateEnabled` / the ActionBar) may
1148
- * synthesize it from the surrounding `DepRegistry` so predicates can
1149
- * inspect selection / scene / etc. Predicates that don't need deps just
1150
- * ignore the arg.
1055
+ * `deps` is the same bag passed to `ImmediateInvoker.run`.
1151
1056
  *
1152
1057
  * `at` is the world point of the event being routed, when it has one — a
1153
1058
  * click, a press, a long-press. An action that only applies somewhere
@@ -1155,10 +1060,18 @@ interface ActionDispatch extends BindingSource {
1155
1060
  * falls through to the next binding and hover prediction shows the
1156
1061
  * action's `cursor` only where it would act. Palettes and keystrokes
1157
1062
  * pass no point; treat absent as "anywhere".
1063
+ *
1064
+ * `carried` is what a drop or a paste being routed brings: its items. For
1065
+ * a drag still in flight they hold a MIME and nothing else, since a
1066
+ * browser withholds contents until the drop. An action that takes only
1067
+ * some content refuses the rest here, so a drag it would not take is not
1068
+ * accepted and a paste it would not take stays the page's.
1158
1069
  */
1159
1070
  enabled?: (deps?: ActionDeps, at?: {
1160
1071
  x: number;
1161
1072
  y: number;
1073
+ }, carried?: {
1074
+ items: readonly IngestItem[];
1162
1075
  }) => true | ActionDisabledReason;
1163
1076
  /**
1164
1077
  * @experimental
@@ -1187,12 +1100,11 @@ interface ActionDispatch extends BindingSource {
1187
1100
  /**
1188
1101
  * CSS cursor shown while the pointer hovers a spot where this action
1189
1102
  * would win the drag — or, when the drag's winner declares no cursor,
1190
- * the click. The hover-cursor pump (in `useGestureDispatcher`) runs
1191
- * `Dispatcher.resolveOnly` on each idle pointermove — the same match
1192
- * walk a real press takes — and applies the winning action's `cursor`,
1193
- * so the hint and the actual target stay in sync by construction. Omitted = no override (the active tool's
1194
- * `Tool.cursor` shows). Affordance hits are resolved earlier in the
1195
- * pump via `AffordanceRegion.cursor` and never reach this field.
1103
+ * the click. The hover-cursor pump runs the same match walk a real press
1104
+ * takes, so the hint and the actual target stay in sync. Omitted = no
1105
+ * override (the active tool's `Tool.cursor` shows). Affordance hits are
1106
+ * resolved earlier in the pump via `AffordanceRegion.cursor` and never
1107
+ * reach this field.
1196
1108
  *
1197
1109
  * Static value only. Prediction runs `enabled()` but cannot run the
1198
1110
  * invoker, so an action that matches yet bails at `start()` (empty
@@ -1210,19 +1122,12 @@ interface ActionDispatch extends BindingSource {
1210
1122
  * ("you are panning"). An action can declare either, both, or neither;
1211
1123
  * with only `cursor` set, the hover hint holds for the duration of the
1212
1124
  * gesture.
1213
- *
1214
- * This is where mid-gesture cursors live now. They used to come from the
1215
- * tool side — an `engaged` phase entry's cursor,
1216
- * or a function-form `Tool.cursor` reading the gesture scratch out of the
1217
- * tool-routing dispatcher. Both belonged to a pipeline whose whole job was
1218
- * being taken over by bindings, and neither could describe a cursor for an
1219
- * action a tool doesn't own.
1220
1125
  */
1221
1126
  activeCursor?: CursorSpec;
1222
1127
  }
1223
1128
  /**
1224
1129
  * @experimental
1225
- * Single registered action. v1: one binding per action.
1130
+ * Single registered action.
1226
1131
  */
1227
1132
  interface Action extends ActionDispatch, ActionPresentation {
1228
1133
  }
@@ -1237,6 +1142,37 @@ interface ActionSource {
1237
1142
  list(): readonly Action[];
1238
1143
  }
1239
1144
 
1145
+ /**
1146
+ * Tuning: what a host changes about an action it did not write — the params
1147
+ * every invocation starts from, and bindings added beside the action's own —
1148
+ * without restating the descriptor. `ActionsRegistry.tune` holds tunings and
1149
+ * applies them to whatever descriptor is live for the id.
1150
+ */
1151
+
1152
+ /** An action's params, or a thunk read each time the action asks for them. */
1153
+ type ActionParams<P = Record<string, unknown>> = Partial<P> | (() => Partial<P>);
1154
+ /** A binding added for an action: a gesture alone, or a gesture with the
1155
+ * options it passes — the {@link BoundGesture} shape, its params typed. */
1156
+ type TunedBinding<P = Record<string, unknown>> = GestureSpec | {
1157
+ spec: GestureSpec;
1158
+ opts: Omit<BindingOpts, 'params'> & {
1159
+ params?: ActionParams<P>;
1160
+ };
1161
+ };
1162
+ /** What a host changes about one action. */
1163
+ interface ActionTuning<P = Record<string, unknown>> {
1164
+ /** Params every invocation starts from. What a binding or a `trigger` call
1165
+ * passes is laid over them, so these fill in what it leaves out. A thunk is
1166
+ * read on every call, and an ongoing action resolving its `opts.params`
1167
+ * mid-gesture sees the value as it is then. */
1168
+ params?: ActionParams<P>;
1169
+ /** Bindings added after the action's own `defaultBinding`. */
1170
+ bindings?: readonly TunedBinding<P>[];
1171
+ }
1172
+ /** `action` with each tuning applied in turn, a later one's params over an
1173
+ * earlier one's. Applying a tuning to a copy it already tuned changes nothing. */
1174
+ declare function tuneAction(action: Action, tunings: readonly ActionTuning[]): Action;
1175
+
1240
1176
  /**
1241
1177
  * @experimental
1242
1178
  * The entries an action contributes to a bar, menu or palette — one per
@@ -1290,20 +1226,9 @@ declare function evaluateEnabled(action: Action, deps?: ActionDeps): ActionEnabl
1290
1226
  declare function evaluateActive(action: Action, deps?: ActionDeps): boolean | undefined;
1291
1227
 
1292
1228
  /**
1293
- * Dep registry for registry unification.
1294
- *
1295
- * Holds named "live source" thunks. Actions declare `requires: ['selection']`;
1296
- * the dispatcher calls `registry.get('selection')` at invocation time to
1297
- * build a typed Deps bag.
1298
- *
1299
- * `DepSchema` — the map of dep name → value type — is the single source of
1300
- * truth for what `requires`/`register`/`get` accept. Its concrete fields live
1301
- * in `./depSchema` (co-located with the dep value types). It is declared there
1302
- * as a plain `export interface` rather than via a cross-module
1303
- * `declare module './depRegistry'` augmentation: that augmentation does NOT
1304
- * merge once rollup-plugin-dts flattens both files into one `.d.ts` chunk,
1305
- * which silently emptied `DepSchema` for consumers. Consumers still extend it
1306
- * via `declare module '@weasel-js/core'` augmentation.
1229
+ * The dep registry: named "live source" thunks. Actions declare
1230
+ * `requires: ['selection']`; the dispatcher calls `registry.get('selection')`
1231
+ * at invocation time to build a typed Deps bag.
1307
1232
  */
1308
1233
 
1309
1234
  /** Holds the live sources an action's declared dependencies resolve to.
@@ -1362,12 +1287,12 @@ type HotkeyTrigger = 'space' | 'alt' | 'ctrl' | 'meta' | 'shift' | (string & {})
1362
1287
  *
1363
1288
  * Note: cursor is NOT here. `Tool.cursor` (inherited from `Contribution`)
1364
1289
  * is already plumbed through `<Canvas>` to `style.cursor` on the host. */
1365
- interface ToolPresentation<TScratch = unknown> {
1290
+ interface ToolPresentation {
1366
1291
  /** Human-readable label, distinct from the `id`. Falls back to `id`. */
1367
1292
  label?: string;
1368
- /** Inline-SVG icon component output. May be a static `ReactNode` or a
1369
- * function of scratch state (rare; useful for shape-aware affordances). */
1370
- icon?: react.ReactNode | ((scratch?: TScratch) => react.ReactNode);
1293
+ /** Inline-SVG icon component output: a static `ReactNode`, or a function
1294
+ * returning one. */
1295
+ icon?: react.ReactNode | (() => react.ReactNode);
1371
1296
  /** Palette grouping key. Tools sharing a group render contiguously
1372
1297
  * with separators between groups. Free-form string; the kit
1373
1298
  * recommends 'select' | 'shape' | 'draw' | 'type' | 'view'. */
@@ -1447,9 +1372,8 @@ interface Contribution<TOverlay = KernelOverlay> extends ContributionRouting, Co
1447
1372
  }
1448
1373
 
1449
1374
  /**
1450
- * Pure matcher primitives live in `@weasel-js/gestures`. This file
1451
- * re-exports them for kit-internal consumers and layers the actions-layer
1452
- * binding-scope / matchBest logic on top.
1375
+ * Re-exports the pure matcher primitives from `@weasel-js/gestures` and layers
1376
+ * the actions-layer binding-scope / matchBest logic on top.
1453
1377
  */
1454
1378
 
1455
1379
  /** Where a binding came from, which is also its priority: a held hotkey beats
@@ -1478,25 +1402,13 @@ interface MatchResult {
1478
1402
  * only the body classification. `kindOf` predicates are handed the hit;
1479
1403
  * `affordance:<k>` matches on its `kind`. The body-class strings (`'empty'`,
1480
1404
  * `'selected-body'`, `'unselected-body'`) and the `kind:` forms resolve from
1481
- * `bodyTarget` / `bodyKind` and never see it — which is why chrome floating
1482
- * over empty canvas used to read as empty canvas.
1405
+ * `bodyTarget` / `bodyKind` and never see it.
1483
1406
  *
1484
1407
  * A predicate declares which it is with `readsAffordance`; one that declares
1485
1408
  * nothing, which only untyped code can write, counts as not reading the hit.
1486
1409
  */
1487
1410
  declare function targetConsultsAffordance(specTarget: TargetSpec | undefined): boolean;
1488
- /**
1489
- * Find the best-matching binding across all scopes.
1490
- *
1491
- * Precedence: hotkey > active > ambient.
1492
- * Within a scope, more-specific bindings win; same-specificity bindings keep
1493
- * registration order (see `matchSorted`).
1494
- * Returns null when nothing matches.
1495
- *
1496
- * Note: this returns only the single best match. The dispatcher uses
1497
- * `matchSorted` so it can fall through to lower-specificity matches when a
1498
- * higher-specificity match's `enabled()` reports disabled.
1499
- */
1411
+ /** The first of {@link matchSorted}'s matches, or null when nothing matches. */
1500
1412
  declare function matchBest(e: InputEvent, bindings: readonly ScopedBinding[], isMac: boolean, engagedChannels?: ReadonlySet<string>): MatchResult | null;
1501
1413
  /**
1502
1414
  * Find every binding that matches the event, sorted by precedence
@@ -1505,12 +1417,7 @@ declare function matchBest(e: InputEvent, bindings: readonly ScopedBinding[], is
1505
1417
  * - Scopes by priority: hotkey > active > ambient.
1506
1418
  * - Within a scope, more-specific bindings beat less-specific (see
1507
1419
  * `specificity()`).
1508
- * - Among same-specificity bindings, first-declared wins (stable sort
1509
- * preserves registration order — same tiebreaker as pre-specificity).
1510
- *
1511
- * Used by the dispatcher to implement specificity-ordered fall-through:
1512
- * when the top match's `enabled()` returns a disabled reason (or the
1513
- * action's `start()` returns an empty handle), the next match is tried.
1420
+ * - Among same-specificity bindings, first-declared wins.
1514
1421
  */
1515
1422
  declare function matchSorted(e: InputEvent, bindings: readonly ScopedBinding[], isMac: boolean, engagedChannels?: ReadonlySet<string>, warn?: (message: string) => void): MatchResult[];
1516
1423
  /** {@link matchSorted}, plus what an exclusive claim on the event barred. */
@@ -1686,11 +1593,9 @@ interface RankedCandidate {
1686
1593
  * `createDispatcher` in `dispatcher.ts` implements it.
1687
1594
  */
1688
1595
 
1689
- /** Everything the dispatcher must consult to route one input event: the
1690
- * registered actions and their deps, which tool is active, which hotkeys are
1691
- * held, and — optionally — the chrome state that action eligibility rules are
1692
- * evaluated against. Rebuilt per event rather than held, so the dispatcher
1693
- * itself stays stateless apart from in-flight gestures. */
1596
+ /** Everything the dispatcher must consult to route one input event. Rebuilt
1597
+ * per event rather than held, so the dispatcher itself stays stateless apart
1598
+ * from in-flight gestures. */
1694
1599
  interface DispatcherContext {
1695
1600
  /** All registered actions; the dispatcher walks `.defaultBinding` for ambient bindings. */
1696
1601
  actions: ActionSource;
@@ -1741,9 +1646,7 @@ interface UiOngoingControl {
1741
1646
  }
1742
1647
  /**
1743
1648
  * Successful `Dispatcher.resolveOnly` prediction: the binding + action that
1744
- * would fire if `event` were dispatched for real. `action` is the resolved
1745
- * descriptor so callers (the hover-cursor pump) can read metadata like
1746
- * `Action.cursor` without a second registry lookup.
1649
+ * would fire if `event` were dispatched for real.
1747
1650
  */
1748
1651
  interface ResolveOnlyResult {
1749
1652
  actionId: string;
@@ -1796,14 +1699,12 @@ interface ResolveAllOptions {
1796
1699
  * Evaluate eligibility and `enabled()` for candidates below the winner
1797
1700
  * instead of short-circuiting them to `shadowed`.
1798
1701
  *
1799
- * Off by default, and deliberately so: the default walk's early exit is what
1800
- * keeps `resolveOnly`'s `enabled()` call count identical to a real dispatch,
1801
- * and `enabled()` predicates are only contractually pure — not free.
1702
+ * Off by default: the default walk's early exit is what keeps
1703
+ * `resolveOnly`'s `enabled()` call count identical to a real dispatch, and
1704
+ * `enabled()` predicates are only contractually pure — not free.
1802
1705
  *
1803
- * Turn it on for diagnostics, where "this one was outranked" is a less
1804
- * useful answer than "this one was outranked AND would have been disabled
1805
- * anyway". With it on, `shadowed` narrows to its precise meaning: this
1806
- * candidate would have fired, but something above it did.
1706
+ * With it on, `shadowed` narrows to its precise meaning: this candidate
1707
+ * would have fired, but something above it did.
1807
1708
  */
1808
1709
  evaluateShadowed?: boolean;
1809
1710
  }
@@ -1825,12 +1726,10 @@ interface Dispatcher {
1825
1726
  */
1826
1727
  handleInput(event: InputEvent, ctx: DispatcherContext): 'handled' | 'unhandled';
1827
1728
  /**
1828
- * Predict which action `event` would route to WITHOUT invoking it. Replays
1829
- * the same walk as `handleInput` — scope assembly, specificity-sorted
1830
- * match, eligibility filter, per-candidate `enabled()` gate — and returns
1831
- * the first candidate that would fire, or `null` when the event would go
1832
- * unhandled. Pure query: no invoker runs, no in-flight state changes, no
1833
- * trace-log entry.
1729
+ * Predict which action `event` would route to WITHOUT invoking it: the
1730
+ * first candidate `handleInput`'s walk would fire, or `null` when the event
1731
+ * would go unhandled. Pure query: no invoker runs, no in-flight state
1732
+ * changes, no trace-log entry.
1834
1733
  *
1835
1734
  * Known divergence from a real dispatch: an ongoing invoker that matches
1836
1735
  * but returns an empty handle at `start()` (runtime bail) makes the real
@@ -1841,25 +1740,15 @@ interface Dispatcher {
1841
1740
  resolveOnly(event: InputEvent, ctx: DispatcherContext): ResolveOnlyResult | null;
1842
1741
  /**
1843
1742
  * Every binding that matches `event`, in dispatch precedence order, each
1844
- * with a verdict explaining whether it would fire. Same walk as
1845
- * `resolveOnly` — scope assembly, specificity-sorted match, eligibility
1846
- * check, per-candidate `enabled()` gate — without stopping at the winner
1847
- * and without invoking anything. Nothing is dropped: candidates that
1848
- * `resolveOnly`'s walk would filter out are kept here and labelled
1849
- * `ineligible` instead. Pure query: no invoker runs, no in-flight state
1850
- * changes, no trace-log entry.
1743
+ * with a verdict explaining whether it would fire. Nothing is dropped:
1744
+ * candidates that `resolveOnly`'s walk would filter out are kept here and
1745
+ * labeled `ineligible` instead. Pure query, and it shares `resolveOnly`'s
1746
+ * known divergence from a real dispatch.
1851
1747
  *
1852
1748
  * `resolveOnly` is the first `would-fire` entry of this list.
1853
1749
  *
1854
- * Shares `resolveOnly`'s known divergence from a real dispatch: an ongoing
1855
- * invoker that matches but returns an empty handle at `start()` makes the
1856
- * real dispatch fall through, and this cannot see that.
1857
- *
1858
- * By default everything below the winner is `shadowed` without being asked,
1859
- * which is what keeps this walk as cheap as the dispatch it replays. Pass
1860
- * `{ evaluateShadowed: true }` to keep evaluating past the winner, so a
1861
- * lower candidate that is ALSO ineligible or disabled says so — see
1862
- * {@link ResolveAllOptions.evaluateShadowed}.
1750
+ * By default everything below the winner is `shadowed` without being asked
1751
+ * — see {@link ResolveAllOptions.evaluateShadowed}.
1863
1752
  */
1864
1753
  resolveAll(event: InputEvent, ctx: DispatcherContext, opts?: ResolveAllOptions): ResolvedCandidate[];
1865
1754
  /**
@@ -1872,7 +1761,6 @@ interface Dispatcher {
1872
1761
  explain(event: InputEvent, ctx: DispatcherContext, opts?: ResolveAllOptions): DispatchRecord;
1873
1762
  /**
1874
1763
  * Synthesize an end-of-gesture for every in-flight ongoing handle.
1875
- * Used by tool-switch cancellation (Q2 decision).
1876
1764
  */
1877
1765
  cancelAll(reason: 'commit' | 'cancel'): void;
1878
1766
  /**
@@ -1888,41 +1776,31 @@ interface Dispatcher {
1888
1776
  */
1889
1777
  inFlightCursor(): CursorSpec | null;
1890
1778
  /**
1891
- * Read-only iterator over currently in-flight `OngoingHandle` instances.
1892
- *
1893
- * Surface for the canvas's preview-ghost layer (`usePreviewGhostLayer`)
1894
- * to walk each handle's `previewIds()` / `previewPose(id)` and render
1895
- * dispatcher-driven gesture previews. Read-only by design:
1896
- * external consumers must not mutate the in-flight map.
1779
+ * Read-only iterator over currently in-flight `OngoingHandle` instances,
1780
+ * for a preview layer to walk each handle's `previewIds()` /
1781
+ * `previewPose(id)`.
1897
1782
  */
1898
1783
  getInFlightHandles(): Iterable<OngoingHandle>;
1899
1784
  /**
1900
- * Subscribe to in-flight state changes. The callback fires after every
1901
- * mutation that affects what the preview-ghost / dispatcher-overlay
1902
- * layers read — handle start, every `onMove` pump, end, cancel,
1903
- * cancel-all — and after any other input handled while a gesture is in
1904
- * flight. Input that arrives with nothing in flight and starts nothing
1905
- * does not fire it. Consumers re-read `getInFlightHandles()` and re-render.
1785
+ * Subscribe to in-flight state changes. The callback fires after handle
1786
+ * start, every `onMove` pump, end, cancel, and cancel-all, and after any
1787
+ * other input handled while a gesture is in flight. Input that arrives
1788
+ * with nothing in flight and starts nothing does not fire it.
1906
1789
  *
1907
1790
  * Returns an unsubscribe function.
1908
1791
  */
1909
1792
  subscribe(fn: () => void): () => void;
1910
1793
  /**
1911
1794
  * Monotonic counter bumped on exactly the events {@link subscribe} fires
1912
- * on. The snapshot half of the `useSyncExternalStore` contract: pair it
1913
- * with `subscribe` to drive a render off in-flight gesture state without
1914
- * a `useReducer` force-rerender.
1795
+ * on: the snapshot half of the `useSyncExternalStore` contract.
1915
1796
  *
1916
- * Starts at 0 and only ever increases. Two reads returning the same number
1917
- * mean no gesture was in flight in between; it does **not** guarantee that
1918
- * a bump changed anything observable (input that matched no binding still
1919
- * counts while a gesture is in flight — see `subscribe`).
1797
+ * A bump does **not** guarantee that anything observable changed (input
1798
+ * that matched no binding still counts while a gesture is in flight — see
1799
+ * `subscribe`).
1920
1800
  */
1921
1801
  getVersion(): number;
1922
1802
  /**
1923
- * Snapshot of the currently active action, for surfaces (chrome-caps
1924
- * visibility rules, debug HUDs) that need to react to "what action
1925
- * is in flight right now."
1803
+ * Snapshot of the currently active action.
1926
1804
  *
1927
1805
  * - `kind` — the `OngoingHandle.kind` reported by the in-flight
1928
1806
  * handle (e.g. `'marquee'`, `'move'`). `null` when no action is
@@ -1933,26 +1811,15 @@ interface Dispatcher {
1933
1811
  *
1934
1812
  * When multiple handles are in flight simultaneously (e.g. a key-held
1935
1813
  * action overlapping a pointer action), the most-recently-started
1936
- * handle wins. This matches user intent: the latest interaction is
1937
- * the one consumers care about.
1938
- *
1939
- * That rule used to be near-vacuous on the pointer side, because every
1940
- * pointer shared one handle slot and two pointer drags could not coexist.
1941
- * With per-pointer keying they can — but only via paths that bypass the
1942
- * multi-pointer policy in `useGestureDispatcher` (which stops a second
1943
- * finger from opening a drag while a pinch is live), such as a mouse and
1944
- * a pen used together. Latest-start remains the right answer there.
1814
+ * handle wins.
1945
1815
  */
1946
1816
  getActiveAction(): {
1947
1817
  kind: string | null;
1948
1818
  id: string | null;
1949
1819
  };
1950
1820
  /**
1951
- * Start an ongoing action driven by UI (not a gesture). Builds an
1952
- * `InvocationCtx` with the given `deps` and `params`, calls
1953
- * `action.invoker.start(ctx, { params })`, and registers the returned
1954
- * handle in the in-flight map so `getInFlightHandles()` reports it —
1955
- * enabling preview rendering via `SceneCanvas`.
1821
+ * Start an ongoing action driven by UI (not a gesture), and register its
1822
+ * handle in the in-flight map so `getInFlightHandles()` reports it.
1956
1823
  *
1957
1824
  * Returns `null` if `actionId` is unknown, the action's invoker is not
1958
1825
  * ongoing, or `start` returned an empty handle.
@@ -1969,10 +1836,8 @@ interface Dispatcher {
1969
1836
  */
1970
1837
 
1971
1838
  /** One entry per "mode change" — a kit state transition that isn't a
1972
- * dispatched input event but is load-bearing for understanding why an
1973
- * input was (or wasn't) handled later. Pushed via `recordModeSwitch()`;
1974
- * consumers include the editAnchors dep (editingId changes), and
1975
- * could expand to active-tool / hotkey-stack / focus changes. */
1839
+ * dispatched input event but explains why an input was (or wasn't) handled
1840
+ * later. Pushed via `recordModeSwitch()`. */
1976
1841
  interface ModeSwitchLogEntry {
1977
1842
  kind: 'mode';
1978
1843
  ts: number;
@@ -1991,12 +1856,31 @@ type TraceLogEntry = DispatchRecord | ModeSwitchLogEntry;
1991
1856
  declare function recordModeSwitch(mode: string, from: string | null, to: string | null, detail?: string): void;
1992
1857
 
1993
1858
  /**
1994
- * Dispatcher orchestrator — pure module, no React, no DOM.
1859
+ * The slot an in-flight ongoing handle is keyed by:
1860
+ * - `key-held`: `key-held-<code>` (`key-held-Space`), so two keys that share a
1861
+ * `key`, such as the two Shifts, hold separately. An event with no code keys
1862
+ * by `key`.
1863
+ * - `pointerdown` and the drag that follows: `pointer-<pointerId>`. An event
1864
+ * with no `pointerId` (a synthesized probe, a programmatic drag, most tests)
1865
+ * keys to `pointer-mouse`.
1866
+ * - `multitouch`: `multitouch-<fingers>`.
1867
+ * - any other kind that starts an ongoing invoker: `ongoing-<kind>`.
1868
+ */
1869
+
1870
+ /**
1871
+ * The in-flight handle slot a pointer's gestures key into. Exported because
1872
+ * the React seam (`useGestureDispatcher`) has to name the same slot when it
1873
+ * asks `dispatcher.inFlight()` whether a given pointer is mid-drag.
1995
1874
  *
1996
- * Assembles `ScopedBinding[]` from the actions registry, active tool, and
1997
- * hotkey stack; matches input events via `matchSorted`; gates each candidate
1998
- * on `enabled()`; then invokes `immediate` or `ongoing` invokers and tracks
1999
- * in-flight handles.
1875
+ * @param pointerId - `PointerEvent.pointerId`, or `undefined` for a
1876
+ * synthesized event with no originating DOM pointer.
1877
+ */
1878
+ declare function pointerGestureId(pointerId: number | undefined): string;
1879
+ /** The in-flight handle slot a multitouch gesture of `fingers` fingers keys into. */
1880
+ declare function multiTouchGestureId(fingers: number): string;
1881
+
1882
+ /**
1883
+ * Dispatcher orchestrator — pure module, no React, no DOM.
2000
1884
  *
2001
1885
  * ## Precedence and fall-through
2002
1886
  * An exclusive affordance claim first bars every binding that does not
@@ -2012,46 +1896,14 @@ declare function recordModeSwitch(mode: string, from: string | null, to: string
2012
1896
  * Candidates whose `eligible` rule is false are dropped first. The dispatcher
2013
1897
  * walks the rest and fires the first action whose `enabled()` returns
2014
1898
  * `true`. If every candidate's `enabled()` returns a disabled reason, the
2015
- * event is unhandled. This mirrors CSS-style specificity matching with a
2016
- * `:not(:disabled)` filter, and lets a tool declare a high-specificity
2017
- * binding (e.g. drag-on-empty → areaSelect) that gracefully falls through
2018
- * to a lower-specificity ambient binding (e.g. drag → viewport.dragPan)
2019
- * when its required deps aren't wired.
2020
- *
2021
- * ## gestureId scheme
2022
- * - `key-held` ongoing actions: `key-held-<code>` (`key-held-Space`), so two
2023
- * keys that share a `key`, such as the two Shifts, hold separately. An event
2024
- * with no code keys by `key`.
2025
- * Chosen because key-held gestures are identified by the held key alone.
2026
- * - `pointerdown` / drag ongoing actions: `pointer-<pointerId>`, taken from
2027
- * the originating DOM `PointerEvent`. Each physical pointer — mouse, each
2028
- * touch, the stylus — gets its own handle slot. Events with no
2029
- * `pointerId` (synthesized probes, programmatic drags, most tests) key to
2030
- * `pointer-mouse`, so a single synthetic pointer behaves as it always has.
2031
- * - `multitouch` ongoing actions: `multitouch-<fingers>`.
2032
- * - Fallback for any other kind that triggers an ongoing invoker: `ongoing-<kind>`.
1899
+ * event is unhandled.
2033
1900
  *
2034
1901
  * ## Action-lookup miss behavior
2035
1902
  * When the walk reaches a binding whose `actionId` has no entry in
2036
1903
  * `ctx.actions.list()`, the dispatcher emits `console.warn` and returns
2037
- * `'unhandled'`. The user's input gesture falls through as if unmatched.
2038
- * This preserves input flow (nothing is swallowed silently) while flagging
2039
- * the misconfiguration at dev time.
1904
+ * `'unhandled'`, so the gesture falls through as if unmatched.
2040
1905
  */
2041
1906
 
2042
- /**
2043
- * The in-flight handle slot a pointer's gestures key into.
2044
- *
2045
- * Exported because the React seam (`useGestureDispatcher`) has to name the
2046
- * same slot when it asks `dispatcher.inFlight()` whether a given pointer is
2047
- * mid-drag. That used to be the string literal `'pointer-mouse'` on both
2048
- * sides, which was correct only because `gestureIdFor` ignored the pointer id
2049
- * entirely — the two lies cancelled out.
2050
- *
2051
- * @param pointerId - `PointerEvent.pointerId`, or `undefined` for a
2052
- * synthesized event with no originating DOM pointer.
2053
- */
2054
- declare function pointerGestureId(pointerId: number | undefined): string;
2055
1907
  /** Build a dispatcher. `getAction` overrides how action ids are resolved;
2056
1908
  * by default the `DispatcherContext`'s registry is used. */
2057
1909
  declare function createDispatcher(opts?: {
@@ -2087,13 +1939,17 @@ interface ActionsRegistry {
2087
1939
  * `trigger` nor `begin` will fire it. Returns a release. This is the
2088
1940
  * "not for me" door — `unregister` is the "should not exist" one. */
2089
1941
  mute(id: string): () => void;
1942
+ /** Tune `id` here and in scopes below: its params and added bindings
1943
+ * apply to whichever descriptor is live for it, registered before the
1944
+ * tuning or after. Returns a release. See {@link ActionTuning}. */
1945
+ tune(id: string, tuning: ActionTuning): () => void;
2090
1946
  list(): readonly Action[];
2091
1947
  /** Fire an immediate-invoker action by id. The optional `params` arg is
2092
1948
  * forwarded to `ImmediateInvoker.run` as its second argument — use it for
2093
1949
  * parametric actions (e.g. `trigger('tool.activate', { toolId: 'rect' })`).
2094
- * Returns whether it ran one: `false` for an unknown id, and for an
2095
- * ongoing-invoker action, which `trigger` cannot reach (see `begin`). It
2096
- * does not ask `enabled`. */
1950
+ * Returns whether it ran one: `false` for an unknown id, for an action
1951
+ * whose `enabled` refuses, and for an ongoing-invoker action, which
1952
+ * `trigger` cannot reach (see `begin`). `enabled` is asked with no point. */
2097
1953
  trigger(id: string, params?: Record<string, unknown>): boolean;
2098
1954
  /**
2099
1955
  * Subscribe to registry mutations. The callback fires after any
@@ -2162,7 +2018,11 @@ declare function offhandKeyFor(trigger: HotkeyTrigger): string;
2162
2018
  /** Build the per-tool `BoundGesture` list for the consolidated action's
2163
2019
  * `defaultBinding`. Each entry pairs a `key-held` spec with
2164
2020
  * `opts.params.toolId` so the invoker's `start` can pick out which tool
2165
- * to engage. */
2021
+ * to engage.
2022
+ *
2023
+ * An offhand tool engages only between gestures. Swapping the tool out
2024
+ * mid-drag would cancel the drag, and the key is free for the gesture's own
2025
+ * tool to use — Space repositions a shape being drawn. */
2166
2026
  declare function buildToolOffhandBindings(specs: readonly ToolOffhandBindingSpec[]): BoundGesture[];
2167
2027
  /** The `key-held` spec for holding `key` (a `KeyboardEvent.key`, or several).
2168
2028
  * A modifier key gets every modifier optional, since its own keydown reports
@@ -2222,13 +2082,15 @@ declare const isControlHandle: TargetPredicate & {
2222
2082
  declare const isAnchorOrControl: TargetPredicate & {
2223
2083
  readsAffordance: true;
2224
2084
  };
2085
+ /** The outline of the path being edited, between its anchors. */
2086
+ declare const isSegment: TargetPredicate & {
2087
+ readsAffordance: true;
2088
+ };
2089
+ /** Anything path edit puts on the path: an anchor, a control, or a segment. */
2090
+ declare const isPathEditPart: TargetPredicate & {
2091
+ readsAffordance: true;
2092
+ };
2225
2093
 
2226
- /**
2227
- * The long-press the gesture dispatcher is timing, as observable state: where
2228
- * the press landed, when the hold started, how far through it is, and whether
2229
- * any binding would fire on it. Feedback — a filling ring, a haptic — reads
2230
- * this; nothing here decides what the feedback looks like.
2231
- */
2232
2094
  /** A press being held toward a long-press. */
2233
2095
  interface PendingLongPress {
2234
2096
  pointerId: number;
@@ -2281,8 +2143,7 @@ interface LongPressState {
2281
2143
  interface LongPressStore extends LongPressState {
2282
2144
  set(next: PendingLongPress | null): void;
2283
2145
  }
2284
- /** How long a touch or pen press is held before it fires `longPress`, in ms. */
2285
- declare const LONG_PRESS_MS = 500;
2146
+
2286
2147
  /** How `useGestureDispatcher` times, publishes and signals a long-press. */
2287
2148
  interface LongPressOptions {
2288
2149
  /** Hold time in ms. Default {@link LONG_PRESS_MS}. */
@@ -2450,6 +2311,10 @@ declare function modeShortcutSpec(shortcut: string): KeySpec;
2450
2311
  * `exit` / `discard` / `commit` / `cancel` while that mode is active, `entry`
2451
2312
  * while it is not. Install it with `<SceneCanvas ambient>`.
2452
2313
  *
2314
+ * An entry key one mode shares with another mode's leaving key is reported as
2315
+ * a route conflict unless `canEnter` is given: that is what keeps the entry
2316
+ * key from firing inside the other mode.
2317
+ *
2453
2318
  * The bindings ride the hotkey tier, so leaving a mode outranks what the same
2454
2319
  * key does otherwise (Escape clearing the selection), and they yield while a
2455
2320
  * gesture is in flight, so Escape mid-drag still cancels the drag first. Keys
@@ -2479,101 +2344,36 @@ interface ToolKeybinding {
2479
2344
  }
2480
2345
  /** Modifier-key snapshot at event dispatch time. */
2481
2346
  type ToolModifiers = ModifierState;
2482
- /** Per-event context passed to every channel handler. `scratch` is typed
2483
- * via the tool's `TScratch` parameter; it survives across a single
2484
- * gesture (pointer-down through end/cancel) and is replaced on next
2485
- * gesture start by `initScratch()`. */
2486
- interface ToolCtx<TScratch = unknown> {
2487
- worldX: number;
2488
- worldY: number;
2489
- modifiers: ToolModifiers;
2490
- selection: SelectionApi;
2491
- /** Adapter/scene access — opaque at this layer; tools that need it
2492
- * cast to a known shape. This layer doesn't constrain it. */
2493
- adapter: unknown;
2494
- applyOps: (ops: Op[], label: string) => void;
2495
- /** Current viewport. Reflects camera-position semantics — see
2496
- * `View` JSDoc. */
2497
- view: View;
2498
- /** Mutate the viewport. In controlled mode this calls the consumer's
2499
- * `onViewChange`; in uncontrolled mode it updates Canvas's internal
2500
- * state. View changes are not undoable. */
2501
- setView: (next: View) => void;
2502
- /** Bounding rect of the canvas element in viewport coords. Used by
2503
- * zoom/pan tools to convert event clientX/clientY to canvas-relative
2504
- * anchors. */
2505
- canvasRect: DOMRect;
2506
- /** Optional debug sink. When `<Canvas debug={...}>` is enabled, Canvas
2507
- * threads its sink here so tool-internal hit math (handle hitboxes,
2508
- * rotation handle, etc.) lands in the same overlay as Canvas's own
2509
- * bounds/origin records. Tools should call this conditionally with `?.`. */
2510
- debug?: DebugSink;
2511
- scratch: TScratch;
2512
- }
2513
- /**
2514
- * What `onActivate` / `onDeactivate` receive: the tool's scratch, from
2515
- * `initScratch()`. A lifecycle moment has no pointer, hit or modifier state to
2516
- * report, so nothing else is populated; a callback needing the selection or
2517
- * the scene closes over it, as the tool's actions do.
2518
- */
2519
- interface ToolLifecycleCtx<TScratch = unknown> {
2520
- scratch: TScratch;
2521
- }
2522
- /** World-space AABB shape used by `previewBounds`. Alias of the kit-wide
2523
- * `Bounds` type — the optional `rotation` field carries through so a tool
2524
- * can report an oriented preview rect (e.g. mid-rotate). */
2525
- type ToolBounds = Bounds;
2526
2347
  /**
2527
2348
  * The focus-declaring case of a `Contribution`: a mode the user switches
2528
- * into, plus the hooks that only make sense for one (`initScratch`,
2529
- * activate/deactivate, live preview, `cursor`). Everything else — bindings,
2530
- * actions, overlay, presentation — is inherited.
2349
+ * into, plus the hooks that only make sense for one (activate/deactivate,
2350
+ * `cursor`). Everything else — bindings, actions, overlay, presentation — is
2351
+ * inherited.
2531
2352
  */
2532
- interface Tool<TScratch = unknown, TOverlay = KernelOverlay> extends Contribution<TOverlay> {
2353
+ interface Tool<TOverlay = KernelOverlay> extends Contribution<TOverlay> {
2533
2354
  /** The key that activates this tool. `useKeybindings` appends a binding
2534
2355
  * entry for it to the consolidated `tool.activate` action (with
2535
2356
  * `opts.params.toolId` set so the invoker knows which tool to switch to).
2536
2357
  * A tool with none has no activation key. */
2537
2358
  keybinding?: ToolKeybinding;
2538
- initScratch?: () => TScratch;
2539
- cursor?: CursorSpec | ((ctx: ToolCtx<TScratch>) => CursorSpec);
2359
+ cursor?: CursorSpec | (() => CursorSpec);
2540
2360
  /** The tool became live: it took the active slot, or its `hotkey` went
2541
- * down, while it held neither. */
2542
- onActivate?: (ctx: ToolLifecycleCtx<TScratch>) => void;
2361
+ * down, while it held neither. A lifecycle moment has no pointer, hit or
2362
+ * modifier state to report; a callback needing the selection or the scene
2363
+ * closes over it, as the tool's actions do. */
2364
+ onActivate?: () => void;
2543
2365
  /** The tool stopped being live in both slots, or its canvas unmounted. */
2544
- onDeactivate?: (ctx: ToolLifecycleCtx<TScratch>) => void;
2545
- /** Returns the in-flight preview pose for `id` if this tool is mid-gesture
2546
- * on it; otherwise `null`. Lets `Canvas.helpersRef.getEffectivePose`
2547
- * reflect live gesture state without reaching into hook internals. The
2548
- * return type is `unknown` here because the Tool interface is pose-agnostic;
2549
- * callers that know the pose shape (e.g. Canvas typed by `TPose`) cast at
2550
- * the use site. */
2551
- previewPose?: (id: string) => unknown;
2552
- /** Returns the in-flight preview bounds for `id` if this tool is mid-gesture
2553
- * on it; otherwise `null`. Optional companion to `previewPose` for tools that
2554
- * can compute bounds without round-tripping through a geometry adapter. */
2555
- previewBounds?: (id: string) => ToolBounds | null;
2556
- /** Returns ids whose committed scene-render should be suppressed while this
2557
- * tool is mid-gesture (e.g. cascade move's dragged + descendant ids whose
2558
- * preview ghosts replace the committed pose). The standard scene slot
2559
- * consults this alongside `previewPose` to avoid double-rendering. Returns
2560
- * `null` when no gesture is in flight. */
2561
- previewIds?: () => Iterable<string> | null;
2366
+ onDeactivate?: () => void;
2562
2367
  }
2563
2368
  /** Whether `entry` is a tool: a contribution that can hold focus, chosen from
2564
- * a palette or held on a key. Only a tool's focus hooks (`initScratch`,
2565
- * `cursor`, `onActivate`, the previews) ever run. */
2566
- declare function isTool<TOverlay>(entry: Contribution<TOverlay>): entry is Tool<unknown, TOverlay>;
2369
+ * a palette or held on a key. Only a tool's focus hooks (`cursor`,
2370
+ * `onActivate`, `onDeactivate`) ever run. */
2371
+ declare function isTool<TOverlay>(entry: Contribution<TOverlay>): entry is Tool<TOverlay>;
2567
2372
  /** Internal — which slot a tool occupies in the dispatch order. */
2568
2373
  type ToolSlot = 'hotkey' | 'active' | 'ambient';
2569
- /** Internal alias for "a Tool of any scratch type" — used in registries and
2570
- * dispatchers that hold tools of heterogeneous scratch shapes. `any` is
2571
- * intentional: `Tool<TScratch>` is invariant in TScratch, so `Tool<unknown>`
2572
- * is too strict for containers that accept any concrete `Tool<T>`. */
2573
- type AnyTool = Tool<any, any>;
2574
- /** "A tool of any scratch type that draws `TOverlay`" — the container form for
2575
- * registries that hold heterogeneous scratch shapes but one overlay type. */
2576
- type AnyToolOf<TOverlay> = Tool<any, TOverlay>;
2374
+ /** A tool drawing any overlay type — the element type for registries and
2375
+ * dispatchers that never read the overlay. */
2376
+ type AnyTool = Tool<any>;
2577
2377
 
2578
2378
  /**
2579
2379
  * What a tool declares.
@@ -2584,7 +2384,7 @@ type AnyToolOf<TOverlay> = Tool<any, TOverlay>;
2584
2384
  * definition is mostly `bindings`, plus presentation and any actions the tool
2585
2385
  * itself introduces.
2586
2386
  */
2587
- interface ToolDef<TScratch = void, TOverlay = KernelOverlay> {
2387
+ interface ToolDef<TOverlay = KernelOverlay> {
2588
2388
  id: string;
2589
2389
  /** Capability tags for modality eligibility. `defineTool` forwards these
2590
2390
  * onto `Tool.eligibility.capabilities`, which is where every gate reads
@@ -2607,7 +2407,7 @@ interface ToolDef<TScratch = void, TOverlay = KernelOverlay> {
2607
2407
  * Introspection-only — do not make this load-bearing in production.
2608
2408
  * Read off the def via `Tool.def` (the reflection escape hatch). */
2609
2409
  hookName?: string;
2610
- presentation?: ToolPresentation<TScratch>;
2410
+ presentation?: ToolPresentation;
2611
2411
  /** Optional caller-supplied activation key. Most built-in tools have their
2612
2412
  * activation key declared in `BUILTIN_SELECT_KEYS` in `useKeybindings.ts`;
2613
2413
  * this field is for tools that want their activation key to be
@@ -2625,17 +2425,10 @@ interface ToolDef<TScratch = void, TOverlay = KernelOverlay> {
2625
2425
  hotkey?: HotkeyTrigger;
2626
2426
  /** Fires as the tool becomes live in the active or hotkey slot. See
2627
2427
  * `Tool.onActivate`. */
2628
- onActivate?: (ctx: ToolLifecycleCtx<TScratch>) => void;
2428
+ onActivate?: () => void;
2629
2429
  /** Fires as the tool stops being live in either slot. */
2630
- onDeactivate?: (ctx: ToolLifecycleCtx<TScratch>) => void;
2631
- cursor?: CursorSpec | ((ctx: ToolCtx<TScratch>) => CursorSpec);
2632
- /** Override the default scratch initializer. Default is `() => null`
2633
- * cast to `TScratch`, which works for tools whose scratch is fresh
2634
- * every gesture. Tools that need scratch identity to survive across
2635
- * gesture boundaries (e.g. the pen tool's multi-click subpath state)
2636
- * pass a stable-ref-returning thunk here. The factory forwards this
2637
- * onto the returned `Tool.initScratch`. */
2638
- initScratch?: () => TScratch;
2430
+ onDeactivate?: () => void;
2431
+ cursor?: CursorSpec | (() => CursorSpec);
2639
2432
  /** Declarative gesture bindings, forwarded onto `Tool.bindings`. The
2640
2433
  * gesture dispatcher consults these at active scope while this tool is the
2641
2434
  * active one, and at hotkey scope while it is held. This is the tool's
@@ -2648,7 +2441,7 @@ interface ToolDef<TScratch = void, TOverlay = KernelOverlay> {
2648
2441
  *
2649
2442
  * The layer's `draw` closure should read dynamic state through refs or
2650
2443
  * closures captured in the enclosing render scope, and gate on it there —
2651
- * `if (!scratch.something) return []` — rather than expecting the kit to
2444
+ * `if (!state.something) return []` — rather than expecting the kit to
2652
2445
  * swap layers as the gesture progresses.
2653
2446
  *
2654
2447
  * Pass an array to contribute several layers; they render in order. */
@@ -2659,20 +2452,10 @@ interface ToolDef<TScratch = void, TOverlay = KernelOverlay> {
2659
2452
  }
2660
2453
 
2661
2454
  /**
2662
- * Build a `Tool<TScratch>` from a declarative `ToolDef<TScratch>`.
2663
- *
2664
- * This used to be a translator: `ToolDef` carried `initial` / `engaged` phase
2665
- * tables of hit-keyed route handlers, and `defineTool` compiled them into the
2666
- * imperative `pointer` / `drag` / `keyboard` / `wheel` handlers the
2667
- * tool-routing dispatcher called. Both ends of that are gone — tools declare
2668
- * `bindings` and the gesture dispatcher routes them — so what remains is
2669
- * identity plumbing plus the id check and the cursor resolver.
2670
- *
2671
- * It stays a function rather than becoming a spread because the id validation
2672
- * and the `initScratch` / `cursor` defaults are worth applying uniformly, and
2673
- * because `Tool.def` gives reflection a handle on the authored form.
2455
+ * Build a `Tool` from a declarative `ToolDef`: validates the id, applies the
2456
+ * `cursor` default, and keeps the authored form on `Tool.def` for reflection.
2674
2457
  */
2675
- declare function defineTool<TScratch = void, TOverlay = KernelOverlay>(def: ToolDef<TScratch, TOverlay>): Tool<TScratch, TOverlay>;
2458
+ declare function defineTool<TOverlay = KernelOverlay>(def: ToolDef<TOverlay>): Tool<TOverlay>;
2676
2459
 
2677
2460
  /**
2678
2461
  * One row in the route registry — a single `GestureBinding` on one tool,
@@ -2720,6 +2503,10 @@ interface RegistryEntry {
2720
2503
  /** The binding's `opts.views`: the views it is live in. Absent when it is
2721
2504
  * live in every view. */
2722
2505
  views?: readonly (string | null)[];
2506
+ /** The binding's `opts.params`, as declared: a thunk is not called. */
2507
+ params?: unknown;
2508
+ /** The binding's `opts.yields`, when it is set. */
2509
+ yields?: true;
2723
2510
  }
2724
2511
  /**
2725
2512
  * Stand-in target for a `{ kindOf }` predicate spec.
@@ -2756,10 +2543,8 @@ declare function buildRouteRegistry(entries: readonly Contribution<unknown>[]):
2756
2543
  */
2757
2544
  declare function routesForSpec(spec: GestureSpec): readonly string[];
2758
2545
 
2759
- /** Two or more tools declare the same exact (phase, gesture, arg, target,
2760
- * modifiers) tuple — the dispatcher's slot precedence picks one
2761
- * arbitrarily (well, deterministically by slot order, but the author
2762
- * probably didn't intend the duplication). */
2546
+ /** Two or more bindings declare the same exact (phase, gesture, arg, target,
2547
+ * modifiers) tuple, so slot order alone picks one. */
2763
2548
  interface Conflict {
2764
2549
  phase: 'initial' | 'engaged' | 'any';
2765
2550
  gesture: GestureName;
@@ -2780,39 +2565,32 @@ interface Conflict {
2780
2565
  * required against Alt required fire on different inputs, and Shift
2781
2566
  * required outranks Shift optional. An optional modifier against a
2782
2567
  * forbidden one is flagged, since both take the bare press.
2783
- * - A binding whose action declines via `enabled()` so a lower-priority
2784
- * one can take the gesture. Detecting that intent would mean evaluating
2785
- * the action; consumers can suppress known-intentional compositions in
2786
- * their UI layer.
2787
- *
2788
- * - Two `{ kindOf }` predicate targets. The route grammar renders every
2789
- * predicate as the single token {@link PREDICATE_TARGET}, so bucketing on
2790
- * the rendered target alone reported select's `resize` / `rotate` / `move`
2791
- * drags — three genuinely different predicates — as a three-way conflict.
2792
- * Predicate entries bucket by function identity instead, which means two
2793
- * *separately written but equivalent* predicates go unflagged. That's the
2794
- * right way to be wrong here: this check has to be silent when nothing is
2795
- * wrong or nobody will keep it on.
2796
- *
2568
+ * - Two `{ kindOf }` predicate targets, unless they are the same function.
2569
+ * Predicates bucket by identity, so two separately written but
2570
+ * equivalent ones go unflagged.
2797
2571
  * - Two actions whose `eligible` rules can never hold together — each
2798
2572
  * mode's own Escape exit, say. See {@link rulesExclusive}.
2799
- *
2800
2573
  * - Two bindings whose `opts.views` keep them from being live and equally
2801
- * ranked in any one view. See `viewsTie`.
2574
+ * ranked in any one view.
2575
+ * - Two bindings of one action with equal params that tie on one platform
2576
+ * only: `mod` against `ctrl`, which are one key off a Mac and two on it.
2577
+ * The same pair tying on both platforms is a plain duplicate and is flagged.
2578
+ *
2579
+ * A binding whose action declines via `enabled()` so a lower-priority one
2580
+ * takes the gesture is flagged all the same, since seeing that intent would
2581
+ * mean evaluating the action, unless it says so with `opts.yields`. A pair
2582
+ * whose earlier binding yields is not a conflict; one whose later binding
2583
+ * yields still is, as the earlier one decides alone.
2802
2584
  *
2803
- * Note that two bindings sharing a tuple on the SAME tool are now possible
2804
- * (bindings are an array, where phase tables were objects with unique
2805
- * keys) — so a conflict may name one tool twice.
2585
+ * Two bindings on the same tool can share a tuple, so a conflict may name
2586
+ * one tool twice.
2806
2587
  *
2807
- * This is the raw same-tuple detector. Feeding it a whole tool *registry*
2808
- * over-reports, because registry tools take turns in the active slot and
2809
- * can't collide with each other — see `findScopedConflicts`.
2588
+ * Feeding this a whole tool *registry* over-reports, because registry tools
2589
+ * take turns in the active slot and can't collide with each other — see
2590
+ * `findScopedConflicts`.
2810
2591
  */
2811
2592
  declare function findConflicts(entries: readonly Contribution<unknown>[]): Conflict[];
2812
- /**
2813
- * The tool scopes as the dispatcher sees them — which is what decides whether
2814
- * two same-tuple bindings can actually collide.
2815
- */
2593
+ /** The tool scopes as the dispatcher sees them. */
2816
2594
  interface ToolScopes {
2817
2595
  /** Tools eligible for the active slot, keyed by id or as a flat list. */
2818
2596
  registry: readonly Contribution<unknown>[] | Readonly<Record<string, Contribution<unknown>>>;
@@ -2829,17 +2607,13 @@ interface ToolScopes {
2829
2607
  /**
2830
2608
  * Detect reachable route conflicts in an assembled tool set and report each one.
2831
2609
  *
2832
- * This is the wiring `findConflicts` spent its first life without: the kit
2833
- * could detect the one class of genuine routing ambiguity it has and never
2834
- * looked. Call it once wherever a tool set is assembled (`useTools` does),
2835
- * behind a `process.env.NODE_ENV !== 'production'` guard.
2610
+ * Call it once wherever a tool set is assembled, behind a
2611
+ * `process.env.NODE_ENV !== 'production'` guard.
2836
2612
  *
2837
2613
  * **Warn, never throw.** A conflict between a consumer's tool and a kit tool
2838
- * is a design question — sometimes deliberate, since the loser can still take
2839
- * the gesture by declining through `enabled()` — and exploding in a running
2840
- * app is the wrong way to raise it. A conflict between two *kit* tools is
2841
- * always a bug, but the place to fail on that is the kit's own test suite
2842
- * (`canvas/SceneCanvas.routeConflicts.test.tsx`), not a consumer's console.
2614
+ * is sometimes deliberate, since the loser can still take the gesture when
2615
+ * the winner declines through `enabled()`. A binding ordered that way on
2616
+ * purpose says so with `opts.yields`, and is not reported.
2843
2617
  *
2844
2618
  * @returns The conflicts found, so callers can dedupe repeat reports.
2845
2619
  */
@@ -2852,7 +2626,10 @@ declare function reportRouteConflicts(scopes: ToolScopes, warn?: (message: strin
2852
2626
  *
2853
2627
  * Extracted from the deleted singular `useKeybinding` hook so the remaining
2854
2628
  * consumers don't depend on its disappearance.
2629
+ *
2630
+ * @packageDocumentation
2855
2631
  */
2632
+
2856
2633
  /**
2857
2634
  * A key plus a modifier policy, as {@link matchesKeyBinding} tests it against a
2858
2635
  * `KeyboardEvent`. Every modifier left unset is forbidden, so `{ key: 'a' }`
@@ -2885,14 +2662,6 @@ interface KeyBinding {
2885
2662
  /** Call `preventDefault` before the handler. Default `true`. */
2886
2663
  preventDefault?: boolean;
2887
2664
  }
2888
- /** Returns true when the target is an input, textarea, or contenteditable element. */
2889
- declare function isEditableTarget(target: EventTarget | null): boolean;
2890
- /**
2891
- * True when `e` is Space or Enter on a focused control those keys activate — a
2892
- * button, a link, a checkbox. The key is that control's, so a page-level
2893
- * listener that claims it cancels the control's own activation.
2894
- */
2895
- declare function activatesFocusedControl(e: KeyboardEvent): boolean;
2896
2665
  /**
2897
2666
  * Pure matcher for `KeyBinding` against a `KeyboardEvent`. Only checks key +
2898
2667
  * modifier policy — does NOT apply the editable-target skip or
@@ -2900,38 +2669,21 @@ declare function activatesFocusedControl(e: KeyboardEvent): boolean;
2900
2669
  * gesture dispatcher so the rules can't drift.
2901
2670
  */
2902
2671
  declare function matchesKeyBinding(e: KeyboardEvent, b: KeyBinding): boolean;
2903
- type ModifierKeys = Pick<KeyboardEvent, 'key' | 'metaKey' | 'ctrlKey' | 'shiftKey' | 'altKey'>;
2904
2672
  /** Which history step `e` asks for: Mod+Z undoes, Shift+Mod+Z and Mod+Y redo. `null` for anything else. Takes a
2905
2673
  * React keyboard event as readily as a DOM one. */
2906
- declare function historyKey(e: ModifierKeys): 'undo' | 'redo' | null;
2674
+ declare function historyKey(e: Pick<KeyboardEvent, 'key' | 'metaKey' | 'ctrlKey' | 'shiftKey' | 'altKey'>): 'undo' | 'redo' | null;
2907
2675
 
2908
2676
  /**
2909
- * Typed keys for the gesture/behavior scratch store.
2910
- *
2911
- * Gestures expose `ctx.scratch: Record<string, unknown>` as a per-gesture
2912
- * mutable bag that behaviors use to stash state across `onDown` / `onMove`
2913
- * / `onEnd` calls. The store is `unknown`-valued because behaviors are
2914
- * tool-agnostic — a single behavior may run under different tools with
2915
- * different scratch shapes. Without a typing convention every read site
2916
- * has to `(ctx.scratch as <some shape>)[KEY]`, and a writer changing the
2917
- * shape can silently break readers with no compile-time signal.
2918
- *
2919
- * `ScratchKey<T>` is a phantom-typed string that carries its payload type
2920
- * via a branded interface. `getScratch` and `setScratch` use the brand to
2921
- * narrow / constrain at the typing layer; at runtime the key is just its
2922
- * underlying string and the store remains a plain `Record`.
2677
+ * Typed keys for the gesture/behavior scratch store, `ctx.scratch`.
2678
+ * `ScratchKey<T>` carries its payload type at the typing layer; at runtime
2679
+ * the key is just its underlying string and the store remains a plain `Record`.
2923
2680
  *
2924
2681
  * @example
2925
2682
  * ```ts
2926
- * // Module-level (or behavior-level) constant, shared by writer + reader:
2683
+ * // Module-level constant, shared by writer + reader:
2927
2684
  * const LASSO_VERTICES = scratchKey<readonly { x: number; y: number }[]>('lasso.vertices');
2928
- *
2929
- * // Writer (a tool's onMove):
2930
2685
  * setScratch(ctx.scratch, LASSO_VERTICES, vertices);
2931
- *
2932
- * // Reader (a behavior's onEnd):
2933
2686
  * const vertices = getScratch(ctx.scratch, LASSO_VERTICES) ?? [];
2934
- * // ^? readonly { x: number; y: number }[] | undefined
2935
2687
  * ```
2936
2688
  */
2937
2689
  /** Phantom-typed key for a scratch slot. The `__scratchKeyPayload` phantom
@@ -2946,13 +2698,9 @@ interface ScratchKey<T> {
2946
2698
  * field already satisfies this; consumers don't need to construct one. */
2947
2699
  type ScratchStore = Record<string, unknown>;
2948
2700
  /**
2949
- * Make a typed scratch key. Use module-level constants so the same key
2950
- * is shared by writer and reader.
2951
- *
2952
- * Namespace the name by behavior or feature to avoid collisions
2953
- * (`'behavior.field'` is a good convention). Two keys with the same
2954
- * `name` refer to the same slot at runtime — the type parameter is a
2955
- * compile-time contract, not a uniqueness guarantee.
2701
+ * Make a typed scratch key. Two keys with the same `name` refer to the same
2702
+ * slot at runtime — the type parameter is a compile-time contract, not a
2703
+ * uniqueness guarantee — so namespace the name (`'behavior.field'`).
2956
2704
  */
2957
2705
  declare function scratchKey<T>(name: string): ScratchKey<T>;
2958
2706
  /** Read a typed slot from the scratch store. Returns `undefined` if
@@ -2993,6 +2741,14 @@ declare function itemsFromFiles(files: File[]): IngestItem[];
2993
2741
  /** Materialize a drop's `DataTransfer`. MUST be called during the `drop`
2994
2742
  * event dispatch (see module doc). */
2995
2743
  declare function itemsFromDataTransfer(dt: DataTransfer): Promise<IngestItem[]>;
2744
+ /**
2745
+ * What a drag still in flight says it carries: one item per flavor, MIME
2746
+ * only. A browser withholds contents until the drop, so nothing may read
2747
+ * `file` or `text` off these; they exist to ask which bindings would match.
2748
+ * A file the browser does not name yet (Firefox reports
2749
+ * `application/x-moz-file`, others only a `Files` type) is `UNKNOWN_MIME`.
2750
+ */
2751
+ declare function itemTypesFromDataTransfer(dt: DataTransfer): readonly IngestItem[];
2996
2752
  /** Materialize a paste's `clipboardData`. Fully synchronous. */
2997
2753
  declare function itemsFromClipboardData(cd: DataTransfer): IngestItem[];
2998
2754
 
@@ -3055,6 +2811,8 @@ declare function dwarn(namespace: string, ...args: unknown[]): void;
3055
2811
  * The pure half: no React, no DOM. The React seam that pumps browser events
3056
2812
  * into the dispatcher, and the providers that feed it, live behind
3057
2813
  * `@weasel-js/routing/react`.
2814
+ *
2815
+ * @packageDocumentation
3058
2816
  */
3059
2817
 
3060
2818
  /**
@@ -3063,10 +2821,8 @@ declare function dwarn(namespace: string, ...args: unknown[]): void;
3063
2821
  * keyed on.
3064
2822
  *
3065
2823
  * It declares one entry, `activeTool`, because the value and the action that
3066
- * requires it (`tool.offhand`) are both routing's. Routing knows that an
3067
- * action names its dependencies and that the registry resolves them at
3068
- * invocation time; it does not know what a selection, a scene, or a viewport
3069
- * is. Whoever supplies those merges them in:
2824
+ * requires it (`tool.offhand`) are both routing's. Whoever supplies the rest
2825
+ * merges them in:
3070
2826
  *
3071
2827
  * ```ts
3072
2828
  * declare module '@weasel-js/routing' {
@@ -3075,12 +2831,7 @@ declare function dwarn(namespace: string, ...args: unknown[]): void;
3075
2831
  * ```
3076
2832
  *
3077
2833
  * `@weasel-js/core` merges the kit's own deps this way and re-exports
3078
- * `DepSchema`, so a consumer adds its own against `'@weasel-js/core'` and
3079
- * never has to know this module exists. The merge carries through the
3080
- * re-export in both directions — `scripts/smoke-consumer-bundle.mjs` asserts
3081
- * it against the published `.d.ts`, which is where it once broke: a
3082
- * cross-module augmentation stops merging when `rollup-plugin-dts` flattens
3083
- * both files into one chunk.
2834
+ * `DepSchema`, so a consumer adds its own against `'@weasel-js/core'`.
3084
2835
  */
3085
2836
  interface DepSchema {
3086
2837
  /** Currently active tool id + hotkey-hold stack. */
@@ -3088,6 +2839,29 @@ interface DepSchema {
3088
2839
  }
3089
2840
  /** Every dep name declared by anything that has merged into {@link DepSchema}. */
3090
2841
  type DepName = keyof DepSchema;
2842
+ /**
2843
+ * The params each action reads, by action id — what types a tuning's
2844
+ * `params` and the params on the bindings it adds. Whoever ships an action
2845
+ * merges its entry in, as with {@link DepSchema}:
2846
+ *
2847
+ * ```ts
2848
+ * declare module '@weasel-js/core' {
2849
+ * interface ActionParamSchema { brushDeleteAnchors: { radiusPx: number } }
2850
+ * }
2851
+ * ```
2852
+ *
2853
+ * Declare an entry as a type literal, not an interface: an interface has no
2854
+ * index signature, so it is not a `Record<string, unknown>`.
2855
+ */
2856
+ interface ActionParamSchema {
2857
+ }
2858
+ /** Tunings keyed by action id: typed by {@link ActionParamSchema} for an id it
2859
+ * declares, `Record<string, unknown>` params for any other. */
2860
+ type ActionTunings = {
2861
+ readonly [K in keyof ActionParamSchema]?: ActionTuning<ActionParamSchema[K]>;
2862
+ } & {
2863
+ readonly [id: string]: ActionTuning | undefined;
2864
+ };
3091
2865
  /**
3092
2866
  * What the kernel drawing these tools takes as an overlay. Routing never reads
3093
2867
  * an overlay, so it has no type of its own for one; the kernel merges its type
@@ -3109,4 +2883,4 @@ type KernelOverlay = OverlaySchema extends {
3109
2883
  overlay: infer O;
3110
2884
  } ? O : unknown;
3111
2885
 
3112
- export { type DispatchRecord as $, type ActionsRegistry as A, type BindingOpts as B, type Contribution as C, type DepRegistry as D, type BindingScope as E, type BindingSource as F, type BodyPredicate as G, type BoundGesture as H, type Bounds as I, type BuildRuleCtxArgs as J, type KernelOverlay as K, type LongPressOptions as L, COARSE_TARGET_SCALE as M, type Condition as N, type OverlayPosition as O, type Conflict as P, type ContributionChrome as Q, type RuleCtx as R, ScopeNode as S, ToolNode as T, type ContributionDeps as U, type ContributionRouting as V, DEFAULT_ALLOWED_CAPABILITIES as W, DEFAULT_DEVICE_PROFILE as X, type DebugSink as Y, type DetectedDeviceFacts as Z, type DeviceProfile as _, type DepName as a, type TraceLogEntry as a$, type DispatchRecordInput as a0, type DispatcherContext as a1, type DragSample as a2, type DroppedCandidate as a3, type Eligibility as a4, type EligibilityState as a5, type GestureBinding as a6, type HandleKind as a7, type HitShape as a8, type HotkeyTrigger as a9, type PointerSessionCancelReason as aA, type PointerSessionOptions as aB, type RankedCandidate as aC, type RecordCandidate as aD, type RegistryEntry as aE, type ResizeAnchor as aF, type ResolveAllOptions as aG, type ResolveOnlyResult as aH, type ResolvedCandidate as aI, type Rule as aJ, type ScopedBinding as aK, type ScratchKey as aL, type ScratchStore as aM, type SelectionApi as aN, type Selector as aO, type SpecificityPart as aP, TOOL_OFFHAND_ID as aQ, type Tool as aR, type ToolBounds as aS, type ToolCtx as aT, type ToolDef as aU, type ToolKeybinding as aV, type ToolLifecycleCtx as aW, type ToolModifiers as aX, type ToolOffhandBindingSpec as aY, type ToolPresentation as aZ, type ToolSlot as a_, INGEST_STRING_MIMES as aa, type ImmediateInvoker as ab, type InvocationCtx as ac, type Invoker as ad, type KeyBinding as ae, LONG_PRESS_MS as af, type LongPressState as ag, type LongPressStore as ah, type MatchOutcome as ai, type MatchResult as aj, type ModeShortcutHandlers as ak, type ModeSwitchLogEntry as al, type ModifierState as am, NEVER as an, type NodeId as ao, type OngoingHandle as ap, type OngoingInvoker as aq, type OngoingOverlay as ar, type OverlayRole as as, type OverlaySchema as at, PREDICATE_TARGET as au, type PendingLongPress as av, type PlacedBy as aw, type Point2 as ax, type PointerSession as ay, type PointerSessionCallbacks as az, type DepSchema as b, scratchKey as b$, type UiOngoingControl as b0, type ViewportGestureKind as b1, type WalkStep as b2, actionBindings as b3, actionItems as b4, activatesFocusedControl as b5, assembleEntries as b6, buildDepsFromRequires as b7, buildRouteRegistry as b8, buildRuleCtx as b9, isRotateHandle as bA, isSelectedBody as bB, isTool as bC, isUnselectedBody as bD, itemsFromClipboardData as bE, itemsFromDataTransfer as bF, itemsFromFiles as bG, liveScope as bH, makeToolOffhandAction as bI, matchBest as bJ, matchSorted as bK, matchSortedWithBarred as bL, matchesKeyBinding as bM, mergeContributions as bN, modeShortcutSpec as bO, modeShortcuts as bP, offhandKeyFor as bQ, openPointerSession as bR, pointerGestureId as bS, recordModeSwitch as bT, reportRouteConflicts as bU, resolveDeviceProfile as bV, resolveParams as bW, routesForSpec as bX, ruleCanHoldIn as bY, rulesExclusive as bZ, scopeBindings as b_, buildToolOffhandBindings as ba, clientToCanvas as bb, clientToCanvasRect as bc, createDepRegistry as bd, createDispatcher as be, createLongPressStore as bf, defineTool as bg, deleteScratch as bh, describeRule as bi, dlog as bj, dwarn as bk, evaluate as bl, evaluateActive as bm, evaluateEnabled as bn, findConflicts as bo, getScratch as bp, heldKeySpec as bq, historyKey as br, isAnchor as bs, isAnchorOrControl as bt, isBody as bu, isControlHandle as bv, isDebugEnabled as bw, isEditableTarget as bx, isEmpty as by, isResizeHandle as bz, type Action as c, setScratch as c0, targetConsultsAffordance as c1, validateActionDefaultBinding as c2, validateActionId as c3, DepNode as d, type AnyToolOf as e, ActiveToolContextProvider as f, ActiveToolContextProviderIfRoot as g, type ActiveToolContextProviderProps as h, type ActiveToolContextValue as i, useOptionalActiveToolContext as j, type Dispatcher as k, type AffordanceHit as l, ALWAYS as m, type ActionDeps as n, ActionDisabledReason as o, type ActionDispatch as p, type ActionEnabledResult as q, type ActionEntry as r, type ActionItem as s, type ActionPresentation as t, useActiveToolContext as u, type ActionSource as v, type ActionVariant as w, type ActionsProp as x, type AnyTool as y, type AssemblyInputs as z };
2886
+ export { DEFAULT_ALLOWED_CAPABILITIES as $, type ActionsRegistry as A, type ActionVariant as B, type Contribution as C, type DepRegistry as D, type ActionsProp as E, type AnyTool as F, type AssemblyInputs as G, type BindingOpts as H, type BindingScope as I, type BindingSource as J, type KernelOverlay as K, type LongPressOptions as L, type BodyPredicate as M, type NodeId as N, type OverlayPosition as O, type BoundGesture as P, type Bounds as Q, type RuleCtx as R, ScopeNode as S, ToolNode as T, type BuildRuleCtxArgs as U, COARSE_TARGET_SCALE as V, type Condition as W, type Conflict as X, type ContributionChrome as Y, type ContributionDeps as Z, type ContributionRouting as _, type DepName as a, type TraceLogEntry as a$, DEFAULT_DEVICE_PROFILE as a0, type DebugSink as a1, type DetectedDeviceFacts as a2, type DeviceProfile as a3, type DispatchRecord as a4, type DispatchRecordInput as a5, type DispatcherContext as a6, type DragSample as a7, type DroppedCandidate as a8, type Eligibility as a9, type Point2 as aA, type PointerSession as aB, type PointerSessionCallbacks as aC, type PointerSessionCancelReason as aD, type PointerSessionOptions as aE, type RankedCandidate as aF, type RecordCandidate as aG, type RegistryEntry as aH, type ResizeAnchor as aI, type ResolveAllOptions as aJ, type ResolveOnlyResult as aK, type ResolvedCandidate as aL, type Rule as aM, type ScopedBinding as aN, type ScratchKey as aO, type ScratchStore as aP, type SelectionApi as aQ, type Selector as aR, type SpecificityPart as aS, TOOL_OFFHAND_ID as aT, type ToolDef as aU, type ToolKeybinding as aV, type ToolModifiers as aW, type ToolOffhandBindingSpec as aX, type ToolPresentation as aY, type ToolScopes as aZ, type ToolSlot as a_, type EligibilityState as aa, type GestureBinding as ab, type HandleKind as ac, type HitShape as ad, type HotkeyTrigger as ae, INGEST_STRING_MIMES as af, type ImmediateInvoker as ag, type InvocationCtx as ah, type Invoker as ai, type KeyBinding as aj, type LongPressState as ak, type LongPressStore as al, type MatchOutcome as am, type MatchResult as an, type ModeShortcutHandlers as ao, type ModeSwitchLogEntry as ap, type ModifierState as aq, NEVER as ar, type OngoingHandle as as, type OngoingInvoker as at, type OngoingOverlay as au, type OverlayRole as av, type OverlaySchema as aw, PREDICATE_TARGET as ax, type PendingLongPress as ay, type PlacedBy as az, type DepSchema as b, ruleCanHoldIn as b$, type TunedBinding as b0, type UiOngoingControl as b1, type ViewportGestureKind as b2, type WalkStep as b3, actionBindings as b4, actionItems as b5, assembleEntries as b6, buildDepsFromRequires as b7, buildRouteRegistry as b8, buildRuleCtx as b9, isRotateHandle as bA, isSegment as bB, isSelectedBody as bC, isTool as bD, isUnselectedBody as bE, itemTypesFromDataTransfer as bF, itemsFromClipboardData as bG, itemsFromDataTransfer as bH, itemsFromFiles as bI, liveScope as bJ, makeToolOffhandAction as bK, matchBest as bL, matchSorted as bM, matchSortedWithBarred as bN, matchesKeyBinding as bO, mergeContributions as bP, modeShortcutSpec as bQ, modeShortcuts as bR, multiTouchGestureId as bS, offhandKeyFor as bT, openPointerSession as bU, pointerGestureId as bV, recordModeSwitch as bW, reportRouteConflicts as bX, resolveDeviceProfile as bY, resolveParams as bZ, routesForSpec as b_, buildToolOffhandBindings as ba, clientToCanvas as bb, clientToCanvasRect as bc, createDepRegistry as bd, createDispatcher as be, createLongPressStore as bf, defineTool as bg, deleteScratch as bh, describeRule as bi, dlog as bj, dwarn as bk, evaluate as bl, evaluateActive as bm, evaluateEnabled as bn, findConflicts as bo, getScratch as bp, heldKeySpec as bq, historyKey as br, isAnchor as bs, isAnchorOrControl as bt, isBody as bu, isControlHandle as bv, isDebugEnabled as bw, isEmpty as bx, isPathEditPart as by, isResizeHandle as bz, type Action as c, rulesExclusive as c0, scopeBindings as c1, scratchKey as c2, setScratch as c3, targetConsultsAffordance as c4, tuneAction as c5, validateActionDefaultBinding as c6, validateActionId as c7, DepNode as d, type Tool as e, ActiveToolContextProvider as f, ActiveToolContextProviderIfRoot as g, type ActiveToolContextProviderProps as h, type ActiveToolContextValue as i, useOptionalActiveToolContext as j, type Dispatcher as k, type AffordanceHit as l, ALWAYS as m, type ActionDeps as n, ActionDisabledReason as o, type ActionDispatch as p, type ActionEnabledResult as q, type ActionEntry as r, type ActionItem as s, type ActionParamSchema as t, useActiveToolContext as u, type ActionParams as v, type ActionPresentation as w, type ActionSource as x, type ActionTuning as y, type ActionTunings as z };