@brimveyn/aimux 1.14.6 → 1.14.8

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.14.6",
3
+ "version": "1.14.8",
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",
package/src/app.tsx CHANGED
@@ -247,8 +247,14 @@ export function App({
247
247
  }, [])
248
248
 
249
249
  const resizingRef = useRef(false)
250
+ // Seeded with state.layout's DEFAULT 80x24; reassigned below once
251
+ // useTerminalResize has produced the open-loop estimate from real dimensions.
252
+ // Reading state.layout here is wrong: on the first render state.layout is
253
+ // still the bootstrap default, and useBackendRuntime's attach effect would
254
+ // then hand 80x24 to the backend, resizing the daemon-persisted PTYs (or
255
+ // newly-spawned ones) to the wrong size and stranding the assistant's
256
+ // already-drawn content in the top-left until a workspace switch.
250
257
  const layoutRef = useRef(state.layout)
251
- layoutRef.current = state.layout
252
258
  const activeTab = useMemo(
253
259
  () => state.tabs.find((tab) => tab.id === state.activeTabId),
254
260
  [state.activeTabId, state.tabs]
@@ -282,6 +288,18 @@ export function App({
282
288
  const contentOriginRef = useRef<TerminalContentOrigin>({ cols: 0, rows: 0, x: 0, y: 0 })
283
289
  const currentSessionWorkspaceSnapshot = currentSession?.workspaceSnapshot
284
290
 
291
+ // Must run before useBackendRuntime so its open-loop terminalSize seeds
292
+ // layoutRef before the attach effect reads it. See the comment on layoutRef.
293
+ const terminalSize = useTerminalResize({
294
+ backend,
295
+ contentOriginRef,
296
+ dimensions,
297
+ dispatch,
298
+ resizingRef,
299
+ state,
300
+ })
301
+ layoutRef.current = { terminalCols: terminalSize.cols, terminalRows: terminalSize.rows }
302
+
285
303
  const syntaxOverlayFlag = resolvedConfig.theme?.beta?.experimentalSyntaxHighlight === true
286
304
  const syntaxOverlayFlagRef = useRef(syntaxOverlayFlag)
287
305
  syntaxOverlayFlagRef.current = syntaxOverlayFlag
@@ -334,15 +352,6 @@ export function App({
334
352
  stateRef,
335
353
  })
336
354
 
337
- const terminalSize = useTerminalResize({
338
- backend,
339
- contentOriginRef,
340
- dimensions,
341
- dispatch,
342
- resizingRef,
343
- state,
344
- })
345
-
346
355
  const {
347
356
  handleEmbeddedGitResizeStart,
348
357
  handleGitPaneResizeStart,
@@ -330,6 +330,37 @@ export function TerminalPane({
330
330
  )
331
331
  const forwardScrollEvent = useCallback(
332
332
  (event: OtuiMouseEvent) => {
333
+ // opentui's processMouseEvent dispatches a scroll to the deepest renderable
334
+ // (the <text> inside this box) BEFORE bubbling up to our handler.
335
+ // TextBufferRenderable's built-in onMouseEvent unconditionally calls
336
+ // handleScroll, which mutates its private _scrollY. preventDefault on the
337
+ // bubbled event can't roll that back — the scroll already happened. And
338
+ // nothing in React ever resets _scrollY for the lifetime of the renderable.
339
+ //
340
+ // Net effect: every wheel event over the terminal nudges the rendered
341
+ // text away from state.viewport.lines by one wheel step. The drift
342
+ // accumulates until the user refreshes aimux (re-mounts the text).
343
+ // Reset _scrollY here, right after the child finished scrolling.
344
+ const scrollTarget = event.target
345
+ if (scrollTarget !== null && typeof scrollTarget === 'object') {
346
+ const currentScrollY = Reflect.get(scrollTarget, 'scrollY')
347
+ if (typeof currentScrollY === 'number' && currentScrollY !== 0) {
348
+ try {
349
+ Reflect.set(scrollTarget, 'scrollY', 0)
350
+ } catch {
351
+ // Not all renderables expose a setter; best-effort only.
352
+ }
353
+ }
354
+ const currentScrollX = Reflect.get(scrollTarget, 'scrollX')
355
+ if (typeof currentScrollX === 'number' && currentScrollX !== 0) {
356
+ try {
357
+ Reflect.set(scrollTarget, 'scrollX', 0)
358
+ } catch {
359
+ // Not all renderables expose a setter; best-effort only.
360
+ }
361
+ }
362
+ }
363
+
333
364
  if (!canForwardMouse && !canUseLocalScrollback) {
334
365
  return
335
366
  }