@issuegraph/editor 0.10.0 → 0.23.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 (113) hide show
  1. package/README.md +38 -7
  2. package/dist/a11y/baseline.d.ts.map +1 -1
  3. package/dist/a11y/baseline.js +4 -0
  4. package/dist/a11y/baseline.js.map +1 -1
  5. package/dist/audit/findings.d.ts +107 -1
  6. package/dist/audit/findings.d.ts.map +1 -1
  7. package/dist/audit/findings.js +114 -7
  8. package/dist/audit/findings.js.map +1 -1
  9. package/dist/audit/panel.d.ts +277 -0
  10. package/dist/audit/panel.d.ts.map +1 -0
  11. package/dist/audit/panel.js +462 -0
  12. package/dist/audit/panel.js.map +1 -0
  13. package/dist/audit/refused.d.ts +99 -0
  14. package/dist/audit/refused.d.ts.map +1 -0
  15. package/dist/audit/refused.js +276 -0
  16. package/dist/audit/refused.js.map +1 -0
  17. package/dist/audit/styles.d.ts +1 -1
  18. package/dist/audit/styles.d.ts.map +1 -1
  19. package/dist/audit/styles.js +428 -0
  20. package/dist/audit/styles.js.map +1 -1
  21. package/dist/audit/surface.d.ts +54 -12
  22. package/dist/audit/surface.d.ts.map +1 -1
  23. package/dist/audit/surface.js +54 -14
  24. package/dist/audit/surface.js.map +1 -1
  25. package/dist/create/draft.d.ts +19 -0
  26. package/dist/create/draft.d.ts.map +1 -1
  27. package/dist/create/draft.js +21 -0
  28. package/dist/create/draft.js.map +1 -1
  29. package/dist/create/keys.d.ts +68 -8
  30. package/dist/create/keys.d.ts.map +1 -1
  31. package/dist/create/keys.js +43 -5
  32. package/dist/create/keys.js.map +1 -1
  33. package/dist/firstpass/render.d.ts.map +1 -1
  34. package/dist/firstpass/render.js +10 -9
  35. package/dist/firstpass/render.js.map +1 -1
  36. package/dist/index.d.ts +40 -15
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +35 -13
  39. package/dist/index.js.map +1 -1
  40. package/dist/overlay/render.d.ts +11 -3
  41. package/dist/overlay/render.d.ts.map +1 -1
  42. package/dist/overlay/render.js +24 -7
  43. package/dist/overlay/render.js.map +1 -1
  44. package/dist/overlay/request.d.ts +71 -0
  45. package/dist/overlay/request.d.ts.map +1 -0
  46. package/dist/overlay/request.js +129 -0
  47. package/dist/overlay/request.js.map +1 -0
  48. package/dist/overlay/styles.d.ts +1 -1
  49. package/dist/overlay/styles.d.ts.map +1 -1
  50. package/dist/overlay/styles.js +38 -7
  51. package/dist/overlay/styles.js.map +1 -1
  52. package/dist/reevaluate/parts.d.ts +130 -0
  53. package/dist/reevaluate/parts.d.ts.map +1 -0
  54. package/dist/reevaluate/parts.js +260 -0
  55. package/dist/reevaluate/parts.js.map +1 -0
  56. package/dist/reevaluate/render.d.ts +18 -11
  57. package/dist/reevaluate/render.d.ts.map +1 -1
  58. package/dist/reevaluate/render.js +21 -135
  59. package/dist/reevaluate/render.js.map +1 -1
  60. package/dist/reevaluate/styles.d.ts +1 -1
  61. package/dist/reevaluate/styles.d.ts.map +1 -1
  62. package/dist/reevaluate/styles.js +26 -0
  63. package/dist/reevaluate/styles.js.map +1 -1
  64. package/dist/reevaluate/view.d.ts +16 -1
  65. package/dist/reevaluate/view.d.ts.map +1 -1
  66. package/dist/reevaluate/view.js +21 -1
  67. package/dist/reevaluate/view.js.map +1 -1
  68. package/dist/reevaluate/words.d.ts +7 -0
  69. package/dist/reevaluate/words.d.ts.map +1 -1
  70. package/dist/scale/ladder.d.ts +62 -6
  71. package/dist/scale/ladder.d.ts.map +1 -1
  72. package/dist/scale/ladder.js +127 -16
  73. package/dist/scale/ladder.js.map +1 -1
  74. package/dist/scale/render.d.ts +44 -0
  75. package/dist/scale/render.d.ts.map +1 -1
  76. package/dist/scale/render.js +223 -30
  77. package/dist/scale/render.js.map +1 -1
  78. package/dist/scale/styles.d.ts +11 -5
  79. package/dist/scale/styles.d.ts.map +1 -1
  80. package/dist/scale/styles.js +185 -4
  81. package/dist/scale/styles.js.map +1 -1
  82. package/dist/workspace/host.d.ts +83 -1
  83. package/dist/workspace/host.d.ts.map +1 -1
  84. package/dist/workspace/host.js +197 -4
  85. package/dist/workspace/host.js.map +1 -1
  86. package/dist/workspace/inspector.d.ts +33 -7
  87. package/dist/workspace/inspector.d.ts.map +1 -1
  88. package/dist/workspace/inspector.js +15 -0
  89. package/dist/workspace/inspector.js.map +1 -1
  90. package/dist/workspace/mount.d.ts +23 -1
  91. package/dist/workspace/mount.d.ts.map +1 -1
  92. package/dist/workspace/mount.js +605 -20
  93. package/dist/workspace/mount.js.map +1 -1
  94. package/dist/workspace/rail.d.ts +5 -2
  95. package/dist/workspace/rail.d.ts.map +1 -1
  96. package/dist/workspace/rail.js.map +1 -1
  97. package/dist/workspace/render.d.ts +390 -3
  98. package/dist/workspace/render.d.ts.map +1 -1
  99. package/dist/workspace/render.js +924 -133
  100. package/dist/workspace/render.js.map +1 -1
  101. package/dist/workspace/selection.d.ts +11 -0
  102. package/dist/workspace/selection.d.ts.map +1 -1
  103. package/dist/workspace/selection.js +10 -0
  104. package/dist/workspace/selection.js.map +1 -1
  105. package/dist/workspace/skeleton.d.ts +33 -0
  106. package/dist/workspace/skeleton.d.ts.map +1 -0
  107. package/dist/workspace/skeleton.js +56 -0
  108. package/dist/workspace/skeleton.js.map +1 -0
  109. package/dist/workspace/styles.d.ts +1 -1
  110. package/dist/workspace/styles.d.ts.map +1 -1
  111. package/dist/workspace/styles.js +931 -19
  112. package/dist/workspace/styles.js.map +1 -1
  113. package/package.json +4 -4
@@ -58,13 +58,39 @@ export const workspaceStylesheet = `
58
58
  squeeze the canvas, which is the one thing fixed positions exist to stop.
59
59
  Nothing failed loudly, because an invalid declaration is simply absent.
60
60
 
61
- 40 is a reading measure for a title plus its id, in characters. */
61
+ 40 is a reading measure for a title plus its id, in characters.
62
+
63
+ THIS IS THE PRE-17k FALLBACK, and it is deliberately the OLD equal measure. 17k
64
+ gives 390 / 1fr / 330 for widths at or above 1360 and something else
65
+ entirely below it: the inspector lifts to an overlay from 1120, and under
66
+ that the canvas collapses to a strip. Neither of those exists yet, so
67
+ applying the wide proportions at every width squeezes the canvas instead
68
+ of collapsing it — measured at 990px, where the canvas fell to 248px. That
69
+ is the one rule 17k states outright inverted: "the rail never yields, the
70
+ canvas yields first." Below the breakpoint the old equal tracks are the
71
+ honest placeholder. */
62
72
  grid-template-columns:
63
73
  calc(var(--ig-char-width) * 40)
64
74
  1fr
65
75
  calc(var(--ig-char-width) * 40);
66
76
  grid-template-rows: auto 1fr;
67
- gap: var(--ig-space);
77
+ /* NO GAP, BECAUSE THE SEAMS ARE DRAWN RULES AND A RULE HAS TO REACH ITS
78
+ CORNER. The tracks used to be separated by --ig-space, and every rule on
79
+ this surface stopped short of the one it should have met: the header's
80
+ border-bottom ran the full width, then a 12px band of ground, and only
81
+ then did the rail's border-right and the inspector's border-left begin —
82
+ three lines that never touched. The canvas toolbar's border-bottom had the
83
+ same defect on the other axis, ending 12px shy of the rail rule at one end
84
+ and the inspector rule at the other, so the zone it captions read as a
85
+ floating band rather than as the top row of a column.
86
+
87
+ THE BREATHING ROOM DID NOT GO ANYWHERE; IT MOVED INSIDE. A gap puts the
88
+ space OUTSIDE the zone, where it separates the rule from its corner. Each
89
+ zone's own content already carries its inset — the rail's rows and footer
90
+ take --ig-space-wide, the canvas toolbar and the inspector take
91
+ --ig-space — so the measured gutter between a rule and the nearest text is
92
+ unchanged in kind, and what is gone is only the dead band the rules were
93
+ stopping at. */
68
94
  background: var(--ig-bg);
69
95
  color: var(--ig-text-body);
70
96
  font-family: var(--ig-font-ui);
@@ -72,6 +98,53 @@ export const workspaceStylesheet = `
72
98
  line-height: var(--ig-line-height);
73
99
  }
74
100
 
101
+ /* THE CONTAINER IS THE MOUNT, NOT THE WORKSPACE, AND THAT IS NOT A DETAIL.
102
+ A container query CANNOT style the element that establishes the container:
103
+ put container-type on .ig-workspace and a rule inside @container matching
104
+ .ig-workspace never fires. It looked right, built clean, and did nothing —
105
+ measured at 1500px, where the rail and inspector sat at 312/312 while only
106
+ the canvas 1fr grew. A rule that can never fire is the same class as a guard
107
+ that can never fail, and the only reason this was caught is that the check
108
+ compared the TRACKS either side of the breakpoint rather than asking whether
109
+ the layout "changed".
110
+
111
+ .ig-mount is this package's own wrapper around the workspace, so the query
112
+ has a box to measure that is not the box it restyles. */
113
+ .ig-mount {
114
+ container-name: ig-workspace;
115
+ container-type: inline-size;
116
+ }
117
+
118
+ /* 17k's WIDE LAYOUT, AT THE WIDTH 17k GIVES IT FOR. A container query, not a
119
+ viewport one, for the reason layer 1's row already answers to: the workspace
120
+ is a box a host places, and a package that reads the window cannot be
121
+ dropped into someone else's page.
122
+
123
+ 1360 IS 17k's OWN NUMBER, not a chosen one — the table's first row reads
124
+ "at or above 1360: 390 / 1fr / 330, three zones, as 17a".
125
+
126
+ THE TWO ZONES ARE NOT THE SAME WIDTH, AND THAT IS THE POINT OF THIS RULE.
127
+ Both tracks were 40 characters, which is one reading measure applied twice;
128
+ 17a draws 1292 = 390 + 572 + 330, a rail WIDER than the inspector. The rail
129
+ carries a rank, a title, a meta line and a delta chip on one row while the
130
+ inspector carries a column of stacked blocks — not the same problem, and
131
+ sizing them alike shortchanged the surface 17k says must never yield.
132
+
133
+ 50 AND 42 CHARACTERS, NOT 390px AND 330px, because this file sizes zones in
134
+ TYPE on purpose: a host that scales its type scales the zones with it, and a
135
+ fixed track would not. At the shipped --ig-char-width of 7.8 that is 390 and
136
+ 327.6 — the rail exact, the inspector 2.4px under 17k's figure. Stated
137
+ rather than rounded away; the type-relative property is worth more than the
138
+ 2.4px, and it is the same BYO argument layer 1's row query rests on. */
139
+ @container ig-workspace (width >= 1360px) {
140
+ .ig-workspace {
141
+ grid-template-columns:
142
+ calc(var(--ig-char-width) * 50)
143
+ 1fr
144
+ calc(var(--ig-char-width) * 42);
145
+ }
146
+ }
147
+
75
148
  .ig-zone {
76
149
  min-width: 0;
77
150
  /* Without this a long title in any zone sets that track's minimum width and
@@ -106,6 +179,46 @@ export const workspaceStylesheet = `
106
179
  font-family: var(--ig-font-mono);
107
180
  }
108
181
 
182
+ /* The adoption pair and the freshness stamp, hoisted out of layer 1's panel
183
+ header by #135. Quiet, because the row's only loud element is the entry at
184
+ its far end -- these are orientation, read once and then ignored. */
185
+ .ig-workspace-counts,
186
+ .ig-workspace-freshness {
187
+ color: var(--ig-text-muted);
188
+ font-family: var(--ig-font-ui);
189
+ font-size: var(--ig-font-size-small);
190
+ }
191
+
192
+ /* THE ONLY TREATMENT STALENESS GETS, and it is why the stamp carries no word
193
+ for it: the state is on the element, so the sheet says what a stale read
194
+ looks like and no host has to translate an adjective. */
195
+ .ig-workspace-freshness[data-stale='true'] {
196
+ color: var(--ig-state-invalid);
197
+ }
198
+
199
+ /* A CONTROL, NOT A CHIP. It sits inside the stamp because that is what it
200
+ refreshes, and it takes the button reset the sheet gives every other quiet
201
+ control on this surface rather than the accent the first-pass entry spends. */
202
+ .ig-workspace-refresh {
203
+ margin-left: var(--ig-space-tight);
204
+ font: inherit;
205
+ color: var(--ig-text);
206
+ background: var(--ig-surface-2);
207
+ border: var(--ig-stroke) solid var(--ig-line);
208
+ border-radius: var(--ig-radius);
209
+ padding: 0 var(--ig-space-tight);
210
+ cursor: pointer;
211
+ }
212
+
213
+ .ig-workspace-refresh:hover {
214
+ border-color: var(--ig-accent);
215
+ }
216
+
217
+ .ig-workspace-refresh:focus-visible {
218
+ outline: var(--ig-focus-ring) solid var(--ig-focus);
219
+ outline-offset: var(--ig-space-tight);
220
+ }
221
+
109
222
  /* The one place the accent is spent on this surface, on the only action that
110
223
  starts something. The frame draws it as the header's primary control. */
111
224
  .ig-workspace-firstpass {
@@ -125,19 +238,331 @@ export const workspaceStylesheet = `
125
238
  /* The rail scrolls; the window slides underneath. */
126
239
  overflow-y: auto;
127
240
  border-right: var(--ig-stroke) solid var(--ig-line);
241
+ /* A COLUMN, SO THE FOOTER CAN TAKE THE SPACE A SHORT ORDER LEAVES. See
242
+ .ig-rail-footer for why sticky alone never did. */
243
+ display: flex;
244
+ flex-direction: column;
245
+ }
246
+
247
+ /* NOTHING IN THE RAIL SHRINKS. A scrolling flex column will squeeze its
248
+ children to fit before it scrolls them, and the rail's virtualization reads
249
+ row positions off a fixed pitch — a squeezed viewer would put every offset on
250
+ the wrong row. Every child keeps its natural height and the zone scrolls. */
251
+ .ig-zone[data-zone='rail'] > * {
252
+ flex: none;
253
+ }
254
+
255
+ /* §17a'S RAIL FOOTER, AND IT IS THE ZONE'S LAST ROW. The frame ends the rail
256
+ with it; that it is PINNED is this sheet's decision, on the reasoning §17c
257
+ already settled one zone over — the rail is the scroller, mount.ts restores
258
+ its scrollTop on every redraw, and a fact placed in the scrolled content is a
259
+ fact you have to go back for. .ig-canvas-toolbar below pins itself against
260
+ the other edge for exactly this reason and is the shape copied here.
261
+
262
+ ON A SHORT RAIL IT IS PUSHED DOWN, NOT STUCK DOWN. margin-top: auto in the
263
+ zone's column takes whatever height the order leaves, so the footer sits on
264
+ the bottom edge whether the rail scrolls or not. An earlier note here said
265
+ sticky alone anchored a short rail and that a column wrapper "changed
266
+ nothing" — that was measured with the rows at 416px in a 445px track, where
267
+ the gap left was about one footer high and flush and not-flush look alike.
268
+ Sticky only moves a box that would otherwise leave the scrollport, so on a
269
+ short rail it never moved at all: the footer sat directly under the last
270
+ row with the rest of the zone empty beneath it. Sticky still does the work
271
+ once the rail overflows; the margin does it before.
272
+
273
+ OPAQUE, BECAUSE IT OCCLUDES. Rows slide under it, so a transparent ground
274
+ would show the order through the count. That is also why the OPEN LIST IS NOT
275
+ IN HERE: an element taller than its scrollport cannot stick — the browser
276
+ clamps it and it scrolls like any other block — so a footer holding 248
277
+ entries loses the pin exactly when it grows, and covers the whole rail on the
278
+ way. The deeper reason is the rail's VIRTUALIZATION: railRowAt maps a scroll
279
+ offset to a row by floor((scrollTop - chrome) / pitch), so content of
280
+ arbitrary height anywhere in this track makes every offset below it name the
281
+ wrong row. The list is drawn in the canvas zone, which has no such
282
+ arithmetic. */
283
+ .ig-rail-footer {
284
+ margin-top: auto;
285
+ position: sticky;
286
+ bottom: 0;
287
+ z-index: 1;
288
+ display: flex;
289
+ align-items: center;
290
+ justify-content: space-between;
291
+ gap: var(--ig-space-tight);
292
+ padding: var(--ig-space-tight) var(--ig-space);
293
+ border-top: var(--ig-stroke) solid var(--ig-line);
294
+ background: var(--ig-bg);
295
+ font-family: var(--ig-font-mono);
296
+ font-size: var(--ig-font-size-small);
297
+ line-height: var(--ig-line-height);
298
+ color: var(--ig-text-body);
299
+ }
300
+
301
+ .ig-rail-count {
302
+ color: var(--ig-text);
303
+ }
304
+
305
+ .ig-rail-isolated-toggle {
306
+ font: inherit;
307
+ color: var(--ig-accent);
308
+ background: none;
309
+ border: none;
310
+ padding: 0;
311
+ cursor: pointer;
312
+ }
313
+
314
+ .ig-rail-isolated-toggle:focus-visible {
315
+ outline: var(--ig-focus-ring) solid var(--ig-focus);
316
+ outline-offset: var(--ig-space-tight);
128
317
  }
129
318
 
130
319
  .ig-zone[data-zone='canvas'] {
131
320
  grid-area: canvas;
132
321
  overflow: auto;
322
+ /* THE OVERLAY'S CONTAINING BLOCK. §17d's panel is positioned against THIS
323
+ zone rather than the workspace, and that is what keeps it off the rail
324
+ without a magic offset: the rail is a different grid area, so an absolutely
325
+ positioned child of the canvas cannot reach it however wide it gets.
326
+ RULINGS.md §3 states the constraint and gives the reason — "judging a
327
+ finding is largely a question of what it would do to the order, so the
328
+ order has to stay readable while you judge." */
329
+ position: relative;
330
+ }
331
+
332
+ /* §17f'S CAPTION, AND IT IS THE ZONE'S FIRST ROW. Frame 17a puts it above the
333
+ graph with a rule under it, which is the same shape the header row beside it
334
+ already has — so it takes that row's padding tokens rather than a bespoke
335
+ pair, and the two read as one band across the surface.
336
+
337
+ THE GROUND, NOT THE CARD COLOUR. --ig-surface is what a RAISED element takes
338
+ here: a bordered card, a button. This row is a full-bleed structural band, and
339
+ painting it the panel colour made it read as a card floating on the canvas —
340
+ which is also what the frame does not draw, and what the comment above it
341
+ claimed it was not. --ig-bg is the ground .ig-workspace itself paints and the
342
+ header row inherits, so the two really do match. It has to be OPAQUE either
343
+ way: the row is sticky over a scrolling graph and must occlude it.
344
+
345
+ IT DOES NOT SCROLL WITH THE CANVAS, deliberately. The zone scrolls (above),
346
+ and a caption saying "6 of 312 shown" that scrolls away is a caption you have
347
+ to go back for. A sticky position keeps it against the zone's top edge
348
+ without taking the graph out of the zone's own scroll. */
349
+ .ig-canvas-toolbar {
350
+ display: flex;
351
+ align-items: center;
352
+ gap: var(--ig-space-tight);
353
+ padding: var(--ig-space-tight) var(--ig-space);
354
+ border-bottom: var(--ig-stroke) solid var(--ig-line);
355
+ position: sticky;
356
+ top: 0;
357
+ background: var(--ig-bg);
358
+ z-index: 1;
359
+ }
360
+
361
+ /* MONO AND MUTED, because it is a read-out rather than a heading: the frame
362
+ sets it in the same mono face at the same muted hue as the rail's own footer
363
+ count, and the two are the same kind of fact. */
364
+ .ig-canvas-caption {
365
+ margin: 0;
366
+ font-family: var(--ig-font-mono);
367
+ font-size: var(--ig-font-size-micro);
368
+ color: var(--ig-text-muted);
369
+ min-width: 0;
370
+ overflow: hidden;
371
+ text-overflow: ellipsis;
372
+ white-space: nowrap;
133
373
  }
134
374
 
375
+ /* ITS OWN CLASS, NOT LAYER 1'S .ig-count. They look like the same thing and are
376
+ not: layer 1's is the isolated-issue tally that "sits directly on the panel,
377
+ outside the padded groups, so it carries the panel's own inset" — 12px 20px of
378
+ it. Borrowing the name put that inset around two numbers in the middle of a
379
+ sentence, which is what the frame comparison caught. */
380
+ .ig-canvas-count {
381
+ color: var(--ig-text-body);
382
+ font-variant-numeric: tabular-nums;
383
+ }
384
+
385
+ /* THE CLAUSES DO NOT BREAK INTERNALLY. The caption is one line that ellipsizes
386
+ as a whole; what must not happen is a label parting from the key it names, or
387
+ a count parting from the word that says what it counts. */
388
+ .ig-canvas-focus,
389
+ .ig-canvas-shown {
390
+ white-space: nowrap;
391
+ }
392
+
393
+ /* PUSHED RIGHT BY ITS OWN MARGIN, NOT BY THE ROW'S JUSTIFICATION. The row's
394
+ arity changes: off the direct tier the caption is gone and the pill is the
395
+ only child, and justify-content: space-between puts a lone item at main
396
+ START — so the pill jumped to the left edge on exactly the tiers a large
397
+ backlog is always in, which is the state this surface exists to handle well.
398
+ margin-left: auto is one declaration that is correct at either arity, and it
399
+ is also what leaves #181's projection toggle a real slot: a control added
400
+ ahead of the caption lands in the left group without touching this rule.
401
+
402
+ THE TINT PAIR, NOT A BARE OUTLINE. The tint fill and tint border tokens
403
+ exist for exactly this — "a fill of its own hue inside a heavier border of
404
+ the same hue" — and the frame draws the pill that way. Reaching for the
405
+ accent directly would give a solid chip that reads as loud as the selection.
406
+
407
+ IT NEVER SHRINKS. The caption beside it is the part that may ellipsize; the
408
+ pill is three or four characters and losing them would leave a coloured stub
409
+ saying nothing. */
410
+ .ig-edit-mode {
411
+ flex: 0 0 auto;
412
+ margin-left: auto;
413
+ font-family: var(--ig-font-mono);
414
+ font-size: var(--ig-font-size-pill);
415
+ letter-spacing: var(--ig-tracking-pill);
416
+ color: var(--ig-accent);
417
+ /* THE TINT TOKENS ARE PERCENTAGES, NOT COLOURS — 8% and 30% — so they are only
418
+ meaningful inside color-mix, which is how every other tinted badge in the
419
+ package consumes them. Handed straight to background and border-color they
420
+ are invalid, and an invalid declaration is DROPPED SILENTLY: the pill
421
+ rendered as bare cyan text with no fill and no border, and no test noticed
422
+ because nothing here was a hex literal. */
423
+ background: color-mix(in srgb, var(--ig-accent) var(--ig-tint-fill), transparent);
424
+ border: var(--ig-stroke) solid
425
+ color-mix(in srgb, var(--ig-accent) var(--ig-tint-border), transparent);
426
+ border-radius: var(--ig-radius-small);
427
+ padding: var(--ig-space-micro) var(--ig-space-snug);
428
+ }
429
+
430
+ /* ONE SCROLL TRACK, AND EXACTLY ONE SIBLING IS BOUNDED INSIDE IT. This zone
431
+ holds three: §17d's AUDIT REGION, this column's selection detail, and the
432
+ .ig-chrome the mount appends. #177 settled how they share it, and the whole
433
+ of the answer is that the zone stays ONE track while the region alone takes a
434
+ declared share of it (max-height: 50%, scrolling itself past that).
435
+ .ig-inspector and .ig-chrome keep this track's own scrolling and declare no
436
+ share, so neither can be clipped by any height.
437
+
438
+ THE BOUNDED SIBLING IS A REGION NOW, AND THAT IS #177'S RULE KEPT RATHER THAN
439
+ RESTATED. §17d has two surfaces — the findings panel and, since the fourth
440
+ class moved out of it, the refused block (audit/refused.ts) — and bounding
441
+ each at half the column lets the two of them take ALL of it, which is worse
442
+ than the state #177 fixed rather than a smaller version of it. So
443
+ workspace/render.ts wraps both in .ig-audit-overlay and the share is declared
444
+ once, here, on the thing the zone actually holds. Neither leaf declares a
445
+ size of its own; audit/styles.ts says so at both.
446
+
447
+ AN EARLIER REVISION OF THIS COMMENT CREDITED audit/styles.ts WITH THE SHARE.
448
+ It never carried it — that file states in as many words that the panel
449
+ "DECLARES NO SIZE OF ITS OWN" — and the misattribution is why the fourth
450
+ sibling nearly arrived unbounded.
451
+
452
+ THAT IS A PROPERTY NOW, NOT A LUCKY DEFAULT, and it is what makes the chrome
453
+ safe. A TWO-PANE VERSION WAS TRIED AND REVERTED: making the zone a flex
454
+ column with overflow: hidden did give the audit and the selection independent
455
+ scroll — and it CLIPPED the mount's chrome, a third sibling holding the
456
+ target search's lower matches, the cancel button and the key legend, all of
457
+ which lost the scrolling the zone used to give them. The lesson was read as
458
+ "treat the chrome as a third pane"; it is better read as "do not make the
459
+ zone a multi-pane layout at all". Bounding one sibling needs no panes.
460
+
461
+ WHY THE PANEL IS THE ONE THAT IS BOUNDED. It is the only GLOBAL member of a
462
+ selection-scoped zone, and renderWorkspace draws it FIRST — so unbounded, it
463
+ is the one sibling whose length pushes a detail the reader just asked for
464
+ below the fold. The selection detail and the chrome are both about what the
465
+ reader is doing; the audit is about the document. §17a fixes the workspace at
466
+ three zones plus a header, so there is nowhere else ambient for it to be, and
467
+ a share is what makes it affordable where it has to live. */
135
468
  .ig-zone[data-zone='inspector'] {
136
469
  grid-area: inspector;
137
470
  overflow-y: auto;
138
471
  border-left: var(--ig-stroke) solid var(--ig-line);
139
472
  }
140
473
 
474
+
475
+
476
+ /* THE REGION SCROLLS; ITS MEMBERS DO NOT SHRINK. Both leaves declare
477
+ min-height: 0 — correct for each of them, because each was once the scroll
478
+ container itself — and in a bounded flex column that is exactly the licence
479
+ to squeeze them below their content. Measured in a browser rather than
480
+ reasoned about: with a refusal and five findings the block was laid out 54px
481
+ tall around 131px of content, and since a leaf's own overflow is visible it
482
+ PAINTED OVER the panel underneath it. Nothing threw, no test failed, and the
483
+ markup was correct — the two surfaces were simply drawn on top of each other.
484
+
485
+ flex-shrink RATHER THAN A HEIGHT ON EITHER LEAF, which is the same layering
486
+ the share above follows: how the region's members divide it is a fact about
487
+ the region, and a leaf that sized itself here would be wrong in every other
488
+ composition. The region's own max-height is what bounds the pair; this is
489
+ what makes the overflow it declares actually reachable. */
490
+ .ig-zone[data-zone='canvas'] .ig-audit-overlay > * {
491
+ flex-shrink: 0;
492
+ }
493
+
494
+
495
+ /* §17d'S PANEL, AS AN OVERLAY OVER THE CANVAS.
496
+
497
+ RULINGS.md §3: "a transient overlay anchored to the header count, floating
498
+ over the canvas, 520px wide, dismissed on Escape." It was a child of the
499
+ INSPECTOR zone until now, sharing that column's height with the selection —
500
+ §17a fixes three zones and none of them is the audit's, so it had nowhere to
501
+ go and took a share of the one next to it. At six findings the inspector was
502
+ pushed below the fold.
503
+
504
+ 520px IS DESIGN'S NUMBER, not this package's, which is why it is written as
505
+ one rather than derived from a token. It is expressed in ch units against the
506
+ type scale so it survives a theme that changes the font size, and clamped to
507
+ the zone so a narrow canvas cannot push it over the rail — the constraint §3
508
+ states outright.
509
+
510
+ ANCHORED TOP-RIGHT, under the header count it belongs to. The count sits at
511
+ the right end of the header band, so the panel hangs from that corner and the
512
+ eye travels the shortest distance from the control to what it opened.
513
+
514
+ IT PAINTS OVER THE GRAPH, which is what an overlay is: z-index above the
515
+ canvas content, and its own raised surface and shadow so the graph beneath
516
+ reads as behind rather than as a background it is part of.
517
+
518
+ --ig-elevation-overlay IS THE TOKEN FOR EXACTLY THIS, and the first draft
519
+ wrote a literal rgb() instead. The package's own guard caught it, correctly:
520
+ the theme's two shadows are deliberately the only values carrying a colour,
521
+ so a host retheming owns them and a literal here would be invisible to that.
522
+
523
+ SCOPED TO THE ZONE, and every declaration is in this one rule. Two reasons,
524
+ and the second is the sheet's own standing rule: an absolutely positioned
525
+ .ig-audit-overlay outside this zone has no containing block to hang from and
526
+ would anchor to the page, and the panel is a public export a consumer can
527
+ render standalone. The same argument the inspector's old share rule made. */
528
+ /* BORDER-BOX, AND IT IS LOAD-BEARING. There IS a universal reset in this
529
+ codebase and it does not reach here: viewer/src/styles.ts scopes it to
530
+ .ig-viewer descendants, and this panel is a sibling of the rail's viewer
531
+ rather than inside it. Without it the cap is measured on the CONTENT box and
532
+ the panel's own padding and border push the drawn box past it. Nothing looks
533
+ broken.
534
+
535
+ THE PANEL SCROLLS, NOT THE LIST INSIDE IT. Scrolling the list would pin the
536
+ panel's head, which is the wrong trade twice over. The ambient count 17d
537
+ fixes is the WORKSPACE HEADER's, drawn in the header grid area and outside
538
+ this zone entirely - it already never moves, so a second pinned count buys
539
+ nothing. And the list has zero horizontal padding, so making it the scroll
540
+ container computes its overflow-x to auto and CLIPS the focus ring on every
541
+ card's control: an outline is ink overflow, so it is cut rather than
542
+ scrolled to. Each leaf's own padding gives those rings room. */
543
+ .ig-zone[data-zone='canvas'] .ig-audit-overlay {
544
+ box-sizing: border-box;
545
+ display: flex;
546
+ flex-direction: column;
547
+ min-height: 0;
548
+ overflow-y: auto;
549
+ position: absolute;
550
+ z-index: 3;
551
+ top: var(--ig-space-tight);
552
+ right: var(--ig-space-tight);
553
+ width: min(calc(var(--ig-char-width) * 66.67), calc(100% - var(--ig-space-tight) * 2));
554
+ max-height: calc(100% - var(--ig-space-tight) * 2);
555
+ /* --ig-surface IS THE RAISED ONE. The first draft wrote --ig-raised, which
556
+ does not exist; the sheet's own guard listed it as a property nothing
557
+ resolves, and an unresolved custom property is not an error in CSS — it
558
+ computes to nothing and the panel would have been transparent over the
559
+ graph. */
560
+ background: var(--ig-surface);
561
+ border: var(--ig-stroke) solid var(--ig-line);
562
+ border-radius: var(--ig-radius);
563
+ box-shadow: var(--ig-elevation-overlay);
564
+ }
565
+
141
566
  /* THE AMBIENT LEFT-BAR. A 2px gold rule on the affected row and nothing else —
142
567
  no fill, no icon, no badge. §17a's whole ask is that encoding errors stay
143
568
  visible while you work rather than pulling you out of it.
@@ -162,8 +587,12 @@ export const workspaceStylesheet = `
162
587
 
163
588
  ONE TREATMENT FOR ALL FOUR SEVERITIES, which is also a correction. §17d
164
589
  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
590
+ row" — and puts the severity distinction in the findings list, which
591
+ audit/panel.ts now draws. (It read "which is host-side" until that panel
592
+ existed; the reasoning is unaffected — the distinction still belongs to the
593
+ list rather than to this bar — but the list is the package's now.)
594
+
595
+ The misleading variant this replaces gave the LEAST urgent
167
596
  severity ("clearing is bookkeeping, not urgency") the alarm colour while
168
597
  every other finding got cyan, inverting the design it came from. */
169
598
  .ig-zone[data-zone='rail'] [data-ig-audit] {
@@ -293,6 +722,16 @@ export const workspaceStylesheet = `
293
722
  gap: var(--ig-space-tight);
294
723
  }
295
724
 
725
+ /* §17a PUTS '+ add' ON THE HEADING'S ROW, opposite the heading. The list
726
+ between them is unbounded, so a control after it drifts down the panel as a
727
+ subject gains relationships; here its position does not depend on content. */
728
+ .ig-inspector-relationships-head {
729
+ display: flex;
730
+ align-items: baseline;
731
+ justify-content: space-between;
732
+ gap: var(--ig-space-tight);
733
+ }
734
+
296
735
  /* SECTION 17a DRAWS EVERY INSPECTOR HEADING IN CAPS -- WHY RANK 2,
297
736
  RELATIONSHIPS, ADD RELATIONSHIP -- so the treatment is shared rather than
298
737
  spelled per heading. Tracking widens with the caps because letterforms at a
@@ -329,10 +768,17 @@ export const workspaceStylesheet = `
329
768
  }
330
769
 
331
770
 
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
771
+ /* THE PANEL'S TWO QUIET BUTTONS SHARE ONE TREATMENT — clear and cancel. They
772
+ are the same affordance at two moments (leave this selection, abandon the
773
+ draft) and drawing them two ways would invent a hierarchy the design does
774
+ not state.
775
+
776
+ '+ add' WAS THE THIRD AND HAS LEFT THE SET, because frame 17a moved it and
777
+ redrew it: on the relationships header row it is unbordered accent text with
778
+ the key beside it, not a filled box. That is a hierarchy the design DOES
779
+ state — a header affordance reads differently from a control in the panel
780
+ body — so matching it here is following the frame rather than inventing a
781
+ difference. Its focus ring is still shared, below. The destructive controls are
336
782
  deliberately NOT in this set: a row's '✕' is a glyph in the row's own slot,
337
783
  and the mount's delete button carries its own danger treatment.
338
784
 
@@ -344,7 +790,6 @@ export const workspaceStylesheet = `
344
790
  baseline that rule's own comment says it is there to get. Declared where each
345
791
  is true instead. */
346
792
  .ig-inspector-clear,
347
- .ig-inspector-addbutton,
348
793
  .ig-inspector-cancel {
349
794
  background: var(--ig-surface-2);
350
795
  color: var(--ig-text-body);
@@ -359,10 +804,42 @@ export const workspaceStylesheet = `
359
804
  .ig-inspector-clear:focus-visible,
360
805
  .ig-inspector-addbutton:focus-visible,
361
806
  .ig-inspector-cancel:focus-visible {
807
+ /* '+ add' keeps the shared focus ring though it left the shared fill: a
808
+ focus indicator is an accessibility guarantee, not a visual register. */
362
809
  outline: var(--ig-focus-ring) solid var(--ig-focus);
363
810
  outline-offset: var(--ig-space-tight);
364
811
  }
365
812
 
813
+ /* §17a's '+ add', AS THE FRAME DRAWS IT: accent text on the header row, no box.
814
+ It is a header affordance rather than a control in the panel body, and the
815
+ frame states that difference by removing the border and the fill rather than
816
+ by moving it alone. */
817
+ .ig-inspector-addbutton {
818
+ display: inline-flex;
819
+ align-items: baseline;
820
+ gap: var(--ig-space-micro);
821
+ background: none;
822
+ border: none;
823
+ border-radius: var(--ig-radius);
824
+ padding: 0;
825
+ color: var(--ig-accent);
826
+ font-family: var(--ig-font-ui);
827
+ font-size: var(--ig-font-size-small);
828
+ cursor: pointer;
829
+ }
830
+
831
+ /* THE KEY, QUIETER THAN THE ACT IT NAMES. It is a hint about how to reach the
832
+ control, not part of the control's label, so it takes the muted tone the
833
+ panel gives every line that states a fact rather than offering an act.
834
+ UPPERCASED HERE, NEVER IN THE MARKUP: the binding table stores the lowercase
835
+ key a press normalizes to, and drawing 'R' is presentation. A second,
836
+ uppercase copy in the package would be the drift RELATE_KEY exists to end. */
837
+ .ig-inspector-addkey {
838
+ color: var(--ig-text-muted);
839
+ font-family: var(--ig-font-mono);
840
+ text-transform: uppercase;
841
+ }
842
+
366
843
  .ig-relationship-list {
367
844
  list-style: none;
368
845
  margin: 0;
@@ -605,18 +1082,30 @@ export const workspaceStylesheet = `
605
1082
  border-style: dotted;
606
1083
  }
607
1084
 
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. */
1085
+ /* THE KIND STEP'S OWN BOX — the heading, the numbered list and the cancel that
1086
+ withdraws from it.
1087
+
1088
+ IT NO LONGER HOLDS '+ add'. This box was once "one place the reader returns
1089
+ to", holding whichever of the two create steps was live; frame 17a splits
1090
+ them by zone, putting '+ add' on the relationships header row and leaving the
1091
+ kinds here under a heading of their own. The two steps are still exclusive —
1092
+ 'createStep' answers one of them — but they are no longer drawn in one
1093
+ place, so this rule is about the kind step alone. */
611
1094
  .ig-inspector-add {
612
1095
  display: flex;
613
1096
  flex-direction: column;
614
1097
  gap: var(--ig-space-tight);
615
1098
  }
616
1099
 
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,
1100
+ /* Sized by its own word rather than stretched across the column — see the
1101
+ quiet-button rule above, which deliberately does not carry this.
1102
+
1103
+ '+ add' IS NO LONGER LISTED HERE, and dropping it was required rather than
1104
+ tidy. This declaration is scoped to '.ig-inspector-add', a COLUMN; '+ add'
1105
+ now sits in the relationships header, a ROW, where 'align-self' beats the
1106
+ container's 'align-items' and would top-align the control against the
1107
+ heading — the exact bug the '.ig-inspector-clear' comment above records
1108
+ having already been fixed once. */
620
1109
  .ig-inspector-cancel {
621
1110
  align-self: flex-start;
622
1111
  }
@@ -641,14 +1130,25 @@ export const workspaceStylesheet = `
641
1130
  gap: var(--ig-space-micro);
642
1131
  }
643
1132
 
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. */
1133
+ /* Full width, so the whole entry is the hit target.
1134
+
1135
+ THE DIGIT IS A CHIP AT THE ROW'S END, which is frame 17a's device and
1136
+ REVERSES the placement #144 shipped. That decision was made in the same §17a
1137
+ pass and with this frame available, so it is overturned on the merits rather
1138
+ than as an oversight: what made the old left-hand column read as a set of
1139
+ KEYS was vertical alignment, which a label long enough to wrap breaks, while
1140
+ a bordered chip carries the key-cap signal on each row by itself and cannot
1141
+ come apart. Reversing a same-campaign decision is called out on the pull
1142
+ request so autnmy/issuegraph#147 rules rather than the implementer.
1143
+
1144
+ CENTRED, NOT BASELINE-ALIGNED. A chip has its own border and padding, so on a
1145
+ baseline it sits low against the glyph and the label; the frame's row centres
1146
+ all three. */
647
1147
  .ig-kind-option {
648
1148
  width: 100%;
649
1149
  display: flex;
650
1150
  gap: var(--ig-space-tight);
651
- align-items: baseline;
1151
+ align-items: center;
652
1152
  background: var(--ig-surface);
653
1153
  color: var(--ig-text-body);
654
1154
  border: var(--ig-stroke) solid var(--ig-line);
@@ -665,7 +1165,18 @@ export const workspaceStylesheet = `
665
1165
  outline-offset: var(--ig-space-tight);
666
1166
  }
667
1167
 
1168
+ /* THE CHIP TAKES THE SLACK BEFORE IT, rather than the label being told to grow.
1169
+ The label is drawn by layer 1's shared 'glyphAndLabel' and carries no class of
1170
+ its own, so selecting it from here means selecting by exclusion — a rule that
1171
+ silently stops matching the day that helper adds an element. An auto margin
1172
+ is stated on the element this rule already owns, needs nothing of the label,
1173
+ and puts the chip at the row's end whatever precedes it. */
668
1174
  .ig-kind-digit {
1175
+ flex: 0 0 auto;
1176
+ margin-left: auto;
1177
+ border: var(--ig-stroke) solid var(--ig-line);
1178
+ border-radius: var(--ig-radius);
1179
+ padding: 0 var(--ig-space-micro);
669
1180
  font-family: var(--ig-font-mono);
670
1181
  color: var(--ig-text-muted);
671
1182
  }
@@ -842,5 +1353,406 @@ export const workspaceStylesheet = `
842
1353
  font-size: var(--ig-font-size-small);
843
1354
  color: var(--ig-text-muted);
844
1355
  }
1356
+
1357
+ /* §17c ON THE ROW: the tint, the third column, and the chip in place.
1358
+
1359
+ THE TINT IS DRAWN FROM AN ATTRIBUTE rather than from a class the row is
1360
+ given, which is audit/surface.ts's precedent and its reason: layer 1 owns
1361
+ the row's markup, so layer 2 says WHICH KIND and the stylesheet says what
1362
+ that kind looks like. A row the edit did not touch carries no attribute at
1363
+ all, so none of these rules can reach it, and "unaffected rows are left
1364
+ completely alone" is structural rather than asserted.
1365
+
1366
+ THESE REACH LAYER 1'S OWN CLASSES, and that is a deliberate exception of
1367
+ exactly the shape reevaluate/styles.ts already declares for the greyed rail:
1368
+ only from INSIDE this surface's own root, and only for a state layer 1 does
1369
+ not model. The viewer draws a row; whether the last edit moved that row is a
1370
+ fact only the store knows, so the mark belongs to whoever knows it.
1371
+
1372
+ BOTH ROW SHAPES, because a delta can land on either. A ranked row is an
1373
+ .ig-slot; an excluded one — the shape an issue takes when an edit turns it
1374
+ into a duplicate — is an .ig-footer-row, and it is still navigable, so
1375
+ reevaluateView still places its chip. An earlier revision listed .ig-slot
1376
+ only, so a left row got its chip and its extended name and no ground.
1377
+
1378
+ EVERY VALUE deltaKind CAN EMIT, and that list is readiness, then a
1379
+ movement's direction, then a presence: promoted, newly-held, up, down,
1380
+ entered, left. An earlier revision keyed a rule on 'absent', which is not a
1381
+ value this code has ever produced — presence is 'entered' | 'left' — so that
1382
+ rule matched nothing while looking like the neutral case, and a left row
1383
+ took the READY tint from the bare fallback below. Named exhaustively now
1384
+ rather than defaulted, so a seventh value renders untinted rather than
1385
+ wrong.
1386
+
1387
+ BACKGROUND-COLOR, NEVER THE SHORTHAND. the background shorthand resets background-image
1388
+ to none, and layer 1 draws the HELD row's hatch as a repeating-linear-
1389
+ gradient on exactly that property. These rules are more specific and load
1390
+ after it, so the shorthand silently took the hatch off any row that was both
1391
+ held and changed — which is not a corner: newly-held is the delta that
1392
+ co-occurs with data-held='true' by definition, so the most common overlap
1393
+ lost the held channel entirely. The longhand leaves the gradient alone.
1394
+
1395
+ COLOUR-MIX ON THE TOKEN THE KIND ALREADY NAMES, at the strength layer 1
1396
+ tints its own rows with. Inventing --ig-row-promoted would put a hue in this
1397
+ package that a host retheming the station colours could not move in step,
1398
+ and inventing a percentage would make this tint drift from the unit tint
1399
+ layer 1 draws with --ig-tint-unit. */
1400
+ .ig-workspace .ig-slot[data-ig-delta='promoted'],
1401
+ .ig-workspace .ig-slot[data-ig-delta='up'],
1402
+ .ig-workspace .ig-slot[data-ig-delta='entered'],
1403
+ .ig-workspace .ig-footer-row[data-ig-delta='promoted'],
1404
+ .ig-workspace .ig-footer-row[data-ig-delta='up'],
1405
+ .ig-workspace .ig-footer-row[data-ig-delta='entered'] {
1406
+ background-color: color-mix(in srgb, var(--ig-station-ready) var(--ig-tint-unit), transparent);
1407
+ }
1408
+
1409
+ .ig-workspace .ig-slot[data-ig-delta='newly-held'],
1410
+ .ig-workspace .ig-slot[data-ig-delta='down'],
1411
+ .ig-workspace .ig-footer-row[data-ig-delta='newly-held'],
1412
+ .ig-workspace .ig-footer-row[data-ig-delta='down'] {
1413
+ background-color: color-mix(in srgb, var(--ig-station-held) var(--ig-tint-unit), transparent);
1414
+ }
1415
+
1416
+ /* A row the edit took OUT of the order. Neutral rather than held: it is not
1417
+ waiting on anything, it is simply no longer ranked. */
1418
+ .ig-workspace .ig-slot[data-ig-delta='left'],
1419
+ .ig-workspace .ig-footer-row[data-ig-delta='left'] {
1420
+ background-color: color-mix(in srgb, var(--ig-text-muted) var(--ig-tint-unit), transparent);
1421
+ }
1422
+
1423
+ /* THE THIRD COLUMN IS WHAT PUTS THE CHIP AT THE ROW'S TRAILING EDGE.
1424
+
1425
+ The frame draws the row as rank, body, chip — three columns, the last
1426
+ sized to its content. Layer 1's own rule is a TWO-column grid, so a chip
1427
+ appended as a third child wrapped onto an implicit second row and sat under
1428
+ the rank. That was visible in the first capture of this pair, and it is why
1429
+ this rule is a column template rather than a margin: in a grid, an auto
1430
+ inline margin cannot move an item that is in the wrong row to begin with.
1431
+
1432
+ ONLY ON A ROW THAT HAS A CHIP, so a row with no delta keeps layer 1's own
1433
+ template untouched. AND ONLY ON .ig-slot: a footer row is not that grid, so
1434
+ naming it here would set a column template on a box that has none. */
1435
+ .ig-workspace .ig-slot[data-ig-delta] {
1436
+ grid-template-columns: var(--ig-rank-column) 1fr auto;
1437
+ }
1438
+
1439
+ /* A FOOTER ROW IS A FLEX BOX, NOT THE THREE-COLUMN GRID.
1440
+
1441
+ A ranked row reaches the trailing edge by the column template above. An
1442
+ excluded or tracker-held row is layer 1's .ig-footer-row — display:flex —
1443
+ so a chip appended to it simply follows the content it was appended after,
1444
+ and the frame puts it at the row's end. An auto inline margin is the flex
1445
+ idiom for that, and it is the right tool HERE for the same reason it was the
1446
+ wrong one on the grid: it moves an item within its line, and on the grid the
1447
+ item was in the wrong line to begin with. */
1448
+ .ig-workspace .ig-footer-row > .ig-delta-chip[data-placed] {
1449
+ margin-inline-start: auto;
1450
+ }
1451
+
1452
+ /* §17c's "write landed · order computing": the PREVIOUS order, held still and
1453
+ greyed one step, with the label saying why.
1454
+
1455
+ THE SAME FILTER, for the reason reevaluate/styles.ts gives where it greys
1456
+ .ig-reevaluate[data-order='held'] .ig-viewer: layer 1 sets color directly on
1457
+ its own descendants, so an inherited colour greys almost nothing, and a list
1458
+ of descendants to override goes stale the first time layer 1 colours
1459
+ something new.
1460
+
1461
+ SCOPED TO THE RAIL ZONE, WHICH THE STANDALONE SURFACE DID NOT HAVE TO BE.
1462
+ There, .ig-viewer is the rail and nothing else. The workspace draws a SECOND
1463
+ viewer in the canvas zone, and greying that one is not a smaller version of
1464
+ the same idea — it is the opposite of what the held state means. What is
1465
+ stale is the ORDER; the canvas is drawing the write states #164 put there,
1466
+ and pending, failed and conflict are told partly in hue. So an in-flight edit
1467
+ would have stripped the colour channel off the very marks that say an edit is
1468
+ in flight. The rail is the only surface whose ranks the store cannot vouch
1469
+ for, so it is the only one greyed. */
1470
+ .ig-workspace[data-order='held'] .ig-zone[data-zone='rail'] .ig-viewer {
1471
+ filter: grayscale(1);
1472
+ }
1473
+
1474
+ /* ── 17k's OTHER TWO LAYOUTS ───────────────────────────────────────────────
1475
+
1476
+ ONE RULE GENERATES ALL THREE, and it is the sentence 17k states outright:
1477
+ THE RAIL NEVER YIELDS. THE CANVAS YIELDS FIRST. THE INSPECTOR YIELDS IN
1478
+ BETWEEN. Because the canvas is already the zone designed to refuse (17f) and
1479
+ the inspector is the edit path that survives without it (17b).
1480
+
1481
+ at or above 1360 390 / 1fr / 330, three zones, as 17a
1482
+ 1120 - 1359 rail unchanged; the inspector lifts to a 330 overlay
1483
+ over the canvas, opened by selection, Escape to dismiss
1484
+ below 1120 rail unchanged; the canvas collapses to a strip that
1485
+ opens full-width on demand; the inspector returns inline
1486
+
1487
+ Below roughly 860 is section 9 territory and out of scope for the package.
1488
+
1489
+ THE RAIL IS THE SAME 50 CHARACTERS IN ALL THREE. That is the whole rule, and
1490
+ it is the one thing to check when touching this block: measured on the demo
1491
+ at 1440, 1280 and 1000 the rail track reads 390 at every one of them. The
1492
+ layout before this shipped read 390 / 698 / 328 at 1440 and 312 / 632 / 312
1493
+ at 1280 — the rail giving up 78 of its 390 to keep a three-column template
1494
+ alive, which is the rule upside down.
1495
+
1496
+ WHY THESE ARE RANGE CONDITIONS AND NOT max-width/min-width PAIRS. A
1497
+ container's inline size is NOT AN INTEGER. Written as (min-width: 1360px)
1498
+ and (max-width: 1359px), a box measured at 1359.5 satisfies NEITHER — so the
1499
+ base fallback comes back, the rail drops to its 40-character measure, and
1500
+ the one rule 17k states outright is broken at exactly the widths a resize
1501
+ drags through. The same hole sits at 1119.5. 'width < 1360' and
1502
+ 'width >= 1360' are complements with nothing between them, which is the
1503
+ property the layouts need and the only reason the syntax changed.
1504
+
1505
+ THE BASE STAYS THE THREE-ZONE TEMPLATE, because a surface rendered WITHOUT a
1506
+ mount has no container at all and no query fires: base-is-narrow would hand
1507
+ a static page a collapsed canvas with a control nothing is listening to. So
1508
+ the wide layout is the floor and the narrow ones are carved off it, widest
1509
+ first.
1510
+
1511
+ THESE SIT AT THE FOOT OF THE SHEET, WHICH IS LOAD-BEARING TWICE. The narrow
1512
+ block must come after the middle one to win where both match; and the zones'
1513
+ own rules have to come first, or a regex looking for the rule that defines
1514
+ .ig-zone[data-zone='inspector'] finds an override instead. That hazard is
1515
+ recorded further up this sheet and has already bitten once in layer 1.
1516
+
1517
+ NEITHER LAYOUT IS THE STYLESHEET'S ALONE. The lifted inspector opens on
1518
+ selection and dismisses on Escape, and the strip opens on a press — so
1519
+ host.ts holds two fields and render.ts publishes them on the surface
1520
+ root as data-canvas and data-inspector. What is NOT in the host is the
1521
+ width: nothing in the package asks how wide it is, per 17k's own note that
1522
+ a package which reads the window cannot be dropped into someone else's
1523
+ settings page. Each attribute is read only by the block that has a use for
1524
+ it, and means nothing at the other widths. */
1525
+
1526
+ /* 17k's STRIP CONTROL, RENDERED ALWAYS AND DRAWN ONLY WHEN NARROW.
1527
+ render.ts cannot know its own width, so the button is always in the
1528
+ markup and this is what keeps it off the other two layouts — display:none
1529
+ rather than visibility, so it is not a tab stop where it has nothing to do.
1530
+ The two narrow states size it themselves, below. */
1531
+ .ig-workspace .ig-canvas-strip {
1532
+ display: none;
1533
+ box-sizing: border-box;
1534
+ border: var(--ig-stroke) solid var(--ig-line);
1535
+ background: var(--ig-surface);
1536
+ color: var(--ig-text-body);
1537
+ font-family: var(--ig-font-ui);
1538
+ font-size: var(--ig-font-size-small);
1539
+ padding: var(--ig-space-tight);
1540
+ cursor: pointer;
1541
+ }
1542
+
1543
+ .ig-workspace .ig-canvas-strip:focus-visible {
1544
+ outline: var(--ig-focus-ring) solid var(--ig-focus);
1545
+ outline-offset: var(--ig-space-tight);
1546
+ }
1547
+
1548
+ /* ONE KEY PER SURFACE, AND THE CANVAS KEEPS IT.
1549
+
1550
+ Both zones hold a viewer, so both draw the grammar legend, and this surface
1551
+ showed the same five line styles twice side by side at the same moment. It is
1552
+ the defect chrome: false already answers one line higher — §16's panel has
1553
+ ONE header — and render.ts records the reasoning for which zone yields:
1554
+ four of the five entries are LINE styles and the rail draws no lines, only
1555
+ badges, so the key belongs beside the strokes it is about. It is the cheaper
1556
+ zone to spend it in, too: one or two wrapped rows of a wide column against
1557
+ four rows of a 390 rail the order needs.
1558
+
1559
+ REACHING A VIEWER CLASS, FROM INSIDE THIS SURFACE'S OWN ROOT, FOR A STATE
1560
+ THAT PACKAGE DOES NOT MODEL — which is the exemption reevaluate/styles.ts
1561
+ already states and uses for the greyed rail. The viewer cannot answer this:
1562
+ each instance is correct on its own and the duplication only exists in the
1563
+ composition, which is this package's. Nothing here changes how a legend looks
1564
+ anywhere else, and the markup is untouched — the rail still RENDERS its key,
1565
+ so the two rules below can hand it back without re-rendering anything. */
1566
+ .ig-workspace[data-canvas-legend='drawn'] .ig-zone[data-zone='rail'] .ig-legend {
1567
+ display: none;
1568
+ }
1569
+
1570
+ /* THE MIDDLE LAYOUT: the inspector lifts, and the rail does not move.
1571
+
1572
+ IT IS PLACED IN THE CANVAS'S GRID AREA, NOT POSITIONED OVER IT. An absolute
1573
+ overlay would need an offset chosen so it clears the rail, and an offset is
1574
+ a number that is right until someone changes a track. A grid area is not:
1575
+ the rail is a DIFFERENT area, so an item placed in the canvas's cannot reach
1576
+ it however wide it gets. Section 17d's audit overlay is positioned on the
1577
+ same argument one surface over, and this is that argument without the
1578
+ position.
1579
+
1580
+ 42 CHARACTERS, THE SAME MEASURE THE WIDE LAYOUT GIVES THE INSPECTOR — 17k
1581
+ lifts the panel, it does not resize it. At the shipped --ig-char-width that
1582
+ is 327.6 against 17k's 330, the 2.4 the wide block already accounts for.
1583
+
1584
+ OPAQUE AND RAISED, because it now has the canvas underneath it rather than a
1585
+ border between them. --ig-elevation-overlay is the token for exactly this
1586
+ and --ig-surface is the raised ground; the zone keeps its own border-left
1587
+ from the base rule, which reads as the panel's leading edge. */
1588
+ @container ig-workspace (width < 1360px) {
1589
+ .ig-workspace {
1590
+ grid-template-areas:
1591
+ 'header header'
1592
+ 'rail canvas';
1593
+ grid-template-columns:
1594
+ calc(var(--ig-char-width) * 50)
1595
+ 1fr;
1596
+ }
1597
+
1598
+ .ig-workspace .ig-zone[data-zone='inspector'] {
1599
+ grid-area: canvas;
1600
+ justify-self: end;
1601
+ inline-size: calc(var(--ig-char-width) * 42);
1602
+ z-index: 1;
1603
+ background: var(--ig-surface);
1604
+ box-shadow: var(--ig-elevation-overlay);
1605
+ }
1606
+
1607
+ /* OPENED BY SELECTION, DISMISSED ON ESCAPE — 17k's own words, and both
1608
+ halves arrive as this one attribute. render.ts writes 'lifted' only when
1609
+ something is SELECTED and the reader has not dismissed it since, so an
1610
+ empty "nothing selected" card never floats over the canvas. */
1611
+ .ig-workspace[data-inspector='dismissed'] .ig-zone[data-zone='inspector'] {
1612
+ display: none;
1613
+ }
1614
+ }
1615
+
1616
+ /* THE NARROW LAYOUT: the canvas collapses, the inspector comes back inline,
1617
+ and the rail still does not move.
1618
+
1619
+ THE INSPECTOR IS RESTORED DECLARATION BY DECLARATION rather than by scoping
1620
+ the block above to a band, because the sheet's guard has no band to scope to.
1621
+ Each line here answers one line there, on the same selector so source order
1622
+ settles it — that is the price of single-sided conditions, paid in full and
1623
+ in one place.
1624
+
1625
+ THE STRIP IS 4 CHARACTERS. The canvas is the zone designed to refuse (17f),
1626
+ so what it yields is everything except the handle that brings it back; the
1627
+ inspector takes 1fr, which at 1000 is 550 and at 860 is 418 — both above the
1628
+ 330 the wide layout gives it, so the zone that yields in between is not
1629
+ yielding here at all. That is the order the rule names. */
1630
+ @container ig-workspace (width < 1120px) {
1631
+ /* THE RAIL TAKES THE KEY BACK, BECAUSE THERE IS NO CANVAS TO CARRY IT. Below
1632
+ this width §17k collapses the canvas to a four-character strip, so the zone
1633
+ the rule above defers to is not on screen — and the station fills the rail
1634
+ draws itself would have no key at all. display: flex rather than revert
1635
+ because that is what .ig-legend is; the value has to be restated, which is
1636
+ the price of a single-sided condition and is paid here in one place. */
1637
+ .ig-workspace[data-canvas-legend='drawn'] .ig-zone[data-zone='rail'] .ig-legend {
1638
+ display: flex;
1639
+ }
1640
+
1641
+ .ig-workspace {
1642
+ grid-template-areas:
1643
+ 'header header header'
1644
+ 'rail canvas inspector';
1645
+ grid-template-columns:
1646
+ calc(var(--ig-char-width) * 50)
1647
+ calc(var(--ig-char-width) * 4)
1648
+ 1fr;
1649
+ }
1650
+
1651
+ .ig-workspace .ig-zone[data-zone='inspector'] {
1652
+ grid-area: inspector;
1653
+ justify-self: stretch;
1654
+ inline-size: auto;
1655
+ z-index: auto;
1656
+ background: none;
1657
+ box-shadow: none;
1658
+ }
1659
+
1660
+ /* THE DISMISSAL MEANS NOTHING HERE, and saying so is the undo of the middle
1661
+ block's only conditional rule. The inspector is a zone again, so there is
1662
+ no overlay to have dismissed. */
1663
+ .ig-workspace[data-inspector='dismissed'] .ig-zone[data-zone='inspector'] {
1664
+ display: block;
1665
+ }
1666
+
1667
+ .ig-workspace[data-canvas='strip'] .ig-canvas-strip {
1668
+ display: block;
1669
+ writing-mode: vertical-rl;
1670
+ inline-size: 100%;
1671
+ block-size: 100%;
1672
+ overflow: hidden;
1673
+ white-space: nowrap;
1674
+ }
1675
+
1676
+ /* COLLAPSED MEANS GONE, NOT CLIPPED. The zone is overflow:auto, so leaving
1677
+ the graph in a 31-wide track would keep every node in the tab order behind
1678
+ a scrollbar the reader cannot use — and a keyboard reader would walk a
1679
+ canvas nobody can see. display:none is what takes them out of the tree,
1680
+ and it is reversible in one attribute.
1681
+
1682
+ TWO EXCEPTIONS, AND NEITHER IS THE CANVAS. The strip is the handle that
1683
+ brings the zone back. 17d's audit overlay is a sibling of the graph rather
1684
+ than part of it — it is anchored to the HEADER COUNT, which is drawn at
1685
+ every width, so a collapsed canvas taking it down would remove a surface
1686
+ the reader opened from a control that is still there. */
1687
+ .ig-workspace[data-canvas='strip']
1688
+ .ig-zone[data-zone='canvas']
1689
+ > :not(.ig-canvas-strip):not(.ig-audit-overlay) {
1690
+ display: none;
1691
+ }
1692
+
1693
+ /* AND THE OVERLAY HAS TO ESCAPE THE STRIP IT HANGS OFF.
1694
+
1695
+ 17d positions it against the canvas ZONE, which is what keeps it off the
1696
+ rail without a magic offset. That argument still holds here and the
1697
+ ARITHMETIC does not: the zone is four characters wide, so the panel's own
1698
+ 'width: min(66.67ch, 100% - gap)' resolves to the strip, and 'right' would
1699
+ anchor a wider one leftwards — across the rail, which is the one thing 17k
1700
+ says an overlay must never do.
1701
+
1702
+ SO THE ANCHOR FLIPS AND THE CLAMP GOES. Anchored at the zone's leading
1703
+ edge it opens away from the rail, into the room the inspector is using —
1704
+ over it, which is what an overlay is for. The zone stops clipping while it
1705
+ is a strip, which costs nothing: every other child is display:none above,
1706
+ so there is nothing else left to spill.
1707
+
1708
+ MEASURED at a 1000 mount: the zone's leading edge is 419, the rail ends at
1709
+ 407, and 520 of panel fits in the 598 that remains. Below about 900 it
1710
+ runs past the trailing edge, which is the band 17k already hands to
1711
+ section 9. */
1712
+ .ig-workspace[data-canvas='strip'] .ig-zone[data-zone='canvas'] {
1713
+ overflow: visible;
1714
+ }
1715
+
1716
+ .ig-workspace[data-canvas='strip'] .ig-zone[data-zone='canvas'] .ig-audit-overlay {
1717
+ left: 0;
1718
+ right: auto;
1719
+ width: calc(var(--ig-char-width) * 66.67);
1720
+ max-height: 100%;
1721
+ /* THE SHADOW GOES WITH THE CLIPPING, and that pairing is the whole point.
1722
+ Section 3 forbids this panel reaching the rail ABSOLUTELY, and it is a
1723
+ rule about what a reader SEES — a soft edge falling across the order
1724
+ breaks it exactly as a border would. Everywhere else the zone's own
1725
+ overflow is what guarantees that; here the zone is deliberately not
1726
+ clipping, so the one thing that could paint outside the border box is
1727
+ removed instead. The panel still reads as raised: it keeps its ground
1728
+ and its border, over a canvas that is four characters wide and drawing
1729
+ nothing. */
1730
+ box-shadow: none;
1731
+ }
1732
+
1733
+ /* OPENED FULL-WIDTH ON DEMAND — 17k's words — and full-width is measured
1734
+ from the rail, which still does not yield. The inspector stands down while
1735
+ it is open, which is the one place this layout borrows from the middle
1736
+ one: the canvas is transient here in the way the panel is there. */
1737
+ .ig-workspace[data-canvas='open'] {
1738
+ grid-template-areas:
1739
+ 'header header'
1740
+ 'rail canvas';
1741
+ grid-template-columns:
1742
+ calc(var(--ig-char-width) * 50)
1743
+ 1fr;
1744
+ }
1745
+
1746
+ .ig-workspace[data-canvas='open'] .ig-zone[data-zone='inspector'] {
1747
+ display: none;
1748
+ }
1749
+
1750
+ /* The same control closes it, so it stops being a strip and reads as a
1751
+ button above the canvas it opened. */
1752
+ .ig-workspace[data-canvas='open'] .ig-canvas-strip {
1753
+ display: block;
1754
+ justify-self: start;
1755
+ }
1756
+ }
845
1757
  `;
846
1758
  //# sourceMappingURL=styles.js.map