@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
@@ -0,0 +1,462 @@
1
+ /**
2
+ * §17d's findings panel: the three RELATIONSHIP findings, as a list.
3
+ *
4
+ * `./surface.ts` draws the ambient half — the persistent header count and the
5
+ * left-bar on an affected rail row. Neither says what was found. This module
6
+ * draws the list, so a reader who sees a count has somewhere to read it.
7
+ *
8
+ * ## Three classes, not four — the fourth is `./refused.ts`'
9
+ *
10
+ * `encoding-refused` is drawn OUTSIDE this panel, and SPEC is why: a refusal is
11
+ * *"surfaced on the issue itself, because until it parses the issue has no
12
+ * edges at all and would otherwise look simply unencoded"*. The other three
13
+ * say something about a relationship that exists; that one says the
14
+ * relationships cannot be read.
15
+ *
16
+ * SO THERE ARE TWO COUNTS NOW, AND THEY ARE DIFFERENT NUMBERS ON PURPOSE.
17
+ * `./surface.ts`'s ambient count stays every finding — it is the persistent
18
+ * control, it sits in the workspace chrome beside both surfaces, and a reader
19
+ * works through all four classes. The count in THIS panel's head is this
20
+ * panel's own cards, because it is enclosed by the list it counts. See
21
+ * {@link renderAuditPanel}.
22
+ *
23
+ * ## It is the module `./surface.ts` predicted
24
+ *
25
+ * That file's rule is *"findings travel as data; whatever lists them owns their
26
+ * escaping"*, and it is still true of it: `renderAuditHeader` concatenates by
27
+ * hand and is safe because the only value reaching it is a COUNT. A `detail` is
28
+ * not a number — it is prose about issues a host supplied — so this module is
29
+ * the lister that owns the escaping, and it owns it the way every other leaf in
30
+ * this package does: it builds an {@link ElementSpec} tree and the caller renders
31
+ * it through the viewer's `renderMarkup`. Nothing here writes a tag.
32
+ *
33
+ * ## The card is THREE lines, or FOUR when the finding carries a walk
34
+ *
35
+ * The frame draws a chip, a title, a mono line of the refs, and a prose
36
+ * paragraph. This module long drew three, on this argument: `AuditFinding.detail`
37
+ * ALREADY OPENS WITH THE MEMBERS — `${members.join(' · ')} form a blocked-by
38
+ * cycle; …` — so *"drawing a members line beside it prints every reference
39
+ * twice"*.
40
+ *
41
+ * THAT ARGUMENT WAS RIGHT ABOUT A LINE THE FRAME DOES NOT DRAW. The frame's
42
+ * third line is `#544 → #551 → #560 → #544` — a WALK, not the `·`-joined set
43
+ * the sentence opens with. Calling it "a mono line of the refs" was the slip:
44
+ * the line that was declined and the line drawn below are not the same line, and
45
+ * the refs are the only thing they have in common.
46
+ *
47
+ * SO THE COST IS CONCEDED RATHER THAN ARGUED AWAY. On a card that carries a
48
+ * walk the refs really do appear twice, and the head three times. What buys it
49
+ * is the one thing the sentence cannot say: `#544 · #551 · #560` does not say
50
+ * who waits on whom, and a stuck group's ordering is the first thing a groomer
51
+ * needs in order to pick an edge to cut. Direction appears nowhere else on the
52
+ * card.
53
+ *
54
+ * `detail` IS LEFT BYTE-IDENTICAL, and composing the walk INTO it was the
55
+ * alternative. It is rejected on §17d rather than on the accessible name — a
56
+ * walk-composed sentence would be collision-free by the same construction the
57
+ * current one is, so that cost is not real. The reason is that the frame draws a
58
+ * SEPARATE line and this section is delivered as a frame/implementation pair, so
59
+ * folding it into the sentence deviates from the artifact the work is measured
60
+ * against; and it would give `detail` two shapes, since a component §6.6
61
+ * declines to order has no walk to compose.
62
+ *
63
+ * THE ARROW GLYPH IS `aria-hidden`, AND THAT IS A GAP RATHER THAN A WIN. The
64
+ * only thing the line adds is direction, and the glyph does not carry it: `→`
65
+ * announces as "right arrow", not "waits on". Announcing the line anyway would
66
+ * repeat three refs `detail` has just named, in a form that has lost the reason
67
+ * it was drawn — the same trade `renderAuditPanel`'s `◆` makes below. The
68
+ * remaining options were a visually-hidden utility this package does not have,
69
+ * or an `aria-label` on a `p` with no role, which is not reliably announced. So
70
+ * a screen reader hears exactly what it heard before this line existed, and the
71
+ * ordering is genuinely lost to it. Filed, not resolved here.
72
+ *
73
+ * THE TITLE IS PER CLASS, AND THE FRAME'S ARE PER FINDING. `AuditFinding` has no
74
+ * title field, and this package appends numbers rather than interpolating them
75
+ * (`WorkspaceWords.whyRank` records why), so a per-finding title would need a
76
+ * template language this package has twice refused to ship. Four fixed titles
77
+ * keep the frame's type hierarchy at no cost to the data model; the difference
78
+ * is a stated deviation rather than a silent one.
79
+ *
80
+ * ## The chip carries `data-ig-audit-kind`, NEVER `data-ig-audit`
81
+ *
82
+ * `./styles.ts` ends with an UNQUALIFIED `[data-ig-audit]` rule — deliberately,
83
+ * because it lands on a row the viewer rendered and this layer may not rewrite
84
+ * that row's `class`. So an element out here carrying the same attribute picks
85
+ * up the rail's 2px gold left-bar whatever hue it was given, and the four-hue
86
+ * chip mapping would draw one gold bar on all four. The severity attribute stays
87
+ * reserved for the rail rows it was scoped for.
88
+ *
89
+ * ## There is no filter control here, and that is SPEC's call rather than a cut
90
+ *
91
+ * The frame's panel header carries `filter the rail to these`. SPEC §17a gives
92
+ * the workspace *"an audit **filter** … for focus; an audit **mode** does not"*,
93
+ * and §17d gives the header count one job — *"always the same click"*. That is
94
+ * one control, which `./surface.ts` already draws and which is on screen beside
95
+ * this panel. A second element on `data-ig-audit-filter` would be a second
96
+ * TOGGLE for one boolean (`workspace/host.ts` flips rather than sets), one
97
+ * announcing `aria-pressed` and one not.
98
+ *
99
+ * The frame needs its own because it draws the panel STANDALONE, with no
100
+ * workspace header in the artboard. Composed, it has one.
101
+ *
102
+ * ## `Show the loop` is in, because §17d's rule is navigation
103
+ *
104
+ * `./surface.ts` states the section's own doctrine: *"every finding is a
105
+ * judgment call, so the surface offers navigation and never a remedy."* The
106
+ * frame's other three buttons are remedies and are deferred. Without this one
107
+ * the panel is a paragraph you cannot act on or navigate from, sitting at the
108
+ * top of a column whose whole job is the selection.
109
+ *
110
+ * IT PUBLISHES `reveal-issue`, NOT the `select-issue` the inspector's hold rows
111
+ * use, and the difference is the whole of the control's safety. `select-issue`
112
+ * is a POINTER: with a relationship draft awaiting its target, `workspace/host
113
+ * .ts` reads a pointer as CHOOSING that target and emits the create proposal.
114
+ * So "go and look" would have declared a relationship — a write, from the one
115
+ * surface whose stated rule is that it never offers one. `reveal-issue` moves
116
+ * the selection and abandons the draft, and can produce no edit at all.
117
+ *
118
+ * AND IT IS DRAWN ONLY FOR A LOADED MEMBER — see {@link navigableMember}.
119
+ *
120
+ * @see https://github.com/autnmy/issuegraph/blob/main/SPEC.md
121
+ */
122
+ import { element } from '@issuegraph/viewer';
123
+ import { AUDIT_FILTER_ATTRIBUTE } from "./surface.js";
124
+ /**
125
+ * The attribute a severity chip carries.
126
+ *
127
+ * ITS OWN NAME RATHER THAN `AUDIT_SEVERITY_ATTRIBUTE`, for the reason in this
128
+ * module's header: that one is matched by an unqualified rule meant for rail
129
+ * rows, and a chip wearing it would draw the ambient bar.
130
+ */
131
+ export const AUDIT_KIND_ATTRIBUTE = 'data-ig-audit-kind';
132
+ /**
133
+ * The navigable member of a finding, or `undefined` when it has none.
134
+ *
135
+ * THE FIRST MEMBER THE DRAWN SURFACE CARRIES, and the filter is not defensive
136
+ * tidying. An encoding refusal is a fact the HOST asserted about an issue it
137
+ * read, and `findings.ts` keeps one whose ref lies outside the loaded document
138
+ * ON PURPOSE — filtering findings to the loaded set would drop them on exactly
139
+ * the issues a paging boundary has not reached. So a finding's members are not
140
+ * all rows, and a control targeting one that is not advertises a move it cannot
141
+ * make: the mount reconciles the unknown selection straight back away.
142
+ *
143
+ * IT ASKS THE CALLER, NOT THE OVERLAY, AND THE DIFFERENCE IS NOT PEDANTRY. An
144
+ * earlier revision used `overlay.rowFor`, which answers "does the AUDIT's
145
+ * document carry this ref" — a different question, and the two come apart
146
+ * exactly where a host audits more than it draws. The demo does: it audits the
147
+ * repository it holds and renders a page of it, so a ref can be audited, have
148
+ * an overlay row, and still reach a view with nothing in it.
149
+ *
150
+ * FIRST RATHER THAN "MOST IMPORTANT": a finding names a component, no member of
151
+ * it leads, and a rule invented here would be a second ranking nothing else in
152
+ * the package agrees with.
153
+ */
154
+ function navigableMember(finding, known) {
155
+ return finding.members.find((ref) => known.has(ref));
156
+ }
157
+ /**
158
+ * The card for one finding.
159
+ *
160
+ * A card whose finding names no loaded issue draws no control — see
161
+ * {@link navigableMember}. It keeps its chip, title and sentence, because the
162
+ * FINDING is still true and still worth reading; what it loses is a move that
163
+ * would not have moved anything.
164
+ */
165
+ function cardSpec(finding, known, edges, words) {
166
+ const target = navigableMember(finding, known);
167
+ return element('li', {
168
+ class: 'ig-audit-card',
169
+ [AUDIT_KIND_ATTRIBUTE]: finding.kind,
170
+ // THE SEVERITY IS PUBLISHED, NOT DRAWN FROM. A host styling the four
171
+ // differently needs it, and on the card — which this package owns — it
172
+ // carries no rule of its own, so it cannot collide the way it would on a
173
+ // chip. See the header.
174
+ 'data-ig-audit-severity': finding.severity,
175
+ }, [
176
+ element('span', { class: 'ig-audit-chip', [AUDIT_KIND_ATTRIBUTE]: finding.kind }, [
177
+ words.classes[finding.kind],
178
+ ]),
179
+ element('p', { class: 'ig-audit-title' }, [words.titles[finding.kind]]),
180
+ // THE FRAME'S THIRD LINE, and only where the reader named an ordering —
181
+ // see this module's header for the amendment that admits it, the cost it
182
+ // concedes, and why it is hidden from the accessibility tree.
183
+ //
184
+ // THE HEAD REPEATS HERE AND NOWHERE ELSE. `AuditFinding.walk` names each
185
+ // member once and implies the return edge, because closing the loop is a
186
+ // drawing decision — this is the drawing.
187
+ //
188
+ // NO `AuditWords` ENTRY. The arrow is a glyph the package draws, the same
189
+ // call `refusedOpen`'s `↗` makes; a host supplies words, not punctuation.
190
+ // GATED ON THE CLASS AS WELL AS THE FIELD. `AuditFinding` is a public
191
+ // export and so is this renderer, so a host can hand it a hand-built
192
+ // finding — and a walk drawn under a `stale blocker` chip would be an
193
+ // ordering of an edge that has none. The field's own doc says "only ever
194
+ // on a cycle finding"; this is what makes that a fact here rather than a
195
+ // hope about callers.
196
+ finding.kind !== 'cycle' || finding.walk === undefined || finding.walk.length === 0
197
+ ? null
198
+ : element('p', { class: 'ig-audit-walk', 'aria-hidden': 'true' }, [
199
+ [...finding.walk, finding.walk[0]].join(' → '),
200
+ ]),
201
+ element('p', { class: 'ig-audit-detail' }, [finding.detail]),
202
+ target === undefined
203
+ ? null
204
+ : element('button', {
205
+ type: 'button',
206
+ class: 'ig-audit-show',
207
+ // `reveal-issue`, NEVER `select-issue`. The latter is a POINTER,
208
+ // and with a draft awaiting its target `workspace/host.ts` reads a
209
+ // pointer as choosing that target and emits the create proposal —
210
+ // so this button would declare a relationship. §17d's surface
211
+ // "offers navigation and never a remedy", and a control that can
212
+ // write is a remedy whatever its label says.
213
+ 'data-ig-command': 'reveal-issue',
214
+ // THE NAME IS THE FINDING'S OWN SENTENCE, AND THAT IS A CLASS
215
+ // REMOVED RATHER THAN A THIRD ATTEMPT AT IT.
216
+ //
217
+ // Several findings mean several of these buttons, and in the one
218
+ // navigation mode that strips the surrounding card — a screen
219
+ // reader's BUTTON LIST — the name is all a reader has. Three
220
+ // revisions tried to SUMMARISE a finding into that name, and each
221
+ // summary collided on a shape the last had not met: the host's
222
+ // word alone collided everywhere; adding the ref collided when
223
+ // one issue carried two findings; adding the class collided when
224
+ // two findings shared a class AND a target — `a blocked-by y` and
225
+ // `a blocked-by z`, two closed blockers on one issue.
226
+ //
227
+ // The PATTERN is the defect. Any hand-picked subset of a
228
+ // finding's attributes has two findings that agree on it, so
229
+ // counting the collisions was never going to end. `detail` is the
230
+ // one value that cannot collide by construction: `findings.ts`
231
+ // composes it from the specific edge or members the finding is
232
+ // about, and documents it as "what the reader can be told, in one
233
+ // sentence" — so two findings with one sentence are one finding.
234
+ //
235
+ // IT ADDS NO ENGLISH. `detail` is already this card's visible
236
+ // prose, so nothing is announced that is not on screen, and the
237
+ // host's word still leads. Appended, never interpolated, the
238
+ // `WorkspaceWords.whyRank` idiom.
239
+ //
240
+ // A LONG NAME IS THE RIGHT TRADE. In a button list a name is an
241
+ // identifier before it is a label, and a distinct sentence beats
242
+ // a short ambiguous phrase; the VISIBLE label stays the short
243
+ // word.
244
+ //
245
+ // `WorkspaceWords.remove` has the same shape one leaf over and is
246
+ // deliberately untouched — filed as #176 rather than widened.
247
+ 'data-ig-target': target,
248
+ 'aria-label': `${words.show} ${finding.detail}`,
249
+ }, [words.show]),
250
+ // §17d's REMEDIES. Drawn after the navigation control, which is the
251
+ // frame's order: `Show the loop` then `pick one edge to drop`.
252
+ remedySpec(finding, known, edges, words),
253
+ ]);
254
+ }
255
+ /**
256
+ * The remedy controls for one finding, or `null` where the frame draws none.
257
+ *
258
+ * NOT ONE OF THEM WRITES. `RULINGS.md` §3 requires remedies and forbids
259
+ * auto-fix in one breath, and the mechanism it names is the same for all of
260
+ * them: *"Taking a remedy selects the finding's issue or edge and hands the
261
+ * work to the inspector, which already owns every edit affordance."* So every
262
+ * button here publishes a SELECTION, and the label says what the reader has
263
+ * decided to do once they are there.
264
+ *
265
+ * THE SUBJECT IS THE FINDING'S EDGE WHERE IT HAS ONE. {@link AuditFinding.edge}
266
+ * carries it for the two edge classes; a cycle names no single edge on purpose
267
+ * — choosing which to drop is the judgment §17d keeps with the owner — so its
268
+ * remedy selects the walk's head instead and the reader picks from there.
269
+ *
270
+ * WITHHELD RATHER THAN DRAWN INERT when the subject is missing. A finding whose
271
+ * edge the drawn document does not carry would give a button that selects
272
+ * nothing: §17d's own `Show the loop` already declines for exactly that reason
273
+ * one function up, and this follows it rather than inventing a second rule.
274
+ */
275
+ function remedySpec(finding, known, edges, words) {
276
+ const { remedies } = words;
277
+ // A SELECT-EDGE BUTTON, BUILT ONCE. The two edge classes differ only in their
278
+ // label, so the shape is written once and the difference stays a word.
279
+ // THE EDGE ITSELF MUST BE DRAWN — not merely its two ends.
280
+ //
281
+ // A remedy publishes `select-edge`, and the host resolves that against the
282
+ // document IT is drawing, which is not always the one the audit read. A
283
+ // remedy naming an edge that document does not hold is a button that clears
284
+ // the reader's selection and opens an inspector with no subject.
285
+ //
286
+ // THE FIRST VERSION OF THIS GUARD ASKED WHETHER BOTH ENDS WERE KNOWN, and a
287
+ // review round reproduced the hole: two issues drawn, the edge between them
288
+ // omitted — which `mount.ts` does on purpose — and both remedies rendered
289
+ // anyway. Endpoint presence is a PROXY for edge presence and they come apart
290
+ // exactly where it matters.
291
+ //
292
+ // `Show the loop` follows the same rule one function up, asking `known` about
293
+ // the ref it will actually navigate to. This asks `edges` about the id it
294
+ // will actually select.
295
+ const reachable = (edge) => edges.has(edge.id);
296
+ const onEdge = (label, tag) => finding.edge === undefined || !reachable(finding.edge)
297
+ ? null
298
+ : element('button', {
299
+ type: 'button',
300
+ class: 'ig-audit-remedy',
301
+ 'data-ig-remedy': tag,
302
+ // `select-edge`, WHICH SELECTS AND DOES NOT WRITE. The inspector
303
+ // draws the edge's own controls once it is selected; this only
304
+ // says which edge the reader is judging.
305
+ 'data-ig-command': 'select-edge',
306
+ 'data-ig-target': finding.edge.id,
307
+ // THE FINDING'S OWN SENTENCE, for the reason `Show the loop`
308
+ // records above: in a screen reader's button list the name is all
309
+ // a reader has, and any hand-picked subset of a finding's
310
+ // attributes has two findings that agree on it.
311
+ 'aria-label': `${label} ${finding.detail}`,
312
+ }, [label]);
313
+ switch (finding.kind) {
314
+ case 'stale-blocker': {
315
+ // TWO CONTROLS, AND THE SECOND IS GATED ON THE FINDING'S OWN FIELD rather
316
+ // than on the class. `keepAsHistory` is read from `AUDIT_CLASS_SPECS` and
317
+ // is `true` for this class alone — it predates this control and was
318
+ // waiting for it — so asking the finding keeps the two in step instead of
319
+ // restating the table here.
320
+ const remove = onEdge(remedies.removeEdge, 'remove-edge');
321
+ const keep = finding.keepAsHistory ? onEdge(remedies.keepAsHistory, 'keep-as-history') : null;
322
+ const both = [remove, keep].filter((spec) => spec !== null);
323
+ return both.length === 0 ? null : element('div', { class: 'ig-audit-remedies' }, both);
324
+ }
325
+ case 'dead-duplicate-ref': {
326
+ const repoint = onEdge(remedies.repoint, 'repoint');
327
+ return repoint === null ? null : element('div', { class: 'ig-audit-remedies' }, [repoint]);
328
+ }
329
+ case 'cycle': {
330
+ // THE WALK'S HEAD, NOT AN EDGE. See this function's header: the cycle is
331
+ // about a component, and the reader chooses the edge in the inspector.
332
+ // `walk` is absent whenever the reader declined to order the component,
333
+ // and then there is no defensible head to name — so no button.
334
+ const head = finding.walk?.[0];
335
+ // AND THE HEAD HAS TO BE DRAWN, for the reason `reachable` records above.
336
+ if (head === undefined || !known.has(head))
337
+ return null;
338
+ return element('div', { class: 'ig-audit-remedies' }, [
339
+ element('button', {
340
+ type: 'button',
341
+ class: 'ig-audit-remedy',
342
+ 'data-ig-remedy': 'pick-edge',
343
+ // `reveal-issue`, NEVER `select-issue`, for the reason the
344
+ // navigation control above records: with a draft awaiting its
345
+ // target, `workspace/host.ts` reads a POINTER as choosing that
346
+ // target and emits a create proposal — so `select-issue` here
347
+ // would declare a relationship while claiming to remove one.
348
+ 'data-ig-command': 'reveal-issue',
349
+ 'data-ig-target': head,
350
+ 'aria-label': `${remedies.pickEdge} ${finding.detail}`,
351
+ }, [remedies.pickEdge]),
352
+ ]);
353
+ }
354
+ // NO REMEDY. The refusal's controls are `refusedOpen` and `refusedRewrite`
355
+ // in `./refused.ts`, and this panel does not list that class at all.
356
+ default:
357
+ return null;
358
+ }
359
+ }
360
+ /**
361
+ * The panel, or `null` when there is nothing to list.
362
+ *
363
+ * `null` RATHER THAN AN EMPTY PANEL, and it is not the same decision as the
364
+ * header count's. The count is drawn at zero on purpose — `./surface.ts`:
365
+ * *"a control that appears when there is bad news is a control the eye has to
366
+ * re-find"* — because it IS the control. A list of nothing is not a control; it
367
+ * is a heading over an empty region in a column whose space belongs to the
368
+ * selection. So the ambient half stays put at zero and the list goes away.
369
+ *
370
+ * IT IS THE LISTED FINDINGS THAT DECIDE, NOT `overlay.findings`. A document
371
+ * whose only findings are encoding refusals has a non-zero count, a bar on
372
+ * every affected row, and NOTHING for this panel to list — so it draws the
373
+ * refused block and no panel at all. Testing the overlay instead would have
374
+ * drawn a panel whose head said `2` above an empty list.
375
+ */
376
+ export function renderAuditPanel(overlay, options) {
377
+ const listed = overlay.findings.filter((finding) => finding.kind !== 'encoding-refused');
378
+ if (listed.length === 0)
379
+ return null;
380
+ const { words } = options;
381
+ return element('section', {
382
+ class: 'ig-audit-panel',
383
+ // A `section` WITH NO NAME IS NOT A LANDMARK AT ALL — the implicit `region`
384
+ // role is conditional on an accessible name, so an unnamed one is exposed
385
+ // as nothing and a reader navigating by landmark cannot find the panel.
386
+ // `firstpass/render.ts` already names its own section from its words; this
387
+ // is the same rule one leaf over.
388
+ 'aria-label': words.heading,
389
+ // A TAB STOP, AND THE REASON IS NOW THE SECOND ONE ALONE. It was the
390
+ // scroll container itself while #177's half-column bound sat on this
391
+ // element; since §17d's fourth class moved to `./refused.ts` the bound and
392
+ // the scrolling belong to the REGION holding both surfaces, and this panel
393
+ // does not shrink inside it. So "there is content only scrolling reaches"
394
+ // is no longer a fact about the panel — but the stop stays, because:
395
+ //
396
+ // ITS CARDS CANNOT BE RELIED ON TO CARRY THE KEYBOARD. `cardSpec` draws
397
+ // its navigation control only for a member the drawn document actually
398
+ // holds, so an audit whose findings all name issues outside the loaded
399
+ // page renders no button at all. That panel has no focusable descendant,
400
+ // and browsers disagree about putting a generic scroll container in the
401
+ // tab order — some do, some never have. Declaring it removes the
402
+ // disagreement rather than depending on which engine is running.
403
+ //
404
+ // It is already named, so this makes a NAMED region focusable rather than
405
+ // adding an anonymous stop. `styles.ts` draws the ring it now needs.
406
+ tabindex: '0',
407
+ }, [
408
+ element('div', { class: 'ig-audit-panel-head' }, [
409
+ // THE MARK IS THE FRAME'S, AND IT IS NOT A WORD. `◆` is the same glyph
410
+ // §17a puts before the ambient count, so the panel and the chip that
411
+ // counts it read as one thing. `aria-hidden`, because it says nothing a
412
+ // reader needs said twice — the count and the heading beside it do.
413
+ element('span', { class: 'ig-audit-mark', 'aria-hidden': 'true' }, ['◆']),
414
+ // THIS PANEL'S OWN CARDS, NOT `overlay.count`, AND THE TWO ARE NOW
415
+ // DIFFERENT NUMBERS. `./surface.ts`'s header count is the persistent
416
+ // control §17d gives one job — "always the same click" — and it counts
417
+ // every finding, because the reader works through all four classes. This
418
+ // number is ENCLOSED IN THE SAME BOUNDED, SCROLLING SECTION AS THE LIST
419
+ // UNDER IT, so once `encoding-refused` moved to `./refused.ts` it would
420
+ // have printed `4` directly above three cards.
421
+ //
422
+ // THE FRAME AGREES: §17d's panel head reads `3 encoding problems` over
423
+ // exactly its own three cards, with the refusal a separate card outside.
424
+ // A count enclosed by the thing it counts is a count OF that thing.
425
+ element('span', { class: 'ig-audit-panel-count' }, [String(listed.length)]),
426
+ // A REAL HEADING, AT THE LEVEL ITS PLACE IN THE ZONE EARNS. A `span` left
427
+ // the panel invisible to heading navigation and put a gap in the column's
428
+ // outline; the caps are the stylesheet's, as they are for every other
429
+ // heading here, so the host's word stays a word.
430
+ //
431
+ // `h2`, NOT `h3`, AND THE LEVEL FOLLOWS THE COMPOSITION. It was an `h3`
432
+ // while the panel was drawn INSIDE `.ig-inspector`, under that column's
433
+ // own `h2`. The panel is a SIBLING of it now, and `renderWorkspace`
434
+ // concatenates the panel first — so an `h3` announced a level three
435
+ // before the level two it claimed to sit under, describing a nesting the
436
+ // markup does not have. Two peers, two `h2`s.
437
+ element('h2', { class: 'ig-audit-panel-heading' }, [words.heading]),
438
+ // §17d's OWN ENTRY POINT, drawn where the frame draws it. `RULINGS.md` §2
439
+ // already settled what it does — *"The filter hides ROWS. It never
440
+ // touches the ORDER"* — and §3 moved it here from the header count so
441
+ // that count could become this panel's anchor.
442
+ //
443
+ // `aria-pressed` IS RIGHT HERE AND WRONG ON THE HEADER. This one really
444
+ // is a toggle with an ON state; the header's is a disclosure.
445
+ element('button', {
446
+ type: 'button',
447
+ class: 'ig-audit-filter',
448
+ 'aria-pressed': options.filtered === true ? 'true' : 'false',
449
+ // ONE CHANNEL, NOT TWO. The mount routes this on the published
450
+ // attribute alone, and a control carrying `data-ig-command` as well
451
+ // would be two entries in the a11y baseline for one button.
452
+ [AUDIT_FILTER_ATTRIBUTE]: '',
453
+ }, [words.filter]),
454
+ ]),
455
+ // ORDERED BY THE CLASS TABLE, WHICH `auditOverlay` ALREADY DID. `findings`
456
+ // arrives in `AUDIT_CLASSES` order because `auditDocument` concatenates its
457
+ // four detectors in that order; re-sorting here would be a second opinion
458
+ // about severity next to `AUDIT_CLASS_SPECS`, which is the one that ranks.
459
+ element('ul', { class: 'ig-audit-list' }, listed.map((finding) => cardSpec(finding, options.known, options.edges, words))),
460
+ ]);
461
+ }
462
+ //# sourceMappingURL=panel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"panel.js","sourceRoot":"","sources":["../../src/audit/panel.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,99 @@
1
+ /**
2
+ * §17d's fourth class, drawn on its own rather than in the findings list.
3
+ *
4
+ * `./panel.ts` lists the three RELATIONSHIP findings — a cycle, a stale
5
+ * blocker, a dead duplicate ref. This module draws the fourth, and the
6
+ * separation is SPEC's rather than a layout preference: an encoding refusal is
7
+ * *"surfaced on the issue itself, because until it parses the issue has no
8
+ * edges at all and would otherwise look simply unencoded"*. The other three say
9
+ * something about a relationship that exists; this one says the relationships
10
+ * cannot be read, which is a statement about an ISSUE.
11
+ *
12
+ * ## It reads the host's refusals, not the audit's findings
13
+ *
14
+ * The two are the same set — `auditDocument` turns every refusal into a finding
15
+ * — but only the refusal carries `diagnostic` and `sourceLine`, and only the
16
+ * refusal is meant to. `AuditFinding` is one shape over four classes, so
17
+ * declaring two fields three of them can never hold would render an absence as
18
+ * a value in the module whose fourth class exists to refuse that; and
19
+ * `AuditFinding.detail` is "one sentence, never parsed, never a code", so
20
+ * recovering a source line out of it would make a sentence a wire format. See
21
+ * {@link EncodingRefusal}.
22
+ *
23
+ * THE COST IS ONE DUPLICATED RULE AND IT IS STATED RATHER THAN HIDDEN.
24
+ * `encodingRefusedFindings` deduplicates refusals by ref, first occurrence
25
+ * winning, and {@link refusalsToDraw} applies the same rule here — so a host
26
+ * that reports one issue twice gets one finding and one card rather than one
27
+ * and two. `findings.ts` warns against exactly this shape ("a renderer doing it
28
+ * would leave the HEADER COUNT wrong"), and the warning is answered rather than
29
+ * ignored: the count is not computed here, the rule is one line rather than a
30
+ * detector, and `./panel.test.ts` pins the arithmetic across both surfaces.
31
+ *
32
+ * The alternative was to drive this off `overlay.findings` filtered to the
33
+ * class, using the refusals only as a `ref -> fields` map. That makes the two
34
+ * surfaces a partition of one list and cannot drift by construction — a real
35
+ * advantage — but it makes the block read a finding whose only role at that
36
+ * point is to be a key, in order to learn a ref it was already handed.
37
+ *
38
+ * ## Nothing here decides that a refusal is worth drawing
39
+ *
40
+ * A refusal naming an issue the drawn page does not carry STILL DRAWS ITS CARD.
41
+ * `findings.ts` keeps such a refusal on purpose — filtering to the loaded set
42
+ * would drop findings on exactly the issues a paging boundary has not reached —
43
+ * and `./surface.ts` deliberately gives it no rail row. What it loses is the
44
+ * move that needs a row: see {@link revealControl}. It keeps the outward link,
45
+ * because leaving the application does not need a local row, and it keeps its
46
+ * prose, because the finding is true.
47
+ *
48
+ * ## The block is a tab stop for the same reason the panel is
49
+ *
50
+ * Every control here is conditional, so a block whose refusals all name
51
+ * unloaded issues, with no URL resolver supplied, has NO focusable descendant —
52
+ * and browsers disagree about putting a generic container in the tab order.
53
+ * `./panel.ts` met this and settled it; this is the same answer one leaf over.
54
+ *
55
+ * @see https://github.com/autnmy/issuegraph/blob/main/SPEC.md
56
+ */
57
+ import type { IssueRef } from '@issuegraph/store';
58
+ import { type ElementSpec } from '@issuegraph/viewer';
59
+ import type { EncodingRefusal } from './findings.ts';
60
+ import { type AuditWords } from './panel.ts';
61
+ export interface EncodingRefusedBlockOptions {
62
+ readonly words: AuditWords;
63
+ /**
64
+ * The keys the DRAWN surface carries.
65
+ *
66
+ * THE SAME SET AND THE SAME RULE `./panel.ts` AND `holdRow` USE, and it is
67
+ * the caller's answer rather than one derived here: a host audits the whole
68
+ * repository it holds and renders a page of it, so a ref can be audited and
69
+ * still have no row to arrive at. Neither this module nor the overlay can see
70
+ * that difference.
71
+ */
72
+ readonly known: ReadonlySet<string>;
73
+ /**
74
+ * Where this issue lives, in the host's own world.
75
+ *
76
+ * THE HOST'S ANSWER BECAUSE IT IS THE ONLY PARTY THAT HAS ONE. An
77
+ * {@link IssueRef} is opaque here and this package holds no repository
78
+ * identity — `check:isolation` is what keeps it that way — so a URL is not
79
+ * derivable, only supplied.
80
+ *
81
+ * ABSENT, `null`, OR NOT LINKABLE ALL MEAN THE SAME THING: no link is drawn.
82
+ * The last of those is not tidiness. An `href` runs in the host's origin, so
83
+ * `javascript:` and `data:` are script-execution sinks that escaping does not
84
+ * close — `renderMarkup` escapes attribute TEXT and has no opinion about
85
+ * schemes. `@issuegraph/viewer` already owns that allowlist and canonicalizes
86
+ * the way a URL parser does, which is what a naive scan gets wrong; a second
87
+ * copy out here would be the mirror whose input space drifts.
88
+ */
89
+ readonly issueUrl?: ((ref: IssueRef) => string | null) | undefined;
90
+ }
91
+ /**
92
+ * The refused block, or `null` when the host reported no refusals.
93
+ *
94
+ * `null` RATHER THAN AN EMPTY BLOCK, the same call `renderAuditPanel` makes: a
95
+ * heading over an empty region is not a control, and this zone's space belongs
96
+ * to the selection.
97
+ */
98
+ export declare function renderEncodingRefusedBlock(encodingRefused: readonly EncodingRefusal[], options: EncodingRefusedBlockOptions): ElementSpec | null;
99
+ //# sourceMappingURL=refused.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"refused.d.ts","sourceRoot":"","sources":["../../src/audit/refused.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,KAAK,WAAW,EAAuB,MAAM,oBAAoB,CAAC;AAE3E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAwB,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAanE,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;IAC3B;;;;;;;;OAQG;IACH,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACpC;;;;;;;;;;;;;;;OAeG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,GAAG,EAAE,QAAQ,KAAK,MAAM,GAAG,IAAI,CAAC,GAAG,SAAS,CAAC;CACpE;AAwMD;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CACxC,eAAe,EAAE,SAAS,eAAe,EAAE,EAC3C,OAAO,EAAE,2BAA2B,GACnC,WAAW,GAAG,IAAI,CA+BpB"}