@issuegraph/editor 0.4.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/README.md +31 -6
  2. package/dist/a11y/baseline.d.ts +182 -0
  3. package/dist/a11y/baseline.d.ts.map +1 -0
  4. package/dist/a11y/baseline.js +430 -0
  5. package/dist/a11y/baseline.js.map +1 -0
  6. package/dist/audit/styles.d.ts +1 -1
  7. package/dist/audit/styles.d.ts.map +1 -1
  8. package/dist/audit/styles.js +1 -1
  9. package/dist/create/draft.d.ts +17 -0
  10. package/dist/create/draft.d.ts.map +1 -1
  11. package/dist/create/draft.js +19 -0
  12. package/dist/create/draft.js.map +1 -1
  13. package/dist/create/keys.d.ts +70 -5
  14. package/dist/create/keys.d.ts.map +1 -1
  15. package/dist/create/keys.js +78 -17
  16. package/dist/create/keys.js.map +1 -1
  17. package/dist/firstpass/candidates.d.ts +17 -1
  18. package/dist/firstpass/candidates.d.ts.map +1 -1
  19. package/dist/index.d.ts +6 -4
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +12 -2
  22. package/dist/index.js.map +1 -1
  23. package/dist/overlay/render.d.ts.map +1 -1
  24. package/dist/overlay/render.js +54 -14
  25. package/dist/overlay/render.js.map +1 -1
  26. package/dist/overlay/styles.d.ts +1 -1
  27. package/dist/overlay/styles.d.ts.map +1 -1
  28. package/dist/overlay/styles.js +40 -0
  29. package/dist/overlay/styles.js.map +1 -1
  30. package/dist/picker/render.d.ts +26 -16
  31. package/dist/picker/render.d.ts.map +1 -1
  32. package/dist/picker/render.js +25 -36
  33. package/dist/picker/render.js.map +1 -1
  34. package/dist/picker/styles.d.ts +7 -7
  35. package/dist/picker/styles.d.ts.map +1 -1
  36. package/dist/picker/styles.js +7 -36
  37. package/dist/picker/styles.js.map +1 -1
  38. package/dist/picker/view.d.ts +8 -4
  39. package/dist/picker/view.d.ts.map +1 -1
  40. package/dist/picker/view.js +4 -3
  41. package/dist/picker/view.js.map +1 -1
  42. package/dist/picker/words.d.ts +12 -10
  43. package/dist/picker/words.d.ts.map +1 -1
  44. package/dist/picker/words.js +12 -8
  45. package/dist/picker/words.js.map +1 -1
  46. package/dist/scale/ladder.d.ts.map +1 -1
  47. package/dist/scale/ladder.js +10 -3
  48. package/dist/scale/ladder.js.map +1 -1
  49. package/dist/scale/render.d.ts.map +1 -1
  50. package/dist/scale/render.js +9 -1
  51. package/dist/scale/render.js.map +1 -1
  52. package/dist/workspace/chrome.d.ts +1 -1
  53. package/dist/workspace/chrome.d.ts.map +1 -1
  54. package/dist/workspace/chrome.js +48 -0
  55. package/dist/workspace/chrome.js.map +1 -1
  56. package/dist/workspace/firstpass.d.ts +166 -0
  57. package/dist/workspace/firstpass.d.ts.map +1 -0
  58. package/dist/workspace/firstpass.js +151 -0
  59. package/dist/workspace/firstpass.js.map +1 -0
  60. package/dist/workspace/host.d.ts +200 -10
  61. package/dist/workspace/host.d.ts.map +1 -1
  62. package/dist/workspace/host.js +420 -42
  63. package/dist/workspace/host.js.map +1 -1
  64. package/dist/workspace/inspector.d.ts +45 -1
  65. package/dist/workspace/inspector.d.ts.map +1 -1
  66. package/dist/workspace/inspector.js +11 -0
  67. package/dist/workspace/inspector.js.map +1 -1
  68. package/dist/workspace/mount.d.ts +52 -5
  69. package/dist/workspace/mount.d.ts.map +1 -1
  70. package/dist/workspace/mount.js +1366 -58
  71. package/dist/workspace/mount.js.map +1 -1
  72. package/dist/workspace/recovery.d.ts +155 -0
  73. package/dist/workspace/recovery.d.ts.map +1 -0
  74. package/dist/workspace/recovery.js +156 -0
  75. package/dist/workspace/recovery.js.map +1 -0
  76. package/dist/workspace/render.d.ts +402 -1
  77. package/dist/workspace/render.d.ts.map +1 -1
  78. package/dist/workspace/render.js +941 -35
  79. package/dist/workspace/render.js.map +1 -1
  80. package/dist/workspace/styles.d.ts +1 -1
  81. package/dist/workspace/styles.d.ts.map +1 -1
  82. package/dist/workspace/styles.js +554 -41
  83. package/dist/workspace/styles.js.map +1 -1
  84. package/package.json +3 -3
package/README.md CHANGED
@@ -138,7 +138,7 @@ let { draft, proposal } = createReducer(IDLE_CREATE_DRAFT, { kind: 'begin', sour
138
138
 
139
139
  **The draft carries no path identity**, deliberately. A `source` filled by a drag and one filled by `R` are the same fact, and a field recording which arrived would be a place for the paths to grow apart. What genuinely differs between them is where the picker is *drawn*, and that is geometry — `pickerPlacement`, from measured bounds — rather than state.
140
140
 
141
- **Direction is the gather order**, `from` = source. Nothing infers it: §17b states direction and offers a flip, and the picker re-derives after the edit lands, so a wrong guess is one act from correct. That is the same reasoning `picker/view.ts` records for retyping across the directed/symmetric split.
141
+ **Direction is the gather order**, `from` = source. Nothing infers it: §17b states direction and offers a flip — both on the selected relationship's own row, drawn by `renderWorkspace`, so a host that wires the published attributes without mounting gets them too — and `pickerView` re-derives after the edit lands, so a wrong guess is one act from correct. That is the same reasoning `picker/view.ts` records for retyping across the directed/symmetric split.
142
142
 
143
143
  **The keyboard is a full loop with no pointer step.** `keyIntent` is a pure key map — a key **press** and a context in, an intent out, no DOM — exactly as the viewer's `navigation.ts` is, so the whole map is exhaustively testable on a runtime with no DOM at all. The digits read `EDGE_FIELDS` from `@issuegraph/core` rather than restating it, so a sixth field gets a `6` for free and the picker and the keyboard cannot disagree. `⌫` binds **both** `Backspace` and `Delete`, because the key §17b draws as `⌫` reports differently across keyboards and binding one would make "no pointer" false on the other. An unbound key answers `none` and is left to the host.
144
144
 
@@ -154,7 +154,7 @@ element.addEventListener('keydown', (event) => {
154
154
  selectedEdge,
155
155
  // Which of the create flow's own interactions is the keyboard in?
156
156
  // Only the shell can see this.
157
- interaction: activeInteraction(), // 'canvas' | 'target-search' | 'elsewhere'
157
+ interaction: activeInteraction(), // 'canvas' | 'kind-chooser' | 'target-search' | 'elsewhere'
158
158
  });
159
159
  if (intent.kind === 'none') return; // someone else's key — let it through
160
160
  event.preventDefault();
@@ -164,17 +164,22 @@ element.addEventListener('keydown', (event) => {
164
164
 
165
165
  **It asks about *our* interaction, not about who else might own the key** — and that is the design decision worth reading, because it replaced the obvious one. Four review rounds each found a different owner the map had failed to anticipate: the platform's `Cmd+R`, the target search's digits, an input method's `⏎`, then an unrelated editable control's `Escape`. Every fix was correct and every one invited the next, because they answered an unanswerable question. *Who else might own this press?* is an inventory of the **host's** widgets — unbounded from in here, and one entry longer every time a host grows a control.
166
166
 
167
- So `CreateInteraction` enumerates **this design's own flow**, which §17b fixes at three states, and the host says which one it is in:
167
+ So `CreateInteraction` enumerates **this design's own flow**, which §17b fixes at four states, and the host says which one it is in:
168
168
 
169
169
  | state | what reaches the map |
170
170
  |---|---|
171
171
  | `canvas` | every binding |
172
+ | `kind-chooser` | `R`, the digits `1`–`5`, and `Escape` — **not** `⏎` |
172
173
  | `target-search` | only `⏎` and `Escape` |
173
174
  | `elsewhere` | nothing |
174
175
 
175
- `elsewhere` is what closes the set: it is *everything that is not our own search box* — an inline title, a filter, a modal, a control this package has never heard of. A fifth widget adds no code here, and `Escape` is surrendered along with the rest, because that control needs `Escape` to cancel its own edit.
176
+ `elsewhere` is what closes the set: it is *everything that is not one of our own steps* — an inline title, a filter, a modal, a control this package has never heard of. A fifth widget adds no code here, and `Escape` is surrendered along with the rest, because that control needs `Escape` to cancel its own edit.
176
177
 
177
- Which bindings reach `target-search` is **data on the binding table**, so no call site decides it and a sixth binding is a compile error until the table answers. `⏎` and `Escape` reach it because the search box is focused at exactly the moment `⏎` must commit the target — the middle of `R → digit → search → ⏎`. Its printable keys do not: most issue references carry a digit, so a map that claimed `1`–`5` there would eat nearly every query, and `⌫` deletes a *character* rather than the reader's selected edge.
178
+ **`kind-chooser` is a fact about the DRAFT, not about focus** — a source gathered and no kind yet, with a focused text box excluded so a search box keeps its own digits. It was folded into `canvas` until [#152](https://github.com/autnmy/issuegraph/issues/152), on the assumption that focus stays on the row a draft was begun from; activating the add control *replaces* that control with the kind list, so focus lands wherever the host's restore puts it, and where a filter has emptied the list there is no row to land on at all. `canvas` answers only for a focused row, so the chooser's own digits were handed back at the one step whose entire purpose is to be answered with a digit.
179
+
180
+ `⏎` is withheld there deliberately, and it is the reason this is a state rather than an alias for `canvas`: `commit-target` fires on `match`, a target query survives a restarted draft, and a `⏎` at the kind step would commit a target before any kind was chosen — while also being taken from the kind button under focus.
181
+
182
+ Which bindings survive `kind-chooser` and `target-search` is **data on the binding table**, so no call site decides it and a sixth binding is a compile error until the table answers. `⏎` and `Escape` reach it because the search box is focused at exactly the moment `⏎` must commit the target — the middle of `R → digit → search → ⏎`. Its printable keys do not: most issue references carry a digit, so a map that claimed `1`–`5` there would eat nearly every query, and `⌫` deletes a *character* rather than the reader's selected edge.
178
183
 
179
184
  `interaction` is **required, not optional**. Every default is wrong for some host, and the plausible one — assume the canvas — is the one that steals keystrokes.
180
185
 
@@ -229,6 +234,20 @@ The selection reaches the **canvas** too, through two additive options on `Scale
229
234
 
230
235
  Ranking a unit's members is `heaviestRow`'s job, in the audit module, **because the weights live there**. The shortcut — take the first matching entry in `overlay.rows` — is wrong in a way that looks right: those rows are sorted by `ref`, lexicographically, so a `stale-blocker` on `a` masks a `cycle` on `b`. A row's severity is the heaviest across its **members**, not its lead. A `together-with` unit is one row and several refs, and a finding can name a member that does not lead — read off the lead alone, an affected unit renders clean, which is the audit failing silently on exactly the rows where an encoding error is hardest to see.
231
236
 
237
+ **An unsettled write the reader can act on gets a card.** §17b draws `failed` as a ghost with a ✕ and `conflict` as a doubled line, and gives each a set of resolutions: `retry` and `discard mine` for a failure, plus `view diff` and `retry on latest` for a conflict — and **never** an auto-merge. `WorkspaceOptions.recoveries` is the narrow input those cards need: the state, the reason or the difference, and the panel it belongs on. Not the write ledger, which carries a mutation and an entire second document per conflict.
238
+
239
+ **The buttons are read from `OVERLAY_TREATMENTS`, which is what makes the prohibition structural.** A card emits one control per affordance the grammar table declares for its state, through a mapping declared `satisfies Record<OverlayAffordance, string>` — so a fourth affordance is a compile error rather than a control that renders and does nothing, and since `OverlayAffordance` has no `merge` member no table entry can produce one. Be exact about what that buys: **no merge button can be drawn**. The behavioural guarantee is one layer down, in a store with no merge call and a `HostEffect` with no merge arm.
240
+
241
+ `retry` and `retryOnLatest` are separate words because they are separate calls. The second re-reads and **adopts the upstream document as the new base** before dispatching again, so the order can move on the press — a single word would make one card understate what its button does.
242
+
243
+ **Every card control carries `data-ig-target` naming its write.** The reducer's `retry` and `discard` arms emit no effect without it, so an omitted attribute leaves three controls that render, read correctly and do nothing. `view-diff` is a `control` case beside them rather than a command arm of its own, because the shell turns every `data-ig-command` into a `control` and an arm outside that shape has no route from a button.
244
+
245
+ **The difference is narrowed by the panel's key set, not by the carrier.** An issue panel speaks for every member of its slot, so a together unit's lead states a partner's conflict — and scoping the difference to the carrier alone would drop the partner's own edge from the one panel entitled to show it. The reader's side of that difference is rebuilt from the mutation through `edgeChangeFor`, because the landed document by contract does not carry an unlanded edit. It covers **issues as well as edges**: §17b's stated cause is that the body changed upstream, and an edges-only difference is empty for the commonest conflict there is.
246
+
247
+ **A recovery with no panel is still drawn.** Where a refusal whose carrier is `null` is dropped — there is nowhere to state it, and the cost is a sentence — a recovery in that position keeps the reader's only retry and discard, so it goes in a region at the foot of the panel under a heading that says why it is not on a relationship.
248
+
249
+ **Not yet drawn: the gold double line.** §17b's conflict *treatment* is a doubled line holding both versions, and `second-version` is a mark for whoever computed the layout. The card is the *affordance* surface; the treatment is [#102](https://github.com/autnmy/issuegraph/issues/102).
250
+
232
251
  **Dark only.** The pass-2 brief carries "light + dark" over from pass 1; light was cut after that pass. There is no forked token set and no `prefers-color-scheme` block — the palette is the viewer's, reached through its custom properties.
233
252
 
234
253
  ## The mount
@@ -259,11 +278,17 @@ handle.destroy(); // every listener off, the nodes it built rem
259
278
 
260
279
  **The canvas draws each edge's write states.** `renderWorkspace` and `renderScaleLadder` take `projected` — the store's own `ProjectedEdge` list — and the ladder attaches an overlay per edge the tier draws: `pending-write`'s dash, an `invalid` or `failed` cross, a `conflict`'s second version, composed with the selection halo on the same line. The mount passes `snapshot.projected` and reconciles the viewer document the host projected with it — the unsettled edges are added, and a landed edge the store hides behind a pending retype or flip is dropped — because the host projects the **order** from the landed document, which must not move for an edit that did not land, while what to **draw** is the store's projection by the store's own contract. The order's status is published on the surface as `data-order="held"` while a write is in flight, in the store's vocabulary, so a host says so without this package inventing the sentence.
261
280
 
281
+ **The first pass is composed, and the scanner is not.** `MountWorkspaceOptions.firstPass` takes one bundle — a `CandidateSource`, the queue's `FirstPassWords`, and three strings the mount draws itself (`exit`, `scanning`, `scanFailed`) — all of it or none, because a scan with no words cannot be drawn and words with no scan have nothing to draw. Supplied, §17a's entry opens a modal over the three zones: the scan, then the queue, then whatever the reader answers. Absent, the mount withholds the command and the entry is inert — the button is drawn *inside* the surface the mount rewrites on every render, so unlike a control a host draws beside the element, it cannot be intercepted from outside. Why no default scanner: the evidence §17e asks for lives in issue bodies, comments and the host's own index, which is the tracker's world and precisely what `@issuegraph/store` refuses to fetch; a heuristic in here would be one vendor's idea of a duplicate, shipped with no way to replace it. `demo/src/firstpass.ts` is a worked example of the host half.
282
+
283
+ **Consent and progress are properties of the shape.** Nothing but an `apply` builds a `Proposal` — opening, drawing, rejecting and skipping emit nothing because there is no arm on which they could — and the mount is pinned at the composed level: a drawn queue writes nothing, a whole queue answered `N`/`S` writes nothing, and one `Y` writes exactly one edge. The denominator is the candidates the host found and never the backlog size, and the mount hands the renderer a `QueueState`, a type that does not hold the backlog size at all. `⌫` steps the queue back and calls the store's own `discardMine`; it does **not** propose a compensating `delete`, so a create that already landed stays landed — the store has no undo for one, and inventing a second retraction path is what `firstpass/queue.ts` refuses in terms.
284
+
285
+ **Closing re-scans, and remembers.** A queue held across a close would go on asking about relationships the reader has since made, so it is dropped — but the candidates already applied or rejected ride outside the phase and filter the next scan, or forty rejections would come back on the next open. A `skip` is *not* filtered: §17e's `S` is "not now", and the deferred set is what a second pass is built from. Each scan carries a generation, so a slow scan answering after a close-and-reopen cannot beat the fresh one.
286
+
262
287
  **Words are the host's, as everywhere here.** `MountWords` extends `WorkspaceWords` with the picker's vocabulary and the chrome's five strings; `keys` is the one optional entry. A command the reducer does not know changes nothing, so a host's own chrome may publish `data-ig-command` on the same attribute outside the element and read it from its own listener — the demo's theme switch does exactly that, and hands the writes log's `retry` and `discard` to `handle.dispatch`.
263
288
 
264
289
  ## Status
265
290
 
266
- The first-pass review queue lands as its own change. The mount is this package's from `0.3.0`; what a host still supplies is what the specification puts there — the data source, the order, the executor holds, the words, the theme — and every listener is the mount's.
291
+ The first-pass review queue is composed from `0.6.0`: §17a's `First pass →` entry opens it, and a host supplies the one thing this package refuses to — a `CandidateSource`. §17e's multi-select bulk path is not composed yet ([#140](https://github.com/autnmy/issuegraph/issues/140)); `planBatch` and `resumeBatch` ship and are tested, and what they still need is a workspace that can hold a selection SET. The mount is this package's from `0.3.0`; what a host still supplies is what the specification puts there — the data source, the order, the executor holds, the words, the theme — and every listener is the mount's.
267
292
 
268
293
  ## Licence
269
294
 
@@ -0,0 +1,182 @@
1
+ /**
2
+ * The accessible contract of every command control, read off a rendered
3
+ * surface — so it is committed as an artifact rather than asserted in prose.
4
+ *
5
+ * ## Why a DOM node and not an `ElementSpec`
6
+ *
7
+ * There is no spec tree for the workspace to walk. `renderWorkspace` returns
8
+ * `markup: string`; it composes its four zones by joining independently
9
+ * rendered strings, the `data-zone` wrapper is the one tag written by hand in
10
+ * that file, and every `…Spec` builder is module-private. Worse for a spec
11
+ * walk, a substantial part of the interactive surface never passes a renderer
12
+ * at all — `mountWorkspace` builds the kind chooser, the target search and its
13
+ * match list with `createElement`, and says so ("WHAT IS LEFT HERE IS WHAT
14
+ * NEEDS A DOM"). A baseline taken over specs would silently omit exactly the
15
+ * keyboard-relevant controls.
16
+ *
17
+ * The rendered DOM has all of it, and it is also what a screen reader actually
18
+ * meets.
19
+ *
20
+ * ## NOT EXPORTED, and that is the correction of a real mistake
21
+ *
22
+ * An earlier revision published `controlSurface` on the package surface,
23
+ * reasoning that a host composing these zones into its own chrome would want
24
+ * the same record. Nothing asked for it, and `index.ts` states the rule it
25
+ * broke: "a published package can add an export later and can never take one
26
+ * back, so nothing is exported before something is owed."
27
+ *
28
+ * The cost was not theoretical. Exporting it turned this file into a
29
+ * general-purpose accessible-name and focusability reader, and accessible-name
30
+ * computation has a long tail — `title`, `placeholder`, an input's own `value`,
31
+ * whitespace-only labels, hidden inputs, `contenteditable`. Three consecutive
32
+ * review rounds produced findings of exactly that shape: each real, each cheap,
33
+ * and none about markup this package draws. That tail has no end, and this
34
+ * change cannot discharge it.
35
+ *
36
+ * Internal, the obligation is bounded and discharged: the reader only ever
37
+ * meets what this package renders, which is a closed set recorded in six
38
+ * states. If a host ever owes this, export it then — with its tail addressed,
39
+ * and against a consumer that actually needs it.
40
+ *
41
+ * ## Structurally typed, so this module still touches no global
42
+ *
43
+ * {@link SurfaceElement} is declared by what it must answer rather than as
44
+ * `Element`, which is `element.ts`'s own `SpecDocument` idiom — "declared
45
+ * structurally rather than as `Document` so a host can pass any implementation
46
+ * that answers these five calls, and so this module stays honest about how
47
+ * little of the DOM it actually needs". `purity.test.ts` imports every shipped
48
+ * module with the browser globals removed; nothing here reaches for one.
49
+ *
50
+ * ## What is recorded, and what is deliberately not
51
+ *
52
+ * VALUES ARE KEPT FOR MACHINE STATES AND DROPPED FOR HUMAN-READABLE NAMES.
53
+ * That split is by provenance, not one blanket rule, because the two fail
54
+ * differently. `aria-pressed` is rendered as `cond ? 'true' : 'false'`, so a
55
+ * baseline holding only the attribute NAME is byte-identical when a toggle
56
+ * inverts — the regression it exists to catch. A label, meanwhile, is often
57
+ * the host's word (`picker/words.ts`, `reevaluate/words.ts` and
58
+ * `firstpass/words.ts` all require a `Record<K, string>` from the host and
59
+ * default none), so pinning the string would fail any host that translated it.
60
+ *
61
+ * An earlier draft justified dropping every value with "the package ships no
62
+ * English", which is false: `scale/render.ts` writes `'connected components'`,
63
+ * `'what to do next'`, `'search matches'` and `'isolated issues'` itself, and
64
+ * the viewer ships more landmark names. The true, narrower claim is the one
65
+ * above.
66
+ *
67
+ * AN IDREF IS RECORDED BY WHETHER IT RESOLVES, never by its value. The
68
+ * disclosure's `aria-controls` names `diffRegionId(recovery.mutationId)`, and a
69
+ * mutation id is generated per run — pinning it would make the artifact churn
70
+ * on every render for no signal. What is worth pinning is the fact a reader
71
+ * depends on: that the id names a region that is actually there. A dangling
72
+ * `aria-controls` is a real defect and a stable one to record.
73
+ */
74
+ /** A text node, or anything else with no tag. */
75
+ export interface SurfaceNode {
76
+ readonly nodeType: number;
77
+ readonly nodeValue: string | null;
78
+ }
79
+ /** The slice of `Element` this module reads. */
80
+ export interface SurfaceElement extends SurfaceNode {
81
+ readonly tagName: string;
82
+ readonly childNodes: ArrayLike<SurfaceNode>;
83
+ getAttribute(name: string): string | null;
84
+ closest(selectors: string): SurfaceElement | null;
85
+ querySelectorAll(selectors: string): Iterable<SurfaceElement>;
86
+ }
87
+ /**
88
+ * Where a control's accessible name comes from, or why it has none.
89
+ *
90
+ * `label` IS A REAL NAME AND WAS MISSING. `scale/render.ts` wraps its search
91
+ * input in a `<label>` carrying the text — the input has a genuine accessible
92
+ * name and no text of its own, no `aria-label` and no `aria-labelledby`. Read
93
+ * without this, it came back `none`, so the naming rule reported a false
94
+ * failure on correct markup and the control could not join the baseline at all.
95
+ * An exported reader that is wrong about native HTML is worse than no reader.
96
+ *
97
+ * `empty` IS ITS OWN ANSWER, not a kind of `none`. An `aria-label=""` is an
98
+ * author who meant to supply a name and supplied nothing — a different defect
99
+ * from a control nobody labelled, and one that reads as deliberate in the
100
+ * markup. Recording it separately is what lets a rule name it. THE VALUE IS
101
+ * NEVER RECORDED: a label is routinely the host's own word (`picker/words.ts`
102
+ * and `reevaluate/words.ts` both require a `Record<K, string>` from the host
103
+ * and default none), so pinning the string would fail any host that translated
104
+ * it. Whether one was supplied is a fact about the markup; what it says is not.
105
+ */
106
+ export type NameSource = 'aria-label' | 'aria-labelledby' | 'label' | 'text' | 'empty' | 'none';
107
+ /**
108
+ * How the keyboard reaches a control.
109
+ *
110
+ * NOT A BOOLEAN, and that is the whole point of the field. This package uses a
111
+ * roving tab stop — the projections render `tabindex: focused ? 0 : -1` — and
112
+ * the viewer's own focusability predicate counts `tabindex="-1"` as focusable.
113
+ * So a boolean "focusable" is satisfied by a control reachable only by pointer
114
+ * and by programmatic focus, which is precisely the defect a keyboard record
115
+ * exists to catch.
116
+ */
117
+ export type TabStop = 'tab' | 'programmatic' | 'none';
118
+ /** One control's accessible contract. */
119
+ export interface ControlEntry {
120
+ readonly zone: string | null;
121
+ /** Which attribute published it — see {@link CONTROL_ATTRIBUTES}. */
122
+ readonly channel: string;
123
+ /** That attribute's value: the command, the answer, or the filter's state. */
124
+ readonly control: string;
125
+ /**
126
+ * What the control acts on, and which option it is.
127
+ *
128
+ * RECORDED BECAUSE THEY SEPARATE OTHERWISE IDENTICAL ROWS. Every relationship
129
+ * row publishes `select-edge`, and the kind list publishes `kind` several
130
+ * times — without these, N controls collapse to N byte-identical entries and
131
+ * losing one of them is invisible in the artifact's diff.
132
+ */
133
+ readonly target: string | null;
134
+ readonly value: string | null;
135
+ readonly tag: string;
136
+ /** `null` where this module has no mapping, which is a fact; a guess would not be. */
137
+ readonly role: string | null;
138
+ readonly tabStop: TabStop;
139
+ /**
140
+ * Whether the control sits under an `inert` subtree.
141
+ *
142
+ * DELIBERATE UNREACHABILITY IS NOT A DEFECT, and without recording it the two
143
+ * are indistinguishable. The mount sets `inert` on every zone while the
144
+ * first-pass overlay is up — "the zones go inert under it" — so those
145
+ * controls are correctly out of the tab order, and a rule that simply
146
+ * demanded reachability would fail on the modal working as designed. Recorded
147
+ * rather than filtered out, because "these eight controls are deliberately
148
+ * unreachable right now" is exactly what a reader of a modal state wants to
149
+ * see.
150
+ */
151
+ readonly inert: boolean;
152
+ readonly name: NameSource;
153
+ /** Enumerated ARIA states with their values, and IDREFs by whether they resolve. */
154
+ readonly aria: Readonly<Record<string, string>>;
155
+ }
156
+ /**
157
+ * Every attribute a control publishes itself on.
158
+ *
159
+ * THREE CHANNELS, NOT ONE, and reading only the first is how a record comes to
160
+ * claim a surface it never saw. `data-ig-command` is the general one, but the
161
+ * first pass's y/n/s answers carry `data-ig-answer` and the audit header's
162
+ * toggle carries `data-ig-audit-filter` — `firstpass/render.ts` and
163
+ * `audit/surface.ts` each say so where they declare theirs. Keyed on the
164
+ * command alone, this would have recorded `null` for the audit toggle and for
165
+ * every answer, and the rules below would have skipped exactly the controls a
166
+ * keyboard reader depends on most.
167
+ */
168
+ export declare const CONTROL_ATTRIBUTES: readonly string[];
169
+ /**
170
+ * Every control under `root`, in document order, sorted so the record is stable.
171
+ *
172
+ * Pure and total. SORTED RATHER THAN LEFT IN DOCUMENT ORDER: a baseline is
173
+ * diffed by people, and a control moving between zones should read as one
174
+ * changed line rather than as a reordering of the whole file. Document order is
175
+ * a fact about layout, which the zone already carries.
176
+ *
177
+ * The bound it does NOT cover is stated where it can be checked: a control
178
+ * publishing itself on some fourth attribute is invisible here, and
179
+ * {@link CONTROL_ATTRIBUTES} is the list to extend.
180
+ */
181
+ export declare function controlSurface(root: SurfaceElement): readonly ControlEntry[];
182
+ //# sourceMappingURL=baseline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"baseline.d.ts","sourceRoot":"","sources":["../../src/a11y/baseline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwEG;AAEH,iDAAiD;AACjD,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CACnC;AAED,gDAAgD;AAChD,MAAM,WAAW,cAAe,SAAQ,WAAW;IACjD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,UAAU,EAAE,SAAS,CAAC,WAAW,CAAC,CAAC;IAC5C,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAC1C,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAAC;IAClD,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,QAAQ,CAAC,cAAc,CAAC,CAAC;CAC/D;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,MAAM,UAAU,GAAG,YAAY,GAAG,iBAAiB,GAAG,OAAO,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;AAEhG;;;;;;;;;GASG;AACH,MAAM,MAAM,OAAO,GAAG,KAAK,GAAG,cAAc,GAAG,MAAM,CAAC;AAEtD,yCAAyC;AACzC,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,qEAAqE;IACrE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,8EAA8E;IAC9E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB;;;;;;;OAOG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,sFAAsF;IACtF,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,oFAAoF;IACpF,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACjD;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,kBAAkB,EAAE,SAAS,MAAM,EAI9C,CAAC;AA6QH;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,cAAc,GAAG,SAAS,YAAY,EAAE,CAqD5E"}