✨ Slice E of #848: run and reconstruct one XMD entry in a REPL - #853
Merged
Merged
Conversation
taras
force-pushed
the
agent/issue-848-journey
branch
2 times, most recently
from
September 28, 2026 00:13
7669750 to
fe6b59d
Compare
Slice E of #848: the one-entry journey, and the `xmd repl` command. The four accepted kernels — the durable projection, the execution, the keyed tree and the terminal platform — are assembled here and not redesigned. Closes nothing on its own: this is the slice that makes them a product. The screen always shows the canonical `xmd://repl/...` location it is at, in full — as many rows as it takes, above whatever surface is being shown, because a prefix of a location is no use to somebody trying to come back to a view. How wide a row may be is a question the layout answers: `surfaceWidth()` and `drawerWidth()` say how much room a size really gives, and a row written to a guessed width is reflowed into rows the layout never allocated. A drawer's rows reach its own edges for the same kind of reason: a renderer writes what changed, so a modal that wrote only its own text would let the transcript show through from where that text ended — that is the one thing a person copies out of it, so it is on the screen rather than only printed when the command ends, and it changes as the draft and the route do. `xmd repl` opens an empty draft in a full-screen terminal. Type or paste one XMD entry, submit it, and watch its real durable execution: the scopes it admits, the bindings its eval blocks publish, a generated fragment's source before that fragment is admitted, and each line it renders. When it asks a question, the question's drawer opens — a blocked document is the interaction, not something to go hunting for — and a valid answer typed into it settles the ordinary `elicit` event, after which what the document renders changes because of the *stored* answer. The screen always shows a canonical `xmd://repl/...` location, and the command prints the one it ended at. Pause is a *hold*, not a request: the journey asks for one before the document can reach its question — the provider's outstanding request is work that keeps a walk from ever becoming satisfied, so a pause asked for then never becomes a hold — and waits for the controller to report exactly `paused` at a later expansion gate. Held, neither the journal nor the screen moves across turns; one Continue releases it and the same execution goes on to the question. `[history]` opens the positions the file holds; selecting one freezes the whole view there, read only, with nothing of the present in it — no live output, no waiting question, no pause capability — and `[live]` returns to the head. A position is a different *reading* of the same file rather than a filter over the head, so selecting one reprojects and then verifies there; a route that does not resolve at the position it names leaves the standing one exactly as it was. Pass that location back and a second process reconstructs the same view from the URL and the retained events alone: the same selected topology, the same binding values, the same transcript, the same recorded answer, the same drawer. It reads no component source, compiles no eval block and asks nobody anything — proved by counting at the seams where that work happens, not at the durable operations that replay enters. `application.ts` is the one state boundary. Every action a component can ask for is a closed union; every navigating one builds a candidate route, resolves it against the model, and is adopted whole or not at all — a navigation that selects nothing leaves the route, the selection and the draft exactly as they were and says why. A refusal of one *action* appears in the footer beside the control that was refused; only a view that cannot exist at all replaces the screen. Components receive detached view data and hold no events, no session, no repository and no host operation, which `repl-boundaries.test.ts` checks in the source rather than in behaviour — because a component that happens not to touch the Journal is not a component that cannot. `program.ts` is the command as one scope. It draws when something that a frame shows has changed — a reprojection, the overlay, a resize, an action — and never on a clock: the frame subscription is taken for the frame it is about to draw and released after it, so demand and drawing are the same fact. The order is snapshot, commit, lay out, render, present, retain the map, then acknowledge. One burst of input is one frame, because a paste arrives as hundreds of text events and a loop that drew after each would make a person watch their document appear a character at a time. Four things the integration found in the kernels beneath it, each fixed here: - The tty scanner returns at most **128 events per call** and buffers the rest, so a paste longer than 128 characters was silently losing everything past it. `input.ts` now drains until the buffer stops producing. - A focus claim in a description is re-read at every commit, and this screen commits every frame — so a standing `focus: true` dragged focus back after every Tab and traversal could never move. A claim is where focus *starts*. - A draft cleared at submission lost somebody's document to a preflight refusal. It clears when the entry exists, which is the only moment it is finished. - A corrupt history raised out of the command instead of mounting the refusal. - A focus marker read after the commit that settles it is a frame behind, so a person reaching for a control was acting on the one after it. A frame whose focus moved is drawn once more with where focus actually is. - A one-line summary longer than its column was reflowed into rows the layout never allocated. A line is a line; what was retained is in the drawer, whole — a recorded question's schema and answer are shown in full there rather than summarized. - Selecting a history position never reprojected at it, so every such route was refused. A position is a different reading of the file: the route is adopted, the model reprojected there, and the view verified at that reading — with the standing route restored untouched when it does not resolve. - A derived focus marker is a frame behind the tree that owns focus, so a person reaching for the marked control was acting on the next one. The focused control is marked, because a person has to be able to see where their next keystroke goes. The marker is derived from the mounted tree and is therefore one frame behind it, which is what focus being the tree's answer means. `specs/repl-spec.md` describes the product for a reader deciding how to use it, and `architecture.md` gains the final inventory and the one data flow. The documentation tests compare its command examples to the real parser, its route grammar to the real codec, its Elicit statement to the real reader, and Freedom's provenance to its manifest. The command's grammar is one optional location and no options. A stray option, a second positional and a malformed location are all refused before a per-user directory is formed, a file is created or the terminal's modes are touched. Six defects review found in this assembly, each fixed here: - A cold location was resolved *after* the session opened, and opening one starts or resumes the execution. A URL naming a scope the file never held could therefore replay, ask its question again and append — and still answer that the URL was wrong. Everything a route names comes from records already on disk, so the route is resolved against the retained history first, where being wrong costs nothing. - `paint()` swallowed a refused subscription, reconcile or render and handed back the previous frame. The command stayed in raw mode on the alternate screen showing a picture it could no longer update. They raise now: the scope that owns the terminal is the only thing that puts its modes back. - Nothing observed the overlay's output. A document that only prints reprojects nothing, asks nothing and expands nothing, so its text waited for an unrelated event or for the root to settle. The session announces its output on its own stream and the command watches it. - The terminal adapters installed unconditionally and Node discovered the absence of a tty when `setRawMode` failed — several steps after an empty history file existed. Whether a person is at the terminal is now part of the terminal Api, answered by each runtime from its own descriptors, and asked before a path is formed or a file created. - An accepted answer left `+elicit` in the route and the typed text in the field. The question was over and the drawer was gone, so the printed location named a drawer nothing mounts. `answer()`'s outcome is read, and an accepted one closes the route it was typed into. - `[continue]` was mounted whenever a controller existed, including while expansion was playing or still *taking* a pause — where activating it would cancel the pause somebody had just asked for. It exists only while a continuation is held, and the reduction refuses one that is not. A seventh, found reviewing the first six: `+elicit` resolved against any live head, whatever the Journal held. A URL kept from while a question was up therefore passed the new preflight, replayed the whole run, mounted no drawer because no live question exists, and reported success. Resolution now takes the one fact a model cannot hold: whether this process is asking. A waiting question is precisely what a Journal never records — it records answers — so nothing in a history distinguishes one that is about to ask from one that never will. `settled` looked like that fact and is not: an execution whose question is answered and whose root is merely still open is unsettled, asks nobody anything on reopening, and would have passed. The fact defaults to *not* asking, so cold resolution refuses a live drawer outright and only the process holding the question can open one.
taras
force-pushed
the
agent/issue-848-journey
branch
from
September 28, 2026 00:22
fe6b59d to
bc909c7
Compare
This was referenced Sep 28, 2026
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.
Closes #848.
Why
Issue #848 asks for a REPL that runs one XMD entry and reconstructs it from a
URL. It was delivered as five slices; this is Slice E, the one-entry journey,
and the only one that makes
xmd replexist. Slices A–D (#849, #850, #851, #852)built the durable projection, the execution, the keyed tree and the terminal
platform. This assembles them and does not redesign any of them.
What changes
Before: the kernels exist and nothing uses them. There is no command.
After:
xmd replopens an empty draft in a full-screen terminal. Type or pasteone XMD entry and submit it, and the REPL runs it for real — the scopes it
admits, the bindings its
evalblocks publish, a generated fragment's sourcebefore that fragment is admitted, and each line it renders. When the entry asks
a question, the question's drawer opens; a valid answer typed into it settles
the ordinary
elicitevent, and what the document renders after that changesbecause of the stored answer. Pause holds expansion at its next gate; History
freezes the view at an earlier position, read only; Continue releases the hold.
The screen always shows its canonical
xmd://repl/...location, in full. Passthat location back and a second process reconstructs the same view from the URL
and the retained events alone — reading no component source, compiling no
evalblock and asking nobody anything.
How it works
application.tsis the one state boundary. Every action a component can ask foris a closed union; every navigating one builds a candidate route, resolves it
against the model, and is adopted whole or not at all. Selecting a history
position is a reprojection rather than a filter: the route is adopted, the model
reprojected there, and the view verified at that reading — with the standing
route restored untouched when it does not resolve.
program.tsis the command as one scope. It draws when something a frame showshas changed — a reprojection, the overlay, a resize, an action — and never on a
clock. The order is snapshot, commit, lay out, render, present, retain the map,
then acknowledge. One burst of input is one frame.
Components receive detached view data and hold no events, no session, no
repository and no host operation. How wide a row may be is a question the layout
answers:
surfaceWidth()anddrawerWidth()say how much room a size reallygives.
Review guide
Start with:
specs/repl-spec.md— the product, for a reader deciding how touse it.
Then review:
packages/cli/src/repl/application.ts— the view, the reducer, the surfacepackages/cli/src/repl/program.ts— the one scope and the frame orderpackages/cli/src/cli.ts—xmd repl [location]and its pre-installer refusalspackages/cli/src/repl/storage.ts,repl-assembly.ts, the four*-repl.tsLook carefully at:
paint(): acknowledged only after the bytes are presentedreproject()and the verify-or-restore around itWhat must stay true
Components hold no authority— checked in the source, not in behaviour, byrepl-boundaries.test.ts: a component that happens not to touch the Journal isnot a component that cannot.
Describing or refusing a command reaches no host— checked by a countingReplHostInstallerdriven throughrunXmd(), entered zero times for programhelp,
repl --helpand all three parser refusals, and once when the commandreally runs.
A cold open performs no completed work— checked by counting reads, compilesand provider calls at the seams where performing them happens.
How to verify it
expansion gate (proving the journal and screen do not move across turns),
inspect an earlier prefix, return live, Continue, answer, settle — then capture
the on-screen URL and cold-open it in a fresh host, comparing topology, complete
binding value, complete schema and answer, ordered History markers and output.
malformed location, a control chord, a too-small terminal and an invalid
navigation that must leave route, focus and pointer target untouched.
route grammar to the real codec, its Elicit statement to the real reader, and
Freedom's provenance to its manifest.
Scope
Included
xmd repl [location], its help, and its refusalsspecs/repl-spec.mdand the final architecture inventoryIntentionally unchanged
DurableEventsGenerated or mechanical changes
test-weights.jsoncomes from the Measure test weights workflow, run36350297300against this branch's production commit. It is committedunchanged as a separate commit on this branch once the run finishes.
Risks and limitations
@bomb.sh/tty0.9.0 returns at most 128 events per scan and neverresolves a lone Escape; both are handled at the normalization boundary and
documented at the call site.
Scope confirmation