@issuegraph/editor 0.3.0 → 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.
Files changed (87) hide show
  1. package/README.md +31 -6
  2. package/dist/a11y/baseline.d.ts +182 -0
  3. package/dist/a11y/baseline.d.ts.map +1 -0
  4. package/dist/a11y/baseline.js +430 -0
  5. package/dist/a11y/baseline.js.map +1 -0
  6. package/dist/audit/styles.d.ts +1 -1
  7. package/dist/audit/styles.d.ts.map +1 -1
  8. package/dist/audit/styles.js +1 -1
  9. package/dist/create/draft.d.ts +17 -0
  10. package/dist/create/draft.d.ts.map +1 -1
  11. package/dist/create/draft.js +19 -0
  12. package/dist/create/draft.js.map +1 -1
  13. package/dist/create/keys.d.ts +70 -5
  14. package/dist/create/keys.d.ts.map +1 -1
  15. package/dist/create/keys.js +78 -17
  16. package/dist/create/keys.js.map +1 -1
  17. package/dist/firstpass/candidates.d.ts +17 -1
  18. package/dist/firstpass/candidates.d.ts.map +1 -1
  19. package/dist/index.d.ts +6 -4
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +12 -2
  22. package/dist/index.js.map +1 -1
  23. package/dist/overlay/render.d.ts.map +1 -1
  24. package/dist/overlay/render.js +54 -14
  25. package/dist/overlay/render.js.map +1 -1
  26. package/dist/overlay/styles.d.ts +1 -1
  27. package/dist/overlay/styles.d.ts.map +1 -1
  28. package/dist/overlay/styles.js +40 -0
  29. package/dist/overlay/styles.js.map +1 -1
  30. package/dist/picker/render.d.ts +26 -16
  31. package/dist/picker/render.d.ts.map +1 -1
  32. package/dist/picker/render.js +25 -36
  33. package/dist/picker/render.js.map +1 -1
  34. package/dist/picker/styles.d.ts +7 -7
  35. package/dist/picker/styles.d.ts.map +1 -1
  36. package/dist/picker/styles.js +7 -36
  37. package/dist/picker/styles.js.map +1 -1
  38. package/dist/picker/view.d.ts +8 -4
  39. package/dist/picker/view.d.ts.map +1 -1
  40. package/dist/picker/view.js +4 -3
  41. package/dist/picker/view.js.map +1 -1
  42. package/dist/picker/words.d.ts +12 -10
  43. package/dist/picker/words.d.ts.map +1 -1
  44. package/dist/picker/words.js +12 -8
  45. package/dist/picker/words.js.map +1 -1
  46. package/dist/scale/ladder.d.ts.map +1 -1
  47. package/dist/scale/ladder.js +10 -0
  48. package/dist/scale/ladder.js.map +1 -1
  49. package/dist/scale/render.d.ts.map +1 -1
  50. package/dist/scale/render.js +9 -1
  51. package/dist/scale/render.js.map +1 -1
  52. package/dist/workspace/chrome.d.ts +1 -1
  53. package/dist/workspace/chrome.d.ts.map +1 -1
  54. package/dist/workspace/chrome.js +48 -0
  55. package/dist/workspace/chrome.js.map +1 -1
  56. package/dist/workspace/firstpass.d.ts +166 -0
  57. package/dist/workspace/firstpass.d.ts.map +1 -0
  58. package/dist/workspace/firstpass.js +151 -0
  59. package/dist/workspace/firstpass.js.map +1 -0
  60. package/dist/workspace/host.d.ts +212 -10
  61. package/dist/workspace/host.d.ts.map +1 -1
  62. package/dist/workspace/host.js +434 -42
  63. package/dist/workspace/host.js.map +1 -1
  64. package/dist/workspace/inspector.d.ts +45 -1
  65. package/dist/workspace/inspector.d.ts.map +1 -1
  66. package/dist/workspace/inspector.js +11 -0
  67. package/dist/workspace/inspector.js.map +1 -1
  68. package/dist/workspace/mount.d.ts +52 -5
  69. package/dist/workspace/mount.d.ts.map +1 -1
  70. package/dist/workspace/mount.js +1391 -56
  71. package/dist/workspace/mount.js.map +1 -1
  72. package/dist/workspace/rail.d.ts.map +1 -1
  73. package/dist/workspace/rail.js +12 -0
  74. package/dist/workspace/rail.js.map +1 -1
  75. package/dist/workspace/recovery.d.ts +155 -0
  76. package/dist/workspace/recovery.d.ts.map +1 -0
  77. package/dist/workspace/recovery.js +156 -0
  78. package/dist/workspace/recovery.js.map +1 -0
  79. package/dist/workspace/render.d.ts +402 -1
  80. package/dist/workspace/render.d.ts.map +1 -1
  81. package/dist/workspace/render.js +947 -36
  82. package/dist/workspace/render.js.map +1 -1
  83. package/dist/workspace/styles.d.ts +1 -1
  84. package/dist/workspace/styles.d.ts.map +1 -1
  85. package/dist/workspace/styles.js +554 -41
  86. package/dist/workspace/styles.js.map +1 -1
  87. package/package.json +3 -3
@@ -83,6 +83,43 @@ export const workspaceStylesheet = `
83
83
  grid-area: header;
84
84
  }
85
85
 
86
+ /* SECTION 17a's HEADER: what backlog this is, how much of it is encoded, what
87
+ is wrong with it, how fresh the read is, and the way into a first pass. One
88
+ row, baseline-aligned, with the first-pass entry pushed to the far end --
89
+ the frame's only primary action on this surface.
90
+
91
+ NOTHING HERE MOVES, for the reason the whole sheet holds: the audit count
92
+ lives in this row, and section 17d asks for a count that never does. The
93
+ guard on that is a literal scan over these bytes, comments included, so the
94
+ words it looks for must not appear even in prose explaining their absence. */
95
+ .ig-workspace-header {
96
+ display: flex;
97
+ align-items: baseline;
98
+ flex-wrap: wrap;
99
+ gap: var(--ig-space);
100
+ padding: var(--ig-space-tight) var(--ig-space);
101
+ border-bottom: var(--ig-stroke) solid var(--ig-line);
102
+ }
103
+
104
+ .ig-workspace-identity {
105
+ color: var(--ig-text);
106
+ font-family: var(--ig-font-mono);
107
+ }
108
+
109
+ /* The one place the accent is spent on this surface, on the only action that
110
+ starts something. The frame draws it as the header's primary control. */
111
+ .ig-workspace-firstpass {
112
+ margin-left: auto;
113
+ background: var(--ig-accent);
114
+ color: var(--ig-bg);
115
+ border: none;
116
+ border-radius: var(--ig-radius);
117
+ padding: var(--ig-space-tight) var(--ig-space);
118
+ font-family: var(--ig-font-ui);
119
+ font-size: var(--ig-font-size-small);
120
+ cursor: pointer;
121
+ }
122
+
86
123
  .ig-zone[data-zone='rail'] {
87
124
  grid-area: rail;
88
125
  /* The rail scrolls; the window slides underneath. */
@@ -107,14 +144,30 @@ export const workspaceStylesheet = `
107
144
 
108
145
  An inset box-shadow rather than a border: a border changes the row's box and
109
146
  would shift every marked row against its neighbours, which is a layout jump
110
- the eye reads as movement. The width is the theme's spine, so the bar scales
111
- with the rest of the rail. */
147
+ the eye reads as movement.
148
+
149
+ IT DREW A 330px CYAN BLOCK UNTIL #122, and the comment above it — unchanged
150
+ here because it was right — is how the defect survived. --ig-spine-width is
151
+ §16b's SPINE CARD WIDTH (330), not a stroke; the sentence "the width is the
152
+ theme's spine, so the bar scales with the rest of the rail" reads as a
153
+ scaling argument and is a token mix-up. --ig-accent is the cyan the panel
154
+ spends once on the number an operator acts on, and it is not the attention
155
+ colour. This rule is more specific than audit/styles.ts's correct one, so
156
+ it silently won.
157
+
158
+ AND ITS TEST PASSED THROUGHOUT. styles.test.ts matched the rule and
159
+ asserted it EXISTED; it never asserted what the declaration resolves to, so
160
+ it was green about a string rather than about a property. The test below it
161
+ now reads the values.
162
+
163
+ ONE TREATMENT FOR ALL FOUR SEVERITIES, which is also a correction. §17d
164
+ names exactly one ambient mark — "a 2px gold left-bar on the affected rail
165
+ row" — and puts the severity distinction in the findings list, which is
166
+ host-side. The misleading variant this replaces gave the LEAST urgent
167
+ severity ("clearing is bookkeeping, not urgency") the alarm colour while
168
+ every other finding got cyan, inverting the design it came from. */
112
169
  .ig-zone[data-zone='rail'] [data-ig-audit] {
113
- box-shadow: inset var(--ig-spine-width) 0 0 0 var(--ig-accent);
114
- }
115
-
116
- .ig-zone[data-zone='rail'] [data-ig-audit='misleading'] {
117
- box-shadow: inset var(--ig-spine-width) 0 0 0 var(--ig-state-invalid);
170
+ box-shadow: inset var(--ig-stroke-audit) 0 0 0 var(--ig-edge-serialize-with);
118
171
  }
119
172
 
120
173
  /* THE ROWS OUTSIDE THE WINDOW, AS HEIGHT. The rail scrolls, so without a spacer
@@ -142,9 +195,16 @@ export const workspaceStylesheet = `
142
195
  --ig-rail-rows is the count, set inline by the renderer. What remains
143
196
  approximate is only VARIABLE row height — a row carrying holds is taller than
144
197
  a bare one — so the scrollbar stays proportional rather than exact. Measuring
145
- that needs a mount, which this package does not have. */
198
+ that needs a mount, which this package does not have.
199
+
200
+ THE PITCH IS THE FLOOR ALONE NOW. Layer 1's section 16 pass separates its rows
201
+ with the panel's own hairlines instead of a margin — a row is
202
+ --ig-row-min-height of content and a hairline, with no gap to add — so the sum
203
+ this used to take double-counted a gap that is no longer there. The row is
204
+ content-sized above that floor, which is the same approximation the note above
205
+ already describes, at a slightly larger typical size. */
146
206
  .ig-rail-spacer {
147
- height: calc((var(--ig-row-height) + var(--ig-space-tight)) * var(--ig-rail-rows, 0));
207
+ height: calc(var(--ig-row-min-height) * var(--ig-rail-rows, 0));
148
208
  }
149
209
 
150
210
  .ig-inspector {
@@ -154,12 +214,28 @@ export const workspaceStylesheet = `
154
214
  padding: var(--ig-space);
155
215
  }
156
216
 
157
- .ig-inspector-empty {
217
+ /* TWO EMPTIES, ONE TREATMENT. "Nothing is selected" and "this issue is related
218
+ to nothing" are different facts and the panel states each in its own place —
219
+ but they are the same KIND of statement, a quiet line where content would be,
220
+ and giving them separate looks would suggest a distinction that is not
221
+ there. */
222
+ .ig-inspector-empty,
223
+ .ig-inspector-none {
158
224
  margin: 0;
159
225
  color: var(--ig-text-muted);
160
226
  font-size: var(--ig-font-size-small);
161
227
  }
162
228
 
229
+ /* The panel's heading row: the name at one end, the way out at the other.
230
+ Baseline-aligned rather than centred, so a heading in caps and a button in
231
+ sentence case sit on one line rather than on two optical ones. */
232
+ .ig-inspector-head {
233
+ display: flex;
234
+ align-items: baseline;
235
+ justify-content: space-between;
236
+ gap: var(--ig-space-tight);
237
+ }
238
+
163
239
  .ig-inspector-issue {
164
240
  display: flex;
165
241
  flex-direction: column;
@@ -172,21 +248,6 @@ export const workspaceStylesheet = `
172
248
  color: var(--ig-text);
173
249
  }
174
250
 
175
- .ig-inspector-key {
176
- font-family: var(--ig-font-mono);
177
- color: var(--ig-text-muted);
178
- }
179
-
180
- .ig-inspector-position {
181
- margin: 0;
182
- font-family: var(--ig-font-mono);
183
- color: var(--ig-text-muted);
184
- }
185
-
186
- .ig-inspector-position[data-ready='true'] {
187
- color: var(--ig-station-ready);
188
- }
189
-
190
251
  .ig-inspector-holds {
191
252
  list-style: none;
192
253
  margin: 0;
@@ -232,14 +293,59 @@ export const workspaceStylesheet = `
232
293
  gap: var(--ig-space-tight);
233
294
  }
234
295
 
235
- .ig-inspector-heading {
296
+ /* SECTION 17a DRAWS EVERY INSPECTOR HEADING IN CAPS -- WHY RANK 2,
297
+ RELATIONSHIPS, ADD RELATIONSHIP -- so the treatment is shared rather than
298
+ spelled per heading. Tracking widens with the caps because letterforms at a
299
+ small size need it; that is what --ig-tracking-label is for. */
300
+ .ig-inspector-heading,
301
+ .ig-inspector-name,
302
+ .ig-why-rank-heading {
236
303
  margin: 0;
237
304
  font-size: var(--ig-font-size-small);
238
305
  color: var(--ig-text-muted);
306
+ text-transform: uppercase;
307
+ letter-spacing: var(--ig-tracking-label);
239
308
  }
240
309
 
241
- .ig-inspector-clear {
242
- align-self: flex-start;
310
+ /* THE EXPLANATION, SECTION 17a's WHOLE POINT FOR THIS ZONE. The clauses are
311
+ inline spans so the block reads as one sentence rather than a list of
312
+ findings: the reader asked one question. */
313
+ .ig-why-rank {
314
+ display: flex;
315
+ flex-direction: column;
316
+ gap: var(--ig-space-tight);
317
+ }
318
+
319
+ .ig-why-rank-sentence {
320
+ margin: 0;
321
+ color: var(--ig-text-body);
322
+ }
323
+
324
+ /* Each clause keeps a space after it so the sentence does not run together.
325
+ A margin rather than a literal space in the markup: the text is host-authored
326
+ and the package does not add characters to it. */
327
+ .ig-why-rank-sentence > * + * {
328
+ margin-left: var(--ig-space-micro);
329
+ }
330
+
331
+
332
+ /* THE PANEL'S THREE QUIET BUTTONS SHARE ONE TREATMENT — clear, '+ add' and
333
+ cancel. They are the same affordance at three moments (leave this selection,
334
+ begin a relationship, abandon the draft) and drawing them three ways would
335
+ invent a hierarchy the design does not state. The destructive controls are
336
+ deliberately NOT in this set: a row's '✕' is a glyph in the row's own slot,
337
+ and the mount's delete button carries its own danger treatment.
338
+
339
+ ALIGNMENT IS NOT PART OF THE TREATMENT, and putting it here was a silent
340
+ contradiction. 'align-self: flex-start' belongs to the two buttons in
341
+ '.ig-inspector-add', a COLUMN, where without it they stretch to the panel's
342
+ full width. The clear button sits in '.ig-inspector-head', a baseline row —
343
+ and 'align-self' beats the container's 'align-items', so it overrode the
344
+ baseline that rule's own comment says it is there to get. Declared where each
345
+ is true instead. */
346
+ .ig-inspector-clear,
347
+ .ig-inspector-addbutton,
348
+ .ig-inspector-cancel {
243
349
  background: var(--ig-surface-2);
244
350
  color: var(--ig-text-body);
245
351
  border: var(--ig-stroke) solid var(--ig-line);
@@ -250,7 +356,9 @@ export const workspaceStylesheet = `
250
356
  cursor: pointer;
251
357
  }
252
358
 
253
- .ig-inspector-clear:focus-visible {
359
+ .ig-inspector-clear:focus-visible,
360
+ .ig-inspector-addbutton:focus-visible,
361
+ .ig-inspector-cancel:focus-visible {
254
362
  outline: var(--ig-focus-ring) solid var(--ig-focus);
255
363
  outline-offset: var(--ig-space-tight);
256
364
  }
@@ -264,8 +372,21 @@ export const workspaceStylesheet = `
264
372
  gap: var(--ig-space-tight);
265
373
  }
266
374
 
375
+ /* IT WRAPS RATHER THAN TRUNCATES, which is '.ig-picker-direction''s rule
376
+ inherited along with §17b's statement. A qualified reference is long —
377
+ 'owner/repo#602' — the inspector is a fixed 40-character track with no
378
+ horizontal scroll, and a row that cannot wrap pushes its trailing controls
379
+ out of the zone entirely. A statement whose object is cut off says something
380
+ other than what the format holds, and a flip a reader cannot reach is the
381
+ affordance this row exists to offer.
382
+
383
+ ON THE ROW AND ON THE STATEMENT BOTH. The row wraps so the slot and the flip
384
+ drop to a second line rather than overflow; '.ig-relationship-select' below
385
+ wraps so the reference pair does the same inside it. Wrapping one without the
386
+ other leaves the other overflowing. */
267
387
  .ig-relationship {
268
388
  display: flex;
389
+ flex-wrap: wrap;
269
390
  gap: var(--ig-space-tight);
270
391
  align-items: baseline;
271
392
  padding: var(--ig-space-tight);
@@ -274,37 +395,70 @@ export const workspaceStylesheet = `
274
395
  background: var(--ig-surface);
275
396
  }
276
397
 
277
- /* Hue by field, from the theme's own edge tokens — the same channel layer 1
278
- uses, so a relationship reads the same colour in the list as on the canvas. */
279
- .ig-relationship[data-edge='blocked-by'] {
398
+ /* HUE BY FIELD, ONCE, FOR EVERY ELEMENT THAT CARRIES 'data-edge'. From the
399
+ theme's own edge tokens — the same channel layer 1 uses — so a relationship
400
+ reads the same colour in the row and in the kind-list entry it was chosen
401
+ from as it does on the canvas. The refusal capsule is deliberately NOT in
402
+ this set: its whole border is the invalid state's dotted stroke, and a hue on
403
+ its left edge would overwrite the half of that a reader looks for first.
404
+
405
+ WRITTEN PER FIELD RATHER THAN FROM A 'data-edge'-KEYED TOKEN, because CSS has
406
+ no way to build 'var(--ig-edge-' + attr() + ')': 'attr()' is usable in
407
+ 'content' and nowhere a custom property name is read. Five rules is the cost
408
+ of that, and the alternative the package rejected is a token per hue set
409
+ inline by the renderer, which moves a theme value into markup.
410
+
411
+ ONE SELECTOR LIST PER FIELD, AND THAT IS THE POINT OF THIS BLOCK. The kind
412
+ list arrived with a verbatim second copy of these five rules under
413
+ '.ig-kind-option' — same five fields, same five tokens — under a comment
414
+ claiming the file should not hold two ways of reading the theme while it
415
+ held the same way twice. A sixth field is now one line here, not two. */
416
+ .ig-relationship[data-edge='blocked-by'],
417
+ .ig-kind-option[data-edge='blocked-by'] {
280
418
  border-left-color: var(--ig-edge-blocked-by);
281
419
  }
282
420
 
283
- .ig-relationship[data-edge='duplicate-of'] {
421
+ .ig-relationship[data-edge='duplicate-of'],
422
+ .ig-kind-option[data-edge='duplicate-of'] {
284
423
  border-left-color: var(--ig-edge-duplicate-of);
285
424
  }
286
425
 
287
- .ig-relationship[data-edge='serialize-with'] {
426
+ .ig-relationship[data-edge='serialize-with'],
427
+ .ig-kind-option[data-edge='serialize-with'] {
288
428
  border-left-color: var(--ig-edge-serialize-with);
289
429
  }
290
430
 
291
- .ig-relationship[data-edge='together-with'] {
431
+ .ig-relationship[data-edge='together-with'],
432
+ .ig-kind-option[data-edge='together-with'] {
292
433
  border-left-color: var(--ig-edge-together-with);
293
434
  }
294
435
 
295
- .ig-relationship[data-edge='decomposed-from'] {
436
+ .ig-relationship[data-edge='decomposed-from'],
437
+ .ig-kind-option[data-edge='decomposed-from'] {
296
438
  border-left-color: var(--ig-edge-decomposed-from);
297
439
  }
298
440
 
441
+ /* THE DESCRIPTION'S BOX, WHETHER OR NOT A CONTROL IS WRAPPING IT. A row's head
442
+ is a button and a phantom capsule's is a plain span -- 'render.ts' withholds
443
+ the selector for an edge the landed document does not carry -- and the two
444
+ occupy the row identically: they fill it, so the reason wraps under them
445
+ rather than squeezing the reference out, and their parts sit on one baseline.
446
+ Declared once, so the capsule cannot come to sit differently from the row it
447
+ stands in for. */
448
+ .ig-relationship-select,
449
+ .ig-relationship-name {
450
+ flex: 1;
451
+ display: flex;
452
+ flex-wrap: wrap;
453
+ gap: var(--ig-space-tight);
454
+ align-items: baseline;
455
+ }
456
+
299
457
  /* The button fills its row, so the whole relationship is the hit target rather
300
458
  than the words inside it. Transparent and borderless: the li already carries
301
459
  the border and the hue, and a second box around it would read as two
302
460
  controls. */
303
461
  .ig-relationship-select {
304
- flex: 1;
305
- display: flex;
306
- gap: var(--ig-space-tight);
307
- align-items: baseline;
308
462
  background: none;
309
463
  border: none;
310
464
  padding: 0;
@@ -320,8 +474,15 @@ export const workspaceStylesheet = `
320
474
  outline-offset: var(--ig-space-tight);
321
475
  }
322
476
 
477
+ /* The glyph and its word are one phrase, so they get their own gap rather than
478
+ inheriting the row's — which spaces the phrase from the reference beside it,
479
+ a wider relationship. NOT a literal space character in the markup: the word
480
+ is the vocabulary's and this package does not add characters to it, which is
481
+ the same rule the why-rank sentence's clauses are spaced by. */
323
482
  .ig-relationship-kind {
324
- font-family: var(--ig-font-mono);
483
+ display: inline-flex;
484
+ gap: var(--ig-space-micro);
485
+ align-items: baseline;
325
486
  color: var(--ig-text-body);
326
487
  }
327
488
 
@@ -329,5 +490,357 @@ export const workspaceStylesheet = `
329
490
  font-family: var(--ig-font-mono);
330
491
  color: var(--ig-text-muted);
331
492
  }
493
+
494
+ /* THE RIGHT-HAND SLOT, WHOSE THREE OCCUPANTS ARE EXCLUSIVE. Two of them are
495
+ statements and one is a control, and they are drawn as such: the state and
496
+ the inbound marker are quiet label text, the remove is a button.
497
+ '.ig-relationship-flip' below is NOT one of the three: it sits beside the
498
+ slot on the selected row, because it is neither a statement about the row nor
499
+ a destructive act on it. */
500
+ .ig-relationship-state,
501
+ .ig-relationship-inbound {
502
+ align-self: center;
503
+ color: var(--ig-text-muted);
504
+ font-size: var(--ig-font-size-small);
505
+ text-transform: uppercase;
506
+ letter-spacing: var(--ig-tracking-label);
507
+ }
508
+
509
+ /* The selected row names the state the canvas is drawing a halo for, so it
510
+ takes the focus hue rather than the muted one — one selection, one colour,
511
+ across the zones. */
512
+ .ig-relationship-state[data-ig-state='selected'] {
513
+ color: var(--ig-focus);
514
+ }
515
+
516
+ /* A GLYPH BUTTON, SIZED BY ITS GLYPH. No border and no fill: the row already
517
+ carries a border, and a second box inside it would read as two controls —
518
+ the same reasoning '.ig-relationship-select' records. */
519
+ .ig-relationship-remove {
520
+ align-self: center;
521
+ background: none;
522
+ border: none;
523
+ padding: 0 var(--ig-space-tight);
524
+ color: var(--ig-text-muted);
525
+ font-family: inherit;
526
+ font-size: inherit;
527
+ line-height: 1;
528
+ cursor: pointer;
529
+ }
530
+
531
+ .ig-relationship-remove:focus-visible {
532
+ outline: var(--ig-focus-ring) solid var(--ig-focus);
533
+ outline-offset: var(--ig-space-tight);
534
+ }
535
+
536
+ /* §17b's FLIP, AT THE ROW'S END. It is drawn last and needs no margin of its
537
+ own to get there: '.ig-relationship-select' above carries 'flex: 1', so the
538
+ statement takes the free space and everything after it is already pushed to
539
+ the end — which is where frame 17b draws this control. A 'margin-left: auto'
540
+ here would be inert, and a comment crediting it would be an explanation that
541
+ outlived the thing it explained.
542
+
543
+ A BORDERED CONTROL RATHER THAN THE REMOVE'S BARE GLYPH, which is the frame's
544
+ own distinction and not decoration: '✕' is a mark a reader recognises without
545
+ a box, and 'flip' is a word that would otherwise read as part of the sentence
546
+ it sits beside. The border is what says it is pressable.
547
+
548
+ ITS FOCUS RING IS NOT OPTIONAL, and it takes the offset the row's OTHER two
549
+ controls already use rather than the one it was born with. This rule came
550
+ from the picker's sheet, where the ring sat a hairline out;
551
+ '.ig-relationship-select' and '.ig-relationship-remove' share this row and
552
+ both ring at '--ig-space-tight', and one control ringing tighter than its
553
+ neighbours reads as a rendering fault rather than as a distinction. This is
554
+ the control §17b calls the guard against the most common encoding mistake; a
555
+ keyboard reader who cannot see where they are has it and not its affordance. */
556
+ .ig-relationship-flip {
557
+ align-self: center;
558
+ background: var(--ig-surface);
559
+ color: var(--ig-text-body);
560
+ border: var(--ig-stroke) solid var(--ig-line);
561
+ border-radius: var(--ig-radius);
562
+ padding: 0 var(--ig-space-tight);
563
+ font-family: inherit;
564
+ font-size: var(--ig-font-size-small);
565
+ line-height: inherit;
566
+ cursor: pointer;
567
+ }
568
+
569
+ .ig-relationship-flip:focus-visible {
570
+ outline: var(--ig-focus-ring) solid var(--ig-focus);
571
+ outline-offset: var(--ig-space-tight);
572
+ }
573
+
574
+ /* A REFUSED EDIT, IN THE ROW IT WOULD HAVE BEEN. It borrows the ghost the
575
+ canvas draws for the same state — a dotted edge in the invalid hue — so the
576
+ line and the row a reader looks between are recognisably one fact.
577
+ NO OPACITY HERE. 'overlay/grammar.ts' holds the opacity every write state is
578
+ drawn at, on the record that a second copy would drift; what this sheet
579
+ contributes is the structure. */
580
+ .ig-relationship-refused {
581
+ display: flex;
582
+ flex-wrap: wrap;
583
+ gap: var(--ig-space-tight);
584
+ align-items: baseline;
585
+ padding: var(--ig-space-tight);
586
+ border: var(--ig-stroke) dotted var(--ig-state-invalid);
587
+ border-radius: var(--ig-radius);
588
+ background: var(--ig-surface);
589
+ }
590
+
591
+ .ig-relationship-reason {
592
+ color: var(--ig-state-invalid);
593
+ font-size: var(--ig-font-size-small);
594
+ }
595
+
596
+ /* A REFUSAL ON A RELATIONSHIP THAT IS STILL THERE. The row stays — it names a
597
+ real edge and its remove control still works — so this marks it rather than
598
+ replacing it: the capsule's dotted stroke, and room for the reason to wrap
599
+ under the row rather than squeezing the reference out of it.
600
+ BORDER-STYLE ONLY, NOT BORDER-COLOR: the field's hue is on the left edge from
601
+ the block above, and a shorthand here would overwrite it and take the row's
602
+ one colour channel away on exactly the rows carrying the most information. */
603
+ .ig-relationship[data-ig-code] {
604
+ flex-wrap: wrap;
605
+ border-style: dotted;
606
+ }
607
+
608
+ /* THE CREATE STEP, WHICHEVER STEP IT IS. One box holds '+ add' on its own and
609
+ the numbered list with its cancel, because they are one place in the panel
610
+ the reader returns to rather than two that happen to alternate. */
611
+ .ig-inspector-add {
612
+ display: flex;
613
+ flex-direction: column;
614
+ gap: var(--ig-space-tight);
615
+ }
616
+
617
+ /* Sized by their own words rather than stretched across the column — see the
618
+ quiet-button rule above, which deliberately does not carry this. */
619
+ .ig-inspector-addbutton,
620
+ .ig-inspector-cancel {
621
+ align-self: flex-start;
622
+ }
623
+
624
+ /* WHOSE DRAFT THIS IS, drawn only when it is not the panel's subject. Quiet and
625
+ small, like every other line that states a fact rather than offering an act:
626
+ it is a caption on the step below it, not a heading over it. The reference
627
+ inside keeps layer 1's '.ig-id' treatment, so the issue reads the same here
628
+ as it does in the why-rank sentence. */
629
+ .ig-inspector-source {
630
+ margin: 0;
631
+ color: var(--ig-text-muted);
632
+ font-size: var(--ig-font-size-small);
633
+ }
634
+
635
+ .ig-kind-list {
636
+ list-style: none;
637
+ margin: 0;
638
+ padding: 0;
639
+ display: flex;
640
+ flex-direction: column;
641
+ gap: var(--ig-space-micro);
642
+ }
643
+
644
+ /* Full width, so the whole entry is the hit target and the digits form a
645
+ readable column down the left — which is what makes the list legible as a
646
+ set of KEYS rather than as five buttons that happen to start with a number. */
647
+ .ig-kind-option {
648
+ width: 100%;
649
+ display: flex;
650
+ gap: var(--ig-space-tight);
651
+ align-items: baseline;
652
+ background: var(--ig-surface);
653
+ color: var(--ig-text-body);
654
+ border: var(--ig-stroke) solid var(--ig-line);
655
+ border-radius: var(--ig-radius);
656
+ padding: var(--ig-space-tight);
657
+ font-family: inherit;
658
+ font-size: var(--ig-font-size-small);
659
+ text-align: left;
660
+ cursor: pointer;
661
+ }
662
+
663
+ .ig-kind-option:focus-visible {
664
+ outline: var(--ig-focus-ring) solid var(--ig-focus);
665
+ outline-offset: var(--ig-space-tight);
666
+ }
667
+
668
+ .ig-kind-digit {
669
+ font-family: var(--ig-font-mono);
670
+ color: var(--ig-text-muted);
671
+ }
672
+
673
+ /* §17b's RECOVERY CARDS. An unsettled write the reader can still act on, drawn
674
+ where they made it.
675
+ THE HUE COMES FROM THE STATE, through the same custom properties the canvas
676
+ draws the line with — so a card and the edge it is about are recognisably one
677
+ fact, and a host retheming the palette moves both at once. The tokens are
678
+ named per state below rather than set once, because '--ig-state-failed' and
679
+ '--ig-state-conflict' are different channels and a shared variable here would
680
+ be a third place the pairing could drift.
681
+ NO OPACITY, for the reason the refusal capsule states: 'overlay/grammar.ts'
682
+ holds what each write state is drawn at, and this sheet contributes structure. */
683
+ .ig-recovery-region {
684
+ display: flex;
685
+ flex-direction: column;
686
+ gap: var(--ig-space-tight);
687
+ margin-top: var(--ig-space);
688
+ }
689
+
690
+ .ig-recovery-list {
691
+ list-style: none;
692
+ margin: 0;
693
+ padding: 0;
694
+ display: flex;
695
+ flex-direction: column;
696
+ gap: var(--ig-space-tight);
697
+ }
698
+
699
+ .ig-recovery {
700
+ display: flex;
701
+ flex-direction: column;
702
+ gap: var(--ig-space-tight);
703
+ padding: var(--ig-space-tight);
704
+ border-radius: var(--ig-radius);
705
+ background: var(--ig-surface);
706
+ border: var(--ig-stroke) solid var(--ig-line);
707
+ }
708
+
709
+ /* GHOST + THE ✕ THE CANVAS DRAWS. §17b gives a failed write a ghosted line with
710
+ a cross terminal; the card carries the same hue so the two read together. */
711
+ .ig-recovery[data-ig-state='failed'] {
712
+ border-color: var(--ig-state-failed);
713
+ border-style: dashed;
714
+ }
715
+
716
+ /* THE GOLD DOUBLE RULE. §17b draws a conflict as a doubled line holding two
717
+ versions. 'border-style: double' is the card's half of that grammar — the
718
+ LINE's half is the 'second-version' mark, which needs a position this layer
719
+ does not have and is issue #102's. */
720
+ .ig-recovery[data-ig-state='conflict'] {
721
+ border-color: var(--ig-state-conflict);
722
+ border-style: double;
723
+ border-width: calc(var(--ig-stroke) * 3);
724
+ }
725
+
726
+ .ig-recovery-name {
727
+ margin: 0;
728
+ font-size: var(--ig-font-size-small);
729
+ font-weight: 600;
730
+ }
731
+
732
+ .ig-recovery[data-ig-state='failed'] .ig-recovery-name {
733
+ color: var(--ig-state-failed);
734
+ }
735
+
736
+ .ig-recovery[data-ig-state='conflict'] .ig-recovery-name {
737
+ color: var(--ig-state-conflict);
738
+ }
739
+
740
+ .ig-recovery-reason,
741
+ .ig-recovery-refresh-error {
742
+ margin: 0;
743
+ font-size: var(--ig-font-size-small);
744
+ color: var(--ig-text-muted);
745
+ }
746
+
747
+ .ig-recovery-refresh-detail {
748
+ font-family: var(--ig-font-mono);
749
+ }
750
+
751
+ /* THE THREE RESOLUTIONS, IN A ROW AND NEVER IN A MENU. §17b lists them as
752
+ peers; nesting one behind a disclosure would rank them, and the one that
753
+ would end up hidden is 'view diff' — the one that lets a reader decide
754
+ between the other two. */
755
+ .ig-recovery-actions {
756
+ display: flex;
757
+ flex-wrap: wrap;
758
+ gap: var(--ig-space-tight);
759
+ }
760
+
761
+ .ig-recovery-action {
762
+ background: var(--ig-surface);
763
+ color: var(--ig-text-body);
764
+ border: var(--ig-stroke) solid var(--ig-line);
765
+ border-radius: var(--ig-radius);
766
+ padding: var(--ig-space-tight);
767
+ font-family: inherit;
768
+ font-size: var(--ig-font-size-small);
769
+ cursor: pointer;
770
+ }
771
+
772
+ .ig-recovery-action:focus-visible {
773
+ outline: var(--ig-focus-ring) solid var(--ig-focus);
774
+ outline-offset: var(--ig-space-tight);
775
+ }
776
+
777
+ .ig-recovery-action[aria-pressed='true'] {
778
+ border-color: var(--ig-focus);
779
+ }
780
+
781
+ /* TWO SIDES, SIDE BY SIDE, AND NOTHING BETWEEN THEM THAT COMBINES THEM. The
782
+ layout is the design constraint: there is no column for a merged result,
783
+ because there is no merged result to put in one. */
784
+ .ig-recovery-diff {
785
+ display: flex;
786
+ flex-direction: column;
787
+ gap: var(--ig-space-tight);
788
+ padding-top: var(--ig-space-tight);
789
+ border-top: var(--ig-stroke) solid var(--ig-line);
790
+ }
791
+
792
+ /* ONE SIDE OF THE DIFFERENCE. Stacked rather than columned: at a panel's width
793
+ two columns give each side about half of an already narrow zone, and a
794
+ relationship row is a sentence rather than a cell. */
795
+ .ig-recovery-side {
796
+ display: flex;
797
+ flex-direction: column;
798
+ gap: var(--ig-space-tight);
799
+ }
800
+
801
+ .ig-recovery-side-name {
802
+ margin: 0;
803
+ font-size: var(--ig-font-size-small);
804
+ color: var(--ig-text-muted);
805
+ font-weight: 400;
806
+ }
807
+
808
+ .ig-recovery-edges,
809
+ .ig-recovery-issues {
810
+ list-style: none;
811
+ margin: 0;
812
+ padding: 0;
813
+ display: flex;
814
+ flex-direction: column;
815
+ gap: var(--ig-space-tight);
816
+ }
817
+
818
+ .ig-recovery-edge,
819
+ .ig-recovery-issue {
820
+ display: flex;
821
+ flex-wrap: wrap;
822
+ gap: var(--ig-space-tight);
823
+ align-items: baseline;
824
+ font-size: var(--ig-font-size-small);
825
+ }
826
+
827
+ /* BOTH TITLES, NEITHER STRUCK THROUGH. A strike-out would say the old one is
828
+ gone, which is exactly the judgement this card must not make for the reader. */
829
+ .ig-recovery-was,
830
+ .ig-recovery-now {
831
+ color: var(--ig-text-muted);
832
+ }
833
+
834
+ .ig-recovery-now {
835
+ color: var(--ig-state-conflict);
836
+ }
837
+
838
+ .ig-recovery-diff-empty {
839
+ margin: 0;
840
+ padding-top: var(--ig-space-tight);
841
+ border-top: var(--ig-stroke) solid var(--ig-line);
842
+ font-size: var(--ig-font-size-small);
843
+ color: var(--ig-text-muted);
844
+ }
332
845
  `;
333
846
  //# sourceMappingURL=styles.js.map