@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 @@
1
+ {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/firstpass/render.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AAEH,OAAO,EAEL,KAAK,KAAK,EAKX,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAA0B,KAAK,aAAa,EAAiB,MAAM,WAAW,CAAC;AACtF,OAAO,KAAK,EAAiB,UAAU,EAAE,MAAM,YAAY,CAAC;AAE5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,6EAA6E;AAC7E,eAAO,MAAM,gBAAgB,mBAAmB,CAAC;AACjD;;;;;;;;GAQG;AACH,eAAO,MAAM,iBAAiB,oBAAoB,CAAC;AACnD,wEAAwE;AACxE,eAAO,MAAM,wBAAwB,qBAAqB,CAAC;AAC3D,kCAAkC;AAClC,eAAO,MAAM,kBAAkB,qBAAqB,CAAC;AACrD,uDAAuD;AACvD,eAAO,MAAM,eAAe,kBAAkB,CAAC;AAE/C,MAAM,WAAW,gBAAgB;IAC/B,2EAA2E;IAC3E,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,SAAS,CAAC;IACnC,mEAAmE;IACnE,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC7C;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B,gFAAgF;IAChF,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB;;;;;;;OAOG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAyHD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,gBAAgB,GAAG,eAAe,CAiD7F"}
@@ -0,0 +1,216 @@
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 { element, renderMarkup, resolveTheme, themeCss, } from '@issuegraph/viewer';
50
+ import { firstPassView } from "./view.js";
51
+ import { firstPassStylesheet } from "./styles.js";
52
+ /** The attribute a shell reads to know which answer a control stands for. */
53
+ export const ANSWER_ATTRIBUTE = 'data-ig-answer';
54
+ /**
55
+ * The attribute on the undo control.
56
+ *
57
+ * ITS OWN ATTRIBUTE, not `data-ig-answer="undo"`. {@link ./queue.ts Answer} is
58
+ * a closed union of three, and undo is not one of them — so a shell reading
59
+ * `data-ig-answer` and switching over `Answer` would be handed a value outside
60
+ * the type it was told to expect. `data-ig-command` is the spelling
61
+ * `picker/render.ts` already uses for a control that is not a choice.
62
+ */
63
+ export const COMMAND_ATTRIBUTE = 'data-ig-command';
64
+ /** The attribute carrying an evidence item's machine-readable token. */
65
+ export const EVIDENCE_TOKEN_ATTRIBUTE = 'data-ig-evidence';
66
+ /** Answers given, on the root. */
67
+ export const ANSWERED_ATTRIBUTE = 'data-ig-answered';
68
+ /** Candidates found — the denominator, on the root. */
69
+ export const FOUND_ATTRIBUTE = 'data-ig-found';
70
+ /** The three answer controls, in §17e's order. */
71
+ const ANSWER_ORDER = Object.freeze(['apply', 'reject', 'skip']);
72
+ /**
73
+ * One evidence item.
74
+ *
75
+ * The text is the host's sentence, placed verbatim. The token rides beside it
76
+ * as an attribute rather than in the text, which is what keeps the rendered
77
+ * prose entirely the host's while leaving the item addressable.
78
+ */
79
+ function evidenceSpec(item) {
80
+ return element('li', { class: 'ig-firstpass-evidence-item', [EVIDENCE_TOKEN_ATTRIBUTE]: item.token }, [
81
+ item.text,
82
+ ]);
83
+ }
84
+ /**
85
+ * The proposed relationship, drawn subject · kind · object.
86
+ *
87
+ * A DEFAULT WORD ORDER, bypassable exactly as the picker's statement is: the
88
+ * view model carries the ordered pair and the kind and no order beyond that, so
89
+ * a host whose language arranges it differently renders the model. The
90
+ * stylesheet lays it out as a flex row, which is what makes that bypass a
91
+ * restyle rather than a re-implementation.
92
+ *
93
+ * The kind is drawn as its FORMAT SPELLING (`blocked-by`), not as a phrase.
94
+ * `PickerWords` words the kinds for the picker, and asking a host to word them
95
+ * a second time here would be two vocabularies free to disagree about one
96
+ * relationship — while reusing `PickerWords` would couple this surface to a
97
+ * flow it is not part of. The spelling is the document's own token, so it is
98
+ * the document's word rather than ours, and it is what the owner will see in
99
+ * the issue body afterwards.
100
+ */
101
+ function questionSpec(question, words) {
102
+ return element('div', { class: 'ig-firstpass-question' }, [
103
+ element('p', { class: 'ig-firstpass-statement', 'data-ig-kind': question.kind }, [
104
+ element('span', { class: 'ig-firstpass-ref', 'data-ig-role': 'from' }, [question.subject]),
105
+ element('span', { class: 'ig-firstpass-kind' }, [question.kind]),
106
+ element('span', { class: 'ig-firstpass-ref', 'data-ig-role': 'to' }, [question.object]),
107
+ ]),
108
+ // NO EVIDENCE IS NO BLOCK, rather than an empty list with a heading. A host
109
+ // whose detector cannot explain itself says so by sending none
110
+ // (`candidates.ts`), and drawing a labelled empty container would turn that
111
+ // into a rendering fault the owner has to interpret.
112
+ question.evidence.length === 0
113
+ ? null
114
+ : element('div', { class: 'ig-firstpass-evidence' }, [
115
+ element('p', { class: 'ig-firstpass-evidence-label' }, [words.evidence]),
116
+ element('ul', { class: 'ig-firstpass-evidence-list', 'aria-label': words.evidence }, question.evidence.map(evidenceSpec)),
117
+ ]),
118
+ ]);
119
+ }
120
+ /**
121
+ * The answer controls.
122
+ *
123
+ * BUTTONS, and a keyboard-first surface still needs them: §17e's claim is that
124
+ * the loop is reachable with no pointer, not that a pointer is refused. A
125
+ * surface with keys and no controls would also be a surface with no discoverable
126
+ * vocabulary and nothing for assistive technology to name.
127
+ */
128
+ function answersSpec(words) {
129
+ return element('div', { class: 'ig-firstpass-answers', role: 'group', 'aria-label': words.answersLabel }, ANSWER_ORDER.map((answer) => element('button', { type: 'button', class: 'ig-firstpass-answer', [ANSWER_ATTRIBUTE]: answer }, [words.answers[answer]])));
130
+ }
131
+ /**
132
+ * The undo control, drawn whenever there is an answer to withdraw.
133
+ *
134
+ * ## It survives the finished state, and that is the point of the rule
135
+ *
136
+ * The last answer is the one most in need of taking back — an accidental `Y` on
137
+ * the final candidate creates the `duplicate-of` §17e says "silently removes
138
+ * real work from the order", and the queue then has no candidate left to show.
139
+ * An earlier revision replaced every control with the finished message at that
140
+ * exact moment, so a reader without a keyboard had no way back from the one
141
+ * answer that matters most.
142
+ *
143
+ * ## One rule, so the pointer and the keyboard cannot disagree
144
+ *
145
+ * `keys.ts` gates undo on `canUndo` — anything answered — and deliberately
146
+ * keeps it live once `hasCandidate` is false. Drawing the control on the same
147
+ * condition is what makes the two paths the same path. The previous version
148
+ * disagreed at BOTH edges, in opposite directions: it drew a dead control
149
+ * before anything was answered, and dropped a live one after the last answer.
150
+ *
151
+ * A control that does nothing is worse than no control, which is why this
152
+ * returns `null` rather than a disabled button: the queue is answered at speed,
153
+ * and an affordance that is sometimes inert teaches the reader to distrust it.
154
+ */
155
+ function undoSpec(progress, words) {
156
+ if (progress.answered === 0)
157
+ return null;
158
+ return element('button', { type: 'button', class: 'ig-firstpass-undo', [COMMAND_ATTRIBUTE]: 'undo' }, [words.undo]);
159
+ }
160
+ /** The progress line. The host's sentence over this package's two numbers. */
161
+ function progressSpec(progress, words) {
162
+ return element('p', { class: 'ig-firstpass-progress' }, [
163
+ words.progress(progress.answered, progress.found),
164
+ ]);
165
+ }
166
+ /**
167
+ * Draw the queue.
168
+ *
169
+ * Takes a {@link QueueState} — never a store and never a source. A host hands
170
+ * the proposal a reduction produced to `Store.propose`, which is the only thing
171
+ * in the family that dispatches.
172
+ */
173
+ export function renderFirstPass(state, options) {
174
+ const view = firstPassView(state);
175
+ const theme = resolveTheme(options.theme);
176
+ const { words } = options;
177
+ const body = (() => {
178
+ switch (view.state) {
179
+ case 'asking':
180
+ return [questionSpec(view.question, words), answersSpec(words), undoSpec(view.progress, words)];
181
+ case 'finished':
182
+ // UNDO OUTLIVES THE QUESTIONS. `finished` always carries at least one
183
+ // answer — a queue that found nothing is `empty` instead — so the
184
+ // control is always drawn here, which is exactly the case that needs it.
185
+ return [
186
+ element('p', { class: 'ig-firstpass-finished' }, [words.finished]),
187
+ undoSpec(view.progress, words),
188
+ ];
189
+ case 'empty':
190
+ // A DIFFERENT SENTENCE FROM `finished`, which is the whole reason the
191
+ // view model separates them. See `view.ts`.
192
+ //
193
+ // NO UNDO, and it needs no test to be sure of it: `empty` means the host
194
+ // found nothing, and nothing found is nothing that can have been
195
+ // answered — so `undoSpec` would return `null` here anyway.
196
+ return [element('p', { class: 'ig-firstpass-empty' }, [words.noCandidates])];
197
+ }
198
+ })();
199
+ const root = element('section', {
200
+ class: 'ig-firstpass',
201
+ // NAMED WITHOUT CLAIMING AN OUTLINE POSITION. The heading refusal above
202
+ // is unchanged — a heading LEVEL is a claim about the host's document
203
+ // structure — but an unnamed `<section>` is a landmark a screen-reader
204
+ // user cannot identify, so the region carries the host's own name here.
205
+ 'aria-label': words.label,
206
+ 'data-ig-state': view.state,
207
+ [ANSWERED_ATTRIBUTE]: String(view.progress.answered),
208
+ [FOUND_ATTRIBUTE]: String(view.progress.found),
209
+ }, [...body, progressSpec(view.progress, words)]);
210
+ return {
211
+ view,
212
+ markup: renderMarkup(root),
213
+ styles: `${themeCss(theme, options.themeSelector ?? ':root')}\n${firstPassStylesheet}`,
214
+ };
215
+ }
216
+ //# sourceMappingURL=render.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.js","sourceRoot":"","sources":["../../src/firstpass/render.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * The first pass's structural stylesheet.
3
+ *
4
+ * Same contract as the picker's, the ladder's and the overlay's: layout, weight
5
+ * and state, never a value. Every colour, length and font is a `var(--ig-…)`
6
+ * the host's theme resolves, and `styles.test.ts` scans these bytes for a
7
+ * literal colour, a fixed length and a token the theme does not define — so the
8
+ * rule is enforced rather than remembered.
9
+ *
10
+ * IT ADDS SELECTORS RATHER THAN REDEFINING THEM. Nothing here reaches into the
11
+ * viewer's own classes; this file styles only what the first pass introduces.
12
+ *
13
+ * ## The statement is a FLEX ROW, and that is load-bearing
14
+ *
15
+ * `render.ts` draws the proposed relationship subject · kind · object and calls
16
+ * that word order a default a host may bypass. The CSS `order` property
17
+ * reorders flex children and does nothing to inline text, so laying it out any
18
+ * other way would make that claim true only in principle — the same reasoning
19
+ * the picker's stylesheet records, and enforced by the same kind of test.
20
+ *
21
+ * ## There is no progress BAR, and its absence is the design
22
+ *
23
+ * §17e: "100% encoded is never the goal and the workspace never implies it is."
24
+ * A bar implies a target by its geometry — an unfilled tail reads as work
25
+ * outstanding whatever the words beside it say. The progress line is therefore
26
+ * typographic, and this sheet gives it no track, no fill and no width driven by
27
+ * the counts. A host that wants a bar draws one knowing what it is claiming.
28
+ *
29
+ * ## Every control gets a focus ring
30
+ *
31
+ * The queue is keyboard-first, so a control with no `:focus-visible` rule is
32
+ * unreachable for the reader this surface is built for on any host that resets
33
+ * the UA outline. Enforced in `styles.test.ts`.
34
+ */
35
+ export declare const firstPassStylesheet = "\n.ig-firstpass {\n display: flex;\n flex-direction: column;\n gap: var(--ig-space);\n font-family: var(--ig-font-ui);\n font-size: var(--ig-font-size);\n line-height: var(--ig-line-height);\n color: var(--ig-text-body);\n background: var(--ig-surface);\n border-radius: var(--ig-radius);\n padding: var(--ig-space);\n}\n\n.ig-firstpass-question {\n display: flex;\n flex-direction: column;\n gap: var(--ig-space-tight);\n}\n\n.ig-firstpass-statement {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: var(--ig-space-tight);\n margin: 0;\n color: var(--ig-text);\n}\n\n.ig-firstpass-ref {\n font-family: var(--ig-font-mono);\n}\n\n.ig-firstpass-kind {\n font-family: var(--ig-font-mono);\n color: var(--ig-text-muted);\n}\n\n.ig-firstpass-evidence {\n display: flex;\n flex-direction: column;\n gap: var(--ig-space-tight);\n border-left: var(--ig-stroke) solid var(--ig-line);\n padding-left: var(--ig-space);\n}\n\n.ig-firstpass-evidence-label {\n margin: 0;\n font-size: var(--ig-font-size-small);\n color: var(--ig-text-muted);\n}\n\n.ig-firstpass-evidence-list {\n display: flex;\n flex-direction: column;\n gap: var(--ig-space-tight);\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.ig-firstpass-evidence-item {\n font-size: var(--ig-font-size-small);\n color: var(--ig-text-body);\n}\n\n.ig-firstpass-answers {\n display: flex;\n flex-wrap: wrap;\n gap: var(--ig-space-tight);\n}\n\n.ig-firstpass-answer,\n.ig-firstpass-undo {\n font: inherit;\n color: var(--ig-text);\n background: var(--ig-surface-2);\n border: var(--ig-stroke) solid var(--ig-line);\n border-radius: var(--ig-radius);\n padding: var(--ig-space-tight) var(--ig-space);\n cursor: pointer;\n}\n\n/* The one answer that writes. Weighted so the irreversible option is the one\n the eye lands on \u2014 \u00A717e's consent rule is about knowing what you agreed to,\n and that starts before the keystroke. */\n.ig-firstpass-answer[data-ig-answer='apply'] {\n border-color: var(--ig-accent);\n}\n\n.ig-firstpass-undo {\n align-self: flex-start;\n color: var(--ig-text-muted);\n}\n\n.ig-firstpass-answer:focus-visible,\n.ig-firstpass-undo:focus-visible {\n outline: var(--ig-focus-ring) solid var(--ig-focus);\n outline-offset: var(--ig-stroke);\n}\n\n.ig-firstpass-progress {\n margin: 0;\n font-size: var(--ig-font-size-small);\n color: var(--ig-text-muted);\n}\n\n.ig-firstpass-finished,\n.ig-firstpass-empty {\n margin: 0;\n color: var(--ig-text);\n}\n";
36
+ //# sourceMappingURL=styles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../../src/firstpass/styles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,eAAO,MAAM,mBAAmB,m9EAgH/B,CAAC"}
@@ -0,0 +1,148 @@
1
+ /**
2
+ * The first pass's structural stylesheet.
3
+ *
4
+ * Same contract as the picker's, the ladder's and the overlay's: layout, weight
5
+ * and state, never a value. Every colour, length and font is a `var(--ig-…)`
6
+ * the host's theme resolves, and `styles.test.ts` scans these bytes for a
7
+ * literal colour, a fixed length and a token the theme does not define — so the
8
+ * rule is enforced rather than remembered.
9
+ *
10
+ * IT ADDS SELECTORS RATHER THAN REDEFINING THEM. Nothing here reaches into the
11
+ * viewer's own classes; this file styles only what the first pass introduces.
12
+ *
13
+ * ## The statement is a FLEX ROW, and that is load-bearing
14
+ *
15
+ * `render.ts` draws the proposed relationship subject · kind · object and calls
16
+ * that word order a default a host may bypass. The CSS `order` property
17
+ * reorders flex children and does nothing to inline text, so laying it out any
18
+ * other way would make that claim true only in principle — the same reasoning
19
+ * the picker's stylesheet records, and enforced by the same kind of test.
20
+ *
21
+ * ## There is no progress BAR, and its absence is the design
22
+ *
23
+ * §17e: "100% encoded is never the goal and the workspace never implies it is."
24
+ * A bar implies a target by its geometry — an unfilled tail reads as work
25
+ * outstanding whatever the words beside it say. The progress line is therefore
26
+ * typographic, and this sheet gives it no track, no fill and no width driven by
27
+ * the counts. A host that wants a bar draws one knowing what it is claiming.
28
+ *
29
+ * ## Every control gets a focus ring
30
+ *
31
+ * The queue is keyboard-first, so a control with no `:focus-visible` rule is
32
+ * unreachable for the reader this surface is built for on any host that resets
33
+ * the UA outline. Enforced in `styles.test.ts`.
34
+ */
35
+ export const firstPassStylesheet = `
36
+ .ig-firstpass {
37
+ display: flex;
38
+ flex-direction: column;
39
+ gap: var(--ig-space);
40
+ font-family: var(--ig-font-ui);
41
+ font-size: var(--ig-font-size);
42
+ line-height: var(--ig-line-height);
43
+ color: var(--ig-text-body);
44
+ background: var(--ig-surface);
45
+ border-radius: var(--ig-radius);
46
+ padding: var(--ig-space);
47
+ }
48
+
49
+ .ig-firstpass-question {
50
+ display: flex;
51
+ flex-direction: column;
52
+ gap: var(--ig-space-tight);
53
+ }
54
+
55
+ .ig-firstpass-statement {
56
+ display: flex;
57
+ flex-wrap: wrap;
58
+ align-items: baseline;
59
+ gap: var(--ig-space-tight);
60
+ margin: 0;
61
+ color: var(--ig-text);
62
+ }
63
+
64
+ .ig-firstpass-ref {
65
+ font-family: var(--ig-font-mono);
66
+ }
67
+
68
+ .ig-firstpass-kind {
69
+ font-family: var(--ig-font-mono);
70
+ color: var(--ig-text-muted);
71
+ }
72
+
73
+ .ig-firstpass-evidence {
74
+ display: flex;
75
+ flex-direction: column;
76
+ gap: var(--ig-space-tight);
77
+ border-left: var(--ig-stroke) solid var(--ig-line);
78
+ padding-left: var(--ig-space);
79
+ }
80
+
81
+ .ig-firstpass-evidence-label {
82
+ margin: 0;
83
+ font-size: var(--ig-font-size-small);
84
+ color: var(--ig-text-muted);
85
+ }
86
+
87
+ .ig-firstpass-evidence-list {
88
+ display: flex;
89
+ flex-direction: column;
90
+ gap: var(--ig-space-tight);
91
+ margin: 0;
92
+ padding: 0;
93
+ list-style: none;
94
+ }
95
+
96
+ .ig-firstpass-evidence-item {
97
+ font-size: var(--ig-font-size-small);
98
+ color: var(--ig-text-body);
99
+ }
100
+
101
+ .ig-firstpass-answers {
102
+ display: flex;
103
+ flex-wrap: wrap;
104
+ gap: var(--ig-space-tight);
105
+ }
106
+
107
+ .ig-firstpass-answer,
108
+ .ig-firstpass-undo {
109
+ font: inherit;
110
+ color: var(--ig-text);
111
+ background: var(--ig-surface-2);
112
+ border: var(--ig-stroke) solid var(--ig-line);
113
+ border-radius: var(--ig-radius);
114
+ padding: var(--ig-space-tight) var(--ig-space);
115
+ cursor: pointer;
116
+ }
117
+
118
+ /* The one answer that writes. Weighted so the irreversible option is the one
119
+ the eye lands on — §17e's consent rule is about knowing what you agreed to,
120
+ and that starts before the keystroke. */
121
+ .ig-firstpass-answer[data-ig-answer='apply'] {
122
+ border-color: var(--ig-accent);
123
+ }
124
+
125
+ .ig-firstpass-undo {
126
+ align-self: flex-start;
127
+ color: var(--ig-text-muted);
128
+ }
129
+
130
+ .ig-firstpass-answer:focus-visible,
131
+ .ig-firstpass-undo:focus-visible {
132
+ outline: var(--ig-focus-ring) solid var(--ig-focus);
133
+ outline-offset: var(--ig-stroke);
134
+ }
135
+
136
+ .ig-firstpass-progress {
137
+ margin: 0;
138
+ font-size: var(--ig-font-size-small);
139
+ color: var(--ig-text-muted);
140
+ }
141
+
142
+ .ig-firstpass-finished,
143
+ .ig-firstpass-empty {
144
+ margin: 0;
145
+ color: var(--ig-text);
146
+ }
147
+ `;
148
+ //# sourceMappingURL=styles.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"styles.js","sourceRoot":"","sources":["../../src/firstpass/styles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,MAAM,CAAC,MAAM,mBAAmB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgHlC,CAAC"}
@@ -0,0 +1,82 @@
1
+ /**
2
+ * The queue as a view model: one question, its evidence, and where you are.
3
+ *
4
+ * Everything a shell needs to draw the first pass, and nothing about how. Same
5
+ * split `picker/view.ts` and `reevaluate/view.ts` keep — the model is
6
+ * exhaustively testable on a runtime with no DOM, and `render.ts` is left with
7
+ * nothing to get wrong except markup.
8
+ *
9
+ * ## The three states are a union, not a candidate plus two flags
10
+ *
11
+ * A queue is showing a question, or it has finished, or the host found nothing
12
+ * to ask about. Modelled as one nullable candidate beside a `finished` boolean,
13
+ * two of the four combinations would be unreachable and a renderer would still
14
+ * have to answer for them. As a union each state carries exactly the fields it
15
+ * has, and §17e's insistence that an empty queue and a completed queue read
16
+ * DIFFERENTLY becomes something the type enforces rather than something a
17
+ * renderer remembers.
18
+ *
19
+ * ## Progress rides on every state
20
+ *
21
+ * Including `finished` — an owner who just answered the last question wants to
22
+ * see the 64 they answered, not a bare congratulation. And including `empty`,
23
+ * where it is honestly zero of zero: §17e's rule is that the denominator is
24
+ * candidates found, and a host that found none has a denominator, not a missing
25
+ * one.
26
+ */
27
+ import type { Candidate } from './candidates.ts';
28
+ import { type QueueProgress, type QueueState } from './queue.ts';
29
+ /**
30
+ * The question on screen.
31
+ *
32
+ * It restates the candidate's own fields rather than nesting it, so a renderer
33
+ * reads `view.subject` instead of `view.candidate.from` — and so the ordered
34
+ * pair is named for what it MEANS in the sentence being asked. Nothing here
35
+ * chooses a word order; that is `render.ts`'s default and a host may bypass it,
36
+ * exactly as the picker's statement is bypassable.
37
+ */
38
+ export interface FirstPassQuestion {
39
+ readonly candidate: Candidate;
40
+ /** The issue the proposed relationship starts from. */
41
+ readonly subject: string;
42
+ /** The issue it points at. */
43
+ readonly object: string;
44
+ /** Which relationship is proposed. */
45
+ readonly kind: string;
46
+ /** Why the host proposed it. May be empty — see `candidates.ts`. */
47
+ readonly evidence: readonly {
48
+ readonly token: string;
49
+ readonly text: string;
50
+ }[];
51
+ }
52
+ /**
53
+ * What the surface is showing.
54
+ *
55
+ * `asking` carries the question; the other two carry none, because there is
56
+ * none. A discriminated union so a renderer's switch is exhaustive and a fourth
57
+ * state — were the design ever to grow one — is a compile error rather than a
58
+ * blank panel.
59
+ */
60
+ export type FirstPassView = {
61
+ readonly state: 'asking';
62
+ readonly question: FirstPassQuestion;
63
+ readonly progress: QueueProgress;
64
+ } | {
65
+ readonly state: 'finished';
66
+ readonly progress: QueueProgress;
67
+ } | {
68
+ readonly state: 'empty';
69
+ readonly progress: QueueProgress;
70
+ };
71
+ /**
72
+ * The view for a queue. Total and pure.
73
+ *
74
+ * `empty` is decided by the DENOMINATOR rather than by the cursor, which is the
75
+ * one subtlety here: a queue of zero candidates is finished the moment it
76
+ * opens, so a test on `finished` alone would report the misconfigured-detector
77
+ * case as a completed first pass. §17e's two facts stay separate because the
78
+ * question asked to tell them apart is "did the host find anything?", not "is
79
+ * there anything left?".
80
+ */
81
+ export declare function firstPassView(state: QueueState): FirstPassView;
82
+ //# sourceMappingURL=view.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"view.d.ts","sourceRoot":"","sources":["../../src/firstpass/view.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,UAAU,EAAmC,MAAM,YAAY,CAAC;AAElG;;;;;;;;GAQG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,uDAAuD;IACvD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,8BAA8B;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,sCAAsC;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,oEAAoE;IACpE,QAAQ,CAAC,QAAQ,EAAE,SAAS;QAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACjF;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,aAAa,GACrB;IAAE,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAA;CAAE,GACpG;IAAE,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAA;CAAE,GAChE;IAAE,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAA;CAAE,CAAC;AAElE;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,aAAa,CAqB9D"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * The queue as a view model: one question, its evidence, and where you are.
3
+ *
4
+ * Everything a shell needs to draw the first pass, and nothing about how. Same
5
+ * split `picker/view.ts` and `reevaluate/view.ts` keep — the model is
6
+ * exhaustively testable on a runtime with no DOM, and `render.ts` is left with
7
+ * nothing to get wrong except markup.
8
+ *
9
+ * ## The three states are a union, not a candidate plus two flags
10
+ *
11
+ * A queue is showing a question, or it has finished, or the host found nothing
12
+ * to ask about. Modelled as one nullable candidate beside a `finished` boolean,
13
+ * two of the four combinations would be unreachable and a renderer would still
14
+ * have to answer for them. As a union each state carries exactly the fields it
15
+ * has, and §17e's insistence that an empty queue and a completed queue read
16
+ * DIFFERENTLY becomes something the type enforces rather than something a
17
+ * renderer remembers.
18
+ *
19
+ * ## Progress rides on every state
20
+ *
21
+ * Including `finished` — an owner who just answered the last question wants to
22
+ * see the 64 they answered, not a bare congratulation. And including `empty`,
23
+ * where it is honestly zero of zero: §17e's rule is that the denominator is
24
+ * candidates found, and a host that found none has a denominator, not a missing
25
+ * one.
26
+ */
27
+ import { currentCandidate, queueProgress } from "./queue.js";
28
+ /**
29
+ * The view for a queue. Total and pure.
30
+ *
31
+ * `empty` is decided by the DENOMINATOR rather than by the cursor, which is the
32
+ * one subtlety here: a queue of zero candidates is finished the moment it
33
+ * opens, so a test on `finished` alone would report the misconfigured-detector
34
+ * case as a completed first pass. §17e's two facts stay separate because the
35
+ * question asked to tell them apart is "did the host find anything?", not "is
36
+ * there anything left?".
37
+ */
38
+ export function firstPassView(state) {
39
+ const progress = queueProgress(state);
40
+ if (progress.found === 0)
41
+ return { state: 'empty', progress };
42
+ const candidate = currentCandidate(state);
43
+ if (candidate === null)
44
+ return { state: 'finished', progress };
45
+ return {
46
+ state: 'asking',
47
+ progress,
48
+ question: {
49
+ candidate,
50
+ subject: candidate.from,
51
+ object: candidate.to,
52
+ kind: candidate.kind,
53
+ // COPIED RATHER THAN PASSED THROUGH, so the view model is a plain value a
54
+ // host can serialise, compare or snapshot without carrying a reference
55
+ // into the candidate set it came from.
56
+ evidence: candidate.evidence.map((item) => ({ token: item.token, text: item.text })),
57
+ },
58
+ };
59
+ }
60
+ //# sourceMappingURL=view.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"view.js","sourceRoot":"","sources":["../../src/firstpass/view.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAGH,OAAO,EAAuC,gBAAgB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAoClG;;;;;;;;;GASG;AACH,MAAM,UAAU,aAAa,CAAC,KAAiB;IAC7C,MAAM,QAAQ,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IACtC,IAAI,QAAQ,CAAC,KAAK,KAAK,CAAC;QAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;IAE9D,MAAM,SAAS,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAC;IAC1C,IAAI,SAAS,KAAK,IAAI;QAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC;IAE/D,OAAO;QACL,KAAK,EAAE,QAAQ;QACf,QAAQ;QACR,QAAQ,EAAE;YACR,SAAS;YACT,OAAO,EAAE,SAAS,CAAC,IAAI;YACvB,MAAM,EAAE,SAAS,CAAC,EAAE;YACpB,IAAI,EAAE,SAAS,CAAC,IAAI;YACpB,0EAA0E;YAC1E,uEAAuE;YACvE,uCAAuC;YACvC,QAAQ,EAAE,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;SACrF;KACF,CAAC;AACJ,CAAC"}