Skip to content

[UX] Performance hot paths: per-keystroke rescans, repeated compiler tree walks, unthrottled refreshes #72

Closed

Description

@GeekCmore

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 statSync on 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-jobs and mode-status refreshes 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

  1. added a commit that references this issue on Sep 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions