Repository navigation
Description
Summary
Four hot-path performance findings, all substantiated from source.
UX-33 — @-mention completion rescans the whole tree per keystroke. fsMentionSuggestions (packages/mayfly/src/interaction/file-mention.ts:232-248) calls collectFsMentionCandidates (file-mention.ts:110-145) fresh on each query: a depth-first walk of up to 2000 entries with no result caching between keystrokes, while pi-tui debounces mention autocomplete at only 20 ms (@earendil-works/pi-tui@0.84.2 dist/components/editor.js:169, applied at :1887). On a large tree without fd, the dropdown visibly lags behind typing. Additionally, file-mention.ts:309 uses statSync per symlink inside the autocomplete loop on this same per-keystroke path, while the sibling scanner in the same file correctly uses await stat (file-mention.ts:134).
UX-34 — UI compiler re-walks the control tree several times per keystroke. Every reconcile() performs two full controlsForNode tree walks (state.controls() and state.allControls()), each rebuilding all control descriptors with JSON.stringify keys (packages/mayfly/src/core/ui-compiler.ts:1269-1274); reconcile is invoked from handleInput (ui-compiler.ts:2119), again inside moveTo (ui-compiler.ts:2143), and from every render/layout pass. Within each walk, every list node computes listWindow twice (ui-compiler.ts:964-965). Separately, arrow-key spatial navigation calls controlRectangles, which does this.root.render(width) plus renderLayoutFrame(...) per keypress (ui-compiler.ts:2084-2092, via :2287), bypassing the renderFrameOnce memo (ui-compiler.ts:1996-2009) — two full tree renders per arrow key.
UX-35 — Overflow log: sync writes on the render path, permanent silence after 200 entries. The default overflow sink does mkdirSync + appendFileSync inside clampFrame, which runs on every rendered frame (packages/mayfly/src/core/frame-clamp.ts:135-139), and the dedupe cap seen.size >= maxEntries (200) has no rotation or reset (frame-clamp.ts:132) — a width regression appearing late in a long session is never recorded, which is exactly the telemetry the backstop exists for.
UX-36 — Unthrottled status refreshes. status-jobs re-runs ctx.jobs.list() on every job output event (packages/mayfly/src/transcript/status-jobs.ts:44-48) — a chatty background job triggers a list per output chunk. mode-status re-reads the projection snapshot on every session event, including stream deltas (packages/mayfly/src/interaction/mode-status.ts:44-46).
Acceptance criteria
- Mention candidates are cached/incrementally refreshed between keystrokes; no
statSyncon the autocomplete path. - One control-tree walk per reconcile (shared descriptor build); spatial navigation reuses the memoized frame.
- Overflow-log writes are async/batched off the render path; the 200-entry cap rotates or resets.
-
status-jobsandmode-statusrefreshes are throttled/coalesced.
Audit reference
docs/audits/2026-09-28-ui-ux-audit-redundancy-and-performance.md § UX-33 – UX-36 (baseline 27bc188, 2026-09-28)
Activity
Merge pull request #91 from Ephemeral-AI-Lab/fix/perf-hotpaths-b