loadtoagent 1.0.0 → 1.3.1
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.ko.md +7 -0
- package/README.md +7 -0
- package/README.zh-CN.md +7 -0
- package/docs/PROVIDER-CONTRACTS.md +23 -1
- package/docs/UI-AUDIT-100.md +118 -0
- package/docs/UI-AUDIT-101-200.md +120 -0
- package/docs/UI-AUDIT-201-300.md +120 -0
- package/main.js +183 -37
- package/package.json +15 -4
- package/preload.js +11 -0
- package/renderer/app-agent-actions.js +125 -53
- package/renderer/app-bootstrap.js +54 -9
- package/renderer/app-dashboard.js +197 -58
- package/renderer/app-drawer-content.js +103 -86
- package/renderer/app-drawer-data.js +26 -13
- package/renderer/app-drawer.js +79 -55
- package/renderer/app-events-dialogs.js +146 -37
- package/renderer/app-events-filters.js +172 -9
- package/renderer/app-events-navigation.js +78 -31
- package/renderer/app-events-sessions.js +171 -4
- package/renderer/app-events.js +2 -1
- package/renderer/app-graph-model.js +32 -4
- package/renderer/app-graph-orchestration.js +16 -13
- package/renderer/app-graph-view.js +228 -112
- package/renderer/app-management.js +211 -0
- package/renderer/app-provider-visibility.js +124 -0
- package/renderer/app-quality.js +568 -0
- package/renderer/app-run-modal.js +103 -33
- package/renderer/app-runtime-overview.js +312 -0
- package/renderer/app-session-render.js +94 -37
- package/renderer/app-tmux-render.js +119 -38
- package/renderer/app.js +155 -76
- package/renderer/i18n-messages.js +833 -15
- package/renderer/i18n.js +104 -0
- package/renderer/index.html +162 -75
- package/renderer/shared.js +17 -0
- package/renderer/styles-agent-map.css +50 -1
- package/renderer/styles-cards.css +26 -0
- package/renderer/styles-collaboration.css +18 -88
- package/renderer/styles-components.css +126 -14
- package/renderer/styles-management.css +355 -0
- package/renderer/styles-overlays.css +13 -2
- package/renderer/styles-product.css +24 -16
- package/renderer/styles-quality.css +312 -0
- package/renderer/styles-readability.css +862 -0
- package/renderer/styles-responsive-product.css +84 -12
- package/renderer/styles-responsive-runtime.css +31 -14
- package/renderer/styles-responsive-shell.css +44 -48
- package/renderer/styles-responsive-workflows.css +37 -17
- package/renderer/styles-run-composer.css +5 -0
- package/renderer/styles-runtime-overview.css +285 -0
- package/renderer/styles-settings.css +160 -0
- package/renderer/styles-terminal.css +339 -2
- package/renderer/styles-tmux.css +154 -0
- package/renderer/styles-workflow-map.css +362 -15
- package/renderer/styles-workflows.css +69 -2
- package/renderer/styles.css +27 -12
- package/renderer/terminal-agent.js +17 -13
- package/renderer/terminal-events.js +233 -40
- package/renderer/terminal-workbench.js +176 -78
- package/renderer/terminal.js +350 -37
- package/src/agentMonitor/claudeParser.js +96 -7
- package/src/agentMonitor/codexParser.js +59 -4
- package/src/agentMonitor/executionActivity.js +282 -0
- package/src/agentMonitor/genericParser.js +56 -13
- package/src/agentMonitor/hierarchy.js +17 -0
- package/src/agentMonitor/responseIntent.js +51 -0
- package/src/agentMonitor.js +21 -3
- package/src/agentRunner.js +66 -1
- package/src/attentionNotifier.js +71 -0
- package/src/automationMonitor.js +258 -0
- package/src/contracts.js +10 -1
- package/src/ipc/registerAgentIpc.js +9 -3
- package/src/ipc/registerAppIpc.js +4 -1
- package/src/ipc/registerTerminalIpc.js +12 -6
- package/src/macUpdateHelper.js +210 -0
- package/src/monitorWorker.js +65 -6
- package/src/platformPath.js +80 -0
- package/src/processMonitor.js +81 -23
- package/src/providerVisibilityStore.js +45 -0
- package/src/sessionIntelligence.js +247 -0
- package/src/terminalHost.js +381 -0
- package/src/terminalHostDaemon.js +60 -0
- package/src/terminalManager.js +158 -3
- package/src/tmuxMonitor.js +2 -2
- package/src/updateInstaller.js +175 -0
|
@@ -0,0 +1,568 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
window.LoadToAgentAppFactories = window.LoadToAgentAppFactories || {};
|
|
4
|
+
|
|
5
|
+
window.LoadToAgentAppFactories.createQualityEnhancements = function createQualityEnhancements(context = {}) {
|
|
6
|
+
const t = (key, params) => window.LoadToAgentI18n.t(key, params);
|
|
7
|
+
const {
|
|
8
|
+
$, state, rememberDialogTrigger, restoreDialogTrigger, setDialogOpenState, currentDialog,
|
|
9
|
+
announce, selectView, openRunModal, performUiAction,
|
|
10
|
+
} = context;
|
|
11
|
+
const DASHBOARD_STORAGE_KEY = "loadtoagent:dashboard-preferences:v2";
|
|
12
|
+
const RUN_DRAFT_STORAGE_KEY = "loadtoagent:run-draft:v2";
|
|
13
|
+
const QUALITY_PREF_STORAGE_KEY = "loadtoagent:quality-preferences:v3";
|
|
14
|
+
const DASHBOARD_VERSION = 2;
|
|
15
|
+
const RUN_DRAFT_VERSION = 2;
|
|
16
|
+
const QUALITY_PREF_VERSION = 3;
|
|
17
|
+
const MAX_QUALITY_TEXT = 180;
|
|
18
|
+
let activeCommandIndex = 0;
|
|
19
|
+
let visibleCommands = [];
|
|
20
|
+
let quickDialogGeneration = 0;
|
|
21
|
+
let shortcutDialogGeneration = 0;
|
|
22
|
+
let qualityMutationFrame = 0;
|
|
23
|
+
let qualityGuardsInstalled = false;
|
|
24
|
+
|
|
25
|
+
function safeParse(storage, key) {
|
|
26
|
+
try {
|
|
27
|
+
const value = JSON.parse(storage.getItem(key) || "null");
|
|
28
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
29
|
+
} catch (error) {
|
|
30
|
+
window.LoadToAgentRendererUtils.reportRecoverableError(`quality-storage-${key}`, error);
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function normalizedSearch(value) {
|
|
36
|
+
return String(value || "").slice(0, 240).replace(/\s+/g, " ").trim();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function qualityText(value, limit = MAX_QUALITY_TEXT) {
|
|
40
|
+
return String(value || "").replace(/\s+/g, " ").trim().slice(0, limit);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function defaultQualityPreferences() {
|
|
44
|
+
return {
|
|
45
|
+
version: QUALITY_PREF_VERSION,
|
|
46
|
+
inputModality: "pointer",
|
|
47
|
+
reduceMotion: false,
|
|
48
|
+
compactDensity: false,
|
|
49
|
+
advancedRunOpen: false,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function loadQualityPreferences() {
|
|
54
|
+
const saved = safeParse(localStorage, QUALITY_PREF_STORAGE_KEY);
|
|
55
|
+
const defaults = defaultQualityPreferences();
|
|
56
|
+
state.qualityPreferences = saved?.version === QUALITY_PREF_VERSION ? {
|
|
57
|
+
...defaults,
|
|
58
|
+
inputModality: saved.inputModality === "keyboard" ? "keyboard" : "pointer",
|
|
59
|
+
reduceMotion: saved.reduceMotion === true,
|
|
60
|
+
compactDensity: saved.compactDensity === true,
|
|
61
|
+
advancedRunOpen: saved.advancedRunOpen === true,
|
|
62
|
+
} : defaults;
|
|
63
|
+
applyQualityPreferences();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function saveQualityPreferences() {
|
|
67
|
+
const value = { ...defaultQualityPreferences(), ...(state.qualityPreferences || {}) };
|
|
68
|
+
value.version = QUALITY_PREF_VERSION;
|
|
69
|
+
try {
|
|
70
|
+
localStorage.setItem(QUALITY_PREF_STORAGE_KEY, JSON.stringify(value));
|
|
71
|
+
} catch (error) {
|
|
72
|
+
window.LoadToAgentRendererUtils.reportRecoverableError("quality-preferences-save", error);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function applyQualityPreferences() {
|
|
77
|
+
const preferences = state.qualityPreferences || defaultQualityPreferences();
|
|
78
|
+
document.body.dataset.inputModality = preferences.inputModality === "keyboard" ? "keyboard" : "pointer";
|
|
79
|
+
document.body.dataset.qualityMotion = preferences.reduceMotion ? "reduced" : "standard";
|
|
80
|
+
document.body.dataset.qualityDensity = preferences.compactDensity ? "compact" : "comfortable";
|
|
81
|
+
document.body.classList.toggle("quality-keyboard-mode", preferences.inputModality === "keyboard");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function loadQualityState() {
|
|
85
|
+
const dashboard = safeParse(localStorage, DASHBOARD_STORAGE_KEY);
|
|
86
|
+
if (dashboard?.version === DASHBOARD_VERSION) {
|
|
87
|
+
state.search = normalizedSearch(dashboard.search);
|
|
88
|
+
state.providerFilters = new Set(
|
|
89
|
+
Array.isArray(dashboard.providers)
|
|
90
|
+
? dashboard.providers.filter((id) => typeof id === "string" && /^[a-z0-9_-]{1,40}$/i.test(id)).slice(0, 20)
|
|
91
|
+
: [],
|
|
92
|
+
);
|
|
93
|
+
state.workspace = typeof dashboard.workspace === "string" && dashboard.workspace.length <= 2_000
|
|
94
|
+
? dashboard.workspace
|
|
95
|
+
: "all";
|
|
96
|
+
state.sort = ["recent", "tokens", "context"].includes(dashboard.sort) ? dashboard.sort : "recent";
|
|
97
|
+
} else {
|
|
98
|
+
state.search = "";
|
|
99
|
+
state.providerFilters.clear();
|
|
100
|
+
state.workspace = "all";
|
|
101
|
+
state.sort = "recent";
|
|
102
|
+
}
|
|
103
|
+
const search = $("#searchInput");
|
|
104
|
+
if (search) search.value = state.search;
|
|
105
|
+
const sort = $("#sortSelect");
|
|
106
|
+
if (sort) sort.value = state.sort;
|
|
107
|
+
loadQualityPreferences();
|
|
108
|
+
|
|
109
|
+
const draft = safeParse(sessionStorage, RUN_DRAFT_STORAGE_KEY);
|
|
110
|
+
if (draft?.version === RUN_DRAFT_VERSION) {
|
|
111
|
+
state.runDraft = {
|
|
112
|
+
prompt: typeof draft.prompt === "string" ? draft.prompt.slice(0, 8_000) : "",
|
|
113
|
+
cwd: typeof draft.cwd === "string" ? draft.cwd.slice(0, 2_000) : "",
|
|
114
|
+
model: typeof draft.model === "string" ? draft.model.slice(0, 160) : "",
|
|
115
|
+
allowWrites: draft.allowWrites === true,
|
|
116
|
+
provider: typeof draft.provider === "string" && /^[a-z0-9_-]{1,40}$/i.test(draft.provider) ? draft.provider : "",
|
|
117
|
+
};
|
|
118
|
+
if (state.runDraft.provider) state.runProvider = state.runDraft.provider;
|
|
119
|
+
} else state.runDraft = { prompt: "", cwd: "", model: "", allowWrites: false, provider: "" };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function saveDashboardPreferences() {
|
|
123
|
+
const value = {
|
|
124
|
+
version: DASHBOARD_VERSION,
|
|
125
|
+
search: normalizedSearch(state.search),
|
|
126
|
+
providers: [...state.providerFilters],
|
|
127
|
+
workspace: String(state.workspace || "all").slice(0, 2_000),
|
|
128
|
+
sort: ["recent", "tokens", "context"].includes(state.sort) ? state.sort : "recent",
|
|
129
|
+
};
|
|
130
|
+
try {
|
|
131
|
+
localStorage.setItem(DASHBOARD_STORAGE_KEY, JSON.stringify(value));
|
|
132
|
+
} catch (error) {
|
|
133
|
+
window.LoadToAgentRendererUtils.reportRecoverableError("dashboard-preferences-save", error);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function currentRunDraft() {
|
|
138
|
+
return {
|
|
139
|
+
version: RUN_DRAFT_VERSION,
|
|
140
|
+
prompt: $("#runPrompt")?.value.slice(0, 8_000) || "",
|
|
141
|
+
cwd: $("#runCwd")?.value.slice(0, 2_000) || "",
|
|
142
|
+
model: $("#runModel")?.value.slice(0, 160) || "",
|
|
143
|
+
allowWrites: Boolean($("#allowWrites")?.checked),
|
|
144
|
+
provider: String(state.runProvider || "").slice(0, 40),
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function saveRunDraft() {
|
|
149
|
+
const draft = currentRunDraft();
|
|
150
|
+
state.runDraft = { ...draft };
|
|
151
|
+
try {
|
|
152
|
+
sessionStorage.setItem(RUN_DRAFT_STORAGE_KEY, JSON.stringify(draft));
|
|
153
|
+
} catch (error) {
|
|
154
|
+
window.LoadToAgentRendererUtils.reportRecoverableError("run-draft-save", error);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function restoreRunDraft() {
|
|
159
|
+
const draft = state.runDraft || {};
|
|
160
|
+
if ($("#runPrompt") && !$("#runPrompt").value) $("#runPrompt").value = draft.prompt || "";
|
|
161
|
+
if ($("#runCwd") && !$("#runCwd").value) $("#runCwd").value = draft.cwd || "";
|
|
162
|
+
if ($("#runModel") && !$("#runModel").value) $("#runModel").value = draft.model || "";
|
|
163
|
+
if ($("#allowWrites")) $("#allowWrites").checked = Boolean(draft.allowWrites);
|
|
164
|
+
if (draft.provider) state.runProvider = draft.provider;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function clearRunDraft(options = {}) {
|
|
168
|
+
state.runDraft = { prompt: "", cwd: "", model: "", allowWrites: false, provider: "" };
|
|
169
|
+
try {
|
|
170
|
+
sessionStorage.removeItem(RUN_DRAFT_STORAGE_KEY);
|
|
171
|
+
} catch (error) {
|
|
172
|
+
window.LoadToAgentRendererUtils.reportRecoverableError("run-draft-clear", error);
|
|
173
|
+
}
|
|
174
|
+
if ($("#runPrompt")) $("#runPrompt").value = "";
|
|
175
|
+
if ($("#runCwd")) $("#runCwd").value = "";
|
|
176
|
+
if ($("#runModel")) $("#runModel").value = "";
|
|
177
|
+
if ($("#allowWrites")) $("#allowWrites").checked = false;
|
|
178
|
+
$("#runForm")?.querySelectorAll('[aria-invalid="true"]').forEach((element) => element.removeAttribute("aria-invalid"));
|
|
179
|
+
$("#runError")?.classList.add("hidden");
|
|
180
|
+
context.syncRunComposer?.();
|
|
181
|
+
if (options.focus !== false) $("#runPrompt")?.focus();
|
|
182
|
+
if (options.silent !== true) announce(t("quality.draft_cleared"));
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function commandDefinitions() {
|
|
186
|
+
return [
|
|
187
|
+
["all", "⌂", t("app.nav.home"), t("quality.command.view"), () => selectView("all", { focusMain: true })],
|
|
188
|
+
["active", "●", t("app.nav.active"), t("quality.command.view"), () => selectView("active", { focusMain: true })],
|
|
189
|
+
["waiting", "!", t("app.nav.needs_review"), t("quality.command.view"), () => selectView("waiting", { focusMain: true })],
|
|
190
|
+
["runtime", "↻", t("app.nav.runtime"), t("quality.command.view"), () => selectView("runtime", { focusMain: true })],
|
|
191
|
+
["terminal", ">_", t("app.nav.session_terminal"), t("quality.command.view"), () => selectView("terminal", { focusMain: true })],
|
|
192
|
+
["tmux", "▦", t("app.nav.tmux"), t("quality.command.view"), () => selectView("tmux", { focusMain: true })],
|
|
193
|
+
["settings", "⚙", t("app.nav.settings"), t("quality.command.view"), () => selectView("settings", { focusMain: true })],
|
|
194
|
+
["new-task", "+", t("ui.new_ai_task"), t("quality.command.action"), () => openRunModal()],
|
|
195
|
+
["probe", "↻", t("ui.check_ai_connections_again"), t("quality.command.action"), () => $("#probeBtn")?.click()],
|
|
196
|
+
["workspace", "⌘", t("ui.add_workspace"), t("quality.command.action"), () => $("#addWorkspaceBtn")?.click()],
|
|
197
|
+
["shortcuts", "?", t("quality.shortcuts.title"), t("quality.command.help"), () => openShortcutHelp()],
|
|
198
|
+
].map(([id, icon, label, group, run]) => ({ id, icon, label, group, run }));
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function renderQuickCommands() {
|
|
202
|
+
const input = $("#quickPaletteInput");
|
|
203
|
+
const query = String(input?.value || "").replace(/\s+/g, " ").trim().toLocaleLowerCase();
|
|
204
|
+
visibleCommands = commandDefinitions().filter((command) => `${command.label} ${command.group}`.toLocaleLowerCase().includes(query));
|
|
205
|
+
activeCommandIndex = Math.max(0, Math.min(activeCommandIndex, visibleCommands.length - 1));
|
|
206
|
+
const list = $("#quickPaletteList");
|
|
207
|
+
list.innerHTML = visibleCommands.map((command, index) => `<button id="quick-command-${command.id}" type="button" role="option" aria-selected="${index === activeCommandIndex ? "true" : "false"}" tabindex="-1" data-quick-command="${command.id}" class="${index === activeCommandIndex ? "active" : ""}"><span aria-hidden="true">${command.icon}</span><b>${context.esc(command.label)}</b><small>${context.esc(command.group)}</small><i aria-hidden="true">↵</i></button>`).join("");
|
|
208
|
+
const active = visibleCommands[activeCommandIndex];
|
|
209
|
+
if (active) input?.setAttribute("aria-activedescendant", `quick-command-${active.id}`);
|
|
210
|
+
else input?.removeAttribute("aria-activedescendant");
|
|
211
|
+
const status = visibleCommands.length
|
|
212
|
+
? t("quality.quick_results", { count: visibleCommands.length })
|
|
213
|
+
: t("quality.quick_empty");
|
|
214
|
+
$("#quickPaletteStatus").textContent = status;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function openQuickPalette() {
|
|
218
|
+
if (currentDialog?.()) return;
|
|
219
|
+
rememberDialogTrigger();
|
|
220
|
+
quickDialogGeneration = context.motionState?.dialogGeneration || 0;
|
|
221
|
+
const modal = $("#quickPaletteModal");
|
|
222
|
+
setDialogOpenState(modal, true);
|
|
223
|
+
modal.classList.remove("hidden");
|
|
224
|
+
$("#quickPaletteInput").value = "";
|
|
225
|
+
activeCommandIndex = 0;
|
|
226
|
+
renderQuickCommands();
|
|
227
|
+
requestAnimationFrame(() => $("#quickPaletteInput")?.focus());
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function closeQuickPalette() {
|
|
231
|
+
const modal = $("#quickPaletteModal");
|
|
232
|
+
if (modal.classList.contains("hidden")) return;
|
|
233
|
+
modal.classList.add("hidden");
|
|
234
|
+
setDialogOpenState(modal, false);
|
|
235
|
+
restoreDialogTrigger(quickDialogGeneration);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function executeQuickCommand(id) {
|
|
239
|
+
const command = visibleCommands.find((item) => item.id === id) || commandDefinitions().find((item) => item.id === id);
|
|
240
|
+
if (!command) return;
|
|
241
|
+
closeQuickPalette();
|
|
242
|
+
command.run();
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function openShortcutHelp() {
|
|
246
|
+
if (currentDialog?.()) return;
|
|
247
|
+
rememberDialogTrigger();
|
|
248
|
+
shortcutDialogGeneration = context.motionState?.dialogGeneration || 0;
|
|
249
|
+
const modal = $("#shortcutHelpModal");
|
|
250
|
+
setDialogOpenState(modal, true);
|
|
251
|
+
modal.classList.remove("hidden");
|
|
252
|
+
requestAnimationFrame(() => $("#closeShortcutHelpBtn")?.focus());
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function closeShortcutHelp() {
|
|
256
|
+
const modal = $("#shortcutHelpModal");
|
|
257
|
+
if (modal.classList.contains("hidden")) return;
|
|
258
|
+
modal.classList.add("hidden");
|
|
259
|
+
setDialogOpenState(modal, false);
|
|
260
|
+
restoreDialogTrigger(shortcutDialogGeneration);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
async function copyText(value) {
|
|
264
|
+
const text = String(value || "");
|
|
265
|
+
if (!text) return false;
|
|
266
|
+
try {
|
|
267
|
+
if (window.loadtoagent?.writeClipboard) await window.loadtoagent.writeClipboard(text);
|
|
268
|
+
else if (navigator.clipboard?.writeText) await navigator.clipboard.writeText(text);
|
|
269
|
+
else {
|
|
270
|
+
const area = document.createElement("textarea");
|
|
271
|
+
area.value = text;
|
|
272
|
+
area.setAttribute("readonly", "");
|
|
273
|
+
area.className = "clipboard-fallback";
|
|
274
|
+
document.body.append(area);
|
|
275
|
+
area.select();
|
|
276
|
+
const copied = document.execCommand("copy");
|
|
277
|
+
area.remove();
|
|
278
|
+
if (!copied) throw new Error("copy unavailable");
|
|
279
|
+
}
|
|
280
|
+
announce(t("quality.copy_success"));
|
|
281
|
+
context.toast?.(t("quality.copy_success"));
|
|
282
|
+
return true;
|
|
283
|
+
} catch (error) {
|
|
284
|
+
window.LoadToAgentRendererUtils.reportRecoverableError("clipboard-copy", error);
|
|
285
|
+
announce(t("quality.copy_failed"));
|
|
286
|
+
context.toast?.(t("quality.copy_failed"));
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function safeBackdrop(backdrop, close, separateSurface = null) {
|
|
292
|
+
const pointerRoot = separateSurface ? document : backdrop;
|
|
293
|
+
let press = null;
|
|
294
|
+
let releaseTimer = 0;
|
|
295
|
+
const updateMovement = (event) => {
|
|
296
|
+
if (!press || press.pointerId !== event.pointerId) return;
|
|
297
|
+
if (Math.hypot(event.clientX - press.x, event.clientY - press.y) > 6) press.moved = true;
|
|
298
|
+
};
|
|
299
|
+
pointerRoot.addEventListener("pointerdown", (event) => {
|
|
300
|
+
if (event.button !== 0 || (separateSurface && backdrop.classList.contains("hidden"))) {
|
|
301
|
+
press = null;
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
clearTimeout(releaseTimer);
|
|
305
|
+
press = {
|
|
306
|
+
pointerId: event.pointerId,
|
|
307
|
+
startedOnBackdrop: event.target === backdrop,
|
|
308
|
+
x: event.clientX,
|
|
309
|
+
y: event.clientY,
|
|
310
|
+
moved: false,
|
|
311
|
+
};
|
|
312
|
+
}, Boolean(separateSurface));
|
|
313
|
+
pointerRoot.addEventListener("pointermove", updateMovement, Boolean(separateSurface));
|
|
314
|
+
pointerRoot.addEventListener("pointerup", (event) => {
|
|
315
|
+
updateMovement(event);
|
|
316
|
+
clearTimeout(releaseTimer);
|
|
317
|
+
// Keep the press through the following click event, then discard it if
|
|
318
|
+
// the pointerup did not produce a click on the backdrop.
|
|
319
|
+
releaseTimer = setTimeout(() => { press = null; }, 0);
|
|
320
|
+
}, Boolean(separateSurface));
|
|
321
|
+
pointerRoot.addEventListener("pointercancel", () => {
|
|
322
|
+
clearTimeout(releaseTimer);
|
|
323
|
+
press = null;
|
|
324
|
+
}, Boolean(separateSurface));
|
|
325
|
+
backdrop.addEventListener("click", (event) => {
|
|
326
|
+
if (event.target !== backdrop) return;
|
|
327
|
+
const directActivation = !press && event.detail === 0;
|
|
328
|
+
const safePointerActivation = Boolean(press && press.startedOnBackdrop && !press.moved);
|
|
329
|
+
clearTimeout(releaseTimer);
|
|
330
|
+
press = null;
|
|
331
|
+
if (directActivation || safePointerActivation) close();
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function markInputModality(mode) {
|
|
336
|
+
const preferences = { ...defaultQualityPreferences(), ...(state.qualityPreferences || {}) };
|
|
337
|
+
preferences.inputModality = mode === "keyboard" ? "keyboard" : "pointer";
|
|
338
|
+
state.qualityPreferences = preferences;
|
|
339
|
+
applyQualityPreferences();
|
|
340
|
+
saveQualityPreferences();
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function describeControl(control) {
|
|
344
|
+
return qualityText(
|
|
345
|
+
control.getAttribute("aria-label")
|
|
346
|
+
|| control.getAttribute("title")
|
|
347
|
+
|| control.textContent
|
|
348
|
+
|| control.getAttribute("data-i18n")
|
|
349
|
+
|| control.id
|
|
350
|
+
|| control.name,
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function enhanceControl(control) {
|
|
355
|
+
if (!(control instanceof HTMLElement)) return;
|
|
356
|
+
if (control.dataset.qualityEnhanced === "true") return;
|
|
357
|
+
control.dataset.qualityEnhanced = "true";
|
|
358
|
+
const label = describeControl(control);
|
|
359
|
+
if (label && !control.getAttribute("aria-label") && control.matches(".icon-button, .top-icon-action, .close-button")) {
|
|
360
|
+
control.setAttribute("aria-label", label);
|
|
361
|
+
}
|
|
362
|
+
if (label && !control.getAttribute("title") && control.scrollWidth > control.clientWidth) control.setAttribute("title", label);
|
|
363
|
+
if (control.matches("button") && !control.getAttribute("type")) control.setAttribute("type", "button");
|
|
364
|
+
if (control.matches("button, [role='button'], input, select, textarea")) {
|
|
365
|
+
control.setAttribute("data-quality-control", "");
|
|
366
|
+
if (control.matches("input[required], textarea[required], select[required]")) control.setAttribute("aria-required", "true");
|
|
367
|
+
}
|
|
368
|
+
if (control.matches("button, [role='button']")) {
|
|
369
|
+
const rect = control.getBoundingClientRect?.();
|
|
370
|
+
if (rect && (rect.width < 40 || rect.height < 40)) control.setAttribute("data-quality-touch-target", "padded");
|
|
371
|
+
}
|
|
372
|
+
if (control.matches(":disabled, [aria-disabled='true']")) {
|
|
373
|
+
control.setAttribute("data-quality-disabled", "true");
|
|
374
|
+
if (!control.getAttribute("aria-describedby")) control.setAttribute("data-quality-disabled-reason", t("quality.disabled_reason"));
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function enhanceQualityControls(root = document) {
|
|
379
|
+
root.querySelectorAll?.("button, [role='button'], input, select, textarea, summary, [tabindex]").forEach(enhanceControl);
|
|
380
|
+
document.querySelectorAll?.("[data-quality-disabled='true']:not(:disabled):not([aria-disabled='true'])").forEach((control) => {
|
|
381
|
+
control.removeAttribute("data-quality-disabled");
|
|
382
|
+
control.removeAttribute("data-quality-disabled-reason");
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function installQualityMutationObserver() {
|
|
387
|
+
const observer = new MutationObserver(() => {
|
|
388
|
+
cancelAnimationFrame(qualityMutationFrame);
|
|
389
|
+
qualityMutationFrame = requestAnimationFrame(() => enhanceQualityControls());
|
|
390
|
+
});
|
|
391
|
+
observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ["disabled", "aria-disabled", "class", "title", "aria-label"] });
|
|
392
|
+
return observer;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function installPressedStateMirrors() {
|
|
396
|
+
document.addEventListener("pointerdown", (event) => {
|
|
397
|
+
const control = event.target.closest?.("button, [role='button']");
|
|
398
|
+
if (control) control.setAttribute("data-quality-pressed", "true");
|
|
399
|
+
}, true);
|
|
400
|
+
["pointerup", "pointercancel", "pointerleave"].forEach((type) => {
|
|
401
|
+
document.addEventListener(type, (event) => {
|
|
402
|
+
const control = event.target.closest?.("button, [role='button']");
|
|
403
|
+
if (control) control.removeAttribute("data-quality-pressed");
|
|
404
|
+
}, true);
|
|
405
|
+
});
|
|
406
|
+
document.addEventListener("keydown", (event) => {
|
|
407
|
+
if (![" ", "Enter"].includes(event.key)) return;
|
|
408
|
+
const control = event.target.closest?.("button, [role='button']");
|
|
409
|
+
if (control) control.setAttribute("data-quality-pressed", "true");
|
|
410
|
+
}, true);
|
|
411
|
+
document.addEventListener("keyup", (event) => {
|
|
412
|
+
if (![" ", "Enter"].includes(event.key)) return;
|
|
413
|
+
const control = event.target.closest?.("button, [role='button']");
|
|
414
|
+
if (control) control.removeAttribute("data-quality-pressed");
|
|
415
|
+
}, true);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function installFormRecovery() {
|
|
419
|
+
document.addEventListener("input", (event) => {
|
|
420
|
+
const field = event.target;
|
|
421
|
+
if (!(field instanceof HTMLElement)) return;
|
|
422
|
+
if (field.matches("[aria-invalid='true']") && qualityText(field.value || field.textContent)) field.removeAttribute("aria-invalid");
|
|
423
|
+
const form = field.closest("form");
|
|
424
|
+
const error = form?.querySelector(".form-error:not(.hidden)");
|
|
425
|
+
if (error && qualityText(field.value || field.textContent)) error.classList.add("hidden");
|
|
426
|
+
}, true);
|
|
427
|
+
document.addEventListener("blur", (event) => {
|
|
428
|
+
const field = event.target;
|
|
429
|
+
if (!(field instanceof HTMLInputElement)) return;
|
|
430
|
+
if (field.matches("#runModel, #tmuxCreateName, #tmuxCreateCwd")) field.value = field.value.trim();
|
|
431
|
+
}, true);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function installDetailsStateMemory() {
|
|
435
|
+
const advanced = document.querySelector(".run-advanced");
|
|
436
|
+
if (!advanced) return;
|
|
437
|
+
advanced.open = Boolean(state.qualityPreferences?.advancedRunOpen);
|
|
438
|
+
advanced.addEventListener("toggle", () => {
|
|
439
|
+
state.qualityPreferences = { ...defaultQualityPreferences(), ...(state.qualityPreferences || {}), advancedRunOpen: advanced.open };
|
|
440
|
+
saveQualityPreferences();
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function installOverflowTitles() {
|
|
445
|
+
const refresh = () => {
|
|
446
|
+
document.querySelectorAll("button, .nav-item, .meta-chip, .session-card h3, .terminal-session-card").forEach((element) => {
|
|
447
|
+
const label = describeControl(element);
|
|
448
|
+
if (label && element.scrollWidth > element.clientWidth && !element.getAttribute("title")) element.setAttribute("title", label);
|
|
449
|
+
});
|
|
450
|
+
};
|
|
451
|
+
refresh();
|
|
452
|
+
window.addEventListener("resize", refresh);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function installViewportSafetyClass() {
|
|
456
|
+
const setViewport = () => {
|
|
457
|
+
document.documentElement.dataset.qualityViewport = window.innerWidth < 760 ? "mobile" : window.innerWidth < 1120 ? "tablet" : "desktop";
|
|
458
|
+
};
|
|
459
|
+
setViewport();
|
|
460
|
+
window.addEventListener("resize", setViewport);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function installGlobalQualityGuards() {
|
|
464
|
+
if (qualityGuardsInstalled) return;
|
|
465
|
+
qualityGuardsInstalled = true;
|
|
466
|
+
enhanceQualityControls();
|
|
467
|
+
installQualityMutationObserver();
|
|
468
|
+
installPressedStateMirrors();
|
|
469
|
+
installFormRecovery();
|
|
470
|
+
installDetailsStateMemory();
|
|
471
|
+
installOverflowTitles();
|
|
472
|
+
installViewportSafetyClass();
|
|
473
|
+
document.addEventListener("keydown", () => markInputModality("keyboard"), true);
|
|
474
|
+
document.addEventListener("pointerdown", () => markInputModality("pointer"), true);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
function bindQualityEvents() {
|
|
478
|
+
installGlobalQualityGuards();
|
|
479
|
+
$("#shortcutHelpBtn")?.addEventListener("click", openShortcutHelp);
|
|
480
|
+
$("#closeShortcutHelpBtn")?.addEventListener("click", closeShortcutHelp);
|
|
481
|
+
$("#closeQuickPaletteBtn")?.addEventListener("click", closeQuickPalette);
|
|
482
|
+
safeBackdrop($("#quickPaletteModal"), closeQuickPalette);
|
|
483
|
+
safeBackdrop($("#shortcutHelpModal"), closeShortcutHelp);
|
|
484
|
+
$("#quickPaletteInput")?.addEventListener("input", () => {
|
|
485
|
+
activeCommandIndex = 0;
|
|
486
|
+
renderQuickCommands();
|
|
487
|
+
});
|
|
488
|
+
$("#quickPaletteInput")?.addEventListener("keydown", (event) => {
|
|
489
|
+
if (["ArrowUp", "ArrowDown", "Home", "End"].includes(event.key)) {
|
|
490
|
+
event.preventDefault();
|
|
491
|
+
const count = visibleCommands.length;
|
|
492
|
+
if (!count) return;
|
|
493
|
+
activeCommandIndex = event.key === "Home"
|
|
494
|
+
? 0
|
|
495
|
+
: event.key === "End"
|
|
496
|
+
? count - 1
|
|
497
|
+
: (activeCommandIndex + (event.key === "ArrowDown" ? 1 : -1) + count) % count;
|
|
498
|
+
renderQuickCommands();
|
|
499
|
+
$("#quickPaletteList")?.querySelector(".active")?.scrollIntoView({ block: "nearest" });
|
|
500
|
+
} else if (event.key === "Enter" && visibleCommands[activeCommandIndex]) {
|
|
501
|
+
event.preventDefault();
|
|
502
|
+
executeQuickCommand(visibleCommands[activeCommandIndex].id);
|
|
503
|
+
}
|
|
504
|
+
});
|
|
505
|
+
$("#quickPaletteList")?.addEventListener("click", (event) => {
|
|
506
|
+
const button = event.target.closest("[data-quick-command]");
|
|
507
|
+
if (button) executeQuickCommand(button.dataset.quickCommand);
|
|
508
|
+
});
|
|
509
|
+
$("#clearRunDraftBtn")?.addEventListener("click", clearRunDraft);
|
|
510
|
+
$("#runForm")?.addEventListener("input", saveRunDraft);
|
|
511
|
+
$("#runForm")?.addEventListener("change", saveRunDraft);
|
|
512
|
+
$("#emptyClearFiltersBtn")?.addEventListener("click", () => $("#resetFiltersBtn")?.click());
|
|
513
|
+
document.addEventListener("keydown", (event) => {
|
|
514
|
+
const editable = event.target instanceof HTMLElement && Boolean(event.target.closest("input, textarea, select, [contenteditable='true']"));
|
|
515
|
+
if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "k") {
|
|
516
|
+
event.preventDefault();
|
|
517
|
+
event.stopImmediatePropagation();
|
|
518
|
+
if ($("#quickPaletteModal").classList.contains("hidden")) openQuickPalette();
|
|
519
|
+
else closeQuickPalette();
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
if (!editable && !event.metaKey && !event.ctrlKey && !event.altKey && event.key === "?" && !currentDialog?.()) {
|
|
523
|
+
event.preventDefault();
|
|
524
|
+
event.stopImmediatePropagation();
|
|
525
|
+
openShortcutHelp();
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
if (event.key !== "Escape") return;
|
|
529
|
+
if (!$("#quickPaletteModal").classList.contains("hidden")) {
|
|
530
|
+
event.preventDefault();
|
|
531
|
+
event.stopImmediatePropagation();
|
|
532
|
+
closeQuickPalette();
|
|
533
|
+
} else if (!$("#shortcutHelpModal").classList.contains("hidden")) {
|
|
534
|
+
event.preventDefault();
|
|
535
|
+
event.stopImmediatePropagation();
|
|
536
|
+
closeShortcutHelp();
|
|
537
|
+
}
|
|
538
|
+
}, true);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
return {
|
|
542
|
+
DASHBOARD_STORAGE_KEY,
|
|
543
|
+
RUN_DRAFT_STORAGE_KEY,
|
|
544
|
+
normalizedSearch,
|
|
545
|
+
loadQualityState,
|
|
546
|
+
saveDashboardPreferences,
|
|
547
|
+
saveRunDraft,
|
|
548
|
+
restoreRunDraft,
|
|
549
|
+
clearRunDraft,
|
|
550
|
+
renderQuickCommands,
|
|
551
|
+
openQuickPalette,
|
|
552
|
+
closeQuickPalette,
|
|
553
|
+
openShortcutHelp,
|
|
554
|
+
closeShortcutHelp,
|
|
555
|
+
copyText,
|
|
556
|
+
safeBackdrop,
|
|
557
|
+
bindQualityEvents,
|
|
558
|
+
QUALITY_PREF_STORAGE_KEY,
|
|
559
|
+
qualityText,
|
|
560
|
+
defaultQualityPreferences,
|
|
561
|
+
loadQualityPreferences,
|
|
562
|
+
saveQualityPreferences,
|
|
563
|
+
applyQualityPreferences,
|
|
564
|
+
enhanceControl,
|
|
565
|
+
enhanceQualityControls,
|
|
566
|
+
installGlobalQualityGuards,
|
|
567
|
+
};
|
|
568
|
+
};
|