@issuegraph/editor 0.1.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 (143) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +238 -0
  3. package/dist/audit/findings.d.ts +225 -0
  4. package/dist/audit/findings.d.ts.map +1 -0
  5. package/dist/audit/findings.js +348 -0
  6. package/dist/audit/findings.js.map +1 -0
  7. package/dist/audit/styles.d.ts +37 -0
  8. package/dist/audit/styles.d.ts.map +1 -0
  9. package/dist/audit/styles.js +72 -0
  10. package/dist/audit/styles.js.map +1 -0
  11. package/dist/audit/surface.d.ts +155 -0
  12. package/dist/audit/surface.d.ts.map +1 -0
  13. package/dist/audit/surface.js +202 -0
  14. package/dist/audit/surface.js.map +1 -0
  15. package/dist/create/draft.d.ts +119 -0
  16. package/dist/create/draft.d.ts.map +1 -0
  17. package/dist/create/draft.js +114 -0
  18. package/dist/create/draft.js.map +1 -0
  19. package/dist/create/keys.d.ts +225 -0
  20. package/dist/create/keys.d.ts.map +1 -0
  21. package/dist/create/keys.js +228 -0
  22. package/dist/create/keys.js.map +1 -0
  23. package/dist/create/placement.d.ts +69 -0
  24. package/dist/create/placement.d.ts.map +1 -0
  25. package/dist/create/placement.js +76 -0
  26. package/dist/create/placement.js.map +1 -0
  27. package/dist/firstpass/batch.d.ts +190 -0
  28. package/dist/firstpass/batch.d.ts.map +1 -0
  29. package/dist/firstpass/batch.js +143 -0
  30. package/dist/firstpass/batch.js.map +1 -0
  31. package/dist/firstpass/candidates.d.ts +128 -0
  32. package/dist/firstpass/candidates.d.ts.map +1 -0
  33. package/dist/firstpass/candidates.js +39 -0
  34. package/dist/firstpass/candidates.js.map +1 -0
  35. package/dist/firstpass/keys.d.ts +90 -0
  36. package/dist/firstpass/keys.d.ts.map +1 -0
  37. package/dist/firstpass/keys.js +112 -0
  38. package/dist/firstpass/keys.js.map +1 -0
  39. package/dist/firstpass/queue.d.ts +183 -0
  40. package/dist/firstpass/queue.d.ts.map +1 -0
  41. package/dist/firstpass/queue.js +159 -0
  42. package/dist/firstpass/queue.js.map +1 -0
  43. package/dist/firstpass/render.d.ts +100 -0
  44. package/dist/firstpass/render.d.ts.map +1 -0
  45. package/dist/firstpass/render.js +216 -0
  46. package/dist/firstpass/render.js.map +1 -0
  47. package/dist/firstpass/styles.d.ts +36 -0
  48. package/dist/firstpass/styles.d.ts.map +1 -0
  49. package/dist/firstpass/styles.js +148 -0
  50. package/dist/firstpass/styles.js.map +1 -0
  51. package/dist/firstpass/view.d.ts +82 -0
  52. package/dist/firstpass/view.d.ts.map +1 -0
  53. package/dist/firstpass/view.js +60 -0
  54. package/dist/firstpass/view.js.map +1 -0
  55. package/dist/firstpass/words.d.ts +105 -0
  56. package/dist/firstpass/words.d.ts.map +1 -0
  57. package/dist/firstpass/words.js +28 -0
  58. package/dist/firstpass/words.js.map +1 -0
  59. package/dist/index.d.ts +150 -0
  60. package/dist/index.d.ts.map +1 -0
  61. package/dist/index.js +147 -0
  62. package/dist/index.js.map +1 -0
  63. package/dist/overlay/grammar.d.ts +259 -0
  64. package/dist/overlay/grammar.d.ts.map +1 -0
  65. package/dist/overlay/grammar.js +223 -0
  66. package/dist/overlay/grammar.js.map +1 -0
  67. package/dist/overlay/render.d.ts +125 -0
  68. package/dist/overlay/render.d.ts.map +1 -0
  69. package/dist/overlay/render.js +439 -0
  70. package/dist/overlay/render.js.map +1 -0
  71. package/dist/overlay/styles.d.ts +41 -0
  72. package/dist/overlay/styles.d.ts.map +1 -0
  73. package/dist/overlay/styles.js +124 -0
  74. package/dist/overlay/styles.js.map +1 -0
  75. package/dist/picker/render.d.ts +80 -0
  76. package/dist/picker/render.d.ts.map +1 -0
  77. package/dist/picker/render.js +112 -0
  78. package/dist/picker/render.js.map +1 -0
  79. package/dist/picker/styles.d.ts +26 -0
  80. package/dist/picker/styles.d.ts.map +1 -0
  81. package/dist/picker/styles.js +116 -0
  82. package/dist/picker/styles.js.map +1 -0
  83. package/dist/picker/view.d.ts +135 -0
  84. package/dist/picker/view.d.ts.map +1 -0
  85. package/dist/picker/view.js +117 -0
  86. package/dist/picker/view.js.map +1 -0
  87. package/dist/picker/words.d.ts +53 -0
  88. package/dist/picker/words.d.ts.map +1 -0
  89. package/dist/picker/words.js +28 -0
  90. package/dist/picker/words.js.map +1 -0
  91. package/dist/reevaluate/render.d.ts +77 -0
  92. package/dist/reevaluate/render.d.ts.map +1 -0
  93. package/dist/reevaluate/render.js +211 -0
  94. package/dist/reevaluate/render.js.map +1 -0
  95. package/dist/reevaluate/styles.d.ts +24 -0
  96. package/dist/reevaluate/styles.d.ts.map +1 -0
  97. package/dist/reevaluate/styles.js +182 -0
  98. package/dist/reevaluate/styles.js.map +1 -0
  99. package/dist/reevaluate/view.d.ts +102 -0
  100. package/dist/reevaluate/view.d.ts.map +1 -0
  101. package/dist/reevaluate/view.js +111 -0
  102. package/dist/reevaluate/view.js.map +1 -0
  103. package/dist/reevaluate/words.d.ts +54 -0
  104. package/dist/reevaluate/words.d.ts.map +1 -0
  105. package/dist/reevaluate/words.js +29 -0
  106. package/dist/reevaluate/words.js.map +1 -0
  107. package/dist/scale/commands.d.ts +60 -0
  108. package/dist/scale/commands.d.ts.map +1 -0
  109. package/dist/scale/commands.js +49 -0
  110. package/dist/scale/commands.js.map +1 -0
  111. package/dist/scale/ladder.d.ts +141 -0
  112. package/dist/scale/ladder.d.ts.map +1 -0
  113. package/dist/scale/ladder.js +243 -0
  114. package/dist/scale/ladder.js.map +1 -0
  115. package/dist/scale/render.d.ts +85 -0
  116. package/dist/scale/render.d.ts.map +1 -0
  117. package/dist/scale/render.js +194 -0
  118. package/dist/scale/render.js.map +1 -0
  119. package/dist/scale/styles.d.ts +21 -0
  120. package/dist/scale/styles.d.ts.map +1 -0
  121. package/dist/scale/styles.js +105 -0
  122. package/dist/scale/styles.js.map +1 -0
  123. package/dist/workspace/inspector.d.ts +117 -0
  124. package/dist/workspace/inspector.d.ts.map +1 -0
  125. package/dist/workspace/inspector.js +140 -0
  126. package/dist/workspace/inspector.js.map +1 -0
  127. package/dist/workspace/rail.d.ts +122 -0
  128. package/dist/workspace/rail.d.ts.map +1 -0
  129. package/dist/workspace/rail.js +216 -0
  130. package/dist/workspace/rail.js.map +1 -0
  131. package/dist/workspace/render.d.ts +139 -0
  132. package/dist/workspace/render.d.ts.map +1 -0
  133. package/dist/workspace/render.js +366 -0
  134. package/dist/workspace/render.js.map +1 -0
  135. package/dist/workspace/selection.d.ts +92 -0
  136. package/dist/workspace/selection.d.ts.map +1 -0
  137. package/dist/workspace/selection.js +83 -0
  138. package/dist/workspace/selection.js.map +1 -0
  139. package/dist/workspace/styles.d.ts +39 -0
  140. package/dist/workspace/styles.d.ts.map +1 -0
  141. package/dist/workspace/styles.js +313 -0
  142. package/dist/workspace/styles.js.map +1 -0
  143. package/package.json +58 -0
@@ -0,0 +1,90 @@
1
+ /**
2
+ * The first pass, as a pure key map: `Y` / `N` / `S`, and `⌫` to undo.
3
+ *
4
+ * §17e is explicit that the queue is keyboard-first, and about why: each answer
5
+ * has to take two seconds, and reaching for a pointer between questions is most
6
+ * of the two seconds. "Keyboard-first" here means the pointer is optional
7
+ * rather than assisted — the whole loop is reachable without one.
8
+ *
9
+ * ## It is `create/keys.ts`'s shape, deliberately and for its scar tissue
10
+ *
11
+ * That module took four review rounds to arrive at asking about OUR OWN
12
+ * interaction instead of trying to enumerate the host's other widgets, and it
13
+ * records the reasoning at length. This map inherits the conclusion rather than
14
+ * re-deriving it: a `KeyPress` carrying the modifier and composition facts, an
15
+ * interaction state supplied by the host, and `none` for everything else.
16
+ *
17
+ * What is NOT inherited is the interaction vocabulary. `CreateInteraction`'s
18
+ * `target-search` is a state of the CREATE flow, and this surface has no search
19
+ * box — so reusing that type would ask a host to answer a question about a flow
20
+ * it is not in. See {@link FirstPassInteraction}.
21
+ *
22
+ * ## `Y`, `N` and `S` are printable, which decides the whole table
23
+ *
24
+ * All three answers are letters. In any focused text control they are that
25
+ * control's characters, so none of them survives editing — and unlike the
26
+ * create flow there is no `⏎`-shaped exception, because nothing in this loop
27
+ * requires typing. That makes the table simpler than `create/keys.ts`'s and the
28
+ * simplicity is a fact about the design rather than a saving: a queue you
29
+ * answer by typing would not be a two-second queue.
30
+ */
31
+ import type { KeyPress } from '../create/keys.ts';
32
+ import type { QueueCommand } from './queue.ts';
33
+ /**
34
+ * Where the keyboard is, in terms of THIS surface.
35
+ *
36
+ * - `queue` — the queue has the keyboard. Every binding reaches it.
37
+ * - `elsewhere` — anything else at all: an inline edit, a filter box, the
38
+ * canvas, a modal, a control this package has never heard of. Nothing
39
+ * reaches.
40
+ *
41
+ * TWO STATES, NOT THREE, and the missing one is the point. `create/keys.ts`
42
+ * needs `target-search` because its own flow contains a text box that two of
43
+ * its bindings must survive. This flow contains none, so a third state would be
44
+ * a distinction with no binding on either side of it — and every state a host
45
+ * has to map its world onto is a chance to map it wrongly.
46
+ */
47
+ export type FirstPassInteraction = 'queue' | 'elsewhere';
48
+ /** What the shell knows when a key arrives. */
49
+ export interface FirstPassContext {
50
+ /**
51
+ * Which of this surface's interactions the keyboard is in.
52
+ *
53
+ * REQUIRED, NOT OPTIONAL, on the same reasoning `KeyboardContext.interaction`
54
+ * records: every default is wrong for some host, and the plausible default —
55
+ * "assume the queue" — is the one that steals keystrokes from a host that has
56
+ * not thought about it.
57
+ */
58
+ readonly interaction: FirstPassInteraction;
59
+ /**
60
+ * Whether a candidate is on screen.
61
+ *
62
+ * An answer with nothing to answer is `none` rather than a command the
63
+ * reducer would discard, so the host does not `preventDefault()` a key it did
64
+ * not consume. The reducer treats the same case as a no-op independently —
65
+ * two guards for one property, which is right here because they protect
66
+ * different things: this one protects the KEY, that one protects the STATE.
67
+ */
68
+ readonly hasCandidate: boolean;
69
+ /**
70
+ * Whether anything has been answered, i.e. whether `⌫` has work.
71
+ *
72
+ * Same reasoning: an undo with an empty history hands the key back.
73
+ */
74
+ readonly canUndo: boolean;
75
+ }
76
+ /** What a key means. `none` leaves the key to the host. */
77
+ export type FirstPassIntent = {
78
+ readonly kind: 'none';
79
+ } | {
80
+ readonly kind: 'queue';
81
+ readonly command: QueueCommand;
82
+ };
83
+ /**
84
+ * What one key press means, given what the shell currently holds.
85
+ *
86
+ * Total and pure. One exhaustive switch over a discriminated union, every arm a
87
+ * single expression, because the decision was already made by the table.
88
+ */
89
+ export declare function firstPassIntent(press: KeyPress, context: FirstPassContext): FirstPassIntent;
90
+ //# sourceMappingURL=keys.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../../src/firstpass/keys.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,KAAK,EAAU,YAAY,EAAE,MAAM,YAAY,CAAC;AAEvD;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,oBAAoB,GAAG,OAAO,GAAG,WAAW,CAAC;AAEzD,+CAA+C;AAC/C,MAAM,WAAW,gBAAgB;IAC/B;;;;;;;OAOG;IACH,QAAQ,CAAC,WAAW,EAAE,oBAAoB,CAAC;IAC3C;;;;;;;;OAQG;IACH,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B;AAED,2DAA2D;AAC3D,MAAM,MAAM,eAAe,GACvB;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACzB;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAA;CAAE,CAAC;AA+D/D;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,gBAAgB,GAAG,eAAe,CAuB3F"}
@@ -0,0 +1,112 @@
1
+ /**
2
+ * The first pass, as a pure key map: `Y` / `N` / `S`, and `⌫` to undo.
3
+ *
4
+ * §17e is explicit that the queue is keyboard-first, and about why: each answer
5
+ * has to take two seconds, and reaching for a pointer between questions is most
6
+ * of the two seconds. "Keyboard-first" here means the pointer is optional
7
+ * rather than assisted — the whole loop is reachable without one.
8
+ *
9
+ * ## It is `create/keys.ts`'s shape, deliberately and for its scar tissue
10
+ *
11
+ * That module took four review rounds to arrive at asking about OUR OWN
12
+ * interaction instead of trying to enumerate the host's other widgets, and it
13
+ * records the reasoning at length. This map inherits the conclusion rather than
14
+ * re-deriving it: a `KeyPress` carrying the modifier and composition facts, an
15
+ * interaction state supplied by the host, and `none` for everything else.
16
+ *
17
+ * What is NOT inherited is the interaction vocabulary. `CreateInteraction`'s
18
+ * `target-search` is a state of the CREATE flow, and this surface has no search
19
+ * box — so reusing that type would ask a host to answer a question about a flow
20
+ * it is not in. See {@link FirstPassInteraction}.
21
+ *
22
+ * ## `Y`, `N` and `S` are printable, which decides the whole table
23
+ *
24
+ * All three answers are letters. In any focused text control they are that
25
+ * control's characters, so none of them survives editing — and unlike the
26
+ * create flow there is no `⏎`-shaped exception, because nothing in this loop
27
+ * requires typing. That makes the table simpler than `create/keys.ts`'s and the
28
+ * simplicity is a fact about the design rather than a saving: a queue you
29
+ * answer by typing would not be a two-second queue.
30
+ */
31
+ /**
32
+ * The vocabulary, as data.
33
+ *
34
+ * §17e names the keys `Y` / `N` / `S` and `⌫`. The letters are mnemonics in
35
+ * English, which is a real limitation and one this package cannot fix from
36
+ * here: a rebinding belongs to the host, and the reducer takes {@link Answer}
37
+ * values rather than key names precisely so a host can bind whatever its
38
+ * readers' keyboards spell without reaching into {@link ./queue.ts}.
39
+ */
40
+ const BINDINGS = new Map([
41
+ ['y', { action: 'answer', answer: 'apply' }],
42
+ ['n', { action: 'answer', answer: 'reject' }],
43
+ ['s', { action: 'answer', answer: 'skip' }],
44
+ // BOTH SPELLINGS OF THE DELETE KEY, for the reason `create/keys.ts` records:
45
+ // §17e writes it `⌫`, which is `Backspace` on the keyboards that have it and
46
+ // `Delete` on those that do not — most notably Apple's. Binding one of the
47
+ // two would make "no pointer" false on whichever hardware got the other.
48
+ ['backspace', { action: 'undo' }],
49
+ ['delete', { action: 'undo' }],
50
+ ]);
51
+ const NONE = Object.freeze({ kind: 'none' });
52
+ /**
53
+ * The press type is `create/keys.ts`'s, imported rather than redeclared.
54
+ *
55
+ * A KEY PRESS IS A PLATFORM FACT, not a fact about either flow: the fields are
56
+ * `KeyboardEvent`'s, fixed by the DOM, and identical here for reasons that are
57
+ * identical too — a bare key name cannot tell `Y` from `Cmd+Y`, an input method
58
+ * owns every printable key while composing, and an auto-repeat is not a second
59
+ * act. Two structurally identical declarations of it would be the drifting
60
+ * second implementation this package family removes everywhere it appears, and
61
+ * they would drift in the worst possible place: the day the DOM grows a field
62
+ * one flow must honour, only one of the two would learn about it.
63
+ *
64
+ * So the shared definition is imported across, and the package's surface keeps
65
+ * exporting it exactly once, from where it is declared — a host wiring both
66
+ * surfaces holds one `KeyPress` and forwards the same event to either map.
67
+ */
68
+ /**
69
+ * Whether this press is a chord that belongs to the platform.
70
+ *
71
+ * `shiftKey` IS DELIBERATELY ABSENT — §17e names its bindings in capitals, and
72
+ * `Shift+y` is how a keyboard reports `Y`, so treating shift as a modifier
73
+ * would unbind the very keys the design specifies. Folded by {@link normalize}.
74
+ */
75
+ function chorded(press) {
76
+ return press.ctrlKey === true || press.metaKey === true || press.altKey === true;
77
+ }
78
+ /** Fold `KeyboardEvent.key` into the table's spelling — see `create/keys.ts`. */
79
+ function normalize(key) {
80
+ return key.toLowerCase();
81
+ }
82
+ /**
83
+ * What one key press means, given what the shell currently holds.
84
+ *
85
+ * Total and pure. One exhaustive switch over a discriminated union, every arm a
86
+ * single expression, because the decision was already made by the table.
87
+ */
88
+ export function firstPassIntent(press, context) {
89
+ // THE PRESS ITSELF DISQUALIFIES IT, before any lookup. A chord and a
90
+ // composition belong to someone else; a repeat is not a fresh act at all.
91
+ if (chorded(press) || press.isComposing === true || press.repeat === true)
92
+ return NONE;
93
+ // NOTHING REACHES FROM `elsewhere`. Written as an early return rather than a
94
+ // decision table on the binding because — unlike the create map — no binding
95
+ // here survives another control's focus, so a per-binding column would have
96
+ // one value in every row. `create/keys.ts` earns its table; this would be
97
+ // ceremony asserting a distinction the design does not make.
98
+ if (context.interaction !== 'queue')
99
+ return NONE;
100
+ const binding = BINDINGS.get(normalize(press.key));
101
+ if (binding === undefined)
102
+ return NONE;
103
+ switch (binding.action) {
104
+ case 'answer':
105
+ return context.hasCandidate
106
+ ? { kind: 'queue', command: { kind: 'answer', answer: binding.answer } }
107
+ : NONE;
108
+ case 'undo':
109
+ return context.canUndo ? { kind: 'queue', command: { kind: 'undo' } } : NONE;
110
+ }
111
+ }
112
+ //# sourceMappingURL=keys.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keys.js","sourceRoot":"","sources":["../../src/firstpass/keys.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AA4DH;;;;;;;;GAQG;AACH,MAAM,QAAQ,GAAiC,IAAI,GAAG,CAAkB;IACtE,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;IAC5C,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;IAC7C,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IAC3C,6EAA6E;IAC7E,6EAA6E;IAC7E,2EAA2E;IAC3E,yEAAyE;IACzE,CAAC,WAAW,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IACjC,CAAC,QAAQ,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;CAC/B,CAAC,CAAC;AAEH,MAAM,IAAI,GAAoB,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;AAE9D;;;;;;;;;;;;;;;GAeG;AAEH;;;;;;GAMG;AACH,SAAS,OAAO,CAAC,KAAe;IAC9B,OAAO,KAAK,CAAC,OAAO,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,KAAK,IAAI,IAAI,KAAK,CAAC,MAAM,KAAK,IAAI,CAAC;AACnF,CAAC;AAED,iFAAiF;AACjF,SAAS,SAAS,CAAC,GAAW;IAC5B,OAAO,GAAG,CAAC,WAAW,EAAE,CAAC;AAC3B,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,KAAe,EAAE,OAAyB;IACxE,qEAAqE;IACrE,0EAA0E;IAC1E,IAAI,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,WAAW,KAAK,IAAI,IAAI,KAAK,CAAC,MAAM,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAEvF,6EAA6E;IAC7E,6EAA6E;IAC7E,4EAA4E;IAC5E,0EAA0E;IAC1E,6DAA6D;IAC7D,IAAI,OAAO,CAAC,WAAW,KAAK,OAAO;QAAE,OAAO,IAAI,CAAC;IAEjD,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;IACnD,IAAI,OAAO,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IAEvC,QAAQ,OAAO,CAAC,MAAM,EAAE,CAAC;QACvB,KAAK,QAAQ;YACX,OAAO,OAAO,CAAC,YAAY;gBACzB,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,EAAE;gBACxE,CAAC,CAAC,IAAI,CAAC;QACX,KAAK,MAAM;YACT,OAAO,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IACjF,CAAC;AACH,CAAC"}
@@ -0,0 +1,183 @@
1
+ /**
2
+ * The first-pass review queue: one candidate, one keystroke, one answer.
3
+ *
4
+ * §17e's loop is `Y` / `N` / `S` with `⌫` to undo, over candidates the host
5
+ * found. This module is the whole of that as a reducer — the same shape
6
+ * `create/draft.ts` and `scale/commands.ts` already use here, so a host
7
+ * reducing one reduces this without learning a second protocol.
8
+ *
9
+ * ## Nothing is applied without a keystroke, and the shape is what guarantees it
10
+ *
11
+ * §17e gives the reason and it is worth quoting rather than paraphrasing: "A
12
+ * wrong `duplicate-of` silently removes real work from the order, so it always
13
+ * costs one keystroke of consent."
14
+ *
15
+ * A `duplicate-of` excludes an issue from the order ENTIRELY. Applied without
16
+ * consent, that is work vanishing from the backlog while looking handled — the
17
+ * dead-duplicate audit class arriving through automation instead of decay.
18
+ *
19
+ * So consent is structural rather than promised: the ONLY thing in this module
20
+ * that returns a non-null proposal is the `answer` command carrying
21
+ * {@link Answer} `apply`, and nothing else here constructs one at all. Advancing
22
+ * the queue, loading it, rendering it and undoing all emit `null` because there
23
+ * is no code path on which they could emit anything else. `queue.test.ts` pins
24
+ * it from the other side — a whole queue driven to exhaustion by `reject` and
25
+ * `skip` dispatches nothing — and `render.test.ts` pins the surface: drawing a
26
+ * candidate is not answering it.
27
+ *
28
+ * ## Progress is bounded by CANDIDATES, and never by backlog size
29
+ *
30
+ * "Progress is bounded by candidates found, not backlog size, so first pass has
31
+ * an end. 100% encoded is never the goal and the workspace never implies it is
32
+ * — most issues legitimately have no relationships."
33
+ *
34
+ * The denominator is therefore {@link QueueState.candidates}`.length` and this
35
+ * module is never told how many issues exist — it cannot compute the wrong
36
+ * denominator because it does not hold the number that would be wrong. An owner
37
+ * with 248 legitimately-isolated issues is not shown "20% done"; they are shown
38
+ * a queue of 64 questions with an end, which is the difference between a
39
+ * surface that gets finished and one that gets abandoned.
40
+ *
41
+ * ## Undo returns the QUESTION; it does not retract the WRITE
42
+ *
43
+ * `⌫` steps the cursor back and un-answers the candidate. What it cannot do is
44
+ * un-dispatch a proposal that already went to the store — that is the store's
45
+ * undo, over its own mutation set, and a second retraction path out here would
46
+ * be free to disagree with it about what undoing a create means.
47
+ *
48
+ * So an undo that withdraws an `apply` REPORTS the withdrawal
49
+ * ({@link QueueResult.withdrawn}) and the host routes it to the store. The
50
+ * queue's own state is honest either way: the candidate is unanswered again,
51
+ * which is what the reader just asked for.
52
+ */
53
+ import type { Proposal } from '@issuegraph/store';
54
+ import type { Candidate, CandidateId } from './candidates.ts';
55
+ /**
56
+ * What an owner said about a candidate.
57
+ *
58
+ * Three, closed by §17e, and named for the DECISION rather than for the key —
59
+ * `apply` rather than `yes`, because the keyboard spelling belongs to
60
+ * {@link ./keys.ts} and a rebinding must not have to reach in here.
61
+ *
62
+ * - `apply` — yes, create it. The one answer that emits.
63
+ * - `reject` — no, this is not a relationship. Answered, and gone.
64
+ * - `skip` — not now. Answered for the purpose of PROGRESS, because the owner
65
+ * did the work of looking; deferring it into a second pass would make the
66
+ * progress bar a lie in the direction that matters, by never letting a queue
67
+ * with hard cases in it end.
68
+ */
69
+ export type Answer = 'apply' | 'reject' | 'skip';
70
+ /** A candidate, and what was said about it. */
71
+ export interface Answered {
72
+ readonly candidate: Candidate;
73
+ readonly answer: Answer;
74
+ }
75
+ /**
76
+ * The queue.
77
+ *
78
+ * `cursor` indexes {@link candidates} and is the number of answers given, which
79
+ * is not a coincidence to be maintained but the reason there is no second
80
+ * field: answers are given in order and undone in order, so the two cannot
81
+ * drift. `answers[i]` is the answer to `candidates[i]`.
82
+ */
83
+ export interface QueueState {
84
+ /** Everything the host found. The denominator, and never anything else. */
85
+ readonly candidates: readonly Candidate[];
86
+ /** How far in. Equal to `answers.length`; past the end means finished. */
87
+ readonly cursor: number;
88
+ /** What was said, oldest first. */
89
+ readonly answers: readonly Answered[];
90
+ }
91
+ /** A queue over the candidates a host supplied, with nothing answered yet. */
92
+ export declare function openQueue(candidates: readonly Candidate[]): QueueState;
93
+ /**
94
+ * One act.
95
+ *
96
+ * `answer` carries the DECISION and not the candidate: the queue shows exactly
97
+ * one candidate, so which one is being answered is the queue's own state rather
98
+ * than something a caller can get wrong. A command naming a candidate would
99
+ * admit answering one that is not on screen, which is consent for a question
100
+ * nobody was asked.
101
+ */
102
+ export type QueueCommand = {
103
+ readonly kind: 'answer';
104
+ readonly answer: Answer;
105
+ } | {
106
+ readonly kind: 'undo';
107
+ };
108
+ /**
109
+ * The next queue, what this transition emitted, and what it took back.
110
+ *
111
+ * Three fields rather than a union because a host wires all three once —
112
+ * `state` always, `proposal` to the store, `withdrawn` to the store's undo —
113
+ * and a union would make the ordinary reduction a switch over cases that mostly
114
+ * do the same thing. Same reasoning `CreateResult` records for its pair.
115
+ */
116
+ export interface QueueResult {
117
+ readonly state: QueueState;
118
+ /** Non-null on exactly one transition: an `apply`. */
119
+ readonly proposal: Proposal | null;
120
+ /**
121
+ * The answer an `undo` took back, or `null`.
122
+ *
123
+ * A withdrawn `apply` is the one the host must act on — its proposal is
124
+ * already at the store. A withdrawn `reject` or `skip` dispatched nothing, so
125
+ * there is nothing to undo beyond the queue position, and it is reported
126
+ * anyway so the host has one code path rather than two.
127
+ */
128
+ readonly withdrawn: Answered | null;
129
+ }
130
+ /** Progress, as §17e defines it. */
131
+ export interface QueueProgress {
132
+ /** Answers given. */
133
+ readonly answered: number;
134
+ /**
135
+ * Candidates FOUND. The denominator, and the whole point of this type.
136
+ *
137
+ * Named `found` rather than `total` deliberately: `total` is the word that
138
+ * invites a reader to reach for the backlog size, and this number is not
139
+ * that. See the module header.
140
+ */
141
+ readonly found: number;
142
+ /** Still to answer. */
143
+ readonly remaining: number;
144
+ /** Nothing left. A queue over no candidates is finished immediately. */
145
+ readonly finished: boolean;
146
+ }
147
+ export declare function queueProgress(state: QueueState): QueueProgress;
148
+ /**
149
+ * The candidate on screen, or `null` when the queue is finished.
150
+ *
151
+ * `null` rather than a sentinel candidate: "there is nothing to answer" is a
152
+ * genuinely different state from "here is a question", and a placeholder would
153
+ * be answerable.
154
+ */
155
+ export declare function currentCandidate(state: QueueState): Candidate | null;
156
+ /**
157
+ * Apply one command. Total, pure, and never mutates what it is given.
158
+ *
159
+ * An exhaustive switch over a discriminated union — the one branching form
160
+ * `AGENTS.md`'s boundary rule leaves open, and the shape every reducer in this
161
+ * package already uses. Adding a command without a case fails the build.
162
+ */
163
+ export declare function queueReducer(state: QueueState, command: QueueCommand): QueueResult;
164
+ /**
165
+ * The candidates an owner deferred, in the order they were deferred.
166
+ *
167
+ * §17e's `S` is "not now" rather than "no", so the deferred set is the thing a
168
+ * second pass is built from — and it is DERIVED here rather than accumulated in
169
+ * {@link QueueState}, so an undo cannot leave it disagreeing with the answers
170
+ * it summarises. That is the same reason `queueProgress` derives its counts:
171
+ * two fields recording one fact is one fact that can be wrong.
172
+ */
173
+ export declare function skippedCandidates(state: QueueState): readonly Candidate[];
174
+ /**
175
+ * Whether a candidate has already been answered in this queue.
176
+ *
177
+ * For a host merging a fresh candidate set into an open queue — a second
178
+ * detector finishing late, say. It asks by {@link CandidateId} because that is
179
+ * the identity the HOST minted, for exactly the reason `candidates.ts` records:
180
+ * two findings about the same pair are not necessarily the same finding.
181
+ */
182
+ export declare function isAnswered(state: QueueState, id: CandidateId): boolean;
183
+ //# sourceMappingURL=queue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queue.d.ts","sourceRoot":"","sources":["../../src/firstpass/queue.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAElD,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAE9D;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC;AAEjD,+CAA+C;AAC/C,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,UAAU;IACzB,2EAA2E;IAC3E,QAAQ,CAAC,UAAU,EAAE,SAAS,SAAS,EAAE,CAAC;IAC1C,0EAA0E;IAC1E,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,mCAAmC;IACnC,QAAQ,CAAC,OAAO,EAAE,SAAS,QAAQ,EAAE,CAAC;CACvC;AAED,8EAA8E;AAC9E,wBAAgB,SAAS,CAAC,UAAU,EAAE,SAAS,SAAS,EAAE,GAAG,UAAU,CAEtE;AAED;;;;;;;;GAQG;AACH,MAAM,MAAM,YAAY,GACpB;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACpD;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAE9B;;;;;;;GAOG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;IAC3B,sDAAsD;IACtD,QAAQ,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAC;IACnC;;;;;;;OAOG;IACH,QAAQ,CAAC,SAAS,EAAE,QAAQ,GAAG,IAAI,CAAC;CACrC;AAED,oCAAoC;AACpC,MAAM,WAAW,aAAa;IAC5B,qBAAqB;IACrB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B;;;;;;OAMG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,uBAAuB;IACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,wEAAwE;IACxE,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC5B;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,aAAa,CAI9D;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,UAAU,GAAG,SAAS,GAAG,IAAI,CAEpE;AAeD;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,YAAY,GAAG,WAAW,CAyClF;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,UAAU,GAAG,SAAS,SAAS,EAAE,CAIzE;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE,EAAE,WAAW,GAAG,OAAO,CAEtE"}
@@ -0,0 +1,159 @@
1
+ /**
2
+ * The first-pass review queue: one candidate, one keystroke, one answer.
3
+ *
4
+ * §17e's loop is `Y` / `N` / `S` with `⌫` to undo, over candidates the host
5
+ * found. This module is the whole of that as a reducer — the same shape
6
+ * `create/draft.ts` and `scale/commands.ts` already use here, so a host
7
+ * reducing one reduces this without learning a second protocol.
8
+ *
9
+ * ## Nothing is applied without a keystroke, and the shape is what guarantees it
10
+ *
11
+ * §17e gives the reason and it is worth quoting rather than paraphrasing: "A
12
+ * wrong `duplicate-of` silently removes real work from the order, so it always
13
+ * costs one keystroke of consent."
14
+ *
15
+ * A `duplicate-of` excludes an issue from the order ENTIRELY. Applied without
16
+ * consent, that is work vanishing from the backlog while looking handled — the
17
+ * dead-duplicate audit class arriving through automation instead of decay.
18
+ *
19
+ * So consent is structural rather than promised: the ONLY thing in this module
20
+ * that returns a non-null proposal is the `answer` command carrying
21
+ * {@link Answer} `apply`, and nothing else here constructs one at all. Advancing
22
+ * the queue, loading it, rendering it and undoing all emit `null` because there
23
+ * is no code path on which they could emit anything else. `queue.test.ts` pins
24
+ * it from the other side — a whole queue driven to exhaustion by `reject` and
25
+ * `skip` dispatches nothing — and `render.test.ts` pins the surface: drawing a
26
+ * candidate is not answering it.
27
+ *
28
+ * ## Progress is bounded by CANDIDATES, and never by backlog size
29
+ *
30
+ * "Progress is bounded by candidates found, not backlog size, so first pass has
31
+ * an end. 100% encoded is never the goal and the workspace never implies it is
32
+ * — most issues legitimately have no relationships."
33
+ *
34
+ * The denominator is therefore {@link QueueState.candidates}`.length` and this
35
+ * module is never told how many issues exist — it cannot compute the wrong
36
+ * denominator because it does not hold the number that would be wrong. An owner
37
+ * with 248 legitimately-isolated issues is not shown "20% done"; they are shown
38
+ * a queue of 64 questions with an end, which is the difference between a
39
+ * surface that gets finished and one that gets abandoned.
40
+ *
41
+ * ## Undo returns the QUESTION; it does not retract the WRITE
42
+ *
43
+ * `⌫` steps the cursor back and un-answers the candidate. What it cannot do is
44
+ * un-dispatch a proposal that already went to the store — that is the store's
45
+ * undo, over its own mutation set, and a second retraction path out here would
46
+ * be free to disagree with it about what undoing a create means.
47
+ *
48
+ * So an undo that withdraws an `apply` REPORTS the withdrawal
49
+ * ({@link QueueResult.withdrawn}) and the host routes it to the store. The
50
+ * queue's own state is honest either way: the candidate is unanswered again,
51
+ * which is what the reader just asked for.
52
+ */
53
+ /** A queue over the candidates a host supplied, with nothing answered yet. */
54
+ export function openQueue(candidates) {
55
+ return { candidates, cursor: 0, answers: [] };
56
+ }
57
+ export function queueProgress(state) {
58
+ const found = state.candidates.length;
59
+ const answered = state.answers.length;
60
+ return { answered, found, remaining: found - answered, finished: answered >= found };
61
+ }
62
+ /**
63
+ * The candidate on screen, or `null` when the queue is finished.
64
+ *
65
+ * `null` rather than a sentinel candidate: "there is nothing to answer" is a
66
+ * genuinely different state from "here is a question", and a placeholder would
67
+ * be answerable.
68
+ */
69
+ export function currentCandidate(state) {
70
+ return state.candidates[state.cursor] ?? null;
71
+ }
72
+ const NOTHING = Object.freeze({ proposal: null, withdrawn: null });
73
+ /**
74
+ * The proposal an `apply` stands for.
75
+ *
76
+ * ITS OWN FUNCTION, so the module has exactly one place that builds a
77
+ * `Proposal` and the consent claim in the header is checkable by reading rather
78
+ * than by trusting. It is also the only reason `Proposal` is imported at all.
79
+ */
80
+ function proposalFor(candidate) {
81
+ return { op: 'create', kind: candidate.kind, from: candidate.from, to: candidate.to };
82
+ }
83
+ /**
84
+ * Apply one command. Total, pure, and never mutates what it is given.
85
+ *
86
+ * An exhaustive switch over a discriminated union — the one branching form
87
+ * `AGENTS.md`'s boundary rule leaves open, and the shape every reducer in this
88
+ * package already uses. Adding a command without a case fails the build.
89
+ */
90
+ export function queueReducer(state, command) {
91
+ switch (command.kind) {
92
+ case 'answer': {
93
+ const candidate = currentCandidate(state);
94
+ // ANSWERING A FINISHED QUEUE IS A NO-OP, not an error and not a wrap-round.
95
+ // A key press racing the last answer is ordinary — the reader is going
96
+ // fast, which is the design target — and the alternatives are both worse
97
+ // than doing nothing: throwing turns a fast reader into an error dialog,
98
+ // and wrapping re-asks a question they already answered.
99
+ if (candidate === null)
100
+ return { state, ...NOTHING };
101
+ const answered = { candidate, answer: command.answer };
102
+ const next = {
103
+ ...state,
104
+ cursor: state.cursor + 1,
105
+ answers: [...state.answers, answered],
106
+ };
107
+ // THE ONLY EMISSION IN THIS MODULE. `reject` and `skip` advance and say
108
+ // nothing, which is what makes "no candidate is applied without a
109
+ // keystroke" a property of the shape rather than a promise.
110
+ return {
111
+ state: next,
112
+ proposal: command.answer === 'apply' ? proposalFor(candidate) : null,
113
+ withdrawn: null,
114
+ };
115
+ }
116
+ case 'undo': {
117
+ const last = state.answers[state.answers.length - 1];
118
+ // Nothing answered is nothing to undo. Same reasoning as above: the key
119
+ // is handed back rather than made into an error.
120
+ if (last === undefined)
121
+ return { state, ...NOTHING };
122
+ return {
123
+ state: {
124
+ ...state,
125
+ cursor: state.cursor - 1,
126
+ answers: state.answers.slice(0, -1),
127
+ },
128
+ proposal: null,
129
+ withdrawn: last,
130
+ };
131
+ }
132
+ }
133
+ }
134
+ /**
135
+ * The candidates an owner deferred, in the order they were deferred.
136
+ *
137
+ * §17e's `S` is "not now" rather than "no", so the deferred set is the thing a
138
+ * second pass is built from — and it is DERIVED here rather than accumulated in
139
+ * {@link QueueState}, so an undo cannot leave it disagreeing with the answers
140
+ * it summarises. That is the same reason `queueProgress` derives its counts:
141
+ * two fields recording one fact is one fact that can be wrong.
142
+ */
143
+ export function skippedCandidates(state) {
144
+ return state.answers
145
+ .filter((answered) => answered.answer === 'skip')
146
+ .map((answered) => answered.candidate);
147
+ }
148
+ /**
149
+ * Whether a candidate has already been answered in this queue.
150
+ *
151
+ * For a host merging a fresh candidate set into an open queue — a second
152
+ * detector finishing late, say. It asks by {@link CandidateId} because that is
153
+ * the identity the HOST minted, for exactly the reason `candidates.ts` records:
154
+ * two findings about the same pair are not necessarily the same finding.
155
+ */
156
+ export function isAnswered(state, id) {
157
+ return state.answers.some((answered) => answered.candidate.id === id);
158
+ }
159
+ //# sourceMappingURL=queue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queue.js","sourceRoot":"","sources":["../../src/firstpass/queue.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,100 @@
1
+ /**
2
+ * The first-pass queue, as markup.
3
+ *
4
+ * ## Drawing a candidate answers nothing
5
+ *
6
+ * The property §17e turns on, and the one this file most has to be trusted
7
+ * with: rendering emits no proposal, wires no listener and dispatches nothing.
8
+ * It publishes `data-ig-answer` on three buttons and stops. The consent
9
+ * §17e requires — "it always costs one keystroke" — survives because the
10
+ * only thing in this package that builds a `Proposal` from a candidate is
11
+ * `queue.ts`'s `apply` arm, and nothing here calls it.
12
+ *
13
+ * `render.test.ts` asserts it from the outside: a rendered candidate produces
14
+ * no proposal, because there is no return channel on which it could.
15
+ *
16
+ * ## Every readable byte comes from the host or from the document
17
+ *
18
+ * The same total claim `picker/render.ts` makes, and it is stricter here than
19
+ * it looks, because the evidence text is host prose flowing through a package
20
+ * surface. This module never reads it, never truncates it and never summarises
21
+ * it — it places it, with the evidence `token` beside it as a data attribute so
22
+ * a host can style a class of reason and a test can assert one without matching
23
+ * on words.
24
+ *
25
+ * ## Progress is drawn as a number the host worded, not a bar
26
+ *
27
+ * §17e: "100% encoded is never the goal and the workspace never implies it is."
28
+ * A progress BAR implies a target by its geometry — an empty tail is a thing
29
+ * left undone — and no wording can undo that. So the surface publishes the two
30
+ * counts as attributes and renders the host's sentence, and a host that wants a
31
+ * bar draws one knowing what it is claiming. `<progress>` is deliberately not
32
+ * used for the same reason.
33
+ *
34
+ * ## It names itself without claiming a heading level
35
+ *
36
+ * Same call `picker/render.ts` records: a heading would be a claim about the
37
+ * HOST's document outline — whether this surface is a section of something, and
38
+ * at what depth — which a package rendered into an unknown page cannot make.
39
+ *
40
+ * That refusal used to be the whole of it, and it left the root `<section>`
41
+ * with no accessible name at all: a screen-reader user navigating by landmarks
42
+ * met an unnamed generic region and could not tell it from any other. The
43
+ * refusal was right and the silence that followed it was not, so the region now
44
+ * carries an `aria-label` from {@link ./words.ts FirstPassWords} — which names
45
+ * the surface without asserting anything about where it sits in an outline. A
46
+ * host that wants it in the outline still wraps it in a heading of its own
47
+ * choosing.
48
+ */
49
+ import { type Theme } from '@issuegraph/viewer';
50
+ import { type FirstPassView } from './view.ts';
51
+ import type { QueueState } from './queue.ts';
52
+ import type { FirstPassWords } from './words.ts';
53
+ /** The attribute a shell reads to know which answer a control stands for. */
54
+ export declare const ANSWER_ATTRIBUTE = "data-ig-answer";
55
+ /**
56
+ * The attribute on the undo control.
57
+ *
58
+ * ITS OWN ATTRIBUTE, not `data-ig-answer="undo"`. {@link ./queue.ts Answer} is
59
+ * a closed union of three, and undo is not one of them — so a shell reading
60
+ * `data-ig-answer` and switching over `Answer` would be handed a value outside
61
+ * the type it was told to expect. `data-ig-command` is the spelling
62
+ * `picker/render.ts` already uses for a control that is not a choice.
63
+ */
64
+ export declare const COMMAND_ATTRIBUTE = "data-ig-command";
65
+ /** The attribute carrying an evidence item's machine-readable token. */
66
+ export declare const EVIDENCE_TOKEN_ATTRIBUTE = "data-ig-evidence";
67
+ /** Answers given, on the root. */
68
+ export declare const ANSWERED_ATTRIBUTE = "data-ig-answered";
69
+ /** Candidates found — the denominator, on the root. */
70
+ export declare const FOUND_ATTRIBUTE = "data-ig-found";
71
+ export interface FirstPassOptions {
72
+ /** The words. Required — see {@link ./words.ts FirstPassWords} for why. */
73
+ readonly words: FirstPassWords;
74
+ readonly theme?: Theme | undefined;
75
+ /** The selector the theme's custom properties are written onto. */
76
+ readonly themeSelector?: string | undefined;
77
+ }
78
+ export interface FirstPassResult {
79
+ readonly view: FirstPassView;
80
+ /** The question, its evidence, the answers and the progress, under one root. */
81
+ readonly markup: string;
82
+ /**
83
+ * The theme and this surface's own stylesheet. Install both.
84
+ *
85
+ * The VIEWER's stylesheet is deliberately not among them, on the same
86
+ * reasoning `picker/render.ts` records: this surface draws no viewer
87
+ * elements, so shipping the canvas sheet with it would give a host that
88
+ * installs the queue alone a stylesheet whose every selector matches nothing.
89
+ */
90
+ readonly styles: string;
91
+ }
92
+ /**
93
+ * Draw the queue.
94
+ *
95
+ * Takes a {@link QueueState} — never a store and never a source. A host hands
96
+ * the proposal a reduction produced to `Store.propose`, which is the only thing
97
+ * in the family that dispatches.
98
+ */
99
+ export declare function renderFirstPass(state: QueueState, options: FirstPassOptions): FirstPassResult;
100
+ //# sourceMappingURL=render.d.ts.map