codetrap 0.1.6 → 0.1.8
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 +159 -51
- package/docs/installation.md +113 -29
- package/package.json +4 -3
- package/plugins/codetrap-agent/.codex-plugin/plugin.json +1 -2
- package/plugins/codetrap-agent/hooks/post-flight-capture.example.md +19 -17
- package/plugins/codetrap-agent/hooks.json +2 -2
- package/{skills → plugins/codetrap-agent/skills}/codetrap-add/SKILL.md +10 -4
- package/plugins/codetrap-agent/skills/codetrap-capture/SKILL.md +14 -3
- package/plugins/codetrap-agent/skills/codetrap-capture-external/SKILL.md +52 -9
- package/plugins/codetrap-agent/skills/codetrap-check/SKILL.md +74 -6
- package/{skills → plugins/codetrap-agent/skills}/codetrap-search/SKILL.md +6 -5
- package/plugins/codetrap-agent/templates/AGENTS.codetrap.md +31 -5
- package/scripts/search-policy-sweep.ts +131 -0
- package/src/commands/workflow.ts +186 -68
- package/src/db/connection.ts +6 -6
- package/src/db/embedding-queries.ts +230 -48
- package/src/db/queries.ts +0 -25
- package/src/db/repository.ts +32 -21
- package/src/db/schema.ts +80 -0
- package/src/index.ts +32 -7
- package/src/lib/command-requests.ts +134 -1
- package/src/lib/config.ts +57 -7
- package/src/lib/constants.ts +1 -1
- package/src/lib/doctor.ts +96 -6
- package/src/lib/embed-output.ts +26 -0
- package/src/lib/embedder.ts +118 -3
- package/src/lib/embedding-health.ts +3 -1
- package/src/lib/embedding-job.ts +3 -0
- package/src/lib/embedding-management.ts +65 -0
- package/src/lib/embedding-runtime.ts +177 -0
- package/src/lib/output-json.ts +0 -2
- package/src/lib/scope-context.ts +17 -11
- package/src/lib/scope-migration.ts +2 -1
- package/src/lib/scope.ts +4 -6
- package/src/lib/search-eval.ts +136 -23
- package/src/lib/search-policy-sweep.ts +563 -0
- package/src/lib/search-policy.ts +0 -4
- package/src/lib/search-service.ts +14 -15
- package/src/lib/session-candidate-document.ts +175 -0
- package/src/lib/session-candidate-scope.ts +6 -0
- package/src/lib/session-capture.ts +298 -32
- package/src/lib/session-codec.ts +1 -8
- package/src/lib/session-operations.ts +111 -51
- package/src/lib/session-review.ts +327 -0
- package/src/lib/session-store.ts +177 -55
- package/src/lib/store.ts +79 -11
- package/src/lib/string-list.ts +3 -0
- package/src/lib/text-lines.ts +7 -0
- package/src/lib/trap-search-document.ts +2 -1
- package/src/lib/value-types.ts +3 -0
- package/src/web/client-review.ts +171 -0
- package/src/web/client-script.ts +1543 -0
- package/src/web/client-shell.ts +414 -0
- package/src/web/client-text.ts +447 -0
- package/src/web/project-registry.ts +3 -5
- package/src/web/server.ts +184 -111
- package/src/web/static.ts +581 -484
- package/skills/codetrap-capture-external/SKILL.md +0 -62
- package/skills/codetrap-check/SKILL.md +0 -69
- package/src/lib/embedding-index.ts +0 -53
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
export const WEB_SHELL_CLIENT_SCRIPT = ` const SHELL_LAYOUT_KEY = "codetrap-shell-layout";
|
|
2
|
+
const SHELL_SIDEBAR_KEY = "codetrap-sidebar-collapsed";
|
|
3
|
+
const SHELL_QUEUE_KEY = "codetrap-queue-collapsed";
|
|
4
|
+
const SHELL_DESKTOP_QUERY = "(min-width: 1061px)";
|
|
5
|
+
const SHELL_SPLITTER_WIDTH = 8;
|
|
6
|
+
const SHELL_PANE_MIN = { rail: 250, detail: 460, queue: 320 };
|
|
7
|
+
const SHELL_COLLAPSE_THRESHOLD = { rail: 180, queue: 230 };
|
|
8
|
+
const SHELL_REVEAL_EDGE_WIDTH = 18;
|
|
9
|
+
const SHELL_PEEK_WIDTH = { rail: 330, queue: 390 };
|
|
10
|
+
|
|
11
|
+
function shellElement() {
|
|
12
|
+
return document.querySelector(".shell");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function isDesktopShellLayout() {
|
|
16
|
+
return window.matchMedia(SHELL_DESKTOP_QUERY).matches;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function readShellLayout() {
|
|
20
|
+
try {
|
|
21
|
+
const saved = JSON.parse(localStorage.getItem(SHELL_LAYOUT_KEY) || "null");
|
|
22
|
+
if (saved && Number.isFinite(saved.rail)) {
|
|
23
|
+
const detail = Number.isFinite(saved.detail) ? saved.detail : saved.queue;
|
|
24
|
+
if (Number.isFinite(detail)) return { rail: saved.rail, detail };
|
|
25
|
+
}
|
|
26
|
+
} catch (_error) {}
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function writeShellLayout(layout) {
|
|
31
|
+
try {
|
|
32
|
+
localStorage.setItem(SHELL_LAYOUT_KEY, JSON.stringify({
|
|
33
|
+
rail: Math.round(layout.rail),
|
|
34
|
+
detail: Math.round(layout.detail)
|
|
35
|
+
}));
|
|
36
|
+
} catch (_error) {}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function clamp(value, min, max) {
|
|
40
|
+
return Math.min(Math.max(value, min), max);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function shellAvailableWidth(shell) {
|
|
44
|
+
let splitterCount = 0;
|
|
45
|
+
if (!state.sidebarCollapsed) splitterCount += 1;
|
|
46
|
+
if (!state.queueCollapsed) splitterCount += 1;
|
|
47
|
+
return Math.max(0, shell.getBoundingClientRect().width - (SHELL_SPLITTER_WIDTH * splitterCount));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function currentShellLayout() {
|
|
51
|
+
const saved = readShellLayout();
|
|
52
|
+
const railWidth = document.querySelector(".rail")?.getBoundingClientRect().width || 0;
|
|
53
|
+
const detailWidth = document.querySelector(".detail")?.getBoundingClientRect().width || 0;
|
|
54
|
+
const rail = railWidth > 0 ? railWidth : saved?.rail || SHELL_PANE_MIN.rail;
|
|
55
|
+
const detail = detailWidth > 0 ? detailWidth : saved?.detail || SHELL_PANE_MIN.detail;
|
|
56
|
+
return { rail, detail };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function normalizeSidebarCollapsedShellLayout(shell, layout) {
|
|
60
|
+
const available = shellAvailableWidth(shell);
|
|
61
|
+
const minimumTotal = SHELL_PANE_MIN.detail + SHELL_PANE_MIN.queue;
|
|
62
|
+
if (available < minimumTotal) return null;
|
|
63
|
+
|
|
64
|
+
const saved = readShellLayout();
|
|
65
|
+
const rail = Number.isFinite(Number(layout.rail)) ? Number(layout.rail) : saved?.rail || SHELL_PANE_MIN.rail;
|
|
66
|
+
let detail = Number(layout.detail);
|
|
67
|
+
if (!Number.isFinite(detail)) detail = saved?.detail || SHELL_PANE_MIN.detail;
|
|
68
|
+
detail = clamp(detail, SHELL_PANE_MIN.detail, available - SHELL_PANE_MIN.queue);
|
|
69
|
+
|
|
70
|
+
return { rail: Math.round(rail), detail: Math.round(detail), available };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function queueWidthForLayout(shell, layout) {
|
|
74
|
+
const available = shellAvailableWidth(shell);
|
|
75
|
+
const rail = state.sidebarCollapsed ? 0 : Number(layout.rail);
|
|
76
|
+
const detail = Number(layout.detail);
|
|
77
|
+
if (!Number.isFinite(detail)) return SHELL_PANE_MIN.queue;
|
|
78
|
+
return available - (Number.isFinite(rail) ? rail : SHELL_PANE_MIN.rail) - detail;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function collapseTargetForLayout(side, layout) {
|
|
82
|
+
const shell = shellElement();
|
|
83
|
+
if (!shell || !isDesktopShellLayout()) return null;
|
|
84
|
+
if (side === "left" && !state.sidebarCollapsed && Number(layout.rail) <= SHELL_COLLAPSE_THRESHOLD.rail) {
|
|
85
|
+
return "sidebar";
|
|
86
|
+
}
|
|
87
|
+
if (side === "right" && !state.queueCollapsed && queueWidthForLayout(shell, layout) <= SHELL_COLLAPSE_THRESHOLD.queue) {
|
|
88
|
+
return "queue";
|
|
89
|
+
}
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function setShellCollapseTarget(target) {
|
|
94
|
+
const shell = shellElement();
|
|
95
|
+
if (!shell) return;
|
|
96
|
+
shell.classList.toggle("rail-collapse-target", target === "sidebar");
|
|
97
|
+
shell.classList.toggle("queue-collapse-target", target === "queue");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function normalizeQueueCollapsedShellLayout(shell, layout) {
|
|
101
|
+
const available = shellAvailableWidth(shell);
|
|
102
|
+
const minimumTotal = SHELL_PANE_MIN.rail + SHELL_PANE_MIN.detail;
|
|
103
|
+
if (available < minimumTotal) return null;
|
|
104
|
+
|
|
105
|
+
let rail = Number(layout.rail);
|
|
106
|
+
if (!Number.isFinite(rail)) rail = SHELL_PANE_MIN.rail;
|
|
107
|
+
rail = clamp(rail, SHELL_PANE_MIN.rail, available - SHELL_PANE_MIN.detail);
|
|
108
|
+
const detail = available - rail;
|
|
109
|
+
|
|
110
|
+
return { rail: Math.round(rail), detail: Math.round(detail), available };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function normalizeDetailOnlyShellLayout(shell, layout) {
|
|
114
|
+
const available = shellAvailableWidth(shell);
|
|
115
|
+
if (available < SHELL_PANE_MIN.detail) return null;
|
|
116
|
+
|
|
117
|
+
const saved = readShellLayout();
|
|
118
|
+
const rail = Number.isFinite(Number(layout.rail)) ? Number(layout.rail) : saved?.rail || SHELL_PANE_MIN.rail;
|
|
119
|
+
const detail = Number.isFinite(Number(layout.detail)) ? Number(layout.detail) : saved?.detail || SHELL_PANE_MIN.detail;
|
|
120
|
+
return { rail: Math.round(rail), detail: Math.round(detail), available };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function normalizeShellLayout(shell, layout) {
|
|
124
|
+
const available = shellAvailableWidth(shell);
|
|
125
|
+
const minimumTotal = SHELL_PANE_MIN.rail + SHELL_PANE_MIN.detail + SHELL_PANE_MIN.queue;
|
|
126
|
+
if (available < minimumTotal) return null;
|
|
127
|
+
|
|
128
|
+
let rail = Number(layout.rail);
|
|
129
|
+
let detail = Number(layout.detail);
|
|
130
|
+
if (!Number.isFinite(rail)) rail = SHELL_PANE_MIN.rail;
|
|
131
|
+
if (!Number.isFinite(detail)) detail = SHELL_PANE_MIN.detail;
|
|
132
|
+
|
|
133
|
+
rail = clamp(rail, SHELL_PANE_MIN.rail, available - SHELL_PANE_MIN.detail - SHELL_PANE_MIN.queue);
|
|
134
|
+
detail = clamp(detail, SHELL_PANE_MIN.detail, available - rail - SHELL_PANE_MIN.queue);
|
|
135
|
+
|
|
136
|
+
return { rail: Math.round(rail), detail: Math.round(detail), available };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function syncShellSplitterValues(layout) {
|
|
140
|
+
const left = document.querySelector("[data-splitter='left']");
|
|
141
|
+
const right = document.querySelector("[data-splitter='right']");
|
|
142
|
+
if (left) {
|
|
143
|
+
left.setAttribute("aria-hidden", String(state.sidebarCollapsed));
|
|
144
|
+
left.setAttribute("aria-valuemin", String(SHELL_PANE_MIN.rail));
|
|
145
|
+
left.setAttribute("aria-valuenow", String(Math.round(layout.rail)));
|
|
146
|
+
left.tabIndex = state.sidebarCollapsed ? -1 : 0;
|
|
147
|
+
}
|
|
148
|
+
if (right) {
|
|
149
|
+
right.setAttribute("aria-hidden", String(state.queueCollapsed));
|
|
150
|
+
right.setAttribute("aria-valuemin", String(SHELL_PANE_MIN.detail));
|
|
151
|
+
right.setAttribute("aria-valuenow", String(Math.round(layout.detail)));
|
|
152
|
+
right.tabIndex = state.queueCollapsed ? -1 : 0;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function applyShellLayout(layout, persist = false) {
|
|
157
|
+
const shell = shellElement();
|
|
158
|
+
if (!shell) return null;
|
|
159
|
+
if (!isDesktopShellLayout()) {
|
|
160
|
+
shell.classList.remove("rail-collapsed");
|
|
161
|
+
shell.classList.remove("queue-collapsed");
|
|
162
|
+
clearShellPeek();
|
|
163
|
+
shell.style.gridTemplateColumns = "";
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
shell.classList.toggle("rail-collapsed", state.sidebarCollapsed);
|
|
167
|
+
shell.classList.toggle("queue-collapsed", state.queueCollapsed);
|
|
168
|
+
if (state.sidebarCollapsed && state.queueCollapsed) {
|
|
169
|
+
const detailOnly = normalizeDetailOnlyShellLayout(shell, layout);
|
|
170
|
+
if (!detailOnly) {
|
|
171
|
+
shell.style.gridTemplateColumns = "";
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
shell.style.gridTemplateColumns = "minmax(" + SHELL_PANE_MIN.detail + "px, 1fr)";
|
|
175
|
+
syncShellSplitterValues(detailOnly);
|
|
176
|
+
if (persist) writeShellLayout(detailOnly);
|
|
177
|
+
return detailOnly;
|
|
178
|
+
}
|
|
179
|
+
if (state.sidebarCollapsed) {
|
|
180
|
+
const collapsed = normalizeSidebarCollapsedShellLayout(shell, layout);
|
|
181
|
+
if (!collapsed) {
|
|
182
|
+
shell.style.gridTemplateColumns = "";
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
shell.style.gridTemplateColumns = collapsed.detail + "px " + SHELL_SPLITTER_WIDTH + "px minmax(" + SHELL_PANE_MIN.queue + "px, 1fr)";
|
|
186
|
+
syncShellSplitterValues(collapsed);
|
|
187
|
+
if (persist) writeShellLayout(collapsed);
|
|
188
|
+
return collapsed;
|
|
189
|
+
}
|
|
190
|
+
if (state.queueCollapsed) {
|
|
191
|
+
const collapsed = normalizeQueueCollapsedShellLayout(shell, layout);
|
|
192
|
+
if (!collapsed) {
|
|
193
|
+
shell.style.gridTemplateColumns = "";
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
shell.style.gridTemplateColumns = collapsed.rail + "px " + SHELL_SPLITTER_WIDTH + "px minmax(" + SHELL_PANE_MIN.detail + "px, 1fr)";
|
|
197
|
+
syncShellSplitterValues(collapsed);
|
|
198
|
+
if (persist) writeShellLayout(collapsed);
|
|
199
|
+
return collapsed;
|
|
200
|
+
}
|
|
201
|
+
const normalized = normalizeShellLayout(shell, layout);
|
|
202
|
+
if (!normalized) {
|
|
203
|
+
shell.style.gridTemplateColumns = "";
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
shell.style.gridTemplateColumns = normalized.rail + "px " + SHELL_SPLITTER_WIDTH + "px " + normalized.detail + "px " + SHELL_SPLITTER_WIDTH + "px minmax(" + SHELL_PANE_MIN.queue + "px, 1fr)";
|
|
207
|
+
syncShellSplitterValues(normalized);
|
|
208
|
+
if (persist) writeShellLayout(normalized);
|
|
209
|
+
return normalized;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function applySavedShellLayout() {
|
|
213
|
+
const shell = shellElement();
|
|
214
|
+
if (!shell) return;
|
|
215
|
+
if (!isDesktopShellLayout()) {
|
|
216
|
+
shell.classList.remove("rail-collapsed");
|
|
217
|
+
shell.classList.remove("queue-collapsed");
|
|
218
|
+
clearShellPeek();
|
|
219
|
+
shell.style.gridTemplateColumns = "";
|
|
220
|
+
renderSidebarToggle();
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
shell.classList.toggle("rail-collapsed", state.sidebarCollapsed);
|
|
224
|
+
shell.classList.toggle("queue-collapsed", state.queueCollapsed);
|
|
225
|
+
const saved = readShellLayout();
|
|
226
|
+
if (saved) {
|
|
227
|
+
applyShellLayout(saved);
|
|
228
|
+
} else {
|
|
229
|
+
shell.style.gridTemplateColumns = "";
|
|
230
|
+
syncShellSplitterValues(currentShellLayout());
|
|
231
|
+
}
|
|
232
|
+
renderSidebarToggle();
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function resetShellLayout() {
|
|
236
|
+
const shell = shellElement();
|
|
237
|
+
try {
|
|
238
|
+
localStorage.removeItem(SHELL_LAYOUT_KEY);
|
|
239
|
+
} catch (_error) {}
|
|
240
|
+
if (shell) shell.style.gridTemplateColumns = "";
|
|
241
|
+
syncShellSplitterValues(currentShellLayout());
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function setSidebarCollapsed(collapsed) {
|
|
245
|
+
state.sidebarCollapsed = Boolean(collapsed);
|
|
246
|
+
try {
|
|
247
|
+
localStorage.setItem(SHELL_SIDEBAR_KEY, state.sidebarCollapsed ? "true" : "false");
|
|
248
|
+
} catch (_error) {}
|
|
249
|
+
if (!state.sidebarCollapsed) setShellPeek("rail", false);
|
|
250
|
+
applySavedShellLayout();
|
|
251
|
+
renderSidebarToggle();
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function setQueueCollapsed(collapsed) {
|
|
255
|
+
state.queueCollapsed = Boolean(collapsed);
|
|
256
|
+
try {
|
|
257
|
+
localStorage.setItem(SHELL_QUEUE_KEY, state.queueCollapsed ? "true" : "false");
|
|
258
|
+
} catch (_error) {}
|
|
259
|
+
if (!state.queueCollapsed) setShellPeek("queue", false);
|
|
260
|
+
applySavedShellLayout();
|
|
261
|
+
renderSidebarToggle();
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function renderSidebarToggle() {
|
|
265
|
+
const button = el("sidebar-toggle");
|
|
266
|
+
const queueButton = el("queue-toggle");
|
|
267
|
+
const desktop = isDesktopShellLayout();
|
|
268
|
+
if (button) {
|
|
269
|
+
const effectiveCollapsed = state.sidebarCollapsed && desktop;
|
|
270
|
+
const label = t(effectiveCollapsed ? "action.showSidebar" : "action.hideSidebar");
|
|
271
|
+
button.classList.toggle("active", effectiveCollapsed);
|
|
272
|
+
button.setAttribute("aria-label", label);
|
|
273
|
+
button.setAttribute("aria-pressed", String(effectiveCollapsed));
|
|
274
|
+
button.title = label;
|
|
275
|
+
}
|
|
276
|
+
if (queueButton) {
|
|
277
|
+
const effectiveCollapsed = state.queueCollapsed && desktop;
|
|
278
|
+
const label = t(effectiveCollapsed ? "action.showQueue" : "action.hideQueue");
|
|
279
|
+
queueButton.classList.toggle("active", effectiveCollapsed);
|
|
280
|
+
queueButton.setAttribute("aria-label", label);
|
|
281
|
+
queueButton.setAttribute("aria-pressed", String(effectiveCollapsed));
|
|
282
|
+
queueButton.title = label;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function nextShellLayout(side, start, delta) {
|
|
287
|
+
if (side === "left") {
|
|
288
|
+
return { rail: start.rail + delta, detail: start.detail - delta };
|
|
289
|
+
}
|
|
290
|
+
return { rail: start.rail, detail: start.detail + delta };
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function nudgeShellLayout(side, delta) {
|
|
294
|
+
applyShellLayout(nextShellLayout(side, currentShellLayout(), delta), true);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function setShellPeek(side, active) {
|
|
298
|
+
const shell = shellElement();
|
|
299
|
+
if (!shell) return;
|
|
300
|
+
const enabled = Boolean(active) && isDesktopShellLayout();
|
|
301
|
+
if (side === "rail") shell.classList.toggle("rail-peeking", enabled && state.sidebarCollapsed);
|
|
302
|
+
if (side === "queue") shell.classList.toggle("queue-peeking", enabled && state.queueCollapsed);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function clearShellPeek() {
|
|
306
|
+
const shell = shellElement();
|
|
307
|
+
if (!shell) return;
|
|
308
|
+
shell.classList.remove("rail-peeking");
|
|
309
|
+
shell.classList.remove("queue-peeking");
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function updateShellPeekFromPointer(event) {
|
|
313
|
+
const shell = shellElement();
|
|
314
|
+
if (!shell || !isDesktopShellLayout()) {
|
|
315
|
+
clearShellPeek();
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
const rect = shell.getBoundingClientRect();
|
|
319
|
+
const insideY = event.clientY >= rect.top && event.clientY <= rect.bottom;
|
|
320
|
+
if (!insideY) {
|
|
321
|
+
clearShellPeek();
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
if (state.sidebarCollapsed) {
|
|
325
|
+
const leftDistance = event.clientX - rect.left;
|
|
326
|
+
const leftLimit = shell.classList.contains("rail-peeking") ? SHELL_PEEK_WIDTH.rail : SHELL_REVEAL_EDGE_WIDTH;
|
|
327
|
+
setShellPeek("rail", leftDistance >= 0 && leftDistance <= leftLimit);
|
|
328
|
+
} else {
|
|
329
|
+
setShellPeek("rail", false);
|
|
330
|
+
}
|
|
331
|
+
if (state.queueCollapsed) {
|
|
332
|
+
const rightDistance = rect.right - event.clientX;
|
|
333
|
+
const rightLimit = shell.classList.contains("queue-peeking") ? SHELL_PEEK_WIDTH.queue : SHELL_REVEAL_EDGE_WIDTH;
|
|
334
|
+
setShellPeek("queue", rightDistance >= 0 && rightDistance <= rightLimit);
|
|
335
|
+
} else {
|
|
336
|
+
setShellPeek("queue", false);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function initShellResizers() {
|
|
341
|
+
const splitters = document.querySelectorAll("[data-splitter]");
|
|
342
|
+
if (!splitters.length) return;
|
|
343
|
+
applySavedShellLayout();
|
|
344
|
+
|
|
345
|
+
splitters.forEach((splitter) => {
|
|
346
|
+
splitter.addEventListener("pointerdown", (event) => {
|
|
347
|
+
if (!isDesktopShellLayout()) return;
|
|
348
|
+
if (splitter.dataset.splitter === "left" && state.sidebarCollapsed) return;
|
|
349
|
+
if (splitter.dataset.splitter === "right" && state.queueCollapsed) return;
|
|
350
|
+
event.preventDefault();
|
|
351
|
+
const side = splitter.dataset.splitter;
|
|
352
|
+
const start = currentShellLayout();
|
|
353
|
+
const startX = event.clientX;
|
|
354
|
+
let lastDelta = 0;
|
|
355
|
+
|
|
356
|
+
splitter.classList.add("dragging");
|
|
357
|
+
document.body.classList.add("resizing-panes");
|
|
358
|
+
if (splitter.setPointerCapture) splitter.setPointerCapture(event.pointerId);
|
|
359
|
+
|
|
360
|
+
const onMove = (moveEvent) => {
|
|
361
|
+
lastDelta = moveEvent.clientX - startX;
|
|
362
|
+
const requested = nextShellLayout(side, start, lastDelta);
|
|
363
|
+
setShellCollapseTarget(collapseTargetForLayout(side, requested));
|
|
364
|
+
applyShellLayout(requested);
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
const stop = () => {
|
|
368
|
+
window.removeEventListener("pointermove", onMove);
|
|
369
|
+
window.removeEventListener("pointerup", stop);
|
|
370
|
+
window.removeEventListener("pointercancel", stop);
|
|
371
|
+
splitter.classList.remove("dragging");
|
|
372
|
+
document.body.classList.remove("resizing-panes");
|
|
373
|
+
const requested = nextShellLayout(side, start, lastDelta);
|
|
374
|
+
const collapseTarget = collapseTargetForLayout(side, requested);
|
|
375
|
+
setShellCollapseTarget(null);
|
|
376
|
+
if (collapseTarget === "sidebar") {
|
|
377
|
+
setSidebarCollapsed(true);
|
|
378
|
+
} else if (collapseTarget === "queue") {
|
|
379
|
+
setQueueCollapsed(true);
|
|
380
|
+
} else {
|
|
381
|
+
applyShellLayout(currentShellLayout(), true);
|
|
382
|
+
}
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
window.addEventListener("pointermove", onMove);
|
|
386
|
+
window.addEventListener("pointerup", stop);
|
|
387
|
+
window.addEventListener("pointercancel", stop);
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
splitter.addEventListener("keydown", (event) => {
|
|
391
|
+
if (!isDesktopShellLayout() || (event.key !== "ArrowLeft" && event.key !== "ArrowRight")) return;
|
|
392
|
+
if (splitter.dataset.splitter === "left" && state.sidebarCollapsed) return;
|
|
393
|
+
if (splitter.dataset.splitter === "right" && state.queueCollapsed) return;
|
|
394
|
+
event.preventDefault();
|
|
395
|
+
const step = event.shiftKey ? 48 : 16;
|
|
396
|
+
nudgeShellLayout(splitter.dataset.splitter, event.key === "ArrowRight" ? step : -step);
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
splitter.addEventListener("dblclick", resetShellLayout);
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
let resizeFrame = 0;
|
|
403
|
+
window.addEventListener("resize", () => {
|
|
404
|
+
if (resizeFrame) cancelAnimationFrame(resizeFrame);
|
|
405
|
+
resizeFrame = requestAnimationFrame(() => {
|
|
406
|
+
resizeFrame = 0;
|
|
407
|
+
applySavedShellLayout();
|
|
408
|
+
renderSidebarToggle();
|
|
409
|
+
});
|
|
410
|
+
});
|
|
411
|
+
window.addEventListener("mousemove", updateShellPeekFromPointer);
|
|
412
|
+
document.addEventListener("mouseleave", clearShellPeek);
|
|
413
|
+
}
|
|
414
|
+
`;
|