umlimator 0.0.0-stage → 0.10.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 zintaen <zintaen@gmail.com>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,159 @@
1
- # Temporary Holding Version
1
+ # umlimator
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [![check](https://github.com/zintaen/umlimator/actions/workflows/check.yml/badge.svg)](https://github.com/zintaen/umlimator/actions/workflows/check.yml)
4
+
5
+ Layered architecture maps you can click through, from one JSON model: an ES module that any page embeds, and a CLI
6
+ that writes a self-contained HTML page and still pictures (SVG, PNG) for documents that cannot run one.
7
+
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
+
10
+ **Examples:** [live on GitHub Pages](https://zintaen.github.io/umlimator/), and [what each one shows](examples/README.md): a C4-style
11
+ drill-down, a data pipeline, an AI agent's skills, and host pages for embedding, live updates and editing.
12
+
13
+ ## What it does
14
+
15
+ - **Layers**: a view per level (system, containers, components, flows). A stacked card opens its inner view, which the
16
+ 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.
20
+ - **Reading**: animated flow, neighbours lit on selection, numbered steps, tours, minimap, light and dark themes.
21
+ - **Keyboard and screen readers**: cards, then lines, in the Tab order (a card's tabs and chips too, for a host that
22
+ listens for click); arrows between cards; selections announced.
23
+ - **Editing** (opt-in): gestures emit `change` events; the host applies them and calls `update()`.
24
+ - **Export**: any view as a stand-alone SVG or PNG, in the browser or from the CLI.
25
+ - **A layout check**: crossings, shared lines, detours, labels on lines, text that overflows its card.
26
+
27
+ ## Install
28
+
29
+ ```bash
30
+ npm install umlimator
31
+ ```
32
+
33
+ Or from GitHub, at a tag or any commit: `"umlimator": "github:zintaen/umlimator#v0.10.0"`. npm 12 refuses git
34
+ dependencies unless the project opts in, so that also needs `allow-git=root` in the `.npmrc` next to `package.json`.
35
+
36
+ ## Embed
37
+
38
+ ```js
39
+ import { render } from 'umlimator'; // one ES module, no dependencies, no build step
40
+ import 'umlimator/theme.css'; // the default --um-* palette and fonts (or set the variables yourself)
41
+
42
+ const map = render(document.getElementById('map'), model, {
43
+ on: { select: e => showDetails(e.target), open: e => { e.preventDefault(); router.push(e.view); } },
44
+ });
45
+ map.update(nextModel); // redraws only what changed; keeps the view, camera and selection
46
+ const svg = map.toSVG({ scale: 2 }); // the current view as a stand-alone file
47
+ const png = await map.toPNG({ view: 'api' }); // any view, without showing it
48
+ map.destroy();
49
+ ```
50
+
51
+ In React, mount once and pass new models to `update`:
52
+
53
+ ```jsx
54
+ function MapView({ model, onSelect }) {
55
+ const el = useRef(null), map = useRef(null), cb = useRef(onSelect);
56
+ cb.current = onSelect;
57
+ useEffect(() => { map.current = render(el.current, model, { on: { select: e => cb.current?.(e.target) } }); return () => map.current.destroy(); }, []);
58
+ useEffect(() => { map.current?.update(model); }, [model]);
59
+ return <div ref={el} style={{ height: 600 }} />;
60
+ }
61
+ ```
62
+
63
+ - `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.
65
+ - Colours are `--um-*` CSS variables, fonts are the host's (`--um-sans`, `--um-mono`), text is measured in that font,
66
+ and nothing is fetched.
67
+ - `layout(model, viewId)` returns a view's geometry without a DOM, in Node too.
68
+ - `editable: true` turns on editing gestures ([docs/editing.md](docs/editing.md); `examples/edit.html` is a working host).
69
+ - `mountPage` is the whole standalone viewer that the CLI writes.
70
+
71
+ The API, its types and the decisions behind it: [docs/module-api.md](docs/module-api.md) and `lib/umlimator.d.ts`.
72
+
73
+ ## Model
74
+
75
+ A model has shared `nodes` and `edges`, and `views` that each show a subset, with optional `containers` (boxes),
76
+ `layout` (`[col, row]` per card), `render: "sequence"` for numbered run-order views and `tour` for a dot that rides a
77
+ path. The full schema is `engine/references/schema.md` (§ Map model); `examples/bookshop.json` uses most of it.
78
+
79
+ Card kinds: `frontend`, `backend`, `ai`, `database`, `queue`, `security`, `cloud`, `external` and `file` (skill-map
80
+ kinds such as `public`, `lane`, `script`, `hook` and `board` work too). Fields beyond the schema:
81
+
82
+ | On | Field | What it does |
83
+ |---|---|---|
84
+ | node | `badges` (`badge`) | tabs on the card, each a string or `{ text, tone }` |
85
+ | node | `icon` | a logo or line icon from `icons/manifest.json` |
86
+ | node | `noSub` | the name only; the description stays in the details panel |
87
+ | node | `opensView`, `opensCount` | the view the card opens, which may not be loaded yet, and the ⤢ number for it |
88
+ | node, edge | `edited` | a marker on the card or line |
89
+ | edge | `kind` | what the line means, drawn as its colour: `calls` (default), `feeds`, `triggers`, `state`, `guard` |
90
+ | edge | `weight` | 1, 2 or 3: how much the line stands for, drawn as its width |
91
+ | edge | `sublabel` | a second, smaller line under the label |
92
+ | edge | `fromSide`, `toSide` | the card sides the line uses |
93
+ | edge | `from`, `to` | may name a container of the view: the line ends on its box |
94
+ | view | `gap`, `rgap` | the space between columns and between rows |
95
+ | view | `keepPairs` | A→B and B→A drawn as two lines |
96
+ | view | `repair: false` | no port repair |
97
+ | view | `subAndChips` | cards 80 px tall that show their sub-line, then their chips |
98
+ | view | `kindsFromLabels` | a line whose edges name no `kind` takes one guessed from its label |
99
+ | container | `badges` | tabs on the box's border |
100
+
101
+ ## Command line
102
+
103
+ ```bash
104
+ python3 umlimator.py render examples/bookshop.json --out page.html # the interactive page, then its layout check
105
+ python3 umlimator.py stills page.html --out stills/ # every view as SVG and PNG
106
+ python3 umlimator.py stills page.html --out stills/ --views overview,api --scale 1.5 --theme light
107
+ python3 umlimator.py check page.html --views 'pic-*' # the layout check on chosen views
108
+ ```
109
+
110
+ The page has breadcrumbs, a details panel per card, ⌘K search, deep links (`#view=<id>&node=<id>`) and export.
111
+ `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`.
113
+
114
+ ## Icons
115
+
116
+ A node's `icon` names an entry in `icons/manifest.json`. Line icons ship in `icons/` and take the card's colour. Brand
117
+ logos do not ship: they are their owners' trademarks. The manifest records each logo's official source and a sha256;
118
+ the first render that needs one downloads it into `~/.cache/umlimator/icons` (or `$UMLIMATOR_ICON_CACHE`) and uses it
119
+ only if the hash matches. A logo is never recoloured; follow each brand's guidelines for its use.
120
+
121
+ ```bash
122
+ python3 -I tools/fetch_icons.py icons "name:slug" # a brand logo: svgl, then devicon, then gilbarbara/logos
123
+ python3 -I tools/fetch_icons.py icons "name=https://..." # a brand's own official file
124
+ python3 -I tools/fetch_icons.py icons "name:lucide/<icon>" # a line icon
125
+ python3 umlimator.py pin-icons # records each new logo's sha256
126
+ ```
127
+
128
+ ## Development
129
+
130
+ ```bash
131
+ node --test # layout() in Node: the bench views' geometry and the layout rules
132
+ python3 tests/embed.py # render() in headless Chrome: events, keyboard, colours, contrast, editing
133
+ python3 bench/bench.py --check-determinism --expect bench/expected.json --max-ms m50=2000
134
+ ```
135
+
136
+ The bench lays out fixed models (10 to 69 cards, and 50 modules with 100 lines) in headless Chrome and reports the
137
+ time per phase, the layout check's issues and a hash of the geometry. `--expect` fails on a view that no longer draws as
138
+ recorded in `bench/expected.json`; a change that moves lines on purpose refreshes it with `--write-expected`. CI runs
139
+ all of the above, and checks that the files copied from upstream (`engine/scripts`, `engine/references`,
140
+ `tools/fetch_icons.py`) are unmodified.
141
+
142
+ To release, bump `package.json`'s version in a PR and add that version's `## X.Y.Z: …` section to
143
+ `docs/module-api.md`. When the PR is merged and the checks pass on `main`, CI tags `vX.Y.Z`, publishes a GitHub
144
+ Release with that section as its notes, and publishes the package to npm (trusted publishing: no token is stored).
145
+ CI also publishes the examples to GitHub Pages from `main`.
146
+
147
+ ```
148
+ umlimator.py CLI: render, stills, check, pin-icons
149
+ 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
151
+ docs/ the module's API and the editing design
152
+ tests/, bench/ tests, benchmark models and results
153
+ icons/ line icons and the logo manifest (sources and hashes only)
154
+ examples/ made-up systems: models, host pages and a Node script (live: zintaen.github.io/umlimator)
155
+ ```
156
+
157
+ ## Licence
158
+
159
+ MIT (`LICENSE`). Third-party notices: `THIRD_PARTY_NOTICES.md`.
@@ -0,0 +1,215 @@
1
+ # Third-party notices
2
+
3
+ umlimator contains, or builds on, the work below. Each entry says what is used and under which licence.
4
+
5
+ 1. super-board `/visual` (MIT): the engine in `lib/` and `engine/`. Its own notices for BuilderIO/skills and archify follow,
6
+ kept as super-board wrote them, with one note added on archify's later licence change.
7
+ 2. caru-ini/stack-diagram (MIT): `tools/fetch_icons.py` and the rules for brand logos.
8
+ 3. Lucide (ISC, some icons MIT from Feather): the line icons in `icons/`.
9
+ 4. Brand logos: not distributed with umlimator (see the last section).
10
+
11
+ ## EricTechPro/super-board, `/visual` skill
12
+
13
+ `lib/umlimator.js` and `engine/assets/template.html` derive from super-board's `skills/visual/assets/template.html`
14
+ at commit `d0b7d5a` (29 Oct 2026), modified by umlimator (the changes are in its git history and `docs/module-api.md`);
15
+ `engine/scripts/visual.py` and `engine/references/` are copied from that commit unmodified. Source:
16
+ https://github.com/EricTechPro/super-board
17
+
18
+ MIT License
19
+
20
+ Copyright (c) 2026 Eric Tech
21
+
22
+ Permission is hereby granted, free of charge, to any person obtaining a copy
23
+ of this software and associated documentation files (the "Software"), to deal
24
+ in the Software without restriction, including without limitation the rights
25
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
26
+ copies of the Software, and to permit persons to whom the Software is
27
+ furnished to do so, subject to the following conditions:
28
+
29
+ The above copyright notice and this permission notice shall be included in all
30
+ copies or substantial portions of the Software.
31
+
32
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
33
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
34
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
35
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
36
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
37
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
38
+ SOFTWARE.
39
+
40
+ super-board's own third-party notice for `/visual`, as it ships it:
41
+
42
+ `/visual` adapts ideas from two MIT-licensed projects. No source file is copied
43
+ whole; the borrowed parts are named below so the attribution is precise.
44
+
45
+ ## BuilderIO/skills (visual-recap, visual-plan)
46
+
47
+ Adapted from BuilderIO/skills (MIT): the per-mode section list, the recap and
48
+ plan quality bar, the grounding and secret-redaction rules, and the
49
+ "Key changes" + file-tree layout. `references/sections.md` carries them. The
50
+ hosted Agent-Native Plans app, MDX blocks, MCP connector, and `npx` tooling are
51
+ not used.
52
+
53
+ Source: https://github.com/BuilderIO/skills
54
+
55
+ MIT License
56
+
57
+ Copyright (c) 2026 Builder.io
58
+
59
+ Permission is hereby granted, free of charge, to any person obtaining a copy
60
+ of this software and associated documentation files (the "Software"), to deal
61
+ in the Software without restriction, including without limitation the rights
62
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
63
+ copies of the Software, and to permit persons to whom the Software is
64
+ furnished to do so, subject to the following conditions:
65
+
66
+ The above copyright notice and this permission notice shall be included in all
67
+ copies or substantial portions of the Software.
68
+
69
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
70
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
71
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
72
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
73
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
74
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
75
+ SOFTWARE.
76
+
77
+ ## tt-a1i/archify
78
+
79
+ Diagram style adapted from Archify (MIT): the semantic node palette (dark and
80
+ light values), the opaque-mask-under-translucent-fill node construction, the
81
+ 40px grid canvas, orthogonal rounded routes with arrowhead markers, and
82
+ per-kind icons (the cloud, shield, and external-link icon paths follow
83
+ Archify's semantic sigils). `assets/template.html` re-implements these as a small inline
84
+ renderer.
85
+
86
+ Viewer UX adapted from Archify's viewer (`viewer/viewer-camera.js`, `semantic-radar.js`,
87
+ `focus.js`, `node-finder.js`, `export.js`): camera zoom and pan with fit and 100% presets
88
+ and keyboard steps, reading depths that hide detail when zoomed out (map / read / full),
89
+ a minimap with a draggable viewport, a node focus panel with upstream and downstream reach
90
+ (Archify's "Semantic Passport"), dimming of unrelated nodes on focus, a node finder, SVG and
91
+ PNG export, and the floating control-pill and toolbar look. These are re-implemented from
92
+ scratch in `assets/template.html`; no Archify source file is copied. The edge router follows
93
+ the approach of Archify's `renderers/shared/route-quality.mjs` (`shortestOrthogonalGridRoute`)
94
+ and `renderers/architecture/routing.mjs`: one port per edge spread along the node side in the
95
+ order of the far ends, a sparse orthogonal visibility grid built from node edges plus clearance
96
+ and earlier routes plus separation, Dijkstra with a bend penalty over (cell, direction) states,
97
+ no collinear reuse of earlier routes, and labels reserved as obstacles for later routes.
98
+ Re-implemented, not copied. Drill-down views,
99
+ breadcrumbs, deep links, and the overlap check are /visual's own. Archify's JSON-IR CLI,
100
+ validators, viewer runtime, and embedded font are not bundled.
101
+
102
+ Source: https://github.com/tt-a1i/archify
103
+
104
+ Note added by umlimator (2 Nov 2026): archify moved to the Apache License 2.0 on 25 Oct 2026 (commit 9c306ee). Its
105
+ NOTICE keeps code and contributions received under MIT through commit 25cf370 under their original terms, and keeps
106
+ the historical MIT notice, reproduced below, in its LICENSE-MIT. archify itself originated from
107
+ Cocoon-AI/architecture-diagram-generator (MIT), which is why Cocoon AI appears in that notice. super-board adapted
108
+ ideas, not files, and umlimator copies none of archify's files.
109
+
110
+ MIT License
111
+
112
+ Copyright (c) 2026 tt-a1i (Archify)
113
+ Copyright (c) 2025 Cocoon AI
114
+
115
+ Permission is hereby granted, free of charge, to any person obtaining a copy
116
+ of this software and associated documentation files (the "Software"), to deal
117
+ in the Software without restriction, including without limitation the rights
118
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
119
+ copies of the Software, and to permit persons to whom the Software is
120
+ furnished to do so, subject to the following conditions:
121
+
122
+ The above copyright notice and this permission notice shall be included in all
123
+ copies or substantial portions of the Software.
124
+
125
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
126
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
127
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
128
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
129
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
130
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
131
+ SOFTWARE.
132
+
133
+ ## caru-ini/stack-diagram
134
+
135
+ `tools/fetch_icons.py` is copied unmodified from caru-ini/stack-diagram (commit `30cbbb7`). The rules umlimator follows
136
+ for brand logos (official marks only, never recoloured, a white tile, a dark tile for a white mark, fetched rather than
137
+ bundled) are stack-diagram's. stack-diagram's bundled fonts (SIL OFL 1.1) are not used. Source:
138
+ https://github.com/caru-ini/stack-diagram
139
+
140
+ MIT License
141
+
142
+ Copyright (c) 2026 caru-ini
143
+
144
+ Permission is hereby granted, free of charge, to any person obtaining a copy
145
+ of this software and associated documentation files (the "Software"), to deal
146
+ in the Software without restriction, including without limitation the rights
147
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
148
+ copies of the Software, and to permit persons to whom the Software is
149
+ furnished to do so, subject to the following conditions:
150
+
151
+ The above copyright notice and this permission notice shall be included in all
152
+ copies or substantial portions of the Software.
153
+
154
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
155
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
156
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
157
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
158
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
159
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
160
+ SOFTWARE.
161
+
162
+ ## Lucide icons
163
+
164
+ The line icons in `icons/` (every manifest entry whose source is `lucide`) are from Lucide, fetched from the
165
+ `lucide-static` package; `tools/fetch_icons.py` sets their stroke colour. Source: https://github.com/lucide-icons/lucide
166
+
167
+ ISC License
168
+
169
+ Copyright (c) 2026 Lucide Icons and Contributors
170
+
171
+ Permission to use, copy, modify, and/or distribute this software for any
172
+ purpose with or without fee is hereby granted, provided that the above
173
+ copyright notice and this permission notice appear in all copies.
174
+
175
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
176
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
177
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
178
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
179
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
180
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
181
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
182
+
183
+ Of the icons umlimator ships, clock, database, link, lock, server, terminal and trash-2 are derived from the Feather
184
+ project, under this licence:
185
+
186
+ The MIT License (MIT)
187
+
188
+ Copyright (c) 2013-present Cole Bemis
189
+
190
+ Permission is hereby granted, free of charge, to any person obtaining a copy
191
+ of this software and associated documentation files (the "Software"), to deal
192
+ in the Software without restriction, including without limitation the rights
193
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
194
+ copies of the Software, and to permit persons to whom the Software is
195
+ furnished to do so, subject to the following conditions:
196
+
197
+ The above copyright notice and this permission notice shall be included in all
198
+ copies or substantial portions of the Software.
199
+
200
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
201
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
202
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
203
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
204
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
205
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
206
+ SOFTWARE.
207
+
208
+ ## Brand logos (not distributed)
209
+
210
+ umlimator does not ship any brand logo. `icons/manifest.json` records, for each one, where its official file is
211
+ published and a sha256; `umlimator.py render` downloads it into the user's cache the first time a model names it.
212
+ The logos are trademarks of their owners and keep their owners' terms: svgl (the project is MIT; the logos belong to
213
+ their owners), devicon (MIT), gilbarbara/logos (CC0), Microsoft's Azure and Entra architecture icons (allowed in
214
+ architecture diagrams, training material and documentation), and files published by the brands themselves. Their use
215
+ in a diagram does not imply endorsement.
@@ -0,0 +1,64 @@
1
+ /* umlimator's default palette and fonts: light, and dark when the system asks or <html data-theme="dark">.
2
+ The canvas reads only these --um-* variables (it bakes in no colour and loads no font), so a host may use this file,
3
+ set the variables itself (e.g. --um-bg: var(--surface)), or switch light and dark by changing them on any ancestor
4
+ of the map. Fonts are the host's: --um-sans and --um-mono name families the page already has; nothing is fetched.
5
+ It meets WCAG AA in both themes (text 4.5:1 on its background, lines 3:1 on the stage): tests/contrast.html.
6
+ The tints (--um-<tone>-f) below are for page chrome: the map mixes its own from the tones and --um-panel in effect
7
+ where it is, so setting a tone on any ancestor of the map recolours its tint too. */
8
+ /* Clean flowchart (GitHub Primer-like): light first, dark when the system or the toggle asks. */
9
+ :root {
10
+ --um-stage-bg: #fbfcfd; --um-logo-tile: #ffffff; --um-logo-tile-dark: #1b1f23; --um-intake-mark: #1b1f23; --um-edited: #bf8700;
11
+ --um-bg: #ffffff; --um-bg-2: #f6f8fa; --um-panel: #ffffff; --um-panel-2: #f6f8fa; --um-border: #d8dee4; --um-border-2: #d1d9e0;
12
+ --um-text: #1f2328; --um-muted: #59636e; --um-dim: #818b98; --um-grid: rgba(31,35,40,.06); --um-mask: #ffffff; --um-arrow: #8c959f;
13
+ --um-shadow: 0 8px 24px rgba(31,35,40,.10); --um-dot: #e6eaef; --um-gridline: transparent; --um-chrome: rgba(255,255,255,.94);
14
+ --um-accent: #0969da; --um-accent-fg: #ffffff; --um-lane: rgba(31,35,40,.025);
15
+ --um-cyan: #0969da; --um-green: #1a7f37; --um-violet: #8250df; --um-amber: #9a6700; --um-rose: #cf222e; --um-orange: #bc4c00; --um-slate: #59636e; --um-fuchsia: #bf3989;
16
+ --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
+ --um-w-calls: #424a53; --um-w-feeds: #0969da; --um-w-triggers: #8250df; --um-w-state: #868f99; --um-w-guard: #cf222e;
18
+ --um-add-bg: rgba(26,127,55,.10); --um-add-tx: #1a7f37; --um-del-bg: rgba(207,34,46,.08); --um-del-tx: #cf222e;
19
+ color-scheme: light;
20
+ --um-accent-f: color-mix(in srgb, var(--um-accent) 12%, var(--um-panel));
21
+ --um-cyan-f: color-mix(in srgb, var(--um-cyan) 9%, var(--um-panel));
22
+ --um-green-f: color-mix(in srgb, var(--um-green) 9%, var(--um-panel));
23
+ --um-violet-f: color-mix(in srgb, var(--um-violet) 9%, var(--um-panel));
24
+ --um-amber-f: color-mix(in srgb, var(--um-amber) 10%, var(--um-panel));
25
+ --um-rose-f: color-mix(in srgb, var(--um-rose) 8%, var(--um-panel));
26
+ --um-orange-f: color-mix(in srgb, var(--um-orange) 9%, var(--um-panel));
27
+ --um-slate-f: color-mix(in srgb, var(--um-slate) 8%, var(--um-panel));
28
+ --um-fuchsia-f: color-mix(in srgb, var(--um-fuchsia) 8%, var(--um-panel));
29
+ --um-k-public-f: color-mix(in srgb, var(--um-k-public) 8%, var(--um-panel));
30
+ --um-k-lane-f: color-mix(in srgb, var(--um-k-lane) 8%, var(--um-panel));
31
+ --um-k-ext-f: color-mix(in srgb, var(--um-k-ext) 7%, var(--um-panel));
32
+ --um-k-verb-f: color-mix(in srgb, var(--um-k-verb) 7%, var(--um-panel));
33
+ --um-k-script-f: color-mix(in srgb, var(--um-k-script) 5%, var(--um-panel));
34
+ --um-k-hook-f: color-mix(in srgb, var(--um-k-hook) 12%, var(--um-panel));
35
+ --um-k-board-f: color-mix(in srgb, var(--um-k-board) 9%, var(--um-panel));
36
+ --um-sans: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif;
37
+ --um-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
38
+ }
39
+ @media (prefers-color-scheme: dark) {
40
+ :root:not([data-theme="light"]) {
41
+ --um-stage-bg: var(--um-bg-2); --um-edited: #d29922;
42
+ --um-bg: #0d1117; --um-bg-2: #010409; --um-panel: #0d1117; --um-panel-2: #151b23; --um-border: #262c36; --um-border-2: #3d444d;
43
+ --um-text: #f0f6fc; --um-muted: #9198a1; --um-dim: #656c76; --um-grid: rgba(240,246,252,.05); --um-mask: #0d1117; --um-arrow: #656c76;
44
+ --um-shadow: 0 10px 30px rgba(1,4,9,.55); --um-dot: #161b22; --um-gridline: transparent; --um-chrome: rgba(13,17,23,.92);
45
+ --um-accent: #4493f8; --um-accent-fg: #ffffff; --um-lane: rgba(240,246,252,.025);
46
+ --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
+ --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
+ --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;
50
+ color-scheme: dark;
51
+ }
52
+ }
53
+ :root[data-theme="dark"] {
54
+ --um-stage-bg: var(--um-bg-2); --um-edited: #d29922;
55
+ --um-bg: #0d1117; --um-bg-2: #010409; --um-panel: #0d1117; --um-panel-2: #151b23; --um-border: #262c36; --um-border-2: #3d444d;
56
+ --um-text: #f0f6fc; --um-muted: #9198a1; --um-dim: #656c76; --um-grid: rgba(240,246,252,.05); --um-mask: #0d1117; --um-arrow: #656c76;
57
+ --um-shadow: 0 10px 30px rgba(1,4,9,.55); --um-dot: #161b22; --um-gridline: transparent; --um-chrome: rgba(13,17,23,.92);
58
+ --um-accent: #4493f8; --um-accent-fg: #ffffff; --um-lane: rgba(240,246,252,.025);
59
+ --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
+ --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
+ --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;
63
+ color-scheme: dark;
64
+ }
@@ -0,0 +1,140 @@
1
+ // Types for lib/umlimator.js (hand-written; the module is plain JavaScript). docs/module-api.md explains the API.
2
+
3
+ /** A map model: engine/references/schema.md § Map model, plus the README's umlimator fields. Never mutated. */
4
+ export interface MapModel {
5
+ title?: string;
6
+ root?: string;
7
+ nodes: MapNode[];
8
+ edges?: MapEdge[];
9
+ views: MapView[];
10
+ icons?: Record<string, { g?: string; src?: string; dark?: boolean }>;
11
+ authors?: Record<string, { name?: string; url?: string; avatar?: string }>;
12
+ [key: string]: unknown;
13
+ }
14
+ /** A badge: its text, and optionally a palette name for its colour (green, amber, rose, accent, violet ... : --um-<tone>). */
15
+ export type Badge = string | { text: string; tone?: string };
16
+ export interface MapNode {
17
+ id: string; label?: string; kind?: string; sublabel?: string; badge?: string; badges?: Badge[]; edited?: boolean; icon?: string; verbs?: string[];
18
+ /** The view this card opens. It may name a view the host has not loaded yet: the card still looks stacked, and
19
+ * double-click, Enter on the selected card and a click on its ⤢ badge emit `open` with that id; the map goes in once
20
+ * the view is in the model (the host loads it, calls update(), then setView(), or calls update() in its open handler). */
21
+ opensView?: string;
22
+ /** The number on the ⤢ badge of a card whose view is not loaded yet (else ⤢ alone). A loaded view's cards are counted. */
23
+ opensCount?: number;
24
+ [key: string]: unknown;
25
+ }
26
+ /** What a line means, drawn as its colour: calls (the default), feeds (hands work on), triggers (suggests, offers, fires),
27
+ * state (reads or writes a file, board or config: a thinner line, a muted label, no flow) and guard (a hook steps in).
28
+ * Colours are --um-w-<kind>. An edge's `style` (emphasis, dashed, security) wins over its kind's colour. */
29
+ export type WireKind = 'calls' | 'feeds' | 'triggers' | 'state' | 'guard';
30
+ /** from / to: a card's id, or a container's id in the view (the line ends on its box's border). */
31
+ export interface MapEdge {
32
+ id?: string; from: string; to: string; label?: string; edited?: boolean;
33
+ /** The line's kind (default calls). A line that stands for several edges (A→B and B→A, parallel edges) takes the first
34
+ * kind they name, in model order. Nothing is guessed from the label unless the view has `kindsFromLabels`. */
35
+ kind?: WireKind;
36
+ /** How much the line stands for, drawn as its width: 1 (the default), 2 or 3 (about 1.8, 3.2 and 4.5 px), the arrowhead and
37
+ * hit line growing with it. The host picks the bucket (say under 10, under 100, 100 or more relations); anything else is 1.
38
+ * A line that stands for several edges takes the largest. */
39
+ weight?: 1 | 2 | 3;
40
+ /** A second, smaller line under the label (a technology, say), placed with it and counted in the room left for labels.
41
+ * Shown only with a label. A line that stands for several edges shows each of their sublabels, joined by " / ". */
42
+ sublabel?: string;
43
+ [key: string]: unknown;
44
+ }
45
+ export interface MapView {
46
+ id: string; parent?: string | null; title?: string; nodeIds: string[];
47
+ edges?: (string | MapEdge)[]; layout?: Record<string, [number, number]>; repair?: boolean;
48
+ /** Cards 80 px tall that show their sub-line and then their chips (`verbs`) on a third line. Default: 64 px, and a card's
49
+ * chips take its sub-line's place. A script card (no kind) shows no sub-line either way. */
50
+ subAndChips?: boolean;
51
+ /** Guess a line's kind from the words of its label ("reads" is state, "triggers" is triggers ...) when its edges name
52
+ * none. Off in render() and layout(), which draw no legend; the standalone page (mountPage) always guesses. */
53
+ kindsFromLabels?: boolean;
54
+ containers?: { id: string; label?: string; nodeIds?: string[]; parent?: string; badges?: Badge[] }[]; // badges: tabs on the box's border
55
+ [key: string]: unknown;
56
+ }
57
+
58
+ /** What a click or selection is about, named by ids from the model. An edge without an id is "<from>-><to>#<n>". */
59
+ export type Ref =
60
+ | { type: 'node'; id: string; view: string }
61
+ | { type: 'edge'; ids: string[]; view: string } // one drawn line can stand for several model edges
62
+ | { type: 'container'; id: string; view: string }
63
+ | { type: 'chip'; node: string; index: number; value: string; view: string }
64
+ | { type: 'badge'; node: string; index: number; value: string; view: string }
65
+ | { type: 'badge'; container: string; index: number; value: string; view: string }; // a container's badge
66
+
67
+ export interface Events {
68
+ click: { target: Ref; event: MouseEvent | KeyboardEvent; preventDefault(): void }; // preventDefault(): the selection stays as it was (no select); Enter on a focused tab or chip is a click (KeyboardEvent)
69
+ select: { target: Ref | null; targets: Ref[] }; // a chip or badge selects its card (a container's badge: its box); targets: every selected thing, the latest last
70
+ open: { target: Ref; view: string; preventDefault(): void }; // view: the one the card opens, which may not be in the model yet
71
+ viewchange: { view: string; from: string };
72
+ change: Change; // only with `editable`
73
+ }
74
+
75
+ /** Where the user dropped or placed something: a point in the view and, on a column view, its column and row. */
76
+ export interface Hint { x: number; y: number; col?: number; row?: number }
77
+ /** An edit the user asked for. Nothing changes until the host applies it and calls update(). */
78
+ export type Change = { id: string; view: string; hint?: Hint } & (
79
+ | { op: 'add'; kind: 'node'; label: string; container?: string }
80
+ | { op: 'add'; kind: 'edge'; from: string; to: string; label?: string }
81
+ | { op: 'rename'; target: Ref; label: string }
82
+ | { op: 'move'; target: Extract<Ref, { type: 'node' }>; to: string | null }
83
+ | { op: 'delete'; target: Ref }
84
+ | { op: 'reorder'; target: Ref; index: number });
85
+
86
+ export interface RenderOptions {
87
+ view?: string; // default: model.root, else the view with no parent
88
+ select?: Ref | null;
89
+ controls?: boolean; // up / zoom / fit / 1:1 / flow buttons (default true)
90
+ minimap?: boolean; // default false
91
+ flow?: boolean; // animated flow along lines (default: on unless prefers-reduced-motion)
92
+ /** Text widths: default the host's font (--um-sans, measured on a canvas); false: a font-independent estimate. */
93
+ measure?: false | ((text: string, px: number) => number);
94
+ /** Edit gestures that emit `change` events: all of them, or only those set true. Off by default. */
95
+ editable?: boolean | { add?: boolean; rename?: boolean; move?: boolean; delete?: boolean; reorder?: boolean };
96
+ /** Cards keep their places across update() (in this handle only, never in the model). Default true. */
97
+ stableLayout?: boolean;
98
+ on?: { [K in keyof Events]?: (e: Events[K]) => void };
99
+ }
100
+
101
+ export interface ExportOptions { view?: string; scale?: number; fontCSS?: string }
102
+
103
+ export interface Handle {
104
+ update(model: MapModel): void;
105
+ setView(id: string): Promise<void> | void;
106
+ select(ref: Ref | null): void;
107
+ fit(): void;
108
+ /** A view (default: the one shown; another is drawn off screen) as a stand-alone SVG file: colours resolved from the
109
+ * host's CSS, drawn at rest (no selection, hover, flow or edit handles), `scale` × its natural size (default 1).
110
+ * `fontCSS`: the host's own @font-face rules (data: URLs) to carry into the file. No network, no headless browser.
111
+ * '' for an unknown or empty view, or a destroyed handle. */
112
+ toSVG(options?: ExportOptions): string;
113
+ /** The same picture as a PNG Blob, drawn on a canvas in the browser (default scale 2). null when there is nothing to draw. */
114
+ toPNG(options?: ExportOptions): Promise<Blob | null>;
115
+ /** Puts keyboard focus on the card or line `ref` names (a chip or badge: its card), by default the selected one, else on
116
+ * the map. The map's element delegates focus too: `host.focus()`, as a dialog does when it gives focus back, lands on
117
+ * the selected card or line. */
118
+ focus(ref?: Ref | null): void;
119
+ on<K extends keyof Events>(type: K, fn: (e: Events[K]) => void): () => void;
120
+ readonly view: string | null;
121
+ readonly selection: Ref | null; // the latest selected
122
+ readonly selected: Ref[]; // every selected thing (Shift / Cmd / Ctrl + click adds cards), the latest last
123
+ destroy(): void;
124
+ }
125
+
126
+ export interface Geometry {
127
+ nodes: { id: string; x: number; y: number; w: number; h: number; [key: string]: unknown }[];
128
+ edges: { from: string; to: string; ids: string[]; pts: [number, number][]; weight: 1 | 2 | 3; wk: WireKind;
129
+ lbl: { x: number; y: number; w: number; h: number; text: string; sub?: string } | null; [key: string]: unknown }[];
130
+ bounds: { x: number; y: number; w: number; h: number };
131
+ [key: string]: unknown;
132
+ }
133
+
134
+ export function render(container: HTMLElement, model: MapModel, options?: RenderOptions): Handle;
135
+ /** Undo for a host: it pushes each model it applies, and hands undo() / redo() results to handle.update(). */
136
+ export interface History { readonly model: MapModel; readonly canUndo: boolean; readonly canRedo: boolean; push(m: MapModel): MapModel; undo(): MapModel; redo(): MapModel }
137
+ 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;
139
+ /** The whole standalone viewer (top bar, views list, details panel, search, export) that umlimator.py writes. */
140
+ export function mountPage(container: HTMLElement, model: MapModel, options?: { params?: URLSearchParams; instant?: boolean }): Handle;