@issuegraph/editor 0.1.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 (143) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +238 -0
  3. package/dist/audit/findings.d.ts +225 -0
  4. package/dist/audit/findings.d.ts.map +1 -0
  5. package/dist/audit/findings.js +348 -0
  6. package/dist/audit/findings.js.map +1 -0
  7. package/dist/audit/styles.d.ts +37 -0
  8. package/dist/audit/styles.d.ts.map +1 -0
  9. package/dist/audit/styles.js +72 -0
  10. package/dist/audit/styles.js.map +1 -0
  11. package/dist/audit/surface.d.ts +155 -0
  12. package/dist/audit/surface.d.ts.map +1 -0
  13. package/dist/audit/surface.js +202 -0
  14. package/dist/audit/surface.js.map +1 -0
  15. package/dist/create/draft.d.ts +119 -0
  16. package/dist/create/draft.d.ts.map +1 -0
  17. package/dist/create/draft.js +114 -0
  18. package/dist/create/draft.js.map +1 -0
  19. package/dist/create/keys.d.ts +225 -0
  20. package/dist/create/keys.d.ts.map +1 -0
  21. package/dist/create/keys.js +228 -0
  22. package/dist/create/keys.js.map +1 -0
  23. package/dist/create/placement.d.ts +69 -0
  24. package/dist/create/placement.d.ts.map +1 -0
  25. package/dist/create/placement.js +76 -0
  26. package/dist/create/placement.js.map +1 -0
  27. package/dist/firstpass/batch.d.ts +190 -0
  28. package/dist/firstpass/batch.d.ts.map +1 -0
  29. package/dist/firstpass/batch.js +143 -0
  30. package/dist/firstpass/batch.js.map +1 -0
  31. package/dist/firstpass/candidates.d.ts +128 -0
  32. package/dist/firstpass/candidates.d.ts.map +1 -0
  33. package/dist/firstpass/candidates.js +39 -0
  34. package/dist/firstpass/candidates.js.map +1 -0
  35. package/dist/firstpass/keys.d.ts +90 -0
  36. package/dist/firstpass/keys.d.ts.map +1 -0
  37. package/dist/firstpass/keys.js +112 -0
  38. package/dist/firstpass/keys.js.map +1 -0
  39. package/dist/firstpass/queue.d.ts +183 -0
  40. package/dist/firstpass/queue.d.ts.map +1 -0
  41. package/dist/firstpass/queue.js +159 -0
  42. package/dist/firstpass/queue.js.map +1 -0
  43. package/dist/firstpass/render.d.ts +100 -0
  44. package/dist/firstpass/render.d.ts.map +1 -0
  45. package/dist/firstpass/render.js +216 -0
  46. package/dist/firstpass/render.js.map +1 -0
  47. package/dist/firstpass/styles.d.ts +36 -0
  48. package/dist/firstpass/styles.d.ts.map +1 -0
  49. package/dist/firstpass/styles.js +148 -0
  50. package/dist/firstpass/styles.js.map +1 -0
  51. package/dist/firstpass/view.d.ts +82 -0
  52. package/dist/firstpass/view.d.ts.map +1 -0
  53. package/dist/firstpass/view.js +60 -0
  54. package/dist/firstpass/view.js.map +1 -0
  55. package/dist/firstpass/words.d.ts +105 -0
  56. package/dist/firstpass/words.d.ts.map +1 -0
  57. package/dist/firstpass/words.js +28 -0
  58. package/dist/firstpass/words.js.map +1 -0
  59. package/dist/index.d.ts +150 -0
  60. package/dist/index.d.ts.map +1 -0
  61. package/dist/index.js +147 -0
  62. package/dist/index.js.map +1 -0
  63. package/dist/overlay/grammar.d.ts +259 -0
  64. package/dist/overlay/grammar.d.ts.map +1 -0
  65. package/dist/overlay/grammar.js +223 -0
  66. package/dist/overlay/grammar.js.map +1 -0
  67. package/dist/overlay/render.d.ts +125 -0
  68. package/dist/overlay/render.d.ts.map +1 -0
  69. package/dist/overlay/render.js +439 -0
  70. package/dist/overlay/render.js.map +1 -0
  71. package/dist/overlay/styles.d.ts +41 -0
  72. package/dist/overlay/styles.d.ts.map +1 -0
  73. package/dist/overlay/styles.js +124 -0
  74. package/dist/overlay/styles.js.map +1 -0
  75. package/dist/picker/render.d.ts +80 -0
  76. package/dist/picker/render.d.ts.map +1 -0
  77. package/dist/picker/render.js +112 -0
  78. package/dist/picker/render.js.map +1 -0
  79. package/dist/picker/styles.d.ts +26 -0
  80. package/dist/picker/styles.d.ts.map +1 -0
  81. package/dist/picker/styles.js +116 -0
  82. package/dist/picker/styles.js.map +1 -0
  83. package/dist/picker/view.d.ts +135 -0
  84. package/dist/picker/view.d.ts.map +1 -0
  85. package/dist/picker/view.js +117 -0
  86. package/dist/picker/view.js.map +1 -0
  87. package/dist/picker/words.d.ts +53 -0
  88. package/dist/picker/words.d.ts.map +1 -0
  89. package/dist/picker/words.js +28 -0
  90. package/dist/picker/words.js.map +1 -0
  91. package/dist/reevaluate/render.d.ts +77 -0
  92. package/dist/reevaluate/render.d.ts.map +1 -0
  93. package/dist/reevaluate/render.js +211 -0
  94. package/dist/reevaluate/render.js.map +1 -0
  95. package/dist/reevaluate/styles.d.ts +24 -0
  96. package/dist/reevaluate/styles.d.ts.map +1 -0
  97. package/dist/reevaluate/styles.js +182 -0
  98. package/dist/reevaluate/styles.js.map +1 -0
  99. package/dist/reevaluate/view.d.ts +102 -0
  100. package/dist/reevaluate/view.d.ts.map +1 -0
  101. package/dist/reevaluate/view.js +111 -0
  102. package/dist/reevaluate/view.js.map +1 -0
  103. package/dist/reevaluate/words.d.ts +54 -0
  104. package/dist/reevaluate/words.d.ts.map +1 -0
  105. package/dist/reevaluate/words.js +29 -0
  106. package/dist/reevaluate/words.js.map +1 -0
  107. package/dist/scale/commands.d.ts +60 -0
  108. package/dist/scale/commands.d.ts.map +1 -0
  109. package/dist/scale/commands.js +49 -0
  110. package/dist/scale/commands.js.map +1 -0
  111. package/dist/scale/ladder.d.ts +141 -0
  112. package/dist/scale/ladder.d.ts.map +1 -0
  113. package/dist/scale/ladder.js +243 -0
  114. package/dist/scale/ladder.js.map +1 -0
  115. package/dist/scale/render.d.ts +85 -0
  116. package/dist/scale/render.d.ts.map +1 -0
  117. package/dist/scale/render.js +194 -0
  118. package/dist/scale/render.js.map +1 -0
  119. package/dist/scale/styles.d.ts +21 -0
  120. package/dist/scale/styles.d.ts.map +1 -0
  121. package/dist/scale/styles.js +105 -0
  122. package/dist/scale/styles.js.map +1 -0
  123. package/dist/workspace/inspector.d.ts +117 -0
  124. package/dist/workspace/inspector.d.ts.map +1 -0
  125. package/dist/workspace/inspector.js +140 -0
  126. package/dist/workspace/inspector.js.map +1 -0
  127. package/dist/workspace/rail.d.ts +122 -0
  128. package/dist/workspace/rail.d.ts.map +1 -0
  129. package/dist/workspace/rail.js +216 -0
  130. package/dist/workspace/rail.js.map +1 -0
  131. package/dist/workspace/render.d.ts +139 -0
  132. package/dist/workspace/render.d.ts.map +1 -0
  133. package/dist/workspace/render.js +366 -0
  134. package/dist/workspace/render.js.map +1 -0
  135. package/dist/workspace/selection.d.ts +92 -0
  136. package/dist/workspace/selection.d.ts.map +1 -0
  137. package/dist/workspace/selection.js +83 -0
  138. package/dist/workspace/selection.js.map +1 -0
  139. package/dist/workspace/styles.d.ts +39 -0
  140. package/dist/workspace/styles.d.ts.map +1 -0
  141. package/dist/workspace/styles.js +313 -0
  142. package/dist/workspace/styles.js.map +1 -0
  143. package/package.json +58 -0
@@ -0,0 +1,439 @@
1
+ /**
2
+ * The overlay grammar, attached to a viewer scene.
3
+ *
4
+ * ## The constraint this module is shaped by
5
+ *
6
+ * Layer 2 does not know where an edge is. `index.ts` records the `together-with`
7
+ * connector as *the one declared crossing* precisely because "only the layer
8
+ * that computes the layout knows where its endpoints are", and that argument
9
+ * binds anything that needs a position — an arrowhead, a chip on a node, a
10
+ * sentence beside a line.
11
+ *
12
+ * So this module draws every treatment that can be stated WITHOUT a position,
13
+ * and declares the rest as marks for whoever owns the layout.
14
+ *
15
+ * ## The line, and it was drawn by evidence rather than by taste
16
+ *
17
+ * **An overlay that REUSES the path's own position can be drawn here. One that
18
+ * needs a NEW position cannot.**
19
+ *
20
+ * A halo is the path again, stroked wider. A marching or dotted ghost is the
21
+ * path again, patterned differently. Neither moves, so neither needs to know
22
+ * where the path goes, and cloning a solved `d` is enough.
23
+ *
24
+ * A conflict's second version is the case that looks identical and is not: it
25
+ * has to sit BESIDE the line, and "beside" is the path's perpendicular — which
26
+ * cannot be recovered from a `d` this layer does not interpret. Four review
27
+ * rounds each found a different way that goes wrong: three strokes where a pair
28
+ * was promised, a companion hidden underneath its twin, a companion that had
29
+ * lost the relationship's own double-line shape, and finally a vertical offset
30
+ * sliding ALONG a `together-with` connector that happens to run vertically —
31
+ * `connectorPath` emits exactly that whenever the two boxes are equal width.
32
+ *
33
+ * Each fix was correct and the next round found the next property, because the
34
+ * missing thing was never a property: it was the geometry. So the companion is
35
+ * no longer drawn here at all. It travels as the `second-version` mark, placed
36
+ * by the layer that computed the layout — the same treatment `node-chip`,
37
+ * `terminal-cross` and `inline-reason` already get, and the same argument
38
+ * `index.ts` makes for the `together-with` connector living in the viewer.
39
+ *
40
+ * What is left genuinely cannot be drawn here: `node-chip` sits on two nodes,
41
+ * `terminal-cross` and `inline-reason` need the line's end, and
42
+ * `second-version` needs its perpendicular. Those travel as
43
+ * {@link EdgeOverlay.marks} for the composer to place, declared rather than
44
+ * half-drawn here.
45
+ *
46
+ * ## How an edge is recognised
47
+ *
48
+ * Viewer edge paths publish no per-edge identity — only the `together-with`
49
+ * connector carries one — so an edge is matched by the accessible name the
50
+ * viewer gives it: `${from} ${treatment.label} ${to}`.
51
+ *
52
+ * The label is read from the viewer's OWN `treatmentFor`, never from a copy, so
53
+ * a renamed relationship moves both sides at once. What stays local is the
54
+ * SHAPE of that sentence, and a shape cannot be imported — so `render.test.ts`
55
+ * pins it with a positive control that attaches to a real rendered scene. If
56
+ * the viewer restyles its label, that control goes red rather than the overlays
57
+ * silently ceasing to attach, which is the failure mode worth buying a test.
58
+ *
59
+ * Publishing an identity on edge paths would be better and is deliberately NOT
60
+ * done here: `keyAt` reads `GROUP_ATTRIBUTE` as POINTER identity, so adding one
61
+ * would change what a click on an edge means. That belongs to the leaf that
62
+ * owns edge selection, not to a grammar.
63
+ *
64
+ * ## Attach to a freshly rendered scene, never to your own output
65
+ *
66
+ * The consequence of matching on the accessible name is that overlaying REWRITES
67
+ * that name — the states are announced there — so an overlaid edge no longer
68
+ * matches. A host renders a new scene on every state change and attaches to
69
+ * that, which is the intended shape and is why this is a boundary rather than a
70
+ * defect.
71
+ *
72
+ * Attaching to an already-overlaid scene is out of contract. It fails SAFELY
73
+ * rather than silently: nothing matches, the scene keeps the overlays it
74
+ * already had, and every edge is reported in {@link AttachResult.unattached}.
75
+ * `render.test.ts` pins that, so the behaviour is a decision rather than an
76
+ * accident. What it must never do is double the announcement, and it does not.
77
+ *
78
+ * ## No timer
79
+ *
80
+ * The marching dash is a CSS animation, which is what §17b asks for. The rule
81
+ * is that nothing changes STATE on its own — no `setTimeout`, no `setInterval`,
82
+ * no self-dismissal. A chip persists until the write settles, and a settled
83
+ * failure persists until the user acts on it.
84
+ */
85
+ import { element, resolveTheme, treatmentFor, } from '@issuegraph/viewer';
86
+ import { HALO_OPACITY, STATE_ATTRIBUTE, overlayFor, overlayLabel, } from "./grammar.js";
87
+ /** The class every mark this module adds carries, so a host can find them. */
88
+ export const OVERLAY_CLASS = 'ig-overlay';
89
+ /** The class the viewer puts on an edge path. Matched, never written. */
90
+ const EDGE_CLASS = 'ig-edge';
91
+ /** The class the viewer puts on a terminal marker. NEVER matched — see below. */
92
+ const TERMINAL_CLASS = 'ig-terminal';
93
+ /**
94
+ * The class the viewer puts on a `together-with` connector.
95
+ *
96
+ * A `together-with` relationship is NOT drawn as an edge path — `graph.ts:657`
97
+ * skips it outright, because it shares a rank rather than ordering anything —
98
+ * so it is drawn as an enclosure plus this connector. Without matching it, one
99
+ * of the five relationships could never be overlaid at all: every state on a
100
+ * `together-with` came back `unattached`.
101
+ */
102
+ const CONNECTOR_CLASS = 'ig-connector';
103
+ /** The attribute the connector publishes its edge identity on. */
104
+ const GROUP_ATTRIBUTE = 'data-ig-group';
105
+ /** The accessible name the viewer gives an edge, reconstructed from its parts. */
106
+ function edgeName(edge) {
107
+ return `${edge.from} ${treatmentFor(edge.kind).label} ${edge.to}`;
108
+ }
109
+ function classesOf(attrs) {
110
+ const value = attrs?.['class'];
111
+ return typeof value === 'string' ? value.split(' ').filter((name) => name !== '') : [];
112
+ }
113
+ function isEdgePath(spec) {
114
+ const names = classesOf(spec.attrs);
115
+ return names.includes(EDGE_CLASS) || names.includes(CONNECTOR_CLASS);
116
+ }
117
+ /**
118
+ * Which overlay this element carries, if any.
119
+ *
120
+ * TWO KEYS, AND IDENTITY IS THE BETTER ONE. Every edge mark publishes
121
+ * `edgeIdentity(field, from, to)` on `data-ig-group`, and that string IS
122
+ * `StoredEdge.id` — the same function derives both — so `ProjectedEdge.id`
123
+ * matches it with no new dependency and no reconstruction.
124
+ *
125
+ * THE NAME IS A FALLBACK THAT NO LONGER HAS A KNOWN CALLER, and it is kept
126
+ * rather than deleted for one reason: this package matches against markup layer
127
+ * 1 owns, and a viewer that has not yet shipped the identity on ordinary edge
128
+ * paths still renders against this reader. Deleting it would turn a version
129
+ * skew into silently unattached overlays instead of a working match.
130
+ */
131
+ function overlayOn(spec, byIdentity, byName) {
132
+ const group = spec.attrs?.[GROUP_ATTRIBUTE];
133
+ if (typeof group === 'string') {
134
+ const overlay = byIdentity.get(group);
135
+ if (overlay !== undefined)
136
+ return { key: group, overlay };
137
+ }
138
+ const label = spec.attrs?.['aria-label'];
139
+ if (typeof label === 'string' && classesOf(spec.attrs).includes(EDGE_CLASS)) {
140
+ const overlay = byName.get(label);
141
+ if (overlay !== undefined)
142
+ return { key: label, overlay };
143
+ }
144
+ return null;
145
+ }
146
+ /**
147
+ * The edge's own path, cloned into an overlay stroke.
148
+ *
149
+ * Every geometric attribute is carried across untouched and every identifying
150
+ * one is dropped: an overlay is decoration, so it must not answer to a name,
151
+ * take focus, or be read out a second time by a screen reader.
152
+ */
153
+ function clonePath(spec, overrides) {
154
+ // `data-ig-group` IS CARRIED, and it is the one identifying attribute that
155
+ // stays — because dropping it would make a clone a DEAD ZONE over its own
156
+ // edge. A dash clone is drawn in FRONT of the stroke, so it is what a pointer
157
+ // lands on; with no identity the viewer's walk climbs past it to the canvas
158
+ // group and reports a click on the edge as a click on nothing, clearing the
159
+ // very selection the reader was making. The halo behind it widens the target
160
+ // for the same edge, which is the same answer either way.
161
+ // It is a POINTER identity and not a focus one — that separation is exactly
162
+ // why the viewer publishes decoration on this attribute — so a clone still
163
+ // takes no tab stop, and `aria-hidden` below still keeps it out of the
164
+ // accessibility tree.
165
+ const { class: _class, role: _role, 'aria-label': _label, ...geometry } = spec.attrs ?? {};
166
+ return {
167
+ tag: spec.tag,
168
+ ns: spec.ns,
169
+ attrs: { ...geometry, 'aria-hidden': 'true', ...overrides },
170
+ };
171
+ }
172
+ /**
173
+ * The marks that can be drawn from the edge's own path, and nothing else.
174
+ *
175
+ * Order matters and is drawn back-to-front: the halo sits BEHIND the edge so it
176
+ * reads as a glow around the line rather than a second line over it, and the
177
+ * dash and the conflict's second version sit in front.
178
+ */
179
+ function overlayMarks(spec, overlay, edge, theme) {
180
+ const behind = [];
181
+ const front = [];
182
+ // EVERY CLONE INHERITS THE TREATMENT, rather than the edge getting it and the
183
+ // clones being re-specified one property at a time. Six of this PR's review
184
+ // findings were one class — a clone that did not inherit the hue, the
185
+ // opacity, the dash, the stroke count or the position of the thing it was
186
+ // cloned from — and each was fixed on its own until the shape of the mistake
187
+ // was visible. Deriving it once is what stops the seventh.
188
+ const inherited = overlay.line?.opacity === null || overlay.line?.opacity === undefined
189
+ ? {}
190
+ : { opacity: overlay.line.opacity };
191
+ if (overlay.halo) {
192
+ behind.push(clonePath(spec, {
193
+ ...inherited,
194
+ class: `${OVERLAY_CLASS} ig-overlay-halo`,
195
+ // From the grammar, never from the stylesheet — see `HALO_OPACITY`.
196
+ opacity: HALO_OPACITY,
197
+ // Widened from the theme's own stroke rather than from a literal, so a
198
+ // host thickening its lines keeps the halo proportionate to them.
199
+ 'stroke-width': theme.metrics['--ig-stroke'] * 3,
200
+ 'stroke-dasharray': null,
201
+ }));
202
+ }
203
+ const line = overlay.line;
204
+ if (line !== null && line.dash !== null) {
205
+ // EVERY dash the table declares, not just the marching one. `invalid` asks
206
+ // for `dotted` and an earlier draft drew only `marching`, so the dotted
207
+ // ghost silently never appeared — the table declared a shape channel that
208
+ // nothing rendered, which is a field that lies rather than a missing case.
209
+ //
210
+ // Drawn on a CLONE so the kind's own dash survives underneath: the pattern
211
+ // is one of the four channels the type identity rests on, and writing
212
+ // `stroke-dasharray` onto the edge itself would spend it.
213
+ front.push(clonePath(spec, {
214
+ ...inherited,
215
+ class: `${OVERLAY_CLASS} ig-overlay-dash ig-overlay-${line.dash}`,
216
+ // THE HUE HAS TO BE STATED. The clone drops `class`, so it is no longer
217
+ // `.ig-edge` and the viewer's `.ig-edge[data-edge=…]` hue rules stop
218
+ // applying to it — a `currentColor` stroke then resolved to the
219
+ // inherited body-text grey and painted OVER the edge, collapsing the
220
+ // hue channel exactly while a write was pending.
221
+ //
222
+ // The state's own hue where it has one (a refusal is red), and the
223
+ // RELATIONSHIP's hue where it does not (a pending write is still a
224
+ // `blocked-by`). Both are read from their owning table — the viewer's
225
+ // `treatmentFor` for the kind — never copied.
226
+ stroke: `var(${line.hueToken ?? treatmentFor(edge.kind).hueToken})`,
227
+ 'stroke-dasharray': null,
228
+ }));
229
+ }
230
+ return { behind, front };
231
+ }
232
+ /**
233
+ * The attributes the overlay writes onto the edge's own path.
234
+ *
235
+ * The name is rebuilt from `base` — the edge's own reconstructed name — rather
236
+ * than read back off the element. Reading the element makes the function
237
+ * accumulate: attaching twice produced `… — writing — writing`, because the
238
+ * second pass took the first pass's output as its starting point. Deriving it
239
+ * from the edge every time makes a second attach a no-op, which is what a
240
+ * caller re-rendering on every state change will do.
241
+ */
242
+ function edgeAttributes(base, overlay) {
243
+ const opacity = overlay.line?.opacity;
244
+ return {
245
+ [STATE_ATTRIBUTE]: overlay.attribute,
246
+ // Re-announced, so a reader who cannot see the halo or the ghost still
247
+ // learns the edge is selected, writing, or refused.
248
+ 'aria-label': overlayLabel(base, overlay),
249
+ // The table is the single source for this number; the stylesheet
250
+ // deliberately carries no opacity rule for a state.
251
+ ...(opacity === null || opacity === undefined ? {} : { opacity }),
252
+ };
253
+ }
254
+ /**
255
+ * Walk a spec tree, overlaying every edge an overlay was supplied for.
256
+ *
257
+ * Rebuilt rather than mutated: an `ElementSpec` is `readonly` throughout, and a
258
+ * scene that changed under a host holding a previous one would defeat exactly
259
+ * the memoisation the viewer canonicalises its state order to enable.
260
+ */
261
+ function overlayTree(spec, context) {
262
+ const children = spec.children ?? [];
263
+ const next = [];
264
+ for (const child of children) {
265
+ if (typeof child === 'string') {
266
+ next.push(child);
267
+ continue;
268
+ }
269
+ // THE TERMINAL IS NEVER TOUCHED, and this is the line that keeps the four
270
+ // redundant channels alive. An overlay adds marks beside the type's own
271
+ // dash, terminal, glyph and hue; it never stands in for one. `failed`'s ✕
272
+ // travels as a MARK for the composer to add beside the terminal, which is
273
+ // why there is no branch here that would draw over it.
274
+ if (classesOf(child.attrs).includes(TERMINAL_CLASS)) {
275
+ next.push(child);
276
+ continue;
277
+ }
278
+ if (!isEdgePath(child)) {
279
+ next.push(overlayTree(child, context));
280
+ continue;
281
+ }
282
+ // AN EDGE THIS MODULE HAS ALREADY OVERLAID IS NOT OVERLAID AGAIN, and this
283
+ // is a DELIBERATE refusal now rather than a side effect of the match key.
284
+ //
285
+ // It used to be one. The overlay rewrites `aria-label`, and the accessible
286
+ // name was the ONLY key an ordinary edge path could be matched by — so a
287
+ // second pass fell through and the scene survived by accident. Edge paths
288
+ // publish `data-ig-group` now, and an identity is not rewritten by
289
+ // decoration, so that accident is gone: a second pass would have matched
290
+ // again and drawn a second halo behind the first.
291
+ //
292
+ // Attaching to this module's own output stays OUT of contract — a host
293
+ // renders a fresh scene and attaches to that. What this pins is that going
294
+ // out of contract is REPORTED rather than silently corrupting: the element
295
+ // is left exactly as it is and its key never reaches `attached`, so the
296
+ // overlay comes back in `unattached` where a host or a test can see it.
297
+ if (child.attrs?.[STATE_ATTRIBUTE] !== undefined && child.attrs[STATE_ATTRIBUTE] !== null) {
298
+ next.push(child);
299
+ continue;
300
+ }
301
+ const match = overlayOn(child, context.byIdentity, context.byName);
302
+ if (match === null || match.overlay.states.length === 0) {
303
+ next.push(child);
304
+ continue;
305
+ }
306
+ const edge = context.edgeOf.get(match.key);
307
+ if (edge === undefined) {
308
+ next.push(child);
309
+ continue;
310
+ }
311
+ context.attached.add(match.key);
312
+ // PER STROKE, NOT PER EDGE — and this is the class fix, not a preference.
313
+ //
314
+ // `serialize-with` is drawn as TWO `.ig-edge` paths, and an earlier
315
+ // revision drew the stroke-derived marks ONCE for the pair. That is what
316
+ // produced a single-stroke conflict companion beside a double-stroke
317
+ // original, and it would have produced a halo around one of two parallel
318
+ // lines next. Every mark built here is CLONED FROM A STROKE, so it belongs
319
+ // to that stroke: clone them all and the kind's shape survives for free
320
+ // instead of being re-derived for each new case.
321
+ //
322
+ // The marks that are genuinely per-edge — the chips, the ✕, the reason —
323
+ // are not built here at all. They need a position this layer does not have,
324
+ // so they travel as declared marks and are placed once by the composer.
325
+ const { behind, front } = overlayMarks(child, match.overlay, edge, context.theme);
326
+ next.push(...behind, { ...child, attrs: { ...child.attrs, ...edgeAttributes(edgeName(edge), match.overlay) } }, ...front);
327
+ }
328
+ return { ...spec, children: next };
329
+ }
330
+ /**
331
+ * Overlay a scene's edges with the states their projection carries.
332
+ *
333
+ * Takes `ProjectedEdge`s rather than resolved overlays because an edge's kind
334
+ * and endpoints are what identify it in the scene, and asking a caller to hand
335
+ * those over separately is asking it to keep two things in step.
336
+ */
337
+ export function attachEdgeOverlays(scene, edges, options = {}) {
338
+ const theme = resolveTheme(options.theme);
339
+ const overlays = edges.map(overlayFor);
340
+ const byIdentity = new Map();
341
+ const byName = new Map();
342
+ const edgeOf = new Map();
343
+ for (const [index, overlay] of overlays.entries()) {
344
+ const edge = edges[index];
345
+ if (edge === undefined || overlay.states.length === 0)
346
+ continue;
347
+ byIdentity.set(edge.id, overlay);
348
+ edgeOf.set(edge.id, edge);
349
+ const name = edgeName(edge);
350
+ byName.set(name, overlay);
351
+ edgeOf.set(name, edge);
352
+ }
353
+ const attached = new Set();
354
+ const context = {
355
+ byIdentity,
356
+ byName,
357
+ edgeOf,
358
+ attached,
359
+ theme,
360
+ };
361
+ const root = overlayTree(scene.root, context);
362
+ // REPORTED, NOT DROPPED. An overlay whose edge the scene does not draw is
363
+ // ordinary — the graph projection has a node budget and falls back to
364
+ // clusters — but it is also what a broken match looks like, and the two must
365
+ // not be indistinguishable. A host can surface it; a test can pin it.
366
+ //
367
+ // Either key counts: a `together-with` is only ever drawn as a connector, so
368
+ // it matches by identity and never by name.
369
+ const unattached = overlays.filter((overlay, index) => {
370
+ const edge = edges[index];
371
+ if (edge === undefined || overlay.states.length === 0)
372
+ return false;
373
+ return !attached.has(edge.id) && !attached.has(edgeName(edge));
374
+ });
375
+ return { scene: { ...scene, root }, overlays, unattached };
376
+ }
377
+ /**
378
+ * The `writing…`, `retry` and reason marks, as controls a host can dispatch.
379
+ *
380
+ * Positioned by whoever calls it — see the module note. It renders the control,
381
+ * publishing what it does as data on `data-ig-overlay`, the same contract
382
+ * `scale/render.ts` established: layer 2 publishes the control, and the mount
383
+ * that owns the state wires it.
384
+ *
385
+ * The reason mark carries the store's `InvalidCode` and NO sentence. A host
386
+ * keys its own message off the code, for the same reason `change.ts` ships
387
+ * counts rather than prose.
388
+ */
389
+ export function renderOverlayMark(overlay, mark, reasonCode) {
390
+ switch (mark) {
391
+ case 'node-chip':
392
+ return element('span', { class: `${OVERLAY_CLASS} ig-overlay-chip`, 'data-ig-overlay': 'writing' },
393
+ // The one word this package renders, and it is a STATE name rather than
394
+ // a message — the same thing `grammar.ts` already announces to a screen
395
+ // reader, so a host that translates translates one vocabulary.
396
+ ['writing…']);
397
+ case 'terminal-cross':
398
+ return element('span', {
399
+ class: `${OVERLAY_CLASS} ig-overlay-cross`,
400
+ 'data-ig-overlay': 'failed',
401
+ role: 'img',
402
+ 'aria-label': 'failed',
403
+ },
404
+ // THE GLYPH IS THE MARK. An earlier draft returned this span EMPTY,
405
+ // carrying colour and typography and no shape — so `failed` lost the
406
+ // one cue that separates it from `invalid` without colour, which is the
407
+ // whole reason a terminal mark is drawn rather than a hue changed.
408
+ //
409
+ // A glyph rather than a word, for the same reason the viewer's edge
410
+ // vocabulary uses ⊘ ⇄ ⧉ ≡ ⑃: it is the non-colour channel, and it needs
411
+ // no translation.
412
+ ['✕']);
413
+ case 'inline-reason':
414
+ // A SLOT, NOT A GLYPH — and the distinction is deliberate rather than an
415
+ // oversight of the same class as the empty ✕ above. This element is
416
+ // EMPTY because the sentence is the host's: the package publishes the
417
+ // store's stable `InvalidCode` and the host keys its own message off it,
418
+ // exactly as `change.ts` ships counts rather than prose. `data-ig-slot`
419
+ // says so out loud, so an empty element here reads as intended rather
420
+ // than as the bug it looked like one case up.
421
+ return element('span', {
422
+ class: `${OVERLAY_CLASS} ig-overlay-reason`,
423
+ 'data-ig-overlay': 'reason',
424
+ 'data-ig-slot': 'reason',
425
+ 'data-ig-code': reasonCode ?? null,
426
+ });
427
+ case 'second-version':
428
+ // Also a slot. The held version is DRAWN by `attachEdgeOverlays` as the
429
+ // companion stroke; what a host adds here is whatever labels or offers to
430
+ // act on it — view-diff, retry-on-latest, discard-mine. Never a merge.
431
+ return element('span', {
432
+ class: `${OVERLAY_CLASS} ig-overlay-held`,
433
+ 'data-ig-overlay': 'conflict',
434
+ 'data-ig-slot': 'held-version',
435
+ 'aria-label': overlayLabel('', overlay).trim(),
436
+ });
437
+ }
438
+ }
439
+ //# sourceMappingURL=render.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.js","sourceRoot":"","sources":["../../src/overlay/render.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * The overlay's structural stylesheet.
3
+ *
4
+ * Same contract as the ladder's and the viewer's: layout, weight and state,
5
+ * never a value. Every colour, length and duration here is a `var(--ig-…)` the
6
+ * host's theme resolves, so a second theme keeps working on the surfaces layer
7
+ * 2 adds. `styles.test.ts` scans these bytes for a literal colour, a fixed
8
+ * length and a token the theme does not define.
9
+ *
10
+ * IT DECLARES NO OPACITY AT ALL. `grammar.ts` is the single source for every
11
+ * alpha this package applies, and `render.ts` writes each onto its element —
12
+ * so there is deliberately no `opacity` rule here, for a state OR for a mark.
13
+ *
14
+ * That rule was written for the state opacities and then broken by the halo,
15
+ * which was styled here at 0.35 because it is not a state. The composited
16
+ * contrast check reads the grammar, so the one alpha it could not see was the
17
+ * one that failed the 3:1 bar. `styles.test.ts` now enforces the rule instead
18
+ * of stating it. A rule would be a
19
+ * second copy of a number, and the two would drift the first time one was
20
+ * tuned. What CSS owns is what CSS can express and the table cannot: the halo's
21
+ * stroke, the marching animation, and the hue each state paints with.
22
+ *
23
+ * IT SELECTS ONLY WHAT THIS PACKAGE ADDS. In particular it never selects
24
+ * `.ig-terminal`: the terminal marker is one of the four redundant channels the
25
+ * edge's type identity rests on, and a rule here could occlude it as surely as
26
+ * a mark could. The four survive because nothing in this file reaches them.
27
+ *
28
+ * ## The marching dash, and why it is not a timer
29
+ *
30
+ * `pending-write` animates `stroke-dashoffset`, which is the design's "marching
31
+ * dash". Nothing about it changes STATE: the animation runs while the edge is
32
+ * pending and stops when the projection stops saying so. A chip that dismissed
33
+ * itself would be the banned thing, and there is none.
34
+ *
35
+ * It is written under `prefers-reduced-motion` guard, because a reader who has
36
+ * asked for less motion still needs to know a write is in flight — the opacity
37
+ * and the dash pattern carry that on their own, so the movement is what drops
38
+ * rather than the signal.
39
+ */
40
+ export declare const edgeOverlayStylesheet = "\n.ig-overlay {\n pointer-events: none;\n}\n\n.ig-overlay-halo {\n fill: none;\n stroke: var(--ig-focus);\n stroke-linecap: round;\n}\n\n/* The stroke is set per element from the treatment table \u2014 a state's own hue\n where it has one, the relationship's where it does not \u2014 for the same reason\n the viewer sets its dash per element: one source for a channel. */\n.ig-overlay-dash {\n fill: none;\n}\n\n.ig-overlay-marching {\n stroke-dasharray: 4 4;\n animation: ig-overlay-march 1s linear infinite;\n}\n\n.ig-overlay-dotted {\n stroke-dasharray: 1 3;\n}\n\n@keyframes ig-overlay-march {\n to {\n stroke-dashoffset: -8;\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .ig-overlay-marching {\n animation: none;\n }\n}\n\n/* NOT prefixed with the edge class. A together-with relationship is drawn as a\n connector rather than an edge path, so an edge-anchored rule left one of the\n five relationships unstyled in every state. Only an overlaid element carries\n the attribute at all, so the bare selector reaches exactly the intended set.\n (No backticks in here: this is inside a template literal, and one ends it.) */\n[data-ig-state~='invalid'] {\n stroke: var(--ig-state-invalid);\n}\n\n[data-ig-state~='failed'] {\n stroke: var(--ig-state-failed);\n}\n\n[data-ig-state~='conflict'] {\n stroke: var(--ig-state-conflict);\n}\n\n.ig-overlay-chip {\n background: var(--ig-surface-2);\n color: var(--ig-text-muted);\n border: var(--ig-stroke) solid var(--ig-line);\n border-radius: var(--ig-radius);\n padding: var(--ig-space-tight);\n font-family: var(--ig-font-ui);\n font-size: var(--ig-font-size-small);\n}\n\n.ig-overlay-cross {\n color: var(--ig-state-failed);\n font-family: var(--ig-font-ui);\n font-size: var(--ig-font-size);\n}\n\n.ig-overlay-reason {\n color: var(--ig-state-invalid);\n font-family: var(--ig-font-ui);\n font-size: var(--ig-font-size-small);\n}\n\n.ig-overlay-held {\n color: var(--ig-state-conflict);\n font-family: var(--ig-font-ui);\n font-size: var(--ig-font-size-small);\n}\n";
41
+ //# sourceMappingURL=styles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../../src/overlay/styles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH,eAAO,MAAM,qBAAqB,6hEAmFjC,CAAC"}
@@ -0,0 +1,124 @@
1
+ /**
2
+ * The overlay's structural stylesheet.
3
+ *
4
+ * Same contract as the ladder's and the viewer's: layout, weight and state,
5
+ * never a value. Every colour, length and duration here is a `var(--ig-…)` the
6
+ * host's theme resolves, so a second theme keeps working on the surfaces layer
7
+ * 2 adds. `styles.test.ts` scans these bytes for a literal colour, a fixed
8
+ * length and a token the theme does not define.
9
+ *
10
+ * IT DECLARES NO OPACITY AT ALL. `grammar.ts` is the single source for every
11
+ * alpha this package applies, and `render.ts` writes each onto its element —
12
+ * so there is deliberately no `opacity` rule here, for a state OR for a mark.
13
+ *
14
+ * That rule was written for the state opacities and then broken by the halo,
15
+ * which was styled here at 0.35 because it is not a state. The composited
16
+ * contrast check reads the grammar, so the one alpha it could not see was the
17
+ * one that failed the 3:1 bar. `styles.test.ts` now enforces the rule instead
18
+ * of stating it. A rule would be a
19
+ * second copy of a number, and the two would drift the first time one was
20
+ * tuned. What CSS owns is what CSS can express and the table cannot: the halo's
21
+ * stroke, the marching animation, and the hue each state paints with.
22
+ *
23
+ * IT SELECTS ONLY WHAT THIS PACKAGE ADDS. In particular it never selects
24
+ * `.ig-terminal`: the terminal marker is one of the four redundant channels the
25
+ * edge's type identity rests on, and a rule here could occlude it as surely as
26
+ * a mark could. The four survive because nothing in this file reaches them.
27
+ *
28
+ * ## The marching dash, and why it is not a timer
29
+ *
30
+ * `pending-write` animates `stroke-dashoffset`, which is the design's "marching
31
+ * dash". Nothing about it changes STATE: the animation runs while the edge is
32
+ * pending and stops when the projection stops saying so. A chip that dismissed
33
+ * itself would be the banned thing, and there is none.
34
+ *
35
+ * It is written under `prefers-reduced-motion` guard, because a reader who has
36
+ * asked for less motion still needs to know a write is in flight — the opacity
37
+ * and the dash pattern carry that on their own, so the movement is what drops
38
+ * rather than the signal.
39
+ */
40
+ export const edgeOverlayStylesheet = `
41
+ .ig-overlay {
42
+ pointer-events: none;
43
+ }
44
+
45
+ .ig-overlay-halo {
46
+ fill: none;
47
+ stroke: var(--ig-focus);
48
+ stroke-linecap: round;
49
+ }
50
+
51
+ /* The stroke is set per element from the treatment table — a state's own hue
52
+ where it has one, the relationship's where it does not — for the same reason
53
+ the viewer sets its dash per element: one source for a channel. */
54
+ .ig-overlay-dash {
55
+ fill: none;
56
+ }
57
+
58
+ .ig-overlay-marching {
59
+ stroke-dasharray: 4 4;
60
+ animation: ig-overlay-march 1s linear infinite;
61
+ }
62
+
63
+ .ig-overlay-dotted {
64
+ stroke-dasharray: 1 3;
65
+ }
66
+
67
+ @keyframes ig-overlay-march {
68
+ to {
69
+ stroke-dashoffset: -8;
70
+ }
71
+ }
72
+
73
+ @media (prefers-reduced-motion: reduce) {
74
+ .ig-overlay-marching {
75
+ animation: none;
76
+ }
77
+ }
78
+
79
+ /* NOT prefixed with the edge class. A together-with relationship is drawn as a
80
+ connector rather than an edge path, so an edge-anchored rule left one of the
81
+ five relationships unstyled in every state. Only an overlaid element carries
82
+ the attribute at all, so the bare selector reaches exactly the intended set.
83
+ (No backticks in here: this is inside a template literal, and one ends it.) */
84
+ [data-ig-state~='invalid'] {
85
+ stroke: var(--ig-state-invalid);
86
+ }
87
+
88
+ [data-ig-state~='failed'] {
89
+ stroke: var(--ig-state-failed);
90
+ }
91
+
92
+ [data-ig-state~='conflict'] {
93
+ stroke: var(--ig-state-conflict);
94
+ }
95
+
96
+ .ig-overlay-chip {
97
+ background: var(--ig-surface-2);
98
+ color: var(--ig-text-muted);
99
+ border: var(--ig-stroke) solid var(--ig-line);
100
+ border-radius: var(--ig-radius);
101
+ padding: var(--ig-space-tight);
102
+ font-family: var(--ig-font-ui);
103
+ font-size: var(--ig-font-size-small);
104
+ }
105
+
106
+ .ig-overlay-cross {
107
+ color: var(--ig-state-failed);
108
+ font-family: var(--ig-font-ui);
109
+ font-size: var(--ig-font-size);
110
+ }
111
+
112
+ .ig-overlay-reason {
113
+ color: var(--ig-state-invalid);
114
+ font-family: var(--ig-font-ui);
115
+ font-size: var(--ig-font-size-small);
116
+ }
117
+
118
+ .ig-overlay-held {
119
+ color: var(--ig-state-conflict);
120
+ font-family: var(--ig-font-ui);
121
+ font-size: var(--ig-font-size-small);
122
+ }
123
+ `;
124
+ //# sourceMappingURL=styles.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"styles.js","sourceRoot":"","sources":["../../src/overlay/styles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH,MAAM,CAAC,MAAM,qBAAqB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmFpC,CAAC"}