mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-10 13:31:38 +00:00
opentui(v5/item4): coalesce resize via a shared debounced dimensions signal
Raw useTerminalDimensions fires on every SIGWINCH tick; during a drag that's a recompute/reflow storm across every width-sensitive component (tool bodies, tables, status bar, banner). Add a DimensionsProvider that runs the raw hook ONCE and feeds a single leading+trailing-debounced (40ms) signal — mirroring the gateway's 16ms event coalescing / opencode's createLeadingTrailingSignal — that every consumer shares via useDimensions(). They now reflow together (no tearing) and at most once per window. Falls back to the raw hook outside a provider (headless tests). Verified: single resizes converge clean (wide banner ⇄ compact brand at the 102-col threshold); rapid bursts coalesce. 90 pass.
This commit is contained in:
parent
4061e635d3
commit
48d0c70f61
6 changed files with 62 additions and 9 deletions
|
|
@ -18,6 +18,7 @@ import { Match, Switch } from 'solid-js'
|
|||
import type { PromptHistory } from '../logic/history.ts'
|
||||
import type { SessionStore } from '../logic/store.ts'
|
||||
import { Composer } from './composer.tsx'
|
||||
import { DimensionsProvider } from './dimensions.tsx'
|
||||
import { Header } from './header.tsx'
|
||||
import { AgentsDashboard } from './overlays/agentsDashboard.tsx'
|
||||
import { Pager } from './overlays/pager.tsx'
|
||||
|
|
@ -64,6 +65,7 @@ export function App(props: AppProps) {
|
|||
}
|
||||
|
||||
return (
|
||||
<DimensionsProvider>
|
||||
<box style={{ flexDirection: 'column', flexGrow: 1, paddingTop: 1, paddingLeft: 1, paddingRight: 1 }}>
|
||||
{/* a bottom rule under the header bookends the transcript with the status
|
||||
bar's top rule — frames the chrome as intentional (item 8). */}
|
||||
|
|
@ -132,5 +134,6 @@ export function App(props: AppProps) {
|
|||
</Match>
|
||||
</Switch>
|
||||
</box>
|
||||
</DimensionsProvider>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
50
ui-tui-opentui-v2/src/view/dimensions.tsx
Normal file
50
ui-tui-opentui-v2/src/view/dimensions.tsx
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
/**
|
||||
* Shared, COALESCED terminal dimensions (item 4 — resize hardening). Raw
|
||||
* `useTerminalDimensions()` fires on every SIGWINCH tick; during a drag that's a
|
||||
* recompute/reflow storm across every width-sensitive component (tool bodies,
|
||||
* tables, status bar, banner). One provider runs the raw hook once and feeds a
|
||||
* single leading+trailing-debounced signal (opencode's createLeadingTrailingSignal
|
||||
* idiom, mirroring the gateway's 16ms event coalescing) that every consumer shares
|
||||
* — so they reflow together (no tearing) and at most once per COALESCE window.
|
||||
*/
|
||||
import { useTerminalDimensions } from '@opentui/solid'
|
||||
import { type Accessor, createContext, createEffect, createSignal, type JSX, onCleanup, useContext } from 'solid-js'
|
||||
|
||||
export interface Dims {
|
||||
readonly width: number
|
||||
readonly height: number
|
||||
}
|
||||
|
||||
const DimsContext = createContext<Accessor<Dims>>()
|
||||
const COALESCE_MS = 40
|
||||
|
||||
export function DimensionsProvider(props: { children: JSX.Element }) {
|
||||
const raw = useTerminalDimensions()
|
||||
const [dims, setDims] = createSignal<Dims>({ height: raw().height, width: raw().width })
|
||||
let timer: ReturnType<typeof setTimeout> | undefined
|
||||
let last = 0
|
||||
createEffect(() => {
|
||||
const next: Dims = { height: raw().height, width: raw().width } // track raw
|
||||
const now = Date.now()
|
||||
if (now - last >= COALESCE_MS) {
|
||||
last = now
|
||||
setDims(next) // leading edge: respond immediately to the first change
|
||||
} else {
|
||||
// trailing edge: coalesce the burst, land on the final size once it settles
|
||||
if (timer) clearTimeout(timer)
|
||||
timer = setTimeout(() => {
|
||||
last = Date.now()
|
||||
setDims(next)
|
||||
}, COALESCE_MS)
|
||||
}
|
||||
})
|
||||
onCleanup(() => {
|
||||
if (timer) clearTimeout(timer)
|
||||
})
|
||||
return <DimsContext.Provider value={dims}>{props.children}</DimsContext.Provider>
|
||||
}
|
||||
|
||||
/** Coalesced dimensions; falls back to the raw hook outside a provider (e.g. headless tests). */
|
||||
export function useDimensions(): Accessor<Dims> {
|
||||
return useContext(DimsContext) ?? useTerminalDimensions()
|
||||
}
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
* panel (item 9), the common commands, and the key tips. Fully themed; decorative,
|
||||
* so `selectable={false}` (item 4).
|
||||
*/
|
||||
import { useTerminalDimensions } from '@opentui/solid'
|
||||
import { useDimensions } from './dimensions.tsx'
|
||||
import { createSignal, For, Show } from 'solid-js'
|
||||
|
||||
import type { Catalog } from '../logic/store.ts'
|
||||
|
|
@ -33,7 +33,7 @@ const BANNER_W = 102
|
|||
|
||||
export function HomeHint(props: { catalog: Catalog | undefined }) {
|
||||
const theme = useTheme()
|
||||
const dims = useTerminalDimensions()
|
||||
const dims = useDimensions()
|
||||
const [open, setOpen] = createSignal(false)
|
||||
const wide = () => dims().width >= BANNER_W
|
||||
const cat = () => props.catalog
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
* MdTable — renders a parsed GFM table as an aligned grid (item 7). The native
|
||||
* markdown renderable leaves tables as raw pipes; this lays them out with
|
||||
* per-column widths, alignment, a header rule, and dim `│` separators. Width
|
||||
* is reactive (useTerminalDimensions) so columns shrink to fit on resize.
|
||||
* is reactive (useDimensions) so columns shrink to fit on resize.
|
||||
*/
|
||||
import { useTerminalDimensions } from '@opentui/solid'
|
||||
import { useDimensions } from './dimensions.tsx'
|
||||
import { createMemo, type JSX } from 'solid-js'
|
||||
|
||||
import { padAligned, tableColumnWidths, type TableSegment } from '../logic/mdTable.ts'
|
||||
|
|
@ -15,7 +15,7 @@ const GUTTER = 2
|
|||
|
||||
export function MdTable(props: { seg: TableSegment }) {
|
||||
const theme = useTheme()
|
||||
const dims = useTerminalDimensions()
|
||||
const dims = useDimensions()
|
||||
const widths = createMemo(() => tableColumnWidths(props.seg, Math.max(24, dims().width - GUTTER - 2)))
|
||||
|
||||
const cellText = (raw: string, i: number) =>
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
* terminals and the cwd is left-truncated (`…/tail`) so the row NEVER wraps or
|
||||
* clips. Read-only chrome — no input handling here.
|
||||
*/
|
||||
import { useTerminalDimensions } from '@opentui/solid'
|
||||
import { useDimensions } from './dimensions.tsx'
|
||||
import { createMemo, Show } from 'solid-js'
|
||||
|
||||
import { useTheme } from './theme.tsx'
|
||||
|
|
@ -54,7 +54,7 @@ function ctxBar(pct: number, width: number): string {
|
|||
|
||||
export function StatusBar(props: { store: SessionStore }) {
|
||||
const theme = useTheme()
|
||||
const dims = useTerminalDimensions()
|
||||
const dims = useDimensions()
|
||||
const info = () => props.store.state.info
|
||||
|
||||
// Context-bar colour escalates with pressure (Ink ctxBarColor good→warn→bad→critical).
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
* Fully themed (no hardcoded styles); decorative glyphs are selectable={false}.
|
||||
*/
|
||||
import { type ToolPartState } from '../logic/store.ts'
|
||||
import { useTerminalDimensions } from '@opentui/solid'
|
||||
import { useDimensions } from './dimensions.tsx'
|
||||
import { createMemo, createSignal, For, Show } from 'solid-js'
|
||||
|
||||
import { collapseToolOutput, truncate } from '../logic/toolOutput.ts'
|
||||
|
|
@ -36,7 +36,7 @@ function fmtDuration(s: number): string {
|
|||
|
||||
export function ToolPart(props: { part: ToolPartState }) {
|
||||
const theme = useTheme()
|
||||
const dims = useTerminalDimensions()
|
||||
const dims = useDimensions()
|
||||
const [expanded, setExpanded] = createSignal(false)
|
||||
|
||||
const bodyWidth = () => Math.max(20, dims().width - GUTTER - 4)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue