pi-minimal 0.4.0 → 0.6.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/CHANGELOG.md +81 -1
- package/CONTEXT.md +107 -0
- package/README.md +9 -6
- package/dist/background-overlay.js +95 -0
- package/dist/background.js +206 -0
- package/dist/config.js +10 -1
- package/dist/cwd.js +2 -2
- package/dist/edit-diff.js +74 -0
- package/dist/footer-contribution-events.js +5 -3
- package/dist/footer-contributions.js +15 -13
- package/dist/footer.js +15 -3
- package/dist/git-state.js +31 -11
- package/dist/herdr.js +78 -0
- package/dist/index.js +79 -202
- package/dist/listeners.js +23 -0
- package/dist/prs-command.js +76 -0
- package/dist/prs.js +270 -0
- package/dist/reclaim.js +0 -3
- package/dist/rex.js +289 -0
- package/dist/session.js +168 -0
- package/dist/siblings.js +14 -0
- package/dist/tools.js +213 -374
- package/dist/tracker.js +2 -2
- package/dist/working-status.js +2 -1
- package/doc/archive/README.md +3 -0
- package/doc/footer-contributions.md +7 -7
- package/package.json +5 -3
- package/release-notes.md +33 -12
- package/src/background-overlay.ts +103 -0
- package/src/background.ts +250 -0
- package/src/config.ts +10 -1
- package/src/cwd.ts +5 -4
- package/src/edit-diff.ts +73 -0
- package/src/footer-contribution-events.ts +7 -4
- package/src/footer-contributions.ts +15 -13
- package/src/footer.ts +23 -3
- package/src/git-state.ts +29 -10
- package/src/herdr.ts +78 -5
- package/src/index.ts +81 -195
- package/src/listeners.ts +22 -0
- package/src/prs-command.ts +81 -0
- package/src/prs.ts +348 -0
- package/src/reclaim.ts +0 -4
- package/src/rex.ts +304 -0
- package/src/session.ts +185 -0
- package/src/siblings.ts +38 -0
- package/src/tools.ts +261 -389
- package/src/tracker.ts +11 -5
- package/src/working-status.ts +2 -1
- package/test/background.test.ts +198 -0
- package/test/config.test.ts +24 -11
- package/test/footer-composition.test.ts +61 -0
- package/test/footer-contribution-events.test.ts +15 -0
- package/test/git-state.test.ts +35 -0
- package/test/index.test.ts +202 -4
- package/test/prs.test.ts +272 -0
- package/test/rex.test.ts +249 -0
- package/test/tools.test.ts +36 -3
- package/test/working-status.test.ts +53 -0
- /package/doc/{plans/2026-09-22-pi-minimal-package.md → archive/2026-09-22-plan-pi-minimal-package.md} +0 -0
- /package/doc/{specs/2026-09-22-pi-minimal-package.md → archive/2026-09-22-spec-pi-minimal-package.md} +0 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { diffWords } from "diff";
|
|
2
|
+
/** The edit tool's `details.diff`, when present and non-empty. */
|
|
3
|
+
export function editDiffText(result) {
|
|
4
|
+
const details = result?.details;
|
|
5
|
+
return typeof details?.diff === "string" && details.diff.trim() ? details.diff : undefined;
|
|
6
|
+
}
|
|
7
|
+
/** pi's edit diff lines: "+123 content", "-123 content", " 123 context"; anything else (e.g. "...") is a separator. */
|
|
8
|
+
function parseDiffLine(line) {
|
|
9
|
+
const match = /^([+\- ])(\s*\d*) (.*)$/.exec(line);
|
|
10
|
+
if (!match)
|
|
11
|
+
return undefined;
|
|
12
|
+
return { prefix: match[1], lineNum: match[2], content: match[3].replace(/\t/g, " ") };
|
|
13
|
+
}
|
|
14
|
+
/** Word-level diff of a 1:1 replacement; changed tokens are inverted, leading indentation is left alone, as in pi. */
|
|
15
|
+
function highlightPair(removed, added, theme) {
|
|
16
|
+
let out = { removed: "", added: "" };
|
|
17
|
+
let firstRemoved = true;
|
|
18
|
+
let firstAdded = true;
|
|
19
|
+
for (const part of diffWords(removed, added)) {
|
|
20
|
+
if (part.removed || part.added) {
|
|
21
|
+
const key = part.removed ? "removed" : "added";
|
|
22
|
+
let value = part.value;
|
|
23
|
+
if (part.removed ? firstRemoved : firstAdded) {
|
|
24
|
+
const ws = /^(\s*)/.exec(value)?.[1] ?? "";
|
|
25
|
+
value = value.slice(ws.length);
|
|
26
|
+
out = { ...out, [key]: out[key] + ws };
|
|
27
|
+
if (part.removed)
|
|
28
|
+
firstRemoved = false;
|
|
29
|
+
else
|
|
30
|
+
firstAdded = false;
|
|
31
|
+
}
|
|
32
|
+
if (value)
|
|
33
|
+
out = { ...out, [key]: out[key] + theme.inverse(value) };
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
out = { removed: out.removed + part.value, added: out.added + part.value };
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return out;
|
|
40
|
+
}
|
|
41
|
+
/** Colours pi's edit diff like pi does: theme diff colours per line, inverse on changed tokens of single-line replacements. */
|
|
42
|
+
export function colorDiffLines(lines, theme) {
|
|
43
|
+
const out = [];
|
|
44
|
+
for (let i = 0; i < lines.length;) {
|
|
45
|
+
const parsed = parseDiffLine(lines[i]);
|
|
46
|
+
if (!parsed) {
|
|
47
|
+
out.push(theme.fg("toolDiffContext", lines[i]));
|
|
48
|
+
i++;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (parsed.prefix === " ") {
|
|
52
|
+
out.push(theme.fg("toolDiffContext", ` ${parsed.lineNum} ${parsed.content}`));
|
|
53
|
+
i++;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
const removed = [];
|
|
57
|
+
const added = [];
|
|
58
|
+
for (let p = parseDiffLine(lines[i] ?? ""); p?.prefix === "-"; p = parseDiffLine(lines[++i] ?? ""))
|
|
59
|
+
removed.push(p);
|
|
60
|
+
for (let p = parseDiffLine(lines[i] ?? ""); p?.prefix === "+"; p = parseDiffLine(lines[++i] ?? ""))
|
|
61
|
+
added.push(p);
|
|
62
|
+
if (removed.length === 1 && added.length === 1) {
|
|
63
|
+
const pair = highlightPair(removed[0].content, added[0].content, theme);
|
|
64
|
+
out.push(theme.fg("toolDiffRemoved", `-${removed[0].lineNum} ${pair.removed}`));
|
|
65
|
+
out.push(theme.fg("toolDiffAdded", `+${added[0].lineNum} ${pair.added}`));
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
for (const line of removed)
|
|
69
|
+
out.push(theme.fg("toolDiffRemoved", `-${line.lineNum} ${line.content}`));
|
|
70
|
+
for (const line of added)
|
|
71
|
+
out.push(theme.fg("toolDiffAdded", `+${line.lineNum} ${line.content}`));
|
|
72
|
+
}
|
|
73
|
+
return out;
|
|
74
|
+
}
|
|
@@ -3,6 +3,8 @@ export const FOOTER_EVENTS = {
|
|
|
3
3
|
ready: "pi-minimal:footer:v1:ready",
|
|
4
4
|
unavailable: "pi-minimal:footer:v1:unavailable",
|
|
5
5
|
};
|
|
6
|
+
/** Slot names on the wire. `summary` is the legacy name of the plan slot and stays accepted. */
|
|
7
|
+
export const FOOTER_SLOTS = ["activity", "plan", "summary"];
|
|
6
8
|
/** Bind once per session, not once per footer mount. Trusted in-process protocol. */
|
|
7
9
|
export function bindFooterContributionEvents(events, registry, sessionId, mode) {
|
|
8
10
|
registry.reset(mode === "tui" ? sessionId : null);
|
|
@@ -13,15 +15,15 @@ export function bindFooterContributionEvents(events, registry, sessionId, mode)
|
|
|
13
15
|
// A different host may have answered first. Never overwrite its result.
|
|
14
16
|
if (request.result !== undefined)
|
|
15
17
|
return;
|
|
16
|
-
if (request.version !== 1 || (request.slot
|
|
18
|
+
if (request.version !== 1 || !FOOTER_SLOTS.includes(request.slot))
|
|
17
19
|
request.result = { ok: false, code: "invalid" };
|
|
18
20
|
else if (mode !== "tui")
|
|
19
21
|
request.result = { ok: false, code: "not-tui" };
|
|
20
22
|
else
|
|
21
|
-
request.result = request.slot === "
|
|
23
|
+
request.result = request.slot === "activity" ? registry.register(request) : registry.registerPlan(request);
|
|
22
24
|
});
|
|
23
25
|
if (mode === "tui")
|
|
24
|
-
events.emit(FOOTER_EVENTS.ready, { version: 1, sessionId, slots: [
|
|
26
|
+
events.emit(FOOTER_EVENTS.ready, { version: 1, sessionId, slots: [...FOOTER_SLOTS] });
|
|
25
27
|
let disposed = false;
|
|
26
28
|
return () => {
|
|
27
29
|
if (disposed)
|
|
@@ -60,7 +60,8 @@ function normalizeSnapshot(input) {
|
|
|
60
60
|
}
|
|
61
61
|
function createFooterSlot(normalize, initialSessionId) {
|
|
62
62
|
let sessionId = initialSessionId;
|
|
63
|
-
|
|
63
|
+
// The registrant's `owner` string is a display identity; this symbol is the actual authority to publish.
|
|
64
|
+
let ownerToken;
|
|
64
65
|
let snapshot;
|
|
65
66
|
const observers = new Set();
|
|
66
67
|
const notify = () => {
|
|
@@ -76,7 +77,7 @@ function createFooterSlot(normalize, initialSessionId) {
|
|
|
76
77
|
subscribe(observer) { observers.add(observer); return () => { observers.delete(observer); }; },
|
|
77
78
|
reset(nextSessionId) {
|
|
78
79
|
sessionId = nextSessionId;
|
|
79
|
-
|
|
80
|
+
ownerToken = undefined;
|
|
80
81
|
snapshot = undefined;
|
|
81
82
|
notify();
|
|
82
83
|
},
|
|
@@ -85,13 +86,13 @@ function createFooterSlot(normalize, initialSessionId) {
|
|
|
85
86
|
return { ok: false, code: "invalid" };
|
|
86
87
|
if (sessionId === null || input.sessionId !== sessionId)
|
|
87
88
|
return { ok: false, code: "wrong-session" };
|
|
88
|
-
if (
|
|
89
|
+
if (ownerToken)
|
|
89
90
|
return { ok: false, code: "occupied" };
|
|
90
91
|
const token = Symbol(input.owner);
|
|
91
|
-
|
|
92
|
+
ownerToken = token;
|
|
92
93
|
return { ok: true, registration: {
|
|
93
94
|
publish(input) {
|
|
94
|
-
if (
|
|
95
|
+
if (ownerToken !== token)
|
|
95
96
|
return { ok: false, code: "disposed" };
|
|
96
97
|
const next = normalize(input);
|
|
97
98
|
if (!next)
|
|
@@ -103,9 +104,9 @@ function createFooterSlot(normalize, initialSessionId) {
|
|
|
103
104
|
return { ok: true };
|
|
104
105
|
},
|
|
105
106
|
dispose() {
|
|
106
|
-
if (
|
|
107
|
+
if (ownerToken !== token)
|
|
107
108
|
return;
|
|
108
|
-
|
|
109
|
+
ownerToken = undefined;
|
|
109
110
|
snapshot = undefined;
|
|
110
111
|
notify();
|
|
111
112
|
},
|
|
@@ -115,16 +116,17 @@ function createFooterSlot(normalize, initialSessionId) {
|
|
|
115
116
|
}
|
|
116
117
|
export function createFooterContributions(initialSessionId = null) {
|
|
117
118
|
const activity = createFooterSlot(normalizeSnapshot, initialSessionId);
|
|
118
|
-
|
|
119
|
+
// The plan slot; "summary" is its legacy wire name.
|
|
120
|
+
const plan = createFooterSlot(normalizePlanSnapshot, initialSessionId);
|
|
119
121
|
return {
|
|
120
122
|
read: activity.read,
|
|
121
|
-
readPlan:
|
|
123
|
+
readPlan: plan.read,
|
|
122
124
|
register: activity.register,
|
|
123
|
-
registerPlan:
|
|
125
|
+
registerPlan: plan.register,
|
|
124
126
|
subscribe(observer) {
|
|
125
|
-
const offActivity = activity.subscribe(observer),
|
|
126
|
-
return () => { offActivity();
|
|
127
|
+
const offActivity = activity.subscribe(observer), offPlan = plan.subscribe(observer);
|
|
128
|
+
return () => { offActivity(); offPlan(); };
|
|
127
129
|
},
|
|
128
|
-
reset(sessionId) { activity.reset(sessionId);
|
|
130
|
+
reset(sessionId) { activity.reset(sessionId); plan.reset(sessionId); },
|
|
129
131
|
};
|
|
130
132
|
}
|
package/dist/footer.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { keyHint } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { truncateToWidth } from "@earendil-works/pi-tui";
|
|
3
3
|
import * as chrome from "./chrome.js";
|
|
4
|
+
import { splitRow } from "./cwd.js";
|
|
4
5
|
import { formatTrackerLines } from "./tracker.js";
|
|
5
6
|
import { activityNeedsAnimation, renderFooterActivity } from "./footer-activity.js";
|
|
6
7
|
import { createLiveWatch } from "./live-watch.js";
|
|
@@ -45,12 +46,16 @@ export function installFooter(ctx, sources, contributions) {
|
|
|
45
46
|
const update = () => { syncAnimation(); redraw(); };
|
|
46
47
|
const unsub = footerData.onBranchChange(redraw);
|
|
47
48
|
const unsubscribeActivity = contributions?.subscribe(update);
|
|
49
|
+
const unsubscribePrs = sources.onPrsChange?.(redraw);
|
|
50
|
+
const unsubscribeBackground = sources.onBackgroundChange?.(redraw);
|
|
48
51
|
stopMount = () => {
|
|
49
52
|
if (disposed)
|
|
50
53
|
return;
|
|
51
54
|
disposed = true;
|
|
52
55
|
unsub();
|
|
53
56
|
unsubscribeActivity?.();
|
|
57
|
+
unsubscribePrs?.();
|
|
58
|
+
unsubscribeBackground?.();
|
|
54
59
|
live.stop();
|
|
55
60
|
};
|
|
56
61
|
update();
|
|
@@ -65,9 +70,16 @@ export function installFooter(ctx, sources, contributions) {
|
|
|
65
70
|
keyHint("app.thinking.cycle", "thinking"),
|
|
66
71
|
keyHint("app.tools.expand", "expand tools"),
|
|
67
72
|
].filter(Boolean));
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
73
|
+
// Left: hints (idle only) then the background count (always). Right: PR chips whenever
|
|
74
|
+
// there are any. The chips are dropped before the left side when the line is too narrow.
|
|
75
|
+
const background = sources.background?.();
|
|
76
|
+
const left = [hints.length > 0 ? theme.fg("dim", hints.join(" · ")) : "", background ? theme.fg("warning", background) : ""]
|
|
77
|
+
.filter(Boolean)
|
|
78
|
+
.join(theme.fg("dim", " · "));
|
|
79
|
+
const prs = sources.prs?.(theme);
|
|
80
|
+
if (left || prs) {
|
|
81
|
+
const line = prs ? splitRow(left, [prs], inner) : truncateToWidth(left, inner, "");
|
|
82
|
+
out.push(`${" ".repeat(margin())}${line}`);
|
|
71
83
|
}
|
|
72
84
|
const activityWidth = Math.max(0, width - margin() * 2);
|
|
73
85
|
const plan = renderFooterPlan(contributions?.readPlan(), activityWidth, theme);
|
package/dist/git-state.js
CHANGED
|
@@ -21,34 +21,50 @@ export function createGitState(pi, cwd, minIntervalMs = 5000) {
|
|
|
21
21
|
let state;
|
|
22
22
|
let lastRun = 0;
|
|
23
23
|
let inFlight;
|
|
24
|
+
let disposed = false;
|
|
24
25
|
const listeners = new Set();
|
|
26
|
+
const notify = () => {
|
|
27
|
+
for (const listener of listeners) {
|
|
28
|
+
try {
|
|
29
|
+
listener();
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// One listener's failure must not starve the others.
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
};
|
|
25
36
|
const refresh = (force = false) => {
|
|
37
|
+
if (disposed)
|
|
38
|
+
return Promise.resolve();
|
|
26
39
|
const now = Date.now();
|
|
27
40
|
if (inFlight)
|
|
28
41
|
return inFlight;
|
|
29
42
|
if (!force && now - lastRun < minIntervalMs)
|
|
30
43
|
return Promise.resolve();
|
|
31
44
|
lastRun = now;
|
|
45
|
+
// Read the directory once so both commands describe the same tree even if cwd changes mid-refresh.
|
|
46
|
+
const dir = cwd();
|
|
32
47
|
inFlight = (async () => {
|
|
48
|
+
let next;
|
|
33
49
|
try {
|
|
34
|
-
const branchRun = await pi.exec("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd:
|
|
35
|
-
if (branchRun.code
|
|
36
|
-
|
|
37
|
-
|
|
50
|
+
const branchRun = await pi.exec("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd: dir, timeout: 2000 });
|
|
51
|
+
if (branchRun.code === 0) {
|
|
52
|
+
const branch = parseBranch(branchRun.stdout);
|
|
53
|
+
const statusRun = await pi.exec("git", ["status", "--porcelain"], { cwd: dir, timeout: 3000 });
|
|
54
|
+
next = { branch, dirty: statusRun.code === 0 ? parsePorcelain(statusRun.stdout) : undefined };
|
|
38
55
|
}
|
|
39
|
-
const branch = parseBranch(branchRun.stdout);
|
|
40
|
-
const statusRun = await pi.exec("git", ["status", "--porcelain"], { cwd: cwd(), timeout: 3000 });
|
|
41
|
-
const dirty = statusRun.code === 0 ? parsePorcelain(statusRun.stdout) : undefined;
|
|
42
|
-
state = { branch, dirty };
|
|
43
56
|
}
|
|
44
57
|
catch {
|
|
45
|
-
|
|
58
|
+
next = undefined;
|
|
46
59
|
}
|
|
47
60
|
finally {
|
|
48
61
|
inFlight = undefined;
|
|
49
|
-
for (const listener of listeners)
|
|
50
|
-
listener();
|
|
51
62
|
}
|
|
63
|
+
// A result that lands after dispose belongs to a session that no longer exists.
|
|
64
|
+
if (disposed)
|
|
65
|
+
return;
|
|
66
|
+
state = next;
|
|
67
|
+
notify();
|
|
52
68
|
})();
|
|
53
69
|
return inFlight;
|
|
54
70
|
};
|
|
@@ -59,5 +75,9 @@ export function createGitState(pi, cwd, minIntervalMs = 5000) {
|
|
|
59
75
|
listeners.add(listener);
|
|
60
76
|
return () => listeners.delete(listener);
|
|
61
77
|
},
|
|
78
|
+
dispose() {
|
|
79
|
+
disposed = true;
|
|
80
|
+
listeners.clear();
|
|
81
|
+
},
|
|
62
82
|
};
|
|
63
83
|
}
|
package/dist/herdr.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createListeners } from "./listeners.js";
|
|
1
2
|
function parseJson(stdout) {
|
|
2
3
|
return JSON.parse(stdout);
|
|
3
4
|
}
|
|
@@ -42,3 +43,80 @@ export function listSiblings(agents, tabs, selfPane, selfTab) {
|
|
|
42
43
|
return undefined;
|
|
43
44
|
return parts;
|
|
44
45
|
}
|
|
46
|
+
const HERDR_FAST_MS = 2000;
|
|
47
|
+
const HERDR_SLOW_MS = 10000;
|
|
48
|
+
const HERDR_EXEC_TIMEOUT_MS = 3000;
|
|
49
|
+
/**
|
|
50
|
+
* Polls `herdr agent list` + `herdr tab list`: every 2 s while siblings are visible, every 10 s
|
|
51
|
+
* otherwise or after a failure. Probes once immediately.
|
|
52
|
+
*/
|
|
53
|
+
export function createHerdrSiblings(pi, env = process.env) {
|
|
54
|
+
let alive = true;
|
|
55
|
+
let siblings;
|
|
56
|
+
let timer;
|
|
57
|
+
let period = 0;
|
|
58
|
+
let inFlight;
|
|
59
|
+
const listeners = createListeners();
|
|
60
|
+
const set = (next) => {
|
|
61
|
+
const changed = JSON.stringify(next) !== JSON.stringify(siblings);
|
|
62
|
+
siblings = next;
|
|
63
|
+
if (changed)
|
|
64
|
+
listeners.notify();
|
|
65
|
+
};
|
|
66
|
+
const schedule = (periodMs) => {
|
|
67
|
+
if (!alive || period === periodMs)
|
|
68
|
+
return;
|
|
69
|
+
if (timer)
|
|
70
|
+
clearInterval(timer);
|
|
71
|
+
period = periodMs;
|
|
72
|
+
timer = setInterval(() => { void probe(); }, periodMs);
|
|
73
|
+
};
|
|
74
|
+
const probe = async () => {
|
|
75
|
+
if (!alive || inFlight)
|
|
76
|
+
return;
|
|
77
|
+
const ac = new AbortController();
|
|
78
|
+
inFlight = ac;
|
|
79
|
+
try {
|
|
80
|
+
const agentRun = await pi.exec("herdr", ["agent", "list"], { timeout: HERDR_EXEC_TIMEOUT_MS, signal: ac.signal });
|
|
81
|
+
if (!alive)
|
|
82
|
+
return;
|
|
83
|
+
const tabRun = await pi.exec("herdr", ["tab", "list"], { timeout: HERDR_EXEC_TIMEOUT_MS, signal: ac.signal });
|
|
84
|
+
if (!alive)
|
|
85
|
+
return;
|
|
86
|
+
if (agentRun.code !== 0 || tabRun.code !== 0 || agentRun.killed || tabRun.killed) {
|
|
87
|
+
set(undefined);
|
|
88
|
+
schedule(HERDR_SLOW_MS);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
set(listSiblings(parseAgentList(agentRun.stdout), parseTabList(tabRun.stdout), env.HERDR_PANE_ID, env.HERDR_TAB_ID));
|
|
92
|
+
schedule(siblings ? HERDR_FAST_MS : HERDR_SLOW_MS);
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
if (!alive)
|
|
96
|
+
return;
|
|
97
|
+
set(undefined);
|
|
98
|
+
schedule(HERDR_SLOW_MS);
|
|
99
|
+
}
|
|
100
|
+
finally {
|
|
101
|
+
if (inFlight === ac)
|
|
102
|
+
inFlight = undefined;
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
void probe();
|
|
106
|
+
return {
|
|
107
|
+
get: () => siblings,
|
|
108
|
+
onChange: listeners.add,
|
|
109
|
+
dispose() {
|
|
110
|
+
if (!alive)
|
|
111
|
+
return;
|
|
112
|
+
alive = false;
|
|
113
|
+
if (timer)
|
|
114
|
+
clearInterval(timer);
|
|
115
|
+
timer = undefined;
|
|
116
|
+
inFlight?.abort();
|
|
117
|
+
inFlight = undefined;
|
|
118
|
+
siblings = undefined;
|
|
119
|
+
listeners.clear();
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
}
|