@weasel-js/labkit 0.1.0 → 0.7.2

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.
@@ -32,6 +32,10 @@ interface Op {
32
32
  args?: unknown;
33
33
  }
34
34
 
35
+ interface BeginJournalOptions {
36
+ label: string;
37
+ targetId?: string;
38
+ }
35
39
  interface Journal {
36
40
  readonly targetId: string | undefined;
37
41
  readonly forkedAtEntryId: number;
@@ -96,6 +100,77 @@ interface HistoryEntry {
96
100
  * restored from an older snapshot that predates this field. */
97
101
  touchedIds?: ReadonlySet<string>;
98
102
  }
103
+ /** Op-batched undo/redo controller returned by `createHistory`. */
104
+ interface History {
105
+ apply(op: Op, label?: string): void;
106
+ applyOps(ops: Op[], label: string): void;
107
+ undo(): void;
108
+ redo(): void;
109
+ canUndo(): boolean;
110
+ canRedo(): boolean;
111
+ /** Number of entries on the undo stack (O(1); `entries().undo.length`
112
+ * without materializing the views). */
113
+ undoDepth(): number;
114
+ /** Number of entries on the redo stack (O(1)). */
115
+ redoDepth(): number;
116
+ clear(): void;
117
+ /** Snapshot of the current undo + redo stacks. `undo` is oldest→newest
118
+ * (i.e. the last element is what `undo()` would pop next); `redo` is
119
+ * also oldest→newest from the user's perspective (i.e. the *first*
120
+ * element is what `redo()` would pop next — see implementation note).
121
+ * Callers should treat the arrays as immutable. */
122
+ entries(): {
123
+ undo: HistoryEntry[];
124
+ redo: HistoryEntry[];
125
+ };
126
+ /** Walk the history forward/back until exactly `n` entries are on the
127
+ * undo stack (0 ≤ n ≤ entries().undo.length + entries().redo.length).
128
+ * Equivalent to repeated `undo()`/`redo()` calls but doesn't bother
129
+ * rebuilding entry snapshots between steps. No-op if already at `n`. */
130
+ goto(n: number): void;
131
+ /** Monotonic counter bumped on every push/undo/redo/clear/coalesce.
132
+ * Cheap to read; callers use it as a React dep to detect changes. */
133
+ getVersion(): number;
134
+ /** Subscribe to history changes. Fires after every push/undo/redo/
135
+ * clear/coalesce. Returns an unsubscribe fn. */
136
+ subscribe(listener: () => void): () => void;
137
+ /** Snapshot the undo + redo stacks in a structured-clone-safe form.
138
+ * Entries whose ops aren't all kit-registered (i.e. any op missing a
139
+ * `name`) are dropped from the snapshot with a debug-level log — they
140
+ * can't round-trip, so we omit them rather than emit a half-restorable
141
+ * entry. The in-memory stacks aren't modified. */
142
+ serialize(): SerializedHistory;
143
+ /** Replace the current undo + redo stacks with the deserialized contents
144
+ * of `snapshot`. Ops are rebuilt via the `rebuildOp` option when
145
+ * provided, then the global registry; unknown names become no-op
146
+ * placeholders so stack ordering survives across kit-version skew.
147
+ * Bumps `version` and notifies subscribers exactly once. */
148
+ restore(snapshot: SerializedHistory): void;
149
+ /** Push an entry whose ops have already been applied to the adapter.
150
+ * Unlike `applyOps`, does NOT call `op.apply()`. Used by Journal.commit
151
+ * to flush a session's net forward ops to the parent as one entry without
152
+ * re-mutating the scene. */
153
+ recordEntry(ops: Op[], label: string): void;
154
+ /** Concatenated forwardOps of every undo-stack entry, in order. Snapshot
155
+ * of "what changes are currently applied via this history" — useful for
156
+ * Journal.commit to flush to a parent, and for any caller that wants to
157
+ * diff against a baseline. */
158
+ allForwardOps(): Op[];
159
+ /** The id that will be assigned to the *next* pushed entry. Stable
160
+ * monotonic counter; callers use it to tag a fork point (see Journal). */
161
+ currentEntryId(): number;
162
+ /** Open a scoped sub-history. All apply/undo/redo on the returned Journal
163
+ * affect the same adapter; on commit, the Journal's net forward ops are
164
+ * flushed to this History as one entry. See spec docs/superpowers/specs/
165
+ * 2026-05-24-modality-design.md for the full lifecycle. */
166
+ beginJournal(opts: BeginJournalOptions): Journal;
167
+ /** Re-activate a suspended journal. Throws if the journal was committed or
168
+ * cancelled (those are terminal). Staleness checking is the caller's
169
+ * responsibility — consult `journal.forkedAtEntryId` against
170
+ * `currentEntryId()` and your own op-semantic rules to decide whether
171
+ * to resume or discard before calling this. */
172
+ resumeJournal(journal: Journal): void;
173
+ }
99
174
 
100
175
  /**
101
176
  * Pose composition for hierarchical scene graphs.
@@ -133,6 +208,14 @@ interface RectPose {
133
208
  /** Rotation in radians around the unrotated AABB center. Absent === 0. */
134
209
  rotation?: number;
135
210
  }
211
+ /** Consumer's pose-composition strategy for hierarchical scenes. `compose`
212
+ * folds a child's pose (in parent's frame) up to the next frame; `decompose`
213
+ * is its inverse. Default is IDENTITY — an absolute-pose scene where every
214
+ * node already stores world coords (parent is grouping-only, no transform). */
215
+ interface PoseComposition<TPose> {
216
+ compose: (parent: TPose, child: TPose) => TPose;
217
+ decompose: (parent: TPose, world: TPose) => TPose;
218
+ }
136
219
 
137
220
  /** Fill rule used by polygon path hit-testing and `ctx.fill()`. */
138
221
  type PathFillRule = 'nonzero' | 'evenodd';
@@ -475,6 +558,26 @@ interface View {
475
558
  };
476
559
  }
477
560
 
561
+ /**
562
+ * Axis-aligned rectangle in world space. Kit-wide `Bounds` shape used by
563
+ * selection, group, and viewport helpers. The optional `rotation` field
564
+ * (radians, around the AABB center) lets selection chrome attach a rotated
565
+ * orientation to an otherwise axis-aligned rect without needing a parallel
566
+ * type.
567
+ */
568
+ interface Bounds {
569
+ x: number;
570
+ y: number;
571
+ width: number;
572
+ height: number;
573
+ rotation?: number;
574
+ }
575
+ /** Pixel dimensions of the canvas viewport. */
576
+ interface ViewportDims {
577
+ width: number;
578
+ height: number;
579
+ }
580
+
478
581
  /**
479
582
  * 2D affine matrix utilities. Column-major 9-element Float32Array, matching
480
583
  * `WebGL2RenderingContext.uniformMatrix3fv` byte order so we can pass the
@@ -682,6 +785,12 @@ interface TextStyle {
682
785
  selectionBackground?: string;
683
786
  /** Selection text color paired with `selectionBackground`. Default: inherits text color. */
684
787
  selectionColor?: string;
788
+ /** Extra advance added after each glyph, in world units. Default 0. */
789
+ letterSpacing?: number;
790
+ /** Default `false`. */
791
+ underline?: boolean;
792
+ /** Default `false`. */
793
+ strikethrough?: boolean;
685
794
  }
686
795
 
687
796
  /**
@@ -692,8 +801,14 @@ interface TextStyle {
692
801
  *
693
802
  * `bold`/`italic` toggles on a run are folded into `fontWeight`/`fontStyle`:
694
803
  * `bold: true` → fontWeight 700, `italic: true` → fontStyle 'italic'.
695
- * Explicit `fontFamily` / `fontSize` / `fill` on the run override the
696
- * node-level value.
804
+ * Explicit `fontFamily` / `fontSize` / `fill` / `letterSpacing` on the run
805
+ * override the node-level value (`letterSpacing: 0` on a run is an override,
806
+ * not an absence — it zeroes inherited tracking).
807
+ *
808
+ * `underline` / `strikethrough` are *additive*, like `bold`/`italic`: a run
809
+ * can turn a decoration on but never off, so they resolve as
810
+ * `run.x || style.x` and not `run.x ?? style.x`. See the header of
811
+ * `runs/rangeStyle.ts` for why the model collapses the tri-state.
697
812
  */
698
813
 
699
814
  interface ResolvedRun {
@@ -703,6 +818,12 @@ interface ResolvedRun {
703
818
  fontWeight: number;
704
819
  fontStyle: 'normal' | 'italic';
705
820
  fill: FillStyle;
821
+ /** Extra advance added after each glyph of this run, in world units. */
822
+ letterSpacing: number;
823
+ /** Draw a rule below this run's baseline. Additive over the node style. */
824
+ underline: boolean;
825
+ /** Draw a rule through this run's x-height. Additive over the node style. */
826
+ strikethrough: boolean;
706
827
  }
707
828
 
708
829
  /**
@@ -847,24 +968,4 @@ interface ShaderDrawCommand {
847
968
  };
848
969
  }
849
970
 
850
- /**
851
- * Axis-aligned rectangle in world space. Kit-wide `Bounds` shape used by
852
- * selection, group, and viewport helpers. The optional `rotation` field
853
- * (radians, around the AABB center) lets selection chrome attach a rotated
854
- * orientation to an otherwise axis-aligned rect without needing a parallel
855
- * type.
856
- */
857
- interface Bounds {
858
- x: number;
859
- y: number;
860
- width: number;
861
- height: number;
862
- rotation?: number;
863
- }
864
- /** Pixel dimensions of the canvas viewport. */
865
- interface ViewportDims {
866
- width: number;
867
- height: number;
868
- }
869
-
870
- export type { Bounds as B, DrawCommand as D, Node as N, Op as O, PathDrawCommand as P, Scene as S, View as V, ViewportDims as a, NodeId as b };
971
+ export type { Bounds as B, DrawCommand as D, History as H, Node as N, Op as O, PathDrawCommand as P, Scene as S, View as V, ViewportDims as a, NodeId as b, Path as c, PoseComposition as d };