@formicoidea/labre-framework-uml 0.42.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.
Files changed (114) hide show
  1. package/LICENSE +384 -0
  2. package/dist/actions.d.ts +430 -0
  3. package/dist/actions.js +724 -0
  4. package/dist/background.d.ts +153 -0
  5. package/dist/background.js +557 -0
  6. package/dist/board-hit.d.ts +137 -0
  7. package/dist/board-hit.js +191 -0
  8. package/dist/commands-manifest.d.ts +19 -0
  9. package/dist/commands-manifest.js +571 -0
  10. package/dist/commands.d.ts +4 -0
  11. package/dist/commands.js +983 -0
  12. package/dist/component.d.ts +521 -0
  13. package/dist/component.js +708 -0
  14. package/dist/consts.d.ts +365 -0
  15. package/dist/consts.js +463 -0
  16. package/dist/descriptor.d.ts +12 -0
  17. package/dist/descriptor.js +10 -0
  18. package/dist/drawio-decode.d.ts +32 -0
  19. package/dist/drawio-decode.js +96 -0
  20. package/dist/drawio-import.d.ts +133 -0
  21. package/dist/drawio-import.js +885 -0
  22. package/dist/edge-morph.d.ts +154 -0
  23. package/dist/edge-morph.js +153 -0
  24. package/dist/edge-styles.d.ts +78 -0
  25. package/dist/edge-styles.js +210 -0
  26. package/dist/effects.d.ts +9 -0
  27. package/dist/effects.js +6 -0
  28. package/dist/element-renderer.d.ts +64 -0
  29. package/dist/element-renderer.js +114 -0
  30. package/dist/element-view.d.ts +234 -0
  31. package/dist/element-view.js +430 -0
  32. package/dist/export.d.ts +61 -0
  33. package/dist/export.js +41 -0
  34. package/dist/filename.d.ts +17 -0
  35. package/dist/filename.js +26 -0
  36. package/dist/frame-tag.d.ts +70 -0
  37. package/dist/frame-tag.js +78 -0
  38. package/dist/grammar.d.ts +695 -0
  39. package/dist/grammar.js +1436 -0
  40. package/dist/import.d.ts +306 -0
  41. package/dist/import.js +1270 -0
  42. package/dist/index.d.ts +47 -0
  43. package/dist/index.js +67 -0
  44. package/dist/interchange.d.ts +125 -0
  45. package/dist/interchange.js +372 -0
  46. package/dist/keywords.d.ts +204 -0
  47. package/dist/keywords.js +361 -0
  48. package/dist/kinds.d.ts +73 -0
  49. package/dist/kinds.js +91 -0
  50. package/dist/legend.d.ts +30 -0
  51. package/dist/legend.js +255 -0
  52. package/dist/model.d.ts +862 -0
  53. package/dist/model.js +1316 -0
  54. package/dist/morph.d.ts +178 -0
  55. package/dist/morph.js +496 -0
  56. package/dist/node/act-glyphs.d.ts +30 -0
  57. package/dist/node/act-glyphs.js +142 -0
  58. package/dist/node/compartment-watcher.d.ts +107 -0
  59. package/dist/node/compartment-watcher.js +163 -0
  60. package/dist/node/fit.d.ts +85 -0
  61. package/dist/node/fit.js +129 -0
  62. package/dist/node/node-renderer.d.ts +49 -0
  63. package/dist/node/node-renderer.js +611 -0
  64. package/dist/node/node-view.d.ts +51 -0
  65. package/dist/node/node-view.js +89 -0
  66. package/dist/node/paint.d.ts +84 -0
  67. package/dist/node/paint.js +143 -0
  68. package/dist/node/sd-glyphs.d.ts +36 -0
  69. package/dist/node/sd-glyphs.js +79 -0
  70. package/dist/node/stm-glyphs.d.ts +22 -0
  71. package/dist/node/stm-glyphs.js +111 -0
  72. package/dist/node/tier-metrics.d.ts +38 -0
  73. package/dist/node/tier-metrics.js +45 -0
  74. package/dist/operators.d.ts +45 -0
  75. package/dist/operators.js +75 -0
  76. package/dist/plantuml-import.d.ts +113 -0
  77. package/dist/plantuml-import.js +1723 -0
  78. package/dist/plantuml.d.ts +94 -0
  79. package/dist/plantuml.js +985 -0
  80. package/dist/presets.d.ts +149 -0
  81. package/dist/presets.js +302 -0
  82. package/dist/profiles.d.ts +2 -0
  83. package/dist/profiles.js +358 -0
  84. package/dist/reading.d.ts +160 -0
  85. package/dist/reading.js +648 -0
  86. package/dist/roles.d.ts +257 -0
  87. package/dist/roles.js +997 -0
  88. package/dist/rules.d.ts +267 -0
  89. package/dist/rules.js +3090 -0
  90. package/dist/templates.d.ts +2 -0
  91. package/dist/templates.js +197 -0
  92. package/dist/toolbar/config.d.ts +228 -0
  93. package/dist/toolbar/config.js +833 -0
  94. package/dist/toolbar/icons.d.ts +440 -0
  95. package/dist/toolbar/icons.js +711 -0
  96. package/dist/toolbar/senior-tool.d.ts +11 -0
  97. package/dist/toolbar/senior-tool.js +21 -0
  98. package/dist/toolbar/uml-menu.d.ts +17 -0
  99. package/dist/toolbar/uml-menu.js +20 -0
  100. package/dist/toolbar/uml-senior-button.d.ts +18 -0
  101. package/dist/toolbar/uml-senior-button.js +22 -0
  102. package/dist/translations.d.ts +27 -0
  103. package/dist/translations.js +117 -0
  104. package/dist/view.d.ts +33 -0
  105. package/dist/view.js +236 -0
  106. package/dist/xmi-import.d.ts +174 -0
  107. package/dist/xmi-import.js +2268 -0
  108. package/dist/xmi.d.ts +61 -0
  109. package/dist/xmi.js +1860 -0
  110. package/dist/xml-reader.d.ts +157 -0
  111. package/dist/xml-reader.js +430 -0
  112. package/dist/xml.d.ts +72 -0
  113. package/dist/xml.js +96 -0
  114. package/package.json +38 -0
@@ -0,0 +1,47 @@
1
+ /**
2
+ * The HEADLESS half of the UML pack: everything a host, an exporter, a rule or
3
+ * a test can read and call without mounting an editor. The lit side — the
4
+ * senior button, its menu, the element views and the renderers — is reached
5
+ * through `./view` and is never re-exported here (`docs/adr/0009` on why the
6
+ * two halves are separate entry points), and the shortcut rows are reached
7
+ * through `./commands-manifest`, whose whole point is importing none of this.
8
+ *
9
+ * Unlike the older packs this index re-exports each module WHOLE rather than
10
+ * naming members one by one. UML 2.5.1 is the largest notation the library
11
+ * carries and its headless surface is the notation itself — the roles, the
12
+ * geometry, the grammar of a property or an operation, the IR and the two
13
+ * writers — so a curated list would have been a second inventory to keep in
14
+ * step with the first, and the thing it usually catches (a member leaking that
15
+ * should have stayed private) is caught here by the module boundary instead:
16
+ * what a module does not export, this file cannot.
17
+ */
18
+ export * from './background.js';
19
+ export * from './board-hit.js';
20
+ export * from './component.js';
21
+ export * from './consts.js';
22
+ export * from './edge-morph.js';
23
+ export * from './edge-styles.js';
24
+ export * from './keywords.js';
25
+ export * from './kinds.js';
26
+ export * from './legend.js';
27
+ export * from './morph.js';
28
+ export * from './operators.js';
29
+ export * from './presets.js';
30
+ export * from './reading.js';
31
+ export * from './roles.js';
32
+ export * from './profiles.js';
33
+ export * from './rules.js';
34
+ export * from './drawio-decode.js';
35
+ export * from './drawio-import.js';
36
+ export * from './export.js';
37
+ export * from './filename.js';
38
+ export * from './grammar.js';
39
+ export * from './interchange.js';
40
+ export * from './model.js';
41
+ export * from './plantuml.js';
42
+ export * from './xmi.js';
43
+ export * from './xmi-import.js';
44
+ export * from './xml-reader.js';
45
+ export { umlCommandIcons, umlCommands } from './commands.js';
46
+ export { umlCommandsManifest } from './commands-manifest.js';
47
+ export { umlTranslationEntries } from './translations.js';
package/dist/index.js ADDED
@@ -0,0 +1,67 @@
1
+ /**
2
+ * The HEADLESS half of the UML pack: everything a host, an exporter, a rule or
3
+ * a test can read and call without mounting an editor. The lit side — the
4
+ * senior button, its menu, the element views and the renderers — is reached
5
+ * through `./view` and is never re-exported here (`docs/adr/0009` on why the
6
+ * two halves are separate entry points), and the shortcut rows are reached
7
+ * through `./commands-manifest`, whose whole point is importing none of this.
8
+ *
9
+ * Unlike the older packs this index re-exports each module WHOLE rather than
10
+ * naming members one by one. UML 2.5.1 is the largest notation the library
11
+ * carries and its headless surface is the notation itself — the roles, the
12
+ * geometry, the grammar of a property or an operation, the IR and the two
13
+ * writers — so a curated list would have been a second inventory to keep in
14
+ * step with the first, and the thing it usually catches (a member leaking that
15
+ * should have stayed private) is caught here by the module boundary instead:
16
+ * what a module does not export, this file cannot.
17
+ */
18
+ /* ── What the notation IS: roles, geometry, the seeds it writes ─────────── */
19
+ export * from './background.js';
20
+ export * from './board-hit.js';
21
+ export * from './component.js';
22
+ export * from './consts.js';
23
+ // The two morph declarations and the line table they read. Headless data: what a
24
+ // classifier and a relationship may BECOME, which kinds are reachable from
25
+ // which, and the patch each is worth. The toolbar module that draws them is
26
+ // `./view.js`; these are what it is parameterized by, and what the unit and
27
+ // integration suites exercise without one.
28
+ export * from './edge-morph.js';
29
+ export * from './edge-styles.js';
30
+ export * from './keywords.js';
31
+ export * from './kinds.js';
32
+ export * from './legend.js';
33
+ export * from './morph.js';
34
+ // The combined fragment's operator table — the picker's DATA, and the words
35
+ // `§17.6.4`'s pentagon tag is offered under. Headless like `kinds.ts` beside it.
36
+ export * from './operators.js';
37
+ export * from './presets.js';
38
+ export * from './reading.js';
39
+ export * from './roles.js';
40
+ /* ── What a finished diagram is HELD TO: the rule pack and its two levels ─ */
41
+ export * from './profiles.js';
42
+ export * from './rules.js';
43
+ /* ── What it can be READ as: the grammar, the IR and the two writers ────── */
44
+ // The draw.io reader and the one impure step in front of it. Two modules and
45
+ // not one, and the split is ADR 0019's: `importDrawio` is pure and synchronous
46
+ // like every other interchange reader (`docs/adr/0012` P3), `decodeDrawio`
47
+ // needs `DecompressionStream` and is the command's to call.
48
+ export * from './drawio-decode.js';
49
+ export * from './drawio-import.js';
50
+ export * from './export.js';
51
+ export * from './filename.js';
52
+ export * from './grammar.js';
53
+ export * from './interchange.js';
54
+ export * from './model.js';
55
+ export * from './plantuml.js';
56
+ export * from './xmi.js';
57
+ // The XMI reader, and the tolerant tokenizer under it. Exported for the reason
58
+ // P3 gives: the parser is a pure function of a string, so a host that is not an
59
+ // editor — labre-mcp, a script, a test — calls it without a surface in sight.
60
+ export * from './xmi-import.js';
61
+ export * from './xml-reader.js';
62
+ /* ── The toolbox, for the host that composes the command registry and the
63
+ translation-key manifest out of the frameworks it installed (see
64
+ `packages/affine/all/src/{commands,translations}.ts`) ──────────────── */
65
+ export { umlCommandIcons, umlCommands } from './commands.js';
66
+ export { umlCommandsManifest } from './commands-manifest.js';
67
+ export { umlTranslationEntries } from './translations.js';
@@ -0,0 +1,125 @@
1
+ import type { InterchangeCapability, InterchangeExportCapability, InterchangeFormat, InterchangeImportCapability } from '@formicoidea/labre-core/blocks/surface';
2
+ import type { GfxPrimitiveElementModel } from '@formicoidea/labre-core/std/gfx';
3
+ import { type UmlModel } from './model.js';
4
+ /**
5
+ * UML's entries in the interchange registry (`docs/adr/0012`, P1).
6
+ *
7
+ * Three formats and five capabilities (`docs/adr/0019`): PlantUML and XMI in
8
+ * both directions, draw.io in only. A row per TRIPLE and never one thing with
9
+ * several spellings — the three make different promises to whoever opens the
10
+ * file, and the registry says so in the only way it can. The asymmetry that is
11
+ * left is deliberate and stated rather than left to be assumed: there is no
12
+ * `uml:drawio:export`, because writing a picture back is a re-render (P2), and
13
+ * a board that came in from draw.io leaves through PlantUML or XMI with its
14
+ * model intact.
15
+ *
16
+ * Everything here is pure. The model builder, both writers and all three
17
+ * readers have never had a `std` in sight, and this file adds no editor to
18
+ * them — the one thing a reader genuinely cannot do, inflating a compressed
19
+ * `.drawio` container, is the COMMAND's and is documented at
20
+ * `runUmlDrawioImport` and in `drawio-decode.ts`. It picks the diagrams
21
+ * out of the elements it is handed, reads each one, and names the file.
22
+ *
23
+ * ## The selection, and what a whole surface means
24
+ *
25
+ * Expressed by the caller through WHICH DIAGRAMS it puts in the element list,
26
+ * exactly as C4 expresses it with boards. A UML diagram frame is one sheet — a
27
+ * class diagram, a use case diagram — and the whole point of drawing four of
28
+ * them side by side is that they are four diagrams (ADR 0017). So every
29
+ * `umlDiagram` in the list becomes a document (PlantUML) or a package (XMI), and
30
+ * a caller that wants one leaves the others out. A headless host that hands over
31
+ * the whole surface gets every diagram, which is the honest whole-surface
32
+ * answer.
33
+ */
34
+ /**
35
+ * PlantUML source. **Semantic** — the file carries a model, not a picture, so it
36
+ * takes the whole preservation contract the day an importer is written.
37
+ *
38
+ * `.puml` first because it is the extension a download is given and the one the
39
+ * PlantUML tooling watches for; `.plantuml` rides behind it, because half the
40
+ * editor plugins in the wild write that instead and a picker that refused them
41
+ * would refuse a valid diagram over a filename.
42
+ *
43
+ * No `mime` on the FORMAT, for the reason `C4_MERMAID_FORMAT` has none: PlantUML
44
+ * has no registered media type, so there is none to declare. What a DOWNLOAD is
45
+ * served as is a different question with an answer — `text/plain`, because an
46
+ * invented type is a file some browsers refuse to save — and it is stated on the
47
+ * export result, where it belongs. The charset rides with it: unlike XML, a
48
+ * PlantUML file has no prolog to declare its own encoding in, and `«interface»`
49
+ * is a keyword that stops meaning anything as mojibake.
50
+ */
51
+ export declare const UML_PLANTUML_EXTENSION = ".puml";
52
+ export declare const UML_PLANTUML_MIME = "text/plain;charset=utf-8";
53
+ export declare const UML_PLANTUML_FORMAT: InterchangeFormat;
54
+ /**
55
+ * XMI 2.5.1. **Semantic**, and the one a modelling tool reads.
56
+ *
57
+ * `.xmi` is what the OMG's own specification calls the file and what Papyrus,
58
+ * MagicDraw and StarUML offer first; `.uml` rides behind it because Eclipse's
59
+ * UML2 writes model files under that name and an author who exported one from
60
+ * there will look for it. `application/xml` rather than `text/xml`: the file is
61
+ * not meant to be read as text by whatever opens it.
62
+ */
63
+ export declare const UML_XMI_EXTENSION = ".xmi";
64
+ export declare const UML_XMI_MIME = "application/xml";
65
+ export declare const UML_XMI_FORMAT: InterchangeFormat;
66
+ /**
67
+ * The diagrams in a list of elements, read into models, in document order.
68
+ *
69
+ * The frames are picked on `type` rather than with `instanceof
70
+ * UmlDiagramElementModel`, which is where this differs from `c4BoardFrom`. The
71
+ * reason is the model builder's own contract: `umlModelFrom` reads STRUCTURAL
72
+ * records so that it is callable with plain objects and no store
73
+ * (`docs/adr/0012` P3), and an `instanceof` in front of it would put back the
74
+ * dependency the purity was for — a capability that only works when a Yjs
75
+ * document minted the elements. `type` is a getter every element model
76
+ * implements and a field every fixture carries, so the same predicate answers
77
+ * for both.
78
+ *
79
+ * Document order matters for the reason it does everywhere in this framework: it
80
+ * is the tie-break attribution breaks on — a centre inside two overlapping
81
+ * frames goes to the first — and the audit's `attribute()` breaks it the same
82
+ * way. Sorting here would make the export disagree with the badge the user can
83
+ * see.
84
+ */
85
+ export declare function umlModelsFrom(elements: readonly GfxPrimitiveElementModel[]): UmlModel[];
86
+ /** `uml:plantuml:export` — UML's first capability. */
87
+ export declare const UML_PLANTUML_EXPORT: InterchangeExportCapability;
88
+ /** `uml:plantuml:import` — a `.puml` from a repository, read as sheets. */
89
+ export declare const UML_PLANTUML_IMPORT: InterchangeImportCapability;
90
+ /** `uml:xmi:export` — the interchange file a modelling tool reads. */
91
+ export declare const UML_XMI_EXPORT: InterchangeExportCapability;
92
+ /** `uml:xmi:import` — the file a modelling tool wrote, read back as sheets. */
93
+ export declare const UML_XMI_IMPORT: InterchangeImportCapability;
94
+ /**
95
+ * draw.io (`mxfile`). **Visual** — the file carries a drawing, not a model.
96
+ *
97
+ * The one tier difference in this pack, and it is the honest one (ADR 0012 P2,
98
+ * `docs/adr/0019`): a `.drawio` cell says `endArrow=block`, not
99
+ * `uml:Generalization`, so every UML fact this reader produces is a guess made
100
+ * from a style string. Good guesses — draw.io's own UML stencils write a small
101
+ * fixed vocabulary — but guesses, and the tier is what tells a user so before
102
+ * the picker closes.
103
+ *
104
+ * `.drawio` first because it is the extension the application writes and the
105
+ * one a picker should offer; `.drawio.xml` behind it for the uncompressed save,
106
+ * and the generic `.xml` last, because plenty of exports land under it and what
107
+ * the file actually IS is decided by the reader.
108
+ *
109
+ * There is no `uml:drawio:export` to pair this with, and there is not meant to
110
+ * be: writing a picture back is a re-render (P2), and a board that came in from
111
+ * draw.io leaves through PlantUML or XMI with its model intact.
112
+ */
113
+ export declare const UML_DRAWIO_EXTENSION = ".drawio";
114
+ export declare const UML_DRAWIO_MIME = "application/xml";
115
+ export declare const UML_DRAWIO_FORMAT: InterchangeFormat;
116
+ /** `uml:drawio:import` — a draw.io drawing, read as UML. */
117
+ export declare const UML_DRAWIO_IMPORT: InterchangeImportCapability;
118
+ /**
119
+ * Everything UML registers, in one list the view extension can hand over.
120
+ *
121
+ * Sorted by capability id, which is the order `interchangeCapabilities` answers
122
+ * in anyway — so a menu built from this list and a menu built from the registry
123
+ * cannot come out in two different orders.
124
+ */
125
+ export declare const UML_INTERCHANGE: readonly InterchangeCapability[];
@@ -0,0 +1,372 @@
1
+ import { interchangeCapabilityId } from '@formicoidea/labre-core/blocks/surface';
2
+ import { importDrawio, UML_DRAWIO_FORMAT_ID } from './drawio-import.js';
3
+ import { exportUmlPlantuml, exportUmlXmi } from './export.js';
4
+ import { umlSafeFilename } from './filename.js';
5
+ import { umlDrawnEdges, umlElementsFromModel } from './import.js';
6
+ import { umlModelFrom } from './model.js';
7
+ import { importPlantuml } from './plantuml-import.js';
8
+ import { importXmi, umlElementsWithForeign } from './xmi-import.js';
9
+ /**
10
+ * UML's entries in the interchange registry (`docs/adr/0012`, P1).
11
+ *
12
+ * Three formats and five capabilities (`docs/adr/0019`): PlantUML and XMI in
13
+ * both directions, draw.io in only. A row per TRIPLE and never one thing with
14
+ * several spellings — the three make different promises to whoever opens the
15
+ * file, and the registry says so in the only way it can. The asymmetry that is
16
+ * left is deliberate and stated rather than left to be assumed: there is no
17
+ * `uml:drawio:export`, because writing a picture back is a re-render (P2), and
18
+ * a board that came in from draw.io leaves through PlantUML or XMI with its
19
+ * model intact.
20
+ *
21
+ * Everything here is pure. The model builder, both writers and all three
22
+ * readers have never had a `std` in sight, and this file adds no editor to
23
+ * them — the one thing a reader genuinely cannot do, inflating a compressed
24
+ * `.drawio` container, is the COMMAND's and is documented at
25
+ * `runUmlDrawioImport` and in `drawio-decode.ts`. It picks the diagrams
26
+ * out of the elements it is handed, reads each one, and names the file.
27
+ *
28
+ * ## The selection, and what a whole surface means
29
+ *
30
+ * Expressed by the caller through WHICH DIAGRAMS it puts in the element list,
31
+ * exactly as C4 expresses it with boards. A UML diagram frame is one sheet — a
32
+ * class diagram, a use case diagram — and the whole point of drawing four of
33
+ * them side by side is that they are four diagrams (ADR 0017). So every
34
+ * `umlDiagram` in the list becomes a document (PlantUML) or a package (XMI), and
35
+ * a caller that wants one leaves the others out. A headless host that hands over
36
+ * the whole surface gets every diagram, which is the honest whole-surface
37
+ * answer.
38
+ */
39
+ /* ── The formats ──────────────────────────────────────────────────────── */
40
+ /**
41
+ * PlantUML source. **Semantic** — the file carries a model, not a picture, so it
42
+ * takes the whole preservation contract the day an importer is written.
43
+ *
44
+ * `.puml` first because it is the extension a download is given and the one the
45
+ * PlantUML tooling watches for; `.plantuml` rides behind it, because half the
46
+ * editor plugins in the wild write that instead and a picker that refused them
47
+ * would refuse a valid diagram over a filename.
48
+ *
49
+ * No `mime` on the FORMAT, for the reason `C4_MERMAID_FORMAT` has none: PlantUML
50
+ * has no registered media type, so there is none to declare. What a DOWNLOAD is
51
+ * served as is a different question with an answer — `text/plain`, because an
52
+ * invented type is a file some browsers refuse to save — and it is stated on the
53
+ * export result, where it belongs. The charset rides with it: unlike XML, a
54
+ * PlantUML file has no prolog to declare its own encoding in, and `«interface»`
55
+ * is a keyword that stops meaning anything as mojibake.
56
+ */
57
+ export const UML_PLANTUML_EXTENSION = '.puml';
58
+ export const UML_PLANTUML_MIME = 'text/plain;charset=utf-8';
59
+ export const UML_PLANTUML_FORMAT = {
60
+ id: 'plantuml',
61
+ tier: 'semantic',
62
+ extensions: [UML_PLANTUML_EXTENSION, '.plantuml'],
63
+ };
64
+ /**
65
+ * XMI 2.5.1. **Semantic**, and the one a modelling tool reads.
66
+ *
67
+ * `.xmi` is what the OMG's own specification calls the file and what Papyrus,
68
+ * MagicDraw and StarUML offer first; `.uml` rides behind it because Eclipse's
69
+ * UML2 writes model files under that name and an author who exported one from
70
+ * there will look for it. `application/xml` rather than `text/xml`: the file is
71
+ * not meant to be read as text by whatever opens it.
72
+ */
73
+ export const UML_XMI_EXTENSION = '.xmi';
74
+ export const UML_XMI_MIME = 'application/xml';
75
+ export const UML_XMI_FORMAT = {
76
+ id: 'xmi',
77
+ tier: 'semantic',
78
+ extensions: [UML_XMI_EXTENSION, '.uml'],
79
+ mime: UML_XMI_MIME,
80
+ };
81
+ /* ── Picking the diagrams ─────────────────────────────────────────────── */
82
+ /**
83
+ * The diagrams in a list of elements, read into models, in document order.
84
+ *
85
+ * The frames are picked on `type` rather than with `instanceof
86
+ * UmlDiagramElementModel`, which is where this differs from `c4BoardFrom`. The
87
+ * reason is the model builder's own contract: `umlModelFrom` reads STRUCTURAL
88
+ * records so that it is callable with plain objects and no store
89
+ * (`docs/adr/0012` P3), and an `instanceof` in front of it would put back the
90
+ * dependency the purity was for — a capability that only works when a Yjs
91
+ * document minted the elements. `type` is a getter every element model
92
+ * implements and a field every fixture carries, so the same predicate answers
93
+ * for both.
94
+ *
95
+ * Document order matters for the reason it does everywhere in this framework: it
96
+ * is the tie-break attribution breaks on — a centre inside two overlapping
97
+ * frames goes to the first — and the audit's `attribute()` breaks it the same
98
+ * way. Sorting here would make the export disagree with the badge the user can
99
+ * see.
100
+ */
101
+ export function umlModelsFrom(elements) {
102
+ const source = elements;
103
+ return source
104
+ .filter(element => element.type === 'umlDiagram')
105
+ .map(diagram => umlModelFrom(diagram, source));
106
+ }
107
+ /* ── The capabilities ─────────────────────────────────────────────────── */
108
+ /**
109
+ * The diagrams as PlantUML source.
110
+ *
111
+ * A thin adapter and nothing else: it reads the models, runs the writer, and
112
+ * names the file. There is no second door — `uml.exportPlantuml` calls THIS, so
113
+ * the command and the registry cannot produce different bytes, different
114
+ * filenames or different warnings: there is nowhere for them to differ.
115
+ *
116
+ * `warnings` is omitted rather than empty when the diagram came out whole, so a
117
+ * caller can ask `if (result.warnings)` and mean it.
118
+ */
119
+ const runUmlPlantumlExport = (elements, context) => {
120
+ const name = umlSafeFilename(context.name);
121
+ const { text, warnings } = exportUmlPlantuml(umlModelsFrom(elements));
122
+ return {
123
+ text,
124
+ filename: `${name}${UML_PLANTUML_EXTENSION}`,
125
+ mime: UML_PLANTUML_MIME,
126
+ ...(warnings.length > 0 ? { warnings } : {}),
127
+ };
128
+ };
129
+ /** `uml:plantuml:export` — UML's first capability. */
130
+ export const UML_PLANTUML_EXPORT = {
131
+ id: interchangeCapabilityId('uml', UML_PLANTUML_FORMAT.id, 'export'),
132
+ framework: 'uml',
133
+ format: UML_PLANTUML_FORMAT,
134
+ direction: 'export',
135
+ run: runUmlPlantumlExport,
136
+ };
137
+ /**
138
+ * How many artefacts one model DREW — what `InterchangeReport.mapped` counts.
139
+ *
140
+ * The frame is deliberately not among them: no file this pack reads has a
141
+ * `umlDiagram` in it, Labre mints one per model, and a count that included it
142
+ * would claim the reader mapped one node more than the source had. Relations
143
+ * are, because a relationship is something the file said and the board now
144
+ * draws.
145
+ *
146
+ * The lines are counted through {@link umlDrawnEdges} rather than as
147
+ * `relations.length`, which is the same list the materializer walks: a behaviour
148
+ * edge may live on the Activity or the StateMachine instead (an XMI `<edge>` is
149
+ * a child of its Activity), and counting one list while drawing two would report
150
+ * an activity sheet as having no arrows on it.
151
+ */
152
+ function umlMappedCount(model) {
153
+ // Every activity and every state machine, not the first of each. `activities`
154
+ // and `stateMachines` are LISTS holding zero or one today (`model.ts` says
155
+ // why: a list makes both writers a loop instead of a branch), and a `[0]`
156
+ // here would be a count that quietly went wrong the day a sheet carried two —
157
+ // the one failure mode a report has, which is being believed.
158
+ const sum = (counts) => counts.reduce((total, count) => total + count, 0);
159
+ return (model.classifiers.length +
160
+ model.packages.length +
161
+ model.actors.length +
162
+ model.useCases.length +
163
+ model.subjects.length +
164
+ model.notes.length +
165
+ model.components.length +
166
+ model.ports.length +
167
+ model.artifacts.length +
168
+ model.nodes.length +
169
+ sum(model.activities.map(activity => activity.nodes.length + activity.partitions.length)) +
170
+ sum(model.stateMachines.map(machine => machine.regions.length +
171
+ machine.states.length +
172
+ machine.finalStates.length +
173
+ machine.pseudostates.length)) +
174
+ umlDrawnEdges(model).length);
175
+ }
176
+ /**
177
+ * PlantUML source as diagrams — the format a `.puml` in a `docs/` folder is in.
178
+ *
179
+ * Two steps: PARSE ({@link importPlantuml}, one model per
180
+ * `@startuml … @enduml`) and MATERIALIZE (the same
181
+ * {@link umlElementsFromModel} the other two readers end on). No third step,
182
+ * because there is nothing foreign to put back: PlantUML is a LINE language, so
183
+ * what this reader cannot map is a line rather than a fragment with an id, and
184
+ * a line is reported rather than carried onto an element that did not produce
185
+ * it.
186
+ *
187
+ * `quarantined` is therefore always zero, and it means what D5 says: nothing was
188
+ * kept-and-deliberately-not-written-back. Everything the parser did not
189
+ * understand is in the notes, and the writer next door emits none of it because
190
+ * it never understood it in the first place.
191
+ */
192
+ const runUmlPlantumlImport = source => {
193
+ const { models, notes } = importPlantuml(source);
194
+ const materialized = umlElementsFromModel(models, {
195
+ formatId: UML_PLANTUML_FORMAT.id,
196
+ });
197
+ const all = [...notes, ...materialized.notes];
198
+ const report = {
199
+ mapped: models.reduce((total, model) => total + umlMappedCount(model), 0),
200
+ carried: all.filter(note => note.kind === 'carried').length,
201
+ quarantined: 0,
202
+ notes: all,
203
+ };
204
+ return { elements: materialized.elements, report };
205
+ };
206
+ /** `uml:plantuml:import` — a `.puml` from a repository, read as sheets. */
207
+ export const UML_PLANTUML_IMPORT = {
208
+ id: interchangeCapabilityId('uml', UML_PLANTUML_FORMAT.id, 'import'),
209
+ framework: 'uml',
210
+ format: UML_PLANTUML_FORMAT,
211
+ direction: 'import',
212
+ run: runUmlPlantumlImport,
213
+ };
214
+ /**
215
+ * The diagrams as one XMI 2.5.1 document.
216
+ *
217
+ * The sanitized name is handed to the writer as well as to the download, so the
218
+ * `uml:Model` inside the file is called what the file is called. That is
219
+ * inherited behaviour rather than a choice made here — `InterchangeExportContext`
220
+ * documents the same consequence for BPMN — and it is the honest one: a board
221
+ * titled `Order/to cash` is named `Order-to cash` in both places rather than
222
+ * differently in each.
223
+ */
224
+ const runUmlXmiExport = (elements, context) => {
225
+ const name = umlSafeFilename(context.name);
226
+ const { text, warnings } = exportUmlXmi(umlModelsFrom(elements), { name });
227
+ return {
228
+ text,
229
+ filename: `${name}${UML_XMI_EXTENSION}`,
230
+ mime: UML_XMI_MIME,
231
+ ...(warnings.length > 0 ? { warnings } : {}),
232
+ };
233
+ };
234
+ /** `uml:xmi:export` — the interchange file a modelling tool reads. */
235
+ export const UML_XMI_EXPORT = {
236
+ id: interchangeCapabilityId('uml', UML_XMI_FORMAT.id, 'export'),
237
+ framework: 'uml',
238
+ format: UML_XMI_FORMAT,
239
+ direction: 'export',
240
+ run: runUmlXmiExport,
241
+ };
242
+ /**
243
+ * An XMI 2.5.1 document as diagrams — the one direction a modelling tool
244
+ * actually writes.
245
+ *
246
+ * Three steps and no fourth: PARSE the file into the phase-1 IR
247
+ * ({@link importXmi}, pure, with its own tokenizer because `DOMParser` is a DOM
248
+ * global and P3 forbids one), MATERIALIZE the IR onto a board (the same
249
+ * {@link umlElementsFromModel} the other two importers use — one layout, one
250
+ * set of seeded sizes, one place to change them), and put the FOREIGN MATTER
251
+ * back on the elements it came off ({@link umlElementsWithForeign}, matching on
252
+ * the source id the materializer already wrote under `interchange.xmi.id`).
253
+ *
254
+ * The counts come from the parser rather than from the notes, because they are
255
+ * what the parser actually did: `mapped` is the artefacts it built, `carried`
256
+ * the attributes it kept on them, `quarantined` the fragments it kept and
257
+ * declared un-re-emittable (D5, and see {@link importXmi}'s header for why
258
+ * everything unmapped in this format lands in that third column rather than the
259
+ * second). The materializer's own notes — D4's `invented-layout`, which every
260
+ * XMI file earns, because the format carries no coordinates — are appended.
261
+ */
262
+ const runUmlXmiImport = (source, context) => {
263
+ const { models, layout, containment, foreign, report } = importXmi(source, context);
264
+ const materialized = umlElementsFromModel(models, {
265
+ formatId: UML_XMI_FORMAT.id,
266
+ // The boxes AND the nesting: an XMI model states containment structurally
267
+ // and this canvas states it geometrically, so the invented layout is what
268
+ // turns `packagedElement` back into a class drawn inside its package
269
+ // (`import.ts`, `UmlLayoutHints.containment`).
270
+ layout: { boxes: layout, containment },
271
+ });
272
+ return {
273
+ elements: umlElementsWithForeign(materialized.elements, foreign, UML_XMI_FORMAT.id),
274
+ report: {
275
+ ...report,
276
+ notes: [...report.notes, ...materialized.notes],
277
+ },
278
+ };
279
+ };
280
+ /** `uml:xmi:import` — the file a modelling tool wrote, read back as sheets. */
281
+ export const UML_XMI_IMPORT = {
282
+ id: interchangeCapabilityId('uml', UML_XMI_FORMAT.id, 'import'),
283
+ framework: 'uml',
284
+ format: UML_XMI_FORMAT,
285
+ direction: 'import',
286
+ run: runUmlXmiImport,
287
+ };
288
+ /* ── The visual tier ──────────────────────────────────────────────────── */
289
+ /**
290
+ * draw.io (`mxfile`). **Visual** — the file carries a drawing, not a model.
291
+ *
292
+ * The one tier difference in this pack, and it is the honest one (ADR 0012 P2,
293
+ * `docs/adr/0019`): a `.drawio` cell says `endArrow=block`, not
294
+ * `uml:Generalization`, so every UML fact this reader produces is a guess made
295
+ * from a style string. Good guesses — draw.io's own UML stencils write a small
296
+ * fixed vocabulary — but guesses, and the tier is what tells a user so before
297
+ * the picker closes.
298
+ *
299
+ * `.drawio` first because it is the extension the application writes and the
300
+ * one a picker should offer; `.drawio.xml` behind it for the uncompressed save,
301
+ * and the generic `.xml` last, because plenty of exports land under it and what
302
+ * the file actually IS is decided by the reader.
303
+ *
304
+ * There is no `uml:drawio:export` to pair this with, and there is not meant to
305
+ * be: writing a picture back is a re-render (P2), and a board that came in from
306
+ * draw.io leaves through PlantUML or XMI with its model intact.
307
+ */
308
+ export const UML_DRAWIO_EXTENSION = '.drawio';
309
+ export const UML_DRAWIO_MIME = 'application/xml';
310
+ export const UML_DRAWIO_FORMAT = {
311
+ id: UML_DRAWIO_FORMAT_ID,
312
+ tier: 'visual',
313
+ extensions: [UML_DRAWIO_EXTENSION, '.drawio.xml', '.xml'],
314
+ mime: UML_DRAWIO_MIME,
315
+ };
316
+ /**
317
+ * A draw.io drawing as a board — decoded XML in, elements out.
318
+ *
319
+ * The compressed-payload split (`docs/adr/0019`) shows up here as a REFUSAL
320
+ * rather than as a decoder: `run` is a pure synchronous function (P3) and
321
+ * inflating a raw-deflate stream is neither, so a payload that still needs
322
+ * `DecompressionStream` comes back as an empty board with one `warning` note
323
+ * saying to decode first. The command does that decoding before it calls this
324
+ * (`importUmlDrawioFile`), and a host calling the capability directly gets a
325
+ * sentence it can act on instead of an empty drawing it cannot explain.
326
+ */
327
+ const runUmlDrawioImport = (source, context) => {
328
+ const { model, notes, layout } = importDrawio(source, { name: context.name });
329
+ const materialized = umlElementsFromModel([model], {
330
+ formatId: UML_DRAWIO_FORMAT.id,
331
+ layout,
332
+ });
333
+ const all = [...notes, ...materialized.notes];
334
+ return {
335
+ elements: materialized.elements,
336
+ report: {
337
+ // What became a drawn, editable artefact — the shapes and the connectors
338
+ // the reader recognised, counted by the same function the two semantic
339
+ // readers use so three formats cannot report the same board differently.
340
+ mapped: umlMappedCount(model),
341
+ carried: all.filter(note => note.kind === 'carried').length,
342
+ // Nothing, ever, and it is a fact about the TIER rather than an omission.
343
+ // Quarantine means "kept in the document and deliberately not re-emitted"
344
+ // (D5), and it only means something for a format Labre writes back. There
345
+ // is no draw.io writer, so there is nothing a fragment could contradict.
346
+ quarantined: 0,
347
+ notes: all,
348
+ },
349
+ };
350
+ };
351
+ /** `uml:drawio:import` — a draw.io drawing, read as UML. */
352
+ export const UML_DRAWIO_IMPORT = {
353
+ id: interchangeCapabilityId('uml', UML_DRAWIO_FORMAT.id, 'import'),
354
+ framework: 'uml',
355
+ format: UML_DRAWIO_FORMAT,
356
+ direction: 'import',
357
+ run: runUmlDrawioImport,
358
+ };
359
+ /**
360
+ * Everything UML registers, in one list the view extension can hand over.
361
+ *
362
+ * Sorted by capability id, which is the order `interchangeCapabilities` answers
363
+ * in anyway — so a menu built from this list and a menu built from the registry
364
+ * cannot come out in two different orders.
365
+ */
366
+ export const UML_INTERCHANGE = [
367
+ UML_DRAWIO_IMPORT,
368
+ UML_PLANTUML_EXPORT,
369
+ UML_PLANTUML_IMPORT,
370
+ UML_XMI_EXPORT,
371
+ UML_XMI_IMPORT,
372
+ ];