Repository navigation
✨ Keep a REPL execution's chat beside its entries (#883, PR 2 of 2) - #921
Conversation
940249a to
879aba8
Compare
PR #921: ✨ Keep a REPL execution's chat beside its entries (#883, PR 2 of 2)202 files, +50413 / -3029 Scope🔴 PR has 53442 lines changed. Split into focused PRs. 🟡 53442 lines changed. PRs under 400 receive more thorough review. 🟡 202 files changed. Are all changes related? 🟡 Changes span 23 directories. 🟡 New abstraction files: packages/core/src/agent/conversation-provider.ts, packages/core/src/agent/provider-configuration.ts. Verify 3+ consumers. StructuralOxlint structural signals:
SlopOxlint slop signals:
Static AnalysisOxlint: 447 diagnostics across 56 files (30 rules) no-shadow (110): packages/cli/tests/fixtures/repl/issue-883/recovery-phase.ts, packages/cli/src/repl/layout.ts, packages/cli/tests/fixtures/repl/issue-883/mounted-program.ts (+20) CorrectnessNo extraneous code patterns detected. |
… PR 2 of 2) Replacement PR2, re-implemented on main's captured-selection model rather than rebased. C's remaining accepted behaviour — the paired chat storage, recovery, the conversation owner, provider configuration and the direct provider, the Sidekick surface, Sessions, controls, questions and the historical submission correction — carried onto the accepted replacement PR1 at 85da74b9. The bound-owner and root-vocabulary hunks that went into PR1 are not applied twice. What main contributes, and what changed to meet it: - A conversation's controls are captured once, when it opens. C passed observations and mutations with every evaluateGeneratedXmdRoot call; main removed that public request. Each control this CLI defines is now published as a component answer under its own name, the selection naming them is offered from the same installation, and canonical capture resolves and seals them before any response exists — so a response's request carries only which evaluation it is, the bytes, and the classes it may draw on. The provider answers each owned name once per execution and then settles, because a provider still answering while a response resolves its admitted name is substituting into that response, which canonical execution refuses. Per execution and not per owner: a reopening, a replacement and a switch are each a fresh capture. - What stays each response's own is where a control's pair is filed and which slot a Submit binds. That travels through the owner's serving holder, named for the length of one evaluation and given up in an `ensure` registered before the work — so it outlives the evaluation's own cleanup and a control settling late still files under the response that ran it. A control invoked when no response is running refuses rather than filing under whichever came next. - <Elicit> is selected, not pinned. The Architect decision removed the pinned* factories from the public surface, and nothing is lost: components/Elicit.ts already offers its paired spelling as a write under https://executable.md/components/elicit/ask, so selecting the protected name captures core's own definition and the form and the class are the declaration's. pinnedElicit() is therefore gone from core; a repository, registered or middleware component of that name still reaches a reply not at all. The controls tier compares the surface's Elicit props against the registry's own schema, so a core component that gained a prop fails the row rather than leaving a reply under-told. - Each control's identity and effect live on the definition the capture reads, under https://executable.md/components/repl/sidekick/<key>, rather than being stated beside the entry. One declaration says what each admitted spelling runs as. - The vocabulary rename lands: AgentSessionSetup, AcpxSessionRequest, ReplLayout, AnswersState and the rest. Where main had moved first — the Dir grant identity, validateBodyStructure, ordinaryGeneratedSelection, PlanWriterPlacement, the audit and session-identity prose — main's is kept and PR2's references follow it. IN5's old-chat fixture is run-produced. It is the bytes a real run of the pre-integration writer at d4b0e326 left behind, captured before this branch existed and checked in with its receipt: one completed response, one interrupted response holding a settled REPL.EntryInput beside a pending <Elicit>, and both root evaluations marker 2 on their own generated_root:N sequences. Reopened cold it changes no byte, sends no prompt and replays no suffix; what it said is still what it says; and a new message continues the same native conversation, allocates generated_root:3 and writes marker 5, with no replacement or migration record and the old control not repeated. Three dead exports and one dead type go with the port, because main's dead-code gate is new relative to C: settled(), executionShape(), openPairedEntries() and ExecutionShape. Eighty-one banned prose words and two new lint rules — a suspending finally, two redundant test scopes — are corrected for the same reason. One fixture adaptation beyond the mechanical ones: Tier HI's `runs()` waited for the entry's text to appear on screen, but the source is typed into the draft, so that text is there from the keystroke — the wait returned while the entry was still being admitted and the next read saw the entry before it. It now waits for the recorded model to hold the entry, which is the fact the rows assert about. EH1 and UI10 in repl-entries.test.ts remain red, byte-identically to the recorded baseline, and command 6 is reported red for them. Refs #883
#883) Four bounded corrections to replacement PR2, inside the accepted architecture. The four new casts are gone, with nothing standing in for them. The control builders take a whole `FunctionComponentDefinition` and get their generated metadata from `offering()`, whose table is annotated as the partial record `GeneratedForms` already is — which is what makes a computed form key check against `InvocationForm` instead of widening. `canonicalProps` narrows the schema with core's own `isJsonObject` twice rather than asserting a shape, and IN5's subprocess chat is read by `chatKinds()`, which narrows each item and reports an unreadable one rather than taking it on trust. IN6's capture and association evidence is complete, as four rows and one extension: - R3m: two owners' controls answer their own request handles. The same source in both, so what tells them apart is the handle each control closed over. - R3n: a definition answered after setup reaches no response and runs nothing. The interception is demonstrably in the document's own chain — it is asked for the root and the holding element — and is never asked for the control's name at all, because a generated admission resolves an admitted name from the sealed definition and does not consult the document's chain for it. - R3o: a selection composed after setup widens nothing. The omitted name is `File`, an ordinary component an entry may be given: a structural construct like `<Each>` would have proved nothing, since the language refuses one wherever it is written whatever any table says. - R3p: the control for both, with nothing intercepting. - R5 "a later response reads a fact that changed": a real entry publishes a root binding between responses, and a third response reads the new value through the control sealed when the conversation opened. A control closed over a reading taken at setup would still answer the empty map. - R5 "each response files its own ordered pairs": one response's read and its failed fill, in admission order under its own response, and the next response's control at order zero under its own — read off the retained history, because a count of prompts cannot say which response a record belongs to. - The held-cleanup Stop row now runs an actual control in the successor and reads the association back: the successor's read is the one answered pair, the held fill stays unfinished under the response that was stopped, and nothing of that response's control is filed under the successor. A late outcome moving is exactly what a response naming cleared before its cleanup had joined would produce. IN5's identity assertion no longer reads the old turns. `canonicalTurns()` goes through `parseEntryRecords` and `parsePromptRecord` to name the turn this message became, and asserts *that* turn's native identity. The fixture already holds `native-old` twice, so the old substring search held even when the new turn ran somewhere else — which a break-it control confirms: pointing the new turn at another conversation now fails with `Expected: "native-old" / Received: "native-somewhere-else"`. The row also compares the conversation records before and after, so no second conversation is prepared or activated, and checks the old file is still a prefix of the new one. The lost output-stream observation is restored where the Planner allocated it: E1a now consumes `execution.output` through completion and requires the document's own text in the chunks and the root's distinctive text absent from both the chunks and the stream's close value. PR1's production code, branch and worktree are untouched. Three stale comments are corrected: `profile.ts` and `questions.ts` said `pinnedElicit()` admits the question, and `respond()` said the ceiling is established per turn. The fixture bytes and receipt are unchanged, byte for byte. Refs #883
#883) Closes the one remaining R-E1 observation. The controls-suite rows reach the canonical capture directly — each opens a HostExecution, evaluates a root and closes it before the next begins — so they say nothing about two conversations being alive together. A Sidekick assembly that handed a second conversation the first one's captured controls satisfied every one of them. The new journey row uses two whole owners. Each harness is held open in a task of its own, because two cannot be acquired in one scope: each registers the agent components, and the engine refuses two registrations of a name at one scope rather than letting one silently win — it says to shadow in a nested scope, and a task is one. Each owner's work runs in that same scope, since the REPL host each harness installs is installed there, which is also what a command does. What distinguishes the two handles is a real REPL fact rather than anything written into the reply: each session has run one entry of its own, and both owners are sent the same control source byte for byte — a read naming `entry-1`, which both sessions have holding different text, and then a write. Observed: both conversations open and distinct before anything; the second owner's whole cycle reading its own entry and filling its own draft while the first owner's draft stays untouched; the first owner then left suspended inside its own fill while the second runs a second cycle and still reads its own; the first released and answering from its own REPL; three turns on one provider and five on the other; and each chat holding its own controls in admission order, under its own conversation and response, naming the other conversation nowhere. A break-it control confirms it discriminates. Sharing one captured ceiling across owners, first-opened winning, fails this row with `Expected value: "Read it." / Received array: ["Open.", "", "Thanks."]` while the whole sequential controls tier still passes. R3m keeps its evidence and loses the claim it could not support: it is now "each capture answers the handle its own installation sealed", and its tier says where the live-owner observation lives. No production code is changed. Refs #883
…#883) The Product Owner asked for Bindings and Sidekick as horizontal tabs, each name said once, and space between the top of the screen and the headings of all three columns. The right pane's two tabs are now its header: one row, Bindings then Sidekick, each as wide as its own name. The pane's separate title is gone — it said Sidekick twice while the Sidekick was the reading — and so is the literal `[selected]` suffix, which the amendment supersedes. Which tab is selected is the active styling the two have always differed by, and keyboard focus stays the separate mark it was. Each of the three shared columns keeps one blank row above its first heading, as real layout padding, so the engine's own remainder is what the rows below are measured against. It holds no text, reaches no pointer and is no Tab stop. The narrow outlet heads no shared column and takes no gap. That row moved the contextual guidance into the rectangle a route drawer's size alone used to state, which would have stopped the screen saying how to close the drawer in front of it — already true at 120x30 before this change. A route drawer now begins below the whole *fitted* guidance block, measured from the rows the frame published rather than counted. Its columns and bottom edge are unchanged, so the difference is spent on drawer-window height. Tier UH1-UH5 observe the one header row with selection and focus apart, both tabs reached by pointer and keyboard, the gap holding nothing reachable, resize through narrow and back, and the guidance unoccluded at three sizes with a wrapped block. Selection evidence had to change with the label: both names are drawn on every frame, so `shows("Bindings")` is true of every screen either reading ever drew. `paneShown`/`panePresented` read the selected pane from the body it draws.
… them (#883) The Product Owner asked for the Transcript and Sessions bodies to have the same two-cell padding the Sidekick's body already had, for the Transcript's title to line up with what is under it, for the blank row above a heading to belong to that heading, and for Earlier/Later to stop reading as dark action bars. Every shared column now publishes two boxes: the pane, which its heading band spans, and the body inset two cells on each side, which every row under that heading is built to. The two are four cells apart and answer different questions, so the manifest says which of the two each box is for, and each body is found by its own id — the narrow outlet is one region for all three readings, and a lookup by region handed the Transcript's directions the chat's width. Guidance, refusal, reading and label fitting, the row boxes, the window counts and the published targets all read the body that contains them. The sidebar is 36 columns at both shared profiles, up from 32 and 28. Inset at the old widths it could not hold a turn's state: `fitLabel` keeps that part whole because a state cut in half is a row saying something untrue, and beneath that guarantee is a hard cut the inset reached — at the medium profile it was already being reached. Thirty-six leaves 30 columns past the focus marker, the room the wide column had before the gutter, so the same rows fit at both sizes. The Transcript and the right pane divide what is left, odd cell to the Transcript. The Transcript's title is inset by the engine's own padding inside a band that still spans the column and draws its rule. The header band owns the blank row above its heading and paints it, which moved ownership and no coordinate. A `navigate` role gives a window's two controls the pane they move instead of the draft's surface, keeping their ink, their focus cue, their target and their action. Tier BP1-BP5 observe the three matching bodies and their inert gutters, a wrapped reading reaching its whole text through its own window, the mounted consumers and narrow, the sidebar's longest rows whole at both sizes, and the inset title, header-coloured gap and pane-coloured navigation.
A successful reply that selected no Output said nothing to the person, so the chat shows nothing for it: no sentence about the absence, no empty row and no bare agent name. The work is still a saved turn in Sessions and still has its own records. A reply that *failed* keeps its recorded reason whether or not it selected Output — the screen-facing projection was dropping the retained failure, so a failure's visibility had been depending on what the response happened to say. A Sessions reveal now picks its row from the composed and fitted rows the frame will draw, so it cannot target a row nothing paints. Where a turn's reply said nothing, what it reveals is the request that turn answered: its own, or, for an automatic follow-up, the person request its chain began with, read from the request boundaries the chat projection already establishes. A turn whose request this history cannot bound reveals nothing rather than a row chosen by resemblance.
Escape closed a route drawer and the Sidekick's question form and refused everything else, so the one modal a person is most likely to press it in did nothing. It now closes the presented Configure by the same transition its visible Close control produces — written once, because two ways out of a modal that disagreed about what closing means would make the outcome depend on how somebody found the exit. Which drawer owns the key is the frame's own answer rather than the held open flag, so a Configure behind another pane or under History consumes no Escape: History closes first and exposes the choices underneath. Configure's window controls now read scroll up and scroll down. Earlier and Later are words about a recorded order, and this is the one window in the program that moves through a list of settings instead. The actions, their order and their scrolling are unchanged, and every other reading keeps its own vocabulary.
…em (#883) Integrating #883's accepted sidebar width with the REPL work main gained since. The thirty-six-column sidebar and the two-cell body gutters leave the Transcript six columns narrower, so sentences that used to end on one row now wrap onto the next — and seven assertions written against a row reported a complete sentence as a truncated one. Nothing is lost on the screen and nothing was weakened here. Guidance keeps its continuations as `guidance:row:1`, a caption keeps its own as `caption:output+33`, and each row keeps the space the fitting cut at. Each reader now joins the rows its reading was cut into: the entry reading's guidance, its Output caption and a failure reason, and, in the two PTY journeys, a URL and a Git object id that are cut mid-token where a column ends. A truncation still fails every one of them. The two Git modules #883 renames carry main's newer importers with them: `run-composition/placement.ts` is `checkout-layout.ts`, and five files main added since reach it under that name. The conversation provider's context gets the explicit type JSR requires of a public symbol, which is the one thing `check:jsr` had to say about this stack.
) Three failures required CI to find, because no frozen selection runs them: `repl-terminal.test.ts` is in none of them, and the two refusals below only happen on the runtimes that install no repository or issue provider — Node and Bun. `repl-terminal.test.ts` asserts the whole frame's geometry as an independent table, and still held the pre-#883 shares. It now states the accepted ones: a thirty-six-column sidebar at both profiles, with what is left split equally — 36/62/62 from 160 and 36/42/42 from 120. The implementation was asked for these numbers and agrees with the table. The two runtime refusals — no Repository composition provider, no Issue provider — are sentences, and the narrower Transcript wraps them, so they are read across their rows like the others. Verified on the exact runtimes that failed: both PTY files under Bun and Node, and the frame file under Deno and Node.
…883) Two rows read a settled fact off whichever frame happened to be current. A startup focus claim and a draft cleared by an admission are both resolved in the frame that carries them, and on a loaded CI machine that is not always the frame that arrives first — so both passed locally and failed in CI twice, which is a divergence rather than a flake. Both now wait for the screen they are about, through the fixture's own `awaiting`. Neither claim moved: the startup row additionally states that the Sidekick's box does *not* hold focus, which is the half that was only implied before. Verified under Bun as well as Deno, Bun being where the admission row failed.
c10a206 to
8b8c66d
Compare
…#883) The one check still failing CI, and the flake recorded as #935. The row samples the lifecycle after a fixed five `sleep(0)` turns, but the phases a consumer is told travel through its own queue and the question is published from inside the element — so how many turns that takes depends on what else the runner has queued. With another suite beside it the sample catches `Elicit:enter:`, the phase immediately before the one the row is about. It now waits for a signal deliberately narrower than its claim: that *some* element has reported waiting on something. Which element, which phase, what it waits on and that it is the only one are all still asserted whole, so a wrong phase or a second waiting element still fails. Discriminating evidence, same revision either way: with the fixed count the six-file selection failed once in three runs; with the wait it passed two of two, the two-file selection three of three, and the file alone. The sibling loop below it keeps its fixed count, because that one asserts the reading stays empty and more turns only strengthen it.
Closes the second half of #883.
Why
A person running a document in
xmd replhad no way to ask about it from insidethe REPL. They had to leave the command, lose the execution they were reading,
and ask somewhere with no access to the entries, the generated source or the
history. PR 1 made a host that can evaluate, preview and converse. This PR gives
the REPL a Sidekick: a conversation that lives beside the entries, under the
agent and configuration the person chooses, and that can read the execution it
is talking about.
What changes
Before:
conversation history, and no way to reach an agent from the command.
After:
are saved, replies stream as they arrive, and previous chats are readable
through Sessions.
own vocabulary and seven shipped teaching documents, and can ask the person a
question that is answered in the pane that asked it.
conversation only under the configuration that conversation has.
odd cell going to Transcript; the Sidekick body is inset two cells each side.
then scrolls internally, reduced on small screens.
How it works
A REPL execution owns one chat history. A message reserves an entry slot through
the same single decision every other submission uses, so Pause is offered from
the moment Run reserves it and a submission is admitted only when its entry
really was. The turn is established against the records for its own request,
which is what lets a cancelled, interrupted or refused turn be told apart from a
failed entry and recovered at its boundaries.
Frame preparation retains plain-text line cuts per line, keyed on text, measured
width and terminal size, in storage the running command owns, and skips chat
fitting entirely when the measured frame presents no chat viewport.
Review guide
Start with:
packages/cli/src/repl/sidekick/owner.tsThen review:
packages/core/src/agent/conversation-provider.ts,provider-configuration.ts,session-setup.ts— the conversation contractand what a saved conversation may continue under.
packages/cli/src/repl/sidekick/history.ts,model.ts,requests.ts— thechat, what a reply reads, and how a turn is established.
packages/cli/src/repl/sidekick/admission.ts,gate.ts,packages/core/src/generated-root-owner.ts— entry reservation, the singleslot decision, and the generated root's own environment.
packages/cli/src/repl/{application,program,layout,fitting}.ts— panegeometry, the composer and retained fitting.
Look carefully at:
retains no close, and an entry scope owns its agent cleanup.
the drawn form owns the keys while the top drawer owns its controls.
immediately; cached cuts never authorize drawing text the current display
facts have cleared.
What must stay true
A reply answers from the acknowledged history, not the screen— enforced bysidekick/history.tsand checked byrepl-sidekick-journey.test.ts.Each chat keeps its own reading; scrolling an older chat cannot move the current one— enforced by per-chat viewport state keyed on chat identity andchecked by CF-P1 in
repl-sidekick-presentation.test.ts.An unchanged chat adds no text-fitting work to a keystroke— enforced byReplPlainFitStoreand checked by RS3–RS5 inrepl-presentation-style.test.ts.Send carries the original literal string; wrapping is display only— enforcedby keeping the original string the sole editing value and checked by MC6.
Transcript and the right pane share the space after the sidebar, odd cell to Transcript— enforced byinspectionWidthinlayout.tsand checked by PW1.A saved conversation continues only under the configuration it has— enforcedby
provider-configuration.tsrefusing an unstatable configuration with anError, and checked byrepl-sidekick-configuration.test.ts.How to verify it
The three focused selections released with this slice's assignment, run against
this exact commit (
d906c636), all green:deno task checkis clean.typed frame, and fails if a key refits history it did not change.
97 − 14 = 83), and failsif earlier lines are refit when the newest Preview line grows.
and a busy Send, and fail if the value and chat rows overlap again.
the chat visible, and 151 to 4.6 hidden. These are local diagnostics, not
thresholds; the RS3–RS5 count assertions are what establish the fix.
Rendered manually in a real tmux terminal at 160x36 and 120x30.
Scope
Included
reading.
Intentionally unchanged
Across the whole PR:
REPL_DESCRIPTIONandSIDEKICK_QUIETstay out of scope, as directed by theassignments.
Sidekick's own.
This PR does add, as its accepted contracts: the conversation-provider contract,
the paired chat history beside entry History, and the
sidekick=chat selectoron the route. The final presentation increment is what introduces no journal,
route or provider interface change and no new stored width — that exclusion
belongs to that increment, not to the branch.
New abstractions
ReplPlainFitStore(repl/fitting.ts) exists because every keystroke refitall chat text — 1,284 measurement renders per frame at twenty wrapping
messages. Consumers: chat, composer and the guidance/refusal directions, each
with its own store so one cannot evict another.
packages/cli/src/repl/sidekick/*— fifteen modules separating owner,history, model, admission, controls, questions, recovery and repository.
Consumers are the REPL program and its tests.
packages/core/src/generated-root-owner.tsandpackages/core/src/agent/{conversation-provider,provider-configuration,session-setup}.tscarry the host-side contract this slice needs from PR 1's host.
Generated or mechanical changes
packages/git/**(44+/44−),packages/workflow/**(19+/19−) andpackages/test-agent/**(5+/5−) are the session/layout/structure vocabularyrename from
9f15dc51, with no intended behavior change.packages/cli/src/documents/sidekick/*.mdare the seven teaching documents areply reads (
c8c689e7); their index is derived, not hand-written.Risks and limitations
work conflicts with
main's ✨ Compose and certify the supervised adversarial implementation workflow #181 U1–U4 prerequisites and Remove unused repository code and reject new dead code in lint #903's dead-coderemoval in roughly twenty files. That resolution belongs to the stack's
rebase, not to this branch, and
main's side is not the answer for theCore files: Add an interactive Sidekick to
xmd repl#883's Core behavior is independent of those changes.full-suite delivery run. Delivery verification, the teaching walkthrough and
the 36-frame capture packet are separate.
through Sessions while the Sidekick has a pending question leaves the chat
viewport zero rows high: twelve accepted fixed rows exhaust the twelve-row
body. Giving the chat a row means giving up one of them — the composer's label
row, a window control, the question's two rows, the seven-row footer or the
supported minimum size — so it is returned as a product decision rather than
chosen in implementation, as the released composer contract directs. The
composer's
Math.max(1, capacity)is the clamp involved."build" turn (🐛 Flake: #870 U2 permission row intermittently misses the "build" Agent turn in the full repl gate #874) and the REPL lifecycle L2 question phase. Re-run the job
rather than chasing them.
outside these three selections.
Scope confirmation
Delivered onto current main (2026-10-16)
This branch is the replacement for the original stack. Head
879aba84,eight signed commits, stacked on #890 at
dbc0fc92, which is itself on mainfd8ae521(#927). It retargets tomainonce #890 merges.Five of the eight commits were reviewed before this restack; three are the UI
corrections the Product Owner asked for after it was opened, each reviewed on
its own exact SHA:
is what keeps
completed, not recorded yetandAll conversations (filtered)whole at the medium profile;
<Output>is out of the chat — nosentence about the absence, no empty row — while a failed reply keeps its
recorded reason whether or not it spoke;
[close]controlproduces, and Configure's window controls read scroll up / scroll down.
range-diffagainst the reviewed source reports seven of the eight commitsidentical. The first carries the integration: main's entry-capability selection
and host install beside this PR's
--sidekick-agentprofile option, main'sentryInstallationbeside the Sidekick profile members, and the two renamed Gitmodules (
run-composition/placement.ts→checkout-layout.ts) carrying thefive importers main added since.
The eighth commit is new and is the integration this PR owes main's newer tests.
The 36-column sidebar leaves the Transcript six columns narrower, so sentences
that used to end on one row now wrap onto the next. Nothing is lost on screen —
guidance keeps
guidance:row:1, a caption keepscaption:output+33, and eachrow keeps the space the fitting cut at — so seven assertions now read their
reading across the rows it was cut into rather than off the first one. A
truncation still fails every one of them. This is the EH1/UI10 obligation
resolved rather than waived; UI11 and UI13 turned out to be the same class.
Evidence on this exact head — the seven frozen selections:
Plus the eight consumers main supplies for its moved repository and runtime
boundaries —
repl-boundaries,repl-repositories,repl-repositories-pty,repl-issues,repl-issues-pty,run-composition,testing-execution-hostand
testing-execution-host-native: 17 passed (97 steps).314 tests, 1,482 steps, zero failures.
deno task check,deno task lintanddeno task validate:docsexit 0, anddeno task check:jsrendsSuccess Dry run complete— which needed one fix of its own, an explicit typeon the agent conversation provider's context, since a public symbol cannot have
an inferred one.
repl-lifecycle's L2 question row failed once and passed on re-run; it is therecorded flake for that phase, and the first failure is preserved.
Deferred and deliberately not here: History self-stacking, conversation labels,
the narrow pending-question layout, Parse #922 and #928–#933.
What CI found that no frozen selection runs (
d8890e40)Three more failures, all the same accepted width meeting readers that assume a
row — and all outside every local selection, which is why only CI saw them:
repl-terminal.test.tsasserts the whole frame's geometry as an independenttable and still held the pre-Add an interactive Sidekick to
xmd repl#883 shares. It now states the accepted ones:36/62/62 from 160 columns and 36/42/42 from 120. The implementation was asked
for those numbers and agrees.
Issue provider — are sentences the narrower Transcript wraps, so they are read
across their rows. These only happen on the runtimes that install no such
provider, which is why a Deno run cannot reach them.
Verified on the exact runtimes that failed: both PTY files under Bun and Node,
and the frame file under Deno and Node.
The last failing check was a cause, not a re-run (
7e7ffd27)repl-lifecycle.test.ts's L2 row sampled the lifecycle after a fixed fivesleep(0)turns. The phases a consumer is told travel through their own queueand the question is published from inside the element, so how many turns that
takes depends on what else the runner has queued — and with another suite beside
it the sample catches
Elicit:enter:, the phase immediately before the one therow is about. It is recorded as flake #935.
It now waits for a signal deliberately narrower than its claim: that some
element has reported waiting on something. Which element, which phase, what it
waits on, and that it is the only one are all still asserted whole, so a wrong
phase or a second waiting element still fails.
Same revision either way: with the fixed count the six-file selection failed
once in three runs; with the wait it passed two of two six-file, three of three
two-file, and the file alone. The sibling fixed-turn loop below it keeps its
count, because that one asserts the reading stays empty and more turns only
strengthen it.
This branch was rebased onto
mainat98cd350b(#890's merge) after that PRmerged. Verified faithful: all ten commits identical by
range-diffand thetree byte-identical to the previously published head.