umlimator 0.12.1 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -13,6 +13,8 @@ export interface MapModel {
13
13
  rules?: Rule[];
14
14
  /** Narrated walk-throughs of the map (#69): handle.play(id), the standalone page's Stories menu, umlimator.py record. */
15
15
  stories?: Story[];
16
+ /** Saved ways to look at the map (#94): handle.lens(id), the standalone page's Lenses menu and #lens=<id>. */
17
+ lenses?: Lens[];
16
18
  [key: string]: unknown;
17
19
  }
18
20
  /** A badge: its text, and optionally a palette name for its colour (green, amber, rose, accent, violet ... : --um-<tone>). */
@@ -25,6 +27,8 @@ export interface MapNode {
25
27
  opensView?: string;
26
28
  /** The number on the ⤢ badge of a card whose view is not loaded yet (else ⤢ alone). A loaded view's cards are counted. */
27
29
  opensCount?: number;
30
+ /** Who owns the card (a team, say): selectors pick by it, and a lens with color 'owner' rings the card in its owner's tone. */
31
+ owner?: string;
28
32
  [key: string]: unknown;
29
33
  }
30
34
  /** What a line means, drawn as its colour: calls (the default), feeds (hands work on), triggers (suggests, offers, fires),
@@ -52,6 +56,10 @@ export interface MapEdge {
52
56
  change?: 'added' | 'removed' | 'changed';
53
57
  /** What drift() found: undrawn (traffic ran on it, nobody drew it) or idle (drawn, carried nothing). */
54
58
  drift?: 'undrawn' | 'idle';
59
+ /** Budgets (#95), which critical() sums along a path: how long the call takes, in ms, and what it costs, in any unit.
60
+ * Not drawn. */
61
+ latency?: number;
62
+ cost?: number;
55
63
  [key: string]: unknown;
56
64
  }
57
65
  export interface MapView {
@@ -84,14 +92,44 @@ export interface Events {
84
92
  change: Change; // only with `editable`
85
93
  /** E or Alt + click on a stacked card, or the ⤡ tab of the box it unfolded into (#71): before the card unfolds in place
86
94
  * (expanded: true) or folds back (false). view: the card's inner view, which may not be in the model yet; the card
87
- * unfolds once it is (the host loads it and calls update() right here, or later expand()). preventDefault(): no change. */
95
+ * unfolds once it is: the update() that brings it unfolds the card (here or later), or expand(). preventDefault(): no change. */
88
96
  expand: { target: Extract<Ref, { type: 'node' }>; view: string; expanded: boolean; preventDefault(): void };
89
- story: { story: string; step: number | null; steps: number }; // a story shows its step (0-based); step null: it ended
97
+ /** A story changed: it shows a step (0-based), it was paused or resumed (playing), or it ended (step null, on destroy()
98
+ * too). playing (0.14.0): it goes on by itself. */
99
+ story: { story: string; step: number | null; steps: number; playing: boolean };
100
+ /** The map's state changed (0.14.0): at most once a frame, with the keys that changed since the last state event. The
101
+ * camera is the exception: a pan, zoom, glide or drag is one event, once it comes to rest, and until then the event's
102
+ * camera is where it last rested (handle.state reads it live). The last event, on destroy(), has destroyed: true. */
103
+ state: { state: MapState; changed: (keyof MapState)[] };
104
+ /** The lens on the map changed: lens(), or an update() whose model lacks the lens shown (lens: null). */
105
+ lens: { lens: string | null };
106
+ }
107
+
108
+ /** The map's state, for hosts and agents (0.14.0): handle.state, the state event, the standalone page's
109
+ * window.umlimatorMap.state and <umlimator-map>'s el.map.state. Fresh on every read, frozen, and plain JSON. */
110
+ export interface MapState {
111
+ view: string | null; // shown now (null: the model has no views)
112
+ navigating: string | null; // the view a move under way is going to
113
+ selection: Ref[]; // as `selected`
114
+ expanded: string[]; // as `expanded`
115
+ lens: string | null;
116
+ overlays: string[]; // the layers on the map, in stack order ('lens', 'highlight', 'replay', 'story', 'tour', a host's)
117
+ story: { id: string; step: number; steps: number; playing: boolean } | null;
118
+ tour: boolean; // a tour's dot is riding
119
+ replay: { playing: boolean } | null; // a replay under way: playing, or paused
120
+ camera: { x: number; y: number; zoom: number }; // the centre in map units, and the zoom (1: actual size); a state event's: at rest
121
+ flow: boolean;
122
+ motion: 'full' | 'reduced' | 'instant';
123
+ gesture: 'idle' | 'dragging' | 'connecting' | 'naming'; // an edit in progress (editable maps)
124
+ destroyed: boolean;
90
125
  }
91
126
 
92
127
  /** Where the user dropped or placed something: a point in the view and, on a column view, its column and row. */
93
128
  export interface Hint { x: number; y: number; col?: number; row?: number }
94
- /** An edit the user asked for. Nothing changes until the host applies it and calls update(). */
129
+ /** An edit the user asked for. Nothing changes until the host applies it and calls update(). The places it hints (a card
130
+ * moved, reordered or added) are used only if that update's model shows the edit: the card in its new box, a new card
131
+ * with its name, or, for a place alone, the card's `layout` entry written or the model unchanged. Any other update drops
132
+ * them. An update back to the model before an applied move, or after it (undo, redo), puts the cards back as they were. */
95
133
  export type Change = { id: string; view: string; hint?: Hint } & (
96
134
  | { op: 'add'; kind: 'node'; label: string; container?: string }
97
135
  | { op: 'add'; kind: 'edge'; from: string; to: string; label?: string }
@@ -104,6 +142,9 @@ export interface RenderOptions {
104
142
  view?: string; // default: model.root, else the view with no parent
105
143
  select?: Ref | null;
106
144
  controls?: boolean; // up / zoom / fit / 1:1 / flow buttons (default true)
145
+ /** 'zoom' (the default): the wheel zooms the map, and a trackpad pans it. 'ctrl': only Ctrl or ⌘ with the wheel (and a
146
+ * trackpad's pinch) zooms, and the wheel otherwise scrolls the page, as a map in a document should. */
147
+ wheel?: 'zoom' | 'ctrl';
107
148
  minimap?: boolean; // default false
108
149
  flow?: boolean; // animated flow along lines (default: on unless prefers-reduced-motion)
109
150
  /** Text widths: default the host's font (--um-sans, measured on a canvas); false: a font-independent estimate. */
@@ -115,6 +156,20 @@ export interface RenderOptions {
115
156
  /** update() eases into the next model: moved cards glide, new ones fade in, removed ones fade out (#67). Default true;
116
157
  * never under prefers-reduced-motion. false draws the next model at once. */
117
158
  ease?: boolean;
159
+ /** The standalone page's pieces (#99), each on its own or any mix, all off by default. They are drawn inside the map's
160
+ * element, around the canvas, with the page's look, keys and --um-* colours; update() and destroy() keep them in step.
161
+ * nav: the views list (Miller columns) left of the map, opened and shut by its ☰ button or O; picking a view shows it
162
+ * (viewchange). It comes after the map in the tab order, so focus given to the map's element still lands on the map. */
163
+ nav?: boolean;
164
+ /** The kinds of cards and lines the view draws, in a box over the map, bottom left, that folds away. */
165
+ legend?: boolean;
166
+ /** A line over the map's foot: what the pointer does, and for a while what just happened (a polite live region). */
167
+ hints?: boolean;
168
+ /** The selected card's details under the map: its kind, actions and tabs (when, what, how, an example, what it comes
169
+ * from and leads to), [ and ] between tabs. Open and Unfold here emit open and expand as the canvas does; → ← ⇄ light
170
+ * what the card leads to, what leads to it, and every path between two selected cards (> < P); ⏱ ¤ the slowest and the
171
+ * costliest path where the view's lines have latency or cost, and ⊘ what loses its way if the card goes down (#95). */
172
+ details?: boolean;
118
173
  on?: { [K in keyof Events]?: (e: Events[K]) => void };
119
174
  }
120
175
 
@@ -126,6 +181,9 @@ export interface Handle {
126
181
  * where it was; none of it with the option `ease: false` or under prefers-reduced-motion, and an export at any point
127
182
  * draws where things end up. */
128
183
  update(model: MapModel): void;
184
+ /** Shows a view, zooming in or out as a drill-down does. A request during a move takes the move to its own view (the
185
+ * latest wins). Resolves true once the view shows; false for a view the model lacks (with a console warning), when a
186
+ * later request supersedes it, or on destroy(). */
129
187
  setView(id: string): Promise<void> | void;
130
188
  select(ref: Ref | null): void;
131
189
  fit(): void;
@@ -153,17 +211,38 @@ export interface Handle {
153
211
  readonly view: string | null;
154
212
  readonly selection: Ref | null; // the latest selected
155
213
  readonly selected: Ref[]; // every selected thing (Shift / Cmd / Ctrl + click adds cards), the latest last
214
+ /** The map's state as one snapshot (0.14.0): fresh, frozen and JSON-serializable on every read. */
215
+ readonly state: MapState;
216
+ /** The view a move is going to, while one is under way (setView(), a drill-down, up); else null. */
217
+ readonly navigating: string | null;
218
+ /** True once destroy() has run: calls then do nothing, and every promise has settled. */
219
+ readonly destroyed: boolean;
220
+ /** The animated flow along lines (the ≋ button and F), to read or set. */
221
+ flow: boolean;
222
+ /** How this map moves: 'instant' (drawn without animation, as the page's #check, #still and #shot are), 'reduced' (the
223
+ * reader's setting), else 'full'. */
224
+ readonly motion: 'full' | 'reduced' | 'instant';
225
+ /** The player of the story shown, whoever started it (play(), the standalone page's Stories menu or a link), else null. */
226
+ readonly story: StoryPlayer | null;
156
227
  /** Sets (or, with null, removes) a named layer of marks, drawn over the picture without touching the model. Layers stack
157
228
  * in the order first set: each property comes from the latest layer that sets it, and a card or line is as lit as its
158
229
  * lowest level. They survive update() and setView(); ids the view doesn't show are ignored. toSVG() and toPNG() draw them. */
159
230
  overlay(name: string, layer: Overlay | null): void;
160
231
  /** The 'highlight' layer: these cards and lines lit, the rest dimmed (in place of the selection's dimming), or null to clear.
161
- * A Ref's level (0 to 1), else its distance (as reach() gives it), fades it. A chip's or badge's Ref lights its card. */
162
- highlight(refs: Lit[] | null): void;
232
+ * A Ref's level (0 to 1), else its distance (as reach() gives it), fades it. A chip's or badge's Ref lights its card.
233
+ * layer (0.14.0): draw them as a layer of that name instead, so two owners never clear each other's (the MCP page's
234
+ * assistant uses 'mcp'); null clears that layer only. */
235
+ highlight(refs: Lit[] | null, options?: { layer?: string }): void;
236
+ /** Puts one of the model's lenses on the map (#94), or none with null: the 'lens' layer lights what it picks and dims the
237
+ * rest, on every view shown, and the view is not laid out again. With color 'owner', each lit card is ringed in its
238
+ * owner's tone, and a legend over the controls names them. It stays across update() and setView(); a model without the
239
+ * lens takes it off. False when the model has no lens `id`. */
240
+ lens(id: string | null): boolean;
163
241
  /** The tour dot rides the line between each pair of cards in turn (either way), through each card to its next line; a card
164
242
  * it reaches flashes, and a pair with no line between them is skipped. Resolves true at the end, false when cut short (a
165
- * new tour or replay, tour(null), a new picture of the view, destroy()). With reduced motion it resolves at once.
166
- * speed: map units a second (default 240). */
243
+ * new tour or replay, tour(null), Esc on the map, a new picture of the view, destroy()). With reduced motion no dot rides:
244
+ * it resolves at once, and the path is lit at rest (its cards ringed and numbered in turn, its lines glowing: the 'tour'
245
+ * layer) and said, until the next tour or replay, tour(null), Esc or a new picture. speed: map units a second (default 240). */
167
246
  tour(path: string[] | null, options?: { speed?: number }): Promise<boolean>;
168
247
  /** Plays a trace: each call from one card to another (a span and its parent's) runs along their line at the trace's relative
169
248
  * time; the line glows while the call lasts and keeps its duration as a label (the 'replay' layer, until stop() or the next
@@ -173,8 +252,11 @@ export interface Handle {
173
252
  /** Tells one of the model's stories (#69): each step in turn sets the view, camera, selection, highlight and caption
174
253
  * (drawn in a bar on the map and said to screen readers), then plays its tour. auto (default true): the next step
175
254
  * follows once a step's duration is up and its tour has ended. step: where to start (0-based). The bar and the keys
176
- * (← → PageUp PageDown, Esc) step through and stop it. null when the model has no story `id` with steps. */
177
- play(id: string, options?: { step?: number; auto?: boolean }): StoryPlayer | null;
255
+ * (← → PageUp PageDown, Esc) step through and stop it. The reader taking the map (a press, a key, a view or a selection
256
+ * the story didn't make) pauses it, and a pause holds the step's tour too, until resume(). A step folds back the cards
257
+ * unfolded on its view first, and its highlight is its own 'story' layer, which dims alone while the step shows.
258
+ * null when the model has no story `id` with steps, and the story shown, if any, plays on; play(null) stops it. */
259
+ play(id: string | null, options?: { step?: number; auto?: boolean }): StoryPlayer | null;
178
260
  destroy(): void;
179
261
  }
180
262
 
@@ -188,9 +270,11 @@ export interface StoryStep {
188
270
  camera?: 'fit' | { x: number; y: number; zoom?: number } | { card: string; zoom?: number }; duration?: number;
189
271
  }
190
272
  /** A story being told. step: the one shown (0-based). next() past the last step ends it (done: true); stop() closes it
191
- * (done: false). */
273
+ * (done: false). state (0.14.0): entering a step (its view, camera, selection and highlight on their way), playing or paused
274
+ * on it, then ended or stopped. */
192
275
  export interface StoryPlayer {
193
276
  readonly story: string; readonly step: number; readonly steps: number; readonly playing: boolean; readonly done: Promise<boolean>;
277
+ readonly state: 'entering' | 'playing' | 'paused' | 'ended' | 'stopped';
194
278
  next(): void; prev(): void; pause(): void; resume(): void; stop(): void;
195
279
  }
196
280
 
@@ -239,9 +323,16 @@ export interface CheckIssue { message: string; targets: Ref[] }
239
323
  * it measures in the DOM (text overflowing its card, titles, edge labels on cards or each other). In Node, text widths
240
324
  * are an estimate unless `measure` is given, so the layout, and what is found, can differ from a browser's. */
241
325
  export function check(model: MapModel, viewId: string, options?: { repair?: boolean; measure?: (text: string, px: number) => number; expand?: string[] }): CheckIssue[];
242
- /** Picks cards by id, kind (as the map draws it: "db" picks databases) or box (at any depth), each a value or a list;
243
- * every key given must hold, and `not` turns a selector round. A line's end on a box is that box: in it, of no kind. */
244
- export interface Selector { id?: string | string[]; kind?: string | string[]; container?: string | string[]; not?: Selector }
326
+ /** Picks cards by id, kind (as the map draws it: "db" picks databases), box (at any depth) or owner, each a value or a
327
+ * list; every key given must hold, and `not` turns a selector round. A line's end on a box is that box: in it, of no kind
328
+ * and no owner. */
329
+ export interface Selector { id?: string | string[]; kind?: string | string[]; container?: string | string[]; owner?: string | string[]; not?: Selector }
330
+ /** A lens's pick: a Selector, and `line`, line kinds. Without `line` it lights the cards it picks and the lines between two
331
+ * of them; with `line`, the lines of those kinds between two cards the rest of it picks, and their ends. */
332
+ export interface LensSelector extends Selector { line?: WireKind | WireKind[] }
333
+ /** A saved way to look at the map (#94): what `pick` picks lit, the rest dimmed (a pick left out lights everything);
334
+ * color 'owner' rings each lit card in its owner's tone, with a legend. */
335
+ export interface Lens { id: string; title?: string; pick?: LensSelector; color?: 'owner' }
245
336
  /** What the model says must hold (`model.rules`): no line from the cards `from` picks to the cards `to` picks (a side left
246
337
  * out picks every card), or, with noCycles, no boxes that reach each other through their lines (a line counts between
247
338
  * the two boxes that hold its ends right under the box they share). A rule that names a box (a container, or a box's
@@ -261,6 +352,20 @@ export function reach(model: MapModel, viewId: string, from: string, options?: {
261
352
  export function describe(model: MapModel, viewId: string): string;
262
353
  /** The spans of an OpenTelemetry trace in OTLP JSON (resourceSpans), as replay() takes them. */
263
354
  export function otlpSpans(otlp: unknown): Span[];
355
+ /** The path along a view's lines with the highest total of `by` (#95): latency (ms, the default) or cost, summed over its
356
+ * lines (a line without one counts 0), from `from` to `to` (either left out: any card), never coming back to a card. Its
357
+ * cards in order, then its lines, as Refs for highlight(); no refs and 0 when no line leads there. No browser needed. */
358
+ export function critical(model: MapModel, viewId: string, options?: { from?: string; to?: string; by?: 'latency' | 'cost' }): { refs: Ref[]; total: number };
359
+ /** What taking cards out of a view would cost (#95), with the model left as it is. unreachable: what loses its way to them
360
+ * (backward, as reach() finds it: for calls, what depends on them), each with its distance from the nearest, the cards
361
+ * themselves at 0. No rule breaks: rules only forbid lines and cycles, which taking cards out never adds. No browser needed. */
362
+ export function whatIf(model: MapModel, viewId: string, options: { remove: string[] }): { unreachable: (Ref & { distance: number })[] };
363
+ /** A trace kept as a numbered view (#96): `model` with a sequence view (render: "sequence") whose steps are the trace's calls
364
+ * between cards (a span and its parent's), in time order, each line labelled with its span's name and its duration under
365
+ * it. A span's service is a card as replay() matches it (match, else id, else label in any case); the caption names the
366
+ * services with no card. The view (id `view`, default "trace") replaces one with that id, else comes last, under the
367
+ * view that shows the most of its cards. The model passed in is not changed. */
368
+ export function sequenceFromTrace(model: MapModel, spans: Span[], options?: { view?: string; match?: Record<string, string>; title?: string }): MapModel;
264
369
  /** What changed from one model to the next (#66), as one model to draw: `after`, with each card marked added, changed or
265
370
  * removed (`change`: a NEW or CHANGED tab; REMOVED is faded, dashed and struck through) and each line likewise (`style`
266
371
  * added or removed; a changed one `edited`); what left comes back in its views, boxes and places, and a card put into