@h-rig/cli 0.0.6-alpha.85 → 0.0.6-alpha.87

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.
Files changed (69) hide show
  1. package/dist/bin/rig.js +79 -1027
  2. package/dist/src/app-opentui/bootstrap.js +79 -1027
  3. package/dist/src/app-opentui/command-palette.js +1 -0
  4. package/dist/src/app-opentui/drone.js +1 -0
  5. package/dist/src/app-opentui/index.js +61 -1024
  6. package/dist/src/app-opentui/list-search.d.ts +5 -1
  7. package/dist/src/app-opentui/list-search.js +2 -2
  8. package/dist/src/app-opentui/react/App.js +62 -970
  9. package/dist/src/app-opentui/react/Backdrop.js +3 -9
  10. package/dist/src/app-opentui/react/ChromeHost.js +6 -1038
  11. package/dist/src/app-opentui/react/SceneFrameView.js +55 -5
  12. package/dist/src/app-opentui/react/launch.js +62 -970
  13. package/dist/src/app-opentui/react/syntax.js +1 -0
  14. package/dist/src/app-opentui/registry.js +3 -5
  15. package/dist/src/app-opentui/render/constants.d.ts +0 -1
  16. package/dist/src/app-opentui/render/constants.js +0 -2
  17. package/dist/src/app-opentui/render/graphics.js +3 -9
  18. package/dist/src/app-opentui/render/hover.d.ts +11 -0
  19. package/dist/src/app-opentui/render/hover.js +34 -0
  20. package/dist/src/app-opentui/render/native-host.js +1 -0
  21. package/dist/src/app-opentui/render/panels.js +50 -3
  22. package/dist/src/app-opentui/render/preloader.js +1 -0
  23. package/dist/src/app-opentui/render/scene.js +1 -0
  24. package/dist/src/app-opentui/render/text.d.ts +13 -0
  25. package/dist/src/app-opentui/render/text.js +54 -5
  26. package/dist/src/app-opentui/render/type-bar.js +1 -0
  27. package/dist/src/app-opentui/runtime.js +62 -1025
  28. package/dist/src/app-opentui/scenes/command.js +1 -0
  29. package/dist/src/app-opentui/scenes/doctor.js +1 -0
  30. package/dist/src/app-opentui/scenes/error.js +1 -0
  31. package/dist/src/app-opentui/scenes/family-domains/agent.js +1 -0
  32. package/dist/src/app-opentui/scenes/family-domains/browser.js +1 -0
  33. package/dist/src/app-opentui/scenes/family-domains/dist.js +1 -0
  34. package/dist/src/app-opentui/scenes/family-domains/git.js +1 -0
  35. package/dist/src/app-opentui/scenes/family-domains/github.js +1 -0
  36. package/dist/src/app-opentui/scenes/family-domains/harness.js +1 -0
  37. package/dist/src/app-opentui/scenes/family-domains/index.js +1 -0
  38. package/dist/src/app-opentui/scenes/family-domains/kit.js +1 -0
  39. package/dist/src/app-opentui/scenes/family-domains/profile.js +1 -0
  40. package/dist/src/app-opentui/scenes/family-domains/queue.js +1 -0
  41. package/dist/src/app-opentui/scenes/family-domains/remote.js +1 -0
  42. package/dist/src/app-opentui/scenes/family-domains/review.js +1 -0
  43. package/dist/src/app-opentui/scenes/family-domains/setup.js +1 -0
  44. package/dist/src/app-opentui/scenes/family-domains/stats.js +1 -0
  45. package/dist/src/app-opentui/scenes/family.js +1 -0
  46. package/dist/src/app-opentui/scenes/fleet.js +3 -5
  47. package/dist/src/app-opentui/scenes/handoff.js +1 -0
  48. package/dist/src/app-opentui/scenes/help.js +1 -0
  49. package/dist/src/app-opentui/scenes/inbox.js +1 -0
  50. package/dist/src/app-opentui/scenes/init.js +1 -0
  51. package/dist/src/app-opentui/scenes/inspect.js +1 -0
  52. package/dist/src/app-opentui/scenes/main.js +1 -0
  53. package/dist/src/app-opentui/scenes/pi.js +1 -0
  54. package/dist/src/app-opentui/scenes/plugin.js +1 -0
  55. package/dist/src/app-opentui/scenes/repo.js +1 -0
  56. package/dist/src/app-opentui/scenes/run-detail.js +1 -0
  57. package/dist/src/app-opentui/scenes/server.js +1 -0
  58. package/dist/src/app-opentui/scenes/tasks.js +3 -5
  59. package/dist/src/app-opentui/scenes/workspace.js +1 -0
  60. package/dist/src/app-opentui/selectable.js +1 -0
  61. package/dist/src/app-opentui/theme.d.ts +1 -0
  62. package/dist/src/app-opentui/theme.js +1 -0
  63. package/package.json +8 -8
  64. package/dist/src/app-opentui/render/image-visual-layer-worker.d.ts +0 -1
  65. package/dist/src/app-opentui/render/image-visual-layer-worker.js +0 -1084
  66. package/dist/src/app-opentui/render/image-visual-layer.d.ts +0 -109
  67. package/dist/src/app-opentui/render/image-visual-layer.js +0 -1068
  68. package/dist/src/app-opentui/terminal-capabilities.d.ts +0 -7
  69. package/dist/src/app-opentui/terminal-capabilities.js +0 -15
@@ -2242,6 +2242,7 @@ var RIG_UI = {
2242
2242
  bg2: "#0b0c0e",
2243
2243
  panel: "#101115",
2244
2244
  panel2: "#14161b",
2245
+ hover: "#181b24",
2245
2246
  glass: "#1e2230",
2246
2247
  border: "#2a2e3a",
2247
2248
  ink: "#f4f5f8",
@@ -2773,19 +2774,6 @@ function panelContentRect(layout, panel) {
2773
2774
  const contentWidth = Math.max(1, width - paddingX * 2 - 1);
2774
2775
  return { left, top, width, height, paddingX, paddingY, contentWidth };
2775
2776
  }
2776
- function panelPixelPlacement(layout, panel, size, minPixels = 8) {
2777
- const cellW = size.width / layout.width;
2778
- const cellH = size.height / layout.height;
2779
- const leftCells = layout.centerLeft + (panel.left ?? 0);
2780
- const topCells = layout.centerTop + panel.top;
2781
- const widthCells = panel.width ?? layout.centerWidth;
2782
- const heightCells = panel.height;
2783
- const left = Math.round(leftCells * cellW);
2784
- const top = Math.round(topCells * cellH);
2785
- const width = Math.round(widthCells * cellW);
2786
- const height = Math.round(heightCells * cellH);
2787
- return width > minPixels && height > minPixels ? { left, top, width, height, leftCells, topCells, widthCells, heightCells } : null;
2788
- }
2789
2777
 
2790
2778
  // packages/cli/src/app-opentui/render/constants.ts
2791
2779
  var LCG_MULTIPLIER = 1664525;
@@ -2818,7 +2806,6 @@ var RAIL_YS_LOOP = [0.18, 0.5, 0.82];
2818
2806
  var RAIL_YS_CARRIER = [0.24, 0.54, 0.82];
2819
2807
  var RAIL_YS_DISPATCH = [0.18, 0.58, 0.82];
2820
2808
  var GLOW_FALLOFF_EXP_ASCII = 2.2;
2821
- var GLOW_FALLOFF_EXP_IMAGE = 2.4;
2822
2809
 
2823
2810
  // packages/cli/src/app-opentui/render/graphics.ts
2824
2811
  var BRAILLE_SAMPLES_X = 2;
@@ -3250,27 +3237,20 @@ function writeCell(layer, x, y, char, fg, bg) {
3250
3237
  function drawFullCell(layer, x, y, bg) {
3251
3238
  writeCell(layer, x, y, " ", TRANSPARENT, bg);
3252
3239
  }
3253
- function drawCornerCut(layer, x, y, char, fg) {
3254
- writeCell(layer, x, y, char, fg, BACKDROP);
3255
- }
3256
- function drawRoundedFill(layer, left, top, right, bottom, dividerY) {
3240
+ function drawPanelFill(layer, left, top, right, bottom, dividerY) {
3257
3241
  const bgForY = (y) => y < dividerY ? PANEL_HEADER_BG : PANEL_BG;
3258
3242
  for (let y = top;y <= bottom; y += 1) {
3259
3243
  const bg = bgForY(y);
3260
3244
  for (let x = left;x <= right; x += 1)
3261
3245
  drawFullCell(layer, x, y, bg);
3262
3246
  }
3263
- drawCornerCut(layer, left, top, "\u2597", PANEL_HEADER_BG);
3264
- drawCornerCut(layer, right, top, "\u2596", PANEL_HEADER_BG);
3265
- drawCornerCut(layer, left, bottom, "\u259D", PANEL_BG);
3266
- drawCornerCut(layer, right, bottom, "\u2598", PANEL_BG);
3267
3247
  }
3268
3248
  function drawAdTerminalPanel(layer, layout, panel) {
3269
3249
  const rect = panelCellRect(layout, panel);
3270
3250
  if (!rect)
3271
3251
  return;
3272
3252
  const { left, top, width, right, bottom, headerHeight, dividerY } = rect;
3273
- drawRoundedFill(layer, left, top, right, bottom, dividerY);
3253
+ drawPanelFill(layer, left, top, right, bottom, dividerY);
3274
3254
  if (dividerY > top && dividerY < bottom) {
3275
3255
  for (let x = left + 2;x < right - 1; x += 1) {
3276
3256
  writeCell(layer, x, dividerY, "\u2500", PANEL_LINE_DIM, PANEL_BG);
@@ -3417,982 +3397,68 @@ function sparklineString(values, width) {
3417
3397
  }).join("");
3418
3398
  }
3419
3399
 
3420
- // packages/cli/src/app-opentui/render/image-visual-layer.ts
3421
- import { Worker } from "worker_threads";
3422
- import { deflateSync } from "zlib";
3423
- import { allocateImageId, deleteKittyImage } from "@earendil-works/pi-tui";
3424
-
3425
- // packages/cli/src/app-opentui/terminal-capabilities.ts
3426
- import { getCapabilities } from "@earendil-works/pi-tui";
3427
- function getRigTerminalCapabilities() {
3428
- const imageProtocol = getCapabilities().images;
3429
- const supportsKittyImages = imageProtocol === "kitty";
3430
- return {
3431
- imageProtocol,
3432
- supportsKittyImages,
3433
- visualMode: supportsKittyImages ? "kitty-images" : "framebuffer-fallback"
3434
- };
3435
- }
3400
+ // packages/cli/src/app-opentui/render/text.ts
3401
+ import { RGBA as RGBA4, TextAttributes as TextAttributes4, TextRenderable } from "@opentui/core";
3436
3402
 
3437
- // packages/cli/src/app-opentui/render/image-visual-layer.ts
3438
- var PANEL_MASK_RADIUS_MIN = 14;
3439
- var PANEL_MASK_RADIUS_MAX = 34;
3440
- var PANEL_MASK_RADIUS_FACTOR = 0.035;
3441
- var PANEL_BLUR_RADIUS_MIN = 8;
3442
- var PANEL_BLUR_RADIUS_MAX = 28;
3443
- var PANEL_BLUR_RADIUS_FACTOR = 0.018;
3444
- var BG = [7, 8, 9, 255];
3445
- var PANEL = [16, 17, 21, 184];
3446
- var PANEL_HEADER = [16, 17, 21, 190];
3447
- var PANEL_LINE2 = [255, 255, 255, 24];
3448
- var LIME = [204, 255, 77, 255];
3449
- var LIME_DIM = [169, 214, 63, 255];
3450
- var CYAN = [86, 216, 255, 255];
3451
- var MAGENTA = [255, 121, 176, 255];
3452
- var INK_DIM = [108, 110, 121, 255];
3453
- var PNG_SIGNATURE = new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10]);
3454
- var CRC_TABLE = makeCrcTable();
3455
- var MAX_IMAGE_BYTES = 48 * 1024 * 1024;
3456
- var KITTY_CHUNK_SIZE = 4096;
3457
- var PANEL_BLUR_SCALE = 1;
3458
- var PANEL_MASK_CACHE_LIMIT = 16;
3459
- var ZLIB_LEVEL = 1;
3460
- var DEFAULT_IMAGE_RESOLUTION_SCALE = 1;
3461
- var LOAD_BUCKETS = 5;
3462
- function loadBucket(load) {
3463
- if (!Number.isFinite(load ?? NaN))
3464
- return 0;
3465
- return Math.max(0, Math.min(LOAD_BUCKETS, Math.round((load ?? 0) * LOAD_BUCKETS)));
3466
- }
3467
- var panelMaskCache = new Map;
3468
- var BG_WORD = rgbaWord(BG);
3469
- function imageTransport() {
3470
- const forced = process.env.RIG_OPENTUI_IMAGE_TRANSPORT;
3471
- if (forced === "rgba-zlib")
3472
- return "rgba-zlib";
3473
- return "png";
3474
- }
3475
- function imageResolutionScale() {
3476
- const raw = Number.parseFloat(process.env.RIG_OPENTUI_IMAGE_SCALE ?? "");
3477
- if (Number.isFinite(raw) && raw > 0)
3478
- return Math.max(0.25, Math.min(1, raw));
3479
- return DEFAULT_IMAGE_RESOLUTION_SCALE;
3480
- }
3481
- function rgbaWord(color) {
3482
- return (color[3] << 24 | color[2] << 16 | color[1] << 8 | color[0]) >>> 0;
3483
- }
3484
- function fillRgba(data, color, word = rgbaWord(color)) {
3485
- if (data.byteOffset % 4 === 0 && data.byteLength % 4 === 0) {
3486
- new Uint32Array(data.buffer, data.byteOffset, data.byteLength / 4).fill(word);
3403
+ // packages/cli/src/app-opentui/render/hover.ts
3404
+ var hoveredId;
3405
+ var listeners = new Set;
3406
+ function isHovered(id) {
3407
+ return id !== undefined && id === hoveredId;
3408
+ }
3409
+ function setHoveredSelectableId(id) {
3410
+ if (hoveredId === id)
3487
3411
  return;
3488
- }
3489
- for (let i = 0;i < data.length; i += 4) {
3490
- data[i] = color[0];
3491
- data[i + 1] = color[1];
3492
- data[i + 2] = color[2];
3493
- data[i + 3] = color[3];
3494
- }
3495
- }
3496
- var ASCII_GLYPHS = {
3497
- ".": ["00000", "00000", "00000", "00000", "00000", "00100", "00100"],
3498
- "'": ["00100", "00100", "00000", "00000", "00000", "00000", "00000"],
3499
- "-": ["00000", "00000", "00000", "11111", "00000", "00000", "00000"],
3500
- _: ["00000", "00000", "00000", "00000", "00000", "00000", "11111"],
3501
- "=": ["00000", "11111", "00000", "00000", "11111", "00000", "00000"],
3502
- "|": ["00100", "00100", "00100", "00100", "00100", "00100", "00100"],
3503
- "/": ["00001", "00010", "00010", "00100", "01000", "01000", "10000"],
3504
- "\\": ["10000", "01000", "01000", "00100", "00010", "00010", "00001"],
3505
- "(": ["00010", "00100", "01000", "01000", "01000", "00100", "00010"],
3506
- ")": ["01000", "00100", "00010", "00010", "00010", "00100", "01000"],
3507
- "!": ["00100", "00100", "00100", "00100", "00100", "00000", "00100"],
3508
- "%": ["11001", "11010", "00100", "01000", "10110", "00110", "00000"],
3509
- $: ["00100", "11110", "10100", "11110", "00101", "11110", "00100"],
3510
- "@": ["01110", "10001", "10111", "10101", "10111", "10000", "01111"],
3511
- o: ["00000", "01110", "10001", "10001", "10001", "01110", "00000"],
3512
- "*": ["00100", "10101", "01110", "11111", "01110", "10101", "00100"]
3513
- };
3514
- function sceneStaticTick(scene) {
3515
- let hash = 0;
3516
- for (let index = 0;index < scene.length; index += 1)
3517
- hash = hash * 33 + scene.charCodeAt(index) >>> 0;
3518
- return 11 + hash % 97;
3519
- }
3520
- function charColor(char, row, totalRows) {
3521
- if (char === "@" || char === "$" || char === "o")
3522
- return LIME;
3523
- if (char === "%" || char === "!" || char === "/" || char === "\\" || char === "|")
3524
- return CYAN;
3525
- if (char === "." || char === "'" || char === "_" || row < 3 || row > totalRows - 4)
3526
- return INK_DIM;
3527
- if (char === "-" || char === "=" || char === "(" || char === ")")
3528
- return LIME_DIM;
3529
- return MAGENTA;
3530
- }
3531
- function fillRectAlpha(data, width, height, left, top, rectWidth, rectHeight, color, alpha) {
3532
- const x0 = Math.max(0, Math.floor(left));
3533
- const y0 = Math.max(0, Math.floor(top));
3534
- const x1 = Math.min(width, Math.ceil(left + rectWidth));
3535
- const y1 = Math.min(height, Math.ceil(top + rectHeight));
3536
- for (let y = y0;y < y1; y += 1) {
3537
- for (let x = x0;x < x1; x += 1)
3538
- blendOver(data, (y * width + x) * 4, color, alpha);
3539
- }
3540
- }
3541
- function drawAsciiGlyph(data, width, height, x, y, cellWidth, cellHeight, char, color, alpha) {
3542
- const pattern = ASCII_GLYPHS[char] ?? ASCII_GLYPHS["*"];
3543
- const glyphWidth = Math.max(1, cellWidth * 0.56);
3544
- const glyphHeight = Math.max(1, cellHeight * 0.7);
3545
- const originX = x + (cellWidth - glyphWidth) * 0.5;
3546
- const originY = y + (cellHeight - glyphHeight) * 0.5;
3547
- const pixelWidth = Math.max(1, glyphWidth / pattern[0].length);
3548
- const pixelHeight = Math.max(1, glyphHeight / pattern.length);
3549
- for (let row = 0;row < pattern.length; row += 1) {
3550
- const bits = pattern[row];
3551
- for (let col = 0;col < bits.length; col += 1) {
3552
- if (bits[col] !== "1")
3553
- continue;
3554
- fillRectAlpha(data, width, height, originX + col * pixelWidth, originY + row * pixelHeight, pixelWidth * 0.72, pixelHeight * 0.72, color, alpha);
3555
- }
3556
- }
3557
- }
3558
- function drawStaticAsciiFleetBackground(data, width, height, scene, layout, load, activeRuns) {
3559
- const rows = fleetRows(sceneStaticTick(scene), { animate: true });
3560
- const cellWidth = width / Math.max(1, layout.width);
3561
- const cellHeight = height / Math.max(1, layout.height);
3562
- const sideBySide = layout.width >= FLEET_GRID_WIDTH * 1.9;
3563
- const leftCells = sideBySide ? Math.max(1, Math.floor(layout.width * 0.3 - FLEET_GRID_WIDTH / 2)) : Math.floor((layout.width - FLEET_GRID_WIDTH) / 2);
3564
- const topCells = Math.floor((layout.height - FLEET_GRID_HEIGHT) / 2);
3565
- const fleetLeft = leftCells * cellWidth;
3566
- const fleetTop = topCells * cellHeight;
3567
- drawGlow2(data, width, height, width * 0.5, height * 0.5, Math.min(width, height) * 0.4, LIME, 0.022 + load * 0.03);
3568
- drawGlow2(data, width, height, width * 0.18, height * 0.22, Math.min(width, height) * 0.28, CYAN, 0.018 + load * 0.012);
3569
- drawGlow2(data, width, height, width * 0.82, height * 0.78, Math.min(width, height) * 0.22, MAGENTA, 0.012 + load * 0.008);
3570
- let droneTotal = 0;
3571
- for (const line2 of rows)
3572
- for (const char of line2)
3573
- if (char === "@" || char === "$" || char === "%")
3574
- droneTotal += 1;
3575
- const litTarget = Math.max(0, Math.min(droneTotal, activeRuns));
3576
- let droneIndex = 0;
3577
- for (let row = 0;row < rows.length; row += 1) {
3578
- const line2 = rows[row];
3579
- for (let col = 0;col < line2.length; col += 1) {
3580
- const char = line2[col];
3581
- if (char === " ")
3582
- continue;
3583
- const x = fleetLeft + col * cellWidth;
3584
- const y = fleetTop + row * cellHeight;
3585
- const color = charColor(char, row, rows.length);
3586
- const isDrone = char === "@" || char === "$" || char === "%";
3587
- const lit = isDrone && droneIndex < litTarget;
3588
- drawAsciiGlyph(data, width, height, x + cellWidth * 0.07, y + cellHeight * 0.05, cellWidth, cellHeight, char, color, lit ? 0.72 : 0.46);
3589
- if (isDrone) {
3590
- droneIndex += 1;
3591
- drawGlow2(data, width, height, x + cellWidth * 0.5, y + cellHeight * 0.5, Math.max(cellWidth, cellHeight) * 0.95, color, lit ? 0.044 : 0.014);
3592
- }
3593
- }
3594
- }
3595
- }
3596
- function applyStaticAtmosphere(data, width, height, load) {
3597
- const cx = width * 0.5;
3598
- const cy = height * 0.42;
3599
- const radius = Math.max(width, height) * 0.78;
3600
- const minVignette = 0.1 - load * 0.02;
3601
- for (let y = 0;y < height; y += 1) {
3602
- for (let x = 0;x < width; x += 1) {
3603
- const distance = Math.hypot(x - cx, y - cy) / radius;
3604
- const alpha = Math.max(minVignette, Math.min(0.82, 0.12 + Math.pow(distance, 1.65) * 0.62));
3605
- blendOver(data, (y * width + x) * 4, BG, alpha);
3606
- }
3607
- }
3608
- drawGlow2(data, width, height, width * 0.82, height * 0.04, Math.min(width, height) * 0.2, LIME, 0.024 + load * 0.012);
3609
- drawGlow2(data, width, height, width * 0.12, height * 0.18, Math.min(width, height) * 0.16, CYAN, 0.018 + load * 0.008);
3610
- }
3611
- function makeCrcTable() {
3612
- const table = new Uint32Array(256);
3613
- for (let n = 0;n < 256; n += 1) {
3614
- let c = n;
3615
- for (let k = 0;k < 8; k += 1)
3616
- c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
3617
- table[n] = c >>> 0;
3618
- }
3619
- return table;
3620
- }
3621
- function crc32(bytes) {
3622
- let c = 4294967295;
3623
- for (let i = 0;i < bytes.length; i += 1)
3624
- c = CRC_TABLE[(c ^ bytes[i]) & 255] ^ c >>> 8;
3625
- return (c ^ 4294967295) >>> 0;
3626
- }
3627
- function chunk2(type, data) {
3628
- const out = new Uint8Array(12 + data.length);
3629
- const view = new DataView(out.buffer);
3630
- view.setUint32(0, data.length);
3631
- out[4] = type.charCodeAt(0);
3632
- out[5] = type.charCodeAt(1);
3633
- out[6] = type.charCodeAt(2);
3634
- out[7] = type.charCodeAt(3);
3635
- out.set(data, 8);
3636
- view.setUint32(8 + data.length, crc32(out.subarray(4, 8 + data.length)));
3637
- return out;
3638
- }
3639
- function encodePng(width, height, rgba) {
3640
- const ihdr = new Uint8Array(13);
3641
- const ihdrView = new DataView(ihdr.buffer);
3642
- ihdrView.setUint32(0, width);
3643
- ihdrView.setUint32(4, height);
3644
- ihdr[8] = 8;
3645
- ihdr[9] = 6;
3646
- ihdr[10] = 0;
3647
- ihdr[11] = 0;
3648
- ihdr[12] = 0;
3649
- const stride = width * 4;
3650
- const raw = new Uint8Array((stride + 1) * height);
3651
- for (let y = 0;y < height; y += 1) {
3652
- const rawOffset = y * (stride + 1);
3653
- raw[rawOffset] = 0;
3654
- raw.set(rgba.subarray(y * stride, y * stride + stride), rawOffset + 1);
3655
- }
3656
- const compressed = deflateSync(raw, { level: ZLIB_LEVEL });
3657
- return Buffer.concat([
3658
- Buffer.from(PNG_SIGNATURE),
3659
- Buffer.from(chunk2("IHDR", ihdr)),
3660
- Buffer.from(chunk2("IDAT", compressed)),
3661
- Buffer.from(chunk2("IEND", new Uint8Array(0)))
3662
- ]);
3663
- }
3664
- function kittyImageSequence(base64, options) {
3665
- const params = [`a=T`, `f=${options.format}`, `q=2`, `C=1`, `c=${options.columns}`, `r=${options.rows}`, `i=${options.imageId}`, `z=${options.zIndex}`];
3666
- if (options.format === 32) {
3667
- params.push(`s=${options.pixelWidth ?? 1}`, `v=${options.pixelHeight ?? 1}`);
3668
- if (options.compressed)
3669
- params.push("o=z");
3670
- }
3671
- if (base64.length <= KITTY_CHUNK_SIZE)
3672
- return `\x1B_G${params.join(",")};${base64}\x1B\\`;
3673
- const chunks = [];
3674
- for (let offset = 0;offset < base64.length; offset += KITTY_CHUNK_SIZE) {
3675
- const part = base64.slice(offset, offset + KITTY_CHUNK_SIZE);
3676
- const last = offset + KITTY_CHUNK_SIZE >= base64.length;
3677
- chunks.push(`\x1B_G${offset === 0 ? `${params.join(",")},` : ""}m=${last ? 0 : 1};${part}\x1B\\`);
3678
- }
3679
- return chunks.join("");
3680
- }
3681
- function clampByte(value) {
3682
- return Math.max(0, Math.min(255, Math.round(value)));
3683
- }
3684
- function blendOver(data, index, color, alphaScale = 1) {
3685
- const alpha = color[3] / 255 * alphaScale;
3686
- const inv = 1 - alpha;
3687
- data[index] = clampByte(color[0] * alpha + data[index] * inv);
3688
- data[index + 1] = clampByte(color[1] * alpha + data[index + 1] * inv);
3689
- data[index + 2] = clampByte(color[2] * alpha + data[index + 2] * inv);
3690
- data[index + 3] = 255;
3691
- }
3692
- function drawDisc(data, width, height, cx, cy, radius, color, alpha = 1) {
3693
- const minX = Math.max(0, Math.floor(cx - radius));
3694
- const maxX = Math.min(width - 1, Math.ceil(cx + radius));
3695
- const minY = Math.max(0, Math.floor(cy - radius));
3696
- const maxY = Math.min(height - 1, Math.ceil(cy + radius));
3697
- for (let y = minY;y <= maxY; y += 1) {
3698
- for (let x = minX;x <= maxX; x += 1) {
3699
- const dx = x + 0.5 - cx;
3700
- const dy = y + 0.5 - cy;
3701
- const d = Math.sqrt(dx * dx + dy * dy);
3702
- if (d > radius)
3703
- continue;
3704
- const edge = Math.max(0, Math.min(1, radius - d));
3705
- blendOver(data, (y * width + x) * 4, color, alpha * Math.min(1, 0.35 + edge));
3706
- }
3707
- }
3412
+ hoveredId = id;
3413
+ for (const listener of listeners)
3414
+ listener();
3708
3415
  }
3709
- function drawGlow2(data, width, height, cx, cy, radius, color, alpha = 0.18) {
3710
- const minX = Math.max(0, Math.floor(cx - radius));
3711
- const maxX = Math.min(width - 1, Math.ceil(cx + radius));
3712
- const minY = Math.max(0, Math.floor(cy - radius));
3713
- const maxY = Math.min(height - 1, Math.ceil(cy + radius));
3714
- for (let y = minY;y <= maxY; y += 1) {
3715
- for (let x = minX;x <= maxX; x += 1) {
3716
- const dx = x + 0.5 - cx;
3717
- const dy = y + 0.5 - cy;
3718
- const d = Math.sqrt(dx * dx + dy * dy);
3719
- if (d > radius)
3720
- continue;
3721
- const falloff = Math.pow(1 - d / Math.max(1, radius), GLOW_FALLOFF_EXP_IMAGE);
3722
- blendOver(data, (y * width + x) * 4, color, alpha * falloff);
3723
- }
3724
- }
3416
+ function clearHoveredSelectableId(id) {
3417
+ if (id !== undefined && hoveredId === id)
3418
+ setHoveredSelectableId(undefined);
3725
3419
  }
3726
- function drawLine2(data, width, height, x0, y0, x1, y1, color, alpha = 0.5, thickness = 1.4) {
3727
- const dx = x1 - x0;
3728
- const dy = y1 - y0;
3729
- const steps = Math.max(1, Math.ceil(Math.hypot(dx, dy) * 1.2));
3730
- for (let i = 0;i <= steps; i += 1) {
3731
- const t2 = i / steps;
3732
- drawDisc(data, width, height, x0 + dx * t2, y0 + dy * t2, thickness, color, alpha);
3733
- }
3734
- }
3735
- function roundRectAlpha(px, py, x, y, w, h, r) {
3736
- const rx = Math.max(x + r, Math.min(px, x + w - r));
3737
- const ry = Math.max(y + r, Math.min(py, y + h - r));
3738
- const dx = px - rx;
3739
- const dy = py - ry;
3740
- const dist = Math.sqrt(dx * dx + dy * dy);
3741
- return Math.max(0, Math.min(1, r + 0.75 - dist));
3742
- }
3743
- function panelRect(layout, panel, size) {
3744
- return panelPixelPlacement(layout, panel, size);
3745
- }
3746
- function downsampleRegion(data, canvasW, canvasH, rect, scale) {
3747
- const w = Math.max(1, Math.ceil(rect.width / scale));
3748
- const h = Math.max(1, Math.ceil(rect.height / scale));
3749
- const out = new Uint8ClampedArray(w * h * 3);
3750
- for (let y = 0;y < h; y += 1) {
3751
- for (let x = 0;x < w; x += 1) {
3752
- let r = 0, g = 0, b = 0, count = 0;
3753
- const sx0 = rect.left + x * scale;
3754
- const sy0 = rect.top + y * scale;
3755
- for (let yy = 0;yy < scale; yy += 1) {
3756
- const sy = sy0 + yy;
3757
- if (sy < 0 || sy >= canvasH)
3758
- continue;
3759
- for (let xx = 0;xx < scale; xx += 1) {
3760
- const sx = sx0 + xx;
3761
- if (sx < 0 || sx >= canvasW)
3762
- continue;
3763
- const i = (sy * canvasW + sx) * 4;
3764
- r += data[i];
3765
- g += data[i + 1];
3766
- b += data[i + 2];
3767
- count += 1;
3768
- }
3769
- }
3770
- const oi = (y * w + x) * 3;
3771
- out[oi] = count ? r / count : BG[0];
3772
- out[oi + 1] = count ? g / count : BG[1];
3773
- out[oi + 2] = count ? b / count : BG[2];
3774
- }
3775
- }
3776
- return { pixels: out, width: w, height: h };
3777
- }
3778
- function blurRgb(buffer, width, height, radius) {
3779
- const tmp = new Uint8ClampedArray(buffer.length);
3780
- const out = new Uint8ClampedArray(buffer.length);
3781
- const window = radius * 2 + 1;
3782
- for (let y = 0;y < height; y += 1) {
3783
- let r = 0, g = 0, b = 0;
3784
- for (let x = -radius;x <= radius; x += 1) {
3785
- const sx = Math.max(0, Math.min(width - 1, x));
3786
- const i = (y * width + sx) * 3;
3787
- r += buffer[i];
3788
- g += buffer[i + 1];
3789
- b += buffer[i + 2];
3790
- }
3791
- for (let x = 0;x < width; x += 1) {
3792
- const oi = (y * width + x) * 3;
3793
- tmp[oi] = r / window;
3794
- tmp[oi + 1] = g / window;
3795
- tmp[oi + 2] = b / window;
3796
- const removeX = Math.max(0, x - radius);
3797
- const addX = Math.min(width - 1, x + radius + 1);
3798
- const ri = (y * width + removeX) * 3;
3799
- const ai = (y * width + addX) * 3;
3800
- r += tmp.length ? buffer[ai] - buffer[ri] : 0;
3801
- g += tmp.length ? buffer[ai + 1] - buffer[ri + 1] : 0;
3802
- b += tmp.length ? buffer[ai + 2] - buffer[ri + 2] : 0;
3803
- }
3804
- }
3805
- for (let x = 0;x < width; x += 1) {
3806
- let r = 0, g = 0, b = 0;
3807
- for (let y = -radius;y <= radius; y += 1) {
3808
- const sy = Math.max(0, Math.min(height - 1, y));
3809
- const i = (sy * width + x) * 3;
3810
- r += tmp[i];
3811
- g += tmp[i + 1];
3812
- b += tmp[i + 2];
3813
- }
3814
- for (let y = 0;y < height; y += 1) {
3815
- const oi = (y * width + x) * 3;
3816
- out[oi] = r / window;
3817
- out[oi + 1] = g / window;
3818
- out[oi + 2] = b / window;
3819
- const removeY = Math.max(0, y - radius);
3820
- const addY = Math.min(height - 1, y + radius + 1);
3821
- const ri = (removeY * width + x) * 3;
3822
- const ai = (addY * width + x) * 3;
3823
- r += tmp[ai] - tmp[ri];
3824
- g += tmp[ai + 1] - tmp[ri + 1];
3825
- b += tmp[ai + 2] - tmp[ri + 2];
3826
- }
3827
- }
3828
- return out;
3829
- }
3830
- function panelMaskKey(width, height, rect, headerPx, blurScale) {
3831
- return `${width}x${height}:${rect.left},${rect.top},${rect.width},${rect.height}:${Math.round(headerPx * 100) / 100}:${blurScale}`;
3832
- }
3833
- function getPanelMask(width, height, rect, headerPx, blurScale) {
3834
- const key = panelMaskKey(width, height, rect, headerPx, blurScale);
3835
- const cached = panelMaskCache.get(key);
3836
- if (cached)
3837
- return cached;
3838
- const radius = Math.max(PANEL_MASK_RADIUS_MIN, Math.min(PANEL_MASK_RADIUS_MAX, Math.round(Math.min(rect.width, rect.height) * PANEL_MASK_RADIUS_FACTOR)));
3839
- const sampledWidth = Math.max(1, Math.ceil(rect.width / blurScale));
3840
- const sampledHeight = Math.max(1, Math.ceil(rect.height / blurScale));
3841
- const x0 = Math.max(0, rect.left);
3842
- const y0 = Math.max(0, rect.top);
3843
- const x1 = Math.min(width - 1, rect.left + rect.width - 1);
3844
- const y1 = Math.min(height - 1, rect.top + rect.height - 1);
3845
- const indices = [];
3846
- const sampleIndices = [];
3847
- const panelAlpha = [];
3848
- const edgeAlpha = [];
3849
- const headerFlags = [];
3850
- const dividerIndices = [];
3851
- for (let y = y0;y <= y1; y += 1) {
3852
- for (let x = x0;x <= x1; x += 1) {
3853
- const a = roundRectAlpha(x + 0.5, y + 0.5, rect.left, rect.top, rect.width, rect.height, radius);
3854
- if (a <= 0)
3855
- continue;
3856
- const edge = roundRectAlpha(x + 0.5, y + 0.5, rect.left + 1.2, rect.top + 1.2, rect.width - 2.4, rect.height - 2.4, Math.max(1, radius - 1.2));
3857
- const sx = Math.max(0, Math.min(sampledWidth - 1, Math.floor((x - rect.left) / blurScale)));
3858
- const sy = Math.max(0, Math.min(sampledHeight - 1, Math.floor((y - rect.top) / blurScale)));
3859
- indices.push((y * width + x) * 4);
3860
- sampleIndices.push((sy * sampledWidth + sx) * 3);
3861
- panelAlpha.push(a);
3862
- edgeAlpha.push(edge < 0.96 ? Math.min(1, (1 - edge) * 0.7) : 0);
3863
- headerFlags.push(y < rect.top + headerPx ? 1 : 0);
3864
- }
3865
- }
3866
- const dividerY = Math.round(rect.top + headerPx);
3867
- if (dividerY > y0 && dividerY < y1) {
3868
- for (let x = x0 + radius;x <= x1 - radius; x += 1) {
3869
- dividerIndices.push((dividerY * width + x) * 4);
3870
- }
3871
- }
3872
- const mask = {
3873
- key,
3874
- indices: Uint32Array.from(indices),
3875
- sampleIndices: Uint32Array.from(sampleIndices),
3876
- panelAlpha: Float32Array.from(panelAlpha),
3877
- edgeAlpha: Float32Array.from(edgeAlpha),
3878
- headerFlags: Uint8Array.from(headerFlags),
3879
- dividerIndices: Uint32Array.from(dividerIndices)
3420
+ function subscribeHover(listener) {
3421
+ listeners.add(listener);
3422
+ return () => {
3423
+ listeners.delete(listener);
3880
3424
  };
3881
- panelMaskCache.set(key, mask);
3882
- while (panelMaskCache.size > PANEL_MASK_CACHE_LIMIT) {
3883
- const oldest = panelMaskCache.keys().next().value;
3884
- if (!oldest)
3885
- break;
3886
- panelMaskCache.delete(oldest);
3887
- }
3888
- return mask;
3889
- }
3890
- function compositePanel(data, width, height, rect, headerPx) {
3891
- const blurScale = PANEL_BLUR_SCALE;
3892
- const sampled = downsampleRegion(data, width, height, rect, blurScale);
3893
- const blurRadius = Math.max(PANEL_BLUR_RADIUS_MIN, Math.min(PANEL_BLUR_RADIUS_MAX, Math.round(Math.min(rect.width, rect.height) * PANEL_BLUR_RADIUS_FACTOR)));
3894
- const blurred = blurRgb(sampled.pixels, sampled.width, sampled.height, blurRadius);
3895
- const mask = getPanelMask(width, height, rect, headerPx, blurScale);
3896
- for (let i = 0;i < mask.indices.length; i += 1) {
3897
- const idx = mask.indices[i];
3898
- const sampleIdx = mask.sampleIndices[i];
3899
- data[idx] = blurred[sampleIdx];
3900
- data[idx + 1] = blurred[sampleIdx + 1];
3901
- data[idx + 2] = blurred[sampleIdx + 2];
3902
- data[idx + 3] = 255;
3903
- blendOver(data, idx, mask.headerFlags[i] ? PANEL_HEADER : PANEL, mask.panelAlpha[i]);
3904
- const edge = mask.edgeAlpha[i];
3905
- if (edge > 0)
3906
- blendOver(data, idx, PANEL_LINE2, edge);
3907
- }
3908
- for (let i = 0;i < mask.dividerIndices.length; i += 1) {
3909
- blendOver(data, mask.dividerIndices[i], PANEL_LINE2, 0.28);
3910
- }
3911
- }
3912
- var RIG_GLYPH_STROKES = {
3913
- R: [[0, 0, 0, 1], [0, 0, 0.82, 0], [0.82, 0, 0.82, 0.5], [0, 0.5, 0.82, 0.5], [0.36, 0.5, 0.92, 1]],
3914
- I: [[0.5, 0, 0.5, 1], [0.18, 0, 0.82, 0], [0.18, 1, 0.82, 1]],
3915
- G: [[0.92, 0.08, 0.12, 0], [0.12, 0, 0.12, 1], [0.12, 1, 0.92, 1], [0.92, 0.5, 0.92, 1], [0.5, 0.5, 0.92, 0.5]]
3916
- };
3917
- function drawBigRigWordmark(data, width, height, layout, load) {
3918
- const sideBySide = layout.width >= FLEET_GRID_WIDTH * 1.9;
3919
- if (!sideBySide)
3920
- return;
3921
- const letters = "RIG";
3922
- const min = Math.min(width, height);
3923
- const glyphH = height * 0.42;
3924
- const glyphW = width * 0.13;
3925
- const gap = glyphW * 0.3;
3926
- const totalW = letters.length * glyphW + (letters.length - 1) * gap;
3927
- const centerX = width * 0.71;
3928
- const startX = centerX - totalW / 2;
3929
- const top = (height - glyphH) / 2;
3930
- const stroke = Math.max(2, min * 0.013);
3931
- drawGlow2(data, width, height, centerX, top + glyphH / 2, Math.max(glyphW, glyphH) * 0.9, LIME, 0.022 + load * 0.02);
3932
- const strokeAlpha = 0.1 + load * 0.06;
3933
- letters.split("").forEach((letter, index) => {
3934
- const segments = RIG_GLYPH_STROKES[letter];
3935
- if (!segments)
3936
- return;
3937
- const ox = startX + index * (glyphW + gap);
3938
- for (const [x0, y0, x1, y1] of segments) {
3939
- drawLine2(data, width, height, ox + x0 * glyphW, top + y0 * glyphH, ox + x1 * glyphW, top + y1 * glyphH, LIME_DIM, strokeAlpha, stroke);
3940
- }
3941
- });
3942
- }
3943
- function drawBackground(data, width, height, _tick, scene, layout, load, activeRuns) {
3944
- fillRgba(data, BG, BG_WORD);
3945
- drawStaticAsciiFleetBackground(data, width, height, scene, layout, load, activeRuns);
3946
- drawBigRigWordmark(data, width, height, layout, load);
3947
- applyStaticAtmosphere(data, width, height, load);
3948
- }
3949
- function imageVisualFrameKey(input) {
3950
- const width = Math.max(1, Math.floor(input.pixelSize.width));
3951
- const height = Math.max(1, Math.floor(input.pixelSize.height));
3952
- const layer = "layer" in input ? input.layer ?? "full" : "full";
3953
- const load = loadBucket(input.load);
3954
- return `${layer}:${width}x${height}:${input.layout.width}x${input.layout.height}:${input.scene}:${input.tick}:l${load}:${input.panels.map((panel) => `${panel.id}:${panel.top}:${panel.height}:${panel.width ?? ""}:${panel.left ?? ""}:${panel.headerHeight ?? ""}`).join("|")}`;
3955
- }
3956
- function encodeKittyFrame(width, height, data, input) {
3957
- if (input.transport === "png") {
3958
- const png = encodePng(width, height, data);
3959
- return kittyImageSequence(png.toString("base64"), { columns: input.layout.width, rows: input.layout.height, imageId: input.imageId, zIndex: input.zIndex, format: 100 });
3960
- }
3961
- const compressed = deflateSync(data, { level: ZLIB_LEVEL });
3962
- return kittyImageSequence(compressed.toString("base64"), {
3963
- columns: input.layout.width,
3964
- rows: input.layout.height,
3965
- imageId: input.imageId,
3966
- zIndex: input.zIndex,
3967
- format: 32,
3968
- pixelWidth: width,
3969
- pixelHeight: height,
3970
- compressed: true
3971
- });
3972
- }
3973
- function cropRgba(data, sourceWidth, rect) {
3974
- const out = new Uint8ClampedArray(rect.width * rect.height * 4);
3975
- for (let y = 0;y < rect.height; y += 1) {
3976
- const sourceStart = ((rect.top + y) * sourceWidth + rect.left) * 4;
3977
- out.set(data.subarray(sourceStart, sourceStart + rect.width * 4), y * rect.width * 4);
3978
- }
3979
- return out;
3980
- }
3981
- function encodePanelPatch(data, sourceWidth, placement, imageId, zIndex) {
3982
- const patch = cropRgba(data, sourceWidth, placement);
3983
- const png = encodePng(placement.width, placement.height, patch);
3984
- const sequence = kittyImageSequence(png.toString("base64"), {
3985
- columns: Math.max(1, Math.round(placement.widthCells)),
3986
- rows: Math.max(1, Math.round(placement.heightCells)),
3987
- imageId,
3988
- zIndex,
3989
- format: 100
3990
- });
3991
- return `\x1B[${Math.max(1, Math.round(placement.topCells) + 1)};${Math.max(1, Math.round(placement.leftCells) + 1)}H${sequence}`;
3992
- }
3993
- function renderImageVisualFrame(input) {
3994
- const width = Math.max(1, Math.floor(input.pixelSize.width));
3995
- const height = Math.max(1, Math.floor(input.pixelSize.height));
3996
- const byteCount = width * height * 4;
3997
- if (byteCount > MAX_IMAGE_BYTES)
3998
- return null;
3999
- const layer = input.layer ?? "full";
4000
- const data = new Uint8ClampedArray(byteCount);
4001
- const load = loadBucket(input.load) / LOAD_BUCKETS;
4002
- const activeRuns = Math.max(0, Math.floor(input.activeRuns ?? 0));
4003
- drawBackground(data, width, height, 0, input.scene, input.layout, load, activeRuns);
4004
- if (layer === "split") {
4005
- const sequences = [encodeKittyFrame(width, height, data, input)];
4006
- let panelIndex = 0;
4007
- for (const panel of input.panels) {
4008
- if (panel.chrome !== "ad-terminal")
4009
- continue;
4010
- const placement = panelPixelPlacement(input.layout, panel, { width, height });
4011
- if (!placement)
4012
- continue;
4013
- const cellH = height / input.layout.height;
4014
- const headerPx = Math.max(cellH * 3, (panel.headerHeight ?? 3) * cellH);
4015
- compositePanel(data, width, height, placement, headerPx);
4016
- const imageId = input.imageIds?.[panelIndex] ?? (input.imageId + 1000 + panelIndex >>> 0 || panelIndex + 1);
4017
- sequences.push(encodePanelPatch(data, width, placement, imageId, -12));
4018
- panelIndex += 1;
4019
- }
4020
- return { key: imageVisualFrameKey(input), sequence: sequences.join("") };
4021
- }
4022
- if (layer === "panel-chrome")
4023
- return { key: imageVisualFrameKey(input), sequence: "" };
4024
- if (layer === "background") {
4025
- return { key: imageVisualFrameKey(input), sequence: encodeKittyFrame(width, height, data, input) };
4026
- }
4027
- if (layer === "panel-patches") {
4028
- const sequences = [];
4029
- let panelIndex = 0;
4030
- for (const panel of input.panels) {
4031
- if (panel.chrome !== "ad-terminal")
4032
- continue;
4033
- const placement = panelPixelPlacement(input.layout, panel, { width, height });
4034
- if (!placement)
4035
- continue;
4036
- const cellH = height / input.layout.height;
4037
- const headerPx = Math.max(cellH * 3, (panel.headerHeight ?? 3) * cellH);
4038
- compositePanel(data, width, height, placement, headerPx);
4039
- const imageId = input.imageIds?.[panelIndex] ?? (input.imageId + 1000 + panelIndex >>> 0 || panelIndex + 1);
4040
- sequences.push(encodePanelPatch(data, width, placement, imageId, input.zIndex));
4041
- panelIndex += 1;
4042
- }
4043
- return { key: imageVisualFrameKey(input), sequence: sequences.join("") };
4044
- }
4045
- for (const panel of input.panels) {
4046
- if (panel.chrome !== "ad-terminal")
4047
- continue;
4048
- const rect = panelRect(input.layout, panel, { width, height });
4049
- if (!rect)
4050
- continue;
4051
- const cellH = height / input.layout.height;
4052
- const headerPx = Math.max(cellH * 3, (panel.headerHeight ?? 3) * cellH);
4053
- compositePanel(data, width, height, rect, headerPx);
4054
- }
4055
- return { key: imageVisualFrameKey(input), sequence: encodeKittyFrame(width, height, data, input) };
4056
- }
4057
- function shouldRenderImagesInline() {
4058
- return process.env.RIG_OPENTUI_IMAGE_WORKER === "0" || import.meta.url.includes("$bunfs");
4059
- }
4060
- function createImageWorker() {
4061
- if (shouldRenderImagesInline())
4062
- return null;
4063
- const isTypeScriptRuntime = import.meta.url.endsWith(".ts");
4064
- const workerUrl = new URL(isTypeScriptRuntime ? "./image-visual-layer-worker.ts" : "./image-visual-layer-worker.js", import.meta.url);
4065
- try {
4066
- return new Worker(workerUrl);
4067
- } catch {
4068
- if (typeof process.versions.bun === "string") {
4069
- try {
4070
- return new Worker("./src/app-opentui/render/image-visual-layer-worker.ts");
4071
- } catch {}
4072
- }
4073
- return null;
4074
- }
4075
- }
4076
- var RECENT_WRITTEN_LIMIT = 24;
4077
- var DEFAULT_IMAGE_OUTPUT_BACKLOG_BYTES = 256 * 1024;
4078
- var DEFAULT_IMAGE_OUTPUT_BYTES_PER_SECOND = 850 * 1024;
4079
- function imageOutputBacklogBytes() {
4080
- const raw = Number.parseInt(process.env.RIG_OPENTUI_IMAGE_BACKLOG_BYTES ?? "", 10);
4081
- if (Number.isFinite(raw) && raw > 0)
4082
- return Math.max(64 * 1024, Math.min(2 * 1024 * 1024, raw));
4083
- return DEFAULT_IMAGE_OUTPUT_BACKLOG_BYTES;
4084
- }
4085
- function imageOutputBytesPerSecond() {
4086
- const raw = Number.parseInt(process.env.RIG_OPENTUI_IMAGE_BYTES_PER_SECOND ?? "", 10);
4087
- if (Number.isFinite(raw) && raw > 0)
4088
- return Math.max(128 * 1024, Math.min(2 * 1024 * 1024, raw));
4089
- return DEFAULT_IMAGE_OUTPUT_BYTES_PER_SECOND;
4090
- }
4091
- function pendingLayerKey(key) {
4092
- return key.split(":", 1)[0] || "full";
4093
3425
  }
4094
3426
 
4095
- class ImageVisualLayer {
4096
- imageId = allocateImageId();
4097
- panelImageIds = Array.from({ length: 8 }, () => allocateImageId());
4098
- panelChromeImageIds = Array.from({ length: 8 }, () => allocateImageId());
4099
- stdout;
4100
- onFrameReady;
4101
- protocol;
4102
- transport = imageTransport();
4103
- maxOutputBacklogBytes = imageOutputBacklogBytes();
4104
- outputBytesPerSecond = imageOutputBytesPerSecond();
4105
- outputBudgetBytes = imageOutputBytesPerSecond();
4106
- lastBudgetAtMs = Date.now();
4107
- motionSlot = this.createSlot();
4108
- workersStarted = false;
4109
- pendingFrames = new Map;
4110
- recentWrittenKeys = [];
4111
- imagesVisible = false;
4112
- flushPaused = false;
4113
- requestId = 0;
4114
- lastBackgroundKey = "";
4115
- fatalWorkerError = null;
4116
- destroyed = false;
4117
- constructor(stdout = process.stdout, onFrameReady) {
4118
- this.stdout = stdout;
4119
- this.onFrameReady = onFrameReady;
4120
- this.protocol = getRigTerminalCapabilities().imageProtocol;
4121
- }
4122
- get enabled() {
4123
- return this.protocol === "kitty";
4124
- }
4125
- clear() {
4126
- if (this.enabled) {
4127
- if (this.imagesVisible)
4128
- this.stdout.write([this.imageId, ...this.panelImageIds, ...this.panelChromeImageIds].map((id) => deleteKittyImage(id)).join("") + "\x1B[?25h");
4129
- this.imagesVisible = false;
4130
- this.pendingFrames.clear();
4131
- this.recentWrittenKeys.length = 0;
4132
- this.resetSlot(this.motionSlot);
4133
- this.lastBackgroundKey = "";
4134
- this.outputBudgetBytes = this.outputBytesPerSecond;
4135
- this.lastBudgetAtMs = Date.now();
4136
- this.motionSlot.clearedThroughRequestId = this.requestId;
4137
- }
4138
- }
4139
- destroy() {
4140
- if (this.destroyed)
4141
- return;
4142
- this.clear();
4143
- this.destroyed = true;
4144
- const workers = [this.motionSlot.worker];
4145
- this.motionSlot.worker = null;
4146
- for (const worker of workers)
4147
- if (worker)
4148
- worker.terminate().catch(() => {
4149
- return;
4150
- });
4151
- }
4152
- setFlushPaused(paused) {
4153
- this.flushPaused = paused;
4154
- }
4155
- isReady(input) {
4156
- if (!this.enabled || this.destroyed)
4157
- return true;
4158
- this.throwIfFatal();
4159
- const splitKey = imageVisualFrameKey({ ...input, tick: 0, layer: "split" });
4160
- return this.recentWrittenKeys.includes(splitKey) || Array.from(this.pendingFrames.values()).some((frame) => frame.key === splitKey);
4161
- }
4162
- render(input) {
4163
- if (!this.enabled || this.destroyed)
4164
- return;
4165
- this.throwIfFatal();
4166
- if (this.transport !== "png")
4167
- this.failWorker("renderer", new Error("OpenTUI image visuals require PNG transport for static-background rendering."));
4168
- this.ensureWorkersStarted();
4169
- const width = Math.max(1, Math.floor(input.pixelSize.width));
4170
- const height = Math.max(1, Math.floor(input.pixelSize.height));
4171
- if (width * height * 4 > MAX_IMAGE_BYTES)
4172
- return;
4173
- if (this.isOutputBackedUp())
4174
- return;
4175
- this.refillOutputBudget();
4176
- const splitInput = { ...input, tick: 0, layer: "split" };
4177
- const splitKey = imageVisualFrameKey(splitInput);
4178
- if (input.force || splitKey !== this.lastBackgroundKey) {
4179
- this.scheduleRender(this.motionSlot, splitInput, this.imageId, -30, input.force, this.panelImageIds);
4180
- this.lastBackgroundKey = splitKey;
4181
- }
4182
- }
4183
- flush() {
4184
- this.throwIfFatal();
4185
- if (this.flushPaused)
4186
- return false;
4187
- if (this.pendingFrames.size === 0)
4188
- return false;
4189
- this.refillOutputBudget();
4190
- if (this.isOutputBackedUp()) {
4191
- const retainedSplit = this.pendingFrames.get("split");
4192
- this.pendingFrames.clear();
4193
- if (retainedSplit)
4194
- this.pendingFrames.set("split", retainedSplit);
4195
- return false;
4196
- }
4197
- const framePriority = (frame) => {
4198
- const layer = pendingLayerKey(frame.key);
4199
- if (layer === "split")
4200
- return 1;
4201
- return 4;
4202
- };
4203
- const frames = Array.from(this.pendingFrames.values()).sort((a, b) => framePriority(a) - framePriority(b));
4204
- const prefix = "\x1B[?25l\x1B7\x1B[s\x1B[H";
4205
- const suffix = "\x1B[u\x1B8";
4206
- const selected = [];
4207
- const retained = new Map;
4208
- let sequence = "";
4209
- let byteLength = Buffer.byteLength(prefix + suffix);
4210
- for (const frame of frames) {
4211
- const candidate = sequence + frame.sequence;
4212
- const candidateBytes = Buffer.byteLength(prefix + candidate + suffix);
4213
- const layer = pendingLayerKey(frame.key);
4214
- const allowOneStaticFrame = selected.length === 0 && layer === "split";
4215
- if (candidateBytes <= this.outputBudgetBytes || allowOneStaticFrame) {
4216
- selected.push(frame);
4217
- sequence = candidate;
4218
- byteLength = candidateBytes;
4219
- } else if (layer === "split") {
4220
- retained.set(layer, frame);
4221
- }
4222
- }
4223
- this.pendingFrames.clear();
4224
- for (const [layer, frame] of retained)
4225
- this.pendingFrames.set(layer, frame);
4226
- if (!sequence || selected.length === 0)
4227
- return false;
4228
- const payload = `${prefix}${sequence}${suffix}`;
4229
- for (const frame of selected)
4230
- this.markWritten(frame.key);
4231
- this.outputBudgetBytes -= byteLength;
4232
- this.stdout.cork?.();
4233
- const accepted = this.stdout.write(payload);
4234
- this.stdout.uncork?.();
4235
- this.imagesVisible = true;
4236
- return accepted;
4237
- }
4238
- createSlot() {
4239
- return { worker: null, busy: false, queuedRequest: null, inFlightKey: "", queuedKey: "", clearedThroughRequestId: 0, latestRequestId: 0 };
4240
- }
4241
- resetSlot(slot) {
4242
- slot.busy = false;
4243
- slot.queuedRequest = null;
4244
- slot.inFlightKey = "";
4245
- slot.queuedKey = "";
4246
- }
4247
- markWritten(key) {
4248
- this.recentWrittenKeys.push(key);
4249
- while (this.recentWrittenKeys.length > RECENT_WRITTEN_LIMIT)
4250
- this.recentWrittenKeys.shift();
4251
- }
4252
- refillOutputBudget() {
4253
- const now = Date.now();
4254
- const elapsedMs = Math.max(0, now - this.lastBudgetAtMs);
4255
- this.lastBudgetAtMs = now;
4256
- this.outputBudgetBytes = Math.min(this.outputBytesPerSecond, this.outputBudgetBytes + elapsedMs / 1000 * this.outputBytesPerSecond);
4257
- }
4258
- isOutputBackedUp() {
4259
- return Boolean(this.stdout.writableNeedDrain) || this.stdout.writableLength > this.maxOutputBacklogBytes;
4260
- }
4261
- stageFrame(frame) {
4262
- this.pendingFrames.set(pendingLayerKey(frame.key), frame);
4263
- }
4264
- isKnownKey(slot, key) {
4265
- return this.recentWrittenKeys.includes(key) || Array.from(this.pendingFrames.values()).some((frame) => frame.key === key) || key === slot.inFlightKey || key === slot.queuedKey;
4266
- }
4267
- ensureWorkersStarted() {
4268
- if (this.workersStarted)
4269
- return;
4270
- this.workersStarted = true;
4271
- this.startWorker(this.motionSlot, "static-visuals");
4272
- }
4273
- failWorker(name, error) {
4274
- const detail = error instanceof Error ? error.message : String(error);
4275
- this.fatalWorkerError ??= new Error(`OpenTUI image worker ${name} failed: ${detail}`);
4276
- throw this.fatalWorkerError;
4277
- }
4278
- throwIfFatal() {
4279
- if (this.fatalWorkerError)
4280
- throw this.fatalWorkerError;
4281
- }
4282
- scheduleRender(slot, input, imageId, zIndex, force, imageIds) {
4283
- this.throwIfFatal();
4284
- const key = imageVisualFrameKey(input);
4285
- if (!force && this.isKnownKey(slot, key))
4286
- return;
4287
- const request = {
4288
- ...input,
4289
- imageId,
4290
- ...imageIds ? { imageIds } : {},
4291
- zIndex,
4292
- transport: this.transport,
4293
- requestId: ++this.requestId,
4294
- key
4295
- };
4296
- slot.latestRequestId = request.requestId;
4297
- if (!slot.worker) {
4298
- this.renderInline(slot, request);
4299
- return;
4300
- }
4301
- this.enqueueWorkerRender(slot, request);
4302
- }
4303
- startWorker(slot, name) {
4304
- const worker = createImageWorker();
4305
- if (!worker)
4306
- return;
4307
- slot.worker = worker;
4308
- worker.on("message", (message) => this.handleWorkerMessage(slot, message));
4309
- worker.once("error", (error) => {
4310
- if (!this.destroyed)
4311
- this.fatalWorkerError ??= new Error(`OpenTUI image worker ${name} failed: ${error instanceof Error ? error.message : String(error)}`);
4312
- });
4313
- worker.once("exit", (code) => {
4314
- if (!this.destroyed)
4315
- this.fatalWorkerError ??= new Error(`OpenTUI image worker ${name} exited unexpectedly with code ${code}.`);
4316
- });
4317
- }
4318
- renderInline(slot, request) {
4319
- try {
4320
- const result = renderImageVisualFrame(request);
4321
- if (result?.sequence && !this.isKnownKey(slot, result.key) && (!this.isOutputBackedUp() || pendingLayerKey(result.key) === "split")) {
4322
- this.stageFrame({ key: result.key, sequence: result.sequence });
4323
- this.onFrameReady?.();
4324
- }
4325
- } catch (error) {
4326
- this.failWorker(String(request.layer ?? "visual"), error);
4327
- }
4328
- }
4329
- enqueueWorkerRender(slot, request) {
4330
- this.throwIfFatal();
4331
- if (slot.busy) {
4332
- slot.queuedRequest = request;
4333
- slot.queuedKey = request.key;
4334
- return;
4335
- }
4336
- this.dispatchWorkerRender(slot, request);
4337
- }
4338
- dispatchWorkerRender(slot, request) {
4339
- this.throwIfFatal();
4340
- if (!slot.worker)
4341
- this.failWorker(String(request.layer ?? "visual"), new Error("worker is unavailable"));
4342
- slot.busy = true;
4343
- slot.inFlightKey = request.key;
4344
- try {
4345
- slot.worker.postMessage(request);
4346
- } catch (error) {
4347
- this.failWorker(String(request.layer ?? "visual"), error);
4348
- }
4349
- }
4350
- handleWorkerMessage(slot, message) {
4351
- if (this.destroyed || !message || typeof message !== "object")
4352
- return;
4353
- const response = message;
4354
- slot.busy = false;
4355
- slot.inFlightKey = "";
4356
- const isStale = response.requestId <= slot.clearedThroughRequestId || response.requestId < slot.latestRequestId;
4357
- if (!isStale) {
4358
- if (response.error)
4359
- this.failWorker(pendingLayerKey(response.key), new Error(response.error));
4360
- if (response.sequence && !this.isKnownKey(slot, response.key) && (!this.isOutputBackedUp() || pendingLayerKey(response.key) === "split")) {
4361
- this.stageFrame({ key: response.key, sequence: response.sequence });
4362
- this.onFrameReady?.();
4363
- }
4364
- }
4365
- const next = slot.queuedRequest;
4366
- slot.queuedRequest = null;
4367
- slot.queuedKey = "";
4368
- if (next && !this.isKnownKey(slot, next.key))
4369
- this.dispatchWorkerRender(slot, next);
4370
- }
3427
+ // packages/cli/src/app-opentui/render/text.ts
3428
+ var TRANSPARENT2 = RGBA4.fromInts(0, 0, 0, 0);
3429
+ var HOVER_BG = RGBA4.fromHex(RIG_UI.hover);
3430
+ function lineIsClickable(line2) {
3431
+ return line2?.selectable !== undefined || line2?.selectableIndex !== undefined;
4371
3432
  }
4372
- function pixelSizeForRenderer(renderer) {
4373
- const resolution = renderer.resolution;
4374
- const base = resolution?.width && resolution?.height ? resolution : { width: renderer.width * 12, height: renderer.height * 24 };
4375
- const scale = imageResolutionScale();
4376
- return {
4377
- width: Math.max(renderer.width, Math.floor(base.width * scale)),
4378
- height: Math.max(renderer.height, Math.floor(base.height * scale))
4379
- };
3433
+ function lineSelectableId(line2) {
3434
+ return line2?.selectable?.id ?? line2?.selectableId;
4380
3435
  }
4381
- function forceModernImageVisuals() {
4382
- return getRigTerminalCapabilities().supportsKittyImages;
3436
+ function lineBackground(line2) {
3437
+ if (line2?.bg)
3438
+ return RGBA4.fromHex(line2.bg);
3439
+ const id = lineSelectableId(line2);
3440
+ if (id !== undefined && isHovered(id))
3441
+ return HOVER_BG;
3442
+ return TRANSPARENT2;
4383
3443
  }
4384
-
4385
- // packages/cli/src/app-opentui/render/text.ts
4386
- import { RGBA as RGBA4, TextAttributes as TextAttributes4, TextRenderable } from "@opentui/core";
4387
- var TRANSPARENT2 = RGBA4.fromInts(0, 0, 0, 0);
4388
3444
  function selectableMouseHandler(currentLineRef, onLineMouseDown) {
4389
3445
  return (event) => {
4390
3446
  const currentLine = currentLineRef();
4391
- if (currentLine?.selectableIndex === undefined)
3447
+ if (!lineIsClickable(currentLine))
4392
3448
  return;
4393
3449
  onLineMouseDown?.(currentLine, event);
4394
3450
  };
4395
3451
  }
3452
+ function hoverHandlers(currentLineRef) {
3453
+ return {
3454
+ onMouseOver: () => {
3455
+ const id = lineSelectableId(currentLineRef());
3456
+ if (id !== undefined)
3457
+ setHoveredSelectableId(id);
3458
+ },
3459
+ onMouseOut: () => clearHoveredSelectableId(lineSelectableId(currentLineRef()))
3460
+ };
3461
+ }
4396
3462
  function createTextLine(renderer, id, top, onLineMouseDown) {
4397
3463
  let currentLine;
4398
3464
  const renderable = new TextRenderable(renderer, {
@@ -4404,7 +3470,8 @@ function createTextLine(renderer, id, top, onLineMouseDown) {
4404
3470
  width: 1,
4405
3471
  fg: RIG_UI.ink2,
4406
3472
  selectable: true,
4407
- onMouseDown: selectableMouseHandler(() => currentLine, onLineMouseDown)
3473
+ onMouseDown: selectableMouseHandler(() => currentLine, onLineMouseDown),
3474
+ ...hoverHandlers(() => currentLine)
4408
3475
  });
4409
3476
  renderable.setRigSceneLine = (line2) => {
4410
3477
  currentLine = line2;
@@ -4421,7 +3488,8 @@ function createFlowTextLine(renderer, id, onLineMouseDown) {
4421
3488
  flexShrink: 0,
4422
3489
  fg: RIG_UI.ink2,
4423
3490
  selectable: true,
4424
- onMouseDown: selectableMouseHandler(() => currentLine, onLineMouseDown)
3491
+ onMouseDown: selectableMouseHandler(() => currentLine, onLineMouseDown),
3492
+ ...hoverHandlers(() => currentLine)
4425
3493
  });
4426
3494
  renderable.setRigSceneLine = (line2) => {
4427
3495
  currentLine = line2;
@@ -4435,7 +3503,7 @@ function applyTextLine(renderable, line2, top, left, width) {
4435
3503
  renderable.width = width;
4436
3504
  renderable.content = line2?.styledText ?? line2?.text ?? "";
4437
3505
  renderable.fg = line2?.fg ?? RIG_UI.ink2;
4438
- renderable.bg = line2?.bg ?? TRANSPARENT2;
3506
+ renderable.bg = lineBackground(line2);
4439
3507
  renderable.attributes = line2?.bold ? TextAttributes4.BOLD : line2?.dim ? TextAttributes4.DIM : 0;
4440
3508
  }
4441
3509
  function applyFlowTextLine(renderable, line2, width) {
@@ -4445,7 +3513,7 @@ function applyFlowTextLine(renderable, line2, width) {
4445
3513
  renderable.width = Math.max(1, width);
4446
3514
  renderable.content = line2?.styledText ?? line2?.text ?? "";
4447
3515
  renderable.fg = line2?.fg ?? RIG_UI.ink2;
4448
- renderable.bg = line2?.bg ?? TRANSPARENT2;
3516
+ renderable.bg = lineBackground(line2);
4449
3517
  renderable.attributes = line2?.bold ? TextAttributes4.BOLD : line2?.dim ? TextAttributes4.DIM : 0;
4450
3518
  }
4451
3519
  function clearTextLines(renderables, from = 0) {
@@ -5557,7 +4625,6 @@ async function launchRigOpenTuiApp(options) {
5557
4625
  const events = createAppEventBus();
5558
4626
  let renderer = null;
5559
4627
  let graphics = null;
5560
- let imageVisuals = null;
5561
4628
  let preloaderBackdrop = null;
5562
4629
  const scrollPanels = [];
5563
4630
  let typeBar = null;
@@ -5585,7 +4652,7 @@ async function launchRigOpenTuiApp(options) {
5585
4652
  if (!renderer || destroyed)
5586
4653
  return;
5587
4654
  const currentState = store.getState();
5588
- renderedSelectableItems = renderNow(renderer, graphics, imageVisuals, preloaderBackdrop, scrollPanels, typeBar, brandText, navText, textLines, nativeHost, currentState, sceneRendererRegistry, preloaderTransition, scheduleAppRender);
4655
+ renderedSelectableItems = renderNow(renderer, graphics, preloaderBackdrop, scrollPanels, typeBar, brandText, navText, textLines, nativeHost, currentState, sceneRendererRegistry, preloaderTransition, scheduleAppRender);
5589
4656
  focusManager.restoreTypeBarFocus(typeBar, { hidden: currentState.scene === "handoff" });
5590
4657
  maybeRedirectFromUnresolvedScene(currentState);
5591
4658
  };
@@ -5745,18 +4812,8 @@ async function launchRigOpenTuiApp(options) {
5745
4812
  });
5746
4813
  renderer.setTerminalTitle?.("Operator cockpit");
5747
4814
  const layout = computeStageLayout(renderer.width, renderer.height);
5748
- imageVisuals = forceModernImageVisuals() ? new ImageVisualLayer(process.stdout, () => scheduleAppRender(0)) : null;
5749
- if (imageVisuals?.enabled) {
5750
- const flushImageVisuals = async () => {
5751
- imageVisuals?.flush();
5752
- };
5753
- renderer.setFrameCallback(flushImageVisuals);
5754
- resources.add(() => renderer?.removeFrameCallback(flushImageVisuals));
5755
- }
5756
- if (!imageVisuals?.enabled) {
5757
- graphics = createGraphicsLayer(renderer, layout);
5758
- renderer.root.add(graphics);
5759
- }
4815
+ graphics = createGraphicsLayer(renderer, layout);
4816
+ renderer.root.add(graphics);
5760
4817
  preloaderBackdrop = new BoxRenderable3(renderer, {
5761
4818
  id: "rig-preloader-backdrop",
5762
4819
  position: "absolute",
@@ -5779,8 +4836,7 @@ async function launchRigOpenTuiApp(options) {
5779
4836
  } else if (!selectById(store, id, renderedSelectableItems)) {
5780
4837
  return;
5781
4838
  }
5782
- if (line2.activateOnClick)
5783
- runAppAction("Activate selection", () => activateSelection(runtime, renderedSelectableItems));
4839
+ runAppAction("Activate selection", () => activateSelection(runtime, renderedSelectableItems));
5784
4840
  };
5785
4841
  for (let index = 0;index < MAX_SCENE_PANELS; index += 1) {
5786
4842
  const panel = createScrollPanelLayer(renderer, `rig-scroll-panel-${index}`, onLineClick);
@@ -5847,6 +4903,7 @@ async function launchRigOpenTuiApp(options) {
5847
4903
  };
5848
4904
  renderer.keyInput.on("keypress", onKeyPress);
5849
4905
  resources.add(() => renderer?.keyInput.off("keypress", onKeyPress));
4906
+ resources.add(subscribeHover(() => scheduleAppRender(0)));
5850
4907
  const onPaste = (event) => {
5851
4908
  const state = store.getState();
5852
4909
  if (state.scene === "handoff") {
@@ -5882,7 +4939,6 @@ async function launchRigOpenTuiApp(options) {
5882
4939
  stopActivePiHost("renderer destroyed");
5883
4940
  stopActiveCommandHost("renderer destroyed");
5884
4941
  destroyNativeHost(nativeHost);
5885
- imageVisuals?.destroy();
5886
4942
  if (renderTimer)
5887
4943
  clearTimeout(renderTimer);
5888
4944
  renderTimer = null;
@@ -5931,14 +4987,13 @@ async function launchRigOpenTuiApp(options) {
5931
4987
  clearTimeout(renderTimer);
5932
4988
  renderTimer = null;
5933
4989
  resources.disposeAll();
5934
- imageVisuals?.destroy();
5935
4990
  stopActivePiHost("app shutdown");
5936
4991
  stopActiveCommandHost("app shutdown");
5937
4992
  if (renderer && !renderer.isDestroyed)
5938
4993
  renderer.destroy();
5939
4994
  }
5940
4995
  }
5941
- function renderNow(renderer, graphics, imageVisuals, preloaderBackdrop, scrollPanels, typeBar, brandText, navText, textLines, nativeHost, state, sceneRendererRegistry, preloaderTransition, scheduleRenderAfter) {
4996
+ function renderNow(renderer, graphics, preloaderBackdrop, scrollPanels, typeBar, brandText, navText, textLines, nativeHost, state, sceneRendererRegistry, preloaderTransition, scheduleRenderAfter) {
5942
4997
  const layout = computeStageLayout(renderer.width, renderer.height);
5943
4998
  const hideNav = () => {
5944
4999
  if (navText)
@@ -5947,7 +5002,6 @@ function renderNow(renderer, graphics, imageVisuals, preloaderBackdrop, scrollPa
5947
5002
  if (layout.tooSmall) {
5948
5003
  if (graphics)
5949
5004
  clearGraphicsLayer(graphics);
5950
- imageVisuals?.clear();
5951
5005
  if (preloaderBackdrop)
5952
5006
  preloaderBackdrop.visible = false;
5953
5007
  if (brandText)
@@ -5981,8 +5035,7 @@ function renderNow(renderer, graphics, imageVisuals, preloaderBackdrop, scrollPa
5981
5035
  preloaderTransition.minUntilMs = now;
5982
5036
  const targetFrame = renderState.typeBar.mode === "command" ? commandPaletteFrame(renderState, layout) : rendererForScene(renderState.scene, sceneRendererRegistry)(renderState, layout);
5983
5037
  const targetContentLayout = targetFrame.fullScreen ? { ...layout, centerWidth: layout.width, centerLeft: 0, centerTop: 0, centerHeight: Math.min(layout.height, MAX_SCENE_LINES) } : layout;
5984
- const targetPanelsForImages = (targetFrame.panels ?? []).map((panel) => ({ ...panel }));
5985
- let targetVisualReady = true;
5038
+ const targetVisualReady = true;
5986
5039
  const fleetStats = fleetStatsFromState(state);
5987
5040
  const fleetLoad = fleetStats.load;
5988
5041
  const fleetActive = fleetStats.active;
@@ -5992,23 +5045,7 @@ function renderNow(renderer, graphics, imageVisuals, preloaderBackdrop, scrollPa
5992
5045
  if (renderState.scene === "command") {
5993
5046
  getActiveCommandHost()?.resize(Math.max(40, targetContentLayout.centerWidth - 12), Math.max(10, targetContentLayout.centerHeight - 6));
5994
5047
  }
5995
- if (imageVisuals && targetFrame.terminalActive) {
5996
- imageVisuals.clear();
5997
- } else if (imageVisuals && !targetFrame.fullScreen && !sceneJustChanged) {
5998
- const visualInput = {
5999
- layout: targetContentLayout,
6000
- pixelSize: pixelSizeForRenderer(renderer),
6001
- panels: targetPanelsForImages,
6002
- scene: state.scene,
6003
- tick: state.tick,
6004
- load: fleetLoad,
6005
- activeRuns: fleetActive
6006
- };
6007
- imageVisuals.render(visualInput);
6008
- targetVisualReady = imageVisuals.isReady(visualInput);
6009
- }
6010
5048
  const preloaderActive = sceneJustChanged && !cachedFastPath || isPreloaderActive(preloaderTransition, targetVisualReady, now);
6011
- imageVisuals?.setFlushPaused(preloaderActive);
6012
5049
  const frame = preloaderActive ? renderAsciiPreloaderFrame(renderState.scene, layout, state.tick) : targetFrame;
6013
5050
  if (graphics) {
6014
5051
  resizeGraphicsLayer(graphics, layout);