koneck 2.68.0 → 2.70.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.
@@ -382,6 +382,25 @@ function onEvent(e) {
382
382
  const box = el('div', 'msg');
383
383
  box.appendChild(el('div', 'who', e.images ? 'you · ' + e.images + ' image(s)' : 'you'));
384
384
  box.appendChild(el('div', 'you', e.text));
385
+ /*
386
+ * What an @ actually attached, under the message.
387
+ *
388
+ * The sentence stays the sentence — nobody wants to scroll past eight hundred lines of their
389
+ * own attachment to find what they asked. But the attachment is a real spend and is stated,
390
+ * including the ones that failed, because a reference that quietly resolved to nothing is how
391
+ * you end up asking why the model ignored the file you gave it.
392
+ */
393
+ if (e.refs && e.refs.length) {
394
+ const row = el('div', 'urefs');
395
+ e.refs.forEach((r) => {
396
+ const bad = r.kind !== 'file' && r.kind !== 'dir';
397
+ row.appendChild(el('span', bad ? 'uref bad' : 'uref',
398
+ bad ? '@' + r.ref + ' — ' + (r.note || 'not attached')
399
+ : '@' + r.ref + (r.kind === 'dir' ? '/ · ' + r.lines + ' entries'
400
+ : ' · ' + r.lines + ' lines')));
401
+ });
402
+ box.appendChild(row);
403
+ }
385
404
  add(box);
386
405
  state.replyEl = null; state.thinkEl = null;
387
406
  setBusy(true, 'thinking');
@@ -2357,6 +2376,7 @@ async function newSession() {
2357
2376
 
2358
2377
  async function send(text) {
2359
2378
  const ta = $('ta');
2379
+ closeMention();
2360
2380
  text = text || ta.value.trim();
2361
2381
  if (!text || state.busy) return;
2362
2382
  // A slash command is not a prompt. It opens the thing that does the same job.
@@ -2380,6 +2400,238 @@ async function send(text) {
2380
2400
  } catch (e) { add(el('div', 'err', e.message)); setBusy(false); }
2381
2401
  }
2382
2402
 
2403
+ /* ── referencing a file with @ ──────────────────────────────── */
2404
+ /*
2405
+ * The picker, and why it ranks rather than lists.
2406
+ *
2407
+ * Every editor has @. What they mostly do with it is fuzzy-match filenames, which answers "which
2408
+ * paths contain these letters" and not "which file did you mean" — and with four thousand paths in
2409
+ * a project those are different questions. So the server ranks by what this project is actually
2410
+ * doing: files git has touched, files this session has already handled, files the ledger holds
2411
+ * findings about. The reason is shown in the row, because a ranking that will not say why it ranked
2412
+ * something is one you cannot argue with.
2413
+ *
2414
+ * And every row carries what attaching it will cost. That is the part that matters most here: an @
2415
+ * puts the file in the prompt, and a picker that hides the price of what it is about to spend is
2416
+ * how somebody ends up wondering where their context window went.
2417
+ */
2418
+ const mention = {
2419
+ open: false,
2420
+ items: [],
2421
+ sel: 0,
2422
+ /* Where the @ sits in the textarea, so the replacement knows what to replace. */
2423
+ start: -1,
2424
+ query: '',
2425
+ /* Requests can land out of order over a slow link; only the newest answer may draw. */
2426
+ seq: 0,
2427
+ timer: null,
2428
+ };
2429
+
2430
+ /*
2431
+ * Where an @ reference being typed begins, or null.
2432
+ *
2433
+ * The same rule as the terminal's: an @ only opens a reference at a word boundary, so an email
2434
+ * address or a decorator in pasted code does not trigger it, and a space closes it because the
2435
+ * paths offered here never contain one.
2436
+ */
2437
+ function activeRef(draft, caret) {
2438
+ const upto = draft.slice(0, caret);
2439
+ const at = upto.lastIndexOf('@');
2440
+ if (at === -1) return null;
2441
+ const before = at === 0 ? '' : upto[at - 1];
2442
+ if (before !== '' && !/\s/.test(before)) return null;
2443
+ const query = upto.slice(at + 1);
2444
+ if (/\s/.test(query)) return null;
2445
+ return { start: at, query: query };
2446
+ }
2447
+
2448
+ function closeMention() {
2449
+ if (mention.timer) { clearTimeout(mention.timer); mention.timer = null; }
2450
+ mention.open = false;
2451
+ mention.items = [];
2452
+ mention.start = -1;
2453
+ $('mention').hidden = true;
2454
+ }
2455
+
2456
+ /*
2457
+ * The rows.
2458
+ *
2459
+ * The filename is emboldened and the directory left plain, because the eye is looking for the name
2460
+ * and the folder is only there to tell two of them apart. Built with el() and textContent — these
2461
+ * are paths from disk, and a filename is as capable of holding a bracket as anything else is.
2462
+ */
2463
+ function drawMention() {
2464
+ const box = $('mention');
2465
+ box.textContent = '';
2466
+
2467
+ const head = el('div', 'mhead');
2468
+ head.appendChild(el('span', null, mention.query ? 'files matching ' + mention.query : 'what you are working on'));
2469
+ head.appendChild(el('span', 'mgap'));
2470
+ head.appendChild(el('span', 'mkeys', '↑↓ move · ⏎ or tab insert · esc close'));
2471
+ box.appendChild(head);
2472
+
2473
+ if (mention.items.length === 0) {
2474
+ box.appendChild(el('div', 'mnone', mention.query
2475
+ ? 'Nothing in this project matches ' + mention.query + '.'
2476
+ : 'Nothing recent to offer yet — type to search the project.'));
2477
+ box.hidden = false;
2478
+ return;
2479
+ }
2480
+
2481
+ mention.items.forEach((item, i) => {
2482
+ const row = el('div', 'mrow' + (i === mention.sel ? ' on' : ''));
2483
+
2484
+ /*
2485
+ * The path, with the name picked out.
2486
+ *
2487
+ * direction:rtl in the stylesheet keeps the filename visible when a deep path has to be
2488
+ * truncated — a left-truncated path is readable and a right-truncated one is not, since
2489
+ * everything that distinguishes it is at the end.
2490
+ */
2491
+ const p = el('div', 'mp');
2492
+ // One isolated LTR run, for the same reason the watch header needs one: the box is RTL so the
2493
+ // front is what gets truncated, and an RTL box reorders the slashes at either edge.
2494
+ const run = el('span', 'pathltr');
2495
+ const cut = item.path.lastIndexOf('/');
2496
+ if (cut > -1) run.appendChild(document.createTextNode(item.path.slice(0, cut + 1)));
2497
+ run.appendChild(el('b', null, cut > -1 ? item.path.slice(cut + 1) : item.path));
2498
+ p.appendChild(run);
2499
+ row.appendChild(p);
2500
+
2501
+ if (item.because && item.because.length) {
2502
+ const why = el('div', 'mwhy');
2503
+ item.because.forEach((reason, n) => {
2504
+ /*
2505
+ * The wording comes from the server.
2506
+ *
2507
+ * A row is a row and not a sentence, so "used in this session" is shown as "in session" —
2508
+ * but the shortening happens once, server-side, and the terminal's picker uses the same
2509
+ * function. Two copies of the same phrasing is how two surfaces end up describing the same
2510
+ * signal differently.
2511
+ */
2512
+ const cls = reason === 'used in this session' ? 's'
2513
+ : reason === 'recently changed' ? 'g' : '';
2514
+ why.appendChild(el('span', cls || null, (item.shorts && item.shorts[n]) || reason));
2515
+ });
2516
+ row.appendChild(why);
2517
+ }
2518
+
2519
+ row.appendChild(item.kind === 'dir'
2520
+ ? el('div', 'mdir', 'folder · listing')
2521
+ : el('div', 'mcost', item.tokens === null ? '' : approxTokens(item.tokens)));
2522
+
2523
+ row.onmousedown = (ev) => { ev.preventDefault(); pickMention(i); };
2524
+ box.appendChild(row);
2525
+ });
2526
+ box.hidden = false;
2527
+
2528
+ const chosen = box.children[mention.sel + 1];
2529
+ if (chosen && chosen.scrollIntoView) chosen.scrollIntoView({ block: 'nearest' });
2530
+ }
2531
+
2532
+ /* Approximate, and never dressed up as exact — the tilde is doing real work. */
2533
+ function approxTokens(n) {
2534
+ if (n < 1000) return '~' + n + ' tok';
2535
+ return '~' + (Math.round(n / 100) / 10) + 'k tok';
2536
+ }
2537
+
2538
+ /*
2539
+ * Asks the server for candidates.
2540
+ *
2541
+ * Debounced, and the answer is discarded unless it is the newest one asked for: typing four
2542
+ * characters quickly fires four requests, and without the sequence check the second can draw over
2543
+ * the fourth and leave the list one keystroke behind the box.
2544
+ */
2545
+ function refreshMention(fresh) {
2546
+ const mine = ++mention.seq;
2547
+ const q = mention.query;
2548
+ const url = '/api/mention?q=' + encodeURIComponent(q)
2549
+ + (state.session ? '&session=' + encodeURIComponent(state.session) : '')
2550
+ + (fresh ? '&fresh=1' : '');
2551
+ api(url).then((out) => {
2552
+ if (mine !== mention.seq || !mention.open) return;
2553
+ mention.items = out.candidates || [];
2554
+ if (mention.sel >= mention.items.length) mention.sel = 0;
2555
+ drawMention();
2556
+ }).catch(() => {
2557
+ if (mine !== mention.seq || !mention.open) return;
2558
+ mention.items = [];
2559
+ drawMention();
2560
+ });
2561
+ }
2562
+
2563
+ /*
2564
+ * Replaces the half-typed reference with the chosen path.
2565
+ *
2566
+ * A trailing space, because the reference is finished and the next thing typed is a sentence — and
2567
+ * without it the picker reopens on the path it just inserted.
2568
+ */
2569
+ function pickMention(i) {
2570
+ const item = mention.items[i];
2571
+ if (!item) return;
2572
+ const ta = $('ta');
2573
+ const value = ta.value;
2574
+ const end = mention.start + 1 + mention.query.length;
2575
+ const inserted = '@' + item.path + (item.kind === 'dir' ? '/' : '') + ' ';
2576
+ ta.value = value.slice(0, mention.start) + inserted + value.slice(end);
2577
+ const caret = mention.start + inserted.length;
2578
+ closeMention();
2579
+ ta.focus();
2580
+ ta.setSelectionRange(caret, caret);
2581
+ ta.dispatchEvent(new Event('input'));
2582
+ }
2583
+
2584
+ /*
2585
+ * Called on every keystroke in the composer.
2586
+ *
2587
+ * The listing and the project signals are refreshed when the reference opens rather than while it
2588
+ * is being typed: opening is the moment a file created a second ago would be noticed missing, and
2589
+ * every keystroke after it only needs the ranking.
2590
+ */
2591
+ function syncMention() {
2592
+ const ta = $('ta');
2593
+ const ref = activeRef(ta.value, ta.selectionStart);
2594
+ if (!ref) { if (mention.open) closeMention(); return; }
2595
+ const opening = !mention.open || ref.start !== mention.start;
2596
+ mention.open = true;
2597
+ mention.start = ref.start;
2598
+ mention.query = ref.query;
2599
+ if (opening) { mention.sel = 0; mention.items = []; drawMention(); }
2600
+ if (mention.timer) clearTimeout(mention.timer);
2601
+ /* No wait at all when it opens: the first list should already be there. */
2602
+ if (opening) { refreshMention(true); return; }
2603
+ mention.timer = setTimeout(() => { mention.timer = null; refreshMention(false); }, 55);
2604
+ }
2605
+
2606
+ /* True when the key was the picker's, so the composer must not also act on it. */
2607
+ function mentionKey(ev) {
2608
+ if (!mention.open) return false;
2609
+ if (ev.key === 'Escape') { closeMention(); return true; }
2610
+ if (ev.key === 'ArrowDown' || (ev.key === 'n' && ev.ctrlKey)) {
2611
+ if (mention.items.length) { mention.sel = (mention.sel + 1) % mention.items.length; drawMention(); }
2612
+ return true;
2613
+ }
2614
+ if (ev.key === 'ArrowUp' || (ev.key === 'p' && ev.ctrlKey)) {
2615
+ if (mention.items.length) {
2616
+ mention.sel = (mention.sel - 1 + mention.items.length) % mention.items.length;
2617
+ drawMention();
2618
+ }
2619
+ return true;
2620
+ }
2621
+ if (ev.key === 'Tab' || (ev.key === 'Enter' && !ev.shiftKey)) {
2622
+ /*
2623
+ * Enter inserts while the picker is open, and only then.
2624
+ *
2625
+ * A half-typed @src/eng followed by Enter is somebody choosing a file, not sending a message —
2626
+ * but with no candidates there is nothing to choose, so it falls through and sends.
2627
+ */
2628
+ if (mention.items.length === 0) return false;
2629
+ pickMention(mention.sel);
2630
+ return true;
2631
+ }
2632
+ return false;
2633
+ }
2634
+
2383
2635
  /* ── wiring ────────────────────────────────────────────────── */
2384
2636
  $('ta').addEventListener('input', (ev) => {
2385
2637
  const ta = ev.target;
@@ -2387,10 +2639,30 @@ $('ta').addEventListener('input', (ev) => {
2387
2639
  ta.style.height = Math.min(ta.scrollHeight, 200) + 'px';
2388
2640
  $('send').disabled = state.busy || !ta.value.trim();
2389
2641
  refreshComposerHints();
2642
+ syncMention();
2390
2643
  });
2391
2644
  $('ta').addEventListener('focus', refreshComposerHints);
2392
- $('ta').addEventListener('blur', refreshComposerHints);
2645
+ $('ta').addEventListener('blur', () => {
2646
+ refreshComposerHints();
2647
+ /*
2648
+ * Closed on blur, but not before a click on a row has landed.
2649
+ *
2650
+ * The rows are wired to mousedown, which fires before blur, so this only ever closes a picker
2651
+ * nobody was clicking in. Without the delay a click on a candidate closed the list under the
2652
+ * pointer and inserted nothing.
2653
+ */
2654
+ setTimeout(() => { if (document.activeElement !== $('ta')) closeMention(); }, 120);
2655
+ });
2656
+ /* Clicking or arrowing to a different place in the text can leave a reference, or enter one. */
2657
+ $('ta').addEventListener('click', syncMention);
2393
2658
  $('ta').addEventListener('keydown', (ev) => {
2659
+ // The picker gets first refusal on the keys it uses, so Enter chooses a file rather than sending
2660
+ // a message that was still being addressed.
2661
+ if (mentionKey(ev)) { ev.preventDefault(); return; }
2662
+ if (ev.key === 'ArrowLeft' || ev.key === 'ArrowRight' || ev.key === 'Home' || ev.key === 'End') {
2663
+ // After the caret has actually moved, not before.
2664
+ setTimeout(syncMention, 0);
2665
+ }
2394
2666
  if (ev.key === 'Enter' && !ev.shiftKey) { ev.preventDefault(); send(); }
2395
2667
  });
2396
2668
  $('send').onclick = () => send();
@@ -3848,7 +4120,16 @@ function stopWatchClock() {
3848
4120
  /** The header: the verb, the target, and how long this step has been going. */
3849
4121
  function watchHead(verb, target) {
3850
4122
  $('wtool').textContent = verb;
3851
- $('wpath').textContent = target;
4123
+ /*
4124
+ * The path in an isolated LTR run.
4125
+ *
4126
+ * Set as text directly, the box's own direction:rtl — which is what truncates the front rather
4127
+ * than the end — moved the leading slash to the far side, so /home/kona/…/page.tsx was shown as
4128
+ * home/kona/…/page.tsx/. The slash was never in the data.
4129
+ */
4130
+ const held = $('wpath');
4131
+ held.textContent = '';
4132
+ held.appendChild(el('span', 'pathltr', target));
3852
4133
  follow.startedAt = Date.now();
3853
4134
  if (!follow.timer) {
3854
4135
  follow.timer = setInterval(() => {
@@ -4068,29 +4349,165 @@ function prettyArgs(args) {
4068
4349
  }
4069
4350
 
4070
4351
  /*
4071
- * The write in progress, repainted as its content arrives.
4352
+ * The write in progress, coloured as it arrives.
4072
4353
  *
4073
- * Plain while it streams and coloured the moment it lands: highlighting needs a round trip per
4074
- * repaint, and four of those a second to colour text that is about to change again is a cost with
4075
- * no reader. The finished render replaces it with the coloured version a fraction of a second
4076
- * later, which is what the eye actually follows.
4354
+ * This used to be deliberately plain, on the reasoning that colouring needs a round trip per
4355
+ * repaint and four of those a second to colour text about to change again was a cost with no
4356
+ * reader. Two thirds of that was wrong. There is a reader — the panel exists to be read while the
4357
+ * work happens, and uncoloured code is the one thing there that looks unfinished. And the cost was
4358
+ * never measured: tokenising a 610-line tsx file is 7.7ms, and the expensive part was never the
4359
+ * highlighting at all, it was rebuilding every row on every repaint.
4360
+ *
4361
+ * So it paints incrementally, which a streaming write allows because it is append-only:
4362
+ *
4363
+ * - the new lines are appended plain, immediately, so the text never waits for colour;
4364
+ * - the server is asked to colour only the lines that arrived, debounced;
4365
+ * - the returned colours are painted into the rows already on screen.
4366
+ *
4367
+ * When the content is not an extension of what was there — a replace, or the model starting over —
4368
+ * the prefix check fails and it repaints from scratch, because guessing wrong about that shows
4369
+ * somebody the wrong file.
4077
4370
  */
4078
4371
  function drawWriteInProgress(path, content) {
4079
4372
  const host = $('wbody');
4080
4373
  let head = host.querySelector('.wwrite');
4081
- if (!head) {
4374
+ const fresh = !head || write.path !== path || !content.startsWith(write.text);
4375
+ if (fresh) {
4082
4376
  host.innerHTML = '';
4083
4377
  head = el('div', 'wwrite');
4084
4378
  host.appendChild(head);
4085
- host.appendChild(el('div', 'wwbody'));
4379
+ const body = el('div', 'wwbody');
4380
+ /*
4381
+ * An empty frame, built here rather than by drawCode.
4382
+ *
4383
+ * drawCode('') is not empty: an empty string splits into one line, so it produced a placeholder
4384
+ * row and every index after it was off by one. That was worse than a cosmetic slip — painting
4385
+ * replaces a row's text with the token text, so row N was being given line N+1's content and
4386
+ * the panel showed text that was never written on that line. Found by comparing each rendered
4387
+ * row against its gutter number: the first mismatch was at index 1.
4388
+ */
4389
+ const frame = el('div', 'rows');
4390
+ frame.appendChild(el('div', 'gut'));
4391
+ frame.appendChild(el('div', 'src'));
4392
+ body.appendChild(frame);
4393
+ host.appendChild(body);
4394
+ write.path = path;
4395
+ write.text = '';
4396
+ write.done = 0;
4397
+ write.asked = 0;
4398
+ // Anything still in flight for the previous file belongs to a generation that no longer exists.
4399
+ write.gen++;
4400
+ if (write.timer) { clearTimeout(write.timer); write.timer = null; }
4086
4401
  }
4402
+
4087
4403
  const lines = content.split('\n');
4088
4404
  head.textContent = 'writing ' + path + ' — ' + lines.length
4089
4405
  + (lines.length === 1 ? ' line so far' : ' lines so far');
4090
- const body = host.querySelector('.wwbody');
4091
- body.innerHTML = '';
4092
- body.appendChild(drawCode(content, [], null, null));
4093
- host.scrollTop = host.scrollHeight;
4406
+
4407
+ /*
4408
+ * The last line is left off until something follows it.
4409
+ *
4410
+ * A streaming write's final line is half-typed by definition, so painting it and then repainting
4411
+ * it on the next frame makes the bottom row flicker for the whole write. Holding it back one
4412
+ * frame costs nothing — the next chunk is milliseconds away — and the bottom of the panel stops
4413
+ * jittering.
4414
+ */
4415
+ const ready = lines.length - 1;
4416
+ const rows = host.querySelector('.rows');
4417
+ const gut = rows.querySelector('.gut');
4418
+ const src = rows.querySelector('.src');
4419
+
4420
+ const wasAtEnd = host.scrollHeight - host.scrollTop - host.clientHeight < 60;
4421
+ for (let i = write.done; i < ready; i++) {
4422
+ gut.appendChild(el('div', null, String(i + 1)));
4423
+ const row = el('div');
4424
+ row.textContent = lines[i] === '' ? '​' : lines[i];
4425
+ src.appendChild(row);
4426
+ }
4427
+ if (ready > write.done) write.done = ready;
4428
+ write.text = content;
4429
+ // Follows the tail, unless somebody has scrolled up to look at something.
4430
+ if (wasAtEnd) host.scrollTop = host.scrollHeight;
4431
+
4432
+ askColour(path, content);
4433
+ }
4434
+
4435
+ /**
4436
+ * The state of the write being watched, so the next chunk knows what is already on screen.
4437
+ *
4438
+ * "done" is how many rows exist; "asked" is how many have been coloured or are being coloured, so a
4439
+ * slow answer cannot cause the same lines to be requested twice.
4440
+ */
4441
+ const write = { path: null, text: '', done: 0, asked: 0, timer: null, gen: 0 };
4442
+
4443
+ /*
4444
+ * Asks for the colours of the lines that have arrived since the last time.
4445
+ *
4446
+ * Debounced, because chunks arrive faster than anybody reads and a request per chunk would be a
4447
+ * request per keystroke of the model's. Coalescing means one request covers everything that landed
4448
+ * while the previous one was in flight, which is exactly the behaviour wanted: the colour lags the
4449
+ * text by one round trip and never by more.
4450
+ */
4451
+ function askColour(path, content) {
4452
+ if (write.timer) return;
4453
+ write.timer = setTimeout(() => {
4454
+ write.timer = null;
4455
+ if (write.path !== path || write.asked >= write.done) return;
4456
+ const from = write.asked;
4457
+ const upto = write.done;
4458
+ /*
4459
+ * Kept against the generation, not against a request counter.
4460
+ *
4461
+ * The first version dropped any answer that was not the newest request, on the reasoning that a
4462
+ * stale answer must not paint. That is true of a *different file* and false of these: two
4463
+ * requests in flight cover different rows, so discarding the older one left the rows it covered
4464
+ * plain for ever while the newer ones coloured around them. Visible in a screenshot as line 28
4465
+ * coloured, 29 plain, 30 onward coloured — which is what sent me looking.
4466
+ *
4467
+ * The generation changes only when the panel resets to another file or the content stops being
4468
+ * an extension of itself, which is the only case where an in-flight answer is genuinely about
4469
+ * something else.
4470
+ */
4471
+ const gen = write.gen;
4472
+ write.asked = upto;
4473
+ api('/api/highlight', { method: 'POST',
4474
+ body: JSON.stringify({ path: path, text: write.text, from: from }) })
4475
+ .then((out) => {
4476
+ if (gen !== write.gen || write.path !== path || !out.tokens) return;
4477
+ paintRows(from, upto, out.tokens);
4478
+ })
4479
+ .catch(() => {
4480
+ // Colour is a bonus and the code is the point, so a failure leaves the plain rows alone —
4481
+ // but the lines are released so a later attempt can still colour them.
4482
+ if (gen === write.gen && write.asked === upto) write.asked = from;
4483
+ });
4484
+ }, 70);
4485
+ }
4486
+
4487
+ /**
4488
+ * Replaces the plain text of rows from..upto with their coloured spans.
4489
+ *
4490
+ * Each row is checked against the text the colours are for before it is touched. That guard is not
4491
+ * defensive habit: painting replaces a row's content with the token text, so an index that is off by
4492
+ * one does not merely mis-colour a line, it shows text that was never written on it. One off-by-one
4493
+ * had already done exactly that. With the check, the worst a future one can do is leave a line
4494
+ * uncoloured, which is a thing somebody can notice and nobody can be misled by.
4495
+ */
4496
+ function paintRows(from, upto, tokens) {
4497
+ const src = $('wbody').querySelector('.src');
4498
+ if (!src) return;
4499
+ for (let i = from; i < upto; i++) {
4500
+ const row = src.children[i];
4501
+ const forLine = tokens[i - from];
4502
+ if (!row || !forLine || !forLine.length) continue;
4503
+ const held = row.textContent;
4504
+ let joined = '';
4505
+ for (const t of forLine) joined += (t && t.text) || '';
4506
+ // The zero-width space stands in for an empty line, so an empty row matches empty tokens.
4507
+ if (joined !== held && !(held === '​' && joined === '')) continue;
4508
+ row.textContent = '';
4509
+ if (!paintTokens(row, forLine, null)) row.textContent = held;
4510
+ }
4094
4511
  }
4095
4512
 
4096
4513
  /** Paints the content a write is carrying, coloured for the file it is going into. */
@@ -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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2nJlB,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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4hKlB,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,CA4kC3C"}
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,CAkoC3C"}
@@ -406,6 +406,45 @@ footer{flex:0 0 auto;border-top:1px solid var(--line);background:var(--panel);pa
406
406
  .live .lel{color:var(--dim);font-variant-numeric:tabular-nums;font-size:11.5px}
407
407
  .live .lstop{border:1px solid var(--red);color:var(--red);border-radius:999px;padding:2px 10px;
408
408
  font-size:11.5px}
409
+ /*
410
+ * The @ picker.
411
+ *
412
+ * A fixed height with its own scroll, because the alternative is a list that resizes the footer on
413
+ * every keystroke — and a composer that moves while you type in it is the thing that makes a picker
414
+ * feel unfinished. Anchored to the box's width so the paths line up with the text they will become.
415
+ */
416
+ .mention{margin-bottom:9px;border:1px solid var(--line-2);border-radius:var(--radius);
417
+ background:var(--panel);max-height:min(46vh,340px);overflow-y:auto;overflow-x:hidden;
418
+ box-shadow:0 -6px 26px rgba(0,0,0,.22)}
419
+ .mention[hidden]{display:none}
420
+ .mhead{display:flex;align-items:center;gap:8px;padding:7px 11px;font-size:10.5px;
421
+ letter-spacing:.09em;text-transform:uppercase;color:var(--dim);
422
+ border-bottom:1px solid var(--line);position:sticky;top:0;background:var(--panel);z-index:1}
423
+ .mhead .mgap{flex:1}
424
+ .mhead .mkeys{text-transform:none;letter-spacing:0;font-size:11px}
425
+ .mrow{display:flex;align-items:baseline;gap:9px;padding:6px 11px;cursor:pointer;
426
+ border-left:2px solid transparent}
427
+ .mrow:hover{background:var(--panel-2)}
428
+ .mrow.on{background:var(--panel-2);border-left-color:var(--cyan)}
429
+ /* The name carries the weight; the directory it sits in is context, not the answer. */
430
+ .mrow .mp{font-family:var(--mono);font-size:12.5px;color:var(--muted);flex:1;min-width:0;
431
+ overflow:hidden;text-overflow:ellipsis;white-space:nowrap;direction:rtl;text-align:left}
432
+ .mrow .mp b{color:var(--ink);font-weight:600}
433
+ .mrow .mwhy{display:flex;gap:5px;flex:0 0 auto}
434
+ /* Why it is where it is, said in the row rather than in a tooltip nobody opens. */
435
+ .mwhy span{font-size:9.5px;letter-spacing:.04em;padding:1px 6px;border-radius:999px;
436
+ border:1px solid var(--line-2);color:var(--dim);white-space:nowrap}
437
+ .mwhy span.s{border-color:var(--cyan);color:var(--cyan)}
438
+ .mwhy span.g{border-color:var(--amber);color:var(--amber)}
439
+ /* Tabular so the costs form a column you can compare down, rather than a ragged edge. */
440
+ .mrow .mcost{flex:0 0 auto;font-size:11px;color:var(--dim);font-variant-numeric:tabular-nums}
441
+ .mrow .mdir{flex:0 0 auto;font-size:11px;color:var(--dim)}
442
+ .mnone{padding:10px 11px;font-size:12.5px;color:var(--dim)}
443
+ /* What an @ attached, under the message it was in. */
444
+ .urefs{display:flex;gap:6px;flex-wrap:wrap;margin-top:6px}
445
+ .uref{font-family:var(--mono);font-size:10.5px;color:var(--dim);padding:1px 7px;
446
+ border:1px solid var(--line-2);border-radius:999px}
447
+ .uref.bad{border-color:var(--red);color:var(--red)}
409
448
  .attached{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:9px}
410
449
  .attached[hidden]{display:none}
411
450
  .thumb{position:relative;width:60px;height:60px;border-radius:8px;overflow:hidden;
@@ -649,6 +688,21 @@ dialog:has(.dlg.wide){max-width:560px}
649
688
  border-bottom:1px solid var(--line);font-size:11.5px;flex:0 0 auto}
650
689
  .whead .wt{color:var(--cyan);font-size:10px;letter-spacing:.09em;text-transform:uppercase;
651
690
  flex:0 0 auto}
691
+ /*
692
+ * A path truncated at the front, without bidi rearranging it.
693
+ *
694
+ * direction:rtl is what puts the ellipsis at the start, which is the only useful place for a path —
695
+ * everything that identifies it is at the end. But a slash is a bidi-neutral character, so in an
696
+ * RTL paragraph the leading one is reordered to the far side and /home/kona/…/page.tsx renders as
697
+ * home/kona/…/page.tsx/ — which is what somebody reported seeing, and it was not a stray character
698
+ * in the data.
699
+ *
700
+ * The fix is an isolated LTR run inside the RTL box: the truncation stays at the front and the text
701
+ * inside is laid out as the plain string it is. Checked in Chromium against the alternatives —
702
+ * unicode-bidi:plaintext fixes the order but moves the ellipsis to the end, which throws away the
703
+ * filename.
704
+ */
705
+ .pathltr{direction:ltr;unicode-bidi:isolate}
652
706
  .whead .wp{font-family:var(--mono);font-size:12px;flex:1;min-width:0;overflow:hidden;
653
707
  text-overflow:ellipsis;white-space:nowrap;direction:rtl;text-align:left}
654
708
  .whead .wms{flex:0 0 auto;font-size:10.5px;color:var(--dim);font-variant-numeric:tabular-nums;
@@ -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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAojC9B,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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0mC9B,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,CA+d7B"}
1
+ {"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../../src/web/ui.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAOH,wBAAgB,IAAI,IAAI,MAAM,CAwe7B"}
package/dist/web/ui.js CHANGED
@@ -214,6 +214,14 @@ export function page() {
214
214
  <button class="lstop" id="livestop">stop</button>
215
215
  </div>
216
216
  <div id="attached" class="attached" hidden></div>
217
+ <!--
218
+ Referencing a file with @.
219
+ ==========================
220
+ Above the box rather than below it, so the list grows up into the transcript instead of
221
+ pushing the input off the bottom of the window — which is where a dropdown under a
222
+ bottom-anchored composer ends up on a short screen.
223
+ -->
224
+ <div id="mention" class="mention" hidden></div>
217
225
  <div class="cin">
218
226
  <button class="attach" id="attach" title="Attach an image">+</button>
219
227
  <textarea id="ta" rows="1" placeholder="Describe what you want to build&hellip;"></textarea>
@@ -246,6 +254,7 @@ export function page() {
246
254
  <span><kbd>Enter</kbd> send</span>
247
255
  <span><kbd>Shift</kbd>+<kbd>Enter</kbd> newline</span>
248
256
  <span><kbd>Ctrl</kbd>+<kbd>K</kbd> commands</span>
257
+ <span><kbd>@</kbd> reference a file or folder</span>
249
258
  <span><kbd>/</kbd> for commands, paste or drop an image</span>
250
259
  </div>
251
260
  </div>
@@ -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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAmdF,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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBA4dF,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC;EAC7C,YAAY,EAAE;;eAED,CAAC;AAChB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "koneck",
3
- "version": "2.68.0",
3
+ "version": "2.70.0",
4
4
  "description": "Kinetically Orchestrated Neural Execution Engine for Code",
5
5
  "type": "module",
6
6
  "bin": {