@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
package/dist/bin/rig.js CHANGED
@@ -17453,6 +17453,7 @@ var init_theme2 = __esm(() => {
17453
17453
  bg2: "#0b0c0e",
17454
17454
  panel: "#101115",
17455
17455
  panel2: "#14161b",
17456
+ hover: "#181b24",
17456
17457
  glass: "#1e2230",
17457
17458
  border: "#2a2e3a",
17458
17459
  ink: "#f4f5f8",
@@ -19802,11 +19803,8 @@ var init_family2 = __esm(() => {
19802
19803
  });
19803
19804
 
19804
19805
  // packages/cli/src/app-opentui/list-search.ts
19805
- function isListSearchScene(scene) {
19806
- return scene === "fleet" || scene === "tasks";
19807
- }
19808
- function listSearchQuery(state) {
19809
- return isListSearchScene(state.scene) ? state.typeBar.value.trim() : "";
19806
+ function listSearchQuery(_state) {
19807
+ return "";
19810
19808
  }
19811
19809
  function valueText(value) {
19812
19810
  if (value === undefined || value === null)
@@ -24321,22 +24319,9 @@ function panelContentRect(layout, panel) {
24321
24319
  const contentWidth = Math.max(1, width - paddingX * 2 - 1);
24322
24320
  return { left, top, width, height, paddingX, paddingY, contentWidth };
24323
24321
  }
24324
- function panelPixelPlacement(layout, panel, size, minPixels = 8) {
24325
- const cellW = size.width / layout.width;
24326
- const cellH = size.height / layout.height;
24327
- const leftCells = layout.centerLeft + (panel.left ?? 0);
24328
- const topCells = layout.centerTop + panel.top;
24329
- const widthCells = panel.width ?? layout.centerWidth;
24330
- const heightCells = panel.height;
24331
- const left = Math.round(leftCells * cellW);
24332
- const top = Math.round(topCells * cellH);
24333
- const width = Math.round(widthCells * cellW);
24334
- const height = Math.round(heightCells * cellH);
24335
- return width > minPixels && height > minPixels ? { left, top, width, height, leftCells, topCells, widthCells, heightCells } : null;
24336
- }
24337
24322
 
24338
24323
  // packages/cli/src/app-opentui/render/constants.ts
24339
- var LCG_MULTIPLIER = 1664525, LCG_INCREMENT = 1013904223, LCG_DIVISOR = 4294967295, PACKET_CYAN_PROBABILITY = 0.22, LANE_INTERP_STEPS = 32, LANE_LEN_BASE = 0.32, LANE_LEN_SPREAD = 0.42, LANE_CURVE_RANGE = 0.7, PACKET_SPEED_BASE = 0.003, PACKET_SPEED_SPREAD = 0.009, DISPATCH_LANES_COMPACT = 7, DISPATCH_LANES_WIDE = 12, DISPATCH_PACKETS_COMPACT = 70, DISPATCH_PACKETS_WIDE = 150, CARRIER_NODES_COMPACT = 5, CARRIER_NODES_WIDE = 7, LOOP_GATES_COMPACT = 4, LOOP_GATES_WIDE = 5, DISPATCH_DRONES_COMPACT = 4, DISPATCH_DRONES_WIDE = 8, CORE_DISPATCH_X = 0.3, CORE_DISPATCH_Y = 0.45, CORE_CARRIER_X = 0.5, CORE_CARRIER_Y = 0.54, CORE_LOOP_X = 0.09, CORE_LOOP_Y = 0.56, RAIL_YS_LOOP, RAIL_YS_CARRIER, RAIL_YS_DISPATCH, GLOW_FALLOFF_EXP_ASCII = 2.2, GLOW_FALLOFF_EXP_IMAGE = 2.4;
24324
+ var LCG_MULTIPLIER = 1664525, LCG_INCREMENT = 1013904223, LCG_DIVISOR = 4294967295, PACKET_CYAN_PROBABILITY = 0.22, LANE_INTERP_STEPS = 32, LANE_LEN_BASE = 0.32, LANE_LEN_SPREAD = 0.42, LANE_CURVE_RANGE = 0.7, PACKET_SPEED_BASE = 0.003, PACKET_SPEED_SPREAD = 0.009, DISPATCH_LANES_COMPACT = 7, DISPATCH_LANES_WIDE = 12, DISPATCH_PACKETS_COMPACT = 70, DISPATCH_PACKETS_WIDE = 150, CARRIER_NODES_COMPACT = 5, CARRIER_NODES_WIDE = 7, LOOP_GATES_COMPACT = 4, LOOP_GATES_WIDE = 5, DISPATCH_DRONES_COMPACT = 4, DISPATCH_DRONES_WIDE = 8, CORE_DISPATCH_X = 0.3, CORE_DISPATCH_Y = 0.45, CORE_CARRIER_X = 0.5, CORE_CARRIER_Y = 0.54, CORE_LOOP_X = 0.09, CORE_LOOP_Y = 0.56, RAIL_YS_LOOP, RAIL_YS_CARRIER, RAIL_YS_DISPATCH, GLOW_FALLOFF_EXP_ASCII = 2.2;
24340
24325
  var init_constants = __esm(() => {
24341
24326
  RAIL_YS_LOOP = [0.18, 0.5, 0.82];
24342
24327
  RAIL_YS_CARRIER = [0.24, 0.54, 0.82];
@@ -24755,27 +24740,20 @@ function writeCell(layer, x, y, char, fg2, bg) {
24755
24740
  function drawFullCell(layer, x, y, bg) {
24756
24741
  writeCell(layer, x, y, " ", TRANSPARENT, bg);
24757
24742
  }
24758
- function drawCornerCut(layer, x, y, char, fg2) {
24759
- writeCell(layer, x, y, char, fg2, BACKDROP);
24760
- }
24761
- function drawRoundedFill(layer, left, top, right, bottom, dividerY) {
24743
+ function drawPanelFill(layer, left, top, right, bottom, dividerY) {
24762
24744
  const bgForY = (y) => y < dividerY ? PANEL_HEADER_BG : PANEL_BG;
24763
24745
  for (let y = top;y <= bottom; y += 1) {
24764
24746
  const bg = bgForY(y);
24765
24747
  for (let x = left;x <= right; x += 1)
24766
24748
  drawFullCell(layer, x, y, bg);
24767
24749
  }
24768
- drawCornerCut(layer, left, top, "\u2597", PANEL_HEADER_BG);
24769
- drawCornerCut(layer, right, top, "\u2596", PANEL_HEADER_BG);
24770
- drawCornerCut(layer, left, bottom, "\u259D", PANEL_BG);
24771
- drawCornerCut(layer, right, bottom, "\u2598", PANEL_BG);
24772
24750
  }
24773
24751
  function drawAdTerminalPanel(layer, layout, panel) {
24774
24752
  const rect = panelCellRect(layout, panel);
24775
24753
  if (!rect)
24776
24754
  return;
24777
24755
  const { left, top, width, right, bottom, headerHeight, dividerY } = rect;
24778
- drawRoundedFill(layer, left, top, right, bottom, dividerY);
24756
+ drawPanelFill(layer, left, top, right, bottom, dividerY);
24779
24757
  if (dividerY > top && dividerY < bottom) {
24780
24758
  for (let x = left + 2;x < right - 1; x += 1) {
24781
24759
  writeCell(layer, x, dividerY, "\u2500", PANEL_LINE_DIM, PANEL_BG);
@@ -24883,973 +24861,69 @@ var init_graphics = __esm(() => {
24883
24861
  ];
24884
24862
  });
24885
24863
 
24886
- // packages/cli/src/app-opentui/terminal-capabilities.ts
24887
- import { getCapabilities } from "@earendil-works/pi-tui";
24888
- function getRigTerminalCapabilities() {
24889
- const imageProtocol = getCapabilities().images;
24890
- const supportsKittyImages = imageProtocol === "kitty";
24891
- return {
24892
- imageProtocol,
24893
- supportsKittyImages,
24894
- visualMode: supportsKittyImages ? "kitty-images" : "framebuffer-fallback"
24895
- };
24896
- }
24897
- var init_terminal_capabilities = () => {};
24898
-
24899
- // packages/cli/src/app-opentui/render/image-visual-layer.ts
24900
- import { Worker } from "worker_threads";
24901
- import { deflateSync } from "zlib";
24902
- import { allocateImageId, deleteKittyImage } from "@earendil-works/pi-tui";
24903
- function loadBucket(load) {
24904
- if (!Number.isFinite(load ?? NaN))
24905
- return 0;
24906
- return Math.max(0, Math.min(LOAD_BUCKETS, Math.round((load ?? 0) * LOAD_BUCKETS)));
24907
- }
24908
- function imageTransport() {
24909
- const forced = process.env.RIG_OPENTUI_IMAGE_TRANSPORT;
24910
- if (forced === "rgba-zlib")
24911
- return "rgba-zlib";
24912
- return "png";
24913
- }
24914
- function imageResolutionScale() {
24915
- const raw = Number.parseFloat(process.env.RIG_OPENTUI_IMAGE_SCALE ?? "");
24916
- if (Number.isFinite(raw) && raw > 0)
24917
- return Math.max(0.25, Math.min(1, raw));
24918
- return DEFAULT_IMAGE_RESOLUTION_SCALE;
24919
- }
24920
- function rgbaWord(color) {
24921
- return (color[3] << 24 | color[2] << 16 | color[1] << 8 | color[0]) >>> 0;
24922
- }
24923
- function fillRgba(data, color, word = rgbaWord(color)) {
24924
- if (data.byteOffset % 4 === 0 && data.byteLength % 4 === 0) {
24925
- new Uint32Array(data.buffer, data.byteOffset, data.byteLength / 4).fill(word);
24926
- return;
24927
- }
24928
- for (let i = 0;i < data.length; i += 4) {
24929
- data[i] = color[0];
24930
- data[i + 1] = color[1];
24931
- data[i + 2] = color[2];
24932
- data[i + 3] = color[3];
24933
- }
24934
- }
24935
- function sceneStaticTick(scene) {
24936
- let hash = 0;
24937
- for (let index = 0;index < scene.length; index += 1)
24938
- hash = hash * 33 + scene.charCodeAt(index) >>> 0;
24939
- return 11 + hash % 97;
24940
- }
24941
- function charColor(char, row, totalRows) {
24942
- if (char === "@" || char === "$" || char === "o")
24943
- return LIME;
24944
- if (char === "%" || char === "!" || char === "/" || char === "\\" || char === "|")
24945
- return CYAN;
24946
- if (char === "." || char === "'" || char === "_" || row < 3 || row > totalRows - 4)
24947
- return INK_DIM;
24948
- if (char === "-" || char === "=" || char === "(" || char === ")")
24949
- return LIME_DIM;
24950
- return MAGENTA;
24951
- }
24952
- function fillRectAlpha(data, width, height, left, top, rectWidth, rectHeight, color, alpha) {
24953
- const x0 = Math.max(0, Math.floor(left));
24954
- const y0 = Math.max(0, Math.floor(top));
24955
- const x1 = Math.min(width, Math.ceil(left + rectWidth));
24956
- const y1 = Math.min(height, Math.ceil(top + rectHeight));
24957
- for (let y = y0;y < y1; y += 1) {
24958
- for (let x = x0;x < x1; x += 1)
24959
- blendOver(data, (y * width + x) * 4, color, alpha);
24960
- }
24961
- }
24962
- function drawAsciiGlyph(data, width, height, x, y, cellWidth, cellHeight, char, color, alpha) {
24963
- const pattern = ASCII_GLYPHS[char] ?? ASCII_GLYPHS["*"];
24964
- const glyphWidth = Math.max(1, cellWidth * 0.56);
24965
- const glyphHeight = Math.max(1, cellHeight * 0.7);
24966
- const originX = x + (cellWidth - glyphWidth) * 0.5;
24967
- const originY = y + (cellHeight - glyphHeight) * 0.5;
24968
- const pixelWidth = Math.max(1, glyphWidth / pattern[0].length);
24969
- const pixelHeight = Math.max(1, glyphHeight / pattern.length);
24970
- for (let row = 0;row < pattern.length; row += 1) {
24971
- const bits = pattern[row];
24972
- for (let col = 0;col < bits.length; col += 1) {
24973
- if (bits[col] !== "1")
24974
- continue;
24975
- fillRectAlpha(data, width, height, originX + col * pixelWidth, originY + row * pixelHeight, pixelWidth * 0.72, pixelHeight * 0.72, color, alpha);
24976
- }
24977
- }
24978
- }
24979
- function drawStaticAsciiFleetBackground(data, width, height, scene, layout, load, activeRuns) {
24980
- const rows = fleetRows(sceneStaticTick(scene), { animate: true });
24981
- const cellWidth = width / Math.max(1, layout.width);
24982
- const cellHeight = height / Math.max(1, layout.height);
24983
- const sideBySide = layout.width >= FLEET_GRID_WIDTH * 1.9;
24984
- const leftCells = sideBySide ? Math.max(1, Math.floor(layout.width * 0.3 - FLEET_GRID_WIDTH / 2)) : Math.floor((layout.width - FLEET_GRID_WIDTH) / 2);
24985
- const topCells = Math.floor((layout.height - FLEET_GRID_HEIGHT) / 2);
24986
- const fleetLeft = leftCells * cellWidth;
24987
- const fleetTop = topCells * cellHeight;
24988
- drawGlow2(data, width, height, width * 0.5, height * 0.5, Math.min(width, height) * 0.4, LIME, 0.022 + load * 0.03);
24989
- drawGlow2(data, width, height, width * 0.18, height * 0.22, Math.min(width, height) * 0.28, CYAN, 0.018 + load * 0.012);
24990
- drawGlow2(data, width, height, width * 0.82, height * 0.78, Math.min(width, height) * 0.22, MAGENTA, 0.012 + load * 0.008);
24991
- let droneTotal = 0;
24992
- for (const line2 of rows)
24993
- for (const char of line2)
24994
- if (char === "@" || char === "$" || char === "%")
24995
- droneTotal += 1;
24996
- const litTarget = Math.max(0, Math.min(droneTotal, activeRuns));
24997
- let droneIndex = 0;
24998
- for (let row = 0;row < rows.length; row += 1) {
24999
- const line2 = rows[row];
25000
- for (let col = 0;col < line2.length; col += 1) {
25001
- const char = line2[col];
25002
- if (char === " ")
25003
- continue;
25004
- const x = fleetLeft + col * cellWidth;
25005
- const y = fleetTop + row * cellHeight;
25006
- const color = charColor(char, row, rows.length);
25007
- const isDrone = char === "@" || char === "$" || char === "%";
25008
- const lit = isDrone && droneIndex < litTarget;
25009
- drawAsciiGlyph(data, width, height, x + cellWidth * 0.07, y + cellHeight * 0.05, cellWidth, cellHeight, char, color, lit ? 0.72 : 0.46);
25010
- if (isDrone) {
25011
- droneIndex += 1;
25012
- drawGlow2(data, width, height, x + cellWidth * 0.5, y + cellHeight * 0.5, Math.max(cellWidth, cellHeight) * 0.95, color, lit ? 0.044 : 0.014);
25013
- }
25014
- }
25015
- }
25016
- }
25017
- function applyStaticAtmosphere(data, width, height, load) {
25018
- const cx = width * 0.5;
25019
- const cy = height * 0.42;
25020
- const radius = Math.max(width, height) * 0.78;
25021
- const minVignette = 0.1 - load * 0.02;
25022
- for (let y = 0;y < height; y += 1) {
25023
- for (let x = 0;x < width; x += 1) {
25024
- const distance = Math.hypot(x - cx, y - cy) / radius;
25025
- const alpha = Math.max(minVignette, Math.min(0.82, 0.12 + Math.pow(distance, 1.65) * 0.62));
25026
- blendOver(data, (y * width + x) * 4, BG, alpha);
25027
- }
25028
- }
25029
- drawGlow2(data, width, height, width * 0.82, height * 0.04, Math.min(width, height) * 0.2, LIME, 0.024 + load * 0.012);
25030
- drawGlow2(data, width, height, width * 0.12, height * 0.18, Math.min(width, height) * 0.16, CYAN, 0.018 + load * 0.008);
25031
- }
25032
- function makeCrcTable() {
25033
- const table = new Uint32Array(256);
25034
- for (let n = 0;n < 256; n += 1) {
25035
- let c = n;
25036
- for (let k = 0;k < 8; k += 1)
25037
- c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
25038
- table[n] = c >>> 0;
25039
- }
25040
- return table;
25041
- }
25042
- function crc32(bytes) {
25043
- let c = 4294967295;
25044
- for (let i = 0;i < bytes.length; i += 1)
25045
- c = CRC_TABLE[(c ^ bytes[i]) & 255] ^ c >>> 8;
25046
- return (c ^ 4294967295) >>> 0;
25047
- }
25048
- function chunk2(type, data) {
25049
- const out = new Uint8Array(12 + data.length);
25050
- const view = new DataView(out.buffer);
25051
- view.setUint32(0, data.length);
25052
- out[4] = type.charCodeAt(0);
25053
- out[5] = type.charCodeAt(1);
25054
- out[6] = type.charCodeAt(2);
25055
- out[7] = type.charCodeAt(3);
25056
- out.set(data, 8);
25057
- view.setUint32(8 + data.length, crc32(out.subarray(4, 8 + data.length)));
25058
- return out;
24864
+ // packages/cli/src/app-opentui/render/hover.ts
24865
+ function getHoveredSelectableId() {
24866
+ return hoveredId;
25059
24867
  }
25060
- function encodePng(width, height, rgba) {
25061
- const ihdr = new Uint8Array(13);
25062
- const ihdrView = new DataView(ihdr.buffer);
25063
- ihdrView.setUint32(0, width);
25064
- ihdrView.setUint32(4, height);
25065
- ihdr[8] = 8;
25066
- ihdr[9] = 6;
25067
- ihdr[10] = 0;
25068
- ihdr[11] = 0;
25069
- ihdr[12] = 0;
25070
- const stride = width * 4;
25071
- const raw = new Uint8Array((stride + 1) * height);
25072
- for (let y = 0;y < height; y += 1) {
25073
- const rawOffset = y * (stride + 1);
25074
- raw[rawOffset] = 0;
25075
- raw.set(rgba.subarray(y * stride, y * stride + stride), rawOffset + 1);
25076
- }
25077
- const compressed = deflateSync(raw, { level: ZLIB_LEVEL });
25078
- return Buffer.concat([
25079
- Buffer.from(PNG_SIGNATURE),
25080
- Buffer.from(chunk2("IHDR", ihdr)),
25081
- Buffer.from(chunk2("IDAT", compressed)),
25082
- Buffer.from(chunk2("IEND", new Uint8Array(0)))
25083
- ]);
24868
+ function isHovered(id) {
24869
+ return id !== undefined && id === hoveredId;
25084
24870
  }
25085
- function kittyImageSequence(base64, options) {
25086
- const params = [`a=T`, `f=${options.format}`, `q=2`, `C=1`, `c=${options.columns}`, `r=${options.rows}`, `i=${options.imageId}`, `z=${options.zIndex}`];
25087
- if (options.format === 32) {
25088
- params.push(`s=${options.pixelWidth ?? 1}`, `v=${options.pixelHeight ?? 1}`);
25089
- if (options.compressed)
25090
- params.push("o=z");
25091
- }
25092
- if (base64.length <= KITTY_CHUNK_SIZE)
25093
- return `\x1B_G${params.join(",")};${base64}\x1B\\`;
25094
- const chunks = [];
25095
- for (let offset = 0;offset < base64.length; offset += KITTY_CHUNK_SIZE) {
25096
- const part = base64.slice(offset, offset + KITTY_CHUNK_SIZE);
25097
- const last = offset + KITTY_CHUNK_SIZE >= base64.length;
25098
- chunks.push(`\x1B_G${offset === 0 ? `${params.join(",")},` : ""}m=${last ? 0 : 1};${part}\x1B\\`);
25099
- }
25100
- return chunks.join("");
25101
- }
25102
- function clampByte(value) {
25103
- return Math.max(0, Math.min(255, Math.round(value)));
25104
- }
25105
- function blendOver(data, index, color, alphaScale = 1) {
25106
- const alpha = color[3] / 255 * alphaScale;
25107
- const inv = 1 - alpha;
25108
- data[index] = clampByte(color[0] * alpha + data[index] * inv);
25109
- data[index + 1] = clampByte(color[1] * alpha + data[index + 1] * inv);
25110
- data[index + 2] = clampByte(color[2] * alpha + data[index + 2] * inv);
25111
- data[index + 3] = 255;
25112
- }
25113
- function drawDisc(data, width, height, cx, cy, radius, color, alpha = 1) {
25114
- const minX = Math.max(0, Math.floor(cx - radius));
25115
- const maxX = Math.min(width - 1, Math.ceil(cx + radius));
25116
- const minY = Math.max(0, Math.floor(cy - radius));
25117
- const maxY = Math.min(height - 1, Math.ceil(cy + radius));
25118
- for (let y = minY;y <= maxY; y += 1) {
25119
- for (let x = minX;x <= maxX; x += 1) {
25120
- const dx = x + 0.5 - cx;
25121
- const dy = y + 0.5 - cy;
25122
- const d = Math.sqrt(dx * dx + dy * dy);
25123
- if (d > radius)
25124
- continue;
25125
- const edge = Math.max(0, Math.min(1, radius - d));
25126
- blendOver(data, (y * width + x) * 4, color, alpha * Math.min(1, 0.35 + edge));
25127
- }
25128
- }
25129
- }
25130
- function drawGlow2(data, width, height, cx, cy, radius, color, alpha = 0.18) {
25131
- const minX = Math.max(0, Math.floor(cx - radius));
25132
- const maxX = Math.min(width - 1, Math.ceil(cx + radius));
25133
- const minY = Math.max(0, Math.floor(cy - radius));
25134
- const maxY = Math.min(height - 1, Math.ceil(cy + radius));
25135
- for (let y = minY;y <= maxY; y += 1) {
25136
- for (let x = minX;x <= maxX; x += 1) {
25137
- const dx = x + 0.5 - cx;
25138
- const dy = y + 0.5 - cy;
25139
- const d = Math.sqrt(dx * dx + dy * dy);
25140
- if (d > radius)
25141
- continue;
25142
- const falloff = Math.pow(1 - d / Math.max(1, radius), GLOW_FALLOFF_EXP_IMAGE);
25143
- blendOver(data, (y * width + x) * 4, color, alpha * falloff);
25144
- }
25145
- }
25146
- }
25147
- function drawLine2(data, width, height, x0, y0, x1, y1, color, alpha = 0.5, thickness = 1.4) {
25148
- const dx = x1 - x0;
25149
- const dy = y1 - y0;
25150
- const steps = Math.max(1, Math.ceil(Math.hypot(dx, dy) * 1.2));
25151
- for (let i = 0;i <= steps; i += 1) {
25152
- const t2 = i / steps;
25153
- drawDisc(data, width, height, x0 + dx * t2, y0 + dy * t2, thickness, color, alpha);
25154
- }
25155
- }
25156
- function roundRectAlpha(px, py, x, y, w, h, r) {
25157
- const rx = Math.max(x + r, Math.min(px, x + w - r));
25158
- const ry = Math.max(y + r, Math.min(py, y + h - r));
25159
- const dx = px - rx;
25160
- const dy = py - ry;
25161
- const dist = Math.sqrt(dx * dx + dy * dy);
25162
- return Math.max(0, Math.min(1, r + 0.75 - dist));
25163
- }
25164
- function panelRect(layout, panel, size) {
25165
- return panelPixelPlacement(layout, panel, size);
25166
- }
25167
- function downsampleRegion(data, canvasW, canvasH, rect, scale) {
25168
- const w = Math.max(1, Math.ceil(rect.width / scale));
25169
- const h = Math.max(1, Math.ceil(rect.height / scale));
25170
- const out = new Uint8ClampedArray(w * h * 3);
25171
- for (let y = 0;y < h; y += 1) {
25172
- for (let x = 0;x < w; x += 1) {
25173
- let r = 0, g = 0, b = 0, count = 0;
25174
- const sx0 = rect.left + x * scale;
25175
- const sy0 = rect.top + y * scale;
25176
- for (let yy = 0;yy < scale; yy += 1) {
25177
- const sy = sy0 + yy;
25178
- if (sy < 0 || sy >= canvasH)
25179
- continue;
25180
- for (let xx = 0;xx < scale; xx += 1) {
25181
- const sx = sx0 + xx;
25182
- if (sx < 0 || sx >= canvasW)
25183
- continue;
25184
- const i = (sy * canvasW + sx) * 4;
25185
- r += data[i];
25186
- g += data[i + 1];
25187
- b += data[i + 2];
25188
- count += 1;
25189
- }
25190
- }
25191
- const oi = (y * w + x) * 3;
25192
- out[oi] = count ? r / count : BG[0];
25193
- out[oi + 1] = count ? g / count : BG[1];
25194
- out[oi + 2] = count ? b / count : BG[2];
25195
- }
25196
- }
25197
- return { pixels: out, width: w, height: h };
25198
- }
25199
- function blurRgb(buffer, width, height, radius) {
25200
- const tmp = new Uint8ClampedArray(buffer.length);
25201
- const out = new Uint8ClampedArray(buffer.length);
25202
- const window = radius * 2 + 1;
25203
- for (let y = 0;y < height; y += 1) {
25204
- let r = 0, g = 0, b = 0;
25205
- for (let x = -radius;x <= radius; x += 1) {
25206
- const sx = Math.max(0, Math.min(width - 1, x));
25207
- const i = (y * width + sx) * 3;
25208
- r += buffer[i];
25209
- g += buffer[i + 1];
25210
- b += buffer[i + 2];
25211
- }
25212
- for (let x = 0;x < width; x += 1) {
25213
- const oi = (y * width + x) * 3;
25214
- tmp[oi] = r / window;
25215
- tmp[oi + 1] = g / window;
25216
- tmp[oi + 2] = b / window;
25217
- const removeX = Math.max(0, x - radius);
25218
- const addX = Math.min(width - 1, x + radius + 1);
25219
- const ri = (y * width + removeX) * 3;
25220
- const ai = (y * width + addX) * 3;
25221
- r += tmp.length ? buffer[ai] - buffer[ri] : 0;
25222
- g += tmp.length ? buffer[ai + 1] - buffer[ri + 1] : 0;
25223
- b += tmp.length ? buffer[ai + 2] - buffer[ri + 2] : 0;
25224
- }
25225
- }
25226
- for (let x = 0;x < width; x += 1) {
25227
- let r = 0, g = 0, b = 0;
25228
- for (let y = -radius;y <= radius; y += 1) {
25229
- const sy = Math.max(0, Math.min(height - 1, y));
25230
- const i = (sy * width + x) * 3;
25231
- r += tmp[i];
25232
- g += tmp[i + 1];
25233
- b += tmp[i + 2];
25234
- }
25235
- for (let y = 0;y < height; y += 1) {
25236
- const oi = (y * width + x) * 3;
25237
- out[oi] = r / window;
25238
- out[oi + 1] = g / window;
25239
- out[oi + 2] = b / window;
25240
- const removeY = Math.max(0, y - radius);
25241
- const addY = Math.min(height - 1, y + radius + 1);
25242
- const ri = (removeY * width + x) * 3;
25243
- const ai = (addY * width + x) * 3;
25244
- r += tmp[ai] - tmp[ri];
25245
- g += tmp[ai + 1] - tmp[ri + 1];
25246
- b += tmp[ai + 2] - tmp[ri + 2];
25247
- }
25248
- }
25249
- return out;
25250
- }
25251
- function panelMaskKey(width, height, rect, headerPx, blurScale) {
25252
- return `${width}x${height}:${rect.left},${rect.top},${rect.width},${rect.height}:${Math.round(headerPx * 100) / 100}:${blurScale}`;
25253
- }
25254
- function getPanelMask(width, height, rect, headerPx, blurScale) {
25255
- const key = panelMaskKey(width, height, rect, headerPx, blurScale);
25256
- const cached = panelMaskCache.get(key);
25257
- if (cached)
25258
- return cached;
25259
- 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)));
25260
- const sampledWidth = Math.max(1, Math.ceil(rect.width / blurScale));
25261
- const sampledHeight = Math.max(1, Math.ceil(rect.height / blurScale));
25262
- const x0 = Math.max(0, rect.left);
25263
- const y0 = Math.max(0, rect.top);
25264
- const x1 = Math.min(width - 1, rect.left + rect.width - 1);
25265
- const y1 = Math.min(height - 1, rect.top + rect.height - 1);
25266
- const indices = [];
25267
- const sampleIndices = [];
25268
- const panelAlpha = [];
25269
- const edgeAlpha = [];
25270
- const headerFlags = [];
25271
- const dividerIndices = [];
25272
- for (let y = y0;y <= y1; y += 1) {
25273
- for (let x = x0;x <= x1; x += 1) {
25274
- const a = roundRectAlpha(x + 0.5, y + 0.5, rect.left, rect.top, rect.width, rect.height, radius);
25275
- if (a <= 0)
25276
- continue;
25277
- 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));
25278
- const sx = Math.max(0, Math.min(sampledWidth - 1, Math.floor((x - rect.left) / blurScale)));
25279
- const sy = Math.max(0, Math.min(sampledHeight - 1, Math.floor((y - rect.top) / blurScale)));
25280
- indices.push((y * width + x) * 4);
25281
- sampleIndices.push((sy * sampledWidth + sx) * 3);
25282
- panelAlpha.push(a);
25283
- edgeAlpha.push(edge < 0.96 ? Math.min(1, (1 - edge) * 0.7) : 0);
25284
- headerFlags.push(y < rect.top + headerPx ? 1 : 0);
25285
- }
25286
- }
25287
- const dividerY = Math.round(rect.top + headerPx);
25288
- if (dividerY > y0 && dividerY < y1) {
25289
- for (let x = x0 + radius;x <= x1 - radius; x += 1) {
25290
- dividerIndices.push((dividerY * width + x) * 4);
25291
- }
25292
- }
25293
- const mask = {
25294
- key,
25295
- indices: Uint32Array.from(indices),
25296
- sampleIndices: Uint32Array.from(sampleIndices),
25297
- panelAlpha: Float32Array.from(panelAlpha),
25298
- edgeAlpha: Float32Array.from(edgeAlpha),
25299
- headerFlags: Uint8Array.from(headerFlags),
25300
- dividerIndices: Uint32Array.from(dividerIndices)
25301
- };
25302
- panelMaskCache.set(key, mask);
25303
- while (panelMaskCache.size > PANEL_MASK_CACHE_LIMIT) {
25304
- const oldest = panelMaskCache.keys().next().value;
25305
- if (!oldest)
25306
- break;
25307
- panelMaskCache.delete(oldest);
25308
- }
25309
- return mask;
25310
- }
25311
- function compositePanel(data, width, height, rect, headerPx) {
25312
- const blurScale = PANEL_BLUR_SCALE;
25313
- const sampled = downsampleRegion(data, width, height, rect, blurScale);
25314
- 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)));
25315
- const blurred = blurRgb(sampled.pixels, sampled.width, sampled.height, blurRadius);
25316
- const mask = getPanelMask(width, height, rect, headerPx, blurScale);
25317
- for (let i = 0;i < mask.indices.length; i += 1) {
25318
- const idx = mask.indices[i];
25319
- const sampleIdx = mask.sampleIndices[i];
25320
- data[idx] = blurred[sampleIdx];
25321
- data[idx + 1] = blurred[sampleIdx + 1];
25322
- data[idx + 2] = blurred[sampleIdx + 2];
25323
- data[idx + 3] = 255;
25324
- blendOver(data, idx, mask.headerFlags[i] ? PANEL_HEADER : PANEL, mask.panelAlpha[i]);
25325
- const edge = mask.edgeAlpha[i];
25326
- if (edge > 0)
25327
- blendOver(data, idx, PANEL_LINE2, edge);
25328
- }
25329
- for (let i = 0;i < mask.dividerIndices.length; i += 1) {
25330
- blendOver(data, mask.dividerIndices[i], PANEL_LINE2, 0.28);
25331
- }
25332
- }
25333
- function drawBigRigWordmark(data, width, height, layout, load) {
25334
- const sideBySide = layout.width >= FLEET_GRID_WIDTH * 1.9;
25335
- if (!sideBySide)
24871
+ function setHoveredSelectableId(id) {
24872
+ if (hoveredId === id)
25336
24873
  return;
25337
- const letters = "RIG";
25338
- const min = Math.min(width, height);
25339
- const glyphH = height * 0.42;
25340
- const glyphW = width * 0.13;
25341
- const gap = glyphW * 0.3;
25342
- const totalW = letters.length * glyphW + (letters.length - 1) * gap;
25343
- const centerX = width * 0.71;
25344
- const startX = centerX - totalW / 2;
25345
- const top = (height - glyphH) / 2;
25346
- const stroke = Math.max(2, min * 0.013);
25347
- drawGlow2(data, width, height, centerX, top + glyphH / 2, Math.max(glyphW, glyphH) * 0.9, LIME, 0.022 + load * 0.02);
25348
- const strokeAlpha = 0.1 + load * 0.06;
25349
- letters.split("").forEach((letter, index) => {
25350
- const segments = RIG_GLYPH_STROKES[letter];
25351
- if (!segments)
25352
- return;
25353
- const ox = startX + index * (glyphW + gap);
25354
- for (const [x0, y0, x1, y1] of segments) {
25355
- drawLine2(data, width, height, ox + x0 * glyphW, top + y0 * glyphH, ox + x1 * glyphW, top + y1 * glyphH, LIME_DIM, strokeAlpha, stroke);
25356
- }
25357
- });
25358
- }
25359
- function drawBackground(data, width, height, _tick, scene, layout, load, activeRuns) {
25360
- fillRgba(data, BG, BG_WORD);
25361
- drawStaticAsciiFleetBackground(data, width, height, scene, layout, load, activeRuns);
25362
- drawBigRigWordmark(data, width, height, layout, load);
25363
- applyStaticAtmosphere(data, width, height, load);
25364
- }
25365
- function imageVisualFrameKey(input) {
25366
- const width = Math.max(1, Math.floor(input.pixelSize.width));
25367
- const height = Math.max(1, Math.floor(input.pixelSize.height));
25368
- const layer = "layer" in input ? input.layer ?? "full" : "full";
25369
- const load = loadBucket(input.load);
25370
- 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("|")}`;
25371
- }
25372
- function encodeKittyFrame(width, height, data, input) {
25373
- if (input.transport === "png") {
25374
- const png = encodePng(width, height, data);
25375
- return kittyImageSequence(png.toString("base64"), { columns: input.layout.width, rows: input.layout.height, imageId: input.imageId, zIndex: input.zIndex, format: 100 });
25376
- }
25377
- const compressed = deflateSync(data, { level: ZLIB_LEVEL });
25378
- return kittyImageSequence(compressed.toString("base64"), {
25379
- columns: input.layout.width,
25380
- rows: input.layout.height,
25381
- imageId: input.imageId,
25382
- zIndex: input.zIndex,
25383
- format: 32,
25384
- pixelWidth: width,
25385
- pixelHeight: height,
25386
- compressed: true
25387
- });
25388
- }
25389
- function cropRgba(data, sourceWidth, rect) {
25390
- const out = new Uint8ClampedArray(rect.width * rect.height * 4);
25391
- for (let y = 0;y < rect.height; y += 1) {
25392
- const sourceStart = ((rect.top + y) * sourceWidth + rect.left) * 4;
25393
- out.set(data.subarray(sourceStart, sourceStart + rect.width * 4), y * rect.width * 4);
25394
- }
25395
- return out;
25396
- }
25397
- function encodePanelPatch(data, sourceWidth, placement, imageId, zIndex) {
25398
- const patch = cropRgba(data, sourceWidth, placement);
25399
- const png = encodePng(placement.width, placement.height, patch);
25400
- const sequence = kittyImageSequence(png.toString("base64"), {
25401
- columns: Math.max(1, Math.round(placement.widthCells)),
25402
- rows: Math.max(1, Math.round(placement.heightCells)),
25403
- imageId,
25404
- zIndex,
25405
- format: 100
25406
- });
25407
- return `\x1B[${Math.max(1, Math.round(placement.topCells) + 1)};${Math.max(1, Math.round(placement.leftCells) + 1)}H${sequence}`;
25408
- }
25409
- function renderImageVisualFrame(input) {
25410
- const width = Math.max(1, Math.floor(input.pixelSize.width));
25411
- const height = Math.max(1, Math.floor(input.pixelSize.height));
25412
- const byteCount = width * height * 4;
25413
- if (byteCount > MAX_IMAGE_BYTES)
25414
- return null;
25415
- const layer = input.layer ?? "full";
25416
- const data = new Uint8ClampedArray(byteCount);
25417
- const load = loadBucket(input.load) / LOAD_BUCKETS;
25418
- const activeRuns = Math.max(0, Math.floor(input.activeRuns ?? 0));
25419
- drawBackground(data, width, height, 0, input.scene, input.layout, load, activeRuns);
25420
- if (layer === "split") {
25421
- const sequences = [encodeKittyFrame(width, height, data, input)];
25422
- let panelIndex = 0;
25423
- for (const panel of input.panels) {
25424
- if (panel.chrome !== "ad-terminal")
25425
- continue;
25426
- const placement = panelPixelPlacement(input.layout, panel, { width, height });
25427
- if (!placement)
25428
- continue;
25429
- const cellH = height / input.layout.height;
25430
- const headerPx = Math.max(cellH * 3, (panel.headerHeight ?? 3) * cellH);
25431
- compositePanel(data, width, height, placement, headerPx);
25432
- const imageId = input.imageIds?.[panelIndex] ?? (input.imageId + 1000 + panelIndex >>> 0 || panelIndex + 1);
25433
- sequences.push(encodePanelPatch(data, width, placement, imageId, -12));
25434
- panelIndex += 1;
25435
- }
25436
- return { key: imageVisualFrameKey(input), sequence: sequences.join("") };
25437
- }
25438
- if (layer === "panel-chrome")
25439
- return { key: imageVisualFrameKey(input), sequence: "" };
25440
- if (layer === "background") {
25441
- return { key: imageVisualFrameKey(input), sequence: encodeKittyFrame(width, height, data, input) };
25442
- }
25443
- if (layer === "panel-patches") {
25444
- const sequences = [];
25445
- let panelIndex = 0;
25446
- for (const panel of input.panels) {
25447
- if (panel.chrome !== "ad-terminal")
25448
- continue;
25449
- const placement = panelPixelPlacement(input.layout, panel, { width, height });
25450
- if (!placement)
25451
- continue;
25452
- const cellH = height / input.layout.height;
25453
- const headerPx = Math.max(cellH * 3, (panel.headerHeight ?? 3) * cellH);
25454
- compositePanel(data, width, height, placement, headerPx);
25455
- const imageId = input.imageIds?.[panelIndex] ?? (input.imageId + 1000 + panelIndex >>> 0 || panelIndex + 1);
25456
- sequences.push(encodePanelPatch(data, width, placement, imageId, input.zIndex));
25457
- panelIndex += 1;
25458
- }
25459
- return { key: imageVisualFrameKey(input), sequence: sequences.join("") };
25460
- }
25461
- for (const panel of input.panels) {
25462
- if (panel.chrome !== "ad-terminal")
25463
- continue;
25464
- const rect = panelRect(input.layout, panel, { width, height });
25465
- if (!rect)
25466
- continue;
25467
- const cellH = height / input.layout.height;
25468
- const headerPx = Math.max(cellH * 3, (panel.headerHeight ?? 3) * cellH);
25469
- compositePanel(data, width, height, rect, headerPx);
25470
- }
25471
- return { key: imageVisualFrameKey(input), sequence: encodeKittyFrame(width, height, data, input) };
25472
- }
25473
- function shouldRenderImagesInline() {
25474
- return process.env.RIG_OPENTUI_IMAGE_WORKER === "0" || import.meta.url.includes("$bunfs");
25475
- }
25476
- function createImageWorker() {
25477
- if (shouldRenderImagesInline())
25478
- return null;
25479
- const isTypeScriptRuntime = import.meta.url.endsWith(".ts");
25480
- const workerUrl = new URL(isTypeScriptRuntime ? "./image-visual-layer-worker.ts" : "./image-visual-layer-worker.js", import.meta.url);
25481
- try {
25482
- return new Worker(workerUrl);
25483
- } catch {
25484
- if (typeof process.versions.bun === "string") {
25485
- try {
25486
- return new Worker("./src/app-opentui/render/image-visual-layer-worker.ts");
25487
- } catch {}
25488
- }
25489
- return null;
25490
- }
25491
- }
25492
- function imageOutputBacklogBytes() {
25493
- const raw = Number.parseInt(process.env.RIG_OPENTUI_IMAGE_BACKLOG_BYTES ?? "", 10);
25494
- if (Number.isFinite(raw) && raw > 0)
25495
- return Math.max(64 * 1024, Math.min(2 * 1024 * 1024, raw));
25496
- return DEFAULT_IMAGE_OUTPUT_BACKLOG_BYTES;
24874
+ hoveredId = id;
24875
+ for (const listener of listeners)
24876
+ listener();
25497
24877
  }
25498
- function imageOutputBytesPerSecond() {
25499
- const raw = Number.parseInt(process.env.RIG_OPENTUI_IMAGE_BYTES_PER_SECOND ?? "", 10);
25500
- if (Number.isFinite(raw) && raw > 0)
25501
- return Math.max(128 * 1024, Math.min(2 * 1024 * 1024, raw));
25502
- return DEFAULT_IMAGE_OUTPUT_BYTES_PER_SECOND;
24878
+ function clearHoveredSelectableId(id) {
24879
+ if (id !== undefined && hoveredId === id)
24880
+ setHoveredSelectableId(undefined);
25503
24881
  }
25504
- function pendingLayerKey(key) {
25505
- return key.split(":", 1)[0] || "full";
25506
- }
25507
-
25508
- class ImageVisualLayer {
25509
- imageId = allocateImageId();
25510
- panelImageIds = Array.from({ length: 8 }, () => allocateImageId());
25511
- panelChromeImageIds = Array.from({ length: 8 }, () => allocateImageId());
25512
- stdout;
25513
- onFrameReady;
25514
- protocol;
25515
- transport = imageTransport();
25516
- maxOutputBacklogBytes = imageOutputBacklogBytes();
25517
- outputBytesPerSecond = imageOutputBytesPerSecond();
25518
- outputBudgetBytes = imageOutputBytesPerSecond();
25519
- lastBudgetAtMs = Date.now();
25520
- motionSlot = this.createSlot();
25521
- workersStarted = false;
25522
- pendingFrames = new Map;
25523
- recentWrittenKeys = [];
25524
- imagesVisible = false;
25525
- flushPaused = false;
25526
- requestId = 0;
25527
- lastBackgroundKey = "";
25528
- fatalWorkerError = null;
25529
- destroyed = false;
25530
- constructor(stdout = process.stdout, onFrameReady) {
25531
- this.stdout = stdout;
25532
- this.onFrameReady = onFrameReady;
25533
- this.protocol = getRigTerminalCapabilities().imageProtocol;
25534
- }
25535
- get enabled() {
25536
- return this.protocol === "kitty";
25537
- }
25538
- clear() {
25539
- if (this.enabled) {
25540
- if (this.imagesVisible)
25541
- this.stdout.write([this.imageId, ...this.panelImageIds, ...this.panelChromeImageIds].map((id) => deleteKittyImage(id)).join("") + "\x1B[?25h");
25542
- this.imagesVisible = false;
25543
- this.pendingFrames.clear();
25544
- this.recentWrittenKeys.length = 0;
25545
- this.resetSlot(this.motionSlot);
25546
- this.lastBackgroundKey = "";
25547
- this.outputBudgetBytes = this.outputBytesPerSecond;
25548
- this.lastBudgetAtMs = Date.now();
25549
- this.motionSlot.clearedThroughRequestId = this.requestId;
25550
- }
25551
- }
25552
- destroy() {
25553
- if (this.destroyed)
25554
- return;
25555
- this.clear();
25556
- this.destroyed = true;
25557
- const workers = [this.motionSlot.worker];
25558
- this.motionSlot.worker = null;
25559
- for (const worker of workers)
25560
- if (worker)
25561
- worker.terminate().catch(() => {
25562
- return;
25563
- });
25564
- }
25565
- setFlushPaused(paused) {
25566
- this.flushPaused = paused;
25567
- }
25568
- isReady(input) {
25569
- if (!this.enabled || this.destroyed)
25570
- return true;
25571
- this.throwIfFatal();
25572
- const splitKey = imageVisualFrameKey({ ...input, tick: 0, layer: "split" });
25573
- return this.recentWrittenKeys.includes(splitKey) || Array.from(this.pendingFrames.values()).some((frame) => frame.key === splitKey);
25574
- }
25575
- render(input) {
25576
- if (!this.enabled || this.destroyed)
25577
- return;
25578
- this.throwIfFatal();
25579
- if (this.transport !== "png")
25580
- this.failWorker("renderer", new Error("OpenTUI image visuals require PNG transport for static-background rendering."));
25581
- this.ensureWorkersStarted();
25582
- const width = Math.max(1, Math.floor(input.pixelSize.width));
25583
- const height = Math.max(1, Math.floor(input.pixelSize.height));
25584
- if (width * height * 4 > MAX_IMAGE_BYTES)
25585
- return;
25586
- if (this.isOutputBackedUp())
25587
- return;
25588
- this.refillOutputBudget();
25589
- const splitInput = { ...input, tick: 0, layer: "split" };
25590
- const splitKey = imageVisualFrameKey(splitInput);
25591
- if (input.force || splitKey !== this.lastBackgroundKey) {
25592
- this.scheduleRender(this.motionSlot, splitInput, this.imageId, -30, input.force, this.panelImageIds);
25593
- this.lastBackgroundKey = splitKey;
25594
- }
25595
- }
25596
- flush() {
25597
- this.throwIfFatal();
25598
- if (this.flushPaused)
25599
- return false;
25600
- if (this.pendingFrames.size === 0)
25601
- return false;
25602
- this.refillOutputBudget();
25603
- if (this.isOutputBackedUp()) {
25604
- const retainedSplit = this.pendingFrames.get("split");
25605
- this.pendingFrames.clear();
25606
- if (retainedSplit)
25607
- this.pendingFrames.set("split", retainedSplit);
25608
- return false;
25609
- }
25610
- const framePriority = (frame) => {
25611
- const layer = pendingLayerKey(frame.key);
25612
- if (layer === "split")
25613
- return 1;
25614
- return 4;
25615
- };
25616
- const frames = Array.from(this.pendingFrames.values()).sort((a, b) => framePriority(a) - framePriority(b));
25617
- const prefix = "\x1B[?25l\x1B7\x1B[s\x1B[H";
25618
- const suffix = "\x1B[u\x1B8";
25619
- const selected = [];
25620
- const retained = new Map;
25621
- let sequence = "";
25622
- let byteLength = Buffer.byteLength(prefix + suffix);
25623
- for (const frame of frames) {
25624
- const candidate = sequence + frame.sequence;
25625
- const candidateBytes = Buffer.byteLength(prefix + candidate + suffix);
25626
- const layer = pendingLayerKey(frame.key);
25627
- const allowOneStaticFrame = selected.length === 0 && layer === "split";
25628
- if (candidateBytes <= this.outputBudgetBytes || allowOneStaticFrame) {
25629
- selected.push(frame);
25630
- sequence = candidate;
25631
- byteLength = candidateBytes;
25632
- } else if (layer === "split") {
25633
- retained.set(layer, frame);
25634
- }
25635
- }
25636
- this.pendingFrames.clear();
25637
- for (const [layer, frame] of retained)
25638
- this.pendingFrames.set(layer, frame);
25639
- if (!sequence || selected.length === 0)
25640
- return false;
25641
- const payload = `${prefix}${sequence}${suffix}`;
25642
- for (const frame of selected)
25643
- this.markWritten(frame.key);
25644
- this.outputBudgetBytes -= byteLength;
25645
- this.stdout.cork?.();
25646
- const accepted = this.stdout.write(payload);
25647
- this.stdout.uncork?.();
25648
- this.imagesVisible = true;
25649
- return accepted;
25650
- }
25651
- createSlot() {
25652
- return { worker: null, busy: false, queuedRequest: null, inFlightKey: "", queuedKey: "", clearedThroughRequestId: 0, latestRequestId: 0 };
25653
- }
25654
- resetSlot(slot) {
25655
- slot.busy = false;
25656
- slot.queuedRequest = null;
25657
- slot.inFlightKey = "";
25658
- slot.queuedKey = "";
25659
- }
25660
- markWritten(key) {
25661
- this.recentWrittenKeys.push(key);
25662
- while (this.recentWrittenKeys.length > RECENT_WRITTEN_LIMIT)
25663
- this.recentWrittenKeys.shift();
25664
- }
25665
- refillOutputBudget() {
25666
- const now = Date.now();
25667
- const elapsedMs = Math.max(0, now - this.lastBudgetAtMs);
25668
- this.lastBudgetAtMs = now;
25669
- this.outputBudgetBytes = Math.min(this.outputBytesPerSecond, this.outputBudgetBytes + elapsedMs / 1000 * this.outputBytesPerSecond);
25670
- }
25671
- isOutputBackedUp() {
25672
- return Boolean(this.stdout.writableNeedDrain) || this.stdout.writableLength > this.maxOutputBacklogBytes;
25673
- }
25674
- stageFrame(frame) {
25675
- this.pendingFrames.set(pendingLayerKey(frame.key), frame);
25676
- }
25677
- isKnownKey(slot, key) {
25678
- return this.recentWrittenKeys.includes(key) || Array.from(this.pendingFrames.values()).some((frame) => frame.key === key) || key === slot.inFlightKey || key === slot.queuedKey;
25679
- }
25680
- ensureWorkersStarted() {
25681
- if (this.workersStarted)
25682
- return;
25683
- this.workersStarted = true;
25684
- this.startWorker(this.motionSlot, "static-visuals");
25685
- }
25686
- failWorker(name, error) {
25687
- const detail = error instanceof Error ? error.message : String(error);
25688
- this.fatalWorkerError ??= new Error(`OpenTUI image worker ${name} failed: ${detail}`);
25689
- throw this.fatalWorkerError;
25690
- }
25691
- throwIfFatal() {
25692
- if (this.fatalWorkerError)
25693
- throw this.fatalWorkerError;
25694
- }
25695
- scheduleRender(slot, input, imageId, zIndex, force, imageIds) {
25696
- this.throwIfFatal();
25697
- const key = imageVisualFrameKey(input);
25698
- if (!force && this.isKnownKey(slot, key))
25699
- return;
25700
- const request = {
25701
- ...input,
25702
- imageId,
25703
- ...imageIds ? { imageIds } : {},
25704
- zIndex,
25705
- transport: this.transport,
25706
- requestId: ++this.requestId,
25707
- key
25708
- };
25709
- slot.latestRequestId = request.requestId;
25710
- if (!slot.worker) {
25711
- this.renderInline(slot, request);
25712
- return;
25713
- }
25714
- this.enqueueWorkerRender(slot, request);
25715
- }
25716
- startWorker(slot, name) {
25717
- const worker = createImageWorker();
25718
- if (!worker)
25719
- return;
25720
- slot.worker = worker;
25721
- worker.on("message", (message2) => this.handleWorkerMessage(slot, message2));
25722
- worker.once("error", (error) => {
25723
- if (!this.destroyed)
25724
- this.fatalWorkerError ??= new Error(`OpenTUI image worker ${name} failed: ${error instanceof Error ? error.message : String(error)}`);
25725
- });
25726
- worker.once("exit", (code) => {
25727
- if (!this.destroyed)
25728
- this.fatalWorkerError ??= new Error(`OpenTUI image worker ${name} exited unexpectedly with code ${code}.`);
25729
- });
25730
- }
25731
- renderInline(slot, request) {
25732
- try {
25733
- const result = renderImageVisualFrame(request);
25734
- if (result?.sequence && !this.isKnownKey(slot, result.key) && (!this.isOutputBackedUp() || pendingLayerKey(result.key) === "split")) {
25735
- this.stageFrame({ key: result.key, sequence: result.sequence });
25736
- this.onFrameReady?.();
25737
- }
25738
- } catch (error) {
25739
- this.failWorker(String(request.layer ?? "visual"), error);
25740
- }
25741
- }
25742
- enqueueWorkerRender(slot, request) {
25743
- this.throwIfFatal();
25744
- if (slot.busy) {
25745
- slot.queuedRequest = request;
25746
- slot.queuedKey = request.key;
25747
- return;
25748
- }
25749
- this.dispatchWorkerRender(slot, request);
25750
- }
25751
- dispatchWorkerRender(slot, request) {
25752
- this.throwIfFatal();
25753
- if (!slot.worker)
25754
- this.failWorker(String(request.layer ?? "visual"), new Error("worker is unavailable"));
25755
- slot.busy = true;
25756
- slot.inFlightKey = request.key;
25757
- try {
25758
- slot.worker.postMessage(request);
25759
- } catch (error) {
25760
- this.failWorker(String(request.layer ?? "visual"), error);
25761
- }
25762
- }
25763
- handleWorkerMessage(slot, message2) {
25764
- if (this.destroyed || !message2 || typeof message2 !== "object")
25765
- return;
25766
- const response = message2;
25767
- slot.busy = false;
25768
- slot.inFlightKey = "";
25769
- const isStale = response.requestId <= slot.clearedThroughRequestId || response.requestId < slot.latestRequestId;
25770
- if (!isStale) {
25771
- if (response.error)
25772
- this.failWorker(pendingLayerKey(response.key), new Error(response.error));
25773
- if (response.sequence && !this.isKnownKey(slot, response.key) && (!this.isOutputBackedUp() || pendingLayerKey(response.key) === "split")) {
25774
- this.stageFrame({ key: response.key, sequence: response.sequence });
25775
- this.onFrameReady?.();
25776
- }
25777
- }
25778
- const next = slot.queuedRequest;
25779
- slot.queuedRequest = null;
25780
- slot.queuedKey = "";
25781
- if (next && !this.isKnownKey(slot, next.key))
25782
- this.dispatchWorkerRender(slot, next);
25783
- }
25784
- }
25785
- function pixelSizeForRenderer(renderer) {
25786
- const resolution = renderer.resolution;
25787
- const base = resolution?.width && resolution?.height ? resolution : { width: renderer.width * 12, height: renderer.height * 24 };
25788
- const scale = imageResolutionScale();
25789
- return {
25790
- width: Math.max(renderer.width, Math.floor(base.width * scale)),
25791
- height: Math.max(renderer.height, Math.floor(base.height * scale))
24882
+ function subscribeHover(listener) {
24883
+ listeners.add(listener);
24884
+ return () => {
24885
+ listeners.delete(listener);
25792
24886
  };
25793
24887
  }
25794
- function forceModernImageVisuals() {
25795
- return getRigTerminalCapabilities().supportsKittyImages;
25796
- }
25797
- var PANEL_MASK_RADIUS_MIN = 14, PANEL_MASK_RADIUS_MAX = 34, PANEL_MASK_RADIUS_FACTOR = 0.035, PANEL_BLUR_RADIUS_MIN = 8, PANEL_BLUR_RADIUS_MAX = 28, PANEL_BLUR_RADIUS_FACTOR = 0.018, BG, PANEL, PANEL_HEADER, PANEL_LINE2, LIME, LIME_DIM, CYAN, MAGENTA, INK_DIM, PNG_SIGNATURE, CRC_TABLE, MAX_IMAGE_BYTES, KITTY_CHUNK_SIZE = 4096, PANEL_BLUR_SCALE = 1, PANEL_MASK_CACHE_LIMIT = 16, ZLIB_LEVEL = 1, DEFAULT_IMAGE_RESOLUTION_SCALE = 1, LOAD_BUCKETS = 5, panelMaskCache, BG_WORD, ASCII_GLYPHS, RIG_GLYPH_STROKES, RECENT_WRITTEN_LIMIT = 24, DEFAULT_IMAGE_OUTPUT_BACKLOG_BYTES, DEFAULT_IMAGE_OUTPUT_BYTES_PER_SECOND;
25798
- var init_image_visual_layer = __esm(() => {
25799
- init_terminal_capabilities();
25800
- init_ascii_fleet();
25801
- init_constants();
25802
- BG = [7, 8, 9, 255];
25803
- PANEL = [16, 17, 21, 184];
25804
- PANEL_HEADER = [16, 17, 21, 190];
25805
- PANEL_LINE2 = [255, 255, 255, 24];
25806
- LIME = [204, 255, 77, 255];
25807
- LIME_DIM = [169, 214, 63, 255];
25808
- CYAN = [86, 216, 255, 255];
25809
- MAGENTA = [255, 121, 176, 255];
25810
- INK_DIM = [108, 110, 121, 255];
25811
- PNG_SIGNATURE = new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10]);
25812
- CRC_TABLE = makeCrcTable();
25813
- MAX_IMAGE_BYTES = 48 * 1024 * 1024;
25814
- panelMaskCache = new Map;
25815
- BG_WORD = rgbaWord(BG);
25816
- ASCII_GLYPHS = {
25817
- ".": ["00000", "00000", "00000", "00000", "00000", "00100", "00100"],
25818
- "'": ["00100", "00100", "00000", "00000", "00000", "00000", "00000"],
25819
- "-": ["00000", "00000", "00000", "11111", "00000", "00000", "00000"],
25820
- _: ["00000", "00000", "00000", "00000", "00000", "00000", "11111"],
25821
- "=": ["00000", "11111", "00000", "00000", "11111", "00000", "00000"],
25822
- "|": ["00100", "00100", "00100", "00100", "00100", "00100", "00100"],
25823
- "/": ["00001", "00010", "00010", "00100", "01000", "01000", "10000"],
25824
- "\\": ["10000", "01000", "01000", "00100", "00010", "00010", "00001"],
25825
- "(": ["00010", "00100", "01000", "01000", "01000", "00100", "00010"],
25826
- ")": ["01000", "00100", "00010", "00010", "00010", "00100", "01000"],
25827
- "!": ["00100", "00100", "00100", "00100", "00100", "00000", "00100"],
25828
- "%": ["11001", "11010", "00100", "01000", "10110", "00110", "00000"],
25829
- $: ["00100", "11110", "10100", "11110", "00101", "11110", "00100"],
25830
- "@": ["01110", "10001", "10111", "10101", "10111", "10000", "01111"],
25831
- o: ["00000", "01110", "10001", "10001", "10001", "01110", "00000"],
25832
- "*": ["00100", "10101", "01110", "11111", "01110", "10101", "00100"]
25833
- };
25834
- RIG_GLYPH_STROKES = {
25835
- 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]],
25836
- I: [[0.5, 0, 0.5, 1], [0.18, 0, 0.82, 0], [0.18, 1, 0.82, 1]],
25837
- 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]]
25838
- };
25839
- DEFAULT_IMAGE_OUTPUT_BACKLOG_BYTES = 256 * 1024;
25840
- DEFAULT_IMAGE_OUTPUT_BYTES_PER_SECOND = 850 * 1024;
24888
+ var hoveredId, listeners;
24889
+ var init_hover = __esm(() => {
24890
+ listeners = new Set;
25841
24891
  });
25842
24892
 
25843
24893
  // packages/cli/src/app-opentui/render/text.ts
25844
24894
  import { RGBA as RGBA4, TextAttributes as TextAttributes4, TextRenderable } from "@opentui/core";
24895
+ function lineIsClickable(line2) {
24896
+ return line2?.selectable !== undefined || line2?.selectableIndex !== undefined;
24897
+ }
24898
+ function lineSelectableId(line2) {
24899
+ return line2?.selectable?.id ?? line2?.selectableId;
24900
+ }
24901
+ function lineBackground(line2) {
24902
+ if (line2?.bg)
24903
+ return RGBA4.fromHex(line2.bg);
24904
+ const id = lineSelectableId(line2);
24905
+ if (id !== undefined && isHovered(id))
24906
+ return HOVER_BG;
24907
+ return TRANSPARENT2;
24908
+ }
25845
24909
  function selectableMouseHandler(currentLineRef, onLineMouseDown) {
25846
24910
  return (event) => {
25847
24911
  const currentLine = currentLineRef();
25848
- if (currentLine?.selectableIndex === undefined)
24912
+ if (!lineIsClickable(currentLine))
25849
24913
  return;
25850
24914
  onLineMouseDown?.(currentLine, event);
25851
24915
  };
25852
24916
  }
24917
+ function hoverHandlers(currentLineRef) {
24918
+ return {
24919
+ onMouseOver: () => {
24920
+ const id = lineSelectableId(currentLineRef());
24921
+ if (id !== undefined)
24922
+ setHoveredSelectableId(id);
24923
+ },
24924
+ onMouseOut: () => clearHoveredSelectableId(lineSelectableId(currentLineRef()))
24925
+ };
24926
+ }
25853
24927
  function createTextLine(renderer, id, top, onLineMouseDown) {
25854
24928
  let currentLine;
25855
24929
  const renderable = new TextRenderable(renderer, {
@@ -25861,7 +24935,8 @@ function createTextLine(renderer, id, top, onLineMouseDown) {
25861
24935
  width: 1,
25862
24936
  fg: RIG_UI.ink2,
25863
24937
  selectable: true,
25864
- onMouseDown: selectableMouseHandler(() => currentLine, onLineMouseDown)
24938
+ onMouseDown: selectableMouseHandler(() => currentLine, onLineMouseDown),
24939
+ ...hoverHandlers(() => currentLine)
25865
24940
  });
25866
24941
  renderable.setRigSceneLine = (line2) => {
25867
24942
  currentLine = line2;
@@ -25878,7 +24953,8 @@ function createFlowTextLine(renderer, id, onLineMouseDown) {
25878
24953
  flexShrink: 0,
25879
24954
  fg: RIG_UI.ink2,
25880
24955
  selectable: true,
25881
- onMouseDown: selectableMouseHandler(() => currentLine, onLineMouseDown)
24956
+ onMouseDown: selectableMouseHandler(() => currentLine, onLineMouseDown),
24957
+ ...hoverHandlers(() => currentLine)
25882
24958
  });
25883
24959
  renderable.setRigSceneLine = (line2) => {
25884
24960
  currentLine = line2;
@@ -25892,7 +24968,7 @@ function applyTextLine(renderable, line2, top, left, width) {
25892
24968
  renderable.width = width;
25893
24969
  renderable.content = line2?.styledText ?? line2?.text ?? "";
25894
24970
  renderable.fg = line2?.fg ?? RIG_UI.ink2;
25895
- renderable.bg = line2?.bg ?? TRANSPARENT2;
24971
+ renderable.bg = lineBackground(line2);
25896
24972
  renderable.attributes = line2?.bold ? TextAttributes4.BOLD : line2?.dim ? TextAttributes4.DIM : 0;
25897
24973
  }
25898
24974
  function applyFlowTextLine(renderable, line2, width) {
@@ -25902,7 +24978,7 @@ function applyFlowTextLine(renderable, line2, width) {
25902
24978
  renderable.width = Math.max(1, width);
25903
24979
  renderable.content = line2?.styledText ?? line2?.text ?? "";
25904
24980
  renderable.fg = line2?.fg ?? RIG_UI.ink2;
25905
- renderable.bg = line2?.bg ?? TRANSPARENT2;
24981
+ renderable.bg = lineBackground(line2);
25906
24982
  renderable.attributes = line2?.bold ? TextAttributes4.BOLD : line2?.dim ? TextAttributes4.DIM : 0;
25907
24983
  }
25908
24984
  function clearTextLines(renderables, from = 0) {
@@ -25916,10 +24992,12 @@ function clearTextLines(renderables, from = 0) {
25916
24992
  renderable.bg = TRANSPARENT2;
25917
24993
  }
25918
24994
  }
25919
- var TRANSPARENT2;
24995
+ var TRANSPARENT2, HOVER_BG;
25920
24996
  var init_text = __esm(() => {
25921
24997
  init_theme2();
24998
+ init_hover();
25922
24999
  TRANSPARENT2 = RGBA4.fromInts(0, 0, 0, 0);
25000
+ HOVER_BG = RGBA4.fromHex(RIG_UI.hover);
25923
25001
  });
25924
25002
 
25925
25003
  // packages/cli/src/app-opentui/render/panels.ts
@@ -27044,7 +26122,7 @@ function __renderSceneForTest(renderer, state, sceneRenderers) {
27044
26122
  renderer.root.add(navText);
27045
26123
  const registry = buildSceneRendererRegistry(sceneRenderers);
27046
26124
  const transition = { scene: state.scene, startedAtMs: 0, minUntilMs: 0 };
27047
- const selectables = renderNow(renderer, null, null, null, scrollPanels, null, null, navText, textLines, nativeHost, state, registry, transition);
26125
+ const selectables = renderNow(renderer, null, null, scrollPanels, null, null, navText, textLines, nativeHost, state, registry, transition);
27048
26126
  return { selectables, nativeHost };
27049
26127
  }
27050
26128
  async function launchRigOpenTuiApp(options) {
@@ -27056,7 +26134,6 @@ async function launchRigOpenTuiApp(options) {
27056
26134
  const events = createAppEventBus();
27057
26135
  let renderer = null;
27058
26136
  let graphics = null;
27059
- let imageVisuals = null;
27060
26137
  let preloaderBackdrop = null;
27061
26138
  const scrollPanels = [];
27062
26139
  let typeBar = null;
@@ -27084,7 +26161,7 @@ async function launchRigOpenTuiApp(options) {
27084
26161
  if (!renderer || destroyed)
27085
26162
  return;
27086
26163
  const currentState = store.getState();
27087
- renderedSelectableItems = renderNow(renderer, graphics, imageVisuals, preloaderBackdrop, scrollPanels, typeBar, brandText, navText, textLines, nativeHost, currentState, sceneRendererRegistry, preloaderTransition, scheduleAppRender);
26164
+ renderedSelectableItems = renderNow(renderer, graphics, preloaderBackdrop, scrollPanels, typeBar, brandText, navText, textLines, nativeHost, currentState, sceneRendererRegistry, preloaderTransition, scheduleAppRender);
27088
26165
  focusManager.restoreTypeBarFocus(typeBar, { hidden: currentState.scene === "handoff" });
27089
26166
  maybeRedirectFromUnresolvedScene(currentState);
27090
26167
  };
@@ -27244,18 +26321,8 @@ async function launchRigOpenTuiApp(options) {
27244
26321
  });
27245
26322
  renderer.setTerminalTitle?.("Operator cockpit");
27246
26323
  const layout = computeStageLayout(renderer.width, renderer.height);
27247
- imageVisuals = forceModernImageVisuals() ? new ImageVisualLayer(process.stdout, () => scheduleAppRender(0)) : null;
27248
- if (imageVisuals?.enabled) {
27249
- const flushImageVisuals = async () => {
27250
- imageVisuals?.flush();
27251
- };
27252
- renderer.setFrameCallback(flushImageVisuals);
27253
- resources.add(() => renderer?.removeFrameCallback(flushImageVisuals));
27254
- }
27255
- if (!imageVisuals?.enabled) {
27256
- graphics = createGraphicsLayer(renderer, layout);
27257
- renderer.root.add(graphics);
27258
- }
26324
+ graphics = createGraphicsLayer(renderer, layout);
26325
+ renderer.root.add(graphics);
27259
26326
  preloaderBackdrop = new BoxRenderable3(renderer, {
27260
26327
  id: "rig-preloader-backdrop",
27261
26328
  position: "absolute",
@@ -27278,8 +26345,7 @@ async function launchRigOpenTuiApp(options) {
27278
26345
  } else if (!selectById(store, id, renderedSelectableItems)) {
27279
26346
  return;
27280
26347
  }
27281
- if (line2.activateOnClick)
27282
- runAppAction("Activate selection", () => activateSelection(runtime, renderedSelectableItems));
26348
+ runAppAction("Activate selection", () => activateSelection(runtime, renderedSelectableItems));
27283
26349
  };
27284
26350
  for (let index = 0;index < MAX_SCENE_PANELS; index += 1) {
27285
26351
  const panel = createScrollPanelLayer(renderer, `rig-scroll-panel-${index}`, onLineClick);
@@ -27346,6 +26412,7 @@ async function launchRigOpenTuiApp(options) {
27346
26412
  };
27347
26413
  renderer.keyInput.on("keypress", onKeyPress);
27348
26414
  resources.add(() => renderer?.keyInput.off("keypress", onKeyPress));
26415
+ resources.add(subscribeHover(() => scheduleAppRender(0)));
27349
26416
  const onPaste = (event) => {
27350
26417
  const state = store.getState();
27351
26418
  if (state.scene === "handoff") {
@@ -27381,7 +26448,6 @@ async function launchRigOpenTuiApp(options) {
27381
26448
  stopActivePiHost("renderer destroyed");
27382
26449
  stopActiveCommandHost("renderer destroyed");
27383
26450
  destroyNativeHost(nativeHost);
27384
- imageVisuals?.destroy();
27385
26451
  if (renderTimer)
27386
26452
  clearTimeout(renderTimer);
27387
26453
  renderTimer = null;
@@ -27430,14 +26496,13 @@ async function launchRigOpenTuiApp(options) {
27430
26496
  clearTimeout(renderTimer);
27431
26497
  renderTimer = null;
27432
26498
  resources.disposeAll();
27433
- imageVisuals?.destroy();
27434
26499
  stopActivePiHost("app shutdown");
27435
26500
  stopActiveCommandHost("app shutdown");
27436
26501
  if (renderer && !renderer.isDestroyed)
27437
26502
  renderer.destroy();
27438
26503
  }
27439
26504
  }
27440
- function renderNow(renderer, graphics, imageVisuals, preloaderBackdrop, scrollPanels, typeBar, brandText, navText, textLines, nativeHost, state, sceneRendererRegistry, preloaderTransition, scheduleRenderAfter) {
26505
+ function renderNow(renderer, graphics, preloaderBackdrop, scrollPanels, typeBar, brandText, navText, textLines, nativeHost, state, sceneRendererRegistry, preloaderTransition, scheduleRenderAfter) {
27441
26506
  const layout = computeStageLayout(renderer.width, renderer.height);
27442
26507
  const hideNav = () => {
27443
26508
  if (navText)
@@ -27446,7 +26511,6 @@ function renderNow(renderer, graphics, imageVisuals, preloaderBackdrop, scrollPa
27446
26511
  if (layout.tooSmall) {
27447
26512
  if (graphics)
27448
26513
  clearGraphicsLayer(graphics);
27449
- imageVisuals?.clear();
27450
26514
  if (preloaderBackdrop)
27451
26515
  preloaderBackdrop.visible = false;
27452
26516
  if (brandText)
@@ -27480,8 +26544,7 @@ function renderNow(renderer, graphics, imageVisuals, preloaderBackdrop, scrollPa
27480
26544
  preloaderTransition.minUntilMs = now;
27481
26545
  const targetFrame = renderState.typeBar.mode === "command" ? commandPaletteFrame(renderState, layout) : rendererForScene(renderState.scene, sceneRendererRegistry)(renderState, layout);
27482
26546
  const targetContentLayout = targetFrame.fullScreen ? { ...layout, centerWidth: layout.width, centerLeft: 0, centerTop: 0, centerHeight: Math.min(layout.height, MAX_SCENE_LINES) } : layout;
27483
- const targetPanelsForImages = (targetFrame.panels ?? []).map((panel) => ({ ...panel }));
27484
- let targetVisualReady = true;
26547
+ const targetVisualReady = true;
27485
26548
  const fleetStats = fleetStatsFromState(state);
27486
26549
  const fleetLoad = fleetStats.load;
27487
26550
  const fleetActive = fleetStats.active;
@@ -27491,23 +26554,7 @@ function renderNow(renderer, graphics, imageVisuals, preloaderBackdrop, scrollPa
27491
26554
  if (renderState.scene === "command") {
27492
26555
  getActiveCommandHost()?.resize(Math.max(40, targetContentLayout.centerWidth - 12), Math.max(10, targetContentLayout.centerHeight - 6));
27493
26556
  }
27494
- if (imageVisuals && targetFrame.terminalActive) {
27495
- imageVisuals.clear();
27496
- } else if (imageVisuals && !targetFrame.fullScreen && !sceneJustChanged) {
27497
- const visualInput = {
27498
- layout: targetContentLayout,
27499
- pixelSize: pixelSizeForRenderer(renderer),
27500
- panels: targetPanelsForImages,
27501
- scene: state.scene,
27502
- tick: state.tick,
27503
- load: fleetLoad,
27504
- activeRuns: fleetActive
27505
- };
27506
- imageVisuals.render(visualInput);
27507
- targetVisualReady = imageVisuals.isReady(visualInput);
27508
- }
27509
26557
  const preloaderActive = sceneJustChanged && !cachedFastPath || isPreloaderActive(preloaderTransition, targetVisualReady, now);
27510
- imageVisuals?.setFlushPaused(preloaderActive);
27511
26558
  const frame = preloaderActive ? renderAsciiPreloaderFrame(renderState.scene, layout, state.tick) : targetFrame;
27512
26559
  if (graphics) {
27513
26560
  resizeGraphicsLayer(graphics, layout);
@@ -27606,8 +26653,8 @@ var init_runtime = __esm(() => {
27606
26653
  init_theme2();
27607
26654
  init_graphics();
27608
26655
  init_fleet_stats();
27609
- init_image_visual_layer();
27610
26656
  init_text();
26657
+ init_hover();
27611
26658
  init_panels();
27612
26659
  init_type_bar();
27613
26660
  init_scene();
@@ -27745,13 +26792,16 @@ function lineContent(line2) {
27745
26792
  }
27746
26793
  function LineView(props) {
27747
26794
  const { line: line2, onLineClick } = props;
26795
+ const id = lineSelectableId(line2);
27748
26796
  return /* @__PURE__ */ jsxDEV("text", {
27749
26797
  style: { position: "absolute", left: props.left, top: props.top, width: props.width, zIndex: 5 },
27750
26798
  fg: line2.fg ?? RIG_UI.ink2,
27751
- bg: line2.bg ?? TRANSPARENT2,
26799
+ bg: lineBackground(line2),
27752
26800
  attributes: lineAttributes(line2),
27753
26801
  content: lineContent(line2),
27754
- onMouseDown: onLineClick ? () => onLineClick(line2) : undefined
26802
+ onMouseDown: onLineClick ? () => onLineClick(line2) : undefined,
26803
+ onMouseOver: id !== undefined ? () => setHoveredSelectableId(id) : undefined,
26804
+ onMouseOut: id !== undefined ? () => clearHoveredSelectableId(id) : undefined
27755
26805
  }, undefined, false, undefined, this);
27756
26806
  }
27757
26807
  function PanelHost(props) {
@@ -27838,6 +26888,7 @@ var init_SceneFrameView = __esm(() => {
27838
26888
  init_theme2();
27839
26889
  init_scene();
27840
26890
  init_text();
26891
+ init_hover();
27841
26892
  init_panels();
27842
26893
  init_native_host();
27843
26894
  init_scroll();
@@ -27991,7 +27042,7 @@ var init_nav = __esm(() => {
27991
27042
  });
27992
27043
 
27993
27044
  // packages/cli/src/app-opentui/react/App.tsx
27994
- import { useMemo, useRef as useRef4 } from "react";
27045
+ import { useMemo, useRef as useRef4, useSyncExternalStore as useSyncExternalStore2 } from "react";
27995
27046
  import { useKeyboard, useRenderer as useRenderer4, useTerminalDimensions as useTerminalDimensions2 } from "@opentui/react";
27996
27047
  import { jsxDEV as jsxDEV3 } from "@opentui/react/jsx-dev-runtime";
27997
27048
  function defaultFrame(state) {
@@ -28009,6 +27060,7 @@ function App(props) {
28009
27060
  const runAppAction = useRunAppAction();
28010
27061
  const store = useStore();
28011
27062
  const renderer = useRenderer4();
27063
+ useSyncExternalStore2(subscribeHover, getHoveredSelectableId, getHoveredSelectableId);
28012
27064
  const dims = useTerminalDimensions2();
28013
27065
  const layout = computeStageLayout(dims.width, dims.height);
28014
27066
  const registry = useMemo(() => ({ ...BUILTIN, ...props.sceneRenderers ?? {} }), [props.sceneRenderers]);
@@ -28043,8 +27095,7 @@ function App(props) {
28043
27095
  } else {
28044
27096
  return;
28045
27097
  }
28046
- if (line2.activateOnClick)
28047
- runAppAction("Activate selection", () => activateSelection(runtime, items));
27098
+ runAppAction("Activate selection", () => activateSelection(runtime, items));
28048
27099
  };
28049
27100
  return /* @__PURE__ */ jsxDEV3("box", {
28050
27101
  style: { position: "absolute", left: 0, top: 0, width: "100%", height: "100%" },
@@ -28094,6 +27145,7 @@ var init_App = __esm(() => {
28094
27145
  init_SceneFrameView();
28095
27146
  init_ChromeHost();
28096
27147
  init_scroll();
27148
+ init_hover();
28097
27149
  init_context();
28098
27150
  init_nav();
28099
27151
  BUILTIN = {