@issuegraph/editor 0.4.0 → 0.22.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/README.md +65 -9
  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 +434 -0
  5. package/dist/a11y/baseline.js.map +1 -0
  6. package/dist/audit/findings.d.ts +107 -1
  7. package/dist/audit/findings.d.ts.map +1 -1
  8. package/dist/audit/findings.js +113 -6
  9. package/dist/audit/findings.js.map +1 -1
  10. package/dist/audit/panel.d.ts +277 -0
  11. package/dist/audit/panel.d.ts.map +1 -0
  12. package/dist/audit/panel.js +462 -0
  13. package/dist/audit/panel.js.map +1 -0
  14. package/dist/audit/refused.d.ts +99 -0
  15. package/dist/audit/refused.d.ts.map +1 -0
  16. package/dist/audit/refused.js +276 -0
  17. package/dist/audit/refused.js.map +1 -0
  18. package/dist/audit/styles.d.ts +1 -1
  19. package/dist/audit/styles.d.ts.map +1 -1
  20. package/dist/audit/styles.js +429 -1
  21. package/dist/audit/styles.js.map +1 -1
  22. package/dist/audit/surface.d.ts +54 -12
  23. package/dist/audit/surface.d.ts.map +1 -1
  24. package/dist/audit/surface.js +53 -13
  25. package/dist/audit/surface.js.map +1 -1
  26. package/dist/create/draft.d.ts +36 -0
  27. package/dist/create/draft.d.ts.map +1 -1
  28. package/dist/create/draft.js +40 -0
  29. package/dist/create/draft.js.map +1 -1
  30. package/dist/create/keys.d.ts +134 -9
  31. package/dist/create/keys.d.ts.map +1 -1
  32. package/dist/create/keys.js +119 -20
  33. package/dist/create/keys.js.map +1 -1
  34. package/dist/firstpass/candidates.d.ts +17 -1
  35. package/dist/firstpass/candidates.d.ts.map +1 -1
  36. package/dist/index.d.ts +42 -17
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +45 -14
  39. package/dist/index.js.map +1 -1
  40. package/dist/overlay/render.d.ts +11 -3
  41. package/dist/overlay/render.d.ts.map +1 -1
  42. package/dist/overlay/render.js +76 -20
  43. package/dist/overlay/render.js.map +1 -1
  44. package/dist/overlay/request.d.ts +71 -0
  45. package/dist/overlay/request.d.ts.map +1 -0
  46. package/dist/overlay/request.js +129 -0
  47. package/dist/overlay/request.js.map +1 -0
  48. package/dist/overlay/styles.d.ts +1 -1
  49. package/dist/overlay/styles.d.ts.map +1 -1
  50. package/dist/overlay/styles.js +78 -7
  51. package/dist/overlay/styles.js.map +1 -1
  52. package/dist/picker/render.d.ts +26 -16
  53. package/dist/picker/render.d.ts.map +1 -1
  54. package/dist/picker/render.js +25 -36
  55. package/dist/picker/render.js.map +1 -1
  56. package/dist/picker/styles.d.ts +7 -7
  57. package/dist/picker/styles.d.ts.map +1 -1
  58. package/dist/picker/styles.js +7 -36
  59. package/dist/picker/styles.js.map +1 -1
  60. package/dist/picker/view.d.ts +8 -4
  61. package/dist/picker/view.d.ts.map +1 -1
  62. package/dist/picker/view.js +4 -3
  63. package/dist/picker/view.js.map +1 -1
  64. package/dist/picker/words.d.ts +12 -10
  65. package/dist/picker/words.d.ts.map +1 -1
  66. package/dist/picker/words.js +12 -8
  67. package/dist/picker/words.js.map +1 -1
  68. package/dist/reevaluate/parts.d.ts +130 -0
  69. package/dist/reevaluate/parts.d.ts.map +1 -0
  70. package/dist/reevaluate/parts.js +260 -0
  71. package/dist/reevaluate/parts.js.map +1 -0
  72. package/dist/reevaluate/render.d.ts +18 -11
  73. package/dist/reevaluate/render.d.ts.map +1 -1
  74. package/dist/reevaluate/render.js +21 -135
  75. package/dist/reevaluate/render.js.map +1 -1
  76. package/dist/reevaluate/styles.d.ts +1 -1
  77. package/dist/reevaluate/styles.d.ts.map +1 -1
  78. package/dist/reevaluate/styles.js +26 -0
  79. package/dist/reevaluate/styles.js.map +1 -1
  80. package/dist/reevaluate/view.d.ts +16 -1
  81. package/dist/reevaluate/view.d.ts.map +1 -1
  82. package/dist/reevaluate/view.js +21 -1
  83. package/dist/reevaluate/view.js.map +1 -1
  84. package/dist/reevaluate/words.d.ts +7 -0
  85. package/dist/reevaluate/words.d.ts.map +1 -1
  86. package/dist/scale/ladder.d.ts +62 -6
  87. package/dist/scale/ladder.d.ts.map +1 -1
  88. package/dist/scale/ladder.js +129 -11
  89. package/dist/scale/ladder.js.map +1 -1
  90. package/dist/scale/render.d.ts +44 -0
  91. package/dist/scale/render.d.ts.map +1 -1
  92. package/dist/scale/render.js +206 -28
  93. package/dist/scale/render.js.map +1 -1
  94. package/dist/scale/styles.d.ts +11 -5
  95. package/dist/scale/styles.d.ts.map +1 -1
  96. package/dist/scale/styles.js +133 -4
  97. package/dist/scale/styles.js.map +1 -1
  98. package/dist/workspace/chrome.d.ts +1 -1
  99. package/dist/workspace/chrome.d.ts.map +1 -1
  100. package/dist/workspace/chrome.js +48 -0
  101. package/dist/workspace/chrome.js.map +1 -1
  102. package/dist/workspace/firstpass.d.ts +166 -0
  103. package/dist/workspace/firstpass.d.ts.map +1 -0
  104. package/dist/workspace/firstpass.js +151 -0
  105. package/dist/workspace/firstpass.js.map +1 -0
  106. package/dist/workspace/host.d.ts +283 -11
  107. package/dist/workspace/host.d.ts.map +1 -1
  108. package/dist/workspace/host.js +614 -43
  109. package/dist/workspace/host.js.map +1 -1
  110. package/dist/workspace/inspector.d.ts +72 -2
  111. package/dist/workspace/inspector.d.ts.map +1 -1
  112. package/dist/workspace/inspector.js +26 -0
  113. package/dist/workspace/inspector.js.map +1 -1
  114. package/dist/workspace/mount.d.ts +75 -6
  115. package/dist/workspace/mount.d.ts.map +1 -1
  116. package/dist/workspace/mount.js +1851 -60
  117. package/dist/workspace/mount.js.map +1 -1
  118. package/dist/workspace/rail.d.ts +5 -2
  119. package/dist/workspace/rail.d.ts.map +1 -1
  120. package/dist/workspace/rail.js.map +1 -1
  121. package/dist/workspace/recovery.d.ts +155 -0
  122. package/dist/workspace/recovery.d.ts.map +1 -0
  123. package/dist/workspace/recovery.js +156 -0
  124. package/dist/workspace/recovery.js.map +1 -0
  125. package/dist/workspace/render.d.ts +779 -2
  126. package/dist/workspace/render.d.ts.map +1 -1
  127. package/dist/workspace/render.js +1727 -61
  128. package/dist/workspace/render.js.map +1 -1
  129. package/dist/workspace/selection.d.ts +11 -0
  130. package/dist/workspace/selection.d.ts.map +1 -1
  131. package/dist/workspace/selection.js +10 -0
  132. package/dist/workspace/selection.js.map +1 -1
  133. package/dist/workspace/styles.d.ts +1 -1
  134. package/dist/workspace/styles.d.ts.map +1 -1
  135. package/dist/workspace/styles.js +1405 -42
  136. package/dist/workspace/styles.js.map +1 -1
  137. package/package.json +4 -4
@@ -8,14 +8,20 @@
8
8
  * scans these bytes for a literal colour or a fixed length and for a token the
9
9
  * theme does not define, so the rule is enforced rather than remembered.
10
10
  *
11
- * IT ADDS SELECTORS RATHER THAN REDEFINING THEM. The refusal, its capsules and
12
- * their counts already have a look — this file styles only what layer 2
13
- * introduces: the ladder container, the routes, the search box, the isolated
14
- * chip and the list it opens.
11
+ * IT ADDS SELECTORS RATHER THAN REDEFINING THEM, with ONE declared exception.
12
+ * The refusal and its counts already have a look, so this file styles what
13
+ * layer 2 introduces: the ladder container, the routes, the search box, the
14
+ * isolated chip, the list it opens, and §17f's capsule card. The exception is
15
+ * `.ig-ladder .ig-refusal .ig-list`, which lays layer 1's list out as a board —
16
+ * declared here because the arrangement is this surface's, and scoped so §16's
17
+ * single-column list and the ladder's own search matches are untouched. The
18
+ * capsule CARD is a new class rather than a redefinition of `.ig-capsule` for
19
+ * the same reason, which is why the list item beside it carries no layer 1
20
+ * class at all.
15
21
  *
16
22
  * Shipped as a string for the same reason the viewer's is: an entry that
17
23
  * imports CSS cannot be loaded by a bare Node runtime, and a string needs no
18
24
  * bundler.
19
25
  */
20
- export declare const scaleLadderStylesheet = "\n.ig-ladder {\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}\n\n.ig-ladder-routes {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: var(--ig-space-tight);\n color: var(--ig-text-muted);\n font-size: var(--ig-font-size-small);\n}\n\n.ig-ladder-search {\n display: flex;\n flex-direction: column;\n gap: var(--ig-space-tight);\n}\n\n.ig-ladder-search input {\n background: var(--ig-surface);\n color: var(--ig-text);\n border: var(--ig-stroke) solid var(--ig-line);\n border-radius: var(--ig-radius);\n padding: var(--ig-space-tight);\n font-family: var(--ig-font-ui);\n font-size: var(--ig-font-size);\n}\n\n.ig-ladder-search input:focus-visible {\n outline: var(--ig-focus-ring) solid var(--ig-focus);\n outline-offset: var(--ig-space-tight);\n}\n\n.ig-ladder-match {\n display: flex;\n gap: var(--ig-space-tight);\n align-items: baseline;\n}\n\n.ig-chip {\n align-self: flex-start;\n background: var(--ig-surface-2);\n color: var(--ig-text-body);\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 font-family: var(--ig-font-ui);\n font-size: var(--ig-font-size-small);\n cursor: pointer;\n}\n\n.ig-chip:focus-visible {\n outline: var(--ig-focus-ring) solid var(--ig-focus);\n outline-offset: var(--ig-space-tight);\n}\n\n.ig-chip[aria-expanded='true'] {\n border-color: var(--ig-accent);\n color: var(--ig-text);\n}\n\n.ig-isolated-list {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: var(--ig-space-tight);\n}\n\n.ig-isolated-list .ig-id {\n font-family: var(--ig-font-mono);\n color: var(--ig-text-muted);\n}\n";
26
+ export declare const scaleLadderStylesheet = "\n.ig-ladder {\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}\n\n.ig-ladder-routes {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: var(--ig-space-tight);\n color: var(--ig-text-muted);\n font-size: var(--ig-font-size-small);\n}\n\n.ig-ladder-search {\n display: flex;\n flex-direction: column;\n gap: var(--ig-space-tight);\n}\n\n.ig-ladder-search input {\n background: var(--ig-surface);\n color: var(--ig-text);\n border: var(--ig-stroke) solid var(--ig-line);\n border-radius: var(--ig-radius);\n padding: var(--ig-space-tight);\n font-family: var(--ig-font-ui);\n font-size: var(--ig-font-size);\n}\n\n.ig-ladder-search input:focus-visible {\n outline: var(--ig-focus-ring) solid var(--ig-focus);\n outline-offset: var(--ig-space-tight);\n}\n\n.ig-ladder-match {\n display: flex;\n gap: var(--ig-space-tight);\n align-items: baseline;\n}\n\n.ig-chip {\n align-self: flex-start;\n background: var(--ig-surface-2);\n color: var(--ig-text-body);\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 font-family: var(--ig-font-ui);\n font-size: var(--ig-font-size-small);\n cursor: pointer;\n}\n\n.ig-chip:focus-visible {\n outline: var(--ig-focus-ring) solid var(--ig-focus);\n outline-offset: var(--ig-space-tight);\n}\n\n.ig-chip[aria-expanded='true'] {\n border-color: var(--ig-accent);\n color: var(--ig-text);\n}\n\n/* THE LIST ITEM IS A GRID CELL AND NOTHING ELSE. It deliberately does not\n carry .ig-capsule: that is layer 1's own card, and a card inside a card put\n the stuck tint and the hover on the inner border only. */\n.ig-capsule-cell {\n display: flex;\n min-width: 0;\n}\n\n/* FOUR CARDS ACROSS A COLUMN, NOT FOUR ROWS DOWN IT. Frame 17f lays the\n capsules out as a board, which is what makes the counts comparable at a\n glance \u2014 the whole reason the count leads. minmax(0, 1fr) rather than a\n width, because this stylesheet declares no fixed length: a title is arbitrary\n and auto would let the longest one set every column. Scoped to the ladder's\n own refusal so \u00A716's single-column list, drawn from the same class in layer\n 1, is untouched. */\n.ig-ladder .ig-refusal .ig-list {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: var(--ig-space);\n}\n\n/* THE WHOLE CAPSULE IS THE CONTROL, so the button carries the card and the\n list item carries nothing. A button wrapping a card needs its own alignment\n and wrapping reset: a user agent centres and nowraps button content, and both\n are wrong for a four-part card with a title in it. */\n.ig-capsule-enter {\n display: grid;\n grid-template-columns: 1fr auto;\n align-items: start;\n gap: var(--ig-space-tight) var(--ig-space);\n width: 100%;\n height: 100%;\n text-align: left;\n background: var(--ig-surface-2);\n color: var(--ig-text-body);\n border: var(--ig-stroke) solid var(--ig-line);\n border-radius: var(--ig-radius);\n padding: var(--ig-space);\n font-family: var(--ig-font-ui);\n font-size: var(--ig-font-size);\n cursor: pointer;\n}\n\n.ig-capsule-enter:focus-visible {\n outline: var(--ig-focus-ring) solid var(--ig-focus);\n outline-offset: var(--ig-space-tight);\n}\n\n.ig-capsule-enter:hover {\n border-color: var(--ig-accent);\n}\n\n/* THE WHOLE STUCK CARD IS TINTED, as frame 17f draws it \u2014 the badge alone puts\n the one finding that stops work outright at the size of every other chip on\n the board. Same hue as the badge inside it, which is blocked-by's, because\n that is the edge the cycle is made of. */\n.ig-capsule-enter[data-reach='cyclic'] {\n border-color: var(--ig-edge-blocked-by);\n}\n\n.ig-capsule-enter[data-reach='cyclic'] .ig-capsule-reach {\n color: var(--ig-edge-blocked-by);\n}\n\n/* THE COUNT LEADS, so it is the one part sized to be read down the board rather\n than within a card. The --ig-font-size-rank token is the one the theme defines\n for exactly this \u2014 \"the rank number itself\" \u2014 so a capsule's count and a rail\n rank are one size by declaration rather than by coincidence. */\n.ig-capsule-size {\n grid-column: 1;\n font-family: var(--ig-font-mono);\n font-size: var(--ig-font-size-rank);\n font-variant-numeric: tabular-nums;\n color: var(--ig-text);\n}\n\n/* END-ALIGNED AND CONTENT-WIDTH. A grid item stretches by default, which drew\n the cycle badge as a full-width bar across the top of the card. The pill is\n the frame's: the blocked count and the cycle sit in one slot, so they read as\n one kind of thing rather than as a bordered badge beside bare text. */\n.ig-capsule-load {\n grid-column: 2;\n justify-self: end;\n background: var(--ig-bg);\n border: var(--ig-stroke) solid var(--ig-line);\n border-radius: var(--ig-radius);\n padding: var(--ig-space-micro) var(--ig-space-snug);\n font-family: var(--ig-font-mono);\n font-size: var(--ig-font-size-small);\n font-variant-numeric: tabular-nums;\n color: var(--ig-text-muted);\n}\n\n/* THE NAME AND THE REACH SPAN BOTH COLUMNS, and the name is the one part\n allowed to wrap: a title is arbitrary length, and letting it size the grid\n would make every card on the board a different shape. */\n.ig-capsule-name {\n grid-column: 1 / -1;\n color: var(--ig-text);\n min-width: 0;\n overflow-wrap: anywhere;\n}\n\n.ig-capsule-reach {\n grid-column: 1 / -1;\n color: var(--ig-text-muted);\n font-size: var(--ig-font-size-small);\n font-variant-numeric: tabular-nums;\n}\n\n.ig-isolated-list {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: var(--ig-space-tight);\n}\n\n/* A ROW PER ENTRY, WITH A GAP THE MARKUP DOES NOT SUPPLY. The element helper\n concatenates its children with no whitespace, so the key and the title\n rendered as one run of text \u2014 #492Audit log pagination. Latent until \u00A717a\n moved the CONTROL to the rail footer and made this list the thing a reader is\n sent to, rather than a chip's afterthought. */\n.ig-isolated-list li {\n display: flex;\n gap: var(--ig-space-tight);\n align-items: baseline;\n min-width: 0;\n}\n\n.ig-isolated-list .ig-id {\n font-family: var(--ig-font-mono);\n color: var(--ig-text-muted);\n flex: none;\n}\n";
21
27
  //# sourceMappingURL=styles.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../../src/scale/styles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,eAAO,MAAM,qBAAqB,23DAoFjC,CAAC"}
1
+ {"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../../src/scale/styles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,eAAO,MAAM,qBAAqB,q3MA+MjC,CAAC"}
@@ -8,10 +8,16 @@
8
8
  * scans these bytes for a literal colour or a fixed length and for a token the
9
9
  * theme does not define, so the rule is enforced rather than remembered.
10
10
  *
11
- * IT ADDS SELECTORS RATHER THAN REDEFINING THEM. The refusal, its capsules and
12
- * their counts already have a look — this file styles only what layer 2
13
- * introduces: the ladder container, the routes, the search box, the isolated
14
- * chip and the list it opens.
11
+ * IT ADDS SELECTORS RATHER THAN REDEFINING THEM, with ONE declared exception.
12
+ * The refusal and its counts already have a look, so this file styles what
13
+ * layer 2 introduces: the ladder container, the routes, the search box, the
14
+ * isolated chip, the list it opens, and §17f's capsule card. The exception is
15
+ * `.ig-ladder .ig-refusal .ig-list`, which lays layer 1's list out as a board —
16
+ * declared here because the arrangement is this surface's, and scoped so §16's
17
+ * single-column list and the ladder's own search matches are untouched. The
18
+ * capsule CARD is a new class rather than a redefinition of `.ig-capsule` for
19
+ * the same reason, which is why the list item beside it carries no layer 1
20
+ * class at all.
15
21
  *
16
22
  * Shipped as a string for the same reason the viewer's is: an entry that
17
23
  * imports CSS cannot be loaded by a bare Node runtime, and a string needs no
@@ -88,6 +94,116 @@ export const scaleLadderStylesheet = `
88
94
  color: var(--ig-text);
89
95
  }
90
96
 
97
+ /* THE LIST ITEM IS A GRID CELL AND NOTHING ELSE. It deliberately does not
98
+ carry .ig-capsule: that is layer 1's own card, and a card inside a card put
99
+ the stuck tint and the hover on the inner border only. */
100
+ .ig-capsule-cell {
101
+ display: flex;
102
+ min-width: 0;
103
+ }
104
+
105
+ /* FOUR CARDS ACROSS A COLUMN, NOT FOUR ROWS DOWN IT. Frame 17f lays the
106
+ capsules out as a board, which is what makes the counts comparable at a
107
+ glance — the whole reason the count leads. minmax(0, 1fr) rather than a
108
+ width, because this stylesheet declares no fixed length: a title is arbitrary
109
+ and auto would let the longest one set every column. Scoped to the ladder's
110
+ own refusal so §16's single-column list, drawn from the same class in layer
111
+ 1, is untouched. */
112
+ .ig-ladder .ig-refusal .ig-list {
113
+ display: grid;
114
+ grid-template-columns: repeat(2, minmax(0, 1fr));
115
+ gap: var(--ig-space);
116
+ }
117
+
118
+ /* THE WHOLE CAPSULE IS THE CONTROL, so the button carries the card and the
119
+ list item carries nothing. A button wrapping a card needs its own alignment
120
+ and wrapping reset: a user agent centres and nowraps button content, and both
121
+ are wrong for a four-part card with a title in it. */
122
+ .ig-capsule-enter {
123
+ display: grid;
124
+ grid-template-columns: 1fr auto;
125
+ align-items: start;
126
+ gap: var(--ig-space-tight) var(--ig-space);
127
+ width: 100%;
128
+ height: 100%;
129
+ text-align: left;
130
+ background: var(--ig-surface-2);
131
+ color: var(--ig-text-body);
132
+ border: var(--ig-stroke) solid var(--ig-line);
133
+ border-radius: var(--ig-radius);
134
+ padding: var(--ig-space);
135
+ font-family: var(--ig-font-ui);
136
+ font-size: var(--ig-font-size);
137
+ cursor: pointer;
138
+ }
139
+
140
+ .ig-capsule-enter:focus-visible {
141
+ outline: var(--ig-focus-ring) solid var(--ig-focus);
142
+ outline-offset: var(--ig-space-tight);
143
+ }
144
+
145
+ .ig-capsule-enter:hover {
146
+ border-color: var(--ig-accent);
147
+ }
148
+
149
+ /* THE WHOLE STUCK CARD IS TINTED, as frame 17f draws it — the badge alone puts
150
+ the one finding that stops work outright at the size of every other chip on
151
+ the board. Same hue as the badge inside it, which is blocked-by's, because
152
+ that is the edge the cycle is made of. */
153
+ .ig-capsule-enter[data-reach='cyclic'] {
154
+ border-color: var(--ig-edge-blocked-by);
155
+ }
156
+
157
+ .ig-capsule-enter[data-reach='cyclic'] .ig-capsule-reach {
158
+ color: var(--ig-edge-blocked-by);
159
+ }
160
+
161
+ /* THE COUNT LEADS, so it is the one part sized to be read down the board rather
162
+ than within a card. The --ig-font-size-rank token is the one the theme defines
163
+ for exactly this — "the rank number itself" — so a capsule's count and a rail
164
+ rank are one size by declaration rather than by coincidence. */
165
+ .ig-capsule-size {
166
+ grid-column: 1;
167
+ font-family: var(--ig-font-mono);
168
+ font-size: var(--ig-font-size-rank);
169
+ font-variant-numeric: tabular-nums;
170
+ color: var(--ig-text);
171
+ }
172
+
173
+ /* END-ALIGNED AND CONTENT-WIDTH. A grid item stretches by default, which drew
174
+ the cycle badge as a full-width bar across the top of the card. The pill is
175
+ the frame's: the blocked count and the cycle sit in one slot, so they read as
176
+ one kind of thing rather than as a bordered badge beside bare text. */
177
+ .ig-capsule-load {
178
+ grid-column: 2;
179
+ justify-self: end;
180
+ background: var(--ig-bg);
181
+ border: var(--ig-stroke) solid var(--ig-line);
182
+ border-radius: var(--ig-radius);
183
+ padding: var(--ig-space-micro) var(--ig-space-snug);
184
+ font-family: var(--ig-font-mono);
185
+ font-size: var(--ig-font-size-small);
186
+ font-variant-numeric: tabular-nums;
187
+ color: var(--ig-text-muted);
188
+ }
189
+
190
+ /* THE NAME AND THE REACH SPAN BOTH COLUMNS, and the name is the one part
191
+ allowed to wrap: a title is arbitrary length, and letting it size the grid
192
+ would make every card on the board a different shape. */
193
+ .ig-capsule-name {
194
+ grid-column: 1 / -1;
195
+ color: var(--ig-text);
196
+ min-width: 0;
197
+ overflow-wrap: anywhere;
198
+ }
199
+
200
+ .ig-capsule-reach {
201
+ grid-column: 1 / -1;
202
+ color: var(--ig-text-muted);
203
+ font-size: var(--ig-font-size-small);
204
+ font-variant-numeric: tabular-nums;
205
+ }
206
+
91
207
  .ig-isolated-list {
92
208
  list-style: none;
93
209
  margin: 0;
@@ -97,9 +213,22 @@ export const scaleLadderStylesheet = `
97
213
  gap: var(--ig-space-tight);
98
214
  }
99
215
 
216
+ /* A ROW PER ENTRY, WITH A GAP THE MARKUP DOES NOT SUPPLY. The element helper
217
+ concatenates its children with no whitespace, so the key and the title
218
+ rendered as one run of text — #492Audit log pagination. Latent until §17a
219
+ moved the CONTROL to the rail footer and made this list the thing a reader is
220
+ sent to, rather than a chip's afterthought. */
221
+ .ig-isolated-list li {
222
+ display: flex;
223
+ gap: var(--ig-space-tight);
224
+ align-items: baseline;
225
+ min-width: 0;
226
+ }
227
+
100
228
  .ig-isolated-list .ig-id {
101
229
  font-family: var(--ig-font-mono);
102
230
  color: var(--ig-text-muted);
231
+ flex: none;
103
232
  }
104
233
  `;
105
234
  //# sourceMappingURL=styles.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"styles.js","sourceRoot":"","sources":["../../src/scale/styles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,MAAM,CAAC,MAAM,qBAAqB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoFpC,CAAC"}
1
+ {"version":3,"file":"styles.js","sourceRoot":"","sources":["../../src/scale/styles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,MAAM,CAAC,MAAM,qBAAqB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+MpC,CAAC"}
@@ -17,5 +17,5 @@
17
17
  * that imports CSS cannot be loaded by a bare Node runtime, and a string needs
18
18
  * no bundler.
19
19
  */
20
- export declare const mountStylesheet = "\n.ig-mount {\n position: relative;\n height: 100%;\n min-height: 0;\n}\n\n.ig-mount > .ig-workspace {\n /* THE FLOATING CHOOSER'S BOX. A canvas drop places the kind chooser inside\n the workspace root, so a host that scopes its theme to that root still\n resolves the chooser's tokens; the root is the positioning box for it. */\n position: relative;\n height: 100%;\n box-sizing: border-box;\n}\n\n.ig-mount[data-dragging='true'] {\n cursor: grabbing;\n user-select: none;\n}\n\n.ig-chrome {\n padding: var(--ig-space-tight) var(--ig-space);\n border-top: var(--ig-stroke) solid var(--ig-line);\n display: flex;\n flex-direction: column;\n gap: var(--ig-space-tight);\n font-family: var(--ig-font-ui);\n font-size: var(--ig-font-size-small);\n line-height: var(--ig-line-height);\n color: var(--ig-text-body);\n}\n\n.ig-chrome-sentence {\n margin: 0;\n font-family: var(--ig-font-mono);\n color: var(--ig-text);\n}\n\n.ig-chrome-button {\n font: inherit;\n font-size: var(--ig-font-size-small);\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 text-align: left;\n}\n\n.ig-chrome-button:hover {\n border-color: var(--ig-accent);\n}\n\n.ig-chrome-button:focus-visible {\n outline: var(--ig-focus-ring) solid var(--ig-focus);\n outline-offset: var(--ig-space-tight);\n}\n\n.ig-chrome-quiet {\n background: none;\n color: var(--ig-text-muted);\n}\n\n.ig-chrome-danger {\n color: var(--ig-state-failed);\n border-color: var(--ig-state-failed);\n}\n\n.ig-chrome-input {\n font: inherit;\n font-size: var(--ig-font-size-small);\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 width: 100%;\n box-sizing: border-box;\n}\n\n.ig-chrome-input:focus-visible {\n outline: var(--ig-focus-ring) solid var(--ig-focus);\n outline-offset: var(--ig-space-tight);\n}\n\n.ig-chrome-kinds {\n display: flex;\n flex-direction: column;\n gap: var(--ig-space-tight);\n}\n\n/* EACH KIND CARRIES ITS HUE ON THE LEFT, the same hue the viewer draws the\n edge in, so the chooser and the canvas name a relationship the same way. */\n.ig-chrome-kinds .ig-chrome-button {\n border-left: var(--ig-terminal-width) solid var(--ig-line);\n}\n\n.ig-chrome-kinds [data-edge='blocked-by'] {\n border-left-color: var(--ig-edge-blocked-by);\n}\n\n.ig-chrome-kinds [data-edge='serialize-with'] {\n border-left-color: var(--ig-edge-serialize-with);\n}\n\n.ig-chrome-kinds [data-edge='together-with'] {\n border-left-color: var(--ig-edge-together-with);\n}\n\n.ig-chrome-kinds [data-edge='duplicate-of'] {\n border-left-color: var(--ig-edge-duplicate-of);\n}\n\n.ig-chrome-kinds [data-edge='decomposed-from'] {\n border-left-color: var(--ig-edge-decomposed-from);\n}\n\n.ig-chrome-matches {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: var(--ig-space-tight);\n}\n\n.ig-chrome-match {\n width: 100%;\n}\n\n.ig-chrome-search,\n.ig-chrome-chooser {\n display: flex;\n flex-direction: column;\n gap: var(--ig-space-tight);\n}\n\n/* PLACED BY THE MOUNT, at the drop point `pickerPlacement` chose: `left` and\n `top` are written inline on the element, so this rule carries the box and\n nothing about where it sits. */\n.ig-chrome-floating {\n position: absolute;\n width: calc(var(--ig-char-width) * 40);\n padding: var(--ig-space-tight) var(--ig-space);\n background: var(--ig-surface-2);\n border: var(--ig-stroke) solid var(--ig-accent);\n border-radius: var(--ig-radius);\n z-index: 2;\n}\n\n.ig-chrome-keys {\n margin: 0;\n font-size: var(--ig-font-size-small);\n color: var(--ig-text-muted);\n}\n";
20
+ export declare const mountStylesheet = "\n.ig-mount {\n position: relative;\n height: 100%;\n min-height: 0;\n}\n\n.ig-mount > .ig-workspace {\n /* THE FLOATING CHOOSER'S BOX. A canvas drop places the kind chooser inside\n the workspace root, so a host that scopes its theme to that root still\n resolves the chooser's tokens; the root is the positioning box for it. */\n position: relative;\n height: 100%;\n box-sizing: border-box;\n}\n\n.ig-mount[data-dragging='true'] {\n cursor: grabbing;\n user-select: none;\n}\n\n.ig-chrome {\n padding: var(--ig-space-tight) var(--ig-space);\n border-top: var(--ig-stroke) solid var(--ig-line);\n display: flex;\n flex-direction: column;\n gap: var(--ig-space-tight);\n font-family: var(--ig-font-ui);\n font-size: var(--ig-font-size-small);\n line-height: var(--ig-line-height);\n color: var(--ig-text-body);\n}\n\n.ig-chrome-sentence {\n margin: 0;\n font-family: var(--ig-font-mono);\n color: var(--ig-text);\n}\n\n.ig-chrome-button {\n font: inherit;\n font-size: var(--ig-font-size-small);\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 text-align: left;\n}\n\n.ig-chrome-button:hover {\n border-color: var(--ig-accent);\n}\n\n.ig-chrome-button:focus-visible {\n outline: var(--ig-focus-ring) solid var(--ig-focus);\n outline-offset: var(--ig-space-tight);\n}\n\n.ig-chrome-quiet {\n background: none;\n color: var(--ig-text-muted);\n}\n\n.ig-chrome-danger {\n color: var(--ig-state-failed);\n border-color: var(--ig-state-failed);\n}\n\n.ig-chrome-input {\n font: inherit;\n font-size: var(--ig-font-size-small);\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 width: 100%;\n box-sizing: border-box;\n}\n\n.ig-chrome-input:focus-visible {\n outline: var(--ig-focus-ring) solid var(--ig-focus);\n outline-offset: var(--ig-space-tight);\n}\n\n.ig-chrome-kinds {\n display: flex;\n flex-direction: column;\n gap: var(--ig-space-tight);\n}\n\n/* EACH KIND CARRIES ITS HUE ON THE LEFT, the same hue the viewer draws the\n edge in, so the chooser and the canvas name a relationship the same way. */\n.ig-chrome-kinds .ig-chrome-button {\n border-left: var(--ig-terminal-width) solid var(--ig-line);\n}\n\n.ig-chrome-kinds [data-edge='blocked-by'] {\n border-left-color: var(--ig-edge-blocked-by);\n}\n\n.ig-chrome-kinds [data-edge='serialize-with'] {\n border-left-color: var(--ig-edge-serialize-with);\n}\n\n.ig-chrome-kinds [data-edge='together-with'] {\n border-left-color: var(--ig-edge-together-with);\n}\n\n.ig-chrome-kinds [data-edge='duplicate-of'] {\n border-left-color: var(--ig-edge-duplicate-of);\n}\n\n.ig-chrome-kinds [data-edge='decomposed-from'] {\n border-left-color: var(--ig-edge-decomposed-from);\n}\n\n.ig-chrome-matches {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: var(--ig-space-tight);\n}\n\n.ig-chrome-match {\n width: 100%;\n}\n\n.ig-chrome-search,\n.ig-chrome-chooser {\n display: flex;\n flex-direction: column;\n gap: var(--ig-space-tight);\n}\n\n/* PLACED BY THE MOUNT, at the drop point `pickerPlacement` chose: `left` and\n `top` are written inline on the element, so this rule carries the box and\n nothing about where it sits. */\n.ig-chrome-floating {\n position: absolute;\n width: calc(var(--ig-char-width) * 40);\n padding: var(--ig-space-tight) var(--ig-space);\n background: var(--ig-surface-2);\n border: var(--ig-stroke) solid var(--ig-accent);\n border-radius: var(--ig-radius);\n z-index: 2;\n}\n\n.ig-chrome-keys {\n margin: 0;\n font-size: var(--ig-font-size-small);\n color: var(--ig-text-muted);\n}\n\n/* THE FIRST PASS COVERS THE WORKSPACE. \u00A717e is a review queue rather than a\n fourth column, so it is drawn over the three zones instead of beside them.\n Placed against the workspace root, which is this sheet's positioning box. */\n.ig-firstpass-overlay {\n position: absolute;\n inset: 0;\n z-index: 3;\n display: flex;\n flex-direction: column;\n align-items: center;\n /* START-SAFE, NOT CENTRED. Evidence is host prose of no fixed length, and a\n short workspace makes any panel too tall \u2014 centring overflow puts the\n question above the container's own origin, where no scroll can reach it.\n Laid out from the top and centred by the panel's own auto margins, which\n collapse the moment there is not enough room. */\n justify-content: flex-start;\n overflow: auto;\n gap: var(--ig-space);\n padding: var(--ig-space-loose);\n box-sizing: border-box;\n background: var(--ig-bg);\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}\n\n/* A CONTAINER THAT TAKES FOCUS NEEDS A RING. The wrapper is focused on open and\n after every answer, so without one a sighted keyboard user cannot see where\n they are on the surface built for them. */\n.ig-firstpass-overlay:focus-visible {\n outline: var(--ig-focus-ring) solid var(--ig-focus);\n outline-offset: calc(var(--ig-stroke) * -1);\n}\n\n.ig-firstpass-panel {\n width: 100%;\n max-width: calc(var(--ig-char-width) * 72);\n /* Centres while it fits, and starts at the top the moment it does not. */\n margin-block: auto;\n}\n\n/* The scanning and failed sentences. Both are the host's words. */\n.ig-firstpass-note {\n margin: 0;\n color: var(--ig-text-muted);\n}\n";
21
21
  //# sourceMappingURL=chrome.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"chrome.d.ts","sourceRoot":"","sources":["../../src/workspace/chrome.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,eAAO,MAAM,eAAe,40HA6J3B,CAAC"}
1
+ {"version":3,"file":"chrome.d.ts","sourceRoot":"","sources":["../../src/workspace/chrome.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,eAAO,MAAM,eAAe,ijLA6M3B,CAAC"}
@@ -174,5 +174,53 @@ export const mountStylesheet = `
174
174
  font-size: var(--ig-font-size-small);
175
175
  color: var(--ig-text-muted);
176
176
  }
177
+
178
+ /* THE FIRST PASS COVERS THE WORKSPACE. §17e is a review queue rather than a
179
+ fourth column, so it is drawn over the three zones instead of beside them.
180
+ Placed against the workspace root, which is this sheet's positioning box. */
181
+ .ig-firstpass-overlay {
182
+ position: absolute;
183
+ inset: 0;
184
+ z-index: 3;
185
+ display: flex;
186
+ flex-direction: column;
187
+ align-items: center;
188
+ /* START-SAFE, NOT CENTRED. Evidence is host prose of no fixed length, and a
189
+ short workspace makes any panel too tall — centring overflow puts the
190
+ question above the container's own origin, where no scroll can reach it.
191
+ Laid out from the top and centred by the panel's own auto margins, which
192
+ collapse the moment there is not enough room. */
193
+ justify-content: flex-start;
194
+ overflow: auto;
195
+ gap: var(--ig-space);
196
+ padding: var(--ig-space-loose);
197
+ box-sizing: border-box;
198
+ background: var(--ig-bg);
199
+ font-family: var(--ig-font-ui);
200
+ font-size: var(--ig-font-size);
201
+ line-height: var(--ig-line-height);
202
+ color: var(--ig-text-body);
203
+ }
204
+
205
+ /* A CONTAINER THAT TAKES FOCUS NEEDS A RING. The wrapper is focused on open and
206
+ after every answer, so without one a sighted keyboard user cannot see where
207
+ they are on the surface built for them. */
208
+ .ig-firstpass-overlay:focus-visible {
209
+ outline: var(--ig-focus-ring) solid var(--ig-focus);
210
+ outline-offset: calc(var(--ig-stroke) * -1);
211
+ }
212
+
213
+ .ig-firstpass-panel {
214
+ width: 100%;
215
+ max-width: calc(var(--ig-char-width) * 72);
216
+ /* Centres while it fits, and starts at the top the moment it does not. */
217
+ margin-block: auto;
218
+ }
219
+
220
+ /* The scanning and failed sentences. Both are the host's words. */
221
+ .ig-firstpass-note {
222
+ margin: 0;
223
+ color: var(--ig-text-muted);
224
+ }
177
225
  `;
178
226
  //# sourceMappingURL=chrome.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"chrome.js","sourceRoot":"","sources":["../../src/workspace/chrome.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,MAAM,CAAC,MAAM,eAAe,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6J9B,CAAC"}
1
+ {"version":3,"file":"chrome.js","sourceRoot":"","sources":["../../src/workspace/chrome.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,MAAM,CAAC,MAAM,eAAe,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6M9B,CAAC"}
@@ -0,0 +1,166 @@
1
+ /**
2
+ * The first pass as a slice of the workspace's state — no DOM, no promises.
3
+ *
4
+ * `firstpass/queue.ts` is the review loop itself and knows nothing about being
5
+ * opened or closed; `firstpass/candidates.ts` declares a port whose one method
6
+ * is asynchronous by construction. Between them sits a lifecycle nobody owned:
7
+ * the surface is shut, then a scan is out, then a queue is up, and a scan can
8
+ * fail. That lifecycle is this file, in the shape `selectionReducer`,
9
+ * `scaleReducer` and `createReducer` already use here — so `host.ts` composes a
10
+ * fourth reducer rather than growing a fourth set of rules.
11
+ *
12
+ * ## The phase is a union, and `decided` sits outside it
13
+ *
14
+ * Everything the phase carries belongs to one phase only, so it is a
15
+ * discriminated union for the reason `firstpass/view.ts` records about its own
16
+ * three states: the impossible combinations stop existing.
17
+ *
18
+ * {@link FirstPassState.decided} is deliberately NOT in the union, because it
19
+ * is the one fact that has to outlive every phase. See below.
20
+ *
21
+ * ## A re-open re-scans, and `decided` is what makes that honest
22
+ *
23
+ * Closing drops the queue rather than resuming it: a queue held across a close
24
+ * carries a candidate set taken before the writes the reader just made, so it
25
+ * would go on asking about relationships that now exist.
26
+ *
27
+ * But a re-scan on its own would re-ask every question the reader already
28
+ * answered — `queue.ts` defines `reject` as "no, this is not a relationship.
29
+ * Answered, and gone", and a host's `findCandidates` is never told what was
30
+ * decided, so it cannot filter them out on the reader's behalf. Forty rejections
31
+ * would come back on the next open, which is a worse outcome than the resume
32
+ * this refuses and defeats §17e's "first pass has an end" across sessions.
33
+ *
34
+ * So the decided ids ride outside the phase and filter the next scan.
35
+ *
36
+ * **`skip` is not decided, and that is the design rather than an omission.**
37
+ * §17e's `S` is "not now", and `queue.ts` ships `skippedCandidates` precisely
38
+ * because "the deferred set is the thing a second pass is built from". A skipped
39
+ * candidate coming back in the next pass is what deferring it meant.
40
+ *
41
+ * ## Scans carry a generation, or the newest answer can lose
42
+ *
43
+ * A reader who closes during a slow scan and opens again has two scans in
44
+ * flight. Discriminating on the phase alone cannot tell their answers apart, so
45
+ * the STALE set — taken before the reader's writes — would open the queue while
46
+ * the fresh one arrived on `open` and was dropped. That is the exact failure the
47
+ * re-scan rule exists to prevent, arriving through the door the rule opened.
48
+ *
49
+ * {@link FirstPassState.scan} is a monotonic counter. Every scan is issued under
50
+ * it, every answer carries the number it was issued under, and an answer whose
51
+ * number is not the current one is dropped.
52
+ */
53
+ import type { Candidate, CandidateId } from '../firstpass/candidates.ts';
54
+ import { type QueueCommand, type QueueResult, type QueueState } from '../firstpass/queue.ts';
55
+ /** What the surface is doing. Exactly one of these, always. */
56
+ export type FirstPassPhase =
57
+ /** Shut. The header's entry is the only way in. */
58
+ {
59
+ readonly kind: 'closed';
60
+ }
61
+ /** A scan is out. The generation is {@link FirstPassState.scan}. */
62
+ | {
63
+ readonly kind: 'scanning';
64
+ }
65
+ /**
66
+ * The host's scan rejected.
67
+ *
68
+ * ITS OWN PHASE rather than an empty queue: `empty` means the host found
69
+ * nothing to encode, which is a claim about the backlog that a failed scan
70
+ * does not license — the vacuity `firstpass/view.ts` separates its own two
71
+ * end states to avoid. And its own phase rather than a silent return to
72
+ * `closed`, which made a broken port indistinguishable from a dead button.
73
+ *
74
+ * It carries no payload: the reason is the host's and this package does not
75
+ * read it. A shell draws its own sentence and its own way out.
76
+ */
77
+ | {
78
+ readonly kind: 'failed';
79
+ } | {
80
+ readonly kind: 'open';
81
+ readonly queue: QueueState;
82
+ };
83
+ export interface FirstPassState {
84
+ readonly phase: FirstPassPhase;
85
+ /**
86
+ * The candidates DECIDED in this state's lifetime — `apply` and `reject`.
87
+ *
88
+ * Filters the next scan, so a re-open does not re-ask a decided question. Held
89
+ * as ids rather than candidates because that is what a scan can be compared
90
+ * against, and because {@link CandidateId} is the identity the HOST minted:
91
+ * `firstpass/candidates.ts` is explicit that two findings about the same pair
92
+ * are not necessarily the same finding, so nothing here derives an identity of
93
+ * its own.
94
+ */
95
+ readonly decided: readonly CandidateId[];
96
+ /** The generation the newest scan was issued under. Monotonic. */
97
+ readonly scan: number;
98
+ }
99
+ export declare const INITIAL_FIRST_PASS: FirstPassState;
100
+ /**
101
+ * One act on the surface.
102
+ *
103
+ * `candidates` and `scan-failed` are the two answers a scan can have, and both
104
+ * carry the generation they were issued under — see the module header.
105
+ */
106
+ export type FirstPassCommand = {
107
+ readonly kind: 'open';
108
+ } | {
109
+ readonly kind: 'candidates';
110
+ readonly scan: number;
111
+ readonly candidates: readonly Candidate[];
112
+ } | {
113
+ readonly kind: 'scan-failed';
114
+ readonly scan: number;
115
+ } | {
116
+ readonly kind: 'queue';
117
+ readonly command: QueueCommand;
118
+ } | {
119
+ readonly kind: 'close';
120
+ }
121
+ /**
122
+ * Close, and forget the decisions too.
123
+ *
124
+ * A DIFFERENT SCANNER IS A DIFFERENT FIRST PASS. {@link CandidateId} is
125
+ * opaque and host-minted, and `candidates.ts` promises only that it is stable
126
+ * for a queue's life — so two independently written detectors may reuse the
127
+ * same id for entirely different findings. Carrying one scanner's decisions
128
+ * into another's scan would silently drop the next detector's question on an
129
+ * id collision, which is the one failure a first pass cannot afford: a
130
+ * question nobody was asked looks exactly like a question already answered.
131
+ */
132
+ | {
133
+ readonly kind: 'reset';
134
+ };
135
+ export interface FirstPassOutcome {
136
+ readonly state: FirstPassState;
137
+ /**
138
+ * The queue transition, when one happened.
139
+ *
140
+ * Passed through UNCHANGED rather than interpreted: `QueueResult` already
141
+ * carries the proposal an `apply` stands for and the answer an `undo` took
142
+ * back, and re-deriving either here would be a second opinion about what the
143
+ * queue just did.
144
+ */
145
+ readonly result: QueueResult | null;
146
+ /**
147
+ * The generation of a scan the caller must now run, or `null`.
148
+ *
149
+ * REPORTED RATHER THAN PERFORMED, because `findCandidates` returns a promise
150
+ * and this reducer is pure. The caller runs the scan and sends the answer back
151
+ * as `candidates` or `scan-failed` carrying this number.
152
+ */
153
+ readonly scanning: number | null;
154
+ }
155
+ /**
156
+ * Apply one command. Total, pure, and never mutates what it is given.
157
+ *
158
+ * Every arm guards on the phase, and a command that does not belong to the
159
+ * current phase changes nothing — the same call `queueReducer` makes about
160
+ * answering a finished queue, and for the same reason: these arrive from a
161
+ * reader going fast and from a promise settling late, so neither is an error.
162
+ */
163
+ export declare function firstPassReducer(state: FirstPassState, command: FirstPassCommand): FirstPassOutcome;
164
+ /** The queue on screen, or `null` when none is. For a shell deciding what to draw. */
165
+ export declare function openQueueOf(state: FirstPassState): QueueState | null;
166
+ //# sourceMappingURL=firstpass.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"firstpass.d.ts","sourceRoot":"","sources":["../../src/workspace/firstpass.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACzE,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,UAAU,EAGhB,MAAM,uBAAuB,CAAC;AAE/B,+DAA+D;AAC/D,MAAM,MAAM,cAAc;AACxB,mDAAmD;AACjD;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;CAAE;AAC7B,oEAAoE;GAClE;IAAE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;CAAE;AAC/B;;;;;;;;;;;GAWG;GACD;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,GAC3B;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAA;CAAE,CAAC;AAE1D,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B;;;;;;;;;OASG;IACH,QAAQ,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,CAAC;IACzC,kEAAkE;IAClE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,eAAO,MAAM,kBAAkB,EAAE,cAI/B,CAAC;AAEH;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GACxB;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACzB;IACE,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,EAAE,SAAS,SAAS,EAAE,CAAC;CAC3C,GACD;IAAE,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACvD;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAA;CAAE,GAC1D;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;CAAE;AAC5B;;;;;;;;;;GAUG;GACD;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAE/B,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B;;;;;;;OAOG;IACH,QAAQ,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,CAAC;IACpC;;;;;;OAMG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAgCD;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,cAAc,EACrB,OAAO,EAAE,gBAAgB,GACxB,gBAAgB,CAiDlB;AAED,sFAAsF;AACtF,wBAAgB,WAAW,CAAC,KAAK,EAAE,cAAc,GAAG,UAAU,GAAG,IAAI,CAEpE"}
@@ -0,0 +1,151 @@
1
+ /**
2
+ * The first pass as a slice of the workspace's state — no DOM, no promises.
3
+ *
4
+ * `firstpass/queue.ts` is the review loop itself and knows nothing about being
5
+ * opened or closed; `firstpass/candidates.ts` declares a port whose one method
6
+ * is asynchronous by construction. Between them sits a lifecycle nobody owned:
7
+ * the surface is shut, then a scan is out, then a queue is up, and a scan can
8
+ * fail. That lifecycle is this file, in the shape `selectionReducer`,
9
+ * `scaleReducer` and `createReducer` already use here — so `host.ts` composes a
10
+ * fourth reducer rather than growing a fourth set of rules.
11
+ *
12
+ * ## The phase is a union, and `decided` sits outside it
13
+ *
14
+ * Everything the phase carries belongs to one phase only, so it is a
15
+ * discriminated union for the reason `firstpass/view.ts` records about its own
16
+ * three states: the impossible combinations stop existing.
17
+ *
18
+ * {@link FirstPassState.decided} is deliberately NOT in the union, because it
19
+ * is the one fact that has to outlive every phase. See below.
20
+ *
21
+ * ## A re-open re-scans, and `decided` is what makes that honest
22
+ *
23
+ * Closing drops the queue rather than resuming it: a queue held across a close
24
+ * carries a candidate set taken before the writes the reader just made, so it
25
+ * would go on asking about relationships that now exist.
26
+ *
27
+ * But a re-scan on its own would re-ask every question the reader already
28
+ * answered — `queue.ts` defines `reject` as "no, this is not a relationship.
29
+ * Answered, and gone", and a host's `findCandidates` is never told what was
30
+ * decided, so it cannot filter them out on the reader's behalf. Forty rejections
31
+ * would come back on the next open, which is a worse outcome than the resume
32
+ * this refuses and defeats §17e's "first pass has an end" across sessions.
33
+ *
34
+ * So the decided ids ride outside the phase and filter the next scan.
35
+ *
36
+ * **`skip` is not decided, and that is the design rather than an omission.**
37
+ * §17e's `S` is "not now", and `queue.ts` ships `skippedCandidates` precisely
38
+ * because "the deferred set is the thing a second pass is built from". A skipped
39
+ * candidate coming back in the next pass is what deferring it meant.
40
+ *
41
+ * ## Scans carry a generation, or the newest answer can lose
42
+ *
43
+ * A reader who closes during a slow scan and opens again has two scans in
44
+ * flight. Discriminating on the phase alone cannot tell their answers apart, so
45
+ * the STALE set — taken before the reader's writes — would open the queue while
46
+ * the fresh one arrived on `open` and was dropped. That is the exact failure the
47
+ * re-scan rule exists to prevent, arriving through the door the rule opened.
48
+ *
49
+ * {@link FirstPassState.scan} is a monotonic counter. Every scan is issued under
50
+ * it, every answer carries the number it was issued under, and an answer whose
51
+ * number is not the current one is dropped.
52
+ */
53
+ import { openQueue, queueReducer, } from "../firstpass/queue.js";
54
+ export const INITIAL_FIRST_PASS = Object.freeze({
55
+ phase: Object.freeze({ kind: 'closed' }),
56
+ decided: Object.freeze([]),
57
+ scan: 0,
58
+ });
59
+ function still(state) {
60
+ return { state, result: null, scanning: null };
61
+ }
62
+ /**
63
+ * The decided set after one queue transition.
64
+ *
65
+ * An added `apply` or `reject` joins it; an `undo` takes its candidate back out.
66
+ * Neither case can collide with an id carried in from an earlier pass, because
67
+ * the queue was built by filtering those ids out — so the set stays a set
68
+ * without this function ever having to ask which pass an id came from.
69
+ */
70
+ function decidedAfter(decided, before, after) {
71
+ if (after.answers.length > before.answers.length) {
72
+ const given = after.answers[after.answers.length - 1];
73
+ if (given === undefined || given.answer === 'skip')
74
+ return decided;
75
+ return [...decided, given.candidate.id];
76
+ }
77
+ if (after.answers.length < before.answers.length) {
78
+ const taken = before.answers[before.answers.length - 1];
79
+ if (taken === undefined || taken.answer === 'skip')
80
+ return decided;
81
+ return decided.filter((id) => id !== taken.candidate.id);
82
+ }
83
+ return decided;
84
+ }
85
+ /**
86
+ * Apply one command. Total, pure, and never mutates what it is given.
87
+ *
88
+ * Every arm guards on the phase, and a command that does not belong to the
89
+ * current phase changes nothing — the same call `queueReducer` makes about
90
+ * answering a finished queue, and for the same reason: these arrive from a
91
+ * reader going fast and from a promise settling late, so neither is an error.
92
+ */
93
+ export function firstPassReducer(state, command) {
94
+ switch (command.kind) {
95
+ case 'open': {
96
+ // ONLY FROM `closed`. A second press while a scan is out must not issue a
97
+ // second scan, and neither `open` nor `failed` is re-openable in place:
98
+ // both draw a way out, and the way back in is through `closed`.
99
+ if (state.phase.kind !== 'closed')
100
+ return still(state);
101
+ const scan = state.scan + 1;
102
+ return {
103
+ state: { ...state, phase: { kind: 'scanning' }, scan },
104
+ result: null,
105
+ scanning: scan,
106
+ };
107
+ }
108
+ case 'candidates': {
109
+ if (state.phase.kind !== 'scanning' || command.scan !== state.scan)
110
+ return still(state);
111
+ const decided = new Set(state.decided);
112
+ // THE DECIDED ARE DROPPED BEFORE THE QUEUE IS BUILT, not hidden after, so
113
+ // the denominator §17e bounds progress by counts what is actually being
114
+ // asked. A queue of five that silently skips four would report "0 of 5".
115
+ const fresh = command.candidates.filter((candidate) => !decided.has(candidate.id));
116
+ return still({ ...state, phase: { kind: 'open', queue: openQueue(fresh) } });
117
+ }
118
+ case 'scan-failed': {
119
+ if (state.phase.kind !== 'scanning' || command.scan !== state.scan)
120
+ return still(state);
121
+ return still({ ...state, phase: { kind: 'failed' } });
122
+ }
123
+ case 'queue': {
124
+ if (state.phase.kind !== 'open')
125
+ return still(state);
126
+ const before = state.phase.queue;
127
+ const result = queueReducer(before, command.command);
128
+ return {
129
+ state: {
130
+ ...state,
131
+ phase: { kind: 'open', queue: result.state },
132
+ decided: decidedAfter(state.decided, before, result.state),
133
+ },
134
+ result,
135
+ scanning: null,
136
+ };
137
+ }
138
+ case 'close':
139
+ // THE QUEUE GOES, `decided` STAYS. See the module header: dropping the
140
+ // queue is what keeps a re-open fresh, and keeping the decisions is what
141
+ // keeps it from re-asking. That holds for the SAME scanner; see `reset`.
142
+ return still({ ...state, phase: { kind: 'closed' } });
143
+ case 'reset':
144
+ return still({ ...state, phase: { kind: 'closed' }, decided: [] });
145
+ }
146
+ }
147
+ /** The queue on screen, or `null` when none is. For a shell deciding what to draw. */
148
+ export function openQueueOf(state) {
149
+ return state.phase.kind === 'open' ? state.phase.queue : null;
150
+ }
151
+ //# sourceMappingURL=firstpass.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"firstpass.js","sourceRoot":"","sources":["../../src/workspace/firstpass.ts"],"names":[],"mappings":"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"}