@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
@@ -2290,6 +2290,7 @@ var RIG_UI = {
2290
2290
  bg2: "#0b0c0e",
2291
2291
  panel: "#101115",
2292
2292
  panel2: "#14161b",
2293
+ hover: "#181b24",
2293
2294
  glass: "#1e2230",
2294
2295
  border: "#2a2e3a",
2295
2296
  ink: "#f4f5f8",
@@ -2576,19 +2577,6 @@ function panelContentRect(layout, panel) {
2576
2577
  const contentWidth = Math.max(1, width - paddingX * 2 - 1);
2577
2578
  return { left, top, width, height, paddingX, paddingY, contentWidth };
2578
2579
  }
2579
- function panelPixelPlacement(layout, panel, size, minPixels = 8) {
2580
- const cellW = size.width / layout.width;
2581
- const cellH = size.height / layout.height;
2582
- const leftCells = layout.centerLeft + (panel.left ?? 0);
2583
- const topCells = layout.centerTop + panel.top;
2584
- const widthCells = panel.width ?? layout.centerWidth;
2585
- const heightCells = panel.height;
2586
- const left = Math.round(leftCells * cellW);
2587
- const top = Math.round(topCells * cellH);
2588
- const width = Math.round(widthCells * cellW);
2589
- const height = Math.round(heightCells * cellH);
2590
- return width > minPixels && height > minPixels ? { left, top, width, height, leftCells, topCells, widthCells, heightCells } : null;
2591
- }
2592
2580
 
2593
2581
  // packages/cli/src/app-opentui/render/constants.ts
2594
2582
  var LCG_MULTIPLIER = 1664525;
@@ -2621,7 +2609,6 @@ var RAIL_YS_LOOP = [0.18, 0.5, 0.82];
2621
2609
  var RAIL_YS_CARRIER = [0.24, 0.54, 0.82];
2622
2610
  var RAIL_YS_DISPATCH = [0.18, 0.58, 0.82];
2623
2611
  var GLOW_FALLOFF_EXP_ASCII = 2.2;
2624
- var GLOW_FALLOFF_EXP_IMAGE = 2.4;
2625
2612
 
2626
2613
  // packages/cli/src/app-opentui/render/graphics.ts
2627
2614
  var BRAILLE_SAMPLES_X = 2;
@@ -3053,27 +3040,20 @@ function writeCell(layer, x, y, char, fg, bg) {
3053
3040
  function drawFullCell(layer, x, y, bg) {
3054
3041
  writeCell(layer, x, y, " ", TRANSPARENT, bg);
3055
3042
  }
3056
- function drawCornerCut(layer, x, y, char, fg) {
3057
- writeCell(layer, x, y, char, fg, BACKDROP);
3058
- }
3059
- function drawRoundedFill(layer, left, top, right, bottom, dividerY) {
3043
+ function drawPanelFill(layer, left, top, right, bottom, dividerY) {
3060
3044
  const bgForY = (y) => y < dividerY ? PANEL_HEADER_BG : PANEL_BG;
3061
3045
  for (let y = top;y <= bottom; y += 1) {
3062
3046
  const bg = bgForY(y);
3063
3047
  for (let x = left;x <= right; x += 1)
3064
3048
  drawFullCell(layer, x, y, bg);
3065
3049
  }
3066
- drawCornerCut(layer, left, top, "\u2597", PANEL_HEADER_BG);
3067
- drawCornerCut(layer, right, top, "\u2596", PANEL_HEADER_BG);
3068
- drawCornerCut(layer, left, bottom, "\u259D", PANEL_BG);
3069
- drawCornerCut(layer, right, bottom, "\u2598", PANEL_BG);
3070
3050
  }
3071
3051
  function drawAdTerminalPanel(layer, layout, panel) {
3072
3052
  const rect = panelCellRect(layout, panel);
3073
3053
  if (!rect)
3074
3054
  return;
3075
3055
  const { left, top, width, right, bottom, headerHeight, dividerY } = rect;
3076
- drawRoundedFill(layer, left, top, right, bottom, dividerY);
3056
+ drawPanelFill(layer, left, top, right, bottom, dividerY);
3077
3057
  if (dividerY > top && dividerY < bottom) {
3078
3058
  for (let x = left + 2;x < right - 1; x += 1) {
3079
3059
  writeCell(layer, x, dividerY, "\u2500", PANEL_LINE_DIM, PANEL_BG);
@@ -3220,963 +3200,71 @@ function sparklineString(values, width) {
3220
3200
  }).join("");
3221
3201
  }
3222
3202
 
3223
- // packages/cli/src/app-opentui/render/image-visual-layer.ts
3224
- import { Worker } from "worker_threads";
3225
- import { deflateSync } from "zlib";
3226
- import { allocateImageId, deleteKittyImage } from "@earendil-works/pi-tui";
3227
-
3228
- // packages/cli/src/app-opentui/terminal-capabilities.ts
3229
- import { getCapabilities } from "@earendil-works/pi-tui";
3230
- function getRigTerminalCapabilities() {
3231
- const imageProtocol = getCapabilities().images;
3232
- const supportsKittyImages = imageProtocol === "kitty";
3233
- return {
3234
- imageProtocol,
3235
- supportsKittyImages,
3236
- visualMode: supportsKittyImages ? "kitty-images" : "framebuffer-fallback"
3237
- };
3238
- }
3203
+ // packages/cli/src/app-opentui/render/text.ts
3204
+ import { RGBA as RGBA4, TextAttributes as TextAttributes4, TextRenderable } from "@opentui/core";
3239
3205
 
3240
- // packages/cli/src/app-opentui/render/image-visual-layer.ts
3241
- var PANEL_MASK_RADIUS_MIN = 14;
3242
- var PANEL_MASK_RADIUS_MAX = 34;
3243
- var PANEL_MASK_RADIUS_FACTOR = 0.035;
3244
- var PANEL_BLUR_RADIUS_MIN = 8;
3245
- var PANEL_BLUR_RADIUS_MAX = 28;
3246
- var PANEL_BLUR_RADIUS_FACTOR = 0.018;
3247
- var BG = [7, 8, 9, 255];
3248
- var PANEL = [16, 17, 21, 184];
3249
- var PANEL_HEADER = [16, 17, 21, 190];
3250
- var PANEL_LINE2 = [255, 255, 255, 24];
3251
- var LIME = [204, 255, 77, 255];
3252
- var LIME_DIM = [169, 214, 63, 255];
3253
- var CYAN = [86, 216, 255, 255];
3254
- var MAGENTA = [255, 121, 176, 255];
3255
- var INK_DIM = [108, 110, 121, 255];
3256
- var PNG_SIGNATURE = new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10]);
3257
- var CRC_TABLE = makeCrcTable();
3258
- var MAX_IMAGE_BYTES = 48 * 1024 * 1024;
3259
- var KITTY_CHUNK_SIZE = 4096;
3260
- var PANEL_BLUR_SCALE = 1;
3261
- var PANEL_MASK_CACHE_LIMIT = 16;
3262
- var ZLIB_LEVEL = 1;
3263
- var LOAD_BUCKETS = 5;
3264
- function loadBucket(load) {
3265
- if (!Number.isFinite(load ?? NaN))
3266
- return 0;
3267
- return Math.max(0, Math.min(LOAD_BUCKETS, Math.round((load ?? 0) * LOAD_BUCKETS)));
3268
- }
3269
- var panelMaskCache = new Map;
3270
- var BG_WORD = rgbaWord(BG);
3271
- function imageTransport() {
3272
- const forced = process.env.RIG_OPENTUI_IMAGE_TRANSPORT;
3273
- if (forced === "rgba-zlib")
3274
- return "rgba-zlib";
3275
- return "png";
3276
- }
3277
- function rgbaWord(color) {
3278
- return (color[3] << 24 | color[2] << 16 | color[1] << 8 | color[0]) >>> 0;
3279
- }
3280
- function fillRgba(data, color, word = rgbaWord(color)) {
3281
- if (data.byteOffset % 4 === 0 && data.byteLength % 4 === 0) {
3282
- new Uint32Array(data.buffer, data.byteOffset, data.byteLength / 4).fill(word);
3283
- return;
3284
- }
3285
- for (let i = 0;i < data.length; i += 4) {
3286
- data[i] = color[0];
3287
- data[i + 1] = color[1];
3288
- data[i + 2] = color[2];
3289
- data[i + 3] = color[3];
3290
- }
3291
- }
3292
- var ASCII_GLYPHS = {
3293
- ".": ["00000", "00000", "00000", "00000", "00000", "00100", "00100"],
3294
- "'": ["00100", "00100", "00000", "00000", "00000", "00000", "00000"],
3295
- "-": ["00000", "00000", "00000", "11111", "00000", "00000", "00000"],
3296
- _: ["00000", "00000", "00000", "00000", "00000", "00000", "11111"],
3297
- "=": ["00000", "11111", "00000", "00000", "11111", "00000", "00000"],
3298
- "|": ["00100", "00100", "00100", "00100", "00100", "00100", "00100"],
3299
- "/": ["00001", "00010", "00010", "00100", "01000", "01000", "10000"],
3300
- "\\": ["10000", "01000", "01000", "00100", "00010", "00010", "00001"],
3301
- "(": ["00010", "00100", "01000", "01000", "01000", "00100", "00010"],
3302
- ")": ["01000", "00100", "00010", "00010", "00010", "00100", "01000"],
3303
- "!": ["00100", "00100", "00100", "00100", "00100", "00000", "00100"],
3304
- "%": ["11001", "11010", "00100", "01000", "10110", "00110", "00000"],
3305
- $: ["00100", "11110", "10100", "11110", "00101", "11110", "00100"],
3306
- "@": ["01110", "10001", "10111", "10101", "10111", "10000", "01111"],
3307
- o: ["00000", "01110", "10001", "10001", "10001", "01110", "00000"],
3308
- "*": ["00100", "10101", "01110", "11111", "01110", "10101", "00100"]
3309
- };
3310
- function sceneStaticTick(scene) {
3311
- let hash = 0;
3312
- for (let index = 0;index < scene.length; index += 1)
3313
- hash = hash * 33 + scene.charCodeAt(index) >>> 0;
3314
- return 11 + hash % 97;
3315
- }
3316
- function charColor(char, row, totalRows) {
3317
- if (char === "@" || char === "$" || char === "o")
3318
- return LIME;
3319
- if (char === "%" || char === "!" || char === "/" || char === "\\" || char === "|")
3320
- return CYAN;
3321
- if (char === "." || char === "'" || char === "_" || row < 3 || row > totalRows - 4)
3322
- return INK_DIM;
3323
- if (char === "-" || char === "=" || char === "(" || char === ")")
3324
- return LIME_DIM;
3325
- return MAGENTA;
3326
- }
3327
- function fillRectAlpha(data, width, height, left, top, rectWidth, rectHeight, color, alpha) {
3328
- const x0 = Math.max(0, Math.floor(left));
3329
- const y0 = Math.max(0, Math.floor(top));
3330
- const x1 = Math.min(width, Math.ceil(left + rectWidth));
3331
- const y1 = Math.min(height, Math.ceil(top + rectHeight));
3332
- for (let y = y0;y < y1; y += 1) {
3333
- for (let x = x0;x < x1; x += 1)
3334
- blendOver(data, (y * width + x) * 4, color, alpha);
3335
- }
3336
- }
3337
- function drawAsciiGlyph(data, width, height, x, y, cellWidth, cellHeight, char, color, alpha) {
3338
- const pattern = ASCII_GLYPHS[char] ?? ASCII_GLYPHS["*"];
3339
- const glyphWidth = Math.max(1, cellWidth * 0.56);
3340
- const glyphHeight = Math.max(1, cellHeight * 0.7);
3341
- const originX = x + (cellWidth - glyphWidth) * 0.5;
3342
- const originY = y + (cellHeight - glyphHeight) * 0.5;
3343
- const pixelWidth = Math.max(1, glyphWidth / pattern[0].length);
3344
- const pixelHeight = Math.max(1, glyphHeight / pattern.length);
3345
- for (let row = 0;row < pattern.length; row += 1) {
3346
- const bits = pattern[row];
3347
- for (let col = 0;col < bits.length; col += 1) {
3348
- if (bits[col] !== "1")
3349
- continue;
3350
- fillRectAlpha(data, width, height, originX + col * pixelWidth, originY + row * pixelHeight, pixelWidth * 0.72, pixelHeight * 0.72, color, alpha);
3351
- }
3352
- }
3353
- }
3354
- function drawStaticAsciiFleetBackground(data, width, height, scene, layout, load, activeRuns) {
3355
- const rows = fleetRows(sceneStaticTick(scene), { animate: true });
3356
- const cellWidth = width / Math.max(1, layout.width);
3357
- const cellHeight = height / Math.max(1, layout.height);
3358
- const sideBySide = layout.width >= FLEET_GRID_WIDTH * 1.9;
3359
- const leftCells = sideBySide ? Math.max(1, Math.floor(layout.width * 0.3 - FLEET_GRID_WIDTH / 2)) : Math.floor((layout.width - FLEET_GRID_WIDTH) / 2);
3360
- const topCells = Math.floor((layout.height - FLEET_GRID_HEIGHT) / 2);
3361
- const fleetLeft = leftCells * cellWidth;
3362
- const fleetTop = topCells * cellHeight;
3363
- drawGlow2(data, width, height, width * 0.5, height * 0.5, Math.min(width, height) * 0.4, LIME, 0.022 + load * 0.03);
3364
- drawGlow2(data, width, height, width * 0.18, height * 0.22, Math.min(width, height) * 0.28, CYAN, 0.018 + load * 0.012);
3365
- drawGlow2(data, width, height, width * 0.82, height * 0.78, Math.min(width, height) * 0.22, MAGENTA, 0.012 + load * 0.008);
3366
- let droneTotal = 0;
3367
- for (const line2 of rows)
3368
- for (const char of line2)
3369
- if (char === "@" || char === "$" || char === "%")
3370
- droneTotal += 1;
3371
- const litTarget = Math.max(0, Math.min(droneTotal, activeRuns));
3372
- let droneIndex = 0;
3373
- for (let row = 0;row < rows.length; row += 1) {
3374
- const line2 = rows[row];
3375
- for (let col = 0;col < line2.length; col += 1) {
3376
- const char = line2[col];
3377
- if (char === " ")
3378
- continue;
3379
- const x = fleetLeft + col * cellWidth;
3380
- const y = fleetTop + row * cellHeight;
3381
- const color = charColor(char, row, rows.length);
3382
- const isDrone = char === "@" || char === "$" || char === "%";
3383
- const lit = isDrone && droneIndex < litTarget;
3384
- drawAsciiGlyph(data, width, height, x + cellWidth * 0.07, y + cellHeight * 0.05, cellWidth, cellHeight, char, color, lit ? 0.72 : 0.46);
3385
- if (isDrone) {
3386
- droneIndex += 1;
3387
- drawGlow2(data, width, height, x + cellWidth * 0.5, y + cellHeight * 0.5, Math.max(cellWidth, cellHeight) * 0.95, color, lit ? 0.044 : 0.014);
3388
- }
3389
- }
3390
- }
3391
- }
3392
- function applyStaticAtmosphere(data, width, height, load) {
3393
- const cx = width * 0.5;
3394
- const cy = height * 0.42;
3395
- const radius = Math.max(width, height) * 0.78;
3396
- const minVignette = 0.1 - load * 0.02;
3397
- for (let y = 0;y < height; y += 1) {
3398
- for (let x = 0;x < width; x += 1) {
3399
- const distance = Math.hypot(x - cx, y - cy) / radius;
3400
- const alpha = Math.max(minVignette, Math.min(0.82, 0.12 + Math.pow(distance, 1.65) * 0.62));
3401
- blendOver(data, (y * width + x) * 4, BG, alpha);
3402
- }
3403
- }
3404
- drawGlow2(data, width, height, width * 0.82, height * 0.04, Math.min(width, height) * 0.2, LIME, 0.024 + load * 0.012);
3405
- drawGlow2(data, width, height, width * 0.12, height * 0.18, Math.min(width, height) * 0.16, CYAN, 0.018 + load * 0.008);
3406
- }
3407
- function makeCrcTable() {
3408
- const table = new Uint32Array(256);
3409
- for (let n = 0;n < 256; n += 1) {
3410
- let c = n;
3411
- for (let k = 0;k < 8; k += 1)
3412
- c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
3413
- table[n] = c >>> 0;
3414
- }
3415
- return table;
3416
- }
3417
- function crc32(bytes) {
3418
- let c = 4294967295;
3419
- for (let i = 0;i < bytes.length; i += 1)
3420
- c = CRC_TABLE[(c ^ bytes[i]) & 255] ^ c >>> 8;
3421
- return (c ^ 4294967295) >>> 0;
3422
- }
3423
- function chunk2(type, data) {
3424
- const out = new Uint8Array(12 + data.length);
3425
- const view = new DataView(out.buffer);
3426
- view.setUint32(0, data.length);
3427
- out[4] = type.charCodeAt(0);
3428
- out[5] = type.charCodeAt(1);
3429
- out[6] = type.charCodeAt(2);
3430
- out[7] = type.charCodeAt(3);
3431
- out.set(data, 8);
3432
- view.setUint32(8 + data.length, crc32(out.subarray(4, 8 + data.length)));
3433
- return out;
3206
+ // packages/cli/src/app-opentui/render/hover.ts
3207
+ var hoveredId;
3208
+ var listeners = new Set;
3209
+ function getHoveredSelectableId() {
3210
+ return hoveredId;
3434
3211
  }
3435
- function encodePng(width, height, rgba) {
3436
- const ihdr = new Uint8Array(13);
3437
- const ihdrView = new DataView(ihdr.buffer);
3438
- ihdrView.setUint32(0, width);
3439
- ihdrView.setUint32(4, height);
3440
- ihdr[8] = 8;
3441
- ihdr[9] = 6;
3442
- ihdr[10] = 0;
3443
- ihdr[11] = 0;
3444
- ihdr[12] = 0;
3445
- const stride = width * 4;
3446
- const raw = new Uint8Array((stride + 1) * height);
3447
- for (let y = 0;y < height; y += 1) {
3448
- const rawOffset = y * (stride + 1);
3449
- raw[rawOffset] = 0;
3450
- raw.set(rgba.subarray(y * stride, y * stride + stride), rawOffset + 1);
3451
- }
3452
- const compressed = deflateSync(raw, { level: ZLIB_LEVEL });
3453
- return Buffer.concat([
3454
- Buffer.from(PNG_SIGNATURE),
3455
- Buffer.from(chunk2("IHDR", ihdr)),
3456
- Buffer.from(chunk2("IDAT", compressed)),
3457
- Buffer.from(chunk2("IEND", new Uint8Array(0)))
3458
- ]);
3459
- }
3460
- function kittyImageSequence(base64, options) {
3461
- const params = [`a=T`, `f=${options.format}`, `q=2`, `C=1`, `c=${options.columns}`, `r=${options.rows}`, `i=${options.imageId}`, `z=${options.zIndex}`];
3462
- if (options.format === 32) {
3463
- params.push(`s=${options.pixelWidth ?? 1}`, `v=${options.pixelHeight ?? 1}`);
3464
- if (options.compressed)
3465
- params.push("o=z");
3466
- }
3467
- if (base64.length <= KITTY_CHUNK_SIZE)
3468
- return `\x1B_G${params.join(",")};${base64}\x1B\\`;
3469
- const chunks = [];
3470
- for (let offset = 0;offset < base64.length; offset += KITTY_CHUNK_SIZE) {
3471
- const part = base64.slice(offset, offset + KITTY_CHUNK_SIZE);
3472
- const last = offset + KITTY_CHUNK_SIZE >= base64.length;
3473
- chunks.push(`\x1B_G${offset === 0 ? `${params.join(",")},` : ""}m=${last ? 0 : 1};${part}\x1B\\`);
3474
- }
3475
- return chunks.join("");
3476
- }
3477
- function clampByte(value) {
3478
- return Math.max(0, Math.min(255, Math.round(value)));
3479
- }
3480
- function blendOver(data, index, color, alphaScale = 1) {
3481
- const alpha = color[3] / 255 * alphaScale;
3482
- const inv = 1 - alpha;
3483
- data[index] = clampByte(color[0] * alpha + data[index] * inv);
3484
- data[index + 1] = clampByte(color[1] * alpha + data[index + 1] * inv);
3485
- data[index + 2] = clampByte(color[2] * alpha + data[index + 2] * inv);
3486
- data[index + 3] = 255;
3487
- }
3488
- function drawDisc(data, width, height, cx, cy, radius, color, alpha = 1) {
3489
- const minX = Math.max(0, Math.floor(cx - radius));
3490
- const maxX = Math.min(width - 1, Math.ceil(cx + radius));
3491
- const minY = Math.max(0, Math.floor(cy - radius));
3492
- const maxY = Math.min(height - 1, Math.ceil(cy + radius));
3493
- for (let y = minY;y <= maxY; y += 1) {
3494
- for (let x = minX;x <= maxX; x += 1) {
3495
- const dx = x + 0.5 - cx;
3496
- const dy = y + 0.5 - cy;
3497
- const d = Math.sqrt(dx * dx + dy * dy);
3498
- if (d > radius)
3499
- continue;
3500
- const edge = Math.max(0, Math.min(1, radius - d));
3501
- blendOver(data, (y * width + x) * 4, color, alpha * Math.min(1, 0.35 + edge));
3502
- }
3503
- }
3504
- }
3505
- function drawGlow2(data, width, height, cx, cy, radius, color, alpha = 0.18) {
3506
- const minX = Math.max(0, Math.floor(cx - radius));
3507
- const maxX = Math.min(width - 1, Math.ceil(cx + radius));
3508
- const minY = Math.max(0, Math.floor(cy - radius));
3509
- const maxY = Math.min(height - 1, Math.ceil(cy + radius));
3510
- for (let y = minY;y <= maxY; y += 1) {
3511
- for (let x = minX;x <= maxX; x += 1) {
3512
- const dx = x + 0.5 - cx;
3513
- const dy = y + 0.5 - cy;
3514
- const d = Math.sqrt(dx * dx + dy * dy);
3515
- if (d > radius)
3516
- continue;
3517
- const falloff = Math.pow(1 - d / Math.max(1, radius), GLOW_FALLOFF_EXP_IMAGE);
3518
- blendOver(data, (y * width + x) * 4, color, alpha * falloff);
3519
- }
3520
- }
3212
+ function isHovered(id) {
3213
+ return id !== undefined && id === hoveredId;
3521
3214
  }
3522
- function drawLine2(data, width, height, x0, y0, x1, y1, color, alpha = 0.5, thickness = 1.4) {
3523
- const dx = x1 - x0;
3524
- const dy = y1 - y0;
3525
- const steps = Math.max(1, Math.ceil(Math.hypot(dx, dy) * 1.2));
3526
- for (let i = 0;i <= steps; i += 1) {
3527
- const t2 = i / steps;
3528
- drawDisc(data, width, height, x0 + dx * t2, y0 + dy * t2, thickness, color, alpha);
3529
- }
3530
- }
3531
- function roundRectAlpha(px, py, x, y, w, h, r) {
3532
- const rx = Math.max(x + r, Math.min(px, x + w - r));
3533
- const ry = Math.max(y + r, Math.min(py, y + h - r));
3534
- const dx = px - rx;
3535
- const dy = py - ry;
3536
- const dist = Math.sqrt(dx * dx + dy * dy);
3537
- return Math.max(0, Math.min(1, r + 0.75 - dist));
3538
- }
3539
- function panelRect(layout, panel, size) {
3540
- return panelPixelPlacement(layout, panel, size);
3541
- }
3542
- function downsampleRegion(data, canvasW, canvasH, rect, scale) {
3543
- const w = Math.max(1, Math.ceil(rect.width / scale));
3544
- const h = Math.max(1, Math.ceil(rect.height / scale));
3545
- const out = new Uint8ClampedArray(w * h * 3);
3546
- for (let y = 0;y < h; y += 1) {
3547
- for (let x = 0;x < w; x += 1) {
3548
- let r = 0, g = 0, b = 0, count = 0;
3549
- const sx0 = rect.left + x * scale;
3550
- const sy0 = rect.top + y * scale;
3551
- for (let yy = 0;yy < scale; yy += 1) {
3552
- const sy = sy0 + yy;
3553
- if (sy < 0 || sy >= canvasH)
3554
- continue;
3555
- for (let xx = 0;xx < scale; xx += 1) {
3556
- const sx = sx0 + xx;
3557
- if (sx < 0 || sx >= canvasW)
3558
- continue;
3559
- const i = (sy * canvasW + sx) * 4;
3560
- r += data[i];
3561
- g += data[i + 1];
3562
- b += data[i + 2];
3563
- count += 1;
3564
- }
3565
- }
3566
- const oi = (y * w + x) * 3;
3567
- out[oi] = count ? r / count : BG[0];
3568
- out[oi + 1] = count ? g / count : BG[1];
3569
- out[oi + 2] = count ? b / count : BG[2];
3570
- }
3571
- }
3572
- return { pixels: out, width: w, height: h };
3573
- }
3574
- function blurRgb(buffer, width, height, radius) {
3575
- const tmp = new Uint8ClampedArray(buffer.length);
3576
- const out = new Uint8ClampedArray(buffer.length);
3577
- const window = radius * 2 + 1;
3578
- for (let y = 0;y < height; y += 1) {
3579
- let r = 0, g = 0, b = 0;
3580
- for (let x = -radius;x <= radius; x += 1) {
3581
- const sx = Math.max(0, Math.min(width - 1, x));
3582
- const i = (y * width + sx) * 3;
3583
- r += buffer[i];
3584
- g += buffer[i + 1];
3585
- b += buffer[i + 2];
3586
- }
3587
- for (let x = 0;x < width; x += 1) {
3588
- const oi = (y * width + x) * 3;
3589
- tmp[oi] = r / window;
3590
- tmp[oi + 1] = g / window;
3591
- tmp[oi + 2] = b / window;
3592
- const removeX = Math.max(0, x - radius);
3593
- const addX = Math.min(width - 1, x + radius + 1);
3594
- const ri = (y * width + removeX) * 3;
3595
- const ai = (y * width + addX) * 3;
3596
- r += tmp.length ? buffer[ai] - buffer[ri] : 0;
3597
- g += tmp.length ? buffer[ai + 1] - buffer[ri + 1] : 0;
3598
- b += tmp.length ? buffer[ai + 2] - buffer[ri + 2] : 0;
3599
- }
3600
- }
3601
- for (let x = 0;x < width; x += 1) {
3602
- let r = 0, g = 0, b = 0;
3603
- for (let y = -radius;y <= radius; y += 1) {
3604
- const sy = Math.max(0, Math.min(height - 1, y));
3605
- const i = (sy * width + x) * 3;
3606
- r += tmp[i];
3607
- g += tmp[i + 1];
3608
- b += tmp[i + 2];
3609
- }
3610
- for (let y = 0;y < height; y += 1) {
3611
- const oi = (y * width + x) * 3;
3612
- out[oi] = r / window;
3613
- out[oi + 1] = g / window;
3614
- out[oi + 2] = b / window;
3615
- const removeY = Math.max(0, y - radius);
3616
- const addY = Math.min(height - 1, y + radius + 1);
3617
- const ri = (removeY * width + x) * 3;
3618
- const ai = (addY * width + x) * 3;
3619
- r += tmp[ai] - tmp[ri];
3620
- g += tmp[ai + 1] - tmp[ri + 1];
3621
- b += tmp[ai + 2] - tmp[ri + 2];
3622
- }
3623
- }
3624
- return out;
3625
- }
3626
- function panelMaskKey(width, height, rect, headerPx, blurScale) {
3627
- return `${width}x${height}:${rect.left},${rect.top},${rect.width},${rect.height}:${Math.round(headerPx * 100) / 100}:${blurScale}`;
3628
- }
3629
- function getPanelMask(width, height, rect, headerPx, blurScale) {
3630
- const key = panelMaskKey(width, height, rect, headerPx, blurScale);
3631
- const cached = panelMaskCache.get(key);
3632
- if (cached)
3633
- return cached;
3634
- 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)));
3635
- const sampledWidth = Math.max(1, Math.ceil(rect.width / blurScale));
3636
- const sampledHeight = Math.max(1, Math.ceil(rect.height / blurScale));
3637
- const x0 = Math.max(0, rect.left);
3638
- const y0 = Math.max(0, rect.top);
3639
- const x1 = Math.min(width - 1, rect.left + rect.width - 1);
3640
- const y1 = Math.min(height - 1, rect.top + rect.height - 1);
3641
- const indices = [];
3642
- const sampleIndices = [];
3643
- const panelAlpha = [];
3644
- const edgeAlpha = [];
3645
- const headerFlags = [];
3646
- const dividerIndices = [];
3647
- for (let y = y0;y <= y1; y += 1) {
3648
- for (let x = x0;x <= x1; x += 1) {
3649
- const a = roundRectAlpha(x + 0.5, y + 0.5, rect.left, rect.top, rect.width, rect.height, radius);
3650
- if (a <= 0)
3651
- continue;
3652
- 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));
3653
- const sx = Math.max(0, Math.min(sampledWidth - 1, Math.floor((x - rect.left) / blurScale)));
3654
- const sy = Math.max(0, Math.min(sampledHeight - 1, Math.floor((y - rect.top) / blurScale)));
3655
- indices.push((y * width + x) * 4);
3656
- sampleIndices.push((sy * sampledWidth + sx) * 3);
3657
- panelAlpha.push(a);
3658
- edgeAlpha.push(edge < 0.96 ? Math.min(1, (1 - edge) * 0.7) : 0);
3659
- headerFlags.push(y < rect.top + headerPx ? 1 : 0);
3660
- }
3661
- }
3662
- const dividerY = Math.round(rect.top + headerPx);
3663
- if (dividerY > y0 && dividerY < y1) {
3664
- for (let x = x0 + radius;x <= x1 - radius; x += 1) {
3665
- dividerIndices.push((dividerY * width + x) * 4);
3666
- }
3667
- }
3668
- const mask = {
3669
- key,
3670
- indices: Uint32Array.from(indices),
3671
- sampleIndices: Uint32Array.from(sampleIndices),
3672
- panelAlpha: Float32Array.from(panelAlpha),
3673
- edgeAlpha: Float32Array.from(edgeAlpha),
3674
- headerFlags: Uint8Array.from(headerFlags),
3675
- dividerIndices: Uint32Array.from(dividerIndices)
3676
- };
3677
- panelMaskCache.set(key, mask);
3678
- while (panelMaskCache.size > PANEL_MASK_CACHE_LIMIT) {
3679
- const oldest = panelMaskCache.keys().next().value;
3680
- if (!oldest)
3681
- break;
3682
- panelMaskCache.delete(oldest);
3683
- }
3684
- return mask;
3685
- }
3686
- function compositePanel(data, width, height, rect, headerPx) {
3687
- const blurScale = PANEL_BLUR_SCALE;
3688
- const sampled = downsampleRegion(data, width, height, rect, blurScale);
3689
- 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)));
3690
- const blurred = blurRgb(sampled.pixels, sampled.width, sampled.height, blurRadius);
3691
- const mask = getPanelMask(width, height, rect, headerPx, blurScale);
3692
- for (let i = 0;i < mask.indices.length; i += 1) {
3693
- const idx = mask.indices[i];
3694
- const sampleIdx = mask.sampleIndices[i];
3695
- data[idx] = blurred[sampleIdx];
3696
- data[idx + 1] = blurred[sampleIdx + 1];
3697
- data[idx + 2] = blurred[sampleIdx + 2];
3698
- data[idx + 3] = 255;
3699
- blendOver(data, idx, mask.headerFlags[i] ? PANEL_HEADER : PANEL, mask.panelAlpha[i]);
3700
- const edge = mask.edgeAlpha[i];
3701
- if (edge > 0)
3702
- blendOver(data, idx, PANEL_LINE2, edge);
3703
- }
3704
- for (let i = 0;i < mask.dividerIndices.length; i += 1) {
3705
- blendOver(data, mask.dividerIndices[i], PANEL_LINE2, 0.28);
3706
- }
3707
- }
3708
- var RIG_GLYPH_STROKES = {
3709
- 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]],
3710
- I: [[0.5, 0, 0.5, 1], [0.18, 0, 0.82, 0], [0.18, 1, 0.82, 1]],
3711
- 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]]
3712
- };
3713
- function drawBigRigWordmark(data, width, height, layout, load) {
3714
- const sideBySide = layout.width >= FLEET_GRID_WIDTH * 1.9;
3715
- if (!sideBySide)
3215
+ function setHoveredSelectableId(id) {
3216
+ if (hoveredId === id)
3716
3217
  return;
3717
- const letters = "RIG";
3718
- const min = Math.min(width, height);
3719
- const glyphH = height * 0.42;
3720
- const glyphW = width * 0.13;
3721
- const gap = glyphW * 0.3;
3722
- const totalW = letters.length * glyphW + (letters.length - 1) * gap;
3723
- const centerX = width * 0.71;
3724
- const startX = centerX - totalW / 2;
3725
- const top = (height - glyphH) / 2;
3726
- const stroke = Math.max(2, min * 0.013);
3727
- drawGlow2(data, width, height, centerX, top + glyphH / 2, Math.max(glyphW, glyphH) * 0.9, LIME, 0.022 + load * 0.02);
3728
- const strokeAlpha = 0.1 + load * 0.06;
3729
- letters.split("").forEach((letter, index) => {
3730
- const segments = RIG_GLYPH_STROKES[letter];
3731
- if (!segments)
3732
- return;
3733
- const ox = startX + index * (glyphW + gap);
3734
- for (const [x0, y0, x1, y1] of segments) {
3735
- drawLine2(data, width, height, ox + x0 * glyphW, top + y0 * glyphH, ox + x1 * glyphW, top + y1 * glyphH, LIME_DIM, strokeAlpha, stroke);
3736
- }
3737
- });
3218
+ hoveredId = id;
3219
+ for (const listener of listeners)
3220
+ listener();
3738
3221
  }
3739
- function drawBackground(data, width, height, _tick, scene, layout, load, activeRuns) {
3740
- fillRgba(data, BG, BG_WORD);
3741
- drawStaticAsciiFleetBackground(data, width, height, scene, layout, load, activeRuns);
3742
- drawBigRigWordmark(data, width, height, layout, load);
3743
- applyStaticAtmosphere(data, width, height, load);
3744
- }
3745
- function imageVisualFrameKey(input) {
3746
- const width = Math.max(1, Math.floor(input.pixelSize.width));
3747
- const height = Math.max(1, Math.floor(input.pixelSize.height));
3748
- const layer = "layer" in input ? input.layer ?? "full" : "full";
3749
- const load = loadBucket(input.load);
3750
- 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("|")}`;
3751
- }
3752
- function encodeKittyFrame(width, height, data, input) {
3753
- if (input.transport === "png") {
3754
- const png = encodePng(width, height, data);
3755
- return kittyImageSequence(png.toString("base64"), { columns: input.layout.width, rows: input.layout.height, imageId: input.imageId, zIndex: input.zIndex, format: 100 });
3756
- }
3757
- const compressed = deflateSync(data, { level: ZLIB_LEVEL });
3758
- return kittyImageSequence(compressed.toString("base64"), {
3759
- columns: input.layout.width,
3760
- rows: input.layout.height,
3761
- imageId: input.imageId,
3762
- zIndex: input.zIndex,
3763
- format: 32,
3764
- pixelWidth: width,
3765
- pixelHeight: height,
3766
- compressed: true
3767
- });
3222
+ function clearHoveredSelectableId(id) {
3223
+ if (id !== undefined && hoveredId === id)
3224
+ setHoveredSelectableId(undefined);
3768
3225
  }
3769
- function cropRgba(data, sourceWidth, rect) {
3770
- const out = new Uint8ClampedArray(rect.width * rect.height * 4);
3771
- for (let y = 0;y < rect.height; y += 1) {
3772
- const sourceStart = ((rect.top + y) * sourceWidth + rect.left) * 4;
3773
- out.set(data.subarray(sourceStart, sourceStart + rect.width * 4), y * rect.width * 4);
3774
- }
3775
- return out;
3776
- }
3777
- function encodePanelPatch(data, sourceWidth, placement, imageId, zIndex) {
3778
- const patch = cropRgba(data, sourceWidth, placement);
3779
- const png = encodePng(placement.width, placement.height, patch);
3780
- const sequence = kittyImageSequence(png.toString("base64"), {
3781
- columns: Math.max(1, Math.round(placement.widthCells)),
3782
- rows: Math.max(1, Math.round(placement.heightCells)),
3783
- imageId,
3784
- zIndex,
3785
- format: 100
3786
- });
3787
- return `\x1B[${Math.max(1, Math.round(placement.topCells) + 1)};${Math.max(1, Math.round(placement.leftCells) + 1)}H${sequence}`;
3788
- }
3789
- function renderImageVisualFrame(input) {
3790
- const width = Math.max(1, Math.floor(input.pixelSize.width));
3791
- const height = Math.max(1, Math.floor(input.pixelSize.height));
3792
- const byteCount = width * height * 4;
3793
- if (byteCount > MAX_IMAGE_BYTES)
3794
- return null;
3795
- const layer = input.layer ?? "full";
3796
- const data = new Uint8ClampedArray(byteCount);
3797
- const load = loadBucket(input.load) / LOAD_BUCKETS;
3798
- const activeRuns = Math.max(0, Math.floor(input.activeRuns ?? 0));
3799
- drawBackground(data, width, height, 0, input.scene, input.layout, load, activeRuns);
3800
- if (layer === "split") {
3801
- const sequences = [encodeKittyFrame(width, height, data, input)];
3802
- let panelIndex = 0;
3803
- for (const panel of input.panels) {
3804
- if (panel.chrome !== "ad-terminal")
3805
- continue;
3806
- const placement = panelPixelPlacement(input.layout, panel, { width, height });
3807
- if (!placement)
3808
- continue;
3809
- const cellH = height / input.layout.height;
3810
- const headerPx = Math.max(cellH * 3, (panel.headerHeight ?? 3) * cellH);
3811
- compositePanel(data, width, height, placement, headerPx);
3812
- const imageId = input.imageIds?.[panelIndex] ?? (input.imageId + 1000 + panelIndex >>> 0 || panelIndex + 1);
3813
- sequences.push(encodePanelPatch(data, width, placement, imageId, -12));
3814
- panelIndex += 1;
3815
- }
3816
- return { key: imageVisualFrameKey(input), sequence: sequences.join("") };
3817
- }
3818
- if (layer === "panel-chrome")
3819
- return { key: imageVisualFrameKey(input), sequence: "" };
3820
- if (layer === "background") {
3821
- return { key: imageVisualFrameKey(input), sequence: encodeKittyFrame(width, height, data, input) };
3822
- }
3823
- if (layer === "panel-patches") {
3824
- const sequences = [];
3825
- let panelIndex = 0;
3826
- for (const panel of input.panels) {
3827
- if (panel.chrome !== "ad-terminal")
3828
- continue;
3829
- const placement = panelPixelPlacement(input.layout, panel, { width, height });
3830
- if (!placement)
3831
- continue;
3832
- const cellH = height / input.layout.height;
3833
- const headerPx = Math.max(cellH * 3, (panel.headerHeight ?? 3) * cellH);
3834
- compositePanel(data, width, height, placement, headerPx);
3835
- const imageId = input.imageIds?.[panelIndex] ?? (input.imageId + 1000 + panelIndex >>> 0 || panelIndex + 1);
3836
- sequences.push(encodePanelPatch(data, width, placement, imageId, input.zIndex));
3837
- panelIndex += 1;
3838
- }
3839
- return { key: imageVisualFrameKey(input), sequence: sequences.join("") };
3840
- }
3841
- for (const panel of input.panels) {
3842
- if (panel.chrome !== "ad-terminal")
3843
- continue;
3844
- const rect = panelRect(input.layout, panel, { width, height });
3845
- if (!rect)
3846
- continue;
3847
- const cellH = height / input.layout.height;
3848
- const headerPx = Math.max(cellH * 3, (panel.headerHeight ?? 3) * cellH);
3849
- compositePanel(data, width, height, rect, headerPx);
3850
- }
3851
- return { key: imageVisualFrameKey(input), sequence: encodeKittyFrame(width, height, data, input) };
3852
- }
3853
- function shouldRenderImagesInline() {
3854
- return process.env.RIG_OPENTUI_IMAGE_WORKER === "0" || import.meta.url.includes("$bunfs");
3855
- }
3856
- function createImageWorker() {
3857
- if (shouldRenderImagesInline())
3858
- return null;
3859
- const isTypeScriptRuntime = import.meta.url.endsWith(".ts");
3860
- const workerUrl = new URL(isTypeScriptRuntime ? "./image-visual-layer-worker.ts" : "./image-visual-layer-worker.js", import.meta.url);
3861
- try {
3862
- return new Worker(workerUrl);
3863
- } catch {
3864
- if (typeof process.versions.bun === "string") {
3865
- try {
3866
- return new Worker("./src/app-opentui/render/image-visual-layer-worker.ts");
3867
- } catch {}
3868
- }
3869
- return null;
3870
- }
3871
- }
3872
- var RECENT_WRITTEN_LIMIT = 24;
3873
- var DEFAULT_IMAGE_OUTPUT_BACKLOG_BYTES = 256 * 1024;
3874
- var DEFAULT_IMAGE_OUTPUT_BYTES_PER_SECOND = 850 * 1024;
3875
- function imageOutputBacklogBytes() {
3876
- const raw = Number.parseInt(process.env.RIG_OPENTUI_IMAGE_BACKLOG_BYTES ?? "", 10);
3877
- if (Number.isFinite(raw) && raw > 0)
3878
- return Math.max(64 * 1024, Math.min(2 * 1024 * 1024, raw));
3879
- return DEFAULT_IMAGE_OUTPUT_BACKLOG_BYTES;
3880
- }
3881
- function imageOutputBytesPerSecond() {
3882
- const raw = Number.parseInt(process.env.RIG_OPENTUI_IMAGE_BYTES_PER_SECOND ?? "", 10);
3883
- if (Number.isFinite(raw) && raw > 0)
3884
- return Math.max(128 * 1024, Math.min(2 * 1024 * 1024, raw));
3885
- return DEFAULT_IMAGE_OUTPUT_BYTES_PER_SECOND;
3886
- }
3887
- function pendingLayerKey(key) {
3888
- return key.split(":", 1)[0] || "full";
3889
- }
3890
-
3891
- class ImageVisualLayer {
3892
- imageId = allocateImageId();
3893
- panelImageIds = Array.from({ length: 8 }, () => allocateImageId());
3894
- panelChromeImageIds = Array.from({ length: 8 }, () => allocateImageId());
3895
- stdout;
3896
- onFrameReady;
3897
- protocol;
3898
- transport = imageTransport();
3899
- maxOutputBacklogBytes = imageOutputBacklogBytes();
3900
- outputBytesPerSecond = imageOutputBytesPerSecond();
3901
- outputBudgetBytes = imageOutputBytesPerSecond();
3902
- lastBudgetAtMs = Date.now();
3903
- motionSlot = this.createSlot();
3904
- workersStarted = false;
3905
- pendingFrames = new Map;
3906
- recentWrittenKeys = [];
3907
- imagesVisible = false;
3908
- flushPaused = false;
3909
- requestId = 0;
3910
- lastBackgroundKey = "";
3911
- fatalWorkerError = null;
3912
- destroyed = false;
3913
- constructor(stdout = process.stdout, onFrameReady) {
3914
- this.stdout = stdout;
3915
- this.onFrameReady = onFrameReady;
3916
- this.protocol = getRigTerminalCapabilities().imageProtocol;
3917
- }
3918
- get enabled() {
3919
- return this.protocol === "kitty";
3920
- }
3921
- clear() {
3922
- if (this.enabled) {
3923
- if (this.imagesVisible)
3924
- this.stdout.write([this.imageId, ...this.panelImageIds, ...this.panelChromeImageIds].map((id) => deleteKittyImage(id)).join("") + "\x1B[?25h");
3925
- this.imagesVisible = false;
3926
- this.pendingFrames.clear();
3927
- this.recentWrittenKeys.length = 0;
3928
- this.resetSlot(this.motionSlot);
3929
- this.lastBackgroundKey = "";
3930
- this.outputBudgetBytes = this.outputBytesPerSecond;
3931
- this.lastBudgetAtMs = Date.now();
3932
- this.motionSlot.clearedThroughRequestId = this.requestId;
3933
- }
3934
- }
3935
- destroy() {
3936
- if (this.destroyed)
3937
- return;
3938
- this.clear();
3939
- this.destroyed = true;
3940
- const workers = [this.motionSlot.worker];
3941
- this.motionSlot.worker = null;
3942
- for (const worker of workers)
3943
- if (worker)
3944
- worker.terminate().catch(() => {
3945
- return;
3946
- });
3947
- }
3948
- setFlushPaused(paused) {
3949
- this.flushPaused = paused;
3950
- }
3951
- isReady(input) {
3952
- if (!this.enabled || this.destroyed)
3953
- return true;
3954
- this.throwIfFatal();
3955
- const splitKey = imageVisualFrameKey({ ...input, tick: 0, layer: "split" });
3956
- return this.recentWrittenKeys.includes(splitKey) || Array.from(this.pendingFrames.values()).some((frame) => frame.key === splitKey);
3957
- }
3958
- render(input) {
3959
- if (!this.enabled || this.destroyed)
3960
- return;
3961
- this.throwIfFatal();
3962
- if (this.transport !== "png")
3963
- this.failWorker("renderer", new Error("OpenTUI image visuals require PNG transport for static-background rendering."));
3964
- this.ensureWorkersStarted();
3965
- const width = Math.max(1, Math.floor(input.pixelSize.width));
3966
- const height = Math.max(1, Math.floor(input.pixelSize.height));
3967
- if (width * height * 4 > MAX_IMAGE_BYTES)
3968
- return;
3969
- if (this.isOutputBackedUp())
3970
- return;
3971
- this.refillOutputBudget();
3972
- const splitInput = { ...input, tick: 0, layer: "split" };
3973
- const splitKey = imageVisualFrameKey(splitInput);
3974
- if (input.force || splitKey !== this.lastBackgroundKey) {
3975
- this.scheduleRender(this.motionSlot, splitInput, this.imageId, -30, input.force, this.panelImageIds);
3976
- this.lastBackgroundKey = splitKey;
3977
- }
3978
- }
3979
- flush() {
3980
- this.throwIfFatal();
3981
- if (this.flushPaused)
3982
- return false;
3983
- if (this.pendingFrames.size === 0)
3984
- return false;
3985
- this.refillOutputBudget();
3986
- if (this.isOutputBackedUp()) {
3987
- const retainedSplit = this.pendingFrames.get("split");
3988
- this.pendingFrames.clear();
3989
- if (retainedSplit)
3990
- this.pendingFrames.set("split", retainedSplit);
3991
- return false;
3992
- }
3993
- const framePriority = (frame) => {
3994
- const layer = pendingLayerKey(frame.key);
3995
- if (layer === "split")
3996
- return 1;
3997
- return 4;
3998
- };
3999
- const frames = Array.from(this.pendingFrames.values()).sort((a, b) => framePriority(a) - framePriority(b));
4000
- const prefix = "\x1B[?25l\x1B7\x1B[s\x1B[H";
4001
- const suffix = "\x1B[u\x1B8";
4002
- const selected = [];
4003
- const retained = new Map;
4004
- let sequence = "";
4005
- let byteLength = Buffer.byteLength(prefix + suffix);
4006
- for (const frame of frames) {
4007
- const candidate = sequence + frame.sequence;
4008
- const candidateBytes = Buffer.byteLength(prefix + candidate + suffix);
4009
- const layer = pendingLayerKey(frame.key);
4010
- const allowOneStaticFrame = selected.length === 0 && layer === "split";
4011
- if (candidateBytes <= this.outputBudgetBytes || allowOneStaticFrame) {
4012
- selected.push(frame);
4013
- sequence = candidate;
4014
- byteLength = candidateBytes;
4015
- } else if (layer === "split") {
4016
- retained.set(layer, frame);
4017
- }
4018
- }
4019
- this.pendingFrames.clear();
4020
- for (const [layer, frame] of retained)
4021
- this.pendingFrames.set(layer, frame);
4022
- if (!sequence || selected.length === 0)
4023
- return false;
4024
- const payload = `${prefix}${sequence}${suffix}`;
4025
- for (const frame of selected)
4026
- this.markWritten(frame.key);
4027
- this.outputBudgetBytes -= byteLength;
4028
- this.stdout.cork?.();
4029
- const accepted = this.stdout.write(payload);
4030
- this.stdout.uncork?.();
4031
- this.imagesVisible = true;
4032
- return accepted;
4033
- }
4034
- createSlot() {
4035
- return { worker: null, busy: false, queuedRequest: null, inFlightKey: "", queuedKey: "", clearedThroughRequestId: 0, latestRequestId: 0 };
4036
- }
4037
- resetSlot(slot) {
4038
- slot.busy = false;
4039
- slot.queuedRequest = null;
4040
- slot.inFlightKey = "";
4041
- slot.queuedKey = "";
4042
- }
4043
- markWritten(key) {
4044
- this.recentWrittenKeys.push(key);
4045
- while (this.recentWrittenKeys.length > RECENT_WRITTEN_LIMIT)
4046
- this.recentWrittenKeys.shift();
4047
- }
4048
- refillOutputBudget() {
4049
- const now = Date.now();
4050
- const elapsedMs = Math.max(0, now - this.lastBudgetAtMs);
4051
- this.lastBudgetAtMs = now;
4052
- this.outputBudgetBytes = Math.min(this.outputBytesPerSecond, this.outputBudgetBytes + elapsedMs / 1000 * this.outputBytesPerSecond);
4053
- }
4054
- isOutputBackedUp() {
4055
- return Boolean(this.stdout.writableNeedDrain) || this.stdout.writableLength > this.maxOutputBacklogBytes;
4056
- }
4057
- stageFrame(frame) {
4058
- this.pendingFrames.set(pendingLayerKey(frame.key), frame);
4059
- }
4060
- isKnownKey(slot, key) {
4061
- return this.recentWrittenKeys.includes(key) || Array.from(this.pendingFrames.values()).some((frame) => frame.key === key) || key === slot.inFlightKey || key === slot.queuedKey;
4062
- }
4063
- ensureWorkersStarted() {
4064
- if (this.workersStarted)
4065
- return;
4066
- this.workersStarted = true;
4067
- this.startWorker(this.motionSlot, "static-visuals");
4068
- }
4069
- failWorker(name, error) {
4070
- const detail = error instanceof Error ? error.message : String(error);
4071
- this.fatalWorkerError ??= new Error(`OpenTUI image worker ${name} failed: ${detail}`);
4072
- throw this.fatalWorkerError;
4073
- }
4074
- throwIfFatal() {
4075
- if (this.fatalWorkerError)
4076
- throw this.fatalWorkerError;
4077
- }
4078
- scheduleRender(slot, input, imageId, zIndex, force, imageIds) {
4079
- this.throwIfFatal();
4080
- const key = imageVisualFrameKey(input);
4081
- if (!force && this.isKnownKey(slot, key))
4082
- return;
4083
- const request = {
4084
- ...input,
4085
- imageId,
4086
- ...imageIds ? { imageIds } : {},
4087
- zIndex,
4088
- transport: this.transport,
4089
- requestId: ++this.requestId,
4090
- key
4091
- };
4092
- slot.latestRequestId = request.requestId;
4093
- if (!slot.worker) {
4094
- this.renderInline(slot, request);
4095
- return;
4096
- }
4097
- this.enqueueWorkerRender(slot, request);
4098
- }
4099
- startWorker(slot, name) {
4100
- const worker = createImageWorker();
4101
- if (!worker)
4102
- return;
4103
- slot.worker = worker;
4104
- worker.on("message", (message) => this.handleWorkerMessage(slot, message));
4105
- worker.once("error", (error) => {
4106
- if (!this.destroyed)
4107
- this.fatalWorkerError ??= new Error(`OpenTUI image worker ${name} failed: ${error instanceof Error ? error.message : String(error)}`);
4108
- });
4109
- worker.once("exit", (code) => {
4110
- if (!this.destroyed)
4111
- this.fatalWorkerError ??= new Error(`OpenTUI image worker ${name} exited unexpectedly with code ${code}.`);
4112
- });
4113
- }
4114
- renderInline(slot, request) {
4115
- try {
4116
- const result = renderImageVisualFrame(request);
4117
- if (result?.sequence && !this.isKnownKey(slot, result.key) && (!this.isOutputBackedUp() || pendingLayerKey(result.key) === "split")) {
4118
- this.stageFrame({ key: result.key, sequence: result.sequence });
4119
- this.onFrameReady?.();
4120
- }
4121
- } catch (error) {
4122
- this.failWorker(String(request.layer ?? "visual"), error);
4123
- }
4124
- }
4125
- enqueueWorkerRender(slot, request) {
4126
- this.throwIfFatal();
4127
- if (slot.busy) {
4128
- slot.queuedRequest = request;
4129
- slot.queuedKey = request.key;
4130
- return;
4131
- }
4132
- this.dispatchWorkerRender(slot, request);
4133
- }
4134
- dispatchWorkerRender(slot, request) {
4135
- this.throwIfFatal();
4136
- if (!slot.worker)
4137
- this.failWorker(String(request.layer ?? "visual"), new Error("worker is unavailable"));
4138
- slot.busy = true;
4139
- slot.inFlightKey = request.key;
4140
- try {
4141
- slot.worker.postMessage(request);
4142
- } catch (error) {
4143
- this.failWorker(String(request.layer ?? "visual"), error);
4144
- }
4145
- }
4146
- handleWorkerMessage(slot, message) {
4147
- if (this.destroyed || !message || typeof message !== "object")
4148
- return;
4149
- const response = message;
4150
- slot.busy = false;
4151
- slot.inFlightKey = "";
4152
- const isStale = response.requestId <= slot.clearedThroughRequestId || response.requestId < slot.latestRequestId;
4153
- if (!isStale) {
4154
- if (response.error)
4155
- this.failWorker(pendingLayerKey(response.key), new Error(response.error));
4156
- if (response.sequence && !this.isKnownKey(slot, response.key) && (!this.isOutputBackedUp() || pendingLayerKey(response.key) === "split")) {
4157
- this.stageFrame({ key: response.key, sequence: response.sequence });
4158
- this.onFrameReady?.();
4159
- }
4160
- }
4161
- const next = slot.queuedRequest;
4162
- slot.queuedRequest = null;
4163
- slot.queuedKey = "";
4164
- if (next && !this.isKnownKey(slot, next.key))
4165
- this.dispatchWorkerRender(slot, next);
4166
- }
3226
+ function subscribeHover(listener) {
3227
+ listeners.add(listener);
3228
+ return () => {
3229
+ listeners.delete(listener);
3230
+ };
4167
3231
  }
4168
3232
 
4169
3233
  // packages/cli/src/app-opentui/render/text.ts
4170
- import { RGBA as RGBA4, TextAttributes as TextAttributes4, TextRenderable } from "@opentui/core";
4171
3234
  var TRANSPARENT2 = RGBA4.fromInts(0, 0, 0, 0);
3235
+ var HOVER_BG = RGBA4.fromHex(RIG_UI.hover);
3236
+ function lineIsClickable(line2) {
3237
+ return line2?.selectable !== undefined || line2?.selectableIndex !== undefined;
3238
+ }
3239
+ function lineSelectableId(line2) {
3240
+ return line2?.selectable?.id ?? line2?.selectableId;
3241
+ }
3242
+ function lineBackground(line2) {
3243
+ if (line2?.bg)
3244
+ return RGBA4.fromHex(line2.bg);
3245
+ const id = lineSelectableId(line2);
3246
+ if (id !== undefined && isHovered(id))
3247
+ return HOVER_BG;
3248
+ return TRANSPARENT2;
3249
+ }
4172
3250
  function selectableMouseHandler(currentLineRef, onLineMouseDown) {
4173
3251
  return (event) => {
4174
3252
  const currentLine = currentLineRef();
4175
- if (currentLine?.selectableIndex === undefined)
3253
+ if (!lineIsClickable(currentLine))
4176
3254
  return;
4177
3255
  onLineMouseDown?.(currentLine, event);
4178
3256
  };
4179
3257
  }
3258
+ function hoverHandlers(currentLineRef) {
3259
+ return {
3260
+ onMouseOver: () => {
3261
+ const id = lineSelectableId(currentLineRef());
3262
+ if (id !== undefined)
3263
+ setHoveredSelectableId(id);
3264
+ },
3265
+ onMouseOut: () => clearHoveredSelectableId(lineSelectableId(currentLineRef()))
3266
+ };
3267
+ }
4180
3268
  function createFlowTextLine(renderer, id, onLineMouseDown) {
4181
3269
  let currentLine;
4182
3270
  const renderable = new TextRenderable(renderer, {
@@ -4187,7 +3275,8 @@ function createFlowTextLine(renderer, id, onLineMouseDown) {
4187
3275
  flexShrink: 0,
4188
3276
  fg: RIG_UI.ink2,
4189
3277
  selectable: true,
4190
- onMouseDown: selectableMouseHandler(() => currentLine, onLineMouseDown)
3278
+ onMouseDown: selectableMouseHandler(() => currentLine, onLineMouseDown),
3279
+ ...hoverHandlers(() => currentLine)
4191
3280
  });
4192
3281
  renderable.setRigSceneLine = (line2) => {
4193
3282
  currentLine = line2;
@@ -4201,7 +3290,7 @@ function applyFlowTextLine(renderable, line2, width) {
4201
3290
  renderable.width = Math.max(1, width);
4202
3291
  renderable.content = line2?.styledText ?? line2?.text ?? "";
4203
3292
  renderable.fg = line2?.fg ?? RIG_UI.ink2;
4204
- renderable.bg = line2?.bg ?? TRANSPARENT2;
3293
+ renderable.bg = lineBackground(line2);
4205
3294
  renderable.attributes = line2?.bold ? TextAttributes4.BOLD : line2?.dim ? TextAttributes4.DIM : 0;
4206
3295
  }
4207
3296
 
@@ -5145,7 +4234,7 @@ async function activateSelection(runtime, renderedItems) {
5145
4234
  }
5146
4235
 
5147
4236
  // packages/cli/src/app-opentui/react/App.tsx
5148
- import { useMemo, useRef as useRef4 } from "react";
4237
+ import { useMemo, useRef as useRef4, useSyncExternalStore as useSyncExternalStore2 } from "react";
5149
4238
  import { useKeyboard, useRenderer as useRenderer4, useTerminalDimensions as useTerminalDimensions2 } from "@opentui/react";
5150
4239
 
5151
4240
  // packages/cli/src/app-opentui/react/Backdrop.tsx
@@ -5230,13 +4319,16 @@ function lineContent(line2) {
5230
4319
  }
5231
4320
  function LineView(props) {
5232
4321
  const { line: line2, onLineClick } = props;
4322
+ const id = lineSelectableId(line2);
5233
4323
  return /* @__PURE__ */ jsxDEV("text", {
5234
4324
  style: { position: "absolute", left: props.left, top: props.top, width: props.width, zIndex: 5 },
5235
4325
  fg: line2.fg ?? RIG_UI.ink2,
5236
- bg: line2.bg ?? TRANSPARENT2,
4326
+ bg: lineBackground(line2),
5237
4327
  attributes: lineAttributes(line2),
5238
4328
  content: lineContent(line2),
5239
- onMouseDown: onLineClick ? () => onLineClick(line2) : undefined
4329
+ onMouseDown: onLineClick ? () => onLineClick(line2) : undefined,
4330
+ onMouseOver: id !== undefined ? () => setHoveredSelectableId(id) : undefined,
4331
+ onMouseOut: id !== undefined ? () => clearHoveredSelectableId(id) : undefined
5240
4332
  }, undefined, false, undefined, this);
5241
4333
  }
5242
4334
  function PanelHost(props) {
@@ -5478,6 +4570,7 @@ function App(props) {
5478
4570
  const runAppAction = useRunAppAction();
5479
4571
  const store = useStore();
5480
4572
  const renderer = useRenderer4();
4573
+ useSyncExternalStore2(subscribeHover, getHoveredSelectableId, getHoveredSelectableId);
5481
4574
  const dims = useTerminalDimensions2();
5482
4575
  const layout = computeStageLayout(dims.width, dims.height);
5483
4576
  const registry = useMemo(() => ({ ...BUILTIN, ...props.sceneRenderers ?? {} }), [props.sceneRenderers]);
@@ -5512,8 +4605,7 @@ function App(props) {
5512
4605
  } else {
5513
4606
  return;
5514
4607
  }
5515
- if (line2.activateOnClick)
5516
- runAppAction("Activate selection", () => activateSelection(runtime, items));
4608
+ runAppAction("Activate selection", () => activateSelection(runtime, items));
5517
4609
  };
5518
4610
  return /* @__PURE__ */ jsxDEV3("box", {
5519
4611
  style: { position: "absolute", left: 0, top: 0, width: "100%", height: "100%" },