mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-30 01:41:43 +00:00
revert(tui): drop DeferredMd, profiling showed it was neutral
Profiled with scripts/profile-tui.py under hold-PageUp + hold-wheel.
The placeholder → microtask-upgrade pattern did not reduce renderer
p99 (63ms → 63ms) or max (96ms → 142ms, slightly worse). Each fresh
row still pays the Md cost — just on a follow-up commit instead of
inline — and the follow-up commit shows up as a second heavy frame
a few ms later.
The real bottlenecks turned out to be:
1. wheel step too large (fixed in 7ca16eea)
2. outer terminal ANSI parse throughput (diagnosing next)
3. React commit frequency during hold-scroll (needs coalescing)
None of which DeferredMd addresses. Clearing the complexity so the
next experiments land on a simpler substrate.
This commit is contained in:
parent
7ca16eea56
commit
d3dedf10aa
3 changed files with 3 additions and 98 deletions
|
|
@ -1,90 +0,0 @@
|
|||
// DeferredMd — renders a lightweight <Text> placeholder on first mount and
|
||||
// upgrades to full <Md> markdown + syntax highlighting in a subsequent
|
||||
// transition commit. Spreads the parse cost off the scroll critical path.
|
||||
//
|
||||
// Why: profiling shows the 63-112ms renderer spikes during hold-PageUp
|
||||
// correlate with fresh MessageLine mounts running the markdown tokenizer
|
||||
// + syntax highlighting synchronously. The new row is added by
|
||||
// useVirtualHistory's slide step; React commits the tree; Ink lays out
|
||||
// Yoga; stdout writes the result. All in one hitch frame.
|
||||
//
|
||||
// With this wrapper, the hitch frame lays out a pre-wrapped plain <Text>
|
||||
// (Yoga only needs to wrap width-known strings — no tokenizer, no
|
||||
// highlighter, no inline regex walk), then a follow-up commit re-renders
|
||||
// the same row with full markdown. The follow-up is gated on a
|
||||
// queueMicrotask so Ink has a chance to paint the placeholder before
|
||||
// React starts the Md-heavy upgrade work.
|
||||
//
|
||||
// Upgrade cache: once a given (theme, text, compact) tuple has been
|
||||
// rendered as full Md, we remember it so remounts (scroll-out then
|
||||
// scroll-back) don't pay the placeholder round-trip again — they mount
|
||||
// straight into the upgraded <Md> subtree, which Md internally memoizes
|
||||
// on text identity, so there's no re-tokenization either.
|
||||
|
||||
import { Text } from '@hermes/ink'
|
||||
import { memo, useEffect, useState } from 'react'
|
||||
|
||||
import type { Theme } from '../theme.js'
|
||||
|
||||
import { Md, stripInlineMarkup } from './markdown.js'
|
||||
|
||||
// Theme object is stable per-session; key upgrades under it so palette
|
||||
// swaps naturally retrigger (colors differ → render changes).
|
||||
const upgraded = new WeakMap<Theme, Set<string>>()
|
||||
|
||||
const cacheKey = (compact: boolean | undefined, text: string) => (compact ? `c:${text}` : `x:${text}`)
|
||||
|
||||
const hasUpgraded = (t: Theme, key: string) => upgraded.get(t)?.has(key) ?? false
|
||||
|
||||
const markUpgraded = (t: Theme, key: string) => {
|
||||
const bucket = upgraded.get(t) ?? new Set<string>()
|
||||
|
||||
bucket.add(key)
|
||||
upgraded.set(t, bucket)
|
||||
}
|
||||
|
||||
export const DeferredMd = memo(function DeferredMd({ color, compact, t, text }: DeferredMdProps) {
|
||||
const key = cacheKey(compact, text)
|
||||
const [ready, setReady] = useState(() => hasUpgraded(t, key) || !text)
|
||||
|
||||
useEffect(() => {
|
||||
if (ready) {
|
||||
return
|
||||
}
|
||||
|
||||
let cancelled = false
|
||||
|
||||
queueMicrotask(() => {
|
||||
if (cancelled) {
|
||||
return
|
||||
}
|
||||
|
||||
markUpgraded(t, key)
|
||||
setReady(true)
|
||||
})
|
||||
|
||||
return () => {
|
||||
cancelled = true
|
||||
}
|
||||
}, [key, ready, t])
|
||||
|
||||
if (ready) {
|
||||
return <Md compact={compact} t={t} text={text} />
|
||||
}
|
||||
|
||||
// Placeholder: strip inline markup so the visible width approximately
|
||||
// matches the final Md layout (bold/italic/links are width-neutral or
|
||||
// collapse to anchor text). Line breaks preserved — Ink's wrap="wrap"
|
||||
// lays the plain text out as blocks at the right column count.
|
||||
// Using <Text> directly (no Box wrapper) so there's no column-flex
|
||||
// decision for Yoga — it just wraps a string.
|
||||
return <Text color={color ?? undefined}>{stripInlineMarkup(text)}</Text>
|
||||
})
|
||||
|
||||
interface DeferredMdProps {
|
||||
/** Fallback color for the placeholder text (typically the role's body color). */
|
||||
color?: string
|
||||
compact?: boolean
|
||||
t: Theme
|
||||
text: string
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@ import { boundedLiveRenderText, compactPreview, hasAnsi, isPasteBackedText, stri
|
|||
import type { Theme } from '../theme.js'
|
||||
import type { ActiveTool, DetailsMode, Msg, SectionVisibility } from '../types.js'
|
||||
|
||||
import { DeferredMd } from './deferredMarkdown.js'
|
||||
import { Md } from './markdown.js'
|
||||
import { StreamingMd } from './streamingMarkdown.js'
|
||||
import { ToolTrail } from './thinking.js'
|
||||
import { TodoPanel } from './todoPanel.js'
|
||||
|
|
@ -107,12 +107,7 @@ export const MessageLine = memo(function MessageLine({
|
|||
// streamingMarkdown.tsx for the cost model.
|
||||
<StreamingMd compact={compact} t={t} text={boundedLiveRenderText(msg.text)} />
|
||||
) : (
|
||||
// Deferred markdown: plain-text placeholder on first mount, upgrade
|
||||
// to full Md on a queued microtask. Spreads the tokenizer + syntax
|
||||
// cost off the scroll critical path so hold-PageUp doesn't hitch
|
||||
// on fresh assistant rows entering overscan. See
|
||||
// deferredMarkdown.tsx for the trade-offs.
|
||||
<DeferredMd color={body} compact={compact} t={t} text={msg.text} />
|
||||
<Md compact={compact} t={t} text={msg.text} />
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -899,7 +899,7 @@ export const ToolTrail = memo(function ToolTrail({
|
|||
return duration ? (
|
||||
<>
|
||||
{label}
|
||||
<Text color={t.color.dim} dim>
|
||||
<Text color={t.color.statusFg} dim>
|
||||
{duration}
|
||||
</Text>
|
||||
</>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue