koneck 2.56.0 → 2.57.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.
@@ -168,7 +168,14 @@ function setView(name) {
168
168
  if (name === 'changes') loadChanges();
169
169
  if (name === 'timing') loadTiming();
170
170
  if (name === 'files') loadTree('');
171
- if (name === 'terminal') { loadJobs(); $('tcmd').focus(); }
171
+ if (name === 'terminal') {
172
+ // Built on first sight and kept, so a pane's scrollback survives leaving the tab and coming
173
+ // back — a terminal that forgets what it printed when you look away is not a terminal.
174
+ if (term.panes.length === 0 || !term.panes[0].el) buildTerminal();
175
+ watchTerminal();
176
+ const here = term.panes[term.active];
177
+ if (here && here.el) here.el.input.focus();
178
+ }
172
179
  if (name === 'search') $('q').focus();
173
180
  if (name === 'review') loadReview();
174
181
  if (name === 'refactor') loadRefactor();
@@ -2620,8 +2627,17 @@ addEventListener('keydown', (ev) => {
2620
2627
  */
2621
2628
  function ansiToHtml(text) {
2622
2629
  const escChar = String.fromCharCode(27);
2630
+ /*
2631
+ * Colour is kept; everything else a terminal emits is swallowed.
2632
+ *
2633
+ * The last alternative is the one that was missing: an escape followed by a single character —
2634
+ * ESC = for keypad mode, ESC > , ESC 7 to save the cursor — has no bracket, so the CSI pattern
2635
+ * never matched it and it reached the page as a control glyph. That is what appeared at the top
2636
+ * of a pane running git.
2637
+ */
2623
2638
  const pattern = new RegExp(escChar + '\\[([0-9;]*)m|' + escChar + '\\[[0-9;?]*[A-Za-z]|'
2624
- + escChar + '\\][^\\u0007]*\\u0007', 'g');
2639
+ + escChar + '\\][^\\u0007]*(?:\\u0007|' + escChar + '\\\\)|'
2640
+ + escChar + '[=>78MDEHcZ]|[\\u0000-\\u0008\\u000b\\u000c\\u000e-\\u001f]', 'g');
2625
2641
  let out = '';
2626
2642
  let open = 0;
2627
2643
  let last = 0;
@@ -2659,96 +2675,303 @@ function ansiToHtml(text) {
2659
2675
  * matters is not a setting but a fact about the command: a test run finishes and a dev server
2660
2676
  * does not, and a runner that waits for the second one looks broken.
2661
2677
  */
2662
- const term = { selected: null, polling: null };
2678
+ /*
2679
+ * A terminal, not a form with an output box.
2680
+ *
2681
+ * What was here was a job list beside a pane of text: you typed into a field at the top, picked a
2682
+ * row, and read the result — which arrived in whole-buffer snapshots a few times a second, because
2683
+ * the view polled while the runner had been emitting a line-by-line event all along. Everything
2684
+ * about it said "web page".
2685
+ *
2686
+ * This is a scrollback: the command you typed stays above what it printed, the cursor waits at the
2687
+ * bottom, and the next thing you type continues the same column of text. Up to three panes, each
2688
+ * its own shell with its own history, because two is the common case — watch the server, run the
2689
+ * tests — and four on a laptop is four things too narrow to read.
2690
+ */
2691
+ const term = {
2692
+ panes: [],
2693
+ /** Which pane the keyboard belongs to. */
2694
+ active: 0,
2695
+ stream: null,
2696
+ /** Pane index by job id, so a line can be routed to the pane that asked for it. */
2697
+ ownerOf: {},
2698
+ fontPx: 12.5,
2699
+ };
2663
2700
 
2664
- async function loadJobs(select) {
2665
- let out;
2666
- try { out = await api('/api/jobs'); } catch (e) { return; }
2667
- const host = $('tjobs');
2701
+ function newPane() {
2702
+ return { job: null, history: [], at: -1, lines: [], running: false, el: null,
2703
+ /** Where this command's output begins, so a catch-up can replace exactly that. */
2704
+ outputStart: 0,
2705
+ /** How many output lines have arrived on the stream, for the same reason. */
2706
+ received: 0 };
2707
+ }
2708
+
2709
+ /** The pane a job's output belongs to, or null when nothing here started it. */
2710
+ function paneForJob(id) {
2711
+ const idx = term.ownerOf[id];
2712
+ return idx === undefined ? null : term.panes[idx] || null;
2713
+ }
2714
+
2715
+ function buildTerminal() {
2716
+ if (term.panes.length === 0) term.panes.push(newPane());
2717
+ const host = $('termpanes');
2668
2718
  host.innerHTML = '';
2669
- if ((out.jobs || []).length === 0) {
2670
- host.appendChild(el('div', 'wempty', 'Nothing run yet.'));
2671
- }
2672
- const pick = select || term.selected;
2673
- let anyRunning = false;
2674
- for (const j of out.jobs || []) {
2675
- if (j.state === 'running') anyRunning = true;
2676
- const row = el('div', 'jrow' + (j.id === pick ? ' on' : ''));
2677
- row.appendChild(el('div', 'jc', j.command));
2678
- const meta = el('div', 'jm');
2679
- meta.appendChild(el('span', 'js ' + j.state, j.state
2680
- + (j.exitCode !== null && j.state !== 'running' ? ' ' + j.exitCode : '')));
2681
- // Who ran it is a column, not a separate screen.
2682
- if (j.by === 'agent') meta.appendChild(el('span', null, 'by the agent'));
2683
- if (j.background) meta.appendChild(el('span', null, 'background'));
2684
- if (j.pty) meta.appendChild(el('span', null, 'tty'));
2685
- meta.appendChild(el('span', null, j.lines + (j.lines === 1 ? ' line' : ' lines')));
2686
- if (j.state === 'running' && j.by !== 'agent') {
2687
- const stop = el('button', 'jk', 'kill');
2688
- stop.onclick = async (ev) => {
2689
- ev.stopPropagation();
2690
- try { await api('/api/kill', { method: 'POST', body: JSON.stringify({ id: j.id }) }); }
2691
- catch (e) {}
2692
- loadJobs(j.id);
2693
- };
2694
- meta.appendChild(stop);
2719
+ term.panes.forEach((pane, index) => {
2720
+ if (index > 0) {
2721
+ // A draggable divider, so a pane can be made wide enough for the thing being read.
2722
+ const grip = el('div', 'termgrip');
2723
+ grip.onmousedown = (ev) => startPaneDrag(ev, index);
2724
+ host.appendChild(grip);
2695
2725
  }
2696
- row.appendChild(meta);
2697
- row.onclick = () => { term.selected = j.id; loadJobs(j.id); showJob(j.id); };
2698
- host.appendChild(row);
2699
- }
2700
- if (pick) showJob(pick);
2701
- // Polled only while something is running, and stopped when nothing is: a terminal tab left open
2702
- // should not be a request every second for the rest of the day.
2703
- if ((anyRunning || state.busy) && !term.polling) {
2704
- term.polling = setInterval(() => {
2705
- if (state.view === 'terminal') loadJobs();
2706
- else { clearInterval(term.polling); term.polling = null; }
2707
- }, 1000);
2708
- } else if (!anyRunning && !state.busy && term.polling) {
2709
- clearInterval(term.polling); term.polling = null;
2710
- }
2726
+ const box = el('div', 'termpane' + (index === term.active ? ' on' : ''));
2727
+ box.style.flex = String(pane.flex || 1);
2728
+ box.onmousedown = () => { term.active = index; markActivePane(); };
2729
+
2730
+ const scroll = el('div', 'termscroll');
2731
+ const body = el('pre', 'termbody');
2732
+ scroll.appendChild(body);
2733
+
2734
+ const row = el('div', 'termrow');
2735
+ row.appendChild(el('span', 'termps', '$'));
2736
+ const input = document.createElement('input');
2737
+ input.className = 'termin';
2738
+ input.spellcheck = false;
2739
+ input.autocomplete = 'off';
2740
+ input.placeholder = index === 0 ? 'npm test, git status, npm run dev…' : '';
2741
+ input.onkeydown = (ev) => paneKey(ev, index);
2742
+ input.onfocus = () => { term.active = index; markActivePane(); };
2743
+ row.appendChild(input);
2744
+ const stop = el('button', 'termstop', 'stop');
2745
+ stop.title = 'Kill what is running in this pane';
2746
+ stop.onclick = () => void killPane(index);
2747
+ row.appendChild(stop);
2748
+
2749
+ box.appendChild(scroll);
2750
+ box.appendChild(row);
2751
+ host.appendChild(box);
2752
+ pane.el = { box: box, scroll: scroll, body: body, input: input, stop: stop };
2753
+ paintPane(index);
2754
+ });
2755
+ applyTerminalLook();
2756
+ markActivePane();
2757
+ const here = term.panes[term.active];
2758
+ if (here && here.el) here.el.input.focus();
2711
2759
  }
2712
2760
 
2713
- async function showJob(id) {
2714
- let job;
2715
- try { job = await api('/api/jobs?id=' + encodeURIComponent(id)); } catch (e) { return; }
2716
- const host = $('tout');
2717
- const wasAtEnd = host.scrollHeight - host.scrollTop - host.clientHeight < 60;
2718
- host.innerHTML = '';
2719
- if (job.trimmed) host.appendChild(el('div', 'oe', 'earlier output dropped\n'));
2720
- // The escape codes are why a pty was allocated; rendering them is the point.
2721
- const body = el('div');
2722
- body.innerHTML = ansiToHtml((job.lines || []).join('\n'));
2723
- host.appendChild(body);
2724
- for (const line of job.lines || []) noticeServedUrl(line);
2725
- if (job.state !== 'running') {
2726
- host.appendChild(el('div', job.state === 'exited' ? null : 'oe',
2727
- '\n[' + job.state + (job.exitCode !== null ? ' ' + job.exitCode : '') + ' after '
2728
- + ms((job.endedAt || Date.now()) - job.startedAt) + ']'));
2761
+ function markActivePane() {
2762
+ term.panes.forEach((pane, i) => {
2763
+ if (pane.el) pane.el.box.className = 'termpane' + (i === term.active ? ' on' : '');
2764
+ });
2765
+ }
2766
+
2767
+ /** Everything this pane has printed, as one column of text. */
2768
+ function paintPane(index) {
2769
+ const pane = term.panes[index];
2770
+ if (!pane || !pane.el) return;
2771
+ const atEnd = pane.el.scroll.scrollHeight - pane.el.scroll.scrollTop
2772
+ - pane.el.scroll.clientHeight < 60;
2773
+ pane.el.body.innerHTML = ansiToHtml(pane.lines.join('\n'));
2774
+ pane.el.stop.hidden = !pane.running;
2775
+ if (atEnd) pane.el.scroll.scrollTop = pane.el.scroll.scrollHeight;
2776
+ }
2777
+
2778
+ function pushPane(index, line, redraw) {
2779
+ const pane = term.panes[index];
2780
+ if (!pane) return;
2781
+ if (redraw && pane.lines.length > 0) pane.lines[pane.lines.length - 1] = line;
2782
+ else pane.lines.push(line);
2783
+ while (pane.lines.length > 4000) pane.lines.shift();
2784
+ paintPane(index);
2785
+ }
2786
+
2787
+ async function paneKey(ev, index) {
2788
+ const pane = term.panes[index];
2789
+ if (!pane || !pane.el) return;
2790
+ const input = pane.el.input;
2791
+ if (ev.key === 'Enter') {
2792
+ const command = input.value.trim();
2793
+ input.value = '';
2794
+ if (command === '') { pushPane(index, '$ '); return; }
2795
+ if (command === 'clear') { pane.lines = []; paintPane(index); return; }
2796
+ pane.history.push(command);
2797
+ pane.at = pane.history.length;
2798
+ // Echoed above its own output, which is the whole difference from a form.
2799
+ pushPane(index, '$ ' + command);
2800
+ pane.outputStart = pane.lines.length;
2801
+ pane.received = 0;
2802
+ await runInPane(index, command);
2803
+ return;
2729
2804
  }
2730
- if (wasAtEnd) host.scrollTop = host.scrollHeight;
2805
+ // A history nobody has to think about: up and down, as everywhere else.
2806
+ if (ev.key === 'ArrowUp') {
2807
+ ev.preventDefault();
2808
+ if (pane.at > 0) pane.at--;
2809
+ input.value = pane.history[pane.at] || '';
2810
+ return;
2811
+ }
2812
+ if (ev.key === 'ArrowDown') {
2813
+ ev.preventDefault();
2814
+ if (pane.at < pane.history.length) pane.at++;
2815
+ input.value = pane.history[pane.at] || '';
2816
+ return;
2817
+ }
2818
+ if (ev.key === 'c' && ev.ctrlKey) { void killPane(index); }
2731
2819
  }
2732
2820
 
2733
- async function runCommand() {
2734
- const command = $('tcmd').value.trim();
2735
- if (!command) return;
2821
+ async function runInPane(index, command) {
2822
+ const pane = term.panes[index];
2823
+ if (!pane) return;
2824
+ // Anything that does not exit on its own is a background job; a runner that waits for a dev
2825
+ // server looks broken, and the fact is about the command rather than a box somebody ticked.
2826
+ const looksLikeServer = /\b(dev|serve|start|watch|nodemon|vite|next\s+dev)\b/.test(command);
2736
2827
  let out;
2737
2828
  try {
2738
- out = await api('/api/run', { method: 'POST', body: JSON.stringify({
2739
- command: command, background: $('tbackground').checked }) });
2829
+ out = await api('/api/run', { method: 'POST',
2830
+ body: JSON.stringify({ command: command, background: looksLikeServer }) });
2740
2831
  } catch (e) {
2741
- $('tout').textContent = e.message;
2832
+ pushPane(index, e.message);
2742
2833
  return;
2743
2834
  }
2744
- $('tcmd').value = '';
2745
- term.selected = out.id;
2746
- loadJobs(out.id);
2835
+ pane.job = out.id;
2836
+ pane.running = true;
2837
+ term.ownerOf[out.id] = index;
2838
+ paintPane(index);
2839
+
2840
+ /*
2841
+ * One catch-up read, because a command can outrun its own stream.
2842
+ *
2843
+ * The event stream is opened when the tab is first shown and takes a moment to establish. A
2844
+ * command typed inside that moment printed its first lines before anything was listening, and
2845
+ * they were gone: the pane showed the echoed command and nothing under it. Measured — the stream
2846
+ * was still in readyState 0 when the lines were emitted.
2847
+ *
2848
+ * So the runner is asked once, shortly after, what the job has actually produced, and anything
2849
+ * the stream missed is filled in. Counted rather than merged, so a line never appears twice.
2850
+ */
2851
+ setTimeout(() => { void catchUpPane(index, out.id); }, 400);
2747
2852
  }
2748
- $('trun').onclick = () => void runCommand();
2749
- $('tcmd').addEventListener('keydown', (ev) => {
2750
- if (ev.key === 'Enter') { ev.preventDefault(); void runCommand(); }
2751
- });
2853
+
2854
+ async function catchUpPane(index, jobId) {
2855
+ const pane = term.panes[index];
2856
+ if (!pane || pane.job !== jobId) return;
2857
+ let job;
2858
+ try { job = await api('/api/jobs?id=' + encodeURIComponent(jobId)); } catch (e) { return; }
2859
+ const produced = (job.lines || []).length;
2860
+ if (produced <= pane.received) return;
2861
+ pane.lines = pane.lines.slice(0, pane.outputStart).concat(job.lines || []);
2862
+ pane.received = produced;
2863
+ if (job.state && job.state !== 'running') pane.running = false;
2864
+ paintPane(index);
2865
+ }
2866
+
2867
+ async function killPane(index) {
2868
+ const pane = term.panes[index];
2869
+ if (!pane || !pane.job || !pane.running) return;
2870
+ try { await api('/api/kill', { method: 'POST', body: JSON.stringify({ id: pane.job }) }); }
2871
+ catch (e) { pushPane(index, e.message); }
2872
+ }
2873
+
2874
+ /*
2875
+ * Live, from the runner's own event stream.
2876
+ *
2877
+ * Its own stream rather than the session's: a command belongs to the workspace, not to whichever
2878
+ * conversation was open when it was typed.
2879
+ */
2880
+ function watchTerminal() {
2881
+ if (term.stream) return;
2882
+ const url = '/api/terminal/events' + (state.token ? '?token=' + encodeURIComponent(state.token) : '');
2883
+ const source = new EventSource(url);
2884
+ term.stream = source;
2885
+ source.onmessage = (ev) => {
2886
+ let e;
2887
+ try { e = JSON.parse(ev.data); } catch (err) { return; }
2888
+ if (e.t === 'job-line') {
2889
+ const idx = term.ownerOf[e.id];
2890
+ if (idx === undefined) return;
2891
+ const pane = term.panes[idx];
2892
+ if (pane && !e.redraw) pane.received++;
2893
+ pushPane(idx, e.line, e.redraw === true);
2894
+ noticeServedUrl(e.line);
2895
+ return;
2896
+ }
2897
+ if (e.t === 'job' && e.job) {
2898
+ const idx = term.ownerOf[e.job.id];
2899
+ if (idx === undefined) return;
2900
+ const pane = term.panes[idx];
2901
+ if (!pane) return;
2902
+ if (e.job.state !== 'running') {
2903
+ pane.running = false;
2904
+ // Said the way a shell says it, rather than as a status badge.
2905
+ pushPane(idx, e.job.exitCode === 0 || e.job.exitCode === null
2906
+ ? ''
2907
+ : '[' + e.job.state + ' ' + e.job.exitCode + ']');
2908
+ }
2909
+ paintPane(idx);
2910
+ }
2911
+ };
2912
+ source.onerror = () => { /* the browser reconnects on its own */ };
2913
+ }
2914
+
2915
+ /** Font size and backdrop, applied to every pane at once. */
2916
+ function applyTerminalLook() {
2917
+ const host = $('termpanes');
2918
+ host.style.setProperty('--term-font', term.fontPx + 'px');
2919
+ const on = $('tbgimg').checked;
2920
+ const strength = Number($('tbgop').value) / 100;
2921
+ host.classList.toggle('backdrop', on);
2922
+ host.style.setProperty('--term-bg-strength', on ? String(strength) : '0');
2923
+ if (on && !host.style.getPropertyValue('--term-bg-image')) {
2924
+ const url = '/api/terminal/background'
2925
+ + (state.token ? '?token=' + encodeURIComponent(state.token) : '');
2926
+ host.style.setProperty('--term-bg-image', 'url("' + url + '")');
2927
+ }
2928
+ }
2929
+
2930
+ function startPaneDrag(ev, dividerIndex) {
2931
+ ev.preventDefault();
2932
+ const left = term.panes[dividerIndex - 1];
2933
+ const right = term.panes[dividerIndex];
2934
+ if (!left || !right || !left.el || !right.el) return;
2935
+ const startX = ev.clientX;
2936
+ const leftWas = left.el.box.getBoundingClientRect().width;
2937
+ const rightWas = right.el.box.getBoundingClientRect().width;
2938
+ const total = leftWas + rightWas;
2939
+ const move = (m) => {
2940
+ const wanted = Math.min(Math.max(leftWas + (m.clientX - startX), 120), total - 120);
2941
+ left.flex = wanted / total;
2942
+ right.flex = (total - wanted) / total;
2943
+ left.el.box.style.flex = String(left.flex);
2944
+ right.el.box.style.flex = String(right.flex);
2945
+ };
2946
+ const up = () => {
2947
+ window.removeEventListener('mousemove', move);
2948
+ window.removeEventListener('mouseup', up);
2949
+ };
2950
+ window.addEventListener('mousemove', move);
2951
+ window.addEventListener('mouseup', up);
2952
+ }
2953
+
2954
+ $('tsplit').onclick = () => {
2955
+ // Three, deliberately. Four on a laptop is four things too narrow to read.
2956
+ if (term.panes.length >= 3) return;
2957
+ term.panes.push(newPane());
2958
+ term.active = term.panes.length - 1;
2959
+ buildTerminal();
2960
+ };
2961
+
2962
+ $('tunsplit').onclick = () => {
2963
+ if (term.panes.length <= 1) return;
2964
+ const gone = term.panes.pop();
2965
+ if (gone && gone.job) delete term.ownerOf[gone.job];
2966
+ term.active = Math.min(term.active, term.panes.length - 1);
2967
+ buildTerminal();
2968
+ };
2969
+
2970
+ $('tbgimg').onchange = applyTerminalLook;
2971
+ $('tbgop').oninput = applyTerminalLook;
2972
+ $('tfontup').onclick = () => { term.fontPx = Math.min(20, term.fontPx + 1); applyTerminalLook(); };
2973
+ $('tfontdown').onclick = () => { term.fontPx = Math.max(9, term.fontPx - 1); applyTerminalLook(); };
2974
+
2752
2975
 
2753
2976
  /* ── the page, as it really renders ───────────────────────────────────────────────────────── */
2754
2977
 
@@ -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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAo0HlB,CAAC;AACF,CAAC"}
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmiIlB,CAAC;AACF,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"ui-css.d.ts","sourceRoot":"","sources":["../../src/web/ui-css.ts"],"names":[],"mappings":"AA8CA,wBAAgB,GAAG,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAw6B3C"}
1
+ {"version":3,"file":"ui-css.d.ts","sourceRoot":"","sources":["../../src/web/ui-css.ts"],"names":[],"mappings":"AA8CA,wBAAgB,GAAG,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAy8B3C"}
@@ -789,34 +789,67 @@ body[data-tint="high"]{filter:contrast(1.14)}
789
789
  body[data-tint="soft"]{filter:contrast(.92) brightness(1.04)}
790
790
 
791
791
  /* ── a terminal ───────────────────────────────────────────────────────────────────────────── */
792
- .termwrap{display:flex;flex-direction:column;height:100%;min-height:0}
793
- .termbar{display:flex;gap:9px;align-items:center;padding:11px 14px;background:var(--panel);
792
+ /*
793
+ * A terminal that reads as one.
794
+ *
795
+ * The difference from the box that was here is not decoration. A terminal is a single column of
796
+ * monospaced text where the command you typed stays above what it printed and the cursor waits at
797
+ * the bottom of it. So: no card around the output, no border between the prompt and the scrollback,
798
+ * one background, and the input line styled as the last line of the text rather than as a form
799
+ * field sitting under it.
800
+ */
801
+ /*
802
+ * The terminal view fills what it is given.
803
+ *
804
+ * A view scrolls its own overflow, which is right for a document and wrong for a terminal: the
805
+ * panes ended a third of the way down the window with dead space under them, because nothing told
806
+ * them how tall they were allowed to be.
807
+ */
808
+ #terminalview{display:flex;flex-direction:column;overflow:hidden}
809
+ .termtop{display:flex;gap:8px;align-items:center;padding:9px 14px;background:var(--panel);
794
810
  border-bottom:1px solid var(--line);flex:0 0 auto}
795
- .termbar .tprompt{color:var(--cyan);font-family:var(--mono);font-size:14px}
796
- .termbar input#tcmd{flex:1;min-width:0;padding:8px 11px;border-radius:8px;
797
- border:1px solid var(--line);background:var(--panel-2);font-family:var(--mono);font-size:12.5px}
798
- .termbar .tbg{display:flex;gap:5px;align-items:center;font-size:11.5px;color:var(--muted);
799
- white-space:nowrap;cursor:pointer}
800
- .termbar .trun{padding:8px 15px;border-radius:8px;border:1px solid var(--cyan);color:var(--cyan);
801
- background:transparent;cursor:pointer;font-size:12.5px}
802
- .termbar .trun:hover{background:var(--panel-2)}
803
- .termsplit{flex:1;display:flex;min-height:0}
804
- .tjobs{flex:0 0 250px;overflow-y:auto;border-right:1px solid var(--line);background:var(--panel)}
805
- .tout{flex:1;min-width:0;overflow:auto;padding:12px 14px;font-family:var(--mono);font-size:11.5px;
806
- line-height:1.55;white-space:pre-wrap;word-break:break-word}
807
- .jrow{padding:9px 12px;border-bottom:1px solid var(--line);cursor:pointer;font-size:12px}
808
- .jrow:hover{background:var(--panel-2)}
809
- .jrow.on{background:var(--panel-2);box-shadow:inset 2px 0 0 var(--cyan)}
810
- .jrow .jc{font-family:var(--mono);font-size:11.5px;overflow:hidden;text-overflow:ellipsis;
811
- white-space:nowrap;color:var(--ink)}
812
- .jrow .jm{display:flex;gap:7px;align-items:center;margin-top:4px;font-size:10.5px;color:var(--dim)}
813
- .jrow .js{padding:1px 6px;border-radius:999px;border:1px solid var(--line)}
814
- .jrow .js.running{color:var(--cyan);border-color:var(--cyan)}
815
- .jrow .js.exited{color:var(--green)}
816
- .jrow .js.failed,.jrow .js.killed{color:var(--red)}
817
- .jrow .jk{margin-left:auto;background:none;border:0;color:var(--dim);cursor:pointer;font-size:11px}
818
- .jrow .jk:hover{color:var(--red)}
819
- .tout .oe{color:var(--red)}
811
+ .termttl{font-size:12px;color:var(--muted);letter-spacing:.02em}
812
+ .termgap{flex:0 0 10px}
813
+ .termbtn{padding:4px 10px;border-radius:7px;border:1px solid var(--line-2);color:var(--muted);
814
+ background:transparent;cursor:pointer;font-size:11.5px}
815
+ .termbtn:hover{border-color:var(--cyan);color:var(--cyan)}
816
+ .termchk{display:flex;gap:5px;align-items:center;font-size:11.5px;color:var(--muted);cursor:pointer}
817
+ .termchk input{accent-color:var(--cyan);cursor:pointer}
818
+ #tbgop{width:88px;accent-color:var(--cyan)}
819
+
820
+ .termpanes{--term-font:12.5px;--term-bg-strength:0;--term-bg-image:none;
821
+ flex:1;display:flex;min-height:0;background:var(--bg)}
822
+ .termgrip{flex:0 0 5px;cursor:col-resize;background:var(--line);opacity:.6}
823
+ .termgrip:hover{background:var(--cyan);opacity:1}
824
+
825
+ .termpane{position:relative;display:flex;flex-direction:column;min-width:0;min-height:0;
826
+ border-left:1px solid transparent}
827
+ .termpane.on{border-left-color:var(--cyan)}
828
+ /*
829
+ * The backdrop sits behind the text at a weight the reader chooses, and never on top of it.
830
+ *
831
+ * Its own layer rather than a background on the pane: an image behind live text needs to be dimmed
832
+ * without dimming the text, and an opacity on the pane itself would take the characters with it.
833
+ */
834
+ .termpane::before{content:'';position:absolute;inset:0;pointer-events:none;
835
+ background-image:var(--term-bg-image);background-size:cover;background-position:center;
836
+ opacity:var(--term-bg-strength);transition:opacity .18s ease}
837
+ .termscroll{position:relative;flex:1;min-height:0;overflow:auto;padding:12px 14px 4px}
838
+ .termbody{margin:0;font-family:var(--mono);font-size:var(--term-font);line-height:1.55;
839
+ white-space:pre-wrap;word-break:break-word;color:var(--ink)}
840
+ .termrow{position:relative;display:flex;gap:8px;align-items:center;padding:2px 14px 12px}
841
+ .termps{font-family:var(--mono);font-size:var(--term-font);color:var(--cyan);flex:0 0 auto}
842
+ /*
843
+ * The input is the last line of the scrollback, not a control under it: no border, no background,
844
+ * the same font and size as the text above, and a caret that looks like a terminal's.
845
+ */
846
+ .termin{flex:1;min-width:0;border:0;outline:0;background:transparent;color:var(--ink);
847
+ font-family:var(--mono);font-size:var(--term-font);line-height:1.55;caret-color:var(--cyan);
848
+ padding:0}
849
+ .termin::placeholder{color:var(--dim)}
850
+ .termstop{flex:0 0 auto;padding:2px 9px;border-radius:6px;border:1px solid var(--red);
851
+ color:var(--red);background:transparent;cursor:pointer;font-size:10.5px}
852
+ .termpanes .oe{color:var(--red)}
820
853
 
821
854
  /* ── the page, as it really renders ───────────────────────────────────────────────────────── */
822
855
  .pvbar{display:flex;gap:8px;align-items:center;padding:11px 14px;background:var(--panel);
@@ -1 +1 @@
1
- {"version":3,"file":"ui-css.js","sourceRoot":"","sources":["../../src/web/ui-css.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH;;;;;;;;;;;;GAYG;AACH,MAAM,IAAI,GAAG;;;;;;;;;;;;;;;;;;CAkBZ,CAAC;AAEF,MAAM,UAAU,GAAG,CAAC,OAAe;IACjC,OAAO;;;;;;;;;;;;;;;;;gBAiBO,OAAO;;;oCAGa,IAAI;;2BAEb,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAg5B9B,CAAC;AACF,CAAC"}
1
+ {"version":3,"file":"ui-css.js","sourceRoot":"","sources":["../../src/web/ui-css.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH;;;;;;;;;;;;GAYG;AACH,MAAM,IAAI,GAAG;;;;;;;;;;;;;;;;;;CAkBZ,CAAC;AAEF,MAAM,UAAU,GAAG,CAAC,OAAe;IACjC,OAAO;;;;;;;;;;;;;;;;;gBAiBO,OAAO;;;oCAGa,IAAI;;2BAEb,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAi7B9B,CAAC;AACF,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../../src/web/ui.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAOH,wBAAgB,IAAI,IAAI,MAAM,CAmW7B"}
1
+ {"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../../src/web/ui.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAOH,wBAAgB,IAAI,IAAI,MAAM,CAiX7B"}
package/dist/web/ui.js CHANGED
@@ -127,21 +127,35 @@ export function page() {
127
127
  </div>
128
128
 
129
129
  <!-- A terminal. Commands you run yourself, in the foreground or left running behind. -->
130
+ <!--
131
+ A terminal, not a form with an output box.
132
+ ==========================================
133
+ What was here was a job list beside a pane of text: you typed into a field at the top, picked a
134
+ row, and read the result. Everything about it said "web page". A terminal is a scrollback where
135
+ the command you typed stays visible above what it printed, the cursor waits at the bottom, and
136
+ the next thing you type continues the same column of text.
137
+
138
+ Up to three panes, because two is the common case for "watch the server, run the tests" and
139
+ four on a laptop is four things too narrow to read. Each pane is its own shell with its own
140
+ scrollback and its own history; the dividers between them are draggable.
141
+ -->
130
142
  <div class="view" id="terminalview" hidden>
131
- <div class="termwrap">
132
- <div class="termbar">
133
- <span class="tprompt">$</span>
134
- <input id="tcmd" placeholder="npm test, git status, npm run dev&hellip;"
135
- autocomplete="off" spellcheck="false">
136
- <label class="tbg" title="Leave it running for a dev server, or anything that does not exit">
137
- <input type="checkbox" id="tbackground"> background
138
- </label>
139
- <button class="trun" id="trun">Run</button>
140
- </div>
141
- <div class="termsplit">
142
- <div class="tjobs" id="tjobs"></div>
143
- <div class="tout" id="tout"></div>
144
- </div>
143
+ <div class="termtop">
144
+ <span class="termttl">Terminal</span>
145
+ <button type="button" class="termbtn" id="tsplit" title="Add a pane (up to three)">split</button>
146
+ <button type="button" class="termbtn" id="tunsplit" title="Close the last pane">unsplit</button>
147
+ <span class="termgap"></span>
148
+ <label class="termchk" title="A picture behind the text, at a weight that keeps it readable">
149
+ <input type="checkbox" id="tbgimg"> backdrop
150
+ </label>
151
+ <input type="range" id="tbgop" min="4" max="40" value="14" title="How strongly it shows"
152
+ aria-label="Backdrop strength">
153
+ <span class="termgap"></span>
154
+ <button type="button" class="termbtn" id="tfontdown" title="Smaller text">A&minus;</button>
155
+ <button type="button" class="termbtn" id="tfontup" title="Larger text">A+</button>
156
+ </div>
157
+ <div class="termpanes" id="termpanes"></div>
158
+ </div>
145
159
  </div>
146
160
  </div>
147
161
 
@@ -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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAuVF,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC;EAC7C,YAAY,EAAE;;eAED,CAAC;AAChB,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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAqWF,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC;EAC7C,YAAY,EAAE;;eAED,CAAC;AAChB,CAAC"}
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "koneck",
3
- "version": "2.56.0",
3
+ "version": "2.57.0",
4
4
  "description": "Kinetically Orchestrated Neural Execution Engine for Code",
5
5
  "type": "module",
6
6
  "bin": {
@@ -30,6 +30,7 @@
30
30
  ],
31
31
  "files": [
32
32
  "dist/",
33
+ "img/",
33
34
  "README.md",
34
35
  "LICENSE"
35
36
  ],