@issuegraph/editor 0.10.0 → 0.22.1

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 (106) 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 +113 -6
  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 +53 -13
  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/index.d.ts +38 -15
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +34 -13
  36. package/dist/index.js.map +1 -1
  37. package/dist/overlay/render.d.ts +11 -3
  38. package/dist/overlay/render.d.ts.map +1 -1
  39. package/dist/overlay/render.js +22 -6
  40. package/dist/overlay/render.js.map +1 -1
  41. package/dist/overlay/request.d.ts +71 -0
  42. package/dist/overlay/request.d.ts.map +1 -0
  43. package/dist/overlay/request.js +129 -0
  44. package/dist/overlay/request.js.map +1 -0
  45. package/dist/overlay/styles.d.ts +1 -1
  46. package/dist/overlay/styles.d.ts.map +1 -1
  47. package/dist/overlay/styles.js +38 -7
  48. package/dist/overlay/styles.js.map +1 -1
  49. package/dist/reevaluate/parts.d.ts +130 -0
  50. package/dist/reevaluate/parts.d.ts.map +1 -0
  51. package/dist/reevaluate/parts.js +260 -0
  52. package/dist/reevaluate/parts.js.map +1 -0
  53. package/dist/reevaluate/render.d.ts +18 -11
  54. package/dist/reevaluate/render.d.ts.map +1 -1
  55. package/dist/reevaluate/render.js +21 -135
  56. package/dist/reevaluate/render.js.map +1 -1
  57. package/dist/reevaluate/styles.d.ts +1 -1
  58. package/dist/reevaluate/styles.d.ts.map +1 -1
  59. package/dist/reevaluate/styles.js +26 -0
  60. package/dist/reevaluate/styles.js.map +1 -1
  61. package/dist/reevaluate/view.d.ts +16 -1
  62. package/dist/reevaluate/view.d.ts.map +1 -1
  63. package/dist/reevaluate/view.js +21 -1
  64. package/dist/reevaluate/view.js.map +1 -1
  65. package/dist/reevaluate/words.d.ts +7 -0
  66. package/dist/reevaluate/words.d.ts.map +1 -1
  67. package/dist/scale/ladder.d.ts +62 -6
  68. package/dist/scale/ladder.d.ts.map +1 -1
  69. package/dist/scale/ladder.js +119 -8
  70. package/dist/scale/ladder.js.map +1 -1
  71. package/dist/scale/render.d.ts +44 -0
  72. package/dist/scale/render.d.ts.map +1 -1
  73. package/dist/scale/render.js +197 -27
  74. package/dist/scale/render.js.map +1 -1
  75. package/dist/scale/styles.d.ts +11 -5
  76. package/dist/scale/styles.d.ts.map +1 -1
  77. package/dist/scale/styles.js +133 -4
  78. package/dist/scale/styles.js.map +1 -1
  79. package/dist/workspace/host.d.ts +83 -1
  80. package/dist/workspace/host.d.ts.map +1 -1
  81. package/dist/workspace/host.js +197 -4
  82. package/dist/workspace/host.js.map +1 -1
  83. package/dist/workspace/inspector.d.ts +33 -7
  84. package/dist/workspace/inspector.d.ts.map +1 -1
  85. package/dist/workspace/inspector.js +15 -0
  86. package/dist/workspace/inspector.js.map +1 -1
  87. package/dist/workspace/mount.d.ts +23 -1
  88. package/dist/workspace/mount.d.ts.map +1 -1
  89. package/dist/workspace/mount.js +500 -17
  90. package/dist/workspace/mount.js.map +1 -1
  91. package/dist/workspace/rail.d.ts +5 -2
  92. package/dist/workspace/rail.d.ts.map +1 -1
  93. package/dist/workspace/rail.js.map +1 -1
  94. package/dist/workspace/render.d.ts +379 -3
  95. package/dist/workspace/render.d.ts.map +1 -1
  96. package/dist/workspace/render.js +893 -133
  97. package/dist/workspace/render.js.map +1 -1
  98. package/dist/workspace/selection.d.ts +11 -0
  99. package/dist/workspace/selection.d.ts.map +1 -1
  100. package/dist/workspace/selection.js +10 -0
  101. package/dist/workspace/selection.js.map +1 -1
  102. package/dist/workspace/styles.d.ts +1 -1
  103. package/dist/workspace/styles.d.ts.map +1 -1
  104. package/dist/workspace/styles.js +868 -18
  105. package/dist/workspace/styles.js.map +1 -1
  106. package/package.json +3 -3
@@ -33,6 +33,7 @@
33
33
  * There is no transition, no animation and no `@keyframes` here, and there is a
34
34
  * test that says so.
35
35
  */
36
+ import { AUDIT_KIND_ATTRIBUTE } from "./panel.js";
36
37
  import { AUDIT_SEVERITY_ATTRIBUTE } from "./surface.js";
37
38
  export const auditStylesheet = `
38
39
  .ig-audit {
@@ -68,5 +69,432 @@ export const auditStylesheet = `
68
69
  [${AUDIT_SEVERITY_ATTRIBUTE}] {
69
70
  box-shadow: inset var(--ig-stroke-audit) 0 0 0 var(--ig-edge-serialize-with);
70
71
  }
72
+
73
+ /* §17d'S PANEL. Note what it does NOT select on: ${AUDIT_SEVERITY_ATTRIBUTE},
74
+ whose rule immediately above is deliberately unqualified because it lands on a
75
+ row the VIEWER rendered. A chip wearing that attribute would draw the rail's
76
+ gold bar whatever hue the class table gave it, which is the four-hue mapping
77
+ below defeated by one attribute. Chips carry ${AUDIT_KIND_ATTRIBUTE}. */
78
+ /* IT IS THE SELECTION'S PEER, NOT ITS HEADING, so it carries its own padding:
79
+ it is a sibling of .ig-inspector inside the zone rather than a child, and
80
+ that column's padding does not reach it.
81
+
82
+ IT DECLARES NO SIZE OF ITS OWN, AND THAT IS THE LAYERING. Issue 177 gave the
83
+ audit half the inspector column, scrolling itself past that — and since the
84
+ fourth class moved to ./refused.ts the half belongs to the REGION holding both
85
+ surfaces rather than to this panel. Either way it is a fact about SHARING A
86
+ COLUMN, not a fact about the panel, so it is declared by the composition that
87
+ owns the zone. See workspace/styles.ts.
88
+
89
+ The difference is reachable rather than theoretical: renderAuditPanel and
90
+ this stylesheet are both public exports, so a consumer can draw the panel
91
+ outside renderWorkspace. Capping it there would hide findings behind an inner
92
+ scrollbar with half the container left empty and no selection detail to
93
+ reserve the space for. A leaf that sized itself would be wrong in every
94
+ composition but one. */
95
+ .ig-audit-panel {
96
+ border-bottom: var(--ig-stroke) solid var(--ig-line);
97
+ display: flex;
98
+ flex-direction: column;
99
+ font-family: var(--ig-font-ui);
100
+ gap: var(--ig-space-snug);
101
+ min-height: 0;
102
+ padding: var(--ig-space);
103
+ }
104
+
105
+ /* THE RING FOR THE PANEL'S OWN TAB STOP. Unscoped, unlike the sizing above,
106
+ because the tabindex is in this leaf's MARKUP and is therefore carried into
107
+ every composition: a focusable element that shows nothing on focus is a stop
108
+ a keyboard reader lands on blind, wherever it is drawn.
109
+
110
+ INSET, WHICH IS THE VIEWER'S OWN IDIOM for exactly this shape — its sheet
111
+ draws every focus ring at a negative offset. Drawn outward it would sit on
112
+ the panel's border box against the zone's edge and the column's own scroll,
113
+ where the top and bottom of the ring are the first thing clipped. */
114
+ .ig-audit-panel:focus-visible {
115
+ outline: var(--ig-focus-ring) solid var(--ig-focus);
116
+ outline-offset: calc(var(--ig-focus-ring) * -1);
117
+ }
118
+
119
+ .ig-audit-panel-head {
120
+ align-items: center;
121
+ display: flex;
122
+ gap: var(--ig-space-tight);
123
+ }
124
+
125
+ /* THE GOLD IS THE AMBIENT MARK'S, not a fifth hue. §17d names one attention
126
+ colour and the rail bar already spends it; the panel head answering in the
127
+ same gold is what makes the count and the list read as one thing. */
128
+ .ig-audit-mark {
129
+ color: var(--ig-edge-serialize-with);
130
+ font-size: var(--ig-font-size-small);
131
+ }
132
+
133
+ .ig-audit-panel-count {
134
+ color: var(--ig-edge-serialize-with);
135
+ font-family: var(--ig-font-mono);
136
+ font-size: var(--ig-font-size-small);
137
+ font-variant-numeric: tabular-nums;
138
+ }
139
+
140
+ .ig-audit-panel-heading {
141
+ color: var(--ig-edge-serialize-with);
142
+ font-weight: inherit;
143
+ margin: 0;
144
+ font-family: var(--ig-font-mono);
145
+ font-size: var(--ig-font-size-small);
146
+ letter-spacing: var(--ig-tracking-group);
147
+ text-transform: uppercase;
148
+ }
149
+
150
+ /* NO CAP ON THE LIST, AND THAT IS STILL RIGHT — the bound belongs to the PANEL
151
+ above, not here. Three caps were tried on this element (a fixed length, a
152
+ share of a flex column, that share measured on the right box) and each was
153
+ correct about the previous one's defect while the column stayed the thing
154
+ that could not be bounded from in here. It still cannot: the list has no
155
+ padding, so a scroll container here computes overflow-x to auto and clips the
156
+ focus ring on every card control. The panel carries the share and the
157
+ scrolling; see the rule above. */
158
+ .ig-audit-list {
159
+ display: flex;
160
+ flex-direction: column;
161
+ gap: var(--ig-space-snug);
162
+ list-style: none;
163
+ margin: 0;
164
+ padding: 0;
165
+ }
166
+
167
+ .ig-audit-card {
168
+ display: flex;
169
+ flex-direction: column;
170
+ gap: var(--ig-space-micro);
171
+ }
172
+
173
+ .ig-audit-chip {
174
+ align-self: flex-start;
175
+ border: var(--ig-stroke) solid var(--ig-line);
176
+ border-radius: var(--ig-radius-small);
177
+ font-family: var(--ig-font-mono);
178
+ font-size: var(--ig-font-size-pill);
179
+ padding: 0 var(--ig-space-tight);
180
+ }
181
+
182
+ .ig-audit-title {
183
+ color: var(--ig-text);
184
+ font-size: var(--ig-font-size);
185
+ margin: 0;
186
+ }
187
+
188
+ /* SENTENCE-LENGTH COPY TAKES --ig-text-body, NEVER --ig-text-muted. SPEC's
189
+ closing note measures muted at 4.65:1 on a plain surface and about 0.4 less
190
+ inside a tint, which drops it under AA — so muted is reserved there for short
191
+ mono labels, and a finding's detail is a sentence. */
192
+ .ig-audit-detail {
193
+ color: var(--ig-text-body);
194
+ font-size: var(--ig-font-size-compact);
195
+ line-height: var(--ig-line-height);
196
+ margin: 0;
197
+ }
198
+
199
+ /* MUTED IS CORRECT HERE, ON THE SAME RULE .ig-audit-refused-source ends this
200
+ file with: the note above reserves muted for SHORT MONO LABELS, and a cycle's
201
+ walk is one — refs and separators, no prose. Length is not the test and could
202
+ not be: a large ring draws a long line, which is what the wrap below is for.
203
+ What muted is reserved AGAINST is sentence-length COPY. The detail rule one block up takes --ig-text-body because it IS a
204
+ sentence, so it is the wrong neighbour to copy.
205
+
206
+ break-word, NOT anywhere. A ref is a token worth keeping whole; the refused
207
+ block accepts mid-token breaks because it quotes arbitrary body text, and a
208
+ walk is a list of identifiers a reader has to be able to match against the
209
+ rail. */
210
+ .ig-audit-walk {
211
+ color: var(--ig-text-muted);
212
+ font-family: var(--ig-font-mono);
213
+ font-size: var(--ig-font-size-compact);
214
+ line-height: var(--ig-line-height);
215
+ margin: 0;
216
+ overflow-wrap: break-word;
217
+ }
218
+
219
+ .ig-audit-show {
220
+ align-self: flex-start;
221
+ background: none;
222
+ border: var(--ig-stroke) solid var(--ig-line);
223
+ border-radius: var(--ig-radius-small);
224
+ color: var(--ig-text);
225
+ cursor: pointer;
226
+ font: inherit;
227
+ font-size: var(--ig-font-size-meta);
228
+ padding: var(--ig-space-micro) var(--ig-space-tight);
229
+ }
230
+
231
+ .ig-audit-show:hover {
232
+ border-color: var(--ig-accent);
233
+ }
234
+
235
+ .ig-audit-show:focus-visible {
236
+ outline: var(--ig-focus-ring) solid var(--ig-focus);
237
+ outline-offset: var(--ig-space-micro);
238
+ }
239
+
240
+ /* 17d'S REMEDIES. A row of them under the card's sentence.
241
+
242
+ THEY TAKE THE SAME CHROME AS .ig-audit-show DELIBERATELY. A remedy and the
243
+ navigation control beside it do the same KIND of thing — both publish a
244
+ selection and neither writes — so drawing the remedies louder would promise
245
+ an action they do not perform. 3's whole point is that the judgment stays
246
+ with the owner, and a button styled like a commit invites a click that is
247
+ not one.
248
+
249
+ WRAPS, because the stale card carries two of them inside a panel of fixed
250
+ width whose cards also indent. A row that cannot wrap pushes its second
251
+ control out of the panel, which is the shape the relationship row one sheet
252
+ over already had to solve. (Worded around two particular verbs on purpose:
253
+ acceptance scans this sheet's PROSE as well as its declarations for the
254
+ vocabulary of a remedy that acts by itself, and it cannot tell a comment
255
+ from a control. Both spellings of that idea are therefore unusable here,
256
+ including in a note explaining why.)
257
+
258
+ (The width itself is 3's number and lives in workspace/styles.ts, where the
259
+ overlay is positioned. Naming it here would put a length in a sheet whose
260
+ own guard reads comments as well as declarations - which is how this
261
+ paragraph was first written, and caught.) */
262
+ .ig-audit-remedies {
263
+ display: flex;
264
+ flex-wrap: wrap;
265
+ gap: var(--ig-space-tight);
266
+ }
267
+
268
+ .ig-audit-remedy {
269
+ background: none;
270
+ border: var(--ig-stroke) solid var(--ig-line);
271
+ border-radius: var(--ig-radius-small);
272
+ color: var(--ig-text);
273
+ cursor: pointer;
274
+ font: inherit;
275
+ font-size: var(--ig-font-size-meta);
276
+ padding: var(--ig-space-micro) var(--ig-space-tight);
277
+ }
278
+
279
+ .ig-audit-remedy:hover {
280
+ border-color: var(--ig-accent);
281
+ }
282
+
283
+ .ig-audit-remedy:focus-visible {
284
+ outline: var(--ig-focus-ring) solid var(--ig-focus);
285
+ outline-offset: var(--ig-space-micro);
286
+ }
287
+
288
+ /* 17d'S OWN ENTRY POINT - 'filter the rail to these' - in the panel head.
289
+
290
+ IT SITS AT THE END OF THE HEAD ROW, after the count and the heading, which is
291
+ where the frame draws it. margin-inline-start: auto rather than a width on
292
+ the heading, so the head stays one flex row whatever the host's word is.
293
+
294
+ aria-pressed IS STYLED, NOT JUST PUBLISHED. 2's ruling is that a filter
295
+ which is visibly on, states what it hides and clears in one click is not a
296
+ refusal - so 'visibly on' is a requirement here rather than a nicety. */
297
+ .ig-audit-filter {
298
+ background: none;
299
+ border: var(--ig-stroke) solid var(--ig-line);
300
+ border-radius: var(--ig-radius-small);
301
+ color: var(--ig-text-muted);
302
+ cursor: pointer;
303
+ font: inherit;
304
+ font-size: var(--ig-font-size-meta);
305
+ margin-inline-start: auto;
306
+ padding: var(--ig-space-micro) var(--ig-space-tight);
307
+ }
308
+
309
+ .ig-audit-filter[aria-pressed='true'] {
310
+ border-color: var(--ig-accent);
311
+ color: var(--ig-accent);
312
+ }
313
+
314
+ .ig-audit-filter:focus-visible {
315
+ outline: var(--ig-focus-ring) solid var(--ig-focus);
316
+ outline-offset: var(--ig-space-micro);
317
+ }
318
+
319
+ /* ONE RULE PER CLASS, WRITTEN OUT, never through an intermediate property: the
320
+ viewer's own badge sheet records why — a \--ig-audit-hue\ set here and read
321
+ here names something no theme and no layout declares, and the token scan
322
+ exists to refuse exactly that.
323
+
324
+ THE TINTS ARE THE VIEWER'S RECIPE, not new numbers. \--ig-tint-fill\ and
325
+ \--ig-tint-border\ are what \.ig-badge[data-edge]\ already mixes an edge hue
326
+ with, and they are the frame's own chip wash to within a point.
327
+
328
+ THE TEXT IS THE BASE HUE. The frame lightens each chip's label a step off the
329
+ hue it tints with; those derivatives have no token, and a literal here is
330
+ refused by the scan two files over, so the label takes the hue itself. */
331
+ .ig-audit-chip[${AUDIT_KIND_ATTRIBUTE}='cycle'] {
332
+ background: color-mix(in srgb, var(--ig-edge-blocked-by) var(--ig-tint-fill), transparent);
333
+ border-color: color-mix(in srgb, var(--ig-edge-blocked-by) var(--ig-tint-border), transparent);
334
+ color: var(--ig-edge-blocked-by);
335
+ }
336
+
337
+ .ig-audit-chip[${AUDIT_KIND_ATTRIBUTE}='stale-blocker'] {
338
+ background: color-mix(in srgb, var(--ig-edge-serialize-with) var(--ig-tint-fill), transparent);
339
+ border-color: color-mix(in srgb, var(--ig-edge-serialize-with) var(--ig-tint-border), transparent);
340
+ color: var(--ig-edge-serialize-with);
341
+ }
342
+
343
+ .ig-audit-chip[${AUDIT_KIND_ATTRIBUTE}='dead-duplicate-ref'] {
344
+ background: color-mix(in srgb, var(--ig-edge-duplicate-of) var(--ig-tint-fill), transparent);
345
+ border-color: color-mix(in srgb, var(--ig-edge-duplicate-of) var(--ig-tint-border), transparent);
346
+ color: var(--ig-edge-duplicate-of);
347
+ }
348
+
349
+ .ig-audit-chip[${AUDIT_KIND_ATTRIBUTE}='encoding-refused'] {
350
+ background: color-mix(in srgb, var(--ig-edge-blocked-by) var(--ig-tint-fill), transparent);
351
+ border-color: color-mix(in srgb, var(--ig-edge-blocked-by) var(--ig-tint-border), transparent);
352
+ color: var(--ig-edge-blocked-by);
353
+ }
354
+
355
+ /* ---- §17d's fourth class, drawn outside the panel (./refused.ts) ---- */
356
+
357
+ /* NO SIZE OF ITS OWN, THE SAME ANSWER .ig-audit-panel GIVES ABOVE. This block
358
+ and that panel are the two members of .ig-audit-overlay, which is what the
359
+ inspector zone actually holds, and how much of the zone's single track that
360
+ region may take is a fact about SHARING A COLUMN rather than a fact about
361
+ either leaf. So the share is declared once by the composition that owns the
362
+ zone — see workspace/styles.ts, which bounds the region alone. */
363
+ .ig-audit-refused {
364
+ display: flex;
365
+ flex-direction: column;
366
+ font-family: var(--ig-font-ui);
367
+ gap: var(--ig-space-snug);
368
+ min-height: 0;
369
+ padding: var(--ig-space);
370
+ }
371
+
372
+ /* THE RING FOR THIS BLOCK'S TAB STOP, unscoped for the reason the panel's is:
373
+ the tabindex is in this leaf's MARKUP, so it is carried into every
374
+ composition, and a focusable element that shows nothing on focus is a stop a
375
+ keyboard reader lands on blind. Inset, the viewer's idiom, so a ring at the
376
+ zone's edge is not the first thing the column's scroll clips. */
377
+ .ig-audit-refused:focus-visible {
378
+ outline: var(--ig-focus-ring) solid var(--ig-focus);
379
+ outline-offset: calc(var(--ig-focus-ring) * -1);
380
+ }
381
+
382
+ /* THE SAME HEADING TREATMENT AS THE PANEL'S, because they are peers in one
383
+ column and §17d draws them as one section. A second treatment would say they
384
+ were two unrelated things. */
385
+ .ig-audit-refused-heading {
386
+ color: var(--ig-edge-blocked-by);
387
+ font-family: var(--ig-font-mono);
388
+ font-size: var(--ig-font-size-small);
389
+ font-weight: inherit;
390
+ letter-spacing: var(--ig-tracking-group);
391
+ margin: 0;
392
+ text-transform: uppercase;
393
+ }
394
+
395
+ .ig-audit-refused-list {
396
+ display: flex;
397
+ flex-direction: column;
398
+ gap: var(--ig-space-snug);
399
+ list-style: none;
400
+ margin: 0;
401
+ padding: 0;
402
+ }
403
+
404
+ /* THE CARD IS DRAWN, UNLIKE THE PANEL'S. The frame gives this one a surface and
405
+ a border because it sits alone rather than in a list of like items — it is
406
+ the one finding that is about an ISSUE rather than a relationship, and the
407
+ box is what says so. */
408
+ .ig-audit-refused-card {
409
+ background: var(--ig-surface-2);
410
+ border: var(--ig-stroke) solid var(--ig-card-line);
411
+ border-radius: var(--ig-radius);
412
+ display: flex;
413
+ flex-direction: column;
414
+ gap: var(--ig-space-tight);
415
+ padding: var(--ig-space-tight);
416
+ }
417
+
418
+ .ig-audit-refused-head {
419
+ align-items: center;
420
+ display: flex;
421
+ gap: var(--ig-space-tight);
422
+ }
423
+
424
+ .ig-audit-refused-ref {
425
+ color: var(--ig-text);
426
+ font-family: var(--ig-font-mono);
427
+ font-size: var(--ig-font-size-meta);
428
+ }
429
+
430
+ /* ONE BLOCK, TWO LINES — the diagnostic and, under it, the line the reader
431
+ stopped on. pre-wrap because the source line is RAW BODY TEXT: it carries
432
+ its own leading space and its own length, and collapsing either would show a
433
+ reader something other than what is in their issue. overflow-wrap keeps a
434
+ long unbroken line inside the column instead of widening the track. */
435
+ .ig-audit-refused-reason {
436
+ display: flex;
437
+ flex-direction: column;
438
+ font-family: var(--ig-font-mono);
439
+ font-size: var(--ig-font-size-meta);
440
+ gap: var(--ig-space-micro);
441
+ overflow-wrap: anywhere;
442
+ white-space: pre-wrap;
443
+ }
444
+
445
+ .ig-audit-refused-diagnostic {
446
+ color: var(--ig-text-body);
447
+ }
448
+
449
+ /* MUTED IS CORRECT HERE AND NOWHERE NEAR A SENTENCE. SPEC's closing note puts
450
+ muted under AA for sentence-length copy, which is why .ig-audit-detail
451
+ takes --ig-text-body — but this is a short mono quotation of the reader's own
452
+ input, the exact shape muted is reserved for, and the frame draws it a step
453
+ back from the diagnostic it belongs to. */
454
+ .ig-audit-refused-source {
455
+ color: var(--ig-text-muted);
456
+ }
457
+
458
+ .ig-audit-refused-controls {
459
+ display: flex;
460
+ flex-wrap: wrap;
461
+ gap: var(--ig-space-tight);
462
+ }
463
+
464
+ /* ONE TREATMENT FOR BOTH CONTROLS, THOUGH ONE IS AN ANCHOR AND ONE A BUTTON.
465
+ The element differs because what they DO differs — the link leaves the
466
+ document and must say so to assistive technology — but they sit side by side
467
+ in the frame as one pair of moves, and drawing them differently would imply a
468
+ difference in weight that §17d does not make. */
469
+ .ig-audit-refused-open,
470
+ .ig-audit-refused-rewrite {
471
+ align-items: center;
472
+ background: none;
473
+ border: var(--ig-stroke) solid var(--ig-line);
474
+ border-radius: var(--ig-radius-small);
475
+ color: var(--ig-text);
476
+ cursor: pointer;
477
+ display: inline-flex;
478
+ font: inherit;
479
+ font-size: var(--ig-font-size-meta);
480
+ gap: var(--ig-space-micro);
481
+ padding: var(--ig-space-micro) var(--ig-space-tight);
482
+ text-decoration: none;
483
+ }
484
+
485
+ .ig-audit-refused-open:hover,
486
+ .ig-audit-refused-rewrite:hover {
487
+ border-color: var(--ig-accent);
488
+ }
489
+
490
+ .ig-audit-refused-open:focus-visible,
491
+ .ig-audit-refused-rewrite:focus-visible {
492
+ outline: var(--ig-focus-ring) solid var(--ig-focus);
493
+ outline-offset: var(--ig-space-micro);
494
+ }
495
+
496
+ .ig-audit-refused-away {
497
+ color: var(--ig-text-muted);
498
+ }
71
499
  `;
72
500
  //# sourceMappingURL=styles.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"styles.js","sourceRoot":"","sources":["../../src/audit/styles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,OAAO,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AAExD,MAAM,CAAC,MAAM,eAAe,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+B5B,wBAAwB;;;CAG1B,CAAC"}
1
+ {"version":3,"file":"styles.js","sourceRoot":"","sources":["../../src/audit/styles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAClD,OAAO,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AAExD,MAAM,CAAC,MAAM,eAAe,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+B5B,wBAAwB;;;;oDAIyB,wBAAwB;;;;kDAI1B,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA8PrD,oBAAoB;;;;;;iBAMpB,oBAAoB;;;;;;iBAMpB,oBAAoB;;;;;;iBAMpB,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsJpC,CAAC"}
@@ -17,10 +17,16 @@
17
17
  * ## Why the bar is CSS on an attribute rather than a drawn element
18
18
  *
19
19
  * The rows belong to `@issuegraph/viewer`, which stamps `KEY_ATTRIBUTE` on
20
- * them. Layer 2 composes layer 1 through its public surface, and the viewer's
21
- * markup primitive is deliberately not on it — so an overlay drawn from out
22
- * here would have to re-implement HTML escaping, which is duplication with an
23
- * injection shape rather than a mirror that merely drifts.
20
+ * them. Layer 2 composes layer 1 through its public surface, and rewriting a
21
+ * row's `class` from out here is reaching past that surface — so a bar drawn as
22
+ * an element would mean this layer assembling markup for a row it does not own.
23
+ *
24
+ * ONE CLAUSE OF THIS USED TO READ "and the viewer's markup primitive is
25
+ * deliberately not on it", and that was never true after `renderMarkup` was
26
+ * exported one pull request later. It is public, and five sibling leaves import
27
+ * it — `scale/render.ts`, `picker/render.ts`, `workspace/render.ts`,
28
+ * `reevaluate/render.ts`, `firstpass/render.ts`. The escaping was never what
29
+ * made the bar an attribute; OWNERSHIP OF THE ROW is, and that part stands.
24
30
  *
25
31
  * An attribute plus a stylesheet needs neither. {@link auditRowAttributes}
26
32
  * answers what an affected row carries, `./styles.ts` draws the bar from it,
@@ -30,10 +36,18 @@
30
36
  * ## Nothing here renders host text
31
37
  *
32
38
  * The only value that reaches the markup is a COUNT, and a count is a number.
33
- * That is a deliberate boundary rather than a happy accident: a finding's
34
- * `detail` is prose about issues a host supplied, so drawing it here would put
35
- * an escaper in a package whose seam says it may not have one. Findings travel
36
- * as data; whatever lists them owns their escaping.
39
+ * That is what makes {@link renderAuditHeader} safe while it concatenates by
40
+ * hand, and it is a boundary rather than a happy accident: a finding's `detail`
41
+ * is prose about issues a host supplied. Findings travel as data; whatever
42
+ * lists them owns their escaping.
43
+ *
44
+ * ## And `./panel.ts` is the lister that rule predicted
45
+ *
46
+ * §17d's findings list is drawn there, not here, and it owns its escaping the
47
+ * way every other leaf does — an `ElementSpec` tree the caller renders through
48
+ * the viewer's `renderMarkup`. So the rule above is unchanged rather than
49
+ * relaxed: this module still renders no host text, and the module that does
50
+ * builds no markup by hand.
37
51
  *
38
52
  * @see https://github.com/autnmy/issuegraph/blob/main/SPEC.md
39
53
  */
@@ -52,6 +66,16 @@ export declare const AUDIT_COUNT_ATTRIBUTE = "data-ig-audit-count";
52
66
  * matching on a class name that is a styling hook rather than a contract.
53
67
  */
54
68
  export declare const AUDIT_FILTER_ATTRIBUTE = "data-ig-audit-filter";
69
+ /**
70
+ * The attribute marking the header's panel control.
71
+ *
72
+ * SEPARATE FROM THE FILTER, because §17d gives the header count exactly one
73
+ * job — *"always the same click"* — and `RULINGS.md` §3 says what that click
74
+ * does: the panel is *"a transient overlay anchored to the header count"*. The
75
+ * filter is drawn INSIDE the panel, where §17d draws it (*"filter the rail to
76
+ * these"*), so one control no longer carries two meanings.
77
+ */
78
+ export declare const AUDIT_PANEL_ATTRIBUTE = "data-ig-audit-panel";
55
79
  /** What one affected row is told about itself. */
56
80
  export interface AuditRow {
57
81
  readonly ref: IssueRef;
@@ -105,8 +129,13 @@ export interface AuditOverlay {
105
129
  * Project findings onto the two things the ambient surface draws.
106
130
  *
107
131
  * Pure, and a function of the findings alone: it neither re-detects anything
108
- * nor reads the document, so the count on screen and the list behind it can
109
- * never disagree about what was found.
132
+ * nor reads the document, so the count on screen and what is drawn behind it
133
+ * can never disagree about what was found.
134
+ *
135
+ * "WHAT IS DRAWN" IS TWO SURFACES, NOT ONE. `./panel.ts` lists the three
136
+ * relationship classes and `./refused.ts` draws the fourth, so this count is a
137
+ * count of both together — which is why it stays every finding while the
138
+ * panel's own head counts only its cards. See {@link renderAuditHeader}.
110
139
  */
111
140
  export declare function auditOverlay(input: AuditInput): AuditOverlay;
112
141
  /**
@@ -137,8 +166,15 @@ export declare function auditFilterKeeps(overlay: AuditOverlay, ref: IssueRef):
137
166
  */
138
167
  export declare function heaviestRow(overlay: AuditOverlay, refs: readonly IssueRef[]): AuditRow | undefined;
139
168
  export interface AuditHeaderOptions {
140
- /** Whether the filter is currently narrowing the rail. Defaults to `false`. */
141
- readonly filtered?: boolean | undefined;
169
+ /**
170
+ * Whether §17d's overlay is open. Defaults to `false`.
171
+ *
172
+ * WAS `filtered`, AND THE SWAP IS THE RULING. This button used to toggle the
173
+ * rail filter; §3 makes it the panel's anchor instead, and the filter moves
174
+ * into the panel head. A host that bound to the old meaning gets a compile
175
+ * error rather than a control that silently does something else.
176
+ */
177
+ readonly open?: boolean | undefined;
142
178
  }
143
179
  /**
144
180
  * The persistent header count, as markup.
@@ -148,6 +184,12 @@ export interface AuditHeaderOptions {
148
184
  * that appears when there is bad news is a control the eye has to re-find, and
149
185
  * §17d's whole ask is that the audit never demands attention it has not earned.
150
186
  *
187
+ * IT COUNTS EVERY FINDING, ACROSS BOTH SURFACES THAT DRAW THEM. `./panel.ts`
188
+ * lists three classes and `./refused.ts` draws the fourth, and a reader works
189
+ * through all four — so splitting this number would hand them two to add up
190
+ * for one question. This one is in the workspace CHROME, beside both; the
191
+ * panel's own head is enclosed by its list and counts that list alone.
192
+ *
151
193
  * A `button` with `aria-pressed`, because a filter toggle is a toggle: it has
152
194
  * two states and a screen reader has to be able to say which one is on.
153
195
  */
@@ -1 +1 @@
1
- {"version":3,"file":"surface.d.ts","sourceRoot":"","sources":["../../src/audit/surface.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAGlD,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEzF;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,kBAAkB,CAAC;AAExD,kFAAkF;AAClF,eAAO,MAAM,qBAAqB,wBAAwB,CAAC;AAE3D;;;GAGG;AACH,eAAO,MAAM,sBAAsB,yBAAyB,CAAC;AAE7D,kDAAkD;AAClD,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC;IACvB;;;OAGG;IACH,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC;IACjC,sEAAsE;IACtE,QAAQ,CAAC,KAAK,EAAE,SAAS,UAAU,EAAE,CAAC;IACtC,uCAAuC;IACvC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,YAAY;IAC3B;;;;;;OAMG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,gEAAgE;IAChE,QAAQ,CAAC,IAAI,EAAE,SAAS,QAAQ,EAAE,CAAC;IACnC;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,QAAQ,KAAK,QAAQ,GAAG,SAAS,CAAC;IACzD,0DAA0D;IAC1D,QAAQ,CAAC,QAAQ,EAAE,SAAS,YAAY,EAAE,CAAC;CAC5C;AAMD;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG,YAAY,CAmE5D;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,YAAY,EACrB,GAAG,EAAE,QAAQ,GACZ,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAIlC;AAED,iFAAiF;AACjF,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAE9E;AAUD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,YAAY,EACrB,IAAI,EAAE,SAAS,QAAQ,EAAE,GACxB,QAAQ,GAAG,SAAS,CAQtB;AAED,MAAM,WAAW,kBAAkB;IACjC,+EAA+E;IAC/E,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACzC;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,YAAY,EAAE,OAAO,GAAE,kBAAuB,GAAG,MAAM,CAYjG"}
1
+ {"version":3,"file":"surface.d.ts","sourceRoot":"","sources":["../../src/audit/surface.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoDG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAGlD,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEzF;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,kBAAkB,CAAC;AAExD,kFAAkF;AAClF,eAAO,MAAM,qBAAqB,wBAAwB,CAAC;AAE3D;;;GAGG;AACH,eAAO,MAAM,sBAAsB,yBAAyB,CAAC;AAE7D;;;;;;;;GAQG;AACH,eAAO,MAAM,qBAAqB,wBAAwB,CAAC;AAE3D,kDAAkD;AAClD,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC;IACvB;;;OAGG;IACH,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC;IACjC,sEAAsE;IACtE,QAAQ,CAAC,KAAK,EAAE,SAAS,UAAU,EAAE,CAAC;IACtC,uCAAuC;IACvC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,YAAY;IAC3B;;;;;;OAMG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,gEAAgE;IAChE,QAAQ,CAAC,IAAI,EAAE,SAAS,QAAQ,EAAE,CAAC;IACnC;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,QAAQ,KAAK,QAAQ,GAAG,SAAS,CAAC;IACzD,0DAA0D;IAC1D,QAAQ,CAAC,QAAQ,EAAE,SAAS,YAAY,EAAE,CAAC;CAC5C;AAMD;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG,YAAY,CAmE5D;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,YAAY,EACrB,GAAG,EAAE,QAAQ,GACZ,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAIlC;AAED,iFAAiF;AACjF,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAE9E;AAUD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,YAAY,EACrB,IAAI,EAAE,SAAS,QAAQ,EAAE,GACxB,QAAQ,GAAG,SAAS,CAQtB;AAED,MAAM,WAAW,kBAAkB;IACjC;;;;;;;OAOG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACrC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,YAAY,EAAE,OAAO,GAAE,kBAAuB,GAAG,MAAM,CAiBjG"}
@@ -17,10 +17,16 @@
17
17
  * ## Why the bar is CSS on an attribute rather than a drawn element
18
18
  *
19
19
  * The rows belong to `@issuegraph/viewer`, which stamps `KEY_ATTRIBUTE` on
20
- * them. Layer 2 composes layer 1 through its public surface, and the viewer's
21
- * markup primitive is deliberately not on it — so an overlay drawn from out
22
- * here would have to re-implement HTML escaping, which is duplication with an
23
- * injection shape rather than a mirror that merely drifts.
20
+ * them. Layer 2 composes layer 1 through its public surface, and rewriting a
21
+ * row's `class` from out here is reaching past that surface — so a bar drawn as
22
+ * an element would mean this layer assembling markup for a row it does not own.
23
+ *
24
+ * ONE CLAUSE OF THIS USED TO READ "and the viewer's markup primitive is
25
+ * deliberately not on it", and that was never true after `renderMarkup` was
26
+ * exported one pull request later. It is public, and five sibling leaves import
27
+ * it — `scale/render.ts`, `picker/render.ts`, `workspace/render.ts`,
28
+ * `reevaluate/render.ts`, `firstpass/render.ts`. The escaping was never what
29
+ * made the bar an attribute; OWNERSHIP OF THE ROW is, and that part stands.
24
30
  *
25
31
  * An attribute plus a stylesheet needs neither. {@link auditRowAttributes}
26
32
  * answers what an affected row carries, `./styles.ts` draws the bar from it,
@@ -30,10 +36,18 @@
30
36
  * ## Nothing here renders host text
31
37
  *
32
38
  * The only value that reaches the markup is a COUNT, and a count is a number.
33
- * That is a deliberate boundary rather than a happy accident: a finding's
34
- * `detail` is prose about issues a host supplied, so drawing it here would put
35
- * an escaper in a package whose seam says it may not have one. Findings travel
36
- * as data; whatever lists them owns their escaping.
39
+ * That is what makes {@link renderAuditHeader} safe while it concatenates by
40
+ * hand, and it is a boundary rather than a happy accident: a finding's `detail`
41
+ * is prose about issues a host supplied. Findings travel as data; whatever
42
+ * lists them owns their escaping.
43
+ *
44
+ * ## And `./panel.ts` is the lister that rule predicted
45
+ *
46
+ * §17d's findings list is drawn there, not here, and it owns its escaping the
47
+ * way every other leaf does — an `ElementSpec` tree the caller renders through
48
+ * the viewer's `renderMarkup`. So the rule above is unchanged rather than
49
+ * relaxed: this module still renders no host text, and the module that does
50
+ * builds no markup by hand.
37
51
  *
38
52
  * @see https://github.com/autnmy/issuegraph/blob/main/SPEC.md
39
53
  */
@@ -51,13 +65,28 @@ export const AUDIT_COUNT_ATTRIBUTE = 'data-ig-audit-count';
51
65
  * matching on a class name that is a styling hook rather than a contract.
52
66
  */
53
67
  export const AUDIT_FILTER_ATTRIBUTE = 'data-ig-audit-filter';
68
+ /**
69
+ * The attribute marking the header's panel control.
70
+ *
71
+ * SEPARATE FROM THE FILTER, because §17d gives the header count exactly one
72
+ * job — *"always the same click"* — and `RULINGS.md` §3 says what that click
73
+ * does: the panel is *"a transient overlay anchored to the header count"*. The
74
+ * filter is drawn INSIDE the panel, where §17d draws it (*"filter the rail to
75
+ * these"*), so one control no longer carries two meanings.
76
+ */
77
+ export const AUDIT_PANEL_ATTRIBUTE = 'data-ig-audit-panel';
54
78
  const CLASS_ORDER = new Map(AUDIT_CLASSES.map((kind, index) => [kind, index]));
55
79
  /**
56
80
  * Project findings onto the two things the ambient surface draws.
57
81
  *
58
82
  * Pure, and a function of the findings alone: it neither re-detects anything
59
- * nor reads the document, so the count on screen and the list behind it can
60
- * never disagree about what was found.
83
+ * nor reads the document, so the count on screen and what is drawn behind it
84
+ * can never disagree about what was found.
85
+ *
86
+ * "WHAT IS DRAWN" IS TWO SURFACES, NOT ONE. `./panel.ts` lists the three
87
+ * relationship classes and `./refused.ts` draws the fourth, so this count is a
88
+ * count of both together — which is why it stays every finding while the
89
+ * panel's own head counts only its cards. See {@link renderAuditHeader}.
61
90
  */
62
91
  export function auditOverlay(input) {
63
92
  // IT RUNS THE AUDIT RATHER THAN ACCEPTING ONE. Taking a finding list made
@@ -183,16 +212,27 @@ export function heaviestRow(overlay, refs) {
183
212
  * that appears when there is bad news is a control the eye has to re-find, and
184
213
  * §17d's whole ask is that the audit never demands attention it has not earned.
185
214
  *
215
+ * IT COUNTS EVERY FINDING, ACROSS BOTH SURFACES THAT DRAW THEM. `./panel.ts`
216
+ * lists three classes and `./refused.ts` draws the fourth, and a reader works
217
+ * through all four — so splitting this number would hand them two to add up
218
+ * for one question. This one is in the workspace CHROME, beside both; the
219
+ * panel's own head is enclosed by its list and counts that list alone.
220
+ *
186
221
  * A `button` with `aria-pressed`, because a filter toggle is a toggle: it has
187
222
  * two states and a screen reader has to be able to say which one is on.
188
223
  */
189
224
  export function renderAuditHeader(overlay, options = {}) {
190
225
  const count = String(overlay.count);
191
- const pressed = options.filtered === true;
226
+ const open = options.open === true;
192
227
  return [
193
228
  `<div class="ig-audit" ${AUDIT_COUNT_ATTRIBUTE}="${count}">`,
194
- `<button type="button" class="ig-audit-toggle" aria-pressed="${String(pressed)}"`,
195
- ` ${AUDIT_FILTER_ATTRIBUTE}>`,
229
+ // `aria-expanded`, NOT `aria-pressed`. The two say different things and the
230
+ // button's job changed underneath it: `pressed` describes a toggle that is
231
+ // ON — the old filter — while `expanded` describes a control that DISCLOSES
232
+ // something, which is what an overlay anchored here is. A reader told
233
+ // "pressed" would be waiting for a state, not for a panel.
234
+ `<button type="button" class="ig-audit-toggle" aria-expanded="${String(open)}"`,
235
+ ` ${AUDIT_PANEL_ATTRIBUTE}>`,
196
236
  `<span class="ig-audit-count">${count}</span>`,
197
237
  `<span class="ig-audit-label">audit</span>`,
198
238
  `</button>`,