@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.
- package/LICENSE +201 -0
- package/README.md +238 -0
- package/dist/audit/findings.d.ts +225 -0
- package/dist/audit/findings.d.ts.map +1 -0
- package/dist/audit/findings.js +348 -0
- package/dist/audit/findings.js.map +1 -0
- package/dist/audit/styles.d.ts +37 -0
- package/dist/audit/styles.d.ts.map +1 -0
- package/dist/audit/styles.js +72 -0
- package/dist/audit/styles.js.map +1 -0
- package/dist/audit/surface.d.ts +155 -0
- package/dist/audit/surface.d.ts.map +1 -0
- package/dist/audit/surface.js +202 -0
- package/dist/audit/surface.js.map +1 -0
- package/dist/create/draft.d.ts +119 -0
- package/dist/create/draft.d.ts.map +1 -0
- package/dist/create/draft.js +114 -0
- package/dist/create/draft.js.map +1 -0
- package/dist/create/keys.d.ts +225 -0
- package/dist/create/keys.d.ts.map +1 -0
- package/dist/create/keys.js +228 -0
- package/dist/create/keys.js.map +1 -0
- package/dist/create/placement.d.ts +69 -0
- package/dist/create/placement.d.ts.map +1 -0
- package/dist/create/placement.js +76 -0
- package/dist/create/placement.js.map +1 -0
- package/dist/firstpass/batch.d.ts +190 -0
- package/dist/firstpass/batch.d.ts.map +1 -0
- package/dist/firstpass/batch.js +143 -0
- package/dist/firstpass/batch.js.map +1 -0
- package/dist/firstpass/candidates.d.ts +128 -0
- package/dist/firstpass/candidates.d.ts.map +1 -0
- package/dist/firstpass/candidates.js +39 -0
- package/dist/firstpass/candidates.js.map +1 -0
- package/dist/firstpass/keys.d.ts +90 -0
- package/dist/firstpass/keys.d.ts.map +1 -0
- package/dist/firstpass/keys.js +112 -0
- package/dist/firstpass/keys.js.map +1 -0
- package/dist/firstpass/queue.d.ts +183 -0
- package/dist/firstpass/queue.d.ts.map +1 -0
- package/dist/firstpass/queue.js +159 -0
- package/dist/firstpass/queue.js.map +1 -0
- package/dist/firstpass/render.d.ts +100 -0
- package/dist/firstpass/render.d.ts.map +1 -0
- package/dist/firstpass/render.js +216 -0
- package/dist/firstpass/render.js.map +1 -0
- package/dist/firstpass/styles.d.ts +36 -0
- package/dist/firstpass/styles.d.ts.map +1 -0
- package/dist/firstpass/styles.js +148 -0
- package/dist/firstpass/styles.js.map +1 -0
- package/dist/firstpass/view.d.ts +82 -0
- package/dist/firstpass/view.d.ts.map +1 -0
- package/dist/firstpass/view.js +60 -0
- package/dist/firstpass/view.js.map +1 -0
- package/dist/firstpass/words.d.ts +105 -0
- package/dist/firstpass/words.d.ts.map +1 -0
- package/dist/firstpass/words.js +28 -0
- package/dist/firstpass/words.js.map +1 -0
- package/dist/index.d.ts +150 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +147 -0
- package/dist/index.js.map +1 -0
- package/dist/overlay/grammar.d.ts +259 -0
- package/dist/overlay/grammar.d.ts.map +1 -0
- package/dist/overlay/grammar.js +223 -0
- package/dist/overlay/grammar.js.map +1 -0
- package/dist/overlay/render.d.ts +125 -0
- package/dist/overlay/render.d.ts.map +1 -0
- package/dist/overlay/render.js +439 -0
- package/dist/overlay/render.js.map +1 -0
- package/dist/overlay/styles.d.ts +41 -0
- package/dist/overlay/styles.d.ts.map +1 -0
- package/dist/overlay/styles.js +124 -0
- package/dist/overlay/styles.js.map +1 -0
- package/dist/picker/render.d.ts +80 -0
- package/dist/picker/render.d.ts.map +1 -0
- package/dist/picker/render.js +112 -0
- package/dist/picker/render.js.map +1 -0
- package/dist/picker/styles.d.ts +26 -0
- package/dist/picker/styles.d.ts.map +1 -0
- package/dist/picker/styles.js +116 -0
- package/dist/picker/styles.js.map +1 -0
- package/dist/picker/view.d.ts +135 -0
- package/dist/picker/view.d.ts.map +1 -0
- package/dist/picker/view.js +117 -0
- package/dist/picker/view.js.map +1 -0
- package/dist/picker/words.d.ts +53 -0
- package/dist/picker/words.d.ts.map +1 -0
- package/dist/picker/words.js +28 -0
- package/dist/picker/words.js.map +1 -0
- package/dist/reevaluate/render.d.ts +77 -0
- package/dist/reevaluate/render.d.ts.map +1 -0
- package/dist/reevaluate/render.js +211 -0
- package/dist/reevaluate/render.js.map +1 -0
- package/dist/reevaluate/styles.d.ts +24 -0
- package/dist/reevaluate/styles.d.ts.map +1 -0
- package/dist/reevaluate/styles.js +182 -0
- package/dist/reevaluate/styles.js.map +1 -0
- package/dist/reevaluate/view.d.ts +102 -0
- package/dist/reevaluate/view.d.ts.map +1 -0
- package/dist/reevaluate/view.js +111 -0
- package/dist/reevaluate/view.js.map +1 -0
- package/dist/reevaluate/words.d.ts +54 -0
- package/dist/reevaluate/words.d.ts.map +1 -0
- package/dist/reevaluate/words.js +29 -0
- package/dist/reevaluate/words.js.map +1 -0
- package/dist/scale/commands.d.ts +60 -0
- package/dist/scale/commands.d.ts.map +1 -0
- package/dist/scale/commands.js +49 -0
- package/dist/scale/commands.js.map +1 -0
- package/dist/scale/ladder.d.ts +141 -0
- package/dist/scale/ladder.d.ts.map +1 -0
- package/dist/scale/ladder.js +243 -0
- package/dist/scale/ladder.js.map +1 -0
- package/dist/scale/render.d.ts +85 -0
- package/dist/scale/render.d.ts.map +1 -0
- package/dist/scale/render.js +194 -0
- package/dist/scale/render.js.map +1 -0
- package/dist/scale/styles.d.ts +21 -0
- package/dist/scale/styles.d.ts.map +1 -0
- package/dist/scale/styles.js +105 -0
- package/dist/scale/styles.js.map +1 -0
- package/dist/workspace/inspector.d.ts +117 -0
- package/dist/workspace/inspector.d.ts.map +1 -0
- package/dist/workspace/inspector.js +140 -0
- package/dist/workspace/inspector.js.map +1 -0
- package/dist/workspace/rail.d.ts +122 -0
- package/dist/workspace/rail.d.ts.map +1 -0
- package/dist/workspace/rail.js +216 -0
- package/dist/workspace/rail.js.map +1 -0
- package/dist/workspace/render.d.ts +139 -0
- package/dist/workspace/render.d.ts.map +1 -0
- package/dist/workspace/render.js +366 -0
- package/dist/workspace/render.js.map +1 -0
- package/dist/workspace/selection.d.ts +92 -0
- package/dist/workspace/selection.d.ts.map +1 -0
- package/dist/workspace/selection.js +83 -0
- package/dist/workspace/selection.js.map +1 -0
- package/dist/workspace/styles.d.ts +39 -0
- package/dist/workspace/styles.d.ts.map +1 -0
- package/dist/workspace/styles.js +313 -0
- package/dist/workspace/styles.js.map +1 -0
- 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":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2DG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAmFxD,oEAAoE;AACpE,SAAS,OAAO,CACd,MAAgB,EAChB,MAAgB,EAChB,IAAc,EACd,SAAyB;IAEzB,kEAAkE;IAClE,4EAA4E;IAC5E,6EAA6E;IAC7E,8EAA8E;IAC9E,wEAAwE;IACxE,OAAO,SAAS,KAAK,aAAa;QAChC,CAAC,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE;QAClD,CAAC,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC;AACvD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,SAAS,CAAC,OAAqB;IAC7C,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC;IAErD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,YAAY,EAAE,EAAE,CAAC;IAElF,MAAM,SAAS,GAAG,oBAAoB,CAAC,IAAI,CAAC,CAAC;IAC7C,IAAI,SAAS,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;QACzC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,0BAA0B,EAAE,IAAI,EAAE,EAAE,CAAC;IAC9E,CAAC;IACD,IAAI,CAAC,SAAS,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;QAC1C,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,oBAAoB,EAAE,IAAI,EAAE,EAAE,CAAC;IACxE,CAAC;IAED,4EAA4E;IAC5E,yEAAyE;IACzE,2EAA2E;IAC3E,kDAAkD;IAClD,MAAM,QAAQ,GAAmB,SAAS,IAAI,aAAa,CAAC;IAC5D,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAY,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC;IAC7F,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC;AAClF,CAAC;AAgBD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,WAAW,CACzB,IAAe,EACf,WAAuC;IAEvC,MAAM,MAAM,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAW,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;IACjG,2EAA2E;IAC3E,8EAA8E;IAC9E,4EAA4E;IAC5E,6BAA6B;IAC7B,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAChC,CAAC,QAAQ,EAAW,EAAE,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,EAAW,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,CACrF,CAAC;IACF,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACnC,OAAO,EAAE,GAAG,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC;AAC1D,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,YAAY,CAAC,IAAc,EAAE,KAAe;IACnD,IAAI,IAAI,CAAC,EAAE,KAAK,QAAQ,IAAI,KAAK,CAAC,EAAE,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAChE,OAAO,IAAI,CAAC,IAAI,KAAK,KAAK,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,KAAK,KAAK,CAAC,IAAI,IAAI,IAAI,CAAC,EAAE,KAAK,KAAK,CAAC,EAAE,CAAC;AACtF,CAAC"}
|
|
@@ -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"}
|