@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
@@ -15585,6 +15585,7 @@ var init_theme2 = __esm(() => {
15585
15585
  bg2: "#0b0c0e",
15586
15586
  panel: "#101115",
15587
15587
  panel2: "#14161b",
15588
+ hover: "#181b24",
15588
15589
  glass: "#1e2230",
15589
15590
  border: "#2a2e3a",
15590
15591
  ink: "#f4f5f8",
@@ -17330,22 +17331,9 @@ function panelContentRect(layout, panel) {
17330
17331
  const contentWidth = Math.max(1, width - paddingX * 2 - 1);
17331
17332
  return { left, top, width, height, paddingX, paddingY, contentWidth };
17332
17333
  }
17333
- function panelPixelPlacement(layout, panel, size, minPixels = 8) {
17334
- const cellW = size.width / layout.width;
17335
- const cellH = size.height / layout.height;
17336
- const leftCells = layout.centerLeft + (panel.left ?? 0);
17337
- const topCells = layout.centerTop + panel.top;
17338
- const widthCells = panel.width ?? layout.centerWidth;
17339
- const heightCells = panel.height;
17340
- const left = Math.round(leftCells * cellW);
17341
- const top = Math.round(topCells * cellH);
17342
- const width = Math.round(widthCells * cellW);
17343
- const height = Math.round(heightCells * cellH);
17344
- return width > minPixels && height > minPixels ? { left, top, width, height, leftCells, topCells, widthCells, heightCells } : null;
17345
- }
17346
17334
 
17347
17335
  // packages/cli/src/app-opentui/render/constants.ts
17348
- 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;
17336
+ 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;
17349
17337
  var init_constants = __esm(() => {
17350
17338
  RAIL_YS_LOOP = [0.18, 0.5, 0.82];
17351
17339
  RAIL_YS_CARRIER = [0.24, 0.54, 0.82];
@@ -17764,27 +17752,20 @@ function writeCell(layer, x, y, char, fg2, bg) {
17764
17752
  function drawFullCell(layer, x, y, bg) {
17765
17753
  writeCell(layer, x, y, " ", TRANSPARENT, bg);
17766
17754
  }
17767
- function drawCornerCut(layer, x, y, char, fg2) {
17768
- writeCell(layer, x, y, char, fg2, BACKDROP);
17769
- }
17770
- function drawRoundedFill(layer, left, top, right, bottom, dividerY) {
17755
+ function drawPanelFill(layer, left, top, right, bottom, dividerY) {
17771
17756
  const bgForY = (y) => y < dividerY ? PANEL_HEADER_BG : PANEL_BG;
17772
17757
  for (let y = top;y <= bottom; y += 1) {
17773
17758
  const bg = bgForY(y);
17774
17759
  for (let x = left;x <= right; x += 1)
17775
17760
  drawFullCell(layer, x, y, bg);
17776
17761
  }
17777
- drawCornerCut(layer, left, top, "\u2597", PANEL_HEADER_BG);
17778
- drawCornerCut(layer, right, top, "\u2596", PANEL_HEADER_BG);
17779
- drawCornerCut(layer, left, bottom, "\u259D", PANEL_BG);
17780
- drawCornerCut(layer, right, bottom, "\u2598", PANEL_BG);
17781
17762
  }
17782
17763
  function drawAdTerminalPanel(layer, layout, panel) {
17783
17764
  const rect = panelCellRect(layout, panel);
17784
17765
  if (!rect)
17785
17766
  return;
17786
17767
  const { left, top, width, right, bottom, headerHeight, dividerY } = rect;
17787
- drawRoundedFill(layer, left, top, right, bottom, dividerY);
17768
+ drawPanelFill(layer, left, top, right, bottom, dividerY);
17788
17769
  if (dividerY > top && dividerY < bottom) {
17789
17770
  for (let x = left + 2;x < right - 1; x += 1) {
17790
17771
  writeCell(layer, x, dividerY, "\u2500", PANEL_LINE_DIM, PANEL_BG);
@@ -17892,973 +17873,69 @@ var init_graphics = __esm(() => {
17892
17873
  ];
17893
17874
  });
17894
17875
 
17895
- // packages/cli/src/app-opentui/terminal-capabilities.ts
17896
- import { getCapabilities } from "@earendil-works/pi-tui";
17897
- function getRigTerminalCapabilities() {
17898
- const imageProtocol = getCapabilities().images;
17899
- const supportsKittyImages = imageProtocol === "kitty";
17900
- return {
17901
- imageProtocol,
17902
- supportsKittyImages,
17903
- visualMode: supportsKittyImages ? "kitty-images" : "framebuffer-fallback"
17904
- };
17876
+ // packages/cli/src/app-opentui/render/hover.ts
17877
+ function getHoveredSelectableId() {
17878
+ return hoveredId;
17905
17879
  }
17906
- var init_terminal_capabilities = () => {};
17907
-
17908
- // packages/cli/src/app-opentui/render/image-visual-layer.ts
17909
- import { Worker } from "worker_threads";
17910
- import { deflateSync } from "zlib";
17911
- import { allocateImageId, deleteKittyImage } from "@earendil-works/pi-tui";
17912
- function loadBucket(load) {
17913
- if (!Number.isFinite(load ?? NaN))
17914
- return 0;
17915
- return Math.max(0, Math.min(LOAD_BUCKETS, Math.round((load ?? 0) * LOAD_BUCKETS)));
17916
- }
17917
- function imageTransport() {
17918
- const forced = process.env.RIG_OPENTUI_IMAGE_TRANSPORT;
17919
- if (forced === "rgba-zlib")
17920
- return "rgba-zlib";
17921
- return "png";
17922
- }
17923
- function imageResolutionScale() {
17924
- const raw = Number.parseFloat(process.env.RIG_OPENTUI_IMAGE_SCALE ?? "");
17925
- if (Number.isFinite(raw) && raw > 0)
17926
- return Math.max(0.25, Math.min(1, raw));
17927
- return DEFAULT_IMAGE_RESOLUTION_SCALE;
17928
- }
17929
- function rgbaWord(color) {
17930
- return (color[3] << 24 | color[2] << 16 | color[1] << 8 | color[0]) >>> 0;
17931
- }
17932
- function fillRgba(data, color, word = rgbaWord(color)) {
17933
- if (data.byteOffset % 4 === 0 && data.byteLength % 4 === 0) {
17934
- new Uint32Array(data.buffer, data.byteOffset, data.byteLength / 4).fill(word);
17935
- return;
17936
- }
17937
- for (let i = 0;i < data.length; i += 4) {
17938
- data[i] = color[0];
17939
- data[i + 1] = color[1];
17940
- data[i + 2] = color[2];
17941
- data[i + 3] = color[3];
17942
- }
17943
- }
17944
- function sceneStaticTick(scene) {
17945
- let hash = 0;
17946
- for (let index = 0;index < scene.length; index += 1)
17947
- hash = hash * 33 + scene.charCodeAt(index) >>> 0;
17948
- return 11 + hash % 97;
17880
+ function isHovered(id) {
17881
+ return id !== undefined && id === hoveredId;
17949
17882
  }
17950
- function charColor(char, row, totalRows) {
17951
- if (char === "@" || char === "$" || char === "o")
17952
- return LIME;
17953
- if (char === "%" || char === "!" || char === "/" || char === "\\" || char === "|")
17954
- return CYAN;
17955
- if (char === "." || char === "'" || char === "_" || row < 3 || row > totalRows - 4)
17956
- return INK_DIM;
17957
- if (char === "-" || char === "=" || char === "(" || char === ")")
17958
- return LIME_DIM;
17959
- return MAGENTA;
17960
- }
17961
- function fillRectAlpha(data, width, height, left, top, rectWidth, rectHeight, color, alpha) {
17962
- const x0 = Math.max(0, Math.floor(left));
17963
- const y0 = Math.max(0, Math.floor(top));
17964
- const x1 = Math.min(width, Math.ceil(left + rectWidth));
17965
- const y1 = Math.min(height, Math.ceil(top + rectHeight));
17966
- for (let y = y0;y < y1; y += 1) {
17967
- for (let x = x0;x < x1; x += 1)
17968
- blendOver(data, (y * width + x) * 4, color, alpha);
17969
- }
17970
- }
17971
- function drawAsciiGlyph(data, width, height, x, y, cellWidth, cellHeight, char, color, alpha) {
17972
- const pattern = ASCII_GLYPHS[char] ?? ASCII_GLYPHS["*"];
17973
- const glyphWidth = Math.max(1, cellWidth * 0.56);
17974
- const glyphHeight = Math.max(1, cellHeight * 0.7);
17975
- const originX = x + (cellWidth - glyphWidth) * 0.5;
17976
- const originY = y + (cellHeight - glyphHeight) * 0.5;
17977
- const pixelWidth = Math.max(1, glyphWidth / pattern[0].length);
17978
- const pixelHeight = Math.max(1, glyphHeight / pattern.length);
17979
- for (let row = 0;row < pattern.length; row += 1) {
17980
- const bits = pattern[row];
17981
- for (let col = 0;col < bits.length; col += 1) {
17982
- if (bits[col] !== "1")
17983
- continue;
17984
- fillRectAlpha(data, width, height, originX + col * pixelWidth, originY + row * pixelHeight, pixelWidth * 0.72, pixelHeight * 0.72, color, alpha);
17985
- }
17986
- }
17987
- }
17988
- function drawStaticAsciiFleetBackground(data, width, height, scene, layout, load, activeRuns) {
17989
- const rows = fleetRows(sceneStaticTick(scene), { animate: true });
17990
- const cellWidth = width / Math.max(1, layout.width);
17991
- const cellHeight = height / Math.max(1, layout.height);
17992
- const sideBySide = layout.width >= FLEET_GRID_WIDTH * 1.9;
17993
- const leftCells = sideBySide ? Math.max(1, Math.floor(layout.width * 0.3 - FLEET_GRID_WIDTH / 2)) : Math.floor((layout.width - FLEET_GRID_WIDTH) / 2);
17994
- const topCells = Math.floor((layout.height - FLEET_GRID_HEIGHT) / 2);
17995
- const fleetLeft = leftCells * cellWidth;
17996
- const fleetTop = topCells * cellHeight;
17997
- drawGlow2(data, width, height, width * 0.5, height * 0.5, Math.min(width, height) * 0.4, LIME, 0.022 + load * 0.03);
17998
- drawGlow2(data, width, height, width * 0.18, height * 0.22, Math.min(width, height) * 0.28, CYAN, 0.018 + load * 0.012);
17999
- drawGlow2(data, width, height, width * 0.82, height * 0.78, Math.min(width, height) * 0.22, MAGENTA, 0.012 + load * 0.008);
18000
- let droneTotal = 0;
18001
- for (const line2 of rows)
18002
- for (const char of line2)
18003
- if (char === "@" || char === "$" || char === "%")
18004
- droneTotal += 1;
18005
- const litTarget = Math.max(0, Math.min(droneTotal, activeRuns));
18006
- let droneIndex = 0;
18007
- for (let row = 0;row < rows.length; row += 1) {
18008
- const line2 = rows[row];
18009
- for (let col = 0;col < line2.length; col += 1) {
18010
- const char = line2[col];
18011
- if (char === " ")
18012
- continue;
18013
- const x = fleetLeft + col * cellWidth;
18014
- const y = fleetTop + row * cellHeight;
18015
- const color = charColor(char, row, rows.length);
18016
- const isDrone = char === "@" || char === "$" || char === "%";
18017
- const lit = isDrone && droneIndex < litTarget;
18018
- drawAsciiGlyph(data, width, height, x + cellWidth * 0.07, y + cellHeight * 0.05, cellWidth, cellHeight, char, color, lit ? 0.72 : 0.46);
18019
- if (isDrone) {
18020
- droneIndex += 1;
18021
- drawGlow2(data, width, height, x + cellWidth * 0.5, y + cellHeight * 0.5, Math.max(cellWidth, cellHeight) * 0.95, color, lit ? 0.044 : 0.014);
18022
- }
18023
- }
18024
- }
18025
- }
18026
- function applyStaticAtmosphere(data, width, height, load) {
18027
- const cx = width * 0.5;
18028
- const cy = height * 0.42;
18029
- const radius = Math.max(width, height) * 0.78;
18030
- const minVignette = 0.1 - load * 0.02;
18031
- for (let y = 0;y < height; y += 1) {
18032
- for (let x = 0;x < width; x += 1) {
18033
- const distance = Math.hypot(x - cx, y - cy) / radius;
18034
- const alpha = Math.max(minVignette, Math.min(0.82, 0.12 + Math.pow(distance, 1.65) * 0.62));
18035
- blendOver(data, (y * width + x) * 4, BG, alpha);
18036
- }
18037
- }
18038
- drawGlow2(data, width, height, width * 0.82, height * 0.04, Math.min(width, height) * 0.2, LIME, 0.024 + load * 0.012);
18039
- drawGlow2(data, width, height, width * 0.12, height * 0.18, Math.min(width, height) * 0.16, CYAN, 0.018 + load * 0.008);
18040
- }
18041
- function makeCrcTable() {
18042
- const table = new Uint32Array(256);
18043
- for (let n = 0;n < 256; n += 1) {
18044
- let c = n;
18045
- for (let k = 0;k < 8; k += 1)
18046
- c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
18047
- table[n] = c >>> 0;
18048
- }
18049
- return table;
18050
- }
18051
- function crc32(bytes) {
18052
- let c = 4294967295;
18053
- for (let i = 0;i < bytes.length; i += 1)
18054
- c = CRC_TABLE[(c ^ bytes[i]) & 255] ^ c >>> 8;
18055
- return (c ^ 4294967295) >>> 0;
18056
- }
18057
- function chunk2(type, data) {
18058
- const out = new Uint8Array(12 + data.length);
18059
- const view = new DataView(out.buffer);
18060
- view.setUint32(0, data.length);
18061
- out[4] = type.charCodeAt(0);
18062
- out[5] = type.charCodeAt(1);
18063
- out[6] = type.charCodeAt(2);
18064
- out[7] = type.charCodeAt(3);
18065
- out.set(data, 8);
18066
- view.setUint32(8 + data.length, crc32(out.subarray(4, 8 + data.length)));
18067
- return out;
18068
- }
18069
- function encodePng(width, height, rgba) {
18070
- const ihdr = new Uint8Array(13);
18071
- const ihdrView = new DataView(ihdr.buffer);
18072
- ihdrView.setUint32(0, width);
18073
- ihdrView.setUint32(4, height);
18074
- ihdr[8] = 8;
18075
- ihdr[9] = 6;
18076
- ihdr[10] = 0;
18077
- ihdr[11] = 0;
18078
- ihdr[12] = 0;
18079
- const stride = width * 4;
18080
- const raw = new Uint8Array((stride + 1) * height);
18081
- for (let y = 0;y < height; y += 1) {
18082
- const rawOffset = y * (stride + 1);
18083
- raw[rawOffset] = 0;
18084
- raw.set(rgba.subarray(y * stride, y * stride + stride), rawOffset + 1);
18085
- }
18086
- const compressed = deflateSync(raw, { level: ZLIB_LEVEL });
18087
- return Buffer.concat([
18088
- Buffer.from(PNG_SIGNATURE),
18089
- Buffer.from(chunk2("IHDR", ihdr)),
18090
- Buffer.from(chunk2("IDAT", compressed)),
18091
- Buffer.from(chunk2("IEND", new Uint8Array(0)))
18092
- ]);
18093
- }
18094
- function kittyImageSequence(base64, options) {
18095
- const params = [`a=T`, `f=${options.format}`, `q=2`, `C=1`, `c=${options.columns}`, `r=${options.rows}`, `i=${options.imageId}`, `z=${options.zIndex}`];
18096
- if (options.format === 32) {
18097
- params.push(`s=${options.pixelWidth ?? 1}`, `v=${options.pixelHeight ?? 1}`);
18098
- if (options.compressed)
18099
- params.push("o=z");
18100
- }
18101
- if (base64.length <= KITTY_CHUNK_SIZE)
18102
- return `\x1B_G${params.join(",")};${base64}\x1B\\`;
18103
- const chunks = [];
18104
- for (let offset = 0;offset < base64.length; offset += KITTY_CHUNK_SIZE) {
18105
- const part = base64.slice(offset, offset + KITTY_CHUNK_SIZE);
18106
- const last = offset + KITTY_CHUNK_SIZE >= base64.length;
18107
- chunks.push(`\x1B_G${offset === 0 ? `${params.join(",")},` : ""}m=${last ? 0 : 1};${part}\x1B\\`);
18108
- }
18109
- return chunks.join("");
18110
- }
18111
- function clampByte(value) {
18112
- return Math.max(0, Math.min(255, Math.round(value)));
18113
- }
18114
- function blendOver(data, index, color, alphaScale = 1) {
18115
- const alpha = color[3] / 255 * alphaScale;
18116
- const inv = 1 - alpha;
18117
- data[index] = clampByte(color[0] * alpha + data[index] * inv);
18118
- data[index + 1] = clampByte(color[1] * alpha + data[index + 1] * inv);
18119
- data[index + 2] = clampByte(color[2] * alpha + data[index + 2] * inv);
18120
- data[index + 3] = 255;
18121
- }
18122
- function drawDisc(data, width, height, cx, cy, radius, color, alpha = 1) {
18123
- const minX = Math.max(0, Math.floor(cx - radius));
18124
- const maxX = Math.min(width - 1, Math.ceil(cx + radius));
18125
- const minY = Math.max(0, Math.floor(cy - radius));
18126
- const maxY = Math.min(height - 1, Math.ceil(cy + radius));
18127
- for (let y = minY;y <= maxY; y += 1) {
18128
- for (let x = minX;x <= maxX; x += 1) {
18129
- const dx = x + 0.5 - cx;
18130
- const dy = y + 0.5 - cy;
18131
- const d = Math.sqrt(dx * dx + dy * dy);
18132
- if (d > radius)
18133
- continue;
18134
- const edge = Math.max(0, Math.min(1, radius - d));
18135
- blendOver(data, (y * width + x) * 4, color, alpha * Math.min(1, 0.35 + edge));
18136
- }
18137
- }
18138
- }
18139
- function drawGlow2(data, width, height, cx, cy, radius, color, alpha = 0.18) {
18140
- const minX = Math.max(0, Math.floor(cx - radius));
18141
- const maxX = Math.min(width - 1, Math.ceil(cx + radius));
18142
- const minY = Math.max(0, Math.floor(cy - radius));
18143
- const maxY = Math.min(height - 1, Math.ceil(cy + radius));
18144
- for (let y = minY;y <= maxY; y += 1) {
18145
- for (let x = minX;x <= maxX; x += 1) {
18146
- const dx = x + 0.5 - cx;
18147
- const dy = y + 0.5 - cy;
18148
- const d = Math.sqrt(dx * dx + dy * dy);
18149
- if (d > radius)
18150
- continue;
18151
- const falloff = Math.pow(1 - d / Math.max(1, radius), GLOW_FALLOFF_EXP_IMAGE);
18152
- blendOver(data, (y * width + x) * 4, color, alpha * falloff);
18153
- }
18154
- }
18155
- }
18156
- function drawLine2(data, width, height, x0, y0, x1, y1, color, alpha = 0.5, thickness = 1.4) {
18157
- const dx = x1 - x0;
18158
- const dy = y1 - y0;
18159
- const steps = Math.max(1, Math.ceil(Math.hypot(dx, dy) * 1.2));
18160
- for (let i = 0;i <= steps; i += 1) {
18161
- const t2 = i / steps;
18162
- drawDisc(data, width, height, x0 + dx * t2, y0 + dy * t2, thickness, color, alpha);
18163
- }
18164
- }
18165
- function roundRectAlpha(px, py, x, y, w, h, r) {
18166
- const rx = Math.max(x + r, Math.min(px, x + w - r));
18167
- const ry = Math.max(y + r, Math.min(py, y + h - r));
18168
- const dx = px - rx;
18169
- const dy = py - ry;
18170
- const dist = Math.sqrt(dx * dx + dy * dy);
18171
- return Math.max(0, Math.min(1, r + 0.75 - dist));
18172
- }
18173
- function panelRect(layout, panel, size) {
18174
- return panelPixelPlacement(layout, panel, size);
18175
- }
18176
- function downsampleRegion(data, canvasW, canvasH, rect, scale) {
18177
- const w = Math.max(1, Math.ceil(rect.width / scale));
18178
- const h = Math.max(1, Math.ceil(rect.height / scale));
18179
- const out = new Uint8ClampedArray(w * h * 3);
18180
- for (let y = 0;y < h; y += 1) {
18181
- for (let x = 0;x < w; x += 1) {
18182
- let r = 0, g = 0, b = 0, count = 0;
18183
- const sx0 = rect.left + x * scale;
18184
- const sy0 = rect.top + y * scale;
18185
- for (let yy = 0;yy < scale; yy += 1) {
18186
- const sy = sy0 + yy;
18187
- if (sy < 0 || sy >= canvasH)
18188
- continue;
18189
- for (let xx = 0;xx < scale; xx += 1) {
18190
- const sx = sx0 + xx;
18191
- if (sx < 0 || sx >= canvasW)
18192
- continue;
18193
- const i = (sy * canvasW + sx) * 4;
18194
- r += data[i];
18195
- g += data[i + 1];
18196
- b += data[i + 2];
18197
- count += 1;
18198
- }
18199
- }
18200
- const oi = (y * w + x) * 3;
18201
- out[oi] = count ? r / count : BG[0];
18202
- out[oi + 1] = count ? g / count : BG[1];
18203
- out[oi + 2] = count ? b / count : BG[2];
18204
- }
18205
- }
18206
- return { pixels: out, width: w, height: h };
18207
- }
18208
- function blurRgb(buffer, width, height, radius) {
18209
- const tmp = new Uint8ClampedArray(buffer.length);
18210
- const out = new Uint8ClampedArray(buffer.length);
18211
- const window = radius * 2 + 1;
18212
- for (let y = 0;y < height; y += 1) {
18213
- let r = 0, g = 0, b = 0;
18214
- for (let x = -radius;x <= radius; x += 1) {
18215
- const sx = Math.max(0, Math.min(width - 1, x));
18216
- const i = (y * width + sx) * 3;
18217
- r += buffer[i];
18218
- g += buffer[i + 1];
18219
- b += buffer[i + 2];
18220
- }
18221
- for (let x = 0;x < width; x += 1) {
18222
- const oi = (y * width + x) * 3;
18223
- tmp[oi] = r / window;
18224
- tmp[oi + 1] = g / window;
18225
- tmp[oi + 2] = b / window;
18226
- const removeX = Math.max(0, x - radius);
18227
- const addX = Math.min(width - 1, x + radius + 1);
18228
- const ri = (y * width + removeX) * 3;
18229
- const ai = (y * width + addX) * 3;
18230
- r += tmp.length ? buffer[ai] - buffer[ri] : 0;
18231
- g += tmp.length ? buffer[ai + 1] - buffer[ri + 1] : 0;
18232
- b += tmp.length ? buffer[ai + 2] - buffer[ri + 2] : 0;
18233
- }
18234
- }
18235
- for (let x = 0;x < width; x += 1) {
18236
- let r = 0, g = 0, b = 0;
18237
- for (let y = -radius;y <= radius; y += 1) {
18238
- const sy = Math.max(0, Math.min(height - 1, y));
18239
- const i = (sy * width + x) * 3;
18240
- r += tmp[i];
18241
- g += tmp[i + 1];
18242
- b += tmp[i + 2];
18243
- }
18244
- for (let y = 0;y < height; y += 1) {
18245
- const oi = (y * width + x) * 3;
18246
- out[oi] = r / window;
18247
- out[oi + 1] = g / window;
18248
- out[oi + 2] = b / window;
18249
- const removeY = Math.max(0, y - radius);
18250
- const addY = Math.min(height - 1, y + radius + 1);
18251
- const ri = (removeY * width + x) * 3;
18252
- const ai = (addY * width + x) * 3;
18253
- r += tmp[ai] - tmp[ri];
18254
- g += tmp[ai + 1] - tmp[ri + 1];
18255
- b += tmp[ai + 2] - tmp[ri + 2];
18256
- }
18257
- }
18258
- return out;
18259
- }
18260
- function panelMaskKey(width, height, rect, headerPx, blurScale) {
18261
- return `${width}x${height}:${rect.left},${rect.top},${rect.width},${rect.height}:${Math.round(headerPx * 100) / 100}:${blurScale}`;
18262
- }
18263
- function getPanelMask(width, height, rect, headerPx, blurScale) {
18264
- const key = panelMaskKey(width, height, rect, headerPx, blurScale);
18265
- const cached = panelMaskCache.get(key);
18266
- if (cached)
18267
- return cached;
18268
- 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)));
18269
- const sampledWidth = Math.max(1, Math.ceil(rect.width / blurScale));
18270
- const sampledHeight = Math.max(1, Math.ceil(rect.height / blurScale));
18271
- const x0 = Math.max(0, rect.left);
18272
- const y0 = Math.max(0, rect.top);
18273
- const x1 = Math.min(width - 1, rect.left + rect.width - 1);
18274
- const y1 = Math.min(height - 1, rect.top + rect.height - 1);
18275
- const indices = [];
18276
- const sampleIndices = [];
18277
- const panelAlpha = [];
18278
- const edgeAlpha = [];
18279
- const headerFlags = [];
18280
- const dividerIndices = [];
18281
- for (let y = y0;y <= y1; y += 1) {
18282
- for (let x = x0;x <= x1; x += 1) {
18283
- const a = roundRectAlpha(x + 0.5, y + 0.5, rect.left, rect.top, rect.width, rect.height, radius);
18284
- if (a <= 0)
18285
- continue;
18286
- 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));
18287
- const sx = Math.max(0, Math.min(sampledWidth - 1, Math.floor((x - rect.left) / blurScale)));
18288
- const sy = Math.max(0, Math.min(sampledHeight - 1, Math.floor((y - rect.top) / blurScale)));
18289
- indices.push((y * width + x) * 4);
18290
- sampleIndices.push((sy * sampledWidth + sx) * 3);
18291
- panelAlpha.push(a);
18292
- edgeAlpha.push(edge < 0.96 ? Math.min(1, (1 - edge) * 0.7) : 0);
18293
- headerFlags.push(y < rect.top + headerPx ? 1 : 0);
18294
- }
18295
- }
18296
- const dividerY = Math.round(rect.top + headerPx);
18297
- if (dividerY > y0 && dividerY < y1) {
18298
- for (let x = x0 + radius;x <= x1 - radius; x += 1) {
18299
- dividerIndices.push((dividerY * width + x) * 4);
18300
- }
18301
- }
18302
- const mask = {
18303
- key,
18304
- indices: Uint32Array.from(indices),
18305
- sampleIndices: Uint32Array.from(sampleIndices),
18306
- panelAlpha: Float32Array.from(panelAlpha),
18307
- edgeAlpha: Float32Array.from(edgeAlpha),
18308
- headerFlags: Uint8Array.from(headerFlags),
18309
- dividerIndices: Uint32Array.from(dividerIndices)
18310
- };
18311
- panelMaskCache.set(key, mask);
18312
- while (panelMaskCache.size > PANEL_MASK_CACHE_LIMIT) {
18313
- const oldest = panelMaskCache.keys().next().value;
18314
- if (!oldest)
18315
- break;
18316
- panelMaskCache.delete(oldest);
18317
- }
18318
- return mask;
18319
- }
18320
- function compositePanel(data, width, height, rect, headerPx) {
18321
- const blurScale = PANEL_BLUR_SCALE;
18322
- const sampled = downsampleRegion(data, width, height, rect, blurScale);
18323
- 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)));
18324
- const blurred = blurRgb(sampled.pixels, sampled.width, sampled.height, blurRadius);
18325
- const mask = getPanelMask(width, height, rect, headerPx, blurScale);
18326
- for (let i = 0;i < mask.indices.length; i += 1) {
18327
- const idx = mask.indices[i];
18328
- const sampleIdx = mask.sampleIndices[i];
18329
- data[idx] = blurred[sampleIdx];
18330
- data[idx + 1] = blurred[sampleIdx + 1];
18331
- data[idx + 2] = blurred[sampleIdx + 2];
18332
- data[idx + 3] = 255;
18333
- blendOver(data, idx, mask.headerFlags[i] ? PANEL_HEADER : PANEL, mask.panelAlpha[i]);
18334
- const edge = mask.edgeAlpha[i];
18335
- if (edge > 0)
18336
- blendOver(data, idx, PANEL_LINE2, edge);
18337
- }
18338
- for (let i = 0;i < mask.dividerIndices.length; i += 1) {
18339
- blendOver(data, mask.dividerIndices[i], PANEL_LINE2, 0.28);
18340
- }
18341
- }
18342
- function drawBigRigWordmark(data, width, height, layout, load) {
18343
- const sideBySide = layout.width >= FLEET_GRID_WIDTH * 1.9;
18344
- if (!sideBySide)
17883
+ function setHoveredSelectableId(id) {
17884
+ if (hoveredId === id)
18345
17885
  return;
18346
- const letters = "RIG";
18347
- const min = Math.min(width, height);
18348
- const glyphH = height * 0.42;
18349
- const glyphW = width * 0.13;
18350
- const gap = glyphW * 0.3;
18351
- const totalW = letters.length * glyphW + (letters.length - 1) * gap;
18352
- const centerX = width * 0.71;
18353
- const startX = centerX - totalW / 2;
18354
- const top = (height - glyphH) / 2;
18355
- const stroke = Math.max(2, min * 0.013);
18356
- drawGlow2(data, width, height, centerX, top + glyphH / 2, Math.max(glyphW, glyphH) * 0.9, LIME, 0.022 + load * 0.02);
18357
- const strokeAlpha = 0.1 + load * 0.06;
18358
- letters.split("").forEach((letter, index) => {
18359
- const segments = RIG_GLYPH_STROKES[letter];
18360
- if (!segments)
18361
- return;
18362
- const ox = startX + index * (glyphW + gap);
18363
- for (const [x0, y0, x1, y1] of segments) {
18364
- drawLine2(data, width, height, ox + x0 * glyphW, top + y0 * glyphH, ox + x1 * glyphW, top + y1 * glyphH, LIME_DIM, strokeAlpha, stroke);
18365
- }
18366
- });
17886
+ hoveredId = id;
17887
+ for (const listener of listeners)
17888
+ listener();
18367
17889
  }
18368
- function drawBackground(data, width, height, _tick, scene, layout, load, activeRuns) {
18369
- fillRgba(data, BG, BG_WORD);
18370
- drawStaticAsciiFleetBackground(data, width, height, scene, layout, load, activeRuns);
18371
- drawBigRigWordmark(data, width, height, layout, load);
18372
- applyStaticAtmosphere(data, width, height, load);
18373
- }
18374
- function imageVisualFrameKey(input) {
18375
- const width = Math.max(1, Math.floor(input.pixelSize.width));
18376
- const height = Math.max(1, Math.floor(input.pixelSize.height));
18377
- const layer = "layer" in input ? input.layer ?? "full" : "full";
18378
- const load = loadBucket(input.load);
18379
- 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("|")}`;
18380
- }
18381
- function encodeKittyFrame(width, height, data, input) {
18382
- if (input.transport === "png") {
18383
- const png = encodePng(width, height, data);
18384
- return kittyImageSequence(png.toString("base64"), { columns: input.layout.width, rows: input.layout.height, imageId: input.imageId, zIndex: input.zIndex, format: 100 });
18385
- }
18386
- const compressed = deflateSync(data, { level: ZLIB_LEVEL });
18387
- return kittyImageSequence(compressed.toString("base64"), {
18388
- columns: input.layout.width,
18389
- rows: input.layout.height,
18390
- imageId: input.imageId,
18391
- zIndex: input.zIndex,
18392
- format: 32,
18393
- pixelWidth: width,
18394
- pixelHeight: height,
18395
- compressed: true
18396
- });
17890
+ function clearHoveredSelectableId(id) {
17891
+ if (id !== undefined && hoveredId === id)
17892
+ setHoveredSelectableId(undefined);
18397
17893
  }
18398
- function cropRgba(data, sourceWidth, rect) {
18399
- const out = new Uint8ClampedArray(rect.width * rect.height * 4);
18400
- for (let y = 0;y < rect.height; y += 1) {
18401
- const sourceStart = ((rect.top + y) * sourceWidth + rect.left) * 4;
18402
- out.set(data.subarray(sourceStart, sourceStart + rect.width * 4), y * rect.width * 4);
18403
- }
18404
- return out;
18405
- }
18406
- function encodePanelPatch(data, sourceWidth, placement, imageId, zIndex) {
18407
- const patch = cropRgba(data, sourceWidth, placement);
18408
- const png = encodePng(placement.width, placement.height, patch);
18409
- const sequence = kittyImageSequence(png.toString("base64"), {
18410
- columns: Math.max(1, Math.round(placement.widthCells)),
18411
- rows: Math.max(1, Math.round(placement.heightCells)),
18412
- imageId,
18413
- zIndex,
18414
- format: 100
18415
- });
18416
- return `\x1B[${Math.max(1, Math.round(placement.topCells) + 1)};${Math.max(1, Math.round(placement.leftCells) + 1)}H${sequence}`;
18417
- }
18418
- function renderImageVisualFrame(input) {
18419
- const width = Math.max(1, Math.floor(input.pixelSize.width));
18420
- const height = Math.max(1, Math.floor(input.pixelSize.height));
18421
- const byteCount = width * height * 4;
18422
- if (byteCount > MAX_IMAGE_BYTES)
18423
- return null;
18424
- const layer = input.layer ?? "full";
18425
- const data = new Uint8ClampedArray(byteCount);
18426
- const load = loadBucket(input.load) / LOAD_BUCKETS;
18427
- const activeRuns = Math.max(0, Math.floor(input.activeRuns ?? 0));
18428
- drawBackground(data, width, height, 0, input.scene, input.layout, load, activeRuns);
18429
- if (layer === "split") {
18430
- const sequences = [encodeKittyFrame(width, height, data, input)];
18431
- let panelIndex = 0;
18432
- for (const panel of input.panels) {
18433
- if (panel.chrome !== "ad-terminal")
18434
- continue;
18435
- const placement = panelPixelPlacement(input.layout, panel, { width, height });
18436
- if (!placement)
18437
- continue;
18438
- const cellH = height / input.layout.height;
18439
- const headerPx = Math.max(cellH * 3, (panel.headerHeight ?? 3) * cellH);
18440
- compositePanel(data, width, height, placement, headerPx);
18441
- const imageId = input.imageIds?.[panelIndex] ?? (input.imageId + 1000 + panelIndex >>> 0 || panelIndex + 1);
18442
- sequences.push(encodePanelPatch(data, width, placement, imageId, -12));
18443
- panelIndex += 1;
18444
- }
18445
- return { key: imageVisualFrameKey(input), sequence: sequences.join("") };
18446
- }
18447
- if (layer === "panel-chrome")
18448
- return { key: imageVisualFrameKey(input), sequence: "" };
18449
- if (layer === "background") {
18450
- return { key: imageVisualFrameKey(input), sequence: encodeKittyFrame(width, height, data, input) };
18451
- }
18452
- if (layer === "panel-patches") {
18453
- const sequences = [];
18454
- let panelIndex = 0;
18455
- for (const panel of input.panels) {
18456
- if (panel.chrome !== "ad-terminal")
18457
- continue;
18458
- const placement = panelPixelPlacement(input.layout, panel, { width, height });
18459
- if (!placement)
18460
- continue;
18461
- const cellH = height / input.layout.height;
18462
- const headerPx = Math.max(cellH * 3, (panel.headerHeight ?? 3) * cellH);
18463
- compositePanel(data, width, height, placement, headerPx);
18464
- const imageId = input.imageIds?.[panelIndex] ?? (input.imageId + 1000 + panelIndex >>> 0 || panelIndex + 1);
18465
- sequences.push(encodePanelPatch(data, width, placement, imageId, input.zIndex));
18466
- panelIndex += 1;
18467
- }
18468
- return { key: imageVisualFrameKey(input), sequence: sequences.join("") };
18469
- }
18470
- for (const panel of input.panels) {
18471
- if (panel.chrome !== "ad-terminal")
18472
- continue;
18473
- const rect = panelRect(input.layout, panel, { width, height });
18474
- if (!rect)
18475
- continue;
18476
- const cellH = height / input.layout.height;
18477
- const headerPx = Math.max(cellH * 3, (panel.headerHeight ?? 3) * cellH);
18478
- compositePanel(data, width, height, rect, headerPx);
18479
- }
18480
- return { key: imageVisualFrameKey(input), sequence: encodeKittyFrame(width, height, data, input) };
18481
- }
18482
- function shouldRenderImagesInline() {
18483
- return process.env.RIG_OPENTUI_IMAGE_WORKER === "0" || import.meta.url.includes("$bunfs");
18484
- }
18485
- function createImageWorker() {
18486
- if (shouldRenderImagesInline())
18487
- return null;
18488
- const isTypeScriptRuntime = import.meta.url.endsWith(".ts");
18489
- const workerUrl = new URL(isTypeScriptRuntime ? "./image-visual-layer-worker.ts" : "./image-visual-layer-worker.js", import.meta.url);
18490
- try {
18491
- return new Worker(workerUrl);
18492
- } catch {
18493
- if (typeof process.versions.bun === "string") {
18494
- try {
18495
- return new Worker("./src/app-opentui/render/image-visual-layer-worker.ts");
18496
- } catch {}
18497
- }
18498
- return null;
18499
- }
18500
- }
18501
- function imageOutputBacklogBytes() {
18502
- const raw = Number.parseInt(process.env.RIG_OPENTUI_IMAGE_BACKLOG_BYTES ?? "", 10);
18503
- if (Number.isFinite(raw) && raw > 0)
18504
- return Math.max(64 * 1024, Math.min(2 * 1024 * 1024, raw));
18505
- return DEFAULT_IMAGE_OUTPUT_BACKLOG_BYTES;
18506
- }
18507
- function imageOutputBytesPerSecond() {
18508
- const raw = Number.parseInt(process.env.RIG_OPENTUI_IMAGE_BYTES_PER_SECOND ?? "", 10);
18509
- if (Number.isFinite(raw) && raw > 0)
18510
- return Math.max(128 * 1024, Math.min(2 * 1024 * 1024, raw));
18511
- return DEFAULT_IMAGE_OUTPUT_BYTES_PER_SECOND;
18512
- }
18513
- function pendingLayerKey(key) {
18514
- return key.split(":", 1)[0] || "full";
18515
- }
18516
-
18517
- class ImageVisualLayer {
18518
- imageId = allocateImageId();
18519
- panelImageIds = Array.from({ length: 8 }, () => allocateImageId());
18520
- panelChromeImageIds = Array.from({ length: 8 }, () => allocateImageId());
18521
- stdout;
18522
- onFrameReady;
18523
- protocol;
18524
- transport = imageTransport();
18525
- maxOutputBacklogBytes = imageOutputBacklogBytes();
18526
- outputBytesPerSecond = imageOutputBytesPerSecond();
18527
- outputBudgetBytes = imageOutputBytesPerSecond();
18528
- lastBudgetAtMs = Date.now();
18529
- motionSlot = this.createSlot();
18530
- workersStarted = false;
18531
- pendingFrames = new Map;
18532
- recentWrittenKeys = [];
18533
- imagesVisible = false;
18534
- flushPaused = false;
18535
- requestId = 0;
18536
- lastBackgroundKey = "";
18537
- fatalWorkerError = null;
18538
- destroyed = false;
18539
- constructor(stdout = process.stdout, onFrameReady) {
18540
- this.stdout = stdout;
18541
- this.onFrameReady = onFrameReady;
18542
- this.protocol = getRigTerminalCapabilities().imageProtocol;
18543
- }
18544
- get enabled() {
18545
- return this.protocol === "kitty";
18546
- }
18547
- clear() {
18548
- if (this.enabled) {
18549
- if (this.imagesVisible)
18550
- this.stdout.write([this.imageId, ...this.panelImageIds, ...this.panelChromeImageIds].map((id) => deleteKittyImage(id)).join("") + "\x1B[?25h");
18551
- this.imagesVisible = false;
18552
- this.pendingFrames.clear();
18553
- this.recentWrittenKeys.length = 0;
18554
- this.resetSlot(this.motionSlot);
18555
- this.lastBackgroundKey = "";
18556
- this.outputBudgetBytes = this.outputBytesPerSecond;
18557
- this.lastBudgetAtMs = Date.now();
18558
- this.motionSlot.clearedThroughRequestId = this.requestId;
18559
- }
18560
- }
18561
- destroy() {
18562
- if (this.destroyed)
18563
- return;
18564
- this.clear();
18565
- this.destroyed = true;
18566
- const workers = [this.motionSlot.worker];
18567
- this.motionSlot.worker = null;
18568
- for (const worker of workers)
18569
- if (worker)
18570
- worker.terminate().catch(() => {
18571
- return;
18572
- });
18573
- }
18574
- setFlushPaused(paused) {
18575
- this.flushPaused = paused;
18576
- }
18577
- isReady(input) {
18578
- if (!this.enabled || this.destroyed)
18579
- return true;
18580
- this.throwIfFatal();
18581
- const splitKey = imageVisualFrameKey({ ...input, tick: 0, layer: "split" });
18582
- return this.recentWrittenKeys.includes(splitKey) || Array.from(this.pendingFrames.values()).some((frame) => frame.key === splitKey);
18583
- }
18584
- render(input) {
18585
- if (!this.enabled || this.destroyed)
18586
- return;
18587
- this.throwIfFatal();
18588
- if (this.transport !== "png")
18589
- this.failWorker("renderer", new Error("OpenTUI image visuals require PNG transport for static-background rendering."));
18590
- this.ensureWorkersStarted();
18591
- const width = Math.max(1, Math.floor(input.pixelSize.width));
18592
- const height = Math.max(1, Math.floor(input.pixelSize.height));
18593
- if (width * height * 4 > MAX_IMAGE_BYTES)
18594
- return;
18595
- if (this.isOutputBackedUp())
18596
- return;
18597
- this.refillOutputBudget();
18598
- const splitInput = { ...input, tick: 0, layer: "split" };
18599
- const splitKey = imageVisualFrameKey(splitInput);
18600
- if (input.force || splitKey !== this.lastBackgroundKey) {
18601
- this.scheduleRender(this.motionSlot, splitInput, this.imageId, -30, input.force, this.panelImageIds);
18602
- this.lastBackgroundKey = splitKey;
18603
- }
18604
- }
18605
- flush() {
18606
- this.throwIfFatal();
18607
- if (this.flushPaused)
18608
- return false;
18609
- if (this.pendingFrames.size === 0)
18610
- return false;
18611
- this.refillOutputBudget();
18612
- if (this.isOutputBackedUp()) {
18613
- const retainedSplit = this.pendingFrames.get("split");
18614
- this.pendingFrames.clear();
18615
- if (retainedSplit)
18616
- this.pendingFrames.set("split", retainedSplit);
18617
- return false;
18618
- }
18619
- const framePriority = (frame) => {
18620
- const layer = pendingLayerKey(frame.key);
18621
- if (layer === "split")
18622
- return 1;
18623
- return 4;
18624
- };
18625
- const frames = Array.from(this.pendingFrames.values()).sort((a, b) => framePriority(a) - framePriority(b));
18626
- const prefix = "\x1B[?25l\x1B7\x1B[s\x1B[H";
18627
- const suffix = "\x1B[u\x1B8";
18628
- const selected = [];
18629
- const retained = new Map;
18630
- let sequence = "";
18631
- let byteLength = Buffer.byteLength(prefix + suffix);
18632
- for (const frame of frames) {
18633
- const candidate = sequence + frame.sequence;
18634
- const candidateBytes = Buffer.byteLength(prefix + candidate + suffix);
18635
- const layer = pendingLayerKey(frame.key);
18636
- const allowOneStaticFrame = selected.length === 0 && layer === "split";
18637
- if (candidateBytes <= this.outputBudgetBytes || allowOneStaticFrame) {
18638
- selected.push(frame);
18639
- sequence = candidate;
18640
- byteLength = candidateBytes;
18641
- } else if (layer === "split") {
18642
- retained.set(layer, frame);
18643
- }
18644
- }
18645
- this.pendingFrames.clear();
18646
- for (const [layer, frame] of retained)
18647
- this.pendingFrames.set(layer, frame);
18648
- if (!sequence || selected.length === 0)
18649
- return false;
18650
- const payload = `${prefix}${sequence}${suffix}`;
18651
- for (const frame of selected)
18652
- this.markWritten(frame.key);
18653
- this.outputBudgetBytes -= byteLength;
18654
- this.stdout.cork?.();
18655
- const accepted = this.stdout.write(payload);
18656
- this.stdout.uncork?.();
18657
- this.imagesVisible = true;
18658
- return accepted;
18659
- }
18660
- createSlot() {
18661
- return { worker: null, busy: false, queuedRequest: null, inFlightKey: "", queuedKey: "", clearedThroughRequestId: 0, latestRequestId: 0 };
18662
- }
18663
- resetSlot(slot) {
18664
- slot.busy = false;
18665
- slot.queuedRequest = null;
18666
- slot.inFlightKey = "";
18667
- slot.queuedKey = "";
18668
- }
18669
- markWritten(key) {
18670
- this.recentWrittenKeys.push(key);
18671
- while (this.recentWrittenKeys.length > RECENT_WRITTEN_LIMIT)
18672
- this.recentWrittenKeys.shift();
18673
- }
18674
- refillOutputBudget() {
18675
- const now = Date.now();
18676
- const elapsedMs = Math.max(0, now - this.lastBudgetAtMs);
18677
- this.lastBudgetAtMs = now;
18678
- this.outputBudgetBytes = Math.min(this.outputBytesPerSecond, this.outputBudgetBytes + elapsedMs / 1000 * this.outputBytesPerSecond);
18679
- }
18680
- isOutputBackedUp() {
18681
- return Boolean(this.stdout.writableNeedDrain) || this.stdout.writableLength > this.maxOutputBacklogBytes;
18682
- }
18683
- stageFrame(frame) {
18684
- this.pendingFrames.set(pendingLayerKey(frame.key), frame);
18685
- }
18686
- isKnownKey(slot, key) {
18687
- return this.recentWrittenKeys.includes(key) || Array.from(this.pendingFrames.values()).some((frame) => frame.key === key) || key === slot.inFlightKey || key === slot.queuedKey;
18688
- }
18689
- ensureWorkersStarted() {
18690
- if (this.workersStarted)
18691
- return;
18692
- this.workersStarted = true;
18693
- this.startWorker(this.motionSlot, "static-visuals");
18694
- }
18695
- failWorker(name, error) {
18696
- const detail = error instanceof Error ? error.message : String(error);
18697
- this.fatalWorkerError ??= new Error(`OpenTUI image worker ${name} failed: ${detail}`);
18698
- throw this.fatalWorkerError;
18699
- }
18700
- throwIfFatal() {
18701
- if (this.fatalWorkerError)
18702
- throw this.fatalWorkerError;
18703
- }
18704
- scheduleRender(slot, input, imageId, zIndex, force, imageIds) {
18705
- this.throwIfFatal();
18706
- const key = imageVisualFrameKey(input);
18707
- if (!force && this.isKnownKey(slot, key))
18708
- return;
18709
- const request = {
18710
- ...input,
18711
- imageId,
18712
- ...imageIds ? { imageIds } : {},
18713
- zIndex,
18714
- transport: this.transport,
18715
- requestId: ++this.requestId,
18716
- key
18717
- };
18718
- slot.latestRequestId = request.requestId;
18719
- if (!slot.worker) {
18720
- this.renderInline(slot, request);
18721
- return;
18722
- }
18723
- this.enqueueWorkerRender(slot, request);
18724
- }
18725
- startWorker(slot, name) {
18726
- const worker = createImageWorker();
18727
- if (!worker)
18728
- return;
18729
- slot.worker = worker;
18730
- worker.on("message", (message2) => this.handleWorkerMessage(slot, message2));
18731
- worker.once("error", (error) => {
18732
- if (!this.destroyed)
18733
- this.fatalWorkerError ??= new Error(`OpenTUI image worker ${name} failed: ${error instanceof Error ? error.message : String(error)}`);
18734
- });
18735
- worker.once("exit", (code) => {
18736
- if (!this.destroyed)
18737
- this.fatalWorkerError ??= new Error(`OpenTUI image worker ${name} exited unexpectedly with code ${code}.`);
18738
- });
18739
- }
18740
- renderInline(slot, request) {
18741
- try {
18742
- const result = renderImageVisualFrame(request);
18743
- if (result?.sequence && !this.isKnownKey(slot, result.key) && (!this.isOutputBackedUp() || pendingLayerKey(result.key) === "split")) {
18744
- this.stageFrame({ key: result.key, sequence: result.sequence });
18745
- this.onFrameReady?.();
18746
- }
18747
- } catch (error) {
18748
- this.failWorker(String(request.layer ?? "visual"), error);
18749
- }
18750
- }
18751
- enqueueWorkerRender(slot, request) {
18752
- this.throwIfFatal();
18753
- if (slot.busy) {
18754
- slot.queuedRequest = request;
18755
- slot.queuedKey = request.key;
18756
- return;
18757
- }
18758
- this.dispatchWorkerRender(slot, request);
18759
- }
18760
- dispatchWorkerRender(slot, request) {
18761
- this.throwIfFatal();
18762
- if (!slot.worker)
18763
- this.failWorker(String(request.layer ?? "visual"), new Error("worker is unavailable"));
18764
- slot.busy = true;
18765
- slot.inFlightKey = request.key;
18766
- try {
18767
- slot.worker.postMessage(request);
18768
- } catch (error) {
18769
- this.failWorker(String(request.layer ?? "visual"), error);
18770
- }
18771
- }
18772
- handleWorkerMessage(slot, message2) {
18773
- if (this.destroyed || !message2 || typeof message2 !== "object")
18774
- return;
18775
- const response = message2;
18776
- slot.busy = false;
18777
- slot.inFlightKey = "";
18778
- const isStale = response.requestId <= slot.clearedThroughRequestId || response.requestId < slot.latestRequestId;
18779
- if (!isStale) {
18780
- if (response.error)
18781
- this.failWorker(pendingLayerKey(response.key), new Error(response.error));
18782
- if (response.sequence && !this.isKnownKey(slot, response.key) && (!this.isOutputBackedUp() || pendingLayerKey(response.key) === "split")) {
18783
- this.stageFrame({ key: response.key, sequence: response.sequence });
18784
- this.onFrameReady?.();
18785
- }
18786
- }
18787
- const next = slot.queuedRequest;
18788
- slot.queuedRequest = null;
18789
- slot.queuedKey = "";
18790
- if (next && !this.isKnownKey(slot, next.key))
18791
- this.dispatchWorkerRender(slot, next);
18792
- }
18793
- }
18794
- function pixelSizeForRenderer(renderer) {
18795
- const resolution = renderer.resolution;
18796
- const base = resolution?.width && resolution?.height ? resolution : { width: renderer.width * 12, height: renderer.height * 24 };
18797
- const scale = imageResolutionScale();
18798
- return {
18799
- width: Math.max(renderer.width, Math.floor(base.width * scale)),
18800
- height: Math.max(renderer.height, Math.floor(base.height * scale))
17894
+ function subscribeHover(listener) {
17895
+ listeners.add(listener);
17896
+ return () => {
17897
+ listeners.delete(listener);
18801
17898
  };
18802
17899
  }
18803
- function forceModernImageVisuals() {
18804
- return getRigTerminalCapabilities().supportsKittyImages;
18805
- }
18806
- 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;
18807
- var init_image_visual_layer = __esm(() => {
18808
- init_terminal_capabilities();
18809
- init_ascii_fleet();
18810
- init_constants();
18811
- BG = [7, 8, 9, 255];
18812
- PANEL = [16, 17, 21, 184];
18813
- PANEL_HEADER = [16, 17, 21, 190];
18814
- PANEL_LINE2 = [255, 255, 255, 24];
18815
- LIME = [204, 255, 77, 255];
18816
- LIME_DIM = [169, 214, 63, 255];
18817
- CYAN = [86, 216, 255, 255];
18818
- MAGENTA = [255, 121, 176, 255];
18819
- INK_DIM = [108, 110, 121, 255];
18820
- PNG_SIGNATURE = new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10]);
18821
- CRC_TABLE = makeCrcTable();
18822
- MAX_IMAGE_BYTES = 48 * 1024 * 1024;
18823
- panelMaskCache = new Map;
18824
- BG_WORD = rgbaWord(BG);
18825
- ASCII_GLYPHS = {
18826
- ".": ["00000", "00000", "00000", "00000", "00000", "00100", "00100"],
18827
- "'": ["00100", "00100", "00000", "00000", "00000", "00000", "00000"],
18828
- "-": ["00000", "00000", "00000", "11111", "00000", "00000", "00000"],
18829
- _: ["00000", "00000", "00000", "00000", "00000", "00000", "11111"],
18830
- "=": ["00000", "11111", "00000", "00000", "11111", "00000", "00000"],
18831
- "|": ["00100", "00100", "00100", "00100", "00100", "00100", "00100"],
18832
- "/": ["00001", "00010", "00010", "00100", "01000", "01000", "10000"],
18833
- "\\": ["10000", "01000", "01000", "00100", "00010", "00010", "00001"],
18834
- "(": ["00010", "00100", "01000", "01000", "01000", "00100", "00010"],
18835
- ")": ["01000", "00100", "00010", "00010", "00010", "00100", "01000"],
18836
- "!": ["00100", "00100", "00100", "00100", "00100", "00000", "00100"],
18837
- "%": ["11001", "11010", "00100", "01000", "10110", "00110", "00000"],
18838
- $: ["00100", "11110", "10100", "11110", "00101", "11110", "00100"],
18839
- "@": ["01110", "10001", "10111", "10101", "10111", "10000", "01111"],
18840
- o: ["00000", "01110", "10001", "10001", "10001", "01110", "00000"],
18841
- "*": ["00100", "10101", "01110", "11111", "01110", "10101", "00100"]
18842
- };
18843
- RIG_GLYPH_STROKES = {
18844
- 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]],
18845
- I: [[0.5, 0, 0.5, 1], [0.18, 0, 0.82, 0], [0.18, 1, 0.82, 1]],
18846
- 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]]
18847
- };
18848
- DEFAULT_IMAGE_OUTPUT_BACKLOG_BYTES = 256 * 1024;
18849
- DEFAULT_IMAGE_OUTPUT_BYTES_PER_SECOND = 850 * 1024;
17900
+ var hoveredId, listeners;
17901
+ var init_hover = __esm(() => {
17902
+ listeners = new Set;
18850
17903
  });
18851
17904
 
18852
17905
  // packages/cli/src/app-opentui/render/text.ts
18853
17906
  import { RGBA as RGBA4, TextAttributes as TextAttributes4, TextRenderable } from "@opentui/core";
17907
+ function lineIsClickable(line2) {
17908
+ return line2?.selectable !== undefined || line2?.selectableIndex !== undefined;
17909
+ }
17910
+ function lineSelectableId(line2) {
17911
+ return line2?.selectable?.id ?? line2?.selectableId;
17912
+ }
17913
+ function lineBackground(line2) {
17914
+ if (line2?.bg)
17915
+ return RGBA4.fromHex(line2.bg);
17916
+ const id = lineSelectableId(line2);
17917
+ if (id !== undefined && isHovered(id))
17918
+ return HOVER_BG;
17919
+ return TRANSPARENT2;
17920
+ }
18854
17921
  function selectableMouseHandler(currentLineRef, onLineMouseDown) {
18855
17922
  return (event) => {
18856
17923
  const currentLine = currentLineRef();
18857
- if (currentLine?.selectableIndex === undefined)
17924
+ if (!lineIsClickable(currentLine))
18858
17925
  return;
18859
17926
  onLineMouseDown?.(currentLine, event);
18860
17927
  };
18861
17928
  }
17929
+ function hoverHandlers(currentLineRef) {
17930
+ return {
17931
+ onMouseOver: () => {
17932
+ const id = lineSelectableId(currentLineRef());
17933
+ if (id !== undefined)
17934
+ setHoveredSelectableId(id);
17935
+ },
17936
+ onMouseOut: () => clearHoveredSelectableId(lineSelectableId(currentLineRef()))
17937
+ };
17938
+ }
18862
17939
  function createTextLine(renderer, id, top, onLineMouseDown) {
18863
17940
  let currentLine;
18864
17941
  const renderable = new TextRenderable(renderer, {
@@ -18870,7 +17947,8 @@ function createTextLine(renderer, id, top, onLineMouseDown) {
18870
17947
  width: 1,
18871
17948
  fg: RIG_UI.ink2,
18872
17949
  selectable: true,
18873
- onMouseDown: selectableMouseHandler(() => currentLine, onLineMouseDown)
17950
+ onMouseDown: selectableMouseHandler(() => currentLine, onLineMouseDown),
17951
+ ...hoverHandlers(() => currentLine)
18874
17952
  });
18875
17953
  renderable.setRigSceneLine = (line2) => {
18876
17954
  currentLine = line2;
@@ -18887,7 +17965,8 @@ function createFlowTextLine(renderer, id, onLineMouseDown) {
18887
17965
  flexShrink: 0,
18888
17966
  fg: RIG_UI.ink2,
18889
17967
  selectable: true,
18890
- onMouseDown: selectableMouseHandler(() => currentLine, onLineMouseDown)
17968
+ onMouseDown: selectableMouseHandler(() => currentLine, onLineMouseDown),
17969
+ ...hoverHandlers(() => currentLine)
18891
17970
  });
18892
17971
  renderable.setRigSceneLine = (line2) => {
18893
17972
  currentLine = line2;
@@ -18901,7 +17980,7 @@ function applyTextLine(renderable, line2, top, left, width) {
18901
17980
  renderable.width = width;
18902
17981
  renderable.content = line2?.styledText ?? line2?.text ?? "";
18903
17982
  renderable.fg = line2?.fg ?? RIG_UI.ink2;
18904
- renderable.bg = line2?.bg ?? TRANSPARENT2;
17983
+ renderable.bg = lineBackground(line2);
18905
17984
  renderable.attributes = line2?.bold ? TextAttributes4.BOLD : line2?.dim ? TextAttributes4.DIM : 0;
18906
17985
  }
18907
17986
  function applyFlowTextLine(renderable, line2, width) {
@@ -18911,7 +17990,7 @@ function applyFlowTextLine(renderable, line2, width) {
18911
17990
  renderable.width = Math.max(1, width);
18912
17991
  renderable.content = line2?.styledText ?? line2?.text ?? "";
18913
17992
  renderable.fg = line2?.fg ?? RIG_UI.ink2;
18914
- renderable.bg = line2?.bg ?? TRANSPARENT2;
17993
+ renderable.bg = lineBackground(line2);
18915
17994
  renderable.attributes = line2?.bold ? TextAttributes4.BOLD : line2?.dim ? TextAttributes4.DIM : 0;
18916
17995
  }
18917
17996
  function clearTextLines(renderables, from = 0) {
@@ -18925,10 +18004,12 @@ function clearTextLines(renderables, from = 0) {
18925
18004
  renderable.bg = TRANSPARENT2;
18926
18005
  }
18927
18006
  }
18928
- var TRANSPARENT2;
18007
+ var TRANSPARENT2, HOVER_BG;
18929
18008
  var init_text = __esm(() => {
18930
18009
  init_theme2();
18010
+ init_hover();
18931
18011
  TRANSPARENT2 = RGBA4.fromInts(0, 0, 0, 0);
18012
+ HOVER_BG = RGBA4.fromHex(RIG_UI.hover);
18932
18013
  });
18933
18014
 
18934
18015
  // packages/cli/src/app-opentui/render/panels.ts
@@ -20053,7 +19134,7 @@ function __renderSceneForTest(renderer, state, sceneRenderers) {
20053
19134
  renderer.root.add(navText);
20054
19135
  const registry = buildSceneRendererRegistry(sceneRenderers);
20055
19136
  const transition = { scene: state.scene, startedAtMs: 0, minUntilMs: 0 };
20056
- const selectables = renderNow(renderer, null, null, null, scrollPanels, null, null, navText, textLines, nativeHost, state, registry, transition);
19137
+ const selectables = renderNow(renderer, null, null, scrollPanels, null, null, navText, textLines, nativeHost, state, registry, transition);
20057
19138
  return { selectables, nativeHost };
20058
19139
  }
20059
19140
  async function launchRigOpenTuiApp(options) {
@@ -20065,7 +19146,6 @@ async function launchRigOpenTuiApp(options) {
20065
19146
  const events = createAppEventBus();
20066
19147
  let renderer = null;
20067
19148
  let graphics = null;
20068
- let imageVisuals = null;
20069
19149
  let preloaderBackdrop = null;
20070
19150
  const scrollPanels = [];
20071
19151
  let typeBar = null;
@@ -20093,7 +19173,7 @@ async function launchRigOpenTuiApp(options) {
20093
19173
  if (!renderer || destroyed)
20094
19174
  return;
20095
19175
  const currentState = store.getState();
20096
- renderedSelectableItems = renderNow(renderer, graphics, imageVisuals, preloaderBackdrop, scrollPanels, typeBar, brandText, navText, textLines, nativeHost, currentState, sceneRendererRegistry, preloaderTransition, scheduleAppRender);
19176
+ renderedSelectableItems = renderNow(renderer, graphics, preloaderBackdrop, scrollPanels, typeBar, brandText, navText, textLines, nativeHost, currentState, sceneRendererRegistry, preloaderTransition, scheduleAppRender);
20097
19177
  focusManager.restoreTypeBarFocus(typeBar, { hidden: currentState.scene === "handoff" });
20098
19178
  maybeRedirectFromUnresolvedScene(currentState);
20099
19179
  };
@@ -20253,18 +19333,8 @@ async function launchRigOpenTuiApp(options) {
20253
19333
  });
20254
19334
  renderer.setTerminalTitle?.("Operator cockpit");
20255
19335
  const layout = computeStageLayout(renderer.width, renderer.height);
20256
- imageVisuals = forceModernImageVisuals() ? new ImageVisualLayer(process.stdout, () => scheduleAppRender(0)) : null;
20257
- if (imageVisuals?.enabled) {
20258
- const flushImageVisuals = async () => {
20259
- imageVisuals?.flush();
20260
- };
20261
- renderer.setFrameCallback(flushImageVisuals);
20262
- resources.add(() => renderer?.removeFrameCallback(flushImageVisuals));
20263
- }
20264
- if (!imageVisuals?.enabled) {
20265
- graphics = createGraphicsLayer(renderer, layout);
20266
- renderer.root.add(graphics);
20267
- }
19336
+ graphics = createGraphicsLayer(renderer, layout);
19337
+ renderer.root.add(graphics);
20268
19338
  preloaderBackdrop = new BoxRenderable3(renderer, {
20269
19339
  id: "rig-preloader-backdrop",
20270
19340
  position: "absolute",
@@ -20287,8 +19357,7 @@ async function launchRigOpenTuiApp(options) {
20287
19357
  } else if (!selectById(store, id, renderedSelectableItems)) {
20288
19358
  return;
20289
19359
  }
20290
- if (line2.activateOnClick)
20291
- runAppAction("Activate selection", () => activateSelection(runtime, renderedSelectableItems));
19360
+ runAppAction("Activate selection", () => activateSelection(runtime, renderedSelectableItems));
20292
19361
  };
20293
19362
  for (let index = 0;index < MAX_SCENE_PANELS; index += 1) {
20294
19363
  const panel = createScrollPanelLayer(renderer, `rig-scroll-panel-${index}`, onLineClick);
@@ -20355,6 +19424,7 @@ async function launchRigOpenTuiApp(options) {
20355
19424
  };
20356
19425
  renderer.keyInput.on("keypress", onKeyPress);
20357
19426
  resources.add(() => renderer?.keyInput.off("keypress", onKeyPress));
19427
+ resources.add(subscribeHover(() => scheduleAppRender(0)));
20358
19428
  const onPaste = (event) => {
20359
19429
  const state = store.getState();
20360
19430
  if (state.scene === "handoff") {
@@ -20390,7 +19460,6 @@ async function launchRigOpenTuiApp(options) {
20390
19460
  stopActivePiHost("renderer destroyed");
20391
19461
  stopActiveCommandHost("renderer destroyed");
20392
19462
  destroyNativeHost(nativeHost);
20393
- imageVisuals?.destroy();
20394
19463
  if (renderTimer)
20395
19464
  clearTimeout(renderTimer);
20396
19465
  renderTimer = null;
@@ -20439,14 +19508,13 @@ async function launchRigOpenTuiApp(options) {
20439
19508
  clearTimeout(renderTimer);
20440
19509
  renderTimer = null;
20441
19510
  resources.disposeAll();
20442
- imageVisuals?.destroy();
20443
19511
  stopActivePiHost("app shutdown");
20444
19512
  stopActiveCommandHost("app shutdown");
20445
19513
  if (renderer && !renderer.isDestroyed)
20446
19514
  renderer.destroy();
20447
19515
  }
20448
19516
  }
20449
- function renderNow(renderer, graphics, imageVisuals, preloaderBackdrop, scrollPanels, typeBar, brandText, navText, textLines, nativeHost, state, sceneRendererRegistry, preloaderTransition, scheduleRenderAfter) {
19517
+ function renderNow(renderer, graphics, preloaderBackdrop, scrollPanels, typeBar, brandText, navText, textLines, nativeHost, state, sceneRendererRegistry, preloaderTransition, scheduleRenderAfter) {
20450
19518
  const layout = computeStageLayout(renderer.width, renderer.height);
20451
19519
  const hideNav = () => {
20452
19520
  if (navText)
@@ -20455,7 +19523,6 @@ function renderNow(renderer, graphics, imageVisuals, preloaderBackdrop, scrollPa
20455
19523
  if (layout.tooSmall) {
20456
19524
  if (graphics)
20457
19525
  clearGraphicsLayer(graphics);
20458
- imageVisuals?.clear();
20459
19526
  if (preloaderBackdrop)
20460
19527
  preloaderBackdrop.visible = false;
20461
19528
  if (brandText)
@@ -20489,8 +19556,7 @@ function renderNow(renderer, graphics, imageVisuals, preloaderBackdrop, scrollPa
20489
19556
  preloaderTransition.minUntilMs = now;
20490
19557
  const targetFrame = renderState.typeBar.mode === "command" ? commandPaletteFrame(renderState, layout) : rendererForScene(renderState.scene, sceneRendererRegistry)(renderState, layout);
20491
19558
  const targetContentLayout = targetFrame.fullScreen ? { ...layout, centerWidth: layout.width, centerLeft: 0, centerTop: 0, centerHeight: Math.min(layout.height, MAX_SCENE_LINES) } : layout;
20492
- const targetPanelsForImages = (targetFrame.panels ?? []).map((panel) => ({ ...panel }));
20493
- let targetVisualReady = true;
19559
+ const targetVisualReady = true;
20494
19560
  const fleetStats = fleetStatsFromState(state);
20495
19561
  const fleetLoad = fleetStats.load;
20496
19562
  const fleetActive = fleetStats.active;
@@ -20500,23 +19566,7 @@ function renderNow(renderer, graphics, imageVisuals, preloaderBackdrop, scrollPa
20500
19566
  if (renderState.scene === "command") {
20501
19567
  getActiveCommandHost()?.resize(Math.max(40, targetContentLayout.centerWidth - 12), Math.max(10, targetContentLayout.centerHeight - 6));
20502
19568
  }
20503
- if (imageVisuals && targetFrame.terminalActive) {
20504
- imageVisuals.clear();
20505
- } else if (imageVisuals && !targetFrame.fullScreen && !sceneJustChanged) {
20506
- const visualInput = {
20507
- layout: targetContentLayout,
20508
- pixelSize: pixelSizeForRenderer(renderer),
20509
- panels: targetPanelsForImages,
20510
- scene: state.scene,
20511
- tick: state.tick,
20512
- load: fleetLoad,
20513
- activeRuns: fleetActive
20514
- };
20515
- imageVisuals.render(visualInput);
20516
- targetVisualReady = imageVisuals.isReady(visualInput);
20517
- }
20518
19569
  const preloaderActive = sceneJustChanged && !cachedFastPath || isPreloaderActive(preloaderTransition, targetVisualReady, now);
20519
- imageVisuals?.setFlushPaused(preloaderActive);
20520
19570
  const frame = preloaderActive ? renderAsciiPreloaderFrame(renderState.scene, layout, state.tick) : targetFrame;
20521
19571
  if (graphics) {
20522
19572
  resizeGraphicsLayer(graphics, layout);
@@ -20615,8 +19665,8 @@ var init_runtime = __esm(() => {
20615
19665
  init_theme2();
20616
19666
  init_graphics();
20617
19667
  init_fleet_stats();
20618
- init_image_visual_layer();
20619
19668
  init_text();
19669
+ init_hover();
20620
19670
  init_panels();
20621
19671
  init_type_bar();
20622
19672
  init_scene();
@@ -20754,13 +19804,16 @@ function lineContent(line2) {
20754
19804
  }
20755
19805
  function LineView(props) {
20756
19806
  const { line: line2, onLineClick } = props;
19807
+ const id = lineSelectableId(line2);
20757
19808
  return /* @__PURE__ */ jsxDEV("text", {
20758
19809
  style: { position: "absolute", left: props.left, top: props.top, width: props.width, zIndex: 5 },
20759
19810
  fg: line2.fg ?? RIG_UI.ink2,
20760
- bg: line2.bg ?? TRANSPARENT2,
19811
+ bg: lineBackground(line2),
20761
19812
  attributes: lineAttributes(line2),
20762
19813
  content: lineContent(line2),
20763
- onMouseDown: onLineClick ? () => onLineClick(line2) : undefined
19814
+ onMouseDown: onLineClick ? () => onLineClick(line2) : undefined,
19815
+ onMouseOver: id !== undefined ? () => setHoveredSelectableId(id) : undefined,
19816
+ onMouseOut: id !== undefined ? () => clearHoveredSelectableId(id) : undefined
20764
19817
  }, undefined, false, undefined, this);
20765
19818
  }
20766
19819
  function PanelHost(props) {
@@ -20847,6 +19900,7 @@ var init_SceneFrameView = __esm(() => {
20847
19900
  init_theme2();
20848
19901
  init_scene();
20849
19902
  init_text();
19903
+ init_hover();
20850
19904
  init_panels();
20851
19905
  init_native_host();
20852
19906
  init_scroll();
@@ -21000,7 +20054,7 @@ var init_nav = __esm(() => {
21000
20054
  });
21001
20055
 
21002
20056
  // packages/cli/src/app-opentui/react/App.tsx
21003
- import { useMemo, useRef as useRef4 } from "react";
20057
+ import { useMemo, useRef as useRef4, useSyncExternalStore as useSyncExternalStore2 } from "react";
21004
20058
  import { useKeyboard, useRenderer as useRenderer4, useTerminalDimensions as useTerminalDimensions2 } from "@opentui/react";
21005
20059
  import { jsxDEV as jsxDEV3 } from "@opentui/react/jsx-dev-runtime";
21006
20060
  function defaultFrame(state) {
@@ -21018,6 +20072,7 @@ function App(props) {
21018
20072
  const runAppAction = useRunAppAction();
21019
20073
  const store = useStore();
21020
20074
  const renderer = useRenderer4();
20075
+ useSyncExternalStore2(subscribeHover, getHoveredSelectableId, getHoveredSelectableId);
21021
20076
  const dims = useTerminalDimensions2();
21022
20077
  const layout = computeStageLayout(dims.width, dims.height);
21023
20078
  const registry = useMemo(() => ({ ...BUILTIN, ...props.sceneRenderers ?? {} }), [props.sceneRenderers]);
@@ -21052,8 +20107,7 @@ function App(props) {
21052
20107
  } else {
21053
20108
  return;
21054
20109
  }
21055
- if (line2.activateOnClick)
21056
- runAppAction("Activate selection", () => activateSelection(runtime, items));
20110
+ runAppAction("Activate selection", () => activateSelection(runtime, items));
21057
20111
  };
21058
20112
  return /* @__PURE__ */ jsxDEV3("box", {
21059
20113
  style: { position: "absolute", left: 0, top: 0, width: "100%", height: "100%" },
@@ -21103,6 +20157,7 @@ var init_App = __esm(() => {
21103
20157
  init_SceneFrameView();
21104
20158
  init_ChromeHost();
21105
20159
  init_scroll();
20160
+ init_hover();
21106
20161
  init_context();
21107
20162
  init_nav();
21108
20163
  BUILTIN = {
@@ -24999,11 +24054,8 @@ init_scene();
24999
24054
  init_selectable();
25000
24055
 
25001
24056
  // packages/cli/src/app-opentui/list-search.ts
25002
- function isListSearchScene(scene) {
25003
- return scene === "fleet" || scene === "tasks";
25004
- }
25005
- function listSearchQuery(state) {
25006
- return isListSearchScene(state.scene) ? state.typeBar.value.trim() : "";
24057
+ function listSearchQuery(_state) {
24058
+ return "";
25007
24059
  }
25008
24060
  function valueText(value) {
25009
24061
  if (value === undefined || value === null)