@brimveyn/aimux 1.19.1 → 1.19.2

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brimveyn/aimux",
3
- "version": "1.19.1",
3
+ "version": "1.19.2",
4
4
  "description": "A terminal multiplexer for AI CLIs. Run Claude, Codex, OpenCode side-by-side with tabbed navigation, split panes, and persistent sessions.",
5
5
  "keywords": [
6
6
  "ai",
@@ -1,32 +1,12 @@
1
1
  import type { ModeId } from '@brimveyn/aimux-config'
2
2
 
3
- import { type BoxRenderable, type OptimizedBuffer, RGBA } from '@opentui/core'
4
-
5
3
  import { describeBindings } from '../../../../input/keymap/describe-bindings'
6
4
  import { useKeymap } from '../../../keymap-context'
7
5
  import { useTheme, useTransparent } from '../../../theme'
6
+ import { fillBoxInterior } from '../../../transparent-fill'
8
7
 
9
8
  const KEYS_COLUMN_WIDTH = 12
10
9
 
11
- const TRANSPARENT_RGBA = RGBA.fromValues(0, 0, 0, 0)
12
-
13
- // Same trick as modal-shell: when our bg is transparent, BoxRenderable skips
14
- // its fill so the chrome chars underneath leak through. setCell bypasses alpha
15
- // blending and char-preservation, so we can wipe every interior cell to
16
- // (' ', TRANSPARENT_RGBA, TRANSPARENT_RGBA). No border on this overlay, so
17
- // fill the full bounds (no 1-cell inset).
18
- function fillBoxInteriorWithSpaces(this: BoxRenderable, buffer: OptimizedBuffer): void {
19
- const x0 = this.screenX
20
- const y0 = this.screenY
21
- const endX = x0 + this.width
22
- const endY = y0 + this.height
23
- for (let y = y0; y < endY; y++) {
24
- for (let x = x0; x < endX; x++) {
25
- buffer.setCell(x, y, ' ', TRANSPARENT_RGBA, TRANSPARENT_RGBA)
26
- }
27
- }
28
- }
29
-
30
10
  interface ModalKeybindsOverlayProps {
31
11
  modeId: ModeId
32
12
  limit?: number
@@ -52,7 +32,7 @@ export function ModalKeybindsOverlay({ limit, modeId }: ModalKeybindsOverlayProp
52
32
  paddingRight={2}
53
33
  paddingTop={1}
54
34
  paddingBottom={1}
55
- renderAfter={transparent ? fillBoxInteriorWithSpaces : undefined}
35
+ renderAfter={transparent ? fillBoxInterior : undefined}
56
36
  >
57
37
  {entries.map((binding) => (
58
38
  <box key={binding.description ?? binding.keys} flexDirection="row">
@@ -1,9 +1,8 @@
1
1
  import type { ModeId } from '@brimveyn/aimux-config'
2
2
  import type { ReactNode } from 'react'
3
3
 
4
- import { type BoxRenderable, type OptimizedBuffer, RGBA } from '@opentui/core'
5
-
6
4
  import { useTheme, useTransparent } from '../../../theme'
5
+ import { fillBorderedBoxInterior } from '../../../transparent-fill'
7
6
  import { ModalKeybindsOverlay } from './modal-keybinds-overlay'
8
7
 
9
8
  interface ModalShellProps {
@@ -16,38 +15,6 @@ interface ModalShellProps {
16
15
  width: number | `${number}%`
17
16
  }
18
17
 
19
- const TRANSPARENT_RGBA = RGBA.fromValues(0, 0, 0, 0)
20
-
21
- // opentui's BoxRenderable only paints a fill when `backgroundColor.a > 0`, so
22
- // a fully transparent modal bg leaks the chrome characters underneath. The
23
- // `renderAfter` hook runs after the box paints itself but before its children
24
- // render, so we manually overwrite every interior cell with a blank space.
25
- //
26
- // Two gotchas we hit by reading the zig source (packages/core/src/zig/buffer.zig):
27
- // 1. `setCellWithAlphaBlending` early-returns via `isFullyTransparent` when
28
- // both fg and bg alpha are 0 — nothing gets written.
29
- // 2. Its `blendCells` deliberately preserves the destination char when the
30
- // overlay char is `DEFAULT_SPACE_CHAR` (codepoint 32) so that drawing a
31
- // space on top of existing text does not erase it.
32
- //
33
- // `setCell` (→ `bufferSetCell` → zig `buffer.set`) bypasses both: it writes
34
- // the cell unconditionally, no alpha check, no blending, no char preservation.
35
- function fillModalInteriorWithSpaces(this: BoxRenderable, buffer: OptimizedBuffer): void {
36
- const x0 = this.screenX
37
- const y0 = this.screenY
38
- const w = this.width
39
- const h = this.height
40
- const startX = x0 + 1
41
- const startY = y0 + 1
42
- const endX = x0 + w - 1
43
- const endY = y0 + h - 1
44
- for (let y = startY; y < endY; y++) {
45
- for (let x = startX; x < endX; x++) {
46
- buffer.setCell(x, y, ' ', TRANSPARENT_RGBA, TRANSPARENT_RGBA)
47
- }
48
- }
49
- }
50
-
51
18
  export function ModalShell({
52
19
  children,
53
20
  footer,
@@ -76,7 +43,7 @@ export function ModalShell({
76
43
  backgroundColor={bg}
77
44
  padding={1}
78
45
  width={width}
79
- renderAfter={transparent ? fillModalInteriorWithSpaces : undefined}
46
+ renderAfter={transparent ? fillBorderedBoxInterior : undefined}
80
47
  >
81
48
  <box width="100%" flexDirection="column" gap={listGap}>
82
49
  <box flexDirection="column">
@@ -9,7 +9,8 @@ import {
9
9
  type ContextMenuState,
10
10
  subscribeContextMenu,
11
11
  } from '../../../context-menu/controller'
12
- import { useTheme } from '../../../theme'
12
+ import { useTheme, useTransparent } from '../../../theme'
13
+ import { fillBorderedBoxInterior } from '../../../transparent-fill'
13
14
 
14
15
  const MenuItem = memo(function MenuItem({
15
16
  active,
@@ -59,6 +60,7 @@ export function ContextMenuOverlay() {
59
60
  const [menu, setMenu] = useState<ContextMenuState | null>(null)
60
61
  const [selected, setSelected] = useState(0)
61
62
  const t = useTheme()
63
+ const transparent = useTransparent()
62
64
  const terminalCols = useAppStore((s) => s.layout.terminalCols)
63
65
  const terminalRows = useAppStore((s) => s.layout.terminalRows)
64
66
 
@@ -131,7 +133,8 @@ export function ContextMenuOverlay() {
131
133
  flexDirection="column"
132
134
  border
133
135
  borderColor={t.border}
134
- backgroundColor={t.backgroundPanel}
136
+ backgroundColor={transparent ? 'transparent' : t.backgroundPanel}
137
+ renderAfter={transparent ? fillBorderedBoxInterior : undefined}
135
138
  onMouseDown={handleMenuMouseDown}
136
139
  >
137
140
  {menu.items.map(([label, onSelect], index) => (
@@ -0,0 +1,49 @@
1
+ import { type BoxRenderable, type OptimizedBuffer, RGBA } from '@opentui/core'
2
+
3
+ export const TRANSPARENT_RGBA = RGBA.fromValues(0, 0, 0, 0)
4
+
5
+ // opentui's BoxRenderable only paints a fill when `backgroundColor.a > 0`, so
6
+ // a fully transparent bg leaks the chrome characters underneath. These helpers
7
+ // run from a box's `renderAfter` hook (after the box paints itself, before its
8
+ // children render) and manually overwrite every interior cell with a blank
9
+ // space so the region reads as "emptied" instead of leaking.
10
+ //
11
+ // Two gotchas we hit by reading the zig source (packages/core/src/zig/buffer.zig):
12
+ // 1. `setCellWithAlphaBlending` early-returns via `isFullyTransparent` when
13
+ // both fg and bg alpha are 0 — nothing gets written.
14
+ // 2. Its `blendCells` deliberately preserves the destination char when the
15
+ // overlay char is `DEFAULT_SPACE_CHAR` (codepoint 32) so that drawing a
16
+ // space on top of existing text does not erase it.
17
+ //
18
+ // `setCell` (→ `bufferSetCell` → zig `buffer.set`) bypasses both: it writes
19
+ // the cell unconditionally, no alpha check, no blending, no char preservation.
20
+
21
+ // Bordered boxes: inset by 1 cell so the painted border is preserved.
22
+ export function fillBorderedBoxInterior(this: BoxRenderable, buffer: OptimizedBuffer): void {
23
+ const x0 = this.screenX
24
+ const y0 = this.screenY
25
+ const w = this.width
26
+ const h = this.height
27
+ const startX = x0 + 1
28
+ const startY = y0 + 1
29
+ const endX = x0 + w - 1
30
+ const endY = y0 + h - 1
31
+ for (let y = startY; y < endY; y++) {
32
+ for (let x = startX; x < endX; x++) {
33
+ buffer.setCell(x, y, ' ', TRANSPARENT_RGBA, TRANSPARENT_RGBA)
34
+ }
35
+ }
36
+ }
37
+
38
+ // Borderless boxes: fill the full bounds (no inset).
39
+ export function fillBoxInterior(this: BoxRenderable, buffer: OptimizedBuffer): void {
40
+ const x0 = this.screenX
41
+ const y0 = this.screenY
42
+ const endX = x0 + this.width
43
+ const endY = y0 + this.height
44
+ for (let y = y0; y < endY; y++) {
45
+ for (let x = x0; x < endX; x++) {
46
+ buffer.setCell(x, y, ' ', TRANSPARENT_RGBA, TRANSPARENT_RGBA)
47
+ }
48
+ }
49
+ }