umlimator 0.12.1 → 0.13.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 +149 -16
- package/lib/umlimator-element.d.ts +19 -0
- package/lib/umlimator-element.js +77 -0
- package/lib/umlimator-markdown-it.d.ts +6 -0
- package/lib/umlimator-markdown-it.js +33 -0
- package/lib/umlimator-remark.d.ts +4 -0
- package/lib/umlimator-remark.js +13 -0
- package/lib/umlimator-theme.css +9 -5
- package/lib/umlimator.d.ts +51 -3
- package/lib/umlimator.js +687 -259
- package/lib/umlimator.schema.json +164 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -18,25 +18,40 @@ drill-down, a data pipeline, an AI agent's skills, and host pages for embedding,
|
|
|
18
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
19
|
view's cards inside and the lines to it moved onto them, the rest of the picture in its order.
|
|
20
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,
|
|
22
|
-
that never run through a card, room for every label, containers kept apart, and cards with
|
|
23
|
-
own, under the view or at the bottom of their box. The same model gives the same picture.
|
|
21
|
+
skip columns included; each card level with the cards its lines come from, in its box or outside it, so lines run
|
|
22
|
+
straight; orthogonal lines that never run through a card, room for every label, containers kept apart, and cards with
|
|
23
|
+
no lines in a row of their own, under the view or at the bottom of their box. The same model gives the same picture.
|
|
24
24
|
- **Reading**: animated flow, neighbours lit on selection, numbered steps, tours, minimap, light and dark themes.
|
|
25
25
|
- **Exploring**: every path between two cards, or everything a card reaches, lit on the map; a view in plain sentences
|
|
26
26
|
(`describe()`); a tour along any path the host names.
|
|
27
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()`)
|
|
28
|
+
pulse, slow hops that glow, labels and tabs; and an OpenTelemetry trace replayed along its lines (`replay()`), or kept
|
|
29
|
+
as a numbered view to review in a pull request (`umlimator.py trace`).
|
|
30
|
+
- **Lenses**: saved ways to look at one map, by team or by data flow: what a lens picks lit and the rest dimmed, cards
|
|
31
|
+
ringed by owner with a legend, from the page's Lenses menu or a link. The view is never laid out again.
|
|
32
|
+
- **Budgets**: lines' latency and cost; the slowest or costliest path lit with its total (`critical()`), and what loses
|
|
33
|
+
its way if a card goes down (`whatIf()`).
|
|
29
34
|
- **Stories**: walk-throughs in the model, step by step (view, camera, selection, highlight, tour and a caption), played
|
|
30
35
|
on the map (`play()`) or from the page's Stories menu, recorded from the page, and written as an animated PNG.
|
|
31
36
|
- **Keyboard and screen readers**: cards, then lines, in the Tab order (a card's tabs and chips too, for a host that
|
|
32
37
|
listens for click); arrows between cards; selections announced.
|
|
38
|
+
- **The page's pieces, embedded**: the standalone page's views list, details panel, legend and hint line, each one a
|
|
39
|
+
`render()` option, around the map in the host's own page.
|
|
33
40
|
- **Editing** (opt-in): gestures emit `change` events; the host applies them and calls `update()`.
|
|
34
|
-
- **Export**: any view as a stand-alone SVG or PNG, in the browser or from the CLI
|
|
41
|
+
- **Export**: any view as a stand-alone SVG or PNG, in the browser or from the CLI; the whole map as one PDF, each view
|
|
42
|
+
with its text, then the legend and the rules (`umlimator.py pdf`).
|
|
43
|
+
- **In docs**: `<umlimator-map src="model.json">` in any page, and an `umlimator` fenced block in VitePress, Docusaurus,
|
|
44
|
+
MkDocs or any Markdown that allows HTML.
|
|
35
45
|
- **A layout check**: crossings, shared lines, detours, labels on lines, text that overflows its card.
|
|
36
46
|
- **Rules**: what the model says must hold ("apps never call a database directly", "no cycles between teams' boxes"),
|
|
37
47
|
marked on the map where it breaks; `umlimator.py check` fails on a broken rule, so CI does.
|
|
38
48
|
- **Change**: what a pull request changes, marked on the map and said in words (with a GitHub Action that posts it);
|
|
39
49
|
a time-lapse of a model's history, where cards glide, fade in and fade out; and drift, the map against the traffic seen.
|
|
50
|
+
- **Import**: a Structurizr workspace, a C4-PlantUML or Mermaid diagram, or what `docker compose config` and `kubectl get`
|
|
51
|
+
print as JSON, read into a model (`umlimator.py import`), with what it can't read listed line by line.
|
|
52
|
+
- **An AI assistant on the map**: an MCP server (`umlimator.py mcp`) through which an assistant such as Claude Code reads
|
|
53
|
+
the model (a view in words, paths, what a card reaches, broken rules, problems, a diff) and drives its page in your
|
|
54
|
+
browser (a view, a highlight, a tour, a story).
|
|
40
55
|
|
|
41
56
|
## Install
|
|
42
57
|
|
|
@@ -44,7 +59,7 @@ drill-down, a data pipeline, an AI agent's skills, and host pages for embedding,
|
|
|
44
59
|
npm install umlimator
|
|
45
60
|
```
|
|
46
61
|
|
|
47
|
-
Or from GitHub, at a tag or any commit: `"umlimator": "github:zintaen/umlimator#v0.
|
|
62
|
+
Or from GitHub, at a tag or any commit: `"umlimator": "github:zintaen/umlimator#v0.13.0"`. npm 12 refuses git
|
|
48
63
|
dependencies unless the project opts in, so that also needs `allow-git=root` in the `.npmrc` next to `package.json`.
|
|
49
64
|
|
|
50
65
|
## Embed
|
|
@@ -85,15 +100,76 @@ function MapView({ model, onSelect }) {
|
|
|
85
100
|
rule the model breaks, view by view.
|
|
86
101
|
- `map.overlay(name, layer)`, `highlight(refs)`, `tour(path)` and `replay(spans)` put the host's marks, a tour and a
|
|
87
102
|
trace on the map; `paths()`, `reach()` and `describe()` walk a view and put it in words, in Node too.
|
|
103
|
+
- `map.lens(id)` puts one of the model's lenses on the map. `critical(model, view, { from, to, by })` finds the slowest
|
|
104
|
+
or costliest path, `whatIf(model, view, { remove })` what loses its way when cards go down, and
|
|
105
|
+
`sequenceFromTrace(model, spans, { view, match })` keeps a trace as a numbered view; all three in Node too.
|
|
88
106
|
- `editable: true` turns on editing gestures ([docs/editing.md](docs/editing.md); `examples/edit.html` is a working host).
|
|
89
107
|
- `diff(before, after)` returns `after` with each card and line marked added, changed or removed, what changed in words,
|
|
90
108
|
and the views that show it; `drift(model, observed)` marks the lines traffic ran on that nobody drew, and the drawn ones
|
|
91
109
|
that carried nothing. Both are models to draw, in Node too. `update()` eases into the next model (`examples/timelapse.html`);
|
|
92
110
|
the option `ease: false` draws it at once.
|
|
93
|
-
- `mountPage` is the whole standalone viewer that the CLI writes.
|
|
111
|
+
- `mountPage` is the whole standalone viewer that the CLI writes. `render()` can draw its pieces too, each on its own or
|
|
112
|
+
any mix, inside the map's element; the host keeps its own header:
|
|
113
|
+
|
|
114
|
+
```js
|
|
115
|
+
render(el, model, { nav: true, details: true, legend: true, hints: true });
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
- `nav`: the views list left of the map, opened and shut by its ☰ button or O. Picking a view emits `viewchange`.
|
|
119
|
+
- `details`: the selected card's panel under the map, with its tabs (`[` and `]`). Its Open and Unfold here buttons
|
|
120
|
+
emit `open` and `expand`, as the canvas does, so a host can load a view first.
|
|
121
|
+
- `legend` and `hints`: the legend, and the line saying what the pointer does, over the map.
|
|
122
|
+
|
|
123
|
+
They share the page's code and look, take the `--um-*` colours (the theme adds `--um-mark` and `--um-scrim` for them),
|
|
124
|
+
and follow `update()` and `destroy()`. `examples/embed.html` uses them.
|
|
94
125
|
|
|
95
126
|
The API, its types and the decisions behind it: [docs/module-api.md](docs/module-api.md) and `lib/umlimator.d.ts`.
|
|
96
127
|
|
|
128
|
+
### In a page or a docs site, as an element
|
|
129
|
+
|
|
130
|
+
`<umlimator-map>` (`umlimator/element`) loads a model and draws it, with no script of your own. Its children show until
|
|
131
|
+
the map is drawn, and stay where scripts don't run ([`examples/element.html`](examples/element.html)):
|
|
132
|
+
|
|
133
|
+
```html
|
|
134
|
+
<link rel="stylesheet" href="node_modules/umlimator/lib/umlimator-theme.css">
|
|
135
|
+
<script type="module" src="node_modules/umlimator/lib/umlimator-element.js"></script>
|
|
136
|
+
|
|
137
|
+
<umlimator-map src="bookshop.json" view="api" minimap height="520">
|
|
138
|
+
<img src="stills/api.png" alt="An order, step by step">
|
|
139
|
+
</umlimator-map>
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
- **The model**: `src`, a URL resolved as an `<img>`'s is (a `data:` URL too), or the model in a child
|
|
143
|
+
`<script type="application/json">`.
|
|
144
|
+
- **Attributes**: `view`, `minimap`, `controls="false"`, `height` (a number is pixels; 480 by default, or size it in CSS)
|
|
145
|
+
and `theme` (`light` or `dark`: theme.css's palette for that map, whatever the page's). Changing one draws the map
|
|
146
|
+
again, and removing the element destroys it.
|
|
147
|
+
- **Events**: `umlimator:select`, `umlimator:open` (cancel it to stay) and `umlimator:viewchange`, the map's event as
|
|
148
|
+
`detail`; `load` once it is drawn, `error` when it can't be. `el.map` is the `render()` handle.
|
|
149
|
+
- **Colours**: theme.css, as above, or your own `--um-*` variables. The element fetches nothing but its model.
|
|
150
|
+
- **Dark mode**: without `theme`, the map follows the page, a docs site's dark mode included: VitePress's
|
|
151
|
+
`<html class="dark">` and Docusaurus's `<html data-theme="dark">`.
|
|
152
|
+
|
|
153
|
+
In a docs site, a fenced block becomes the element. It names the model's `src`, with `view` or any other attribute, or
|
|
154
|
+
holds the model in JSON:
|
|
155
|
+
|
|
156
|
+
````markdown
|
|
157
|
+
```umlimator
|
|
158
|
+
src: /maps/bookshop.json
|
|
159
|
+
view: api
|
|
160
|
+
```
|
|
161
|
+
````
|
|
162
|
+
|
|
163
|
+
- **VitePress**: in `.vitepress/config`, `markdown: { config: md => md.use(umlimator) }` (`import umlimator from
|
|
164
|
+
'umlimator/markdown-it'`) and `vue: { template: { compilerOptions: { isCustomElement: t => t === 'umlimator-map' } } }`;
|
|
165
|
+
in `.vitepress/theme/index`, `import 'umlimator/element'` and `import 'umlimator/theme.css'`.
|
|
166
|
+
- **Docusaurus**: `remarkPlugins: [umlimator]` (`import umlimator from 'umlimator/remark'`) in the docs preset's options,
|
|
167
|
+
and a client module (`clientModules: ['./src/umlimator.js']`) that imports `umlimator/element` and
|
|
168
|
+
`umlimator/theme.css`.
|
|
169
|
+
- **MkDocs, or any site that allows HTML**: write the element itself in the Markdown. Copy `lib/umlimator.js`,
|
|
170
|
+
`lib/umlimator-element.js` and `lib/umlimator-theme.css` into `docs/js/`, and load them in `mkdocs.yml`:
|
|
171
|
+
`extra_javascript: [{ path: js/umlimator-element.js, type: module }]` and `extra_css: [js/umlimator-theme.css]`.
|
|
172
|
+
|
|
97
173
|
## Model
|
|
98
174
|
|
|
99
175
|
A model has shared `nodes` and `edges`, and `views` that each show a subset, with optional `containers` (boxes),
|
|
@@ -111,6 +187,7 @@ kinds such as `public`, `lane`, `script`, `hook` and `board` work too). Fields b
|
|
|
111
187
|
| node | `icon` | a logo or line icon from `icons/manifest.json` |
|
|
112
188
|
| node | `noSub` | the name only; the description stays in the details panel |
|
|
113
189
|
| node | `opensView`, `opensCount` | the view the card opens, which may not be loaded yet, and the ⤢ number for it |
|
|
190
|
+
| node | `owner` | who owns the card (a team): rules and lenses pick by it, and a lens can ring cards by owner |
|
|
114
191
|
| node, edge | `edited` | a marker on the card or line |
|
|
115
192
|
| edge | `kind` | what the line means, drawn as its colour: `calls` (default), `feeds`, `triggers`, `state`, `guard` |
|
|
116
193
|
| edge | `weight` | 1, 2 or 3: how much the line stands for, drawn as its width |
|
|
@@ -119,14 +196,16 @@ kinds such as `public`, `lane`, `script`, `hook` and `board` work too). Fields b
|
|
|
119
196
|
| edge | `from`, `to` | may name a container of the view: the line ends on its box |
|
|
120
197
|
| edge | `style: "faded"` | the line in its kind's colour, faint and with no flow (drift's idle lines) |
|
|
121
198
|
| edge | `drift` | what `drift()` found: `undrawn` or `idle` |
|
|
199
|
+
| edge | `latency`, `cost` | budgets, in ms and in any unit: `critical()` sums them along a path (not drawn) |
|
|
122
200
|
| view | `gap`, `rgap` | the space between columns and between rows |
|
|
123
201
|
| view | `keepPairs` | A→B and B→A drawn as two lines |
|
|
124
202
|
| view | `repair: false` | no port repair |
|
|
125
203
|
| view | `subAndChips` | cards 80 px tall that show their sub-line, then their chips |
|
|
126
204
|
| view | `kindsFromLabels` | a line whose edges name no `kind` takes one guessed from its label |
|
|
127
205
|
| 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
|
|
206
|
+
| model | `rules` | what must hold: no line `from` some cards `to` others (picked by id, kind, box or owner), or `noCycles` between boxes |
|
|
129
207
|
| model | `stories` | walk-throughs: steps that each set the view, camera, selection, highlight, tour and caption |
|
|
208
|
+
| model | `lenses` | ways to look at the map: what `pick` picks (by id, kind, box, owner or `line` kind) lit, the rest dimmed; `color: "owner"` rings cards by owner |
|
|
130
209
|
|
|
131
210
|
## Command line
|
|
132
211
|
|
|
@@ -141,15 +220,37 @@ python3 umlimator.py drift model.json traffic.json --out drift.html # drawn vs
|
|
|
141
220
|
python3 umlimator.py timelapse model.json --out history.html # the model's versions in git history, on a slider
|
|
142
221
|
python3 umlimator.py check page.html --expand # also each view with each stacked card unfolded
|
|
143
222
|
python3 umlimator.py record bike-share.html --story ride --out ride.png # bike-share's story as an animated PNG, its tours riding
|
|
223
|
+
python3 umlimator.py import workspace.dsl --out model.json # a diagram you have already, as a model
|
|
224
|
+
docker compose config --format json > compose.json && python3 umlimator.py import compose.json --out model.json
|
|
225
|
+
kubectl get deploy,sts,svc,ing -A -o json > cluster.json && python3 umlimator.py import cluster.json --out model.json
|
|
226
|
+
python3 umlimator.py pdf page.html --out map.pdf # every view in one PDF, with its text, the legend, the rules
|
|
227
|
+
python3 umlimator.py trace model.json trace.json --view ride-end --match match.json --out model.json # an OTLP trace kept as a numbered view
|
|
228
|
+
python3 umlimator.py mcp model.json # an MCP server over stdio, for an AI assistant (below)
|
|
144
229
|
```
|
|
145
230
|
|
|
146
231
|
`check` also reports each rule the model breaks, and then exits 1.
|
|
147
232
|
|
|
148
|
-
|
|
149
|
-
|
|
233
|
+
`import` reads Structurizr DSL (people, systems, containers and components, their relationships, and the workspace's
|
|
234
|
+
views), C4-PlantUML (its element, relationship and boundary macros), Mermaid (`flowchart` or `graph`, and `C4Context`,
|
|
235
|
+
`C4Container` or `C4Component`), and the JSON of `docker compose config` (services, `depends_on`, networks) and of
|
|
236
|
+
`kubectl get` (workloads, Services, Ingresses). It tells the format from the content, then the extension; `--format`
|
|
237
|
+
decides. Groups, boundaries, subgraphs, networks and namespaces become boxes; Structurizr's views stay views, a card
|
|
238
|
+
opening the view below it; `examples/clinic.json` is `examples/clinic.dsl`, imported. What each element becomes, and
|
|
239
|
+
what is left out: [docs/module-api.md § Import](docs/module-api.md#import-90). Whatever the file holds that `import`
|
|
240
|
+
doesn't read is listed on stderr with its line (in JSON, its path).
|
|
241
|
+
|
|
242
|
+
`pdf` prints the map through Chrome's own print, A4 across: a title page, then each view on a page of its own, as a
|
|
243
|
+
vector picture with its `describe()` text, then the legend and each rule, holding or broken in the views printed
|
|
244
|
+
([bike-share's](https://zintaen.github.io/umlimator/examples/bike-share.pdf), as CI builds it). It takes `--views` and
|
|
245
|
+
`--theme`, as `stills` does.
|
|
246
|
+
|
|
247
|
+
The page has breadcrumbs, a details panel per card (with the slowest path from it and what-if), ⌘K search, deep links
|
|
248
|
+
(`#view=<id>&node=<id>`, `#story=<id>&step=<n>` for a story's step, and `#lens=<id>`), stories, lenses, and export. For
|
|
249
|
+
scripts and tests, it keeps its map's handle on `window.umlimatorMap`: the `Handle` that `render()` returns (`setView()`,
|
|
250
|
+
`highlight()`, `tour()`, `play()` ...).
|
|
150
251
|
`stills` skips a view whose inputs have not changed since its last export (`--all` exports every view). Needs Python
|
|
151
|
-
3.9+ (standard library only), Google Chrome or Chromium for `stills`, `check` and `
|
|
152
|
-
and `
|
|
252
|
+
3.9+ (standard library only), Google Chrome or Chromium for `stills`, `check`, `record` and `pdf`, and Node 18+ for
|
|
253
|
+
`diff`, `drift`, `trace` and `mcp`.
|
|
153
254
|
|
|
154
255
|
`record` writes an animated PNG, which browsers and GitHub show as it is. For a GIF or an MP4 (for chat or slides),
|
|
155
256
|
convert it with ffmpeg:
|
|
@@ -170,11 +271,39 @@ jobs:
|
|
|
170
271
|
runs-on: ubuntu-latest
|
|
171
272
|
steps:
|
|
172
273
|
- uses: actions/checkout@v4
|
|
173
|
-
- uses: zintaen/umlimator@v0.
|
|
274
|
+
- uses: zintaen/umlimator@v0.13.0
|
|
174
275
|
with:
|
|
175
276
|
models: "docs/*.json" # optional: where the models are (default: every .json that is a map model)
|
|
176
277
|
```
|
|
177
278
|
|
|
279
|
+
## An AI assistant on the map (MCP)
|
|
280
|
+
|
|
281
|
+
`umlimator.py mcp model.json` is a [Model Context Protocol](https://modelcontextprotocol.io) server over stdio: an AI
|
|
282
|
+
assistant reads the model, and shows you what it finds on the map in your browser. Add it to Claude Code:
|
|
283
|
+
|
|
284
|
+
```bash
|
|
285
|
+
claude mcp add umlimator -- python3 /path/to/umlimator.py mcp /path/to/model.json
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
| Tool | What it does |
|
|
289
|
+
|---|---|
|
|
290
|
+
| `list_views()` | the views (id, title, parent, number of cards) and the stories |
|
|
291
|
+
| `describe_view(view)` | the view in sentences (`describe()`), then each card's id |
|
|
292
|
+
| `paths(view, from, to)` | every card and line on a path from one card to another (`paths()`), as Refs for `highlight` |
|
|
293
|
+
| `reach(view, card, backward?, depth?)` | what a card leads to, or what leads to it, with the distance of each (`reach()`) |
|
|
294
|
+
| `broken_rules()`, `validate()` | each break of the model's `rules` (`rules()`), and its own problems (`validate()`) |
|
|
295
|
+
| `diff(other_model_path)` | what changed from another model file to this one, in words, and the views that show it (`diff()`) |
|
|
296
|
+
| `show_view(view)` | shows a view on the page |
|
|
297
|
+
| `highlight(refs, view?)` | lights cards and lines (their ids, or the Refs `paths` and `reach` give) and dims the rest |
|
|
298
|
+
| `tour(cards, view?)` | a dot rides the lines through these cards |
|
|
299
|
+
| `play_story(id)`, `clear()` | plays one of the model's stories; takes the highlight, the tour and the story off |
|
|
300
|
+
|
|
301
|
+
- The read tools run the module's own functions in Node on the model file, read again at each call, so edits show up.
|
|
302
|
+
- The page tools drive the map's page, which the server serves at `http://127.0.0.1:7391` (`--port N`, or another port
|
|
303
|
+
when that one is taken). Open it in a browser. Each tool says whether a page is connected, and gives the URL when none
|
|
304
|
+
is; a page that connects later shows the latest command, and a page drawn from an older model draws itself again.
|
|
305
|
+
- The server listens on 127.0.0.1 only and fetches nothing, so a logo not in the cache yet keeps its card's sigil.
|
|
306
|
+
|
|
178
307
|
## Map a repo with Claude Code
|
|
179
308
|
|
|
180
309
|
The `map-repo` skill reads a codebase, writes a C4-style model (context, containers, components), renders it to a page
|
|
@@ -203,11 +332,13 @@ python3 -I tools/fetch_icons.py icons "name:lucide/<icon>" # a line icon
|
|
|
203
332
|
python3 umlimator.py pin-icons # records each new logo's sha256
|
|
204
333
|
```
|
|
205
334
|
|
|
335
|
+
With `UMLIMATOR_OFFLINE=1` the CLI fetches nothing: a logo not in the cache keeps its card's sigil (`mcp` sets it).
|
|
336
|
+
|
|
206
337
|
## Development
|
|
207
338
|
|
|
208
339
|
```bash
|
|
209
340
|
node --test # layout() in Node: the bench views' geometry and the layout rules
|
|
210
|
-
python3 tests/embed.py # render() in headless Chrome: events, keyboard, colours, contrast, editing
|
|
341
|
+
python3 tests/embed.py # render() in headless Chrome: events, keyboard, colours, contrast, editing, the element
|
|
211
342
|
python3 bench/bench.py --check-determinism --expect bench/expected.json --max-ms m50=2000
|
|
212
343
|
```
|
|
213
344
|
|
|
@@ -223,9 +354,11 @@ Release with that section as its notes, and publishes the package to npm (truste
|
|
|
223
354
|
CI also publishes the examples to GitHub Pages from `main`.
|
|
224
355
|
|
|
225
356
|
```
|
|
226
|
-
umlimator.py CLI: render, stills, check, record, pin-icons, diff, drift, timelapse
|
|
357
|
+
umlimator.py CLI: render, stills, check, record, pdf, pin-icons, diff, drift, timelapse, import, trace, mcp
|
|
358
|
+
umlimator_import.py import's readers: Structurizr DSL, C4-PlantUML, Mermaid, compose and Kubernetes JSON
|
|
359
|
+
umlimator_mcp.py the MCP server that `umlimator.py mcp` runs
|
|
227
360
|
action.yml the diff Action: what a pull request changes in its maps, posted on it
|
|
228
|
-
lib/ the module (umlimator.js), its types and the
|
|
361
|
+
lib/ the module (umlimator.js), its types, the default theme, <umlimator-map> and the docs plugins
|
|
229
362
|
engine/ the page templates the CLI inlines the module into; the upstream renderer and schema
|
|
230
363
|
docs/ the module's API and the editing design
|
|
231
364
|
tests/, bench/ tests, benchmark models and results
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Types for lib/umlimator-element.js, `umlimator/element` (#92). Its header says what each attribute and event does.
|
|
2
|
+
import type { Events, Handle } from './umlimator.js';
|
|
3
|
+
|
|
4
|
+
/** <umlimator-map src view minimap controls theme height>: the model from `src` (a URL) or a child
|
|
5
|
+
* <script type="application/json">, drawn by render(); its other children show until the map is drawn. */
|
|
6
|
+
export class UmlimatorMap extends HTMLElement {
|
|
7
|
+
/** render()'s handle once the map is drawn (each `load` event), else null. */
|
|
8
|
+
readonly map: Handle | null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
declare global {
|
|
12
|
+
interface HTMLElementTagNameMap { 'umlimator-map': UmlimatorMap }
|
|
13
|
+
/** The map's events, re-dispatched on the element (they bubble); preventDefault() on umlimator:open keeps the view. */
|
|
14
|
+
interface HTMLElementEventMap {
|
|
15
|
+
'umlimator:select': CustomEvent<Events['select']>;
|
|
16
|
+
'umlimator:open': CustomEvent<Events['open']>;
|
|
17
|
+
'umlimator:viewchange': CustomEvent<Events['viewchange']>;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/* <umlimator-map> (#92): a map in any page from HTML alone, as `umlimator/element`.
|
|
2
|
+
|
|
3
|
+
<umlimator-map src="model.json" view="system" minimap height="560">
|
|
4
|
+
<img src="system.png" alt="The system view">
|
|
5
|
+
</umlimator-map>
|
|
6
|
+
|
|
7
|
+
The model comes from `src`, a URL (a data: URL too), or from a child <script type="application/json">, and render()
|
|
8
|
+
draws it in the element's shadow root. The element's other children (a still, say) show until the map is drawn, and stay
|
|
9
|
+
where scripts don't run. Changing src, view, minimap or controls (controls="false" hides them) draws it again, and
|
|
10
|
+
removing the element destroys the map. height: a number is pixels (default 480px; CSS may size it instead). theme:
|
|
11
|
+
"light" or "dark", that map's palette from lib/umlimator-theme.css, whatever the page's; without it the map follows the
|
|
12
|
+
page, a docs site's dark mode too (VitePress's html.dark, Docusaurus's html[data-theme="dark"]). The colours come from
|
|
13
|
+
that file or the host's own --um-* variables; the element fetches nothing but its model.
|
|
14
|
+
Events: the map's select, open and viewchange as umlimator:select, umlimator:open and umlimator:viewchange, the map's
|
|
15
|
+
event as `detail` (preventDefault() on umlimator:open keeps the view); load each time the map is drawn, error when it
|
|
16
|
+
can't be (its children show again). `el.map` is render()'s handle once drawn (toSVG(), highlight(), setView() ...),
|
|
17
|
+
else null. */
|
|
18
|
+
import { render } from './umlimator.js';
|
|
19
|
+
|
|
20
|
+
const Base = globalThis.HTMLElement || class {}; // importable where there is no DOM (a docs site's server render)
|
|
21
|
+
|
|
22
|
+
export class UmlimatorMap extends Base {
|
|
23
|
+
static observedAttributes = ['src', 'view', 'minimap', 'controls', 'height'];
|
|
24
|
+
map = null;
|
|
25
|
+
#drawn = 0; #due = false; #src = null; #model = null;
|
|
26
|
+
|
|
27
|
+
constructor() {
|
|
28
|
+
super();
|
|
29
|
+
this.attachShadow({ mode: 'open' }).innerHTML = '<style>:host { display: block; height: 480px; overflow: hidden; } ::slotted(img) { display: block; max-width: 100%; max-height: 100%; }</style>'
|
|
30
|
+
+ '<slot></slot><div hidden style="height: 100%"></div>';
|
|
31
|
+
}
|
|
32
|
+
connectedCallback() { this.#soon(); }
|
|
33
|
+
disconnectedCallback() { this.#drop(); }
|
|
34
|
+
attributeChangedCallback(name, was, now) {
|
|
35
|
+
if (name === 'height') this.style.height = now == null ? '' : /^\d+(\.\d+)?$/.test(now) ? now + 'px' : now;
|
|
36
|
+
else this.#soon();
|
|
37
|
+
}
|
|
38
|
+
#soon() { // attributes set together draw once
|
|
39
|
+
if (this.#due) return;
|
|
40
|
+
this.#due = true;
|
|
41
|
+
queueMicrotask(() => { this.#due = false; if (this.isConnected) this.#draw(); });
|
|
42
|
+
}
|
|
43
|
+
#drop() {
|
|
44
|
+
this.map?.destroy(); this.map = null;
|
|
45
|
+
const [, slot, box] = this.shadowRoot.children; slot.hidden = false; box.hidden = true;
|
|
46
|
+
}
|
|
47
|
+
#fail(err) { this.#drop(); console.warn('<umlimator-map>:', err); this.dispatchEvent(new Event('error')); } // the children show again
|
|
48
|
+
async #draw() {
|
|
49
|
+
const n = ++this.#drawn, src = this.getAttribute('src'), get = a => this.getAttribute(a); // a later draw wins
|
|
50
|
+
let model = src && src === this.#src ? this.#model : null;
|
|
51
|
+
try {
|
|
52
|
+
if (src && !model) {
|
|
53
|
+
const r = await fetch(src);
|
|
54
|
+
if (!r.ok) throw new Error(`${src}: ${r.status} ${r.statusText}`);
|
|
55
|
+
model = await r.json();
|
|
56
|
+
} else if (!src) {
|
|
57
|
+
const json = this.querySelector(':scope > script[type="application/json"]');
|
|
58
|
+
if (!json) throw new Error('no src, and no <script type="application/json"> child');
|
|
59
|
+
model = JSON.parse(json.textContent);
|
|
60
|
+
}
|
|
61
|
+
} catch (err) { if (n === this.#drawn) this.#fail(err); return; }
|
|
62
|
+
if (n !== this.#drawn || !this.isConnected) return;
|
|
63
|
+
if (src) { this.#src = src; this.#model = model; }
|
|
64
|
+
this.#drop();
|
|
65
|
+
const [, slot, box] = this.shadowRoot.children; slot.hidden = true; box.hidden = false;
|
|
66
|
+
// the map's events, as DOM events on the element; a host that cancels umlimator:open cancels the open
|
|
67
|
+
const on = Object.fromEntries(['select', 'open', 'viewchange'].map(t => [t, e => {
|
|
68
|
+
if (!this.dispatchEvent(new CustomEvent('umlimator:' + t, { detail: e, bubbles: true, composed: true, cancelable: t === 'open' }))) e.preventDefault();
|
|
69
|
+
}]));
|
|
70
|
+
try {
|
|
71
|
+
this.map = render(box, model, { view: get('view') || undefined, minimap: this.hasAttribute('minimap') && get('minimap') !== 'false', controls: get('controls') !== 'false', on });
|
|
72
|
+
} catch (err) { return this.#fail(err); }
|
|
73
|
+
this.dispatchEvent(new Event('load'));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (globalThis.customElements && !customElements.get('umlimator-map')) customElements.define('umlimator-map', UmlimatorMap);
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Types for lib/umlimator-markdown-it.js, `umlimator/markdown-it` (#92).
|
|
2
|
+
/** A markdown-it plugin: each ```umlimator fenced block becomes a <umlimator-map> element. md.use(umlimator) */
|
|
3
|
+
export default function umlimator(md: any): void;
|
|
4
|
+
/** A block's text as the element's attributes (src, view, minimap, controls, theme, height), an inline model as a data:
|
|
5
|
+
* URL in src. Throws on a block the element can't use. */
|
|
6
|
+
export function attrs(text: string): Record<string, string>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/* A markdown-it plugin (VitePress, or any markdown-it), as `umlimator/markdown-it` (#92): a ```umlimator fenced block
|
|
2
|
+
becomes a <umlimator-map> element, which `umlimator/element` draws in the browser. md.use(umlimator)
|
|
3
|
+
|
|
4
|
+
```umlimator
|
|
5
|
+
src: /maps/shop.json
|
|
6
|
+
view: api
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
The block's `key: value` lines are the element's attributes (src, view, minimap, controls, theme, height); after them,
|
|
10
|
+
or instead of src, it may hold a model in JSON. An inline model goes into src as a data: URL, since VitePress compiles
|
|
11
|
+
the page as a Vue template, which drops a <script> child. A block the element can't use fails the build, where it is
|
|
12
|
+
seen. */
|
|
13
|
+
const KEYS = ['src', 'view', 'minimap', 'controls', 'theme', 'height'];
|
|
14
|
+
|
|
15
|
+
/** A block's text as the element's attributes. */
|
|
16
|
+
export function attrs(text) {
|
|
17
|
+
const at = text.search(/^\s*\{/m), out = {};
|
|
18
|
+
for (const line of (at < 0 ? text : text.slice(0, at)).split('\n').filter(l => l.trim())) {
|
|
19
|
+
const m = line.match(/^\s*([\w-]+)\s*:\s*(.*?)\s*$/);
|
|
20
|
+
if (!m || !KEYS.includes(m[1])) throw new Error(`umlimator block: "${line.trim()}" is not one of ${KEYS.map(k => k + ':').join(' ')} or a model in JSON`);
|
|
21
|
+
out[m[1]] = m[2];
|
|
22
|
+
}
|
|
23
|
+
if ((at >= 0) === !!out.src) throw new Error('umlimator block: give either src: (a model\'s URL) or a model in JSON');
|
|
24
|
+
if (at >= 0) out.src = 'data:application/json,' + encodeURIComponent(JSON.stringify(JSON.parse(text.slice(at)))); // minified; a broken model throws
|
|
25
|
+
return out;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const html = text => `<umlimator-map${Object.entries(attrs(text)).map(([k, v]) => ` ${k}="${v.replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<')}"`).join('')}></umlimator-map>\n`;
|
|
29
|
+
|
|
30
|
+
export default function umlimator(md) {
|
|
31
|
+
const fence = md.renderer.rules.fence;
|
|
32
|
+
md.renderer.rules.fence = (tokens, i, ...rest) => tokens[i].info.trim().split(/\s+/)[0] === 'umlimator' ? html(tokens[i].content) : fence(tokens, i, ...rest);
|
|
33
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// Types for lib/umlimator-remark.js, `umlimator/remark` (#92).
|
|
2
|
+
/** A remark plugin: each ```umlimator fenced block becomes a <umlimator-map> element (a node with data.hName, which MDX
|
|
3
|
+
* and remark-rehype draw as it). remarkPlugins: [umlimator] */
|
|
4
|
+
export default function umlimator(): (tree: any) => void;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/* A remark plugin (Docusaurus, MDX, or any remark-rehype pipeline), as `umlimator/remark` (#92): a ```umlimator fenced
|
|
2
|
+
block becomes a <umlimator-map> element, with the attributes umlimator/markdown-it gives it (its header says what a
|
|
3
|
+
block holds). The block's node becomes one that names the element (data.hName, data.hProperties), which MDX and
|
|
4
|
+
remark-rehype both turn into it. remarkPlugins: [umlimator] */
|
|
5
|
+
import { attrs } from './umlimator-markdown-it.js';
|
|
6
|
+
|
|
7
|
+
export default function umlimator() {
|
|
8
|
+
const walk = node => node.children?.forEach((c, i) => {
|
|
9
|
+
if (c.type === 'code' && c.lang === 'umlimator') node.children[i] = { type: 'umlimator', data: { hName: 'umlimator-map', hProperties: attrs(c.value) }, children: [] };
|
|
10
|
+
else walk(c);
|
|
11
|
+
});
|
|
12
|
+
return walk;
|
|
13
|
+
}
|
package/lib/umlimator-theme.css
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
/* umlimator's default palette and fonts: light, and dark when the system asks or <html data-theme="dark"
|
|
1
|
+
/* umlimator's default palette and fonts: light, and dark when the system asks or <html data-theme="dark">; a
|
|
2
|
+
<umlimator-map theme="light|dark"> takes that palette whatever the page's, and one without a theme is dark in a docs
|
|
3
|
+
site's dark mode too (VitePress's <html class="dark">, Docusaurus's <html data-theme="dark">).
|
|
2
4
|
The canvas reads only these --um-* variables (it bakes in no colour and loads no font), so a host may use this file,
|
|
3
5
|
set the variables itself (e.g. --um-bg: var(--surface)), or switch light and dark by changing them on any ancestor
|
|
4
6
|
of the map. Fonts are the host's: --um-sans and --um-mono name families the page already has; nothing is fetched.
|
|
@@ -6,7 +8,7 @@
|
|
|
6
8
|
The tints (--um-<tone>-f) below are for page chrome: the map mixes its own from the tones and --um-panel in effect
|
|
7
9
|
where it is, so setting a tone on any ancestor of the map recolours its tint too. */
|
|
8
10
|
/* Clean flowchart (GitHub Primer-like): light first, dark when the system or the toggle asks. */
|
|
9
|
-
:root {
|
|
11
|
+
:root, umlimator-map[theme="light"] {
|
|
10
12
|
--um-stage-bg: #fbfcfd; --um-logo-tile: #ffffff; --um-logo-tile-dark: #1b1f23; --um-intake-mark: #1b1f23; --um-edited: #bf8700;
|
|
11
13
|
--um-bg: #ffffff; --um-bg-2: #f6f8fa; --um-panel: #ffffff; --um-panel-2: #f6f8fa; --um-border: #d8dee4; --um-border-2: #d1d9e0;
|
|
12
14
|
--um-text: #1f2328; --um-muted: #59636e; --um-dim: #818b98; --um-grid: rgba(31,35,40,.06); --um-mask: #ffffff; --um-arrow: #8c959f;
|
|
@@ -16,6 +18,7 @@
|
|
|
16
18
|
--um-k-public: #0969da; --um-k-lane: #0f766e; --um-k-ext: #8250df; --um-k-verb: #bf3989; --um-k-script: #6e7781; --um-k-hook: #cf222e; --um-k-board: #9a6700;
|
|
17
19
|
--um-w-calls: #424a53; --um-w-feeds: #0969da; --um-w-triggers: #8250df; --um-w-state: #868f99; --um-w-guard: #cf222e;
|
|
18
20
|
--um-add-bg: rgba(26,127,55,.10); --um-add-tx: #1a7f37; --um-del-bg: rgba(207,34,46,.08); --um-del-tx: #cf222e;
|
|
21
|
+
--um-mark: #fff1a6; --um-scrim: rgba(15,18,22,.72); /* the details panel's ==highlighter==, and the dark behind an example shown full size */
|
|
19
22
|
color-scheme: light;
|
|
20
23
|
--um-accent-f: color-mix(in srgb, var(--um-accent) 12%, var(--um-panel));
|
|
21
24
|
--um-cyan-f: color-mix(in srgb, var(--um-cyan) 9%, var(--um-panel));
|
|
@@ -46,11 +49,12 @@
|
|
|
46
49
|
--um-cyan: #4493f8; --um-green: #3fb950; --um-violet: #ab7df8; --um-amber: #d29922; --um-rose: #f85149; --um-orange: #db6d28; --um-slate: #9198a1; --um-fuchsia: #db61a2;
|
|
47
50
|
--um-k-public: #4493f8; --um-k-lane: #2dd4bf; --um-k-ext: #ab7df8; --um-k-verb: #f778ba; --um-k-script: #9198a1; --um-k-hook: #f85149; --um-k-board: #d29922;
|
|
48
51
|
--um-w-calls: #b7bdc8; --um-w-feeds: #4493f8; --um-w-triggers: #ab7df8; --um-w-state: #59626e; --um-w-guard: #f85149;
|
|
49
|
-
--um-add-bg: rgba(63,185,80,.13); --um-add-tx: #56d364; --um-del-bg: rgba(248,81,73,.12); --um-del-tx: #ff7b72;
|
|
52
|
+
--um-add-bg: rgba(63,185,80,.13); --um-add-tx: #56d364; --um-del-bg: rgba(248,81,73,.12); --um-del-tx: #ff7b72; --um-mark: rgba(250,204,21,.32);
|
|
50
53
|
color-scheme: dark;
|
|
51
54
|
}
|
|
52
55
|
}
|
|
53
|
-
|
|
56
|
+
/* the docs sites' dark mode: :where() weighs nothing, so a host's own --um-* on the element still win */
|
|
57
|
+
:root[data-theme="dark"], umlimator-map[theme="dark"], :where(html.dark, html[data-theme="dark"]) :where(umlimator-map:not([theme])) {
|
|
54
58
|
--um-stage-bg: var(--um-bg-2); --um-edited: #d29922;
|
|
55
59
|
--um-bg: #0d1117; --um-bg-2: #010409; --um-panel: #0d1117; --um-panel-2: #151b23; --um-border: #262c36; --um-border-2: #3d444d;
|
|
56
60
|
--um-text: #f0f6fc; --um-muted: #9198a1; --um-dim: #656c76; --um-grid: rgba(240,246,252,.05); --um-mask: #0d1117; --um-arrow: #656c76;
|
|
@@ -59,6 +63,6 @@
|
|
|
59
63
|
--um-cyan: #4493f8; --um-green: #3fb950; --um-violet: #ab7df8; --um-amber: #d29922; --um-rose: #f85149; --um-orange: #db6d28; --um-slate: #9198a1; --um-fuchsia: #db61a2;
|
|
60
64
|
--um-k-public: #4493f8; --um-k-lane: #2dd4bf; --um-k-ext: #ab7df8; --um-k-verb: #f778ba; --um-k-script: #9198a1; --um-k-hook: #f85149; --um-k-board: #d29922;
|
|
61
65
|
--um-w-calls: #b7bdc8; --um-w-feeds: #4493f8; --um-w-triggers: #ab7df8; --um-w-state: #59626e; --um-w-guard: #f85149;
|
|
62
|
-
--um-add-bg: rgba(63,185,80,.13); --um-add-tx: #56d364; --um-del-bg: rgba(248,81,73,.12); --um-del-tx: #ff7b72;
|
|
66
|
+
--um-add-bg: rgba(63,185,80,.13); --um-add-tx: #56d364; --um-del-bg: rgba(248,81,73,.12); --um-del-tx: #ff7b72; --um-mark: rgba(250,204,21,.32);
|
|
63
67
|
color-scheme: dark;
|
|
64
68
|
}
|
package/lib/umlimator.d.ts
CHANGED
|
@@ -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 {
|
|
@@ -115,6 +123,20 @@ export interface RenderOptions {
|
|
|
115
123
|
/** update() eases into the next model: moved cards glide, new ones fade in, removed ones fade out (#67). Default true;
|
|
116
124
|
* never under prefers-reduced-motion. false draws the next model at once. */
|
|
117
125
|
ease?: boolean;
|
|
126
|
+
/** The standalone page's pieces (#99), each on its own or any mix, all off by default. They are drawn inside the map's
|
|
127
|
+
* element, around the canvas, with the page's look, keys and --um-* colours; update() and destroy() keep them in step.
|
|
128
|
+
* nav: the views list (Miller columns) left of the map, opened and shut by its ☰ button or O; picking a view shows it
|
|
129
|
+
* (viewchange). It comes after the map in the tab order, so focus given to the map's element still lands on the map. */
|
|
130
|
+
nav?: boolean;
|
|
131
|
+
/** The kinds of cards and lines the view draws, in a box over the map, bottom left, that folds away. */
|
|
132
|
+
legend?: boolean;
|
|
133
|
+
/** A line over the map's foot: what the pointer does, and for a while what just happened (a polite live region). */
|
|
134
|
+
hints?: boolean;
|
|
135
|
+
/** The selected card's details under the map: its kind, actions and tabs (when, what, how, an example, what it comes
|
|
136
|
+
* from and leads to), [ and ] between tabs. Open and Unfold here emit open and expand as the canvas does; → ← ⇄ light
|
|
137
|
+
* what the card leads to, what leads to it, and every path between two selected cards (> < P); ⏱ ¤ the slowest and the
|
|
138
|
+
* costliest path where the view's lines have latency or cost, and ⊘ what loses its way if the card goes down (#95). */
|
|
139
|
+
details?: boolean;
|
|
118
140
|
on?: { [K in keyof Events]?: (e: Events[K]) => void };
|
|
119
141
|
}
|
|
120
142
|
|
|
@@ -160,6 +182,11 @@ export interface Handle {
|
|
|
160
182
|
/** The 'highlight' layer: these cards and lines lit, the rest dimmed (in place of the selection's dimming), or null to clear.
|
|
161
183
|
* 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
184
|
highlight(refs: Lit[] | null): void;
|
|
185
|
+
/** 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
|
|
186
|
+
* rest, on every view shown, and the view is not laid out again. With color 'owner', each lit card is ringed in its
|
|
187
|
+
* owner's tone, and a legend over the controls names them. It stays across update() and setView(); a model without the
|
|
188
|
+
* lens takes it off. False when the model has no lens `id`. */
|
|
189
|
+
lens(id: string | null): boolean;
|
|
163
190
|
/** 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
191
|
* it reaches flashes, and a pair with no line between them is skipped. Resolves true at the end, false when cut short (a
|
|
165
192
|
* new tour or replay, tour(null), a new picture of the view, destroy()). With reduced motion it resolves at once.
|
|
@@ -239,9 +266,16 @@ export interface CheckIssue { message: string; targets: Ref[] }
|
|
|
239
266
|
* it measures in the DOM (text overflowing its card, titles, edge labels on cards or each other). In Node, text widths
|
|
240
267
|
* are an estimate unless `measure` is given, so the layout, and what is found, can differ from a browser's. */
|
|
241
268
|
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)
|
|
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
|
-
|
|
269
|
+
/** Picks cards by id, kind (as the map draws it: "db" picks databases), box (at any depth) or owner, each a value or a
|
|
270
|
+
* 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
|
|
271
|
+
* and no owner. */
|
|
272
|
+
export interface Selector { id?: string | string[]; kind?: string | string[]; container?: string | string[]; owner?: string | string[]; not?: Selector }
|
|
273
|
+
/** A lens's pick: a Selector, and `line`, line kinds. Without `line` it lights the cards it picks and the lines between two
|
|
274
|
+
* of them; with `line`, the lines of those kinds between two cards the rest of it picks, and their ends. */
|
|
275
|
+
export interface LensSelector extends Selector { line?: WireKind | WireKind[] }
|
|
276
|
+
/** A saved way to look at the map (#94): what `pick` picks lit, the rest dimmed (a pick left out lights everything);
|
|
277
|
+
* color 'owner' rings each lit card in its owner's tone, with a legend. */
|
|
278
|
+
export interface Lens { id: string; title?: string; pick?: LensSelector; color?: 'owner' }
|
|
245
279
|
/** What the model says must hold (`model.rules`): no line from the cards `from` picks to the cards `to` picks (a side left
|
|
246
280
|
* out picks every card), or, with noCycles, no boxes that reach each other through their lines (a line counts between
|
|
247
281
|
* 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 +295,20 @@ export function reach(model: MapModel, viewId: string, from: string, options?: {
|
|
|
261
295
|
export function describe(model: MapModel, viewId: string): string;
|
|
262
296
|
/** The spans of an OpenTelemetry trace in OTLP JSON (resourceSpans), as replay() takes them. */
|
|
263
297
|
export function otlpSpans(otlp: unknown): Span[];
|
|
298
|
+
/** The path along a view's lines with the highest total of `by` (#95): latency (ms, the default) or cost, summed over its
|
|
299
|
+
* lines (a line without one counts 0), from `from` to `to` (either left out: any card), never coming back to a card. Its
|
|
300
|
+
* cards in order, then its lines, as Refs for highlight(); no refs and 0 when no line leads there. No browser needed. */
|
|
301
|
+
export function critical(model: MapModel, viewId: string, options?: { from?: string; to?: string; by?: 'latency' | 'cost' }): { refs: Ref[]; total: number };
|
|
302
|
+
/** What taking cards out of a view would cost (#95), with the model left as it is. unreachable: what loses its way to them
|
|
303
|
+
* (backward, as reach() finds it: for calls, what depends on them), each with its distance from the nearest, the cards
|
|
304
|
+
* themselves at 0. No rule breaks: rules only forbid lines and cycles, which taking cards out never adds. No browser needed. */
|
|
305
|
+
export function whatIf(model: MapModel, viewId: string, options: { remove: string[] }): { unreachable: (Ref & { distance: number })[] };
|
|
306
|
+
/** A trace kept as a numbered view (#96): `model` with a sequence view (render: "sequence") whose steps are the trace's calls
|
|
307
|
+
* between cards (a span and its parent's), in time order, each line labelled with its span's name and its duration under
|
|
308
|
+
* it. A span's service is a card as replay() matches it (match, else id, else label in any case); the caption names the
|
|
309
|
+
* services with no card. The view (id `view`, default "trace") replaces one with that id, else comes last, under the
|
|
310
|
+
* view that shows the most of its cards. The model passed in is not changed. */
|
|
311
|
+
export function sequenceFromTrace(model: MapModel, spans: Span[], options?: { view?: string; match?: Record<string, string>; title?: string }): MapModel;
|
|
264
312
|
/** What changed from one model to the next (#66), as one model to draw: `after`, with each card marked added, changed or
|
|
265
313
|
* removed (`change`: a NEW or CHANGED tab; REMOVED is faded, dashed and struck through) and each line likewise (`style`
|
|
266
314
|
* added or removed; a changed one `edited`); what left comes back in its views, boxes and places, and a card put into
|