bom-pi 0.0.0-stage → 0.0.2
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/README.md +33 -2
- package/apps/README.md +7 -0
- package/apps/web/README.md +21 -0
- package/apps/web/pocket-adapter.mjs +323 -0
- package/apps/web/public/LICENSE.pocket +21 -0
- package/apps/web/public/activity-fold.js +28 -0
- package/apps/web/public/agent-now.js +37 -0
- package/apps/web/public/agent-tile.js +79 -0
- package/apps/web/public/app.js +975 -0
- package/apps/web/public/apple-touch-icon.png +0 -0
- package/apps/web/public/artifact-bridge.js +687 -0
- package/apps/web/public/avatar.js +87 -0
- package/apps/web/public/back.js +554 -0
- package/apps/web/public/box-keys.js +216 -0
- package/apps/web/public/browser-panel.js +1097 -0
- package/apps/web/public/browser.js +68 -0
- package/apps/web/public/calls.js +139 -0
- package/apps/web/public/chat.js +919 -0
- package/apps/web/public/clock.js +34 -0
- package/apps/web/public/commands.js +578 -0
- package/apps/web/public/composer.js +2068 -0
- package/apps/web/public/diff-parse.js +672 -0
- package/apps/web/public/diff.js +1646 -0
- package/apps/web/public/dock.js +299 -0
- package/apps/web/public/extension-ui.js +2734 -0
- package/apps/web/public/files-panel.js +775 -0
- package/apps/web/public/files.js +368 -0
- package/apps/web/public/fonts/FiraCode-VF.woff2 +0 -0
- package/apps/web/public/fonts/Geist.woff2 +0 -0
- package/apps/web/public/fonts/IBMPlexMono-Variable-Italic.woff2 +0 -0
- package/apps/web/public/fonts/IBMPlexMono-Variable.woff2 +0 -0
- package/apps/web/public/fonts/InterVariable-Italic.woff2 +0 -0
- package/apps/web/public/fonts/InterVariable.woff2 +0 -0
- package/apps/web/public/fonts/Iosevka-Bold.woff2 +0 -0
- package/apps/web/public/fonts/Iosevka-BoldItalic.woff2 +0 -0
- package/apps/web/public/fonts/Iosevka-Italic.woff2 +0 -0
- package/apps/web/public/fonts/Iosevka-Regular.woff2 +0 -0
- package/apps/web/public/fonts/Iosevka-SemiBold.woff2 +0 -0
- package/apps/web/public/fonts/Iosevka-SemiBoldItalic.woff2 +0 -0
- package/apps/web/public/fonts/JetBrainsMono-Italic.woff2 +0 -0
- package/apps/web/public/fonts/JetBrainsMono.woff2 +0 -0
- package/apps/web/public/fonts/NerdFontsSymbolsMono.woff2 +0 -0
- package/apps/web/public/fonts/SOURCES.md +69 -0
- package/apps/web/public/gestures.js +610 -0
- package/apps/web/public/highlight.js +480 -0
- package/apps/web/public/history.js +70 -0
- package/apps/web/public/home.js +169 -0
- package/apps/web/public/icon-192.png +0 -0
- package/apps/web/public/icon-512.png +0 -0
- package/apps/web/public/icon-maskable-512.png +0 -0
- package/apps/web/public/icon.svg +16 -0
- package/apps/web/public/index.html +71 -0
- package/apps/web/public/launcher.js +1293 -0
- package/apps/web/public/linked-sessions.js +55 -0
- package/apps/web/public/live.js +39 -0
- package/apps/web/public/manifest.webmanifest +32 -0
- package/apps/web/public/model-refresh.js +73 -0
- package/apps/web/public/notify.js +251 -0
- package/apps/web/public/peeks.js +920 -0
- package/apps/web/public/pip-attachments.js +182 -0
- package/apps/web/public/pip-window.js +141 -0
- package/apps/web/public/pip.js +1217 -0
- package/apps/web/public/quote-reply.js +109 -0
- package/apps/web/public/quote-text.js +31 -0
- package/apps/web/public/rich.js +403 -0
- package/apps/web/public/run-frame.js +106 -0
- package/apps/web/public/safe-mode.js +71 -0
- package/apps/web/public/seen.js +169 -0
- package/apps/web/public/session-tree.js +117 -0
- package/apps/web/public/sessions.js +2695 -0
- package/apps/web/public/share.js +126 -0
- package/apps/web/public/sheets/appearance.js +820 -0
- package/apps/web/public/sheets/branch.js +353 -0
- package/apps/web/public/sheets/extensions.js +818 -0
- package/apps/web/public/sheets/file.js +318 -0
- package/apps/web/public/sheets/find.js +250 -0
- package/apps/web/public/sheets/folder.js +680 -0
- package/apps/web/public/sheets/gallery.js +2340 -0
- package/apps/web/public/sheets/hooks.js +350 -0
- package/apps/web/public/sheets/memory.js +531 -0
- package/apps/web/public/sheets/menu.js +580 -0
- package/apps/web/public/sheets/message.js +220 -0
- package/apps/web/public/sheets/model.js +412 -0
- package/apps/web/public/sheets/people.js +414 -0
- package/apps/web/public/sheets/pi-sessions.js +264 -0
- package/apps/web/public/sheets/providers.js +525 -0
- package/apps/web/public/sheets/running.js +130 -0
- package/apps/web/public/sheets/schedules.js +71 -0
- package/apps/web/public/sheets/session.js +249 -0
- package/apps/web/public/sheets/shortcuts.js +101 -0
- package/apps/web/public/sheets/spend.js +150 -0
- package/apps/web/public/sheets/text.js +53 -0
- package/apps/web/public/sheets/viewers.js +363 -0
- package/apps/web/public/sheets/worktree.js +50 -0
- package/apps/web/public/sheets.js +410 -0
- package/apps/web/public/signin.js +57 -0
- package/apps/web/public/slots.js +208 -0
- package/apps/web/public/store.js +1760 -0
- package/apps/web/public/style.css +18625 -0
- package/apps/web/public/subagents-board.js +879 -0
- package/apps/web/public/subagents.js +273 -0
- package/apps/web/public/sw.js +169 -0
- package/apps/web/public/taps.js +26 -0
- package/apps/web/public/theme-surface.d.ts +4 -0
- package/apps/web/public/theme-surface.js +266 -0
- package/apps/web/public/theme.js +845 -0
- package/apps/web/public/themes/lantern/colors.toml +25 -0
- package/apps/web/public/themes/lantern/direction.css +60 -0
- package/apps/web/public/themes/lantern/frame.css +254 -0
- package/apps/web/public/themes/lantern/theme.css +10 -0
- package/apps/web/public/themes/lantern/theme.json +7 -0
- package/apps/web/public/themes/pocket/theme.css +189 -0
- package/apps/web/public/themes/pocket/theme.json +6 -0
- package/apps/web/public/themes.js +556 -0
- package/apps/web/public/transcript.js +3348 -0
- package/apps/web/public/trust.js +295 -0
- package/apps/web/public/turns.js +507 -0
- package/apps/web/public/ui.js +1910 -0
- package/apps/web/public/voice.js +345 -0
- package/apps/web/server.mjs +774 -0
- package/package.json +43 -4
- package/packages/cli/src/bom-pi.mjs +452 -0
- package/packages/cli/templates/blank.factory.json +7 -0
- package/packages/cli/templates/dev.factory.json +11 -0
- package/packages/cli/templates/docs.factory.json +12 -0
- package/packages/cli/templates/ops.factory.json +14 -0
- package/packages/cli/templates/triage.factory.json +10 -0
- package/packages/core/src/daemon.mjs +2047 -0
- package/packages/core/src/limit-gate.mjs +192 -0
- package/packages/core/src/rpc-client.mjs +66 -0
- package/systemd/README.md +11 -0
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
// The branch picker: the git branch this session's folder has checked out, and the others to switch to or make, in a
|
|
2
|
+
// menu down from the branch in the top bar.
|
|
3
|
+
import { useEffect, useRef, useState } from "preact/hooks";
|
|
4
|
+
import { actions, attempt, canSteer, closeSheet, notify, scoped, store } from "../store.js";
|
|
5
|
+
import { useDragToClose } from "../gestures.js";
|
|
6
|
+
import {
|
|
7
|
+
anchorStyle,
|
|
8
|
+
BOTTOM_SHEETS,
|
|
9
|
+
html,
|
|
10
|
+
Icon,
|
|
11
|
+
popAnchor,
|
|
12
|
+
scrimPress,
|
|
13
|
+
shortPath,
|
|
14
|
+
timeAgo,
|
|
15
|
+
} from "../ui.js";
|
|
16
|
+
|
|
17
|
+
const coarsePointer = matchMedia("(pointer: coarse)").matches;
|
|
18
|
+
|
|
19
|
+
/** The picker is for people who can steer, in a session whose folder is in a git repository. */
|
|
20
|
+
export const branchAvailable = () => canSteer() && Boolean(store.state.view.branch);
|
|
21
|
+
|
|
22
|
+
/** Where the picker opens: under the branch in the top bar (`popAnchor`), or above the message box without it. */
|
|
23
|
+
const branchAnchor = () => popAnchor(".topbar button.title-branch") ?? popAnchor(".composer");
|
|
24
|
+
|
|
25
|
+
/** What a folder has checked out, in a few characters: its branch, or the commit it is at. */
|
|
26
|
+
export const headLabel = (head) => head?.branch ?? head?.detached ?? "";
|
|
27
|
+
|
|
28
|
+
/** A name typed for a new branch, as git takes one: spaces become dashes. */
|
|
29
|
+
const asBranchName = (text) => text.trim().replace(/\s+/g, "-");
|
|
30
|
+
|
|
31
|
+
/** Why this person cannot switch branches here now, or null when they can: the server's rules, said before a tap. */
|
|
32
|
+
function whyNot() {
|
|
33
|
+
const { view, me } = store.state;
|
|
34
|
+
|
|
35
|
+
if (!canSteer()) {
|
|
36
|
+
return "Viewers can see the branch, not switch it.";
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (scoped()) {
|
|
40
|
+
return "You were invited to one session: switching the branch changes the whole folder.";
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (view.turns?.on && view.turns.driver !== me?.id) {
|
|
44
|
+
return "Take turns is on: the driver switches branches.";
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (view.live?.busy) {
|
|
48
|
+
return "Pi is working: switch once it is done, or stop it.";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** How far a branch is from the remote branch it follows, as ↑2 ↓1, or that the remote branch is gone. */
|
|
55
|
+
function Track({ upstream }) {
|
|
56
|
+
if (!upstream) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (upstream.gone) {
|
|
61
|
+
return html`<span class="branch-track warn" title=${`${upstream.name} is gone`}>gone</span>`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
(upstream.ahead > 0 || upstream.behind > 0) &&
|
|
66
|
+
html`<span class="branch-track" title=${`Against ${upstream.name}`}>
|
|
67
|
+
${upstream.ahead > 0 ? `↑${upstream.ahead}` : ""}${upstream.ahead > 0 && upstream.behind > 0 ? " " : ""}${upstream.behind > 0 ? `↓${upstream.behind}` : ""}
|
|
68
|
+
</span>`
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The branch picker: a menu down from the branch, with the folder's branches (the current one first and checked, then
|
|
74
|
+
* the newest), the remote branches it has no local branch for, and, for a name typed that is not a branch yet, a new
|
|
75
|
+
* branch made from what is checked out. Arrows and Enter pick, as in the model picker; `/branch fix` opens it already
|
|
76
|
+
* searching.
|
|
77
|
+
*/
|
|
78
|
+
export function BranchPicker() {
|
|
79
|
+
const { view, server } = store.state;
|
|
80
|
+
const [query, setQuery] = useState(store.state.sheet?.query ?? "");
|
|
81
|
+
const [branches, setBranches] = useState(null);
|
|
82
|
+
const [error, setError] = useState(null);
|
|
83
|
+
const [switching, setSwitching] = useState(false);
|
|
84
|
+
const [picked, setPicked] = useState(0);
|
|
85
|
+
const [anchor, setAnchor] = useState(branchAnchor);
|
|
86
|
+
const box = useRef(null);
|
|
87
|
+
const search = useRef(null);
|
|
88
|
+
const list = useRef(null);
|
|
89
|
+
// Arrows scroll the list to the row they reach; the mouse does not, or the list would run away under it.
|
|
90
|
+
const keyed = useRef(false);
|
|
91
|
+
|
|
92
|
+
// On a phone, a swipe away from where it opens closes it: up, under the top bar's branch.
|
|
93
|
+
useDragToClose(box, {
|
|
94
|
+
dir: anchor?.down ? "up" : "down",
|
|
95
|
+
onClose: closeSheet,
|
|
96
|
+
enabled: BOTTOM_SHEETS.matches,
|
|
97
|
+
});
|
|
98
|
+
const blocked = whyNot();
|
|
99
|
+
const needle = query.trim().toLowerCase();
|
|
100
|
+
const matches = (name) => needle === "" || name.toLowerCase().includes(needle);
|
|
101
|
+
// The current branch first, then the rest newest first: Enter on an untouched list keeps the branch.
|
|
102
|
+
const local = (branches?.local ?? [])
|
|
103
|
+
.filter((branch) => matches(branch.name))
|
|
104
|
+
.sort((a, b) => Number(b.current) - Number(a.current));
|
|
105
|
+
const remote = (branches?.remote ?? []).filter((branch) => matches(branch.name));
|
|
106
|
+
const name = asBranchName(query);
|
|
107
|
+
// A name typed that is no branch yet, here or as a remote one (which its own row switches to), can be made.
|
|
108
|
+
const make =
|
|
109
|
+
branches &&
|
|
110
|
+
name !== "" &&
|
|
111
|
+
!branches.local.some((branch) => branch.name === name) &&
|
|
112
|
+
!branches.remote.some((branch) => branch.name.slice(branch.name.indexOf("/") + 1) === name)
|
|
113
|
+
? name
|
|
114
|
+
: null;
|
|
115
|
+
const rows = [
|
|
116
|
+
...local.map((branch) => ({ kind: "local", key: `l:${branch.name}`, branch })),
|
|
117
|
+
...remote.map((branch) => ({ kind: "remote", key: `r:${branch.name}`, branch })),
|
|
118
|
+
...(make ? [{ kind: "make", key: "make", name: make }] : []),
|
|
119
|
+
];
|
|
120
|
+
// The list can change while the picker is open.
|
|
121
|
+
const active = Math.max(0, Math.min(picked, rows.length - 1));
|
|
122
|
+
|
|
123
|
+
useEffect(() => {
|
|
124
|
+
const place = () => setAnchor(branchAnchor());
|
|
125
|
+
const onKey = (event) => event.key === "Escape" && closeSheet();
|
|
126
|
+
|
|
127
|
+
addEventListener("resize", place);
|
|
128
|
+
visualViewport?.addEventListener("resize", place);
|
|
129
|
+
addEventListener("keydown", onKey);
|
|
130
|
+
// Keys go to the picker, not the message box behind it. Phones keep their keyboard down until the search is tapped.
|
|
131
|
+
(coarsePointer ? box.current : search.current)?.focus({ preventScroll: true });
|
|
132
|
+
|
|
133
|
+
return () => {
|
|
134
|
+
removeEventListener("resize", place);
|
|
135
|
+
visualViewport?.removeEventListener("resize", place);
|
|
136
|
+
removeEventListener("keydown", onKey);
|
|
137
|
+
};
|
|
138
|
+
}, []);
|
|
139
|
+
// Read each time the picker opens (one opened again while it was closing, or by `/branch name` while open, is the same
|
|
140
|
+
// picker) and when the branch changes while it is open.
|
|
141
|
+
const opened = store.state.sheet;
|
|
142
|
+
const head = JSON.stringify(view.branch);
|
|
143
|
+
|
|
144
|
+
useEffect(() => {
|
|
145
|
+
if (opened?.type !== "branch") {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
let live = true;
|
|
150
|
+
|
|
151
|
+
setError(null);
|
|
152
|
+
actions.branches().then(
|
|
153
|
+
(each) => live && setBranches(each),
|
|
154
|
+
(failure) => live && setError(failure.message),
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
return () => {
|
|
158
|
+
live = false;
|
|
159
|
+
};
|
|
160
|
+
}, [opened, head]);
|
|
161
|
+
useEffect(() => {
|
|
162
|
+
if (opened?.type === "branch" && opened.query !== undefined) {
|
|
163
|
+
setQuery(opened.query);
|
|
164
|
+
setPicked(0);
|
|
165
|
+
}
|
|
166
|
+
}, [opened]);
|
|
167
|
+
useEffect(() => {
|
|
168
|
+
if (!keyed.current) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
keyed.current = false;
|
|
173
|
+
list.current?.querySelector(".pop-menu-row.on")?.scrollIntoView({ block: "nearest" });
|
|
174
|
+
}, [active]);
|
|
175
|
+
|
|
176
|
+
const pick = (row) => {
|
|
177
|
+
if (row.kind === "local" && row.branch.current) {
|
|
178
|
+
closeSheet();
|
|
179
|
+
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (row.kind === "local" && row.branch.worktree) {
|
|
184
|
+
notify(
|
|
185
|
+
"info",
|
|
186
|
+
`${row.branch.name} is checked out in ${shortPath(row.branch.worktree, server?.home)}: git switches to it only there.`,
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (blocked) {
|
|
193
|
+
notify("error", blocked);
|
|
194
|
+
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const target =
|
|
199
|
+
row.kind === "remote"
|
|
200
|
+
? { track: row.branch.name }
|
|
201
|
+
: row.kind === "make"
|
|
202
|
+
? { name: row.name, create: true }
|
|
203
|
+
: { name: row.branch.name };
|
|
204
|
+
|
|
205
|
+
setSwitching(true);
|
|
206
|
+
attempt(async () => {
|
|
207
|
+
const { branch } = await actions.switchBranch(target);
|
|
208
|
+
|
|
209
|
+
notify("info", `On ${branch} now.`);
|
|
210
|
+
closeSheet();
|
|
211
|
+
}).then(() => setSwitching(false));
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const rowBody = (each) => {
|
|
215
|
+
if (each.kind === "make") {
|
|
216
|
+
return html`<${Icon} name="plus" size=${14} />
|
|
217
|
+
<span class="pop-menu-row-text">
|
|
218
|
+
<span class="pop-menu-row-name">Make the branch <span class="mono">${each.name}</span></span>
|
|
219
|
+
<span class="pop-menu-row-sub">from ${headLabel(view.branch) || "here"}${branches.changed > 0 ? ", with your uncommitted changes" : ""}</span>
|
|
220
|
+
</span>`;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const { branch } = each;
|
|
224
|
+
const sub =
|
|
225
|
+
each.kind === "local" && branch.worktree
|
|
226
|
+
? `in another worktree: ${shortPath(branch.worktree, server?.home)}`
|
|
227
|
+
: [branch.subject, branch.at ? timeAgo(branch.at) : "no commits yet"]
|
|
228
|
+
.filter(Boolean)
|
|
229
|
+
.join(" · ");
|
|
230
|
+
|
|
231
|
+
return html`<span class="pop-menu-row-text">
|
|
232
|
+
<span class="pop-menu-row-name mono">${branch.name}</span>
|
|
233
|
+
<span class="pop-menu-row-sub">${sub}</span>
|
|
234
|
+
</span>
|
|
235
|
+
${each.kind === "local" && html`<${Track} upstream=${branch.upstream} />`}
|
|
236
|
+
${each.kind === "local" && branch.current && html`<${Icon} name="check" size=${14} />`}`;
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
// Arrows and Enter move through the rows from the search box or the picker itself, not from its other buttons.
|
|
240
|
+
const onKeyDown = (event) => {
|
|
241
|
+
if (
|
|
242
|
+
event.isComposing ||
|
|
243
|
+
rows.length === 0 ||
|
|
244
|
+
(event.target !== search.current && event.target !== box.current)
|
|
245
|
+
) {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Alt+↑/↓ stays the app's: it switches sessions.
|
|
250
|
+
const step = event.altKey
|
|
251
|
+
? 0
|
|
252
|
+
: event.key === "ArrowDown"
|
|
253
|
+
? 1
|
|
254
|
+
: event.key === "ArrowUp"
|
|
255
|
+
? -1
|
|
256
|
+
: 0;
|
|
257
|
+
|
|
258
|
+
if (step !== 0) {
|
|
259
|
+
event.preventDefault();
|
|
260
|
+
keyed.current = true;
|
|
261
|
+
setPicked((active + step + rows.length) % rows.length);
|
|
262
|
+
} else if (event.key === "Enter" && rows[active] && !switching) {
|
|
263
|
+
event.preventDefault();
|
|
264
|
+
pick(rows[active]);
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
const row = (each, index, body) => html`<button
|
|
269
|
+
key=${each.key}
|
|
270
|
+
class=${`pop-menu-row ${index === active ? "on" : ""} ${each.branch?.worktree ? "elsewhere" : ""}`}
|
|
271
|
+
role="option"
|
|
272
|
+
aria-selected=${each.kind === "local" && each.branch.current}
|
|
273
|
+
disabled=${switching}
|
|
274
|
+
onMouseMove=${() => index !== active && setPicked(index)}
|
|
275
|
+
onClick=${() => pick(each)}
|
|
276
|
+
>
|
|
277
|
+
${body}
|
|
278
|
+
</button>`;
|
|
279
|
+
|
|
280
|
+
return html`<div
|
|
281
|
+
class="overlay pop-overlay"
|
|
282
|
+
onMouseDown=${scrimPress(closeSheet)}
|
|
283
|
+
>
|
|
284
|
+
<section
|
|
285
|
+
class=${`pop-menu branch-picker ${anchor ? (anchor.down ? "down" : "") : "free"}`}
|
|
286
|
+
style=${anchorStyle(anchor)}
|
|
287
|
+
ref=${box}
|
|
288
|
+
role="dialog"
|
|
289
|
+
aria-modal="true"
|
|
290
|
+
aria-label="Branch"
|
|
291
|
+
tabindex="-1"
|
|
292
|
+
onKeyDown=${onKeyDown}
|
|
293
|
+
>
|
|
294
|
+
<div class="grip" aria-hidden="true"></div>
|
|
295
|
+
<header class="pop-menu-head">
|
|
296
|
+
<span>Branch</span>
|
|
297
|
+
<span class="branch-head mono" title=${view.branch?.detached ? "No branch: a commit" : "Checked out"}>
|
|
298
|
+
<${Icon} name="fork" size=${12} /> ${headLabel(view.branch) || "none"}
|
|
299
|
+
</span>
|
|
300
|
+
</header>
|
|
301
|
+
<label class="search pop-menu-search">
|
|
302
|
+
<${Icon} name="search" size=${15} />
|
|
303
|
+
<input
|
|
304
|
+
ref=${search}
|
|
305
|
+
placeholder="Switch to or make a branch"
|
|
306
|
+
value=${query}
|
|
307
|
+
autocapitalize="off"
|
|
308
|
+
autocomplete="off"
|
|
309
|
+
spellcheck="false"
|
|
310
|
+
onInput=${(event) => {
|
|
311
|
+
setQuery(event.currentTarget.value);
|
|
312
|
+
setPicked(0);
|
|
313
|
+
}}
|
|
314
|
+
/>
|
|
315
|
+
</label>
|
|
316
|
+
<div class="pop-menu-list" ref=${list} role="listbox" aria-label="Branches">
|
|
317
|
+
${error && html`<p class="muted pop-menu-note">${error}</p>`}
|
|
318
|
+
${!branches && !error && html`<p class="muted pop-menu-note">Asking git…</p>`}
|
|
319
|
+
${
|
|
320
|
+
branches &&
|
|
321
|
+
rows.length === 0 &&
|
|
322
|
+
html`<p class="muted pop-menu-note">No branch matches “${query.trim()}”.</p>`
|
|
323
|
+
}
|
|
324
|
+
${rows.flatMap((each, index) => [
|
|
325
|
+
each.kind === "remote" &&
|
|
326
|
+
rows[index - 1]?.kind !== "remote" &&
|
|
327
|
+
html`<div class="pop-menu-label" key="remote">Remote</div>`,
|
|
328
|
+
row(each, index, rowBody(each)),
|
|
329
|
+
])}
|
|
330
|
+
</div>
|
|
331
|
+
${
|
|
332
|
+
branches &&
|
|
333
|
+
(blocked || branches.changed > 0 || branches.more > 0) &&
|
|
334
|
+
html`<div class="pop-menu-foot small">
|
|
335
|
+
${blocked && html`<p class="warn">${blocked}</p>`}
|
|
336
|
+
${
|
|
337
|
+
!blocked &&
|
|
338
|
+
branches.changed > 0 &&
|
|
339
|
+
html`<p class="muted">
|
|
340
|
+
${branches.changed} ${branches.changed === 1 ? "file has" : "files have"} uncommitted changes: they come along, unless the other branch changes them too.
|
|
341
|
+
</p>`
|
|
342
|
+
}
|
|
343
|
+
${
|
|
344
|
+
branches.more > 0 &&
|
|
345
|
+
html`<p class="muted">
|
|
346
|
+
${`${branches.more.toLocaleString()} older branches are not listed.`}
|
|
347
|
+
</p>`
|
|
348
|
+
}
|
|
349
|
+
</div>`
|
|
350
|
+
}
|
|
351
|
+
</section>
|
|
352
|
+
</div>`;
|
|
353
|
+
}
|