Repository navigation
⚡️ Reuse the layout work REPL typing and focus did not change (#881) - #889
Merged
Merged
Conversation
taras
force-pushed
the
agent/issue-881-pr4
branch
from
October 10, 2026 09:16
6f5f150 to
db4896f
Compare
taras
force-pushed
the
agent/issue-881-pr4
branch
from
October 10, 2026 09:55
db4896f to
f3024b6
Compare
taras
added this pull request to stack #888
October 10, 2026 10:05
taras
force-pushed
the
agent/issue-881-pr4
branch
from
October 10, 2026 10:07
f3024b6 to
4da6190
Compare
taras
force-pushed
the
agent/issue-881-pr4
branch
from
October 10, 2026 10:08
4da6190 to
6749fa8
Compare
taras
force-pushed
the
agent/issue-881-pr4
branch
from
October 10, 2026 10:19
6749fa8 to
22b9d2e
Compare
taras
marked this pull request as ready for review
October 10, 2026 10:27
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Typing in the REPL does far more layout work than a keystroke needs, and the
work grows with the length of the selected entry rather than with what
changed. Measured on the production commit path, one keystroke beside a
222-line selected entry cost 2,247 engine measurements — for 52 terminal
bytes.
Follow-up to #881's accepted parity implementation. Stacked on #887.
What changes
Before: every dispatched action — including every keystroke — built a view and
ran the whole frame preparation before reducing, and read one field of it.
Source fitting was recomputed from scratch on every frame. A focus move
painted the entire frame twice.
After: an interaction that changes nothing a measurement decides reuses the
geometry it already has.
Five is the fixed cost a complete frame always pays. It no longer grows with
the entry.
How it works
Three independent changes:
readsGeometry, exported besidereduceRepl, says which actions needthe frame's admission. Only the four scrolls do; the reducer reads
admissionin exactly four branches.act()prepares only for those, andskips the
build()and thescreen.size()read for everything else.prepareReadingsplits into geometry and decoration.fitGeometryperforms every engine render and answers where each line is cut and how
wide each piece is;
decoratebuilds rows from the lines as they standnow. A
ReplFitStoreowned byrunReplProgram's scope holds the lastgeometry with the inputs it came from.
the tree, so the loop takes it before composing instead of drawing the old
control and correcting it.
Review guide
Start with:
packages/cli/src/repl/fitting.ts— the geometry/decorationsplit and
createFitStore.Then review:
readsGeometryinapplication.ts, and the gate at its call site inact()prepareFrame/commitReplFrameact()and the first-frame paintRS2andRS3Look carefully at:
independently, and inputs are compared rather than digested.
selection and
inspectedstay fresh without a Journal append.What must stay true
changes —
commitReplFrameis untouched; the store invalidates on sizeand room. Checked by
TL3and the resize journeys.session.reading()is still calledevery frame. This caches presentation work only, never the stale-reading
shortcut rejected in Navigate recorded History positions with a measured REPL rail (#881, 3/3) #887's review.
lagging it. Checked by
RS1.How to verify it
RS2proves a complete frame after a keystroke costs the same for a12-line and a 222-line reading, and fails if production stops threading the
store.
RS3proves typing, erasing, Tab and Backtab each read the terminal sizeexactly four times through the real loop, and fails if the gate is bypassed
at its call site.
S5proves a 222-line reading is measured once and not again, and thatdepth, room, terminal size and one line's text each invalidate on their own
— each compared against a freshly measured fit.
EU2proves no non-scroll action's answer depends on the admission, overall 24 action kinds.
→
52 passed (218 steps)and56 passed (146 steps).Seven mutation controls, each required to break the test it names, are in
.reviewer/issue-881/pr4-evidence/.controls.py.Scope
Included
Intentionally unchanged
appear in the remaining cost.
readingOf(view)still rebuilds every frame. Thatis where live phases come from, so retaining it is a different and riskier
invalidation problem.
New abstractions
ReplFitStoreandReplRowCutexist because the geometry has to be heldsomewhere with the inputs it was computed from, and because separating the
measured half from the live half is what makes it reusable at all.
Risks and limitations
reading tree is rebuilt and decorated per frame. No engine calls, but it
still grows with entry length.
J2's drag is armed on an absolute count of terminal size reads, and thischange removes one per keystroke. Its constant moved from 4 to 3, confirmed
against the instrumented call sequence —
too-small-check → drawnAt → measurement → revalidation— rather than against the test going green.Every assertion in that test is unchanged.
Scope confirmation