koneck 2.37.0 → 2.38.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/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +8 -3
- package/dist/browser.js.map +1 -1
- package/dist/web/api.d.ts.map +1 -1
- package/dist/web/api.js +148 -0
- package/dist/web/api.js.map +1 -1
- package/dist/web/files.d.ts +29 -0
- package/dist/web/files.d.ts.map +1 -1
- package/dist/web/files.js +55 -3
- package/dist/web/files.js.map +1 -1
- package/dist/web/jobs.d.ts +76 -0
- package/dist/web/jobs.d.ts.map +1 -0
- package/dist/web/jobs.js +149 -0
- package/dist/web/jobs.js.map +1 -0
- package/dist/web/ui-client.d.ts.map +1 -1
- package/dist/web/ui-client.js +398 -7
- package/dist/web/ui-client.js.map +1 -1
- package/dist/web/ui-css.d.ts.map +1 -1
- package/dist/web/ui-css.js +87 -0
- package/dist/web/ui-css.js.map +1 -1
- package/dist/web/ui.d.ts.map +1 -1
- package/dist/web/ui.js +35 -0
- package/dist/web/ui.js.map +1 -1
- package/package.json +1 -1
package/dist/web/ui-client.js
CHANGED
|
@@ -156,7 +156,7 @@ function md(src) {
|
|
|
156
156
|
/* ── views ─────────────────────────────────────────────────── */
|
|
157
157
|
function setView(name) {
|
|
158
158
|
state.view = name;
|
|
159
|
-
for (const v of ['chat', 'changes', 'timing', 'files', 'search',
|
|
159
|
+
for (const v of ['chat', 'changes', 'timing', 'files', 'terminal', 'preview', 'search',
|
|
160
160
|
'review', 'refactor', 'restore', 'settings']) {
|
|
161
161
|
$(v + 'view').hidden = v !== name;
|
|
162
162
|
$('tab-' + v).className = 'tab' + (v === name ? ' on' : '');
|
|
@@ -166,6 +166,8 @@ function setView(name) {
|
|
|
166
166
|
if (name === 'changes') loadChanges();
|
|
167
167
|
if (name === 'timing') loadTiming();
|
|
168
168
|
if (name === 'files') loadTree('');
|
|
169
|
+
if (name === 'terminal') { loadJobs(); $('tcmd').focus(); }
|
|
170
|
+
if (name === 'preview') loadPreview();
|
|
169
171
|
if (name === 'search') $('q').focus();
|
|
170
172
|
if (name === 'review') loadReview();
|
|
171
173
|
if (name === 'refactor') loadRefactor();
|
|
@@ -722,6 +724,27 @@ function renderTreeInto(host, entries, depth) {
|
|
|
722
724
|
if (!entries.length) host.appendChild(el('div', 'shead', 'empty'));
|
|
723
725
|
}
|
|
724
726
|
|
|
727
|
+
/**
|
|
728
|
+
* A file, open and editable.
|
|
729
|
+
*
|
|
730
|
+
* The tab could show a file and not change it, which makes it a viewer. Editing a line and saving
|
|
731
|
+
* is the smallest useful thing a person does to a file, and going through the agent to do it —
|
|
732
|
+
* asking a model to please change one character — is slower, less certain, and costs tokens.
|
|
733
|
+
*
|
|
734
|
+
* One textarea, deliberately. Undo and redo are the browser's own, which means every shortcut a
|
|
735
|
+
* person already knows works, including the ones nobody thinks about: ctrl+Z, ctrl+shift+Z, ctrl+Y,
|
|
736
|
+
* the platform variants, and undo history that survives switching tabs. A hand-rolled stack would
|
|
737
|
+
* be a worse version of something already there.
|
|
738
|
+
*/
|
|
739
|
+
const editing = { path: null, text: '', size: 0, mtimeMs: 0, dirty: false };
|
|
740
|
+
|
|
741
|
+
function editState(word, cls) {
|
|
742
|
+
const el0 = $('edstate');
|
|
743
|
+
if (!el0) return;
|
|
744
|
+
el0.textContent = word;
|
|
745
|
+
el0.className = 'est' + (cls ? ' ' + cls : '');
|
|
746
|
+
}
|
|
747
|
+
|
|
725
748
|
async function openFile(rel) {
|
|
726
749
|
const host = $('fileview');
|
|
727
750
|
host.innerHTML = '';
|
|
@@ -739,12 +762,79 @@ async function openFile(rel) {
|
|
|
739
762
|
host.appendChild(el('div', 'empty', 'Binary file — nothing to show as text.'));
|
|
740
763
|
return;
|
|
741
764
|
}
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
765
|
+
|
|
766
|
+
editing.path = data.path;
|
|
767
|
+
editing.text = data.text;
|
|
768
|
+
editing.size = data.size;
|
|
769
|
+
editing.mtimeMs = data.mtimeMs;
|
|
770
|
+
editing.dirty = false;
|
|
771
|
+
|
|
772
|
+
const bar = el('div', 'edbar');
|
|
773
|
+
const undo = el('button', 'ebtn', 'undo');
|
|
774
|
+
const redo = el('button', 'ebtn', 'redo');
|
|
775
|
+
const save = el('button', 'ebtn save', 'save');
|
|
776
|
+
const revert = el('button', 'ebtn', 'revert');
|
|
777
|
+
const status = el('span', 'est', data.truncated ? 'read only — opened truncated' : 'saved');
|
|
778
|
+
status.id = 'edstate';
|
|
779
|
+
for (const b of [undo, redo, save, revert]) b.type = 'button';
|
|
780
|
+
bar.appendChild(undo); bar.appendChild(redo); bar.appendChild(save); bar.appendChild(revert);
|
|
781
|
+
bar.appendChild(status);
|
|
782
|
+
host.appendChild(bar);
|
|
783
|
+
|
|
784
|
+
const area = document.createElement('textarea');
|
|
785
|
+
area.className = 'edit';
|
|
786
|
+
area.spellcheck = false;
|
|
787
|
+
area.value = data.text;
|
|
788
|
+
// A truncated file must never be saved: the editor only ever had the first part of it.
|
|
789
|
+
area.readOnly = data.truncated === true;
|
|
790
|
+
save.disabled = data.truncated === true;
|
|
791
|
+
host.appendChild(area);
|
|
792
|
+
|
|
793
|
+
area.addEventListener('input', () => {
|
|
794
|
+
editing.dirty = area.value !== editing.text;
|
|
795
|
+
editState(editing.dirty ? 'unsaved' : 'saved', editing.dirty ? 'dirty' : '');
|
|
796
|
+
});
|
|
797
|
+
// The browser's own undo, so every shortcut a person knows already works.
|
|
798
|
+
undo.onclick = () => { area.focus(); document.execCommand('undo'); area.dispatchEvent(new Event('input')); };
|
|
799
|
+
redo.onclick = () => { area.focus(); document.execCommand('redo'); area.dispatchEvent(new Event('input')); };
|
|
800
|
+
revert.onclick = () => {
|
|
801
|
+
area.value = editing.text;
|
|
802
|
+
editing.dirty = false;
|
|
803
|
+
editState('saved');
|
|
804
|
+
};
|
|
805
|
+
|
|
806
|
+
const doSave = async () => {
|
|
807
|
+
if (area.readOnly) return;
|
|
808
|
+
editState('saving…');
|
|
809
|
+
let out;
|
|
810
|
+
try {
|
|
811
|
+
out = await api('/api/file', { method: 'POST', body: JSON.stringify({
|
|
812
|
+
path: editing.path, text: area.value, size: editing.size, mtimeMs: editing.mtimeMs }) });
|
|
813
|
+
} catch (e) {
|
|
814
|
+
// A refusal because the agent wrote the file while it was open is the one worth acting on,
|
|
815
|
+
// so it comes with the way through rather than just a message.
|
|
816
|
+
editState(e.message.slice(0, 90), 'bad');
|
|
817
|
+
if (e.status === 409) {
|
|
818
|
+
const again = el('button', 'ebtn', 'reload from disk');
|
|
819
|
+
again.type = 'button';
|
|
820
|
+
again.onclick = () => openFile(rel);
|
|
821
|
+
bar.insertBefore(again, status);
|
|
822
|
+
}
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
825
|
+
editing.text = area.value;
|
|
826
|
+
editing.size = out.size;
|
|
827
|
+
editing.mtimeMs = out.mtimeMs;
|
|
828
|
+
editing.dirty = false;
|
|
829
|
+
editState('saved');
|
|
830
|
+
};
|
|
831
|
+
save.onclick = () => void doSave();
|
|
832
|
+
area.addEventListener('keydown', (ev) => {
|
|
833
|
+
if ((ev.ctrlKey || ev.metaKey) && ev.key.toLowerCase() === 's') {
|
|
834
|
+
ev.preventDefault();
|
|
835
|
+
void doSave();
|
|
836
|
+
}
|
|
837
|
+
});
|
|
748
838
|
}
|
|
749
839
|
|
|
750
840
|
/* ── search ────────────────────────────────────────────────── */
|
|
@@ -1129,6 +1219,78 @@ async function loadSettings() {
|
|
|
1129
1219
|
const wrap = el('div', 'wrap');
|
|
1130
1220
|
host.appendChild(wrap);
|
|
1131
1221
|
|
|
1222
|
+
/* ── how it looks ──────────────────────────────────────────────────────────────────────
|
|
1223
|
+
* Typography is a working condition, not a decoration: somebody reading code for nine hours on
|
|
1224
|
+
* a 1080p panel and somebody on a 4K laptop do not want the same size, and the font a person
|
|
1225
|
+
* reads fastest is the one they are used to. Held per browser, because it belongs to whoever is
|
|
1226
|
+
* looking rather than to the workspace.
|
|
1227
|
+
*/
|
|
1228
|
+
wrap.appendChild(el('div', 'sect', 'Type'));
|
|
1229
|
+
const tbox = el('div');
|
|
1230
|
+
wrap.appendChild(tbox);
|
|
1231
|
+
|
|
1232
|
+
const typeRow = (label, hint, control) => {
|
|
1233
|
+
const row = el('div', 'setrow');
|
|
1234
|
+
const left = el('div');
|
|
1235
|
+
left.appendChild(el('div', 'sl', label));
|
|
1236
|
+
if (hint) left.appendChild(el('div', 'sd', hint));
|
|
1237
|
+
row.appendChild(left);
|
|
1238
|
+
row.appendChild(control);
|
|
1239
|
+
tbox.appendChild(row);
|
|
1240
|
+
};
|
|
1241
|
+
|
|
1242
|
+
const sizePick = document.createElement('select');
|
|
1243
|
+
for (const [label, value] of [['Small', '12.5px'], ['Normal', '14px'], ['Large', '15.5px'],
|
|
1244
|
+
['Larger', '17px'], ['Largest', '19px']]) {
|
|
1245
|
+
const o = document.createElement('option');
|
|
1246
|
+
o.value = value; o.textContent = label + ' (' + value + ')';
|
|
1247
|
+
if (value === look.size) o.selected = true;
|
|
1248
|
+
sizePick.appendChild(o);
|
|
1249
|
+
}
|
|
1250
|
+
sizePick.onchange = () => setLook({ size: sizePick.value });
|
|
1251
|
+
typeRow('Size', 'Everything scales from this, so code and prose stay in proportion.', sizePick);
|
|
1252
|
+
|
|
1253
|
+
const famPick = document.createElement('select');
|
|
1254
|
+
for (const [label, value] of UI_FONTS) {
|
|
1255
|
+
const o = document.createElement('option');
|
|
1256
|
+
o.value = value; o.textContent = label;
|
|
1257
|
+
if (value === look.family) o.selected = true;
|
|
1258
|
+
famPick.appendChild(o);
|
|
1259
|
+
}
|
|
1260
|
+
famPick.onchange = () => setLook({ family: famPick.value });
|
|
1261
|
+
typeRow('Interface font', 'What the page is set in.', famPick);
|
|
1262
|
+
|
|
1263
|
+
const monoPick = document.createElement('select');
|
|
1264
|
+
for (const [label, value] of MONO_FONTS) {
|
|
1265
|
+
const o = document.createElement('option');
|
|
1266
|
+
o.value = value; o.textContent = label;
|
|
1267
|
+
if (value === look.mono) o.selected = true;
|
|
1268
|
+
monoPick.appendChild(o);
|
|
1269
|
+
}
|
|
1270
|
+
monoPick.onchange = () => setLook({ mono: monoPick.value });
|
|
1271
|
+
typeRow('Code font', 'Diffs, file contents, the terminal.', monoPick);
|
|
1272
|
+
|
|
1273
|
+
const tintPick = document.createElement('select');
|
|
1274
|
+
for (const [label, value] of [['None', 'none'], ['Warmer', 'warm'], ['Cooler', 'cool'],
|
|
1275
|
+
['More contrast', 'high'], ['Softer', 'soft']]) {
|
|
1276
|
+
const o = document.createElement('option');
|
|
1277
|
+
o.value = value; o.textContent = label;
|
|
1278
|
+
if (value === look.tint) o.selected = true;
|
|
1279
|
+
tintPick.appendChild(o);
|
|
1280
|
+
}
|
|
1281
|
+
tintPick.onchange = () => setLook({ tint: tintPick.value });
|
|
1282
|
+
typeRow('Tone', 'Warmth and contrast, on top of the light or dark theme.', tintPick);
|
|
1283
|
+
|
|
1284
|
+
const sample = el('div', 'setrow');
|
|
1285
|
+
const demo = el('div');
|
|
1286
|
+
demo.appendChild(el('div', 'sl', 'The quick brown fox jumps over the lazy dog'));
|
|
1287
|
+
const code = el('div', 'sd');
|
|
1288
|
+
code.style.fontFamily = 'var(--mono)';
|
|
1289
|
+
code.textContent = 'const rate = 0.15; // ILlO0 1 — how it reads in code';
|
|
1290
|
+
demo.appendChild(code);
|
|
1291
|
+
sample.appendChild(demo);
|
|
1292
|
+
tbox.appendChild(sample);
|
|
1293
|
+
|
|
1132
1294
|
/* health */
|
|
1133
1295
|
wrap.appendChild(el('div', 'sect', 'Health'));
|
|
1134
1296
|
const hbox = el('div');
|
|
@@ -1914,6 +2076,233 @@ $('pmode').onclick = async () => {
|
|
|
1914
2076
|
}
|
|
1915
2077
|
$('modedlg').showModal();
|
|
1916
2078
|
};
|
|
2079
|
+
/* ── how it looks ─────────────────────────────────────────────────────────────────────────── */
|
|
2080
|
+
|
|
2081
|
+
/**
|
|
2082
|
+
* Typography, chosen and remembered.
|
|
2083
|
+
*
|
|
2084
|
+
* Per browser rather than per workspace: it belongs to whoever is looking. Applied as variables on
|
|
2085
|
+
* the root element so one setting moves the whole page — a size that changed the prose and left the
|
|
2086
|
+
* code at 12.5px would be worse than none.
|
|
2087
|
+
*
|
|
2088
|
+
* Only fonts that are already on the machine are offered. A font pulled from a CDN does not arrive
|
|
2089
|
+
* on the machine most likely to be running a local model, which is one that is offline.
|
|
2090
|
+
*/
|
|
2091
|
+
const UI_FONTS = [
|
|
2092
|
+
['System', "system-ui,-apple-system,'Segoe UI',Roboto,Ubuntu,sans-serif"],
|
|
2093
|
+
['Humanist sans', "'Segoe UI','Helvetica Neue',Helvetica,Arial,sans-serif"],
|
|
2094
|
+
['Grotesque sans', "Inter,'Roboto','Helvetica Neue',Arial,sans-serif"],
|
|
2095
|
+
['Serif', "Georgia,'Times New Roman',Times,serif"],
|
|
2096
|
+
['Monospace everywhere', "ui-monospace,'SF Mono',Menlo,Consolas,monospace"],
|
|
2097
|
+
];
|
|
2098
|
+
const MONO_FONTS = [
|
|
2099
|
+
['System monospace', "ui-monospace,'SF Mono',Menlo,Consolas,'DejaVu Sans Mono',monospace"],
|
|
2100
|
+
['JetBrains Mono', "'JetBrains Mono',ui-monospace,Menlo,Consolas,monospace"],
|
|
2101
|
+
['Fira Code', "'Fira Code','Fira Mono',ui-monospace,Menlo,Consolas,monospace"],
|
|
2102
|
+
['Source Code Pro', "'Source Code Pro',ui-monospace,Menlo,Consolas,monospace"],
|
|
2103
|
+
['Courier', "'Courier New',Courier,monospace"],
|
|
2104
|
+
];
|
|
2105
|
+
|
|
2106
|
+
const look = (() => {
|
|
2107
|
+
const fallback = { size: '14px', family: UI_FONTS[0][1], mono: MONO_FONTS[0][1], tint: 'none' };
|
|
2108
|
+
try {
|
|
2109
|
+
const saved = JSON.parse(localStorage.getItem('koneck.look') || '{}');
|
|
2110
|
+
return { ...fallback, ...(saved && typeof saved === 'object' ? saved : {}) };
|
|
2111
|
+
} catch (e) { return fallback; }
|
|
2112
|
+
})();
|
|
2113
|
+
|
|
2114
|
+
function applyLook() {
|
|
2115
|
+
const root = document.documentElement;
|
|
2116
|
+
root.style.setProperty('--ui', look.size);
|
|
2117
|
+
root.style.setProperty('--uifam', look.family);
|
|
2118
|
+
root.style.setProperty('--mono', look.mono);
|
|
2119
|
+
document.body.setAttribute('data-tint', look.tint || 'none');
|
|
2120
|
+
}
|
|
2121
|
+
|
|
2122
|
+
function setLook(change) {
|
|
2123
|
+
Object.assign(look, change);
|
|
2124
|
+
try { localStorage.setItem('koneck.look', JSON.stringify(look)); } catch (e) {}
|
|
2125
|
+
applyLook();
|
|
2126
|
+
}
|
|
2127
|
+
|
|
2128
|
+
/* ── a terminal ───────────────────────────────────────────────────────────────────────────── */
|
|
2129
|
+
|
|
2130
|
+
/**
|
|
2131
|
+
* Commands you run yourself.
|
|
2132
|
+
*
|
|
2133
|
+
* The interface had a card for every command the AGENT ran and no way to run one, which made it a
|
|
2134
|
+
* spectator's seat. Half of working in a repository is running things, and the distinction that
|
|
2135
|
+
* matters is not a setting but a fact about the command: a test run finishes and a dev server
|
|
2136
|
+
* does not, and a runner that waits for the second one looks broken.
|
|
2137
|
+
*/
|
|
2138
|
+
const term = { selected: null, polling: null };
|
|
2139
|
+
|
|
2140
|
+
async function loadJobs(select) {
|
|
2141
|
+
let out;
|
|
2142
|
+
try { out = await api('/api/jobs'); } catch (e) { return; }
|
|
2143
|
+
const host = $('tjobs');
|
|
2144
|
+
host.innerHTML = '';
|
|
2145
|
+
if ((out.jobs || []).length === 0) {
|
|
2146
|
+
host.appendChild(el('div', 'wempty', 'Nothing run yet.'));
|
|
2147
|
+
}
|
|
2148
|
+
const pick = select || term.selected;
|
|
2149
|
+
let anyRunning = false;
|
|
2150
|
+
for (const j of out.jobs || []) {
|
|
2151
|
+
if (j.state === 'running') anyRunning = true;
|
|
2152
|
+
const row = el('div', 'jrow' + (j.id === pick ? ' on' : ''));
|
|
2153
|
+
row.appendChild(el('div', 'jc', j.command));
|
|
2154
|
+
const meta = el('div', 'jm');
|
|
2155
|
+
meta.appendChild(el('span', 'js ' + j.state, j.state
|
|
2156
|
+
+ (j.exitCode !== null && j.state !== 'running' ? ' ' + j.exitCode : '')));
|
|
2157
|
+
if (j.background) meta.appendChild(el('span', null, 'background'));
|
|
2158
|
+
meta.appendChild(el('span', null, j.lines + (j.lines === 1 ? ' line' : ' lines')));
|
|
2159
|
+
if (j.state === 'running') {
|
|
2160
|
+
const stop = el('button', 'jk', 'kill');
|
|
2161
|
+
stop.onclick = async (ev) => {
|
|
2162
|
+
ev.stopPropagation();
|
|
2163
|
+
try { await api('/api/kill', { method: 'POST', body: JSON.stringify({ id: j.id }) }); }
|
|
2164
|
+
catch (e) {}
|
|
2165
|
+
loadJobs(j.id);
|
|
2166
|
+
};
|
|
2167
|
+
meta.appendChild(stop);
|
|
2168
|
+
}
|
|
2169
|
+
row.appendChild(meta);
|
|
2170
|
+
row.onclick = () => { term.selected = j.id; loadJobs(j.id); showJob(j.id); };
|
|
2171
|
+
host.appendChild(row);
|
|
2172
|
+
}
|
|
2173
|
+
if (pick) showJob(pick);
|
|
2174
|
+
// Polled only while something is running, and stopped when nothing is: a terminal tab left open
|
|
2175
|
+
// should not be a request every second for the rest of the day.
|
|
2176
|
+
if (anyRunning && !term.polling) {
|
|
2177
|
+
term.polling = setInterval(() => {
|
|
2178
|
+
if (state.view === 'terminal') loadJobs(); else { clearInterval(term.polling); term.polling = null; }
|
|
2179
|
+
}, 1000);
|
|
2180
|
+
} else if (!anyRunning && term.polling) {
|
|
2181
|
+
clearInterval(term.polling); term.polling = null;
|
|
2182
|
+
}
|
|
2183
|
+
}
|
|
2184
|
+
|
|
2185
|
+
async function showJob(id) {
|
|
2186
|
+
let job;
|
|
2187
|
+
try { job = await api('/api/jobs?id=' + encodeURIComponent(id)); } catch (e) { return; }
|
|
2188
|
+
const host = $('tout');
|
|
2189
|
+
const wasAtEnd = host.scrollHeight - host.scrollTop - host.clientHeight < 60;
|
|
2190
|
+
host.innerHTML = '';
|
|
2191
|
+
if (job.trimmed) host.appendChild(el('div', 'oe', '… earlier output dropped\n'));
|
|
2192
|
+
host.appendChild(document.createTextNode((job.lines || []).join('\n')));
|
|
2193
|
+
if (job.state !== 'running') {
|
|
2194
|
+
host.appendChild(el('div', job.state === 'exited' ? null : 'oe',
|
|
2195
|
+
'\n[' + job.state + (job.exitCode !== null ? ' ' + job.exitCode : '') + ' after '
|
|
2196
|
+
+ ms((job.endedAt || Date.now()) - job.startedAt) + ']'));
|
|
2197
|
+
}
|
|
2198
|
+
if (wasAtEnd) host.scrollTop = host.scrollHeight;
|
|
2199
|
+
}
|
|
2200
|
+
|
|
2201
|
+
async function runCommand() {
|
|
2202
|
+
const command = $('tcmd').value.trim();
|
|
2203
|
+
if (!command) return;
|
|
2204
|
+
let out;
|
|
2205
|
+
try {
|
|
2206
|
+
out = await api('/api/run', { method: 'POST', body: JSON.stringify({
|
|
2207
|
+
command: command, background: $('tbackground').checked }) });
|
|
2208
|
+
} catch (e) {
|
|
2209
|
+
$('tout').textContent = e.message;
|
|
2210
|
+
return;
|
|
2211
|
+
}
|
|
2212
|
+
$('tcmd').value = '';
|
|
2213
|
+
term.selected = out.id;
|
|
2214
|
+
loadJobs(out.id);
|
|
2215
|
+
}
|
|
2216
|
+
$('trun').onclick = () => void runCommand();
|
|
2217
|
+
$('tcmd').addEventListener('keydown', (ev) => {
|
|
2218
|
+
if (ev.key === 'Enter') { ev.preventDefault(); void runCommand(); }
|
|
2219
|
+
});
|
|
2220
|
+
|
|
2221
|
+
/* ── the page, as it really renders ───────────────────────────────────────────────────────── */
|
|
2222
|
+
|
|
2223
|
+
/**
|
|
2224
|
+
* The same browser the agent drives.
|
|
2225
|
+
*
|
|
2226
|
+
* Deliberately the same one rather than a second: watching the agent work and then reaching in to
|
|
2227
|
+
* click something yourself only means anything if it is the same page. A click on the screenshot
|
|
2228
|
+
* is dispatched as a real mouse event at the same point, so what happens is what would happen to a
|
|
2229
|
+
* person clicking there.
|
|
2230
|
+
*/
|
|
2231
|
+
const preview = { shot: null, width: 0 };
|
|
2232
|
+
|
|
2233
|
+
function drawPreview(data) {
|
|
2234
|
+
const host = $('pvbody');
|
|
2235
|
+
host.innerHTML = '';
|
|
2236
|
+
if (!data || data.running === false) {
|
|
2237
|
+
host.appendChild(el('div', 'pvnone', (data && data.note)
|
|
2238
|
+
|| 'Nothing open yet.'));
|
|
2239
|
+
$('pvinfo').textContent = '';
|
|
2240
|
+
return;
|
|
2241
|
+
}
|
|
2242
|
+
$('pvurl').value = data.url || $('pvurl').value;
|
|
2243
|
+
$('pvinfo').textContent = data.title || '';
|
|
2244
|
+
if (!data.shot) {
|
|
2245
|
+
host.appendChild(el('div', 'pvnone', 'The browser is open but produced no picture.'));
|
|
2246
|
+
return;
|
|
2247
|
+
}
|
|
2248
|
+
const img = document.createElement('img');
|
|
2249
|
+
img.className = 'pvshot';
|
|
2250
|
+
img.src = data.shot;
|
|
2251
|
+
img.alt = data.title || 'the page';
|
|
2252
|
+
// The picture is 1280 wide whatever it is displayed at, so a click has to be scaled back into
|
|
2253
|
+
// page coordinates or it lands somewhere else entirely.
|
|
2254
|
+
img.onclick = async (ev) => {
|
|
2255
|
+
const box = img.getBoundingClientRect();
|
|
2256
|
+
const scale = img.naturalWidth / box.width;
|
|
2257
|
+
const x = Math.round((ev.clientX - box.left) * scale);
|
|
2258
|
+
const y = Math.round((ev.clientY - box.top) * scale);
|
|
2259
|
+
$('pvinfo').textContent = 'clicking ' + x + ',' + y + '…';
|
|
2260
|
+
await browserDo({ action: 'click', x: x, y: y });
|
|
2261
|
+
};
|
|
2262
|
+
host.appendChild(img);
|
|
2263
|
+
const hint = el('div', 'pvhint',
|
|
2264
|
+
'Click the page to click it for real. ' + (data.controls || []).length
|
|
2265
|
+
+ ' interactive elements. Scroll with the buttons below.');
|
|
2266
|
+
host.appendChild(hint);
|
|
2267
|
+
const row = el('div', 'pvhint');
|
|
2268
|
+
for (const [label, by] of [['scroll up', -600], ['scroll down', 600]]) {
|
|
2269
|
+
const b = el('button', 'pvbtn', label);
|
|
2270
|
+
b.style.margin = '0 4px';
|
|
2271
|
+
b.onclick = () => void browserDo({ action: 'scroll', by: by });
|
|
2272
|
+
row.appendChild(b);
|
|
2273
|
+
}
|
|
2274
|
+
host.appendChild(row);
|
|
2275
|
+
}
|
|
2276
|
+
|
|
2277
|
+
async function browserDo(body) {
|
|
2278
|
+
$('pvinfo').textContent = 'working…';
|
|
2279
|
+
let out;
|
|
2280
|
+
try { out = await api('/api/browser', { method: 'POST', body: JSON.stringify(body) }); }
|
|
2281
|
+
catch (e) {
|
|
2282
|
+
$('pvbody').innerHTML = '';
|
|
2283
|
+
$('pvbody').appendChild(el('div', 'pvnone', e.message));
|
|
2284
|
+
$('pvinfo').textContent = '';
|
|
2285
|
+
return;
|
|
2286
|
+
}
|
|
2287
|
+
drawPreview({ running: true, ...out });
|
|
2288
|
+
}
|
|
2289
|
+
|
|
2290
|
+
async function loadPreview() {
|
|
2291
|
+
let out;
|
|
2292
|
+
try { out = await api('/api/browser'); } catch (e) { out = null; }
|
|
2293
|
+
drawPreview(out);
|
|
2294
|
+
}
|
|
2295
|
+
$('pvgo').onclick = () => void browserDo({ action: 'open', url: $('pvurl').value.trim() });
|
|
2296
|
+
$('pvurl').addEventListener('keydown', (ev) => {
|
|
2297
|
+
if (ev.key === 'Enter') { ev.preventDefault(); $('pvgo').onclick(); }
|
|
2298
|
+
});
|
|
2299
|
+
$('pvrefresh').onclick = () => void browserDo({ action: 'read' });
|
|
2300
|
+
$('pvclose').onclick = async () => {
|
|
2301
|
+
try { await api('/api/browser', { method: 'POST', body: JSON.stringify({ action: 'close' }) }); }
|
|
2302
|
+
catch (e) {}
|
|
2303
|
+
loadPreview();
|
|
2304
|
+
};
|
|
2305
|
+
|
|
1917
2306
|
/* ── watching it work ─────────────────────────────────────────────────────────────────────── */
|
|
1918
2307
|
|
|
1919
2308
|
/**
|
|
@@ -2460,6 +2849,8 @@ for (const b of document.querySelectorAll('.hero .ex button')) {
|
|
|
2460
2849
|
|
|
2461
2850
|
// The pill has to say what the remembered setting actually is before anything is asked.
|
|
2462
2851
|
setFollow(follow.on);
|
|
2852
|
+
// And the page has to be set in the chosen type before it is looked at, not after.
|
|
2853
|
+
applyLook();
|
|
2463
2854
|
|
|
2464
2855
|
(async function start() {
|
|
2465
2856
|
let hello;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ui-client.js","sourceRoot":"","sources":["../../src/web/ui-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,YAAY;IAC1B,OAAO,MAAM,CAAC,GAAG,CAAA
|
|
1
|
+
{"version":3,"file":"ui-client.js","sourceRoot":"","sources":["../../src/web/ui-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,YAAY;IAC1B,OAAO,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwyFlB,CAAC;AACF,CAAC"}
|
package/dist/web/ui-css.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ui-css.d.ts","sourceRoot":"","sources":["../../src/web/ui-css.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,wBAAgB,GAAG,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"ui-css.d.ts","sourceRoot":"","sources":["../../src/web/ui-css.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,wBAAgB,GAAG,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAmuB3C"}
|
package/dist/web/ui-css.js
CHANGED
|
@@ -468,6 +468,9 @@ textarea::placeholder{color:var(--dim)}
|
|
|
468
468
|
.setrow .si{flex:1;min-width:0}
|
|
469
469
|
.setrow .sl{font-size:13px;font-weight:500}
|
|
470
470
|
.setrow .sd{font-size:11.5px;color:var(--muted);margin-top:2px}
|
|
471
|
+
/* A type row is a label and a control, side by side. */
|
|
472
|
+
.setrow select{flex:0 0 auto;max-width:52%;padding:6px 9px;border-radius:7px;
|
|
473
|
+
border:1px solid var(--line);background:var(--panel-2);color:var(--ink);font-size:12px}
|
|
471
474
|
.setrow .sc{flex:0 0 200px;display:flex;gap:6px;align-items:center}
|
|
472
475
|
.setrow input[type=text],.setrow input[type=number],.setrow select{width:100%;padding:6px 9px;
|
|
473
476
|
border-radius:7px;border:1px solid var(--line);background:var(--panel-2);font-size:12.5px}
|
|
@@ -642,6 +645,90 @@ dialog:has(.dlg.wide){max-width:560px}
|
|
|
642
645
|
.resumed .ri{color:var(--muted);font-family:var(--mono);font-size:12px}
|
|
643
646
|
.resumed .rn{font-size:12px;color:var(--dim);margin-top:7px}
|
|
644
647
|
|
|
648
|
+
/*
|
|
649
|
+
* Typography you choose.
|
|
650
|
+
*
|
|
651
|
+
* Held as variables on :root so the whole page follows one setting, rather than a font size that
|
|
652
|
+
* changes the body text and leaves the code at 12.5px. Every size below is relative to --ui so a
|
|
653
|
+
* single number moves the lot proportionally.
|
|
654
|
+
*/
|
|
655
|
+
:root{--ui:14px; --uifam:system-ui,-apple-system,'Segoe UI',Roboto,Ubuntu,sans-serif;
|
|
656
|
+
--monofam:'JetBrains Mono',ui-monospace,'SF Mono',Menlo,Consolas,monospace; --uihue:none}
|
|
657
|
+
body{font-size:var(--ui);font-family:var(--uifam)}
|
|
658
|
+
/* Applied as a filter so it tints the interface without needing a value per token. */
|
|
659
|
+
body[data-tint="warm"]{filter:sepia(.14) saturate(1.08)}
|
|
660
|
+
body[data-tint="cool"]{filter:hue-rotate(-12deg) saturate(1.06)}
|
|
661
|
+
body[data-tint="high"]{filter:contrast(1.14)}
|
|
662
|
+
body[data-tint="soft"]{filter:contrast(.92) brightness(1.04)}
|
|
663
|
+
|
|
664
|
+
/* ── a terminal ───────────────────────────────────────────────────────────────────────────── */
|
|
665
|
+
.termwrap{display:flex;flex-direction:column;height:100%;min-height:0}
|
|
666
|
+
.termbar{display:flex;gap:9px;align-items:center;padding:11px 14px;background:var(--panel);
|
|
667
|
+
border-bottom:1px solid var(--line);flex:0 0 auto}
|
|
668
|
+
.termbar .tprompt{color:var(--cyan);font-family:var(--mono);font-size:14px}
|
|
669
|
+
.termbar input#tcmd{flex:1;min-width:0;padding:8px 11px;border-radius:8px;
|
|
670
|
+
border:1px solid var(--line);background:var(--panel-2);font-family:var(--mono);font-size:12.5px}
|
|
671
|
+
.termbar .tbg{display:flex;gap:5px;align-items:center;font-size:11.5px;color:var(--muted);
|
|
672
|
+
white-space:nowrap;cursor:pointer}
|
|
673
|
+
.termbar .trun{padding:8px 15px;border-radius:8px;border:1px solid var(--cyan);color:var(--cyan);
|
|
674
|
+
background:transparent;cursor:pointer;font-size:12.5px}
|
|
675
|
+
.termbar .trun:hover{background:var(--panel-2)}
|
|
676
|
+
.termsplit{flex:1;display:flex;min-height:0}
|
|
677
|
+
.tjobs{flex:0 0 250px;overflow-y:auto;border-right:1px solid var(--line);background:var(--panel)}
|
|
678
|
+
.tout{flex:1;min-width:0;overflow:auto;padding:12px 14px;font-family:var(--mono);font-size:11.5px;
|
|
679
|
+
line-height:1.55;white-space:pre-wrap;word-break:break-word}
|
|
680
|
+
.jrow{padding:9px 12px;border-bottom:1px solid var(--line);cursor:pointer;font-size:12px}
|
|
681
|
+
.jrow:hover{background:var(--panel-2)}
|
|
682
|
+
.jrow.on{background:var(--panel-2);box-shadow:inset 2px 0 0 var(--cyan)}
|
|
683
|
+
.jrow .jc{font-family:var(--mono);font-size:11.5px;overflow:hidden;text-overflow:ellipsis;
|
|
684
|
+
white-space:nowrap;color:var(--ink)}
|
|
685
|
+
.jrow .jm{display:flex;gap:7px;align-items:center;margin-top:4px;font-size:10.5px;color:var(--dim)}
|
|
686
|
+
.jrow .js{padding:1px 6px;border-radius:999px;border:1px solid var(--line)}
|
|
687
|
+
.jrow .js.running{color:var(--cyan);border-color:var(--cyan)}
|
|
688
|
+
.jrow .js.exited{color:var(--green)}
|
|
689
|
+
.jrow .js.failed,.jrow .js.killed{color:var(--red)}
|
|
690
|
+
.jrow .jk{margin-left:auto;background:none;border:0;color:var(--dim);cursor:pointer;font-size:11px}
|
|
691
|
+
.jrow .jk:hover{color:var(--red)}
|
|
692
|
+
.tout .oe{color:var(--red)}
|
|
693
|
+
|
|
694
|
+
/* ── the page, as it really renders ───────────────────────────────────────────────────────── */
|
|
695
|
+
.pvwrap{display:flex;flex-direction:column;height:100%;min-height:0}
|
|
696
|
+
.pvbar{display:flex;gap:8px;align-items:center;padding:11px 14px;background:var(--panel);
|
|
697
|
+
border-bottom:1px solid var(--line);flex:0 0 auto}
|
|
698
|
+
.pvbar input{flex:1;min-width:0;padding:8px 11px;border-radius:8px;border:1px solid var(--line);
|
|
699
|
+
background:var(--panel-2);font-family:var(--mono);font-size:12.5px}
|
|
700
|
+
.pvbar .pvgo{padding:8px 15px;border-radius:8px;border:1px solid var(--cyan);color:var(--cyan);
|
|
701
|
+
background:transparent;cursor:pointer;font-size:12.5px}
|
|
702
|
+
.pvbar .pvbtn{padding:8px 12px;border-radius:8px;border:1px solid var(--line);color:var(--muted);
|
|
703
|
+
background:transparent;cursor:pointer;font-size:12px}
|
|
704
|
+
.pvbar .pvbtn:hover{color:var(--ink)}
|
|
705
|
+
.pvinfo{font-size:11px;color:var(--dim);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
|
|
706
|
+
max-width:24%}
|
|
707
|
+
.pvbody{flex:1;overflow:auto;min-height:0;display:flex;flex-direction:column;align-items:center;
|
|
708
|
+
padding:16px;background:var(--panel-2)}
|
|
709
|
+
/* The screenshot is the page. Clicking it clicks the page at the same point. */
|
|
710
|
+
.pvshot{max-width:100%;border:1px solid var(--line);border-radius:8px;cursor:crosshair;
|
|
711
|
+
background:#fff;box-shadow:var(--shadow)}
|
|
712
|
+
.pvhint{margin-top:11px;font-size:11.5px;color:var(--dim);text-align:center;line-height:1.6}
|
|
713
|
+
.pvnone{padding:40px 20px;color:var(--dim);font-size:13px;text-align:center;line-height:1.7;
|
|
714
|
+
max-width:520px}
|
|
715
|
+
|
|
716
|
+
/* ── an editable file ─────────────────────────────────────────────────────────────────────── */
|
|
717
|
+
.edbar{display:flex;gap:9px;align-items:center;padding:8px 14px;background:var(--panel);
|
|
718
|
+
border-bottom:1px solid var(--line);flex:0 0 auto;font-size:11.5px}
|
|
719
|
+
.edbar .ebtn{padding:5px 11px;border-radius:7px;border:1px solid var(--line);background:transparent;
|
|
720
|
+
color:var(--muted);cursor:pointer;font-size:11.5px}
|
|
721
|
+
.edbar .ebtn:hover{color:var(--ink)}
|
|
722
|
+
.edbar .ebtn.save{border-color:var(--cyan);color:var(--cyan)}
|
|
723
|
+
.edbar .ebtn:disabled{opacity:.45;cursor:default}
|
|
724
|
+
.edbar .est{margin-left:auto;color:var(--dim);white-space:nowrap}
|
|
725
|
+
.edbar .est.dirty{color:var(--amber)}
|
|
726
|
+
.edbar .est.bad{color:var(--red)}
|
|
727
|
+
/* One textarea, so the browser's own undo and redo work — a hand-rolled stack would be worse. */
|
|
728
|
+
.edit{flex:1;width:100%;min-height:0;resize:none;border:0;outline:none;padding:12px 14px;
|
|
729
|
+
background:var(--bg);color:var(--ink);font-family:var(--mono);font-size:12.5px;line-height:1.5;
|
|
730
|
+
tab-size:2;white-space:pre;overflow:auto}
|
|
731
|
+
|
|
645
732
|
/* Carrying on from a transcript, offered in the bar that says it is read only. */
|
|
646
733
|
.readonly .rgap{flex:1}
|
|
647
734
|
.readonly .rwhy{font-size:11.5px;color:var(--dim);white-space:nowrap;overflow:hidden;
|
package/dist/web/ui-css.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ui-css.js","sourceRoot":"","sources":["../../src/web/ui-css.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,GAAG,CAAC,OAAe;IACjC,OAAO;;;;;;;;;;;;gBAYO,OAAO
|
|
1
|
+
{"version":3,"file":"ui-css.js","sourceRoot":"","sources":["../../src/web/ui-css.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,GAAG,CAAC,OAAe;IACjC,OAAO;;;;;;;;;;;;gBAYO,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqtBtB,CAAC;AACF,CAAC"}
|
package/dist/web/ui.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../../src/web/ui.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAOH,wBAAgB,IAAI,IAAI,MAAM,
|
|
1
|
+
{"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../../src/web/ui.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAOH,wBAAgB,IAAI,IAAI,MAAM,CA4R7B"}
|
package/dist/web/ui.js
CHANGED
|
@@ -45,6 +45,8 @@ export function page() {
|
|
|
45
45
|
<button class="tab" id="tab-changes">Changes</button>
|
|
46
46
|
<button class="tab" id="tab-timing">Timing</button>
|
|
47
47
|
<button class="tab" id="tab-files">Files</button>
|
|
48
|
+
<button class="tab" id="tab-terminal">Terminal</button>
|
|
49
|
+
<button class="tab" id="tab-preview">Preview</button>
|
|
48
50
|
<button class="tab" id="tab-search">Search</button>
|
|
49
51
|
<button class="tab" id="tab-review">Review</button>
|
|
50
52
|
<button class="tab" id="tab-refactor">Refactor</button>
|
|
@@ -108,6 +110,39 @@ export function page() {
|
|
|
108
110
|
</div>
|
|
109
111
|
</div>
|
|
110
112
|
|
|
113
|
+
<!-- A terminal. Commands you run yourself, in the foreground or left running behind. -->
|
|
114
|
+
<div class="view" id="terminalview" hidden>
|
|
115
|
+
<div class="termwrap">
|
|
116
|
+
<div class="termbar">
|
|
117
|
+
<span class="tprompt">$</span>
|
|
118
|
+
<input id="tcmd" placeholder="npm test, git status, npm run dev…"
|
|
119
|
+
autocomplete="off" spellcheck="false">
|
|
120
|
+
<label class="tbg" title="Leave it running — for a dev server, or anything that does not exit">
|
|
121
|
+
<input type="checkbox" id="tbackground"> background
|
|
122
|
+
</label>
|
|
123
|
+
<button class="trun" id="trun">Run</button>
|
|
124
|
+
</div>
|
|
125
|
+
<div class="termsplit">
|
|
126
|
+
<div class="tjobs" id="tjobs"></div>
|
|
127
|
+
<div class="tout" id="tout"></div>
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
|
+
</div>
|
|
131
|
+
|
|
132
|
+
<!-- The same browser the agent drives, so watching it and reaching in mean the same page. -->
|
|
133
|
+
<div class="view" id="previewview" hidden>
|
|
134
|
+
<div class="pvwrap">
|
|
135
|
+
<div class="pvbar">
|
|
136
|
+
<input id="pvurl" placeholder="http://localhost:3000" autocomplete="off" spellcheck="false">
|
|
137
|
+
<button class="pvgo" id="pvgo">Open</button>
|
|
138
|
+
<button class="pvbtn" id="pvrefresh" title="Read the page again">Refresh</button>
|
|
139
|
+
<button class="pvbtn" id="pvclose" title="Shut the browser down">Close</button>
|
|
140
|
+
<span class="pvinfo" id="pvinfo"></span>
|
|
141
|
+
</div>
|
|
142
|
+
<div class="pvbody" id="pvbody"></div>
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
|
|
111
146
|
<div class="view" id="reviewview" hidden></div>
|
|
112
147
|
<div class="view" id="refactorview" hidden></div>
|
|
113
148
|
<div class="view" id="restoreview" hidden></div>
|
package/dist/web/ui.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ui.js","sourceRoot":"","sources":["../../src/web/ui.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAClC,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE9C,MAAM,UAAU,IAAI;IAClB,OAAO;;;;;;;SAOA,GAAG,CAAC,SAAS,CAAC
|
|
1
|
+
{"version":3,"file":"ui.js","sourceRoot":"","sources":["../../src/web/ui.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAClC,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE9C,MAAM,UAAU,IAAI;IAClB,OAAO;;;;;;;SAOA,GAAG,CAAC,SAAS,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAgRF,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC;EAC7C,YAAY,EAAE;;eAED,CAAC;AAChB,CAAC"}
|