umlimator 0.10.0 → 0.12.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.
package/README.md CHANGED
@@ -7,22 +7,36 @@ that writes a self-contained HTML page and still pictures (SVG, PNG) for documen
7
7
 
8
8
  ![The example model's overview: a made-up bookshop with logos, line icons and a numbered flow behind the API card](examples/stills/overview.png)
9
9
 
10
- **Examples:** [live on GitHub Pages](https://zintaen.github.io/umlimator/), and [what each one shows](examples/README.md): a C4-style
10
+ **Examples:** [live on GitHub Pages](https://zintaen.github.io/umlimator/), a [playground](https://zintaen.github.io/umlimator/examples/playground.html)
11
+ to try a model in, and [what each one shows](examples/README.md): a C4-style
11
12
  drill-down, a data pipeline, an AI agent's skills, and host pages for embedding, live updates and editing.
12
13
 
13
14
  ## What it does
14
15
 
15
16
  - **Layers**: a view per level (system, containers, components, flows). A stacked card opens its inner view, which the
16
17
  host may load only when asked.
17
- - **Layout and routing**: columns in reading order, each ordered so the lines between columns cross as little as they
18
- can; orthogonal lines that never run through a card, room for every label, containers kept apart, and cards with no
19
- lines in a row of their own, under the view or at the bottom of their box. The same model gives the same picture.
18
+ - **Unfolding in place**: a stacked card can also open into a box on the map it is on (E, or Alt + click), its inner
19
+ view's cards inside and the lines to it moved onto them, the rest of the picture in its order.
20
+ - **Layout and routing**: columns in reading order, each ordered so the lines cross as little as they can, those that
21
+ skip columns included; each card level with the cards its lines come from, so lines run straight; orthogonal lines
22
+ that never run through a card, room for every label, containers kept apart, and cards with no lines in a row of their
23
+ own, under the view or at the bottom of their box. The same model gives the same picture.
20
24
  - **Reading**: animated flow, neighbours lit on selection, numbered steps, tours, minimap, light and dark themes.
25
+ - **Exploring**: every path between two cards, or everything a card reaches, lit on the map; a view in plain sentences
26
+ (`describe()`); a tour along any path the host names.
27
+ - **Live data**: marks over the map without touching the model (`overlay()`): flow as fast as the traffic, errors that
28
+ pulse, slow hops that glow, labels and tabs; and an OpenTelemetry trace replayed along its lines (`replay()`).
29
+ - **Stories**: walk-throughs in the model, step by step (view, camera, selection, highlight, tour and a caption), played
30
+ on the map (`play()`) or from the page's Stories menu, recorded from the page, and written as an animated PNG.
21
31
  - **Keyboard and screen readers**: cards, then lines, in the Tab order (a card's tabs and chips too, for a host that
22
32
  listens for click); arrows between cards; selections announced.
23
33
  - **Editing** (opt-in): gestures emit `change` events; the host applies them and calls `update()`.
24
34
  - **Export**: any view as a stand-alone SVG or PNG, in the browser or from the CLI.
25
35
  - **A layout check**: crossings, shared lines, detours, labels on lines, text that overflows its card.
36
+ - **Rules**: what the model says must hold ("apps never call a database directly", "no cycles between teams' boxes"),
37
+ marked on the map where it breaks; `umlimator.py check` fails on a broken rule, so CI does.
38
+ - **Change**: what a pull request changes, marked on the map and said in words (with a GitHub Action that posts it);
39
+ a time-lapse of a model's history, where cards glide, fade in and fade out; and drift, the map against the traffic seen.
26
40
 
27
41
  ## Install
28
42
 
@@ -30,7 +44,7 @@ drill-down, a data pipeline, an AI agent's skills, and host pages for embedding,
30
44
  npm install umlimator
31
45
  ```
32
46
 
33
- Or from GitHub, at a tag or any commit: `"umlimator": "github:zintaen/umlimator#v0.10.0"`. npm 12 refuses git
47
+ Or from GitHub, at a tag or any commit: `"umlimator": "github:zintaen/umlimator#v0.12.0"`. npm 12 refuses git
34
48
  dependencies unless the project opts in, so that also needs `allow-git=root` in the `.npmrc` next to `package.json`.
35
49
 
36
50
  ## Embed
@@ -61,11 +75,21 @@ function MapView({ model, onSelect }) {
61
75
  ```
62
76
 
63
77
  - `render` draws one view at a time into a shadow root (give the element a height), with pan, zoom and drill-down.
64
- Events (`click`, `select`, `open`, `viewchange`, `change`) name what they are about by ids from the model.
78
+ Events (`click`, `select`, `open`, `viewchange`, `change`, `expand`) name what they are about by ids from the model.
79
+ - `map.expand(id)` unfolds a stacked card in place and `map.collapse(id)` folds it; `layout(model, view, { expand })`
80
+ draws the same in Node.
65
81
  - Colours are `--um-*` CSS variables, fonts are the host's (`--um-sans`, `--um-mono`), text is measured in that font,
66
82
  and nothing is fetched.
67
- - `layout(model, viewId)` returns a view's geometry without a DOM, in Node too.
83
+ - `layout(model, viewId)` returns a view's geometry without a DOM, in Node too; `check(model, viewId)` runs the layout
84
+ check on it, and `validate(model)` finds dangling or repeated ids before anything is drawn. `rules(model)` lists every
85
+ rule the model breaks, view by view.
86
+ - `map.overlay(name, layer)`, `highlight(refs)`, `tour(path)` and `replay(spans)` put the host's marks, a tour and a
87
+ trace on the map; `paths()`, `reach()` and `describe()` walk a view and put it in words, in Node too.
68
88
  - `editable: true` turns on editing gestures ([docs/editing.md](docs/editing.md); `examples/edit.html` is a working host).
89
+ - `diff(before, after)` returns `after` with each card and line marked added, changed or removed, what changed in words,
90
+ and the views that show it; `drift(model, observed)` marks the lines traffic ran on that nobody drew, and the drawn ones
91
+ that carried nothing. Both are models to draw, in Node too. `update()` eases into the next model (`examples/timelapse.html`);
92
+ the option `ease: false` draws it at once.
69
93
  - `mountPage` is the whole standalone viewer that the CLI writes.
70
94
 
71
95
  The API, its types and the decisions behind it: [docs/module-api.md](docs/module-api.md) and `lib/umlimator.d.ts`.
@@ -75,6 +99,8 @@ The API, its types and the decisions behind it: [docs/module-api.md](docs/module
75
99
  A model has shared `nodes` and `edges`, and `views` that each show a subset, with optional `containers` (boxes),
76
100
  `layout` (`[col, row]` per card), `render: "sequence"` for numbered run-order views and `tour` for a dot that rides a
77
101
  path. The full schema is `engine/references/schema.md` (§ Map model); `examples/bookshop.json` uses most of it.
102
+ A model file that starts with `"$schema": "<path to>/umlimator.schema.json"` (exported as `umlimator/schema.json`)
103
+ gets completion and checks in editors, and `validate(model)` lists its errors and warnings, each with where it is.
78
104
 
79
105
  Card kinds: `frontend`, `backend`, `ai`, `database`, `queue`, `security`, `cloud`, `external` and `file` (skill-map
80
106
  kinds such as `public`, `lane`, `script`, `hook` and `board` work too). Fields beyond the schema:
@@ -91,12 +117,16 @@ kinds such as `public`, `lane`, `script`, `hook` and `board` work too). Fields b
91
117
  | edge | `sublabel` | a second, smaller line under the label |
92
118
  | edge | `fromSide`, `toSide` | the card sides the line uses |
93
119
  | edge | `from`, `to` | may name a container of the view: the line ends on its box |
120
+ | edge | `style: "faded"` | the line in its kind's colour, faint and with no flow (drift's idle lines) |
121
+ | edge | `drift` | what `drift()` found: `undrawn` or `idle` |
94
122
  | view | `gap`, `rgap` | the space between columns and between rows |
95
123
  | view | `keepPairs` | A→B and B→A drawn as two lines |
96
124
  | view | `repair: false` | no port repair |
97
125
  | view | `subAndChips` | cards 80 px tall that show their sub-line, then their chips |
98
126
  | view | `kindsFromLabels` | a line whose edges name no `kind` takes one guessed from its label |
99
127
  | container | `badges` | tabs on the box's border |
128
+ | model | `rules` | what must hold: no line `from` some cards `to` others (picked by id, kind or box), or `noCycles` between boxes |
129
+ | model | `stories` | walk-throughs: steps that each set the view, camera, selection, highlight, tour and caption |
100
130
 
101
131
  ## Command line
102
132
 
@@ -105,11 +135,59 @@ python3 umlimator.py render examples/bookshop.json --out page.html # the inter
105
135
  python3 umlimator.py stills page.html --out stills/ # every view as SVG and PNG
106
136
  python3 umlimator.py stills page.html --out stills/ --views overview,api --scale 1.5 --theme light
107
137
  python3 umlimator.py check page.html --views 'pic-*' # the layout check on chosen views
138
+ python3 umlimator.py diff before.json after.json --out diff.html --stills diff/ # what changed, marked and in words
139
+ python3 umlimator.py diff --base origin/main --out diff/ # every model changed since a ref, and a summary.md
140
+ python3 umlimator.py drift model.json traffic.json --out drift.html # drawn vs. seen (a Jaeger or Tempo dependency list)
141
+ python3 umlimator.py timelapse model.json --out history.html # the model's versions in git history, on a slider
142
+ python3 umlimator.py check page.html --expand # also each view with each stacked card unfolded
143
+ python3 umlimator.py record bike-share.html --story ride --out ride.png # bike-share's story as an animated PNG, a frame per step
108
144
  ```
109
145
 
110
- The page has breadcrumbs, a details panel per card, ⌘K search, deep links (`#view=<id>&node=<id>`) and export.
146
+ `check` also reports each rule the model breaks, and then exits 1.
147
+
148
+ The page has breadcrumbs, a details panel per card, ⌘K search, deep links (`#view=<id>&node=<id>`, and
149
+ `#story=<id>&step=<n>` for a story's step), stories, and export.
111
150
  `stills` skips a view whose inputs have not changed since its last export (`--all` exports every view). Needs Python
112
- 3.9+ (standard library only), and Google Chrome or Chromium for `stills` and `check`.
151
+ 3.9+ (standard library only), Google Chrome or Chromium for `stills`, `check` and `record`, and Node 18+ for `diff`
152
+ and `drift`.
153
+
154
+ `record` writes an animated PNG, which browsers and GitHub show as it is. For a GIF or an MP4 (for chat or slides),
155
+ convert it with ffmpeg:
156
+
157
+ ```bash
158
+ ffmpeg -f apng -i ride.png -vf "split[a][b];[a]palettegen[p];[b][p]paletteuse" ride.gif
159
+ ffmpeg -f apng -i ride.png -vf "scale=trunc(iw/2)*2:trunc(ih/2)*2" -pix_fmt yuv420p ride.mp4
160
+ ```
161
+
162
+ On pull requests, the diff Action posts what they change in your maps: one comment with the summary and a sketch, and
163
+ the pictures as the run's artifact.
164
+
165
+ ```yaml
166
+ on: pull_request
167
+ permissions: { contents: read, pull-requests: write }
168
+ jobs:
169
+ maps:
170
+ runs-on: ubuntu-latest
171
+ steps:
172
+ - uses: actions/checkout@v4
173
+ - uses: zintaen/umlimator@v0.12.0
174
+ with:
175
+ models: "docs/*.json" # optional: where the models are (default: every .json that is a map model)
176
+ ```
177
+
178
+ ## Map a repo with Claude Code
179
+
180
+ The `map-repo` skill reads a codebase, writes a C4-style model (context, containers, components), renders it to a page
181
+ and stills, and fixes what the layout check reports. Install it, then ask Claude Code to map the repository:
182
+
183
+ ```
184
+ /plugin marketplace add zintaen/umlimator
185
+ /plugin install umlimator@umlimator
186
+ ```
187
+
188
+ On Claude Code 2.1.275 or later, `/plugin install umlimator --marketplace zintaen/umlimator` does both in one step. The
189
+ skill needs git, Python 3.9+ and Chrome; on first use it fetches the CLI of one pinned release into
190
+ `~/.cache/umlimator/cli`.
113
191
 
114
192
  ## Icons
115
193
 
@@ -145,13 +223,15 @@ Release with that section as its notes, and publishes the package to npm (truste
145
223
  CI also publishes the examples to GitHub Pages from `main`.
146
224
 
147
225
  ```
148
- umlimator.py CLI: render, stills, check, pin-icons
226
+ umlimator.py CLI: render, stills, check, record, pin-icons, diff, drift, timelapse
227
+ action.yml the diff Action: what a pull request changes in its maps, posted on it
149
228
  lib/ the module (umlimator.js), its types and the default theme
150
- engine/ the page template the CLI inlines the module into; the upstream renderer and schema
229
+ engine/ the page templates the CLI inlines the module into; the upstream renderer and schema
151
230
  docs/ the module's API and the editing design
152
231
  tests/, bench/ tests, benchmark models and results
153
232
  icons/ line icons and the logo manifest (sources and hashes only)
154
233
  examples/ made-up systems: models, host pages and a Node script (live: zintaen.github.io/umlimator)
234
+ .claude-plugin/, plugins/ the Claude Code marketplace and its map-repo skill
155
235
  ```
156
236
 
157
237
  ## Licence
@@ -9,6 +9,10 @@ export interface MapModel {
9
9
  views: MapView[];
10
10
  icons?: Record<string, { g?: string; src?: string; dark?: boolean }>;
11
11
  authors?: Record<string, { name?: string; url?: string; avatar?: string }>;
12
+ /** What must hold: rules() lists what breaks them, the map marks it, and umlimator.py check fails on it. */
13
+ rules?: Rule[];
14
+ /** Narrated walk-throughs of the map (#69): handle.play(id), the standalone page's Stories menu, umlimator.py record. */
15
+ stories?: Story[];
12
16
  [key: string]: unknown;
13
17
  }
14
18
  /** A badge: its text, and optionally a palette name for its colour (green, amber, rose, accent, violet ... : --um-<tone>). */
@@ -40,6 +44,14 @@ export interface MapEdge {
40
44
  /** A second, smaller line under the label (a technology, say), placed with it and counted in the room left for labels.
41
45
  * Shown only with a label. A line that stands for several edges shows each of their sublabels, joined by " / ". */
42
46
  sublabel?: string;
47
+ /** A style that wins over the kind's colour: emphasis (green), added (green, flowing), dashed or async (violet),
48
+ * security (rose, dashed), removed (rose, dashed, faded) or faded (its kind's colour, faint, with no flow). */
49
+ style?: 'emphasis' | 'added' | 'dashed' | 'async' | 'security' | 'removed' | 'faded';
50
+ /** A change mark: added and removed draw only with the matching `style` on a map; diff() sets both, and marks a
51
+ * changed line `edited`. */
52
+ change?: 'added' | 'removed' | 'changed';
53
+ /** What drift() found: undrawn (traffic ran on it, nobody drew it) or idle (drawn, carried nothing). */
54
+ drift?: 'undrawn' | 'idle';
43
55
  [key: string]: unknown;
44
56
  }
45
57
  export interface MapView {
@@ -70,6 +82,11 @@ export interface Events {
70
82
  open: { target: Ref; view: string; preventDefault(): void }; // view: the one the card opens, which may not be in the model yet
71
83
  viewchange: { view: string; from: string };
72
84
  change: Change; // only with `editable`
85
+ /** 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
+ * (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. */
88
+ 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
73
90
  }
74
91
 
75
92
  /** Where the user dropped or placed something: a point in the view and, on a column view, its column and row. */
@@ -95,12 +112,19 @@ export interface RenderOptions {
95
112
  editable?: boolean | { add?: boolean; rename?: boolean; move?: boolean; delete?: boolean; reorder?: boolean };
96
113
  /** Cards keep their places across update() (in this handle only, never in the model). Default true. */
97
114
  stableLayout?: boolean;
115
+ /** update() eases into the next model: moved cards glide, new ones fade in, removed ones fade out (#67). Default true;
116
+ * never under prefers-reduced-motion. false draws the next model at once. */
117
+ ease?: boolean;
98
118
  on?: { [K in keyof Events]?: (e: Events[K]) => void };
99
119
  }
100
120
 
101
121
  export interface ExportOptions { view?: string; scale?: number; fontCSS?: string }
102
122
 
103
123
  export interface Handle {
124
+ /** Draws the next model, redrawing only what changed and keeping the view, camera and selection. With motion it eases
125
+ * in (#67): moved cards glide from where they were, new cards and rerouted lines fade in, and what left fades out
126
+ * where it was; none of it with the option `ease: false` or under prefers-reduced-motion, and an export at any point
127
+ * draws where things end up. */
104
128
  update(model: MapModel): void;
105
129
  setView(id: string): Promise<void> | void;
106
130
  select(ref: Ref | null): void;
@@ -117,12 +141,74 @@ export interface Handle {
117
141
  * the selected card or line. */
118
142
  focus(ref?: Ref | null): void;
119
143
  on<K extends keyof Events>(type: K, fn: (e: Events[K]) => void): () => void;
144
+ /** Unfolds a stacked card of the view shown into a box in place (#71): its inner view's own cards inside, this view's
145
+ * lines to it moved onto them, the rest of the picture in its order. In this handle only, never in the model; kept per
146
+ * view across update(). False when it can't: no loaded inner view, nothing of its own inside, a numbered (sequence)
147
+ * view in or opened, an editable map. No expand event: that is for gestures. */
148
+ expand(id: string): boolean;
149
+ /** Folds an unfolded card back (and anything unfolded inside it). False when it was not unfolded. */
150
+ collapse(id: string): boolean;
151
+ readonly expanded: string[]; // the cards unfolded on the view shown, in the order they were
120
152
  readonly view: string | null;
121
153
  readonly selection: Ref | null; // the latest selected
122
154
  readonly selected: Ref[]; // every selected thing (Shift / Cmd / Ctrl + click adds cards), the latest last
155
+ /** Sets (or, with null, removes) a named layer of marks, drawn over the picture without touching the model. Layers stack
156
+ * 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
157
+ * lowest level. They survive update() and setView(); ids the view doesn't show are ignored. toSVG() and toPNG() draw them. */
158
+ overlay(name: string, layer: Overlay | null): void;
159
+ /** The 'highlight' layer: these cards and lines lit, the rest dimmed (in place of the selection's dimming), or null to clear.
160
+ * 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. */
161
+ highlight(refs: Lit[] | null): void;
162
+ /** The tour dot rides the line between each pair of cards in turn (either way), through each card to its next line; a card
163
+ * it reaches flashes, and a pair with no line between them is skipped. Resolves true at the end, false when cut short (a
164
+ * new tour or replay, tour(null), a new picture of the view, destroy()). With reduced motion it resolves at once.
165
+ * speed: map units a second (default 240). */
166
+ tour(path: string[] | null, options?: { speed?: number }): Promise<boolean>;
167
+ /** 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
168
+ * time; the line glows while the call lasts and keeps its duration as a label (the 'replay' layer, until stop() or the next
169
+ * replay). A span's service names its card: through `match`, else as a card's id, else as its label (any case).
170
+ * duration: the whole trace's playing time in ms (default 6000). With reduced motion the labels appear at once. */
171
+ replay(spans: Span[], options?: { match?: Record<string, string>; duration?: number }): Replay;
172
+ /** Tells one of the model's stories (#69): each step in turn sets the view, camera, selection, highlight and caption
173
+ * (drawn in a bar on the map and said to screen readers), then plays its tour. auto (default true): the next step
174
+ * follows once a step's duration is up and its tour has ended. step: where to start (0-based). The bar and the keys
175
+ * (← → PageUp PageDown, Esc) step through and stop it. null when the model has no story `id` with steps. */
176
+ play(id: string, options?: { step?: number; auto?: boolean }): StoryPlayer | null;
123
177
  destroy(): void;
124
178
  }
125
179
 
180
+ /** A walk-through: its steps, in order. */
181
+ export interface Story { id: string; title?: string; steps: StoryStep[] }
182
+ /** What one step shows. select: a card id, a line id, or a Ref. highlight: card ids, line ids or Lits; the lines between
183
+ * two cards it names light too. tour: card ids, the dot's path. camera: 'fit' (the default), a point and zoom, or a card
184
+ * to frame. duration: the step's time in ms when playing (default 4000; its tour runs to its end first). */
185
+ export interface StoryStep {
186
+ view: string; caption?: string; select?: string | Ref; highlight?: (string | Lit)[]; tour?: string[];
187
+ camera?: 'fit' | { x: number; y: number; zoom?: number } | { card: string; zoom?: number }; duration?: number;
188
+ }
189
+ /** A story being told. step: the one shown (0-based). next() past the last step ends it (done: true); stop() closes it
190
+ * (done: false). */
191
+ export interface StoryPlayer {
192
+ readonly story: string; readonly step: number; readonly steps: number; readonly playing: boolean; readonly done: Promise<boolean>;
193
+ next(): void; prev(): void; pause(): void; resume(): void; stop(): void;
194
+ }
195
+
196
+ /** A mark on a card. tone: a palette name (--um-<tone>), drawn as a ring; badge: a tab after the card's own; level: 0 to 1. */
197
+ export interface CardMark { tone?: string; badge?: Badge; pulse?: boolean; level?: number }
198
+ /** A mark on a line. speed: its flow's speed, as a factor (0 stops it); tone: a glow under it; label: a pill under its own;
199
+ * dash: drawn dashed; level: 0 to 1. A drawn line that stands for several edges takes the strongest of their marks. */
200
+ export interface LineMark { tone?: string; speed?: number; pulse?: boolean; label?: string; level?: number; dash?: boolean }
201
+ /** A layer of marks: cards by node id, lines by edge id ("<from>-><to>#<n>" for an edge without one, as Refs name it).
202
+ * dim: what the layer doesn't name fades. */
203
+ export interface Overlay { nodes?: Record<string, CardMark>; edges?: Record<string, LineMark>; dim?: boolean }
204
+ /** A Ref for highlight(), with how lit it is: level (0 to 1), else distance (each step out a little fainter). */
205
+ export type Lit = Ref & { level?: number; distance?: number };
206
+ /** A span of a trace: start and end in milliseconds; service names the card it ran in; parent is its caller's span id. */
207
+ export interface Span { id: string; parent?: string | null; service: string; start: number; end: number; name?: string }
208
+ /** A replay in progress. hops: the calls it plays; skipped: spans whose service has no card on the view, or whose call has
209
+ * no line there. done: true when it played to the end, false when stopped or cut short. */
210
+ export interface Replay { readonly hops: number; readonly skipped: number; readonly done: Promise<boolean>; pause(): void; resume(): void; stop(): void }
211
+
126
212
  export interface Geometry {
127
213
  nodes: { id: string; x: number; y: number; w: number; h: number; [key: string]: unknown }[];
128
214
  edges: { from: string; to: string; ids: string[]; pts: [number, number][]; weight: 1 | 2 | 3; wk: WireKind;
@@ -135,6 +221,62 @@ export function render(container: HTMLElement, model: MapModel, options?: Render
135
221
  /** Undo for a host: it pushes each model it applies, and hands undo() / redo() results to handle.update(). */
136
222
  export interface History { readonly model: MapModel; readonly canUndo: boolean; readonly canRedo: boolean; push(m: MapModel): MapModel; undo(): MapModel; redo(): MapModel }
137
223
  export function history(model: MapModel, limit?: number): History;
138
- export function layout(model: MapModel, viewId: string, options?: { repair?: boolean; measure?: (text: string, px: number) => number }): Geometry;
224
+ /** expand: cards of the view unfolded in place (#71), as handle.expand() draws them; the geometry's `unfolded` lists those
225
+ * that did (one inside another after it), and its `families` the boxes they became (`unfolds`: the inner view). */
226
+ export function layout(model: MapModel, viewId: string, options?: { repair?: boolean; measure?: (text: string, px: number) => number; expand?: string[] }): Geometry & { unfolded?: string[] };
227
+ /** A model's own problem. path says where: "views[2].nodeIds[4]". Errors draw as holes or not at all (missing,
228
+ * repeated or dangling ids, a box inside itself); warnings draw other than they say (an unknown kind, weight or side,
229
+ * a view a card opens that the host has not loaded yet). The JSON Schema is umlimator/schema.json. */
230
+ export interface Problem { level: 'error' | 'warning'; path: string; message: string }
231
+ export function validate(model: MapModel): Problem[];
232
+ /** One issue of the layout check: its message, as the page's check words it, and the cards, lines and boxes it is about
233
+ * (exactly those, even where two cards share a label). */
234
+ export interface CheckIssue { message: string; targets: Ref[] }
235
+ /** The layout check's geometry for one view, on layout()'s geometry: no browser needed. The page's check (CLI) adds what
236
+ * it measures in the DOM (text overflowing its card, titles, edge labels on cards or each other). In Node, text widths
237
+ * are an estimate unless `measure` is given, so the layout, and what is found, can differ from a browser's. */
238
+ export function check(model: MapModel, viewId: string, options?: { repair?: boolean; measure?: (text: string, px: number) => number; expand?: string[] }): CheckIssue[];
239
+ /** Picks cards by id, kind (as the map draws it: "db" picks databases) or box (at any depth), each a value or a list;
240
+ * 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. */
241
+ export interface Selector { id?: string | string[]; kind?: string | string[]; container?: string | string[]; not?: Selector }
242
+ /** What the model says must hold (`model.rules`): no line from the cards `from` picks to the cards `to` picks (a side left
243
+ * out picks every card), or, with noCycles, no boxes that reach each other through their lines (a line counts between
244
+ * 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
245
+ * id, in from, to or a not) holds only in the views that draw one of the boxes it names; any other, in every view.
246
+ * `views` narrows that further. */
247
+ export interface Rule { id: string; title?: string; views?: string[]; from?: Selector; to?: Selector; noCycles?: boolean }
248
+ /** A rule broken in a view: its message (as umlimator.py check prints it), and the lines, and for a cycle the boxes, it is
249
+ * about. The map draws those lines rose, with a mark that names the rule. */
250
+ export interface RuleBreak { rule: string; view: string; message: string; targets: Ref[] }
251
+ /** Every rule the model breaks, view by view. No browser needed. */
252
+ export function rules(model: MapModel): RuleBreak[];
253
+ /** Every card and line on a path from one card to another along the view's lines, as Refs ([] when none joins them). */
254
+ export function paths(model: MapModel, viewId: string, from: string, to: string): Ref[];
255
+ /** What a card's lines lead to, or (backward) what leads to it, as Refs with their distance in lines; the card first, at 0. */
256
+ export function reach(model: MapModel, viewId: string, from: string, options?: { backward?: boolean; depth?: number }): (Ref & { distance: number })[];
257
+ /** A view in plain sentences: cards and kinds, boxes, what each card's lines do, cards with no line. Deterministic. */
258
+ export function describe(model: MapModel, viewId: string): string;
259
+ /** The spans of an OpenTelemetry trace in OTLP JSON (resourceSpans), as replay() takes them. */
260
+ export function otlpSpans(otlp: unknown): Span[];
261
+ /** What changed from one model to the next (#66), as one model to draw: `after`, with each card marked added, changed or
262
+ * removed (`change`: a NEW or CHANGED tab; REMOVED is faded, dashed and struck through) and each line likewise (`style`
263
+ * added or removed; a changed one `edited`); what left comes back in its views, boxes and places, and a card put into
264
+ * or taken out of a view is marked in that view only (`nodeOverrides`). Cards and lines match by id (a line without one
265
+ * by its ends, as a Ref names it). `summary`: what changed in words, in model order ("adds Payments queue between
266
+ * Trip service and Payment provider"); `views`: those that show a change. The marks are the model's own fields, so a
267
+ * page or still the CLI makes shows them. Neither input is changed. */
268
+ export interface DiffResult { model: MapModel; summary: string[]; views: string[] }
269
+ export function diff(before: MapModel | null, after: MapModel | null): DiffResult;
270
+ /** A call between two services as traffic showed it: { from, to, count }, or a Jaeger or Tempo dependency. */
271
+ export type Observed = { from: string; to: string; count?: number } | { parent: string; child: string; callCount?: number };
272
+ /** The map against the traffic seen (#68). A line that ran but is not drawn comes in (`drift: 'undrawn'`: rose, dashed,
273
+ * labelled "not drawn" with its calls); a drawn line between two cards traffic reached, that carried none, fades
274
+ * (`drift: 'idle'`, style `faded`); other lines are left as they are. A service is the card `match` names, else the one
275
+ * with its id, else with its label (case aside). `counts`: lines that carried traffic, came in, are idle, or could not
276
+ * be judged (an end traffic never reached), and the services with no card; `views`: those that show drift. The counts
277
+ * are of model edges, not drawn lines: a line that stands for several edges (A→B and B→A) fades only when every one of
278
+ * them is idle, so a map can show fewer faded lines than `idle` counts. */
279
+ export interface DriftResult { model: MapModel; counts: { carried: number; undrawn: number; idle: number; unknown: number; unmatched: string[] }; views: string[] }
280
+ export function drift(model: MapModel, observed: Observed[] | { data: Observed[] }, options?: { match?: Record<string, string> }): DriftResult;
139
281
  /** The whole standalone viewer (top bar, views list, details panel, search, export) that umlimator.py writes. */
140
282
  export function mountPage(container: HTMLElement, model: MapModel, options?: { params?: URLSearchParams; instant?: boolean }): Handle;