@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,76 @@
1
+ /**
2
+ * Where the canvas path draws its type picker.
3
+ *
4
+ * §17b puts the picker "at the drop point", and the kit's implementation note
5
+ * fixes how that coordinate is arrived at: *"Hand-authoring these coordinates
6
+ * against remembered positions is the failure mode; compute them from layout."*
7
+ * So this takes MEASURED bounds — the container's, and the picker's own — and
8
+ * derives a position from them. It holds no constants about how big anything is.
9
+ *
10
+ * ## The one thing it adds to "at the drop point"
11
+ *
12
+ * A drop near the right or bottom edge would put a picker anchored there partly
13
+ * outside the canvas, where the reader cannot reach the options. So the picker
14
+ * FLIPS to the other side of the drop point rather than being nudged: a flip
15
+ * keeps the drop point on a corner of the picker, so the picker still visibly
16
+ * belongs to the gesture that opened it, while a nudge slides it off the point
17
+ * and reads as landing somewhere arbitrary.
18
+ *
19
+ * A flip is reported rather than only applied ({@link AxisPlacement.flipped}),
20
+ * because the side the picker opens on decides which corner its callout points
21
+ * from — and a host that had to re-derive that from the coordinates would be
22
+ * recomputing a decision already made here.
23
+ *
24
+ * ## It is geometry, so it is not in the draft
25
+ *
26
+ * The canvas is the only path with a drop point: the inspector opens its picker
27
+ * in a panel and the keyboard never has a pointer position at all. That is the
28
+ * whole of what distinguishes the three, and keeping it here — rather than as a
29
+ * field on {@link ./draft.ts CreateDraft} — is what stops "which path started
30
+ * this" from becoming state the rest of the create logic could branch on.
31
+ *
32
+ * Pure arithmetic on numbers, so it runs under the purity test like everything
33
+ * else here: it never measures anything itself. Measuring is the shell's, from
34
+ * `getBoundingClientRect` or its own layout output; both arrive as plain data.
35
+ */
36
+ /**
37
+ * Place one axis: open forward from the drop, flip if that overflows, then clamp.
38
+ *
39
+ * ONE RULE APPLIED TWICE rather than written out for x and for y. The two axes
40
+ * differ only in which measurements they read, and a second copy is a second
41
+ * place for the arithmetic to drift — which on this surface would show up as a
42
+ * picker correct horizontally and off-canvas vertically.
43
+ *
44
+ * The clamp is last and is not redundant with the flip. A picker LARGER than
45
+ * its container overflows whichever way it opens, so the flip cannot save it and
46
+ * something has to decide what it does instead: it pins to the container's start
47
+ * edge, which keeps the options that exist reachable. `Math.min` before
48
+ * `Math.max` is what produces that — with the picker oversized the inner term
49
+ * lands before the start edge, and the outer `max` pulls it back — so the order
50
+ * is load-bearing rather than stylistic.
51
+ */
52
+ function place(drop, extent, start, span) {
53
+ const end = start + span;
54
+ const flipped = drop + extent > end;
55
+ const anchored = flipped ? drop - extent : drop;
56
+ return { position: Math.max(start, Math.min(anchored, end - extent)), flipped };
57
+ }
58
+ /**
59
+ * The picker's top-left corner for a drop at `drop`, inside `container`.
60
+ *
61
+ * Every input is measured by the caller; nothing here assumes a size, a margin
62
+ * or a viewport. `container` is the canvas's own measured bounds rather than the
63
+ * window's, because the canvas is the surface the drop happened on and a picker
64
+ * escaping it would sit over the order rail beside it.
65
+ */
66
+ export function pickerPlacement(drop, picker, container) {
67
+ const horizontal = place(drop.x, picker.width, container.x, container.width);
68
+ const vertical = place(drop.y, picker.height, container.y, container.height);
69
+ return {
70
+ x: horizontal.position,
71
+ y: vertical.position,
72
+ flippedX: horizontal.flipped,
73
+ flippedY: vertical.flipped,
74
+ };
75
+ }
76
+ //# sourceMappingURL=placement.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"placement.js","sourceRoot":"","sources":["../../src/create/placement.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAkDH;;;;;;;;;;;;;;;GAeG;AACH,SAAS,KAAK,CAAC,IAAY,EAAE,MAAc,EAAE,KAAa,EAAE,IAAY;IACtE,MAAM,GAAG,GAAG,KAAK,GAAG,IAAI,CAAC;IACzB,MAAM,OAAO,GAAG,IAAI,GAAG,MAAM,GAAG,GAAG,CAAC;IACpC,MAAM,QAAQ,GAAG,OAAO,CAAC,CAAC,CAAC,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;IAChD,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,GAAG,MAAM,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC;AAClF,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAAC,IAAW,EAAE,MAAY,EAAE,SAAiB;IAC1E,MAAM,UAAU,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,EAAE,MAAM,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC,EAAE,SAAS,CAAC,KAAK,CAAC,CAAC;IAC7E,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC;IAC7E,OAAO;QACL,CAAC,EAAE,UAAU,CAAC,QAAQ;QACtB,CAAC,EAAE,QAAQ,CAAC,QAAQ;QACpB,QAAQ,EAAE,UAAU,CAAC,OAAO;QAC5B,QAAQ,EAAE,QAAQ,CAAC,OAAO;KAC3B,CAAC;AACJ,CAAC"}
@@ -0,0 +1,190 @@
1
+ /**
2
+ * Multi-select: the other bulk path, for structure the owner already knows.
3
+ *
4
+ * The queue is for structure the owner has to be SHOWN. This is for the case
5
+ * §17e names beside it — the owner already knows these six issues are one
6
+ * serialized group, and answering six questions about a fact they arrived
7
+ * holding is worse than stating it once.
8
+ *
9
+ * §17e fixes three things about it, and each is load-bearing here:
10
+ *
11
+ * "symmetric types apply to a whole selection at once; directed types need
12
+ * one pick. N issues = N writes; the confirm states the count and the batch
13
+ * is resumable if some fail."
14
+ *
15
+ * ## Directedness is read from the FORMAT, never listed here
16
+ *
17
+ * `isSymmetricEdgeField` in `@issuegraph/core` owns the split, for the reason
18
+ * `picker/view.ts` and `create/keys.ts` both record: a local list out here is
19
+ * the drifting second implementation the package family removes everywhere it
20
+ * appears, and a sixth field added to the format would leave it silently wrong
21
+ * with nothing failing to say so. A new symmetric field applies to a whole
22
+ * selection here the day core learns about it, with no edit to this file.
23
+ *
24
+ * ## Why a STAR, and why the anchor is stated rather than inferred
25
+ *
26
+ * N selected issues do not name N-1 edges on their own; some shape has to be
27
+ * chosen, and the choice is an opinion whichever way it goes. A clique is
28
+ * N(N-1)/2 writes for a fact the owner stated once — quadratic in the thing
29
+ * §17e is trying to make cheap. A chain is N-1 writes but imposes an ORDER the
30
+ * selection never carried, and for a symmetric field an invented order is
31
+ * exactly the wrong kind of invention.
32
+ *
33
+ * A star is N-1 writes and imposes one fact: which issue is the anchor. So that
34
+ * fact is TAKEN rather than guessed — {@link BatchRequest.anchor} — and it is
35
+ * the owner's pick, which is what makes the shape honest instead of arbitrary.
36
+ * For a symmetric kind the star still describes the whole component core means
37
+ * by these fields: every member is connected, which is the property, and the
38
+ * anchor is not privileged by the format afterwards.
39
+ *
40
+ * ## The "one pick" a directed type needs is DIRECTION, not the anchor
41
+ *
42
+ * Both kinds take an anchor, so that is not the thing that distinguishes them.
43
+ * What a directed kind additionally needs is which way the edges point — an
44
+ * anchor that BLOCKS the others and one that is BLOCKED BY them are opposite
45
+ * facts about the same six issues, and nothing about the selection implies
46
+ * either. So {@link BatchRequest.direction} is required for a directed kind and
47
+ * REFUSED for a symmetric one, rather than accepted and ignored: a host that
48
+ * sends one for `together-with` has misunderstood something, and silently
49
+ * dropping it would let a UI grow a direction control that does nothing.
50
+ *
51
+ * ## A refusal is a value, not a throw
52
+ *
53
+ * The same call `structuralRefusal` makes in the store, and for the same
54
+ * reason: these are things a person can do, so they get a surface to be drawn
55
+ * on rather than an exception to be caught. Nothing here adjudicates whether
56
+ * the resulting EDGES are legal — self-edges, duplicates and unknown issues are
57
+ * `structuralRefusal`'s, and a second validity rule out here is a second place
58
+ * for the answer to drift. This module refuses only what it alone can see:
59
+ * whether the REQUEST names a batch at all.
60
+ */
61
+ import type { EdgeKind, IssueRef, Proposal } from '@issuegraph/store';
62
+ /**
63
+ * Which way a directed batch points, relative to the anchor.
64
+ *
65
+ * Named for the anchor rather than for a field's own phrasing (`blocks` /
66
+ * `blocked-by`) because it has to read the same for all three directed fields,
67
+ * and only the anchor is common to them.
68
+ */
69
+ export type BatchDirection = 'from-anchor' | 'to-anchor';
70
+ /** What the owner selected and asked for. */
71
+ export interface BatchRequest {
72
+ /**
73
+ * The anchor. Every edge in the batch touches it.
74
+ *
75
+ * It is not required to be a member of {@link members}, and deliberately so:
76
+ * "these five all block the release issue" is the ordinary shape, and the
77
+ * release issue is not one of the five.
78
+ */
79
+ readonly anchor: IssueRef;
80
+ /** The rest of the selection. Each one gets an edge to or from the anchor. */
81
+ readonly members: readonly IssueRef[];
82
+ readonly kind: EdgeKind;
83
+ /**
84
+ * Required for a directed kind, and refused for a symmetric one. See the
85
+ * module header — this is the "one pick", and it is not the anchor.
86
+ */
87
+ readonly direction?: BatchDirection | undefined;
88
+ }
89
+ /**
90
+ * Why a request does not name a batch.
91
+ *
92
+ * A closed union rather than a message, so a host words it — the same reason
93
+ * `reevaluate/words.ts` gives — and so a test asserts the reason rather than
94
+ * matching prose.
95
+ */
96
+ export type BatchRefusal =
97
+ /** Fewer than one member: there is no edge to write. */
98
+ {
99
+ readonly reason: 'no-members';
100
+ }
101
+ /** A directed kind arrived without its direction pick. */
102
+ | {
103
+ readonly reason: 'direction-required';
104
+ readonly kind: EdgeKind;
105
+ }
106
+ /** A symmetric kind arrived with a direction, which means something is wrong. */
107
+ | {
108
+ readonly reason: 'direction-not-applicable';
109
+ readonly kind: EdgeKind;
110
+ };
111
+ /**
112
+ * THERE IS NO `unknown-kind` REFUSAL, and its absence is deliberate.
113
+ *
114
+ * An earlier revision carried one, guarding `isSymmetricEdgeField` — which
115
+ * takes an `EdgeField` rather than a bare string precisely so it is never asked
116
+ * about a typo. But {@link BatchRequest.kind} is already `EdgeKind`, the closed
117
+ * union, so the case the guard answered cannot be constructed: the compiler
118
+ * rejected the very test written to exercise it.
119
+ *
120
+ * That is the shape `create/draft.ts` records a mutation control catching in
121
+ * this package once already — a branch asserting its own necessity that no test
122
+ * can reach. Deleting it is also what the family's validity rule says to do
123
+ * anyway: `structuralRefusal` owns whether an edge is legal, and a second
124
+ * validity rule out here is a second place for the answer to drift.
125
+ */
126
+ /**
127
+ * A batch ready to confirm.
128
+ *
129
+ * `count` is `proposals.length` and exists so the confirm cannot state a
130
+ * different number from the one it is about to write. §17e requires the confirm
131
+ * to state the count; deriving it at the render site would be the second place
132
+ * that number lives.
133
+ */
134
+ export interface BatchPlan {
135
+ readonly proposals: readonly Proposal[];
136
+ /** What the confirm states. Always `proposals.length`. */
137
+ readonly count: number;
138
+ readonly kind: EdgeKind;
139
+ readonly anchor: IssueRef;
140
+ }
141
+ export type BatchOutcome = {
142
+ readonly ok: true;
143
+ readonly plan: BatchPlan;
144
+ } | {
145
+ readonly ok: false;
146
+ readonly refusal: BatchRefusal;
147
+ };
148
+ /**
149
+ * Turn a selection into the writes it stands for, or say why it is not a batch.
150
+ *
151
+ * Total and pure. Every refusal is checked before any proposal is built, so a
152
+ * refused request produces no partial plan for a caller to mistake for one.
153
+ */
154
+ export declare function planBatch(request: BatchRequest): BatchOutcome;
155
+ /**
156
+ * How one proposal of a batch settled.
157
+ *
158
+ * `landed` and `failed` only. A proposal still IN FLIGHT is deliberately not a
159
+ * third value: resuming while writes are outstanding would re-send them, which
160
+ * is the duplicate-write failure the store's closed operation set exists to
161
+ * prevent. A host resumes once the batch has settled, and a proposal it has no
162
+ * outcome for is treated as still owed — see {@link resumeBatch}.
163
+ */
164
+ export interface BatchSettlement {
165
+ readonly proposal: Proposal;
166
+ readonly settled: 'landed' | 'failed';
167
+ }
168
+ /**
169
+ * What a partially-failed batch still owes.
170
+ *
171
+ * §17e: "the batch is resumable if some fail." Resumability is a real
172
+ * requirement rather than a nicety — a bulk write at §17f's sizes crosses a
173
+ * network N times, and a batch that has to be redone from the start after one
174
+ * timeout is a batch an owner will not retry.
175
+ *
176
+ * Returns a plan over the proposals that did NOT land, or `null` when nothing
177
+ * is owed. `null` rather than an empty plan: "the batch is finished" is a
178
+ * different state from "here is a batch of nothing to confirm", and an empty
179
+ * plan would render a confirm stating a count of zero.
180
+ *
181
+ * ## Unmentioned proposals are still owed, and that is the fail-safe direction
182
+ *
183
+ * A settlement list that is missing entries — a host that lost track, a session
184
+ * that died mid-batch — leaves those proposals in the resumed plan. Re-offering
185
+ * a write that actually landed costs the owner one refusal from the store's own
186
+ * duplicate rule; dropping a write that never landed loses a relationship
187
+ * silently, and nothing downstream would ever surface it.
188
+ */
189
+ export declare function resumeBatch(plan: BatchPlan, settlements: readonly BatchSettlement[]): BatchPlan | null;
190
+ //# sourceMappingURL=batch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"batch.d.ts","sourceRoot":"","sources":["../../src/firstpass/batch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2DG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAEtE;;;;;;GAMG;AACH,MAAM,MAAM,cAAc,GAAG,aAAa,GAAG,WAAW,CAAC;AAEzD,6CAA6C;AAC7C,MAAM,WAAW,YAAY;IAC3B;;;;;;OAMG;IACH,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC;IAC1B,8EAA8E;IAC9E,QAAQ,CAAC,OAAO,EAAE,SAAS,QAAQ,EAAE,CAAC;IACtC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB;;;OAGG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,cAAc,GAAG,SAAS,CAAC;CACjD;AAED;;;;;;GAMG;AACH,MAAM,MAAM,YAAY;AACtB,wDAAwD;AACtD;IAAE,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAA;CAAE;AACnC,0DAA0D;GACxD;IAAE,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;CAAE;AACpE,iFAAiF;GAC/E;IAAE,QAAQ,CAAC,MAAM,EAAE,0BAA0B,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,CAAC;AAE7E;;;;;;;;;;;;;;GAcG;AAEH;;;;;;;GAOG;AACH,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,SAAS,EAAE,SAAS,QAAQ,EAAE,CAAC;IACxC,0DAA0D;IAC1D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC;CAC3B;AAED,MAAM,MAAM,YAAY,GACpB;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;CAAE,GAC/C;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAA;CAAE,CAAC;AAmB3D;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,YAAY,GAAG,YAAY,CAoB7D;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,QAAQ,GAAG,QAAQ,CAAC;CACvC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,SAAS,EACf,WAAW,EAAE,SAAS,eAAe,EAAE,GACtC,SAAS,GAAG,IAAI,CAWlB"}
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Multi-select: the other bulk path, for structure the owner already knows.
3
+ *
4
+ * The queue is for structure the owner has to be SHOWN. This is for the case
5
+ * §17e names beside it — the owner already knows these six issues are one
6
+ * serialized group, and answering six questions about a fact they arrived
7
+ * holding is worse than stating it once.
8
+ *
9
+ * §17e fixes three things about it, and each is load-bearing here:
10
+ *
11
+ * "symmetric types apply to a whole selection at once; directed types need
12
+ * one pick. N issues = N writes; the confirm states the count and the batch
13
+ * is resumable if some fail."
14
+ *
15
+ * ## Directedness is read from the FORMAT, never listed here
16
+ *
17
+ * `isSymmetricEdgeField` in `@issuegraph/core` owns the split, for the reason
18
+ * `picker/view.ts` and `create/keys.ts` both record: a local list out here is
19
+ * the drifting second implementation the package family removes everywhere it
20
+ * appears, and a sixth field added to the format would leave it silently wrong
21
+ * with nothing failing to say so. A new symmetric field applies to a whole
22
+ * selection here the day core learns about it, with no edit to this file.
23
+ *
24
+ * ## Why a STAR, and why the anchor is stated rather than inferred
25
+ *
26
+ * N selected issues do not name N-1 edges on their own; some shape has to be
27
+ * chosen, and the choice is an opinion whichever way it goes. A clique is
28
+ * N(N-1)/2 writes for a fact the owner stated once — quadratic in the thing
29
+ * §17e is trying to make cheap. A chain is N-1 writes but imposes an ORDER the
30
+ * selection never carried, and for a symmetric field an invented order is
31
+ * exactly the wrong kind of invention.
32
+ *
33
+ * A star is N-1 writes and imposes one fact: which issue is the anchor. So that
34
+ * fact is TAKEN rather than guessed — {@link BatchRequest.anchor} — and it is
35
+ * the owner's pick, which is what makes the shape honest instead of arbitrary.
36
+ * For a symmetric kind the star still describes the whole component core means
37
+ * by these fields: every member is connected, which is the property, and the
38
+ * anchor is not privileged by the format afterwards.
39
+ *
40
+ * ## The "one pick" a directed type needs is DIRECTION, not the anchor
41
+ *
42
+ * Both kinds take an anchor, so that is not the thing that distinguishes them.
43
+ * What a directed kind additionally needs is which way the edges point — an
44
+ * anchor that BLOCKS the others and one that is BLOCKED BY them are opposite
45
+ * facts about the same six issues, and nothing about the selection implies
46
+ * either. So {@link BatchRequest.direction} is required for a directed kind and
47
+ * REFUSED for a symmetric one, rather than accepted and ignored: a host that
48
+ * sends one for `together-with` has misunderstood something, and silently
49
+ * dropping it would let a UI grow a direction control that does nothing.
50
+ *
51
+ * ## A refusal is a value, not a throw
52
+ *
53
+ * The same call `structuralRefusal` makes in the store, and for the same
54
+ * reason: these are things a person can do, so they get a surface to be drawn
55
+ * on rather than an exception to be caught. Nothing here adjudicates whether
56
+ * the resulting EDGES are legal — self-edges, duplicates and unknown issues are
57
+ * `structuralRefusal`'s, and a second validity rule out here is a second place
58
+ * for the answer to drift. This module refuses only what it alone can see:
59
+ * whether the REQUEST names a batch at all.
60
+ */
61
+ import { isSymmetricEdgeField } from '@issuegraph/core';
62
+ /** One edge of the star, in the direction the request asked for. */
63
+ function edgeFor(anchor, member, kind, direction) {
64
+ // A SYMMETRIC KIND STILL LANDS AS AN ORDERED PAIR, which is not a
65
+ // contradiction: `create/draft.ts` records why the store keeps the pair for
66
+ // symmetric kinds too — an editor has to know which issue carries the field.
67
+ // `from-anchor` is the arm a symmetric request takes, so the anchor holds it,
68
+ // which is the arrangement an owner who picked the anchor would expect.
69
+ return direction === 'from-anchor'
70
+ ? { op: 'create', kind, from: anchor, to: member }
71
+ : { op: 'create', kind, from: member, to: anchor };
72
+ }
73
+ /**
74
+ * Turn a selection into the writes it stands for, or say why it is not a batch.
75
+ *
76
+ * Total and pure. Every refusal is checked before any proposal is built, so a
77
+ * refused request produces no partial plan for a caller to mistake for one.
78
+ */
79
+ export function planBatch(request) {
80
+ const { anchor, members, kind, direction } = request;
81
+ if (members.length === 0)
82
+ return { ok: false, refusal: { reason: 'no-members' } };
83
+ const symmetric = isSymmetricEdgeField(kind);
84
+ if (symmetric && direction !== undefined) {
85
+ return { ok: false, refusal: { reason: 'direction-not-applicable', kind } };
86
+ }
87
+ if (!symmetric && direction === undefined) {
88
+ return { ok: false, refusal: { reason: 'direction-required', kind } };
89
+ }
90
+ // `direction ?? 'from-anchor'` rather than a non-null assertion: the checks
91
+ // above make the fallback unreachable for a directed kind, and it is the
92
+ // arm a symmetric kind takes anyway — so the expression is total without a
93
+ // cast, which the strict-TypeScript rule forbids.
94
+ const pointing = direction ?? 'from-anchor';
95
+ const proposals = members.map((member) => edgeFor(anchor, member, kind, pointing));
96
+ return { ok: true, plan: { proposals, count: proposals.length, kind, anchor } };
97
+ }
98
+ /**
99
+ * What a partially-failed batch still owes.
100
+ *
101
+ * §17e: "the batch is resumable if some fail." Resumability is a real
102
+ * requirement rather than a nicety — a bulk write at §17f's sizes crosses a
103
+ * network N times, and a batch that has to be redone from the start after one
104
+ * timeout is a batch an owner will not retry.
105
+ *
106
+ * Returns a plan over the proposals that did NOT land, or `null` when nothing
107
+ * is owed. `null` rather than an empty plan: "the batch is finished" is a
108
+ * different state from "here is a batch of nothing to confirm", and an empty
109
+ * plan would render a confirm stating a count of zero.
110
+ *
111
+ * ## Unmentioned proposals are still owed, and that is the fail-safe direction
112
+ *
113
+ * A settlement list that is missing entries — a host that lost track, a session
114
+ * that died mid-batch — leaves those proposals in the resumed plan. Re-offering
115
+ * a write that actually landed costs the owner one refusal from the store's own
116
+ * duplicate rule; dropping a write that never landed loses a relationship
117
+ * silently, and nothing downstream would ever surface it.
118
+ */
119
+ export function resumeBatch(plan, settlements) {
120
+ const landed = settlements.filter((s) => s.settled === 'landed').map((s) => s.proposal);
121
+ // COMPARED STRUCTURALLY, not by identity. A settlement comes back from the
122
+ // host — across a dispatch, possibly across a serialization boundary — so the
123
+ // object is not the one this module handed out, and `includes` would report
124
+ // every write as still owed.
125
+ const owed = plan.proposals.filter((proposal) => !landed.some((done) => sameProposal(proposal, done)));
126
+ if (owed.length === 0)
127
+ return null;
128
+ return { ...plan, proposals: owed, count: owed.length };
129
+ }
130
+ /**
131
+ * Whether two proposals stand for the same write.
132
+ *
133
+ * ONLY `create` IS COMPARED IN FULL, because only `create` can be in a batch —
134
+ * {@link planBatch} builds nothing else. The other three arms are answered by
135
+ * the `op` mismatch above them, and an exhaustive comparison of operations this
136
+ * module cannot produce would be untestable code asserting its own necessity.
137
+ */
138
+ function sameProposal(left, right) {
139
+ if (left.op !== 'create' || right.op !== 'create')
140
+ return false;
141
+ return left.kind === right.kind && left.from === right.from && left.to === right.to;
142
+ }
143
+ //# sourceMappingURL=batch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"batch.js","sourceRoot":"","sources":["../../src/firstpass/batch.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,128 @@
1
+ /**
2
+ * What a first-pass candidate IS, and the port it arrives through.
3
+ *
4
+ * §17e describes the adoption moment: a mature backlog arrives with 312 issues
5
+ * and zero relationships. "A blank canvas is the wrong tool: the owner doesn't
6
+ * want to draw 60 edges, they want to answer 60 questions." So the first pass
7
+ * is a review QUEUE — one proposed relationship at a time, with the evidence
8
+ * that suggested it, answerable in two seconds.
9
+ *
10
+ * ## The package ships no scanner, and that is a boundary rather than a gap
11
+ *
12
+ * The evidence §17e names — "both bodies reference `src/auth/session.ts`",
13
+ * "#455 is linked from a comment on #512" — is not derivable from a
14
+ * `GraphDocument`. It needs issue BODIES, COMMENTS and the host's own index,
15
+ * which is the tracker's world and precisely what `@issuegraph/store` refuses
16
+ * to fetch. A heuristic living in here would be an un-themeable product opinion
17
+ * shipped inside a published package: every host would get one vendor's idea of
18
+ * what "looks like a duplicate" means, with no way to replace it and no way to
19
+ * tune it against their own backlog.
20
+ *
21
+ * So candidates come IN, with their evidence already attached, exactly as
22
+ * `StoreConfig.derive` takes an order rather than inventing one. The port below
23
+ * is the declared shape of that crossing; nothing here implements it.
24
+ *
25
+ * ## Evidence carries the host's WORDS, not ours
26
+ *
27
+ * The same rule `reevaluate/words.ts` records, arriving through a different
28
+ * door. A sentence explaining why two issues might be duplicates is a claim
29
+ * about the host's tracker in the host's language, so this package cannot write
30
+ * one — and unlike the re-evaluate summary there is not even a fixed vocabulary
31
+ * to word, because the SET of reasons a host can offer is the host's to grow.
32
+ *
33
+ * What this package supplies instead is the {@link CandidateEvidence.token}: a
34
+ * machine-readable handle a host can style and a test can assert on, carried
35
+ * beside the prose rather than derived from it. That keeps the rendered surface
36
+ * testable without this package ever reading, parsing or authoring the text.
37
+ */
38
+ import type { EdgeKind, IssueRef } from '@issuegraph/store';
39
+ /**
40
+ * A candidate's identity, stable across a queue's life.
41
+ *
42
+ * OPAQUE TO THIS PACKAGE, and deliberately not derived from the relationship it
43
+ * proposes. Two candidates can legitimately propose the same pair with the same
44
+ * kind on different evidence — a host running two detectors will produce
45
+ * exactly that — and an identity computed from `from`/`to`/`kind` would silently
46
+ * collapse them, losing one detector's evidence and one of the owner's answers.
47
+ *
48
+ * The host mints it because the host knows what it means for two of its own
49
+ * findings to be the same finding.
50
+ */
51
+ export type CandidateId = string;
52
+ /**
53
+ * One reason a candidate was proposed.
54
+ *
55
+ * `text` is the host's sentence and this package renders it verbatim: it never
56
+ * reads it, never parses it, and never writes one of its own.
57
+ */
58
+ export interface CandidateEvidence {
59
+ /**
60
+ * A machine-readable handle for this KIND of reason — `shared-path`,
61
+ * `linked-from-comment`, whatever the host's detectors are called.
62
+ *
63
+ * It reaches the markup as a data attribute, so a host styles one class of
64
+ * evidence differently and a test asserts a candidate carried the reason it
65
+ * should have, both without matching on prose. This package assigns it no
66
+ * meaning and holds no list of legal values — a closed set here would be the
67
+ * scanner opinion the module header refuses, one layer down.
68
+ */
69
+ readonly token: string;
70
+ /** The host's own words. Rendered as given. */
71
+ readonly text: string;
72
+ }
73
+ /**
74
+ * One proposed relationship, and why.
75
+ *
76
+ * It is shaped as the `create` proposal it would become — `kind`, `from`, `to`
77
+ * — rather than as a looser "these two look related". A candidate that cannot
78
+ * name the relationship it is proposing is not answerable in two seconds, which
79
+ * is the whole design target; the owner would have to open the picker for every
80
+ * one, and the queue would be the canvas again with extra steps.
81
+ */
82
+ export interface Candidate {
83
+ readonly id: CandidateId;
84
+ /** Which relationship is being proposed. */
85
+ readonly kind: EdgeKind;
86
+ /** The issue the relationship would be created FROM. */
87
+ readonly from: IssueRef;
88
+ /** The issue it would be created TO. */
89
+ readonly to: IssueRef;
90
+ /**
91
+ * Why the host thinks so. At least one, by construction of a useful queue —
92
+ * but not enforced here, because a host with a detector that genuinely cannot
93
+ * explain itself should be able to say so by sending none rather than by
94
+ * inventing a sentence.
95
+ */
96
+ readonly evidence: readonly CandidateEvidence[];
97
+ }
98
+ /**
99
+ * The port. A host implements it; this package calls it and ships no default.
100
+ *
101
+ * ASYNC because finding candidates means reading the tracker — bodies,
102
+ * comments, an index — and every honest implementation of that is a network
103
+ * round trip. Modelling it as synchronous would force hosts to pre-compute the
104
+ * whole set before the surface could open, which at §17f's sizes is the wrong
105
+ * shape.
106
+ *
107
+ * NO DEFAULT IMPLEMENTATION, on the same reasoning `StoreConfig.derive` carries:
108
+ * a default would be a second implementation of the thing the host was asked to
109
+ * supply, and the first one anybody hit a limitation with would be impossible to
110
+ * replace because it was already load-bearing.
111
+ *
112
+ * The queue itself takes a resolved `readonly Candidate[]` rather than this
113
+ * port, so the reducer stays pure and synchronously testable. The port is what
114
+ * the SHELL holds; the queue is what the shell drives with the answer.
115
+ */
116
+ export interface CandidateSource {
117
+ /**
118
+ * Every candidate the host can find for this document, with its evidence.
119
+ *
120
+ * "Every" is the host's to bound. §17e fixes what this package does with the
121
+ * count rather than what the count should be: progress is measured against
122
+ * candidates FOUND, so a host that returns its best 40 gets a queue with an
123
+ * end at 40, and one that returns 600 gets a queue with an end at 600.
124
+ * Neither is a truncation this package can detect or should report.
125
+ */
126
+ readonly findCandidates: () => Promise<readonly Candidate[]>;
127
+ }
128
+ //# sourceMappingURL=candidates.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"candidates.d.ts","sourceRoot":"","sources":["../../src/firstpass/candidates.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAE5D;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC;AAEjC;;;;;GAKG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;;;;;;;OASG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,+CAA+C;IAC/C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,EAAE,EAAE,WAAW,CAAC;IACzB,4CAA4C;IAC5C,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,wDAAwD;IACxD,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,wCAAwC;IACxC,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC;IACtB;;;;;OAKG;IACH,QAAQ,CAAC,QAAQ,EAAE,SAAS,iBAAiB,EAAE,CAAC;CACjD;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,WAAW,eAAe;IAC9B;;;;;;;;OAQG;IACH,QAAQ,CAAC,cAAc,EAAE,MAAM,OAAO,CAAC,SAAS,SAAS,EAAE,CAAC,CAAC;CAC9D"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * What a first-pass candidate IS, and the port it arrives through.
3
+ *
4
+ * §17e describes the adoption moment: a mature backlog arrives with 312 issues
5
+ * and zero relationships. "A blank canvas is the wrong tool: the owner doesn't
6
+ * want to draw 60 edges, they want to answer 60 questions." So the first pass
7
+ * is a review QUEUE — one proposed relationship at a time, with the evidence
8
+ * that suggested it, answerable in two seconds.
9
+ *
10
+ * ## The package ships no scanner, and that is a boundary rather than a gap
11
+ *
12
+ * The evidence §17e names — "both bodies reference `src/auth/session.ts`",
13
+ * "#455 is linked from a comment on #512" — is not derivable from a
14
+ * `GraphDocument`. It needs issue BODIES, COMMENTS and the host's own index,
15
+ * which is the tracker's world and precisely what `@issuegraph/store` refuses
16
+ * to fetch. A heuristic living in here would be an un-themeable product opinion
17
+ * shipped inside a published package: every host would get one vendor's idea of
18
+ * what "looks like a duplicate" means, with no way to replace it and no way to
19
+ * tune it against their own backlog.
20
+ *
21
+ * So candidates come IN, with their evidence already attached, exactly as
22
+ * `StoreConfig.derive` takes an order rather than inventing one. The port below
23
+ * is the declared shape of that crossing; nothing here implements it.
24
+ *
25
+ * ## Evidence carries the host's WORDS, not ours
26
+ *
27
+ * The same rule `reevaluate/words.ts` records, arriving through a different
28
+ * door. A sentence explaining why two issues might be duplicates is a claim
29
+ * about the host's tracker in the host's language, so this package cannot write
30
+ * one — and unlike the re-evaluate summary there is not even a fixed vocabulary
31
+ * to word, because the SET of reasons a host can offer is the host's to grow.
32
+ *
33
+ * What this package supplies instead is the {@link CandidateEvidence.token}: a
34
+ * machine-readable handle a host can style and a test can assert on, carried
35
+ * beside the prose rather than derived from it. That keeps the rendered surface
36
+ * testable without this package ever reading, parsing or authoring the text.
37
+ */
38
+ export {};
39
+ //# sourceMappingURL=candidates.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"candidates.js","sourceRoot":"","sources":["../../src/firstpass/candidates.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG"}