Skip to content

Editor rework: explicit modes, line navigation, durable accepts, undo/redo (spec 1.0+1.1) - #45

Open
Vadim1987 wants to merge 9 commits into
aldum:devfrom
Vadim1987:deliver/editor-stage1
Open

Editor rework: explicit modes, line navigation, durable accepts, undo/redo (spec 1.0+1.1)#45
Vadim1987 wants to merge 9 commits into
aldum:devfrom
Vadim1987:deliver/editor-stage1

Conversation

@Vadim1987

@Vadim1987 Vadim1987 commented Jul 21, 2026

Copy link
Copy Markdown

Restructured at dsent's request: nine self-contained commits on top of
current dev, one feature each, the suite green at every commit
(693 → 760). Read in order — each commit message explains its piece.
Content is the device-tested editor rework, releases 1.0 and 1.1 of the
behavior spec.

What it does

  • Explicit nav/editing modes replace the implicit edit flag; the
    statusline always shows the mode, Esc semantics per spec.
  • Line-wise navigation: an active line, Ctrl+Up/Down block jumps,
    bare Home/End to the file edges, page moves, peeking on Ctrl+Alt.
  • One gate out of an open block: dirty-check → validate →
    accept/refuse; acceptance writes to the file immediately and stays on
    the block. Typing in navigation opens the block at the active line.
  • Durable writes: per-accept fsync plus lifecycle flushes
    (dsent's work) — an accepted edit survives a force-stop, which
    matters on the device's exfat/FUSE storage.
  • Checkpoints: Ctrl+K writes {file}.~save, Ctrl+Shift+K restores
    with a repeated-press confirmation, revert() from the REPL.
  • 14-line block limit with an actionable message; auto-format on
    open; unparseable files open read-only.
  • Mouse: click selects, click inside places the cursor, double
    click opens, a click away goes through the same acceptance gate.
  • Undo/Redo (1.1): two levels — word-grouped text history inside an
    open block; a 32-step ring of file operations in navigation
    (accept, move, delete, insert, discard). A discarded-but-valid draft
    is recoverable; checkpoint restore is a boundary. Shift+Esc on a
    changed block asks first.
  • Refusal feedback: one knock sound for every refused action, a
    frame around the message, cursor to the first error.
  • Editor-specific behavior (word deletion, edit history, the refusal
    frame) is scoped behind an explicit flag on the shared input widget —
    the console, project inputs and search keep the plain widget,
    regression-pinned from their side.

Review notes

  • The hotkey layout normalizes toward conventions (table in
    doc/EDITOR.md); Ctrl+W now deletes the previous word (readline),
    leaving a block is Shift+Esc.
  • Repeat swallowing for command keys (spec §7) waits on the Editor API with callback(s?) under the compy namespace compy-toys/compy#77 input
    routing and is intentionally absent here.
  • Found while benchmarking, pre-existing at 945a5d1:
    BufferModel:_text_change calls rechunk() twice back to back —
    looks like a copy-paste; happy to fix in a follow-up if you confirm
    it's not intentional.
  • Device-tested on hardware (typing, acceptance, checkpoints on the
    live FS, chord glyph suppression, sound, undo paths); that pass is
    what surfaced the fsync work.

If this is easier to review split by stage (modes / navigation / gate
/ checkpoints / mouse / undo), say so and I'll break it up — the
history is linear and cuts cleanly.

@Vadim1987

Copy link
Copy Markdown
Author

Depends on #46 (the FS durability API, split out at dsent's request; this branch carries the same API commit so it stays self-contained).

@Vadim1987
Vadim1987 force-pushed the deliver/editor-stage1 branch from c1184a8 to 16eb33d Compare July 24, 2026 13:24
dsent added a commit to dsent/compy-ide that referenced this pull request Jul 27, 2026
hleb-rubanau added a commit to hleb-rubanau/compy-dev that referenced this pull request Sep 4, 2026
Owner direction: build on top of PR aldum#45 so the two ship together, the
edge's remainder afterwards, and ensure no fundamental conflicts. There
are none. A trial merge in a throwaway clone outside /repo runs at
1100/22 against our 1055 and aldum#45's own green 753, and every one of our
six failures is the editor route's key semantics — which aldum#45 redefines on
purpose — while nothing in compy.input's machinery moves.

Three things the trial settled that reasoning would not have. set_text
was tested both ways: ours plus their history reset is 1100/22, theirs is
1094/28, and the six extra are our own content contracts — so ours is the
merge, and the sixteen editor failures are identical either way. Those
sixteen are the probe's own --ours on controller.lua, not a defect: aldum#45's
editor work reaches into the framework's power-shortcut block, and the
real integration is to express its reservations as entries in our
RESERVED table, which is what that table is for. And the two test
harnesses are not interchangeable — adopting theirs wholesale adds 145
errors in our specs and fixes none of theirs.

One genuine two-answers-one-key conflict: bare Ctrl+S in the editor, ours
closing the buffer against their checkpoint. Our own D-EXACT-RESERVE work
picked the same ctrl+s / ctrl+shift+s pair as its worked example, and the
exactness rule is what makes deciding the two members separately legal.

Suite 1055 / 0 / 0 / 10 (LuaJIT 2.1) in /repo — untouched throughout.
hleb-rubanau added a commit to hleb-rubanau/compy-dev that referenced this pull request Sep 4, 2026
hleb-rubanau added a commit to hleb-rubanau/compy-dev that referenced this pull request Sep 4, 2026
Order: the example repos first because they are free, the platform merge
before the device passes because the rework moves keys a hand-run
checklist exercises, the edge remainder after the release.

R1 is the owner's rather than ours, and it is the one worth reading
first: merging aldum#45 while aldum#45 is still open puts its 52 commits inside our
own PR's diff, which defeats the reviewability gate the release is
measured against. The mitigation is coordination — aldum#45 lands first — not
code.

Three risks are already observed rather than imagined. A positional
signature merged wrong and failed two layers away, in the trial. The two
test harnesses are not interchangeable, measured. And the phase's boot
baseline of 1055 becomes wrong by construction the moment both suites
share a tree, so the merge commit states the new arithmetic and moves the
pointer with it.

The plan also says what it does not decide: bare Ctrl+S, the landing
order, and whether five editor-route specs are re-pinned by us or by the
author of the redesign that moved them.

Roadmap rows catch up: maze measured across every branch, keyboard first
and free with its renamed upstream noted, balloons frozen by owner
attestation, PR aldum#22 ignored.

Suite 1055 / 0 / 0 / 10 (LuaJIT 2.1).
hleb-rubanau added a commit to hleb-rubanau/compy-dev that referenced this pull request Sep 4, 2026
…and run

The honest answer to "did you analyze all drifts by essence" was: aldum#45
yes, keyboard yes at its scale, maze not applicable, the edge no — it had
a conflict prediction and a reading of subjects. This closes that.

The reading found what a merge cannot. The Android exit commit reroutes
every full exit so the device returns to its launcher first, and one of
the call sites it rewrites is the Ctrl+Escape handler this branch moved
into the reservation table. The rewrite lands on the old location, our
reservation does not conflict with it, and it keeps calling
love.event.quit() directly. The defect is in the line that does not
conflict, one screen above the one that does, and it fails only on a
device. One line fixes it, and the fix is in the built tree.

Two more that no headless suite can clear: the render-cost fix deletes
lines from the very view function we also edited, and the terminal
repaint gate makes drawing conditional. Named for the device pass, not
cleared. The prompt label widening is verified to work through our
constructor, so it is a surface decision rather than a break.

The stack was built rather than argued: ours 1055, on the rework
1100/22, with the whole edge on top 1108/22 — the same 22. The edge adds
no new failure.

Two corrections. The remainder is 15 commits, not 16: the filesystem
durability API is already ours. And the plan's "merge, never rebase" is
replaced — aldum#45 may be force-pushed, so the merge is disposable
reconciliation and the shipping artifact is a patch set generated against
whatever aldum#45 is at generation time, with every base pinned.

Suite 1055 / 0 / 0 / 10 (LuaJIT 2.1) in /repo, untouched.
hleb-rubanau added a commit to hleb-rubanau/compy-dev that referenced this pull request Sep 4, 2026
Three landscape clarifications from the owner land on three rows. aldum#45 is
the target base by content and may be force-pushed for commit hygiene, so
MERGE-01-05's "merge, never rebase" becomes "the merge is disposable
reconciliation" — the shipping artifact is a patch set generated against
aldum#45's head at generation time, with every base pinned and the stack
re-run rather than the patches merely re-applied.

PR-01-01 says the same at the point where it bites: the slices have
always been patches, and they are cut against aldum#45's head, not against our
merge.

MERGE-01-06 gains the essence reading and the stack result — 15 commits,
1108/22, no new failure — and its three findings, the first of which is
the class this whole exercise exists to catch: a change that merges
cleanly and stops working, because the line it rewrites is one we moved.

Suite 1055 / 0 / 0 / 10 (LuaJIT 2.1).
hleb-rubanau added a commit to hleb-rubanau/compy-dev that referenced this pull request Sep 4, 2026
…y hash

The owner's question was right and it corrects two published numbers, in
the direction of less work.

aldum#45 forked from aldum/dev on 2026-07-09 and is seven commits behind its
head — the fs durability API, the editor's checkpoint fs info, the
terminal repaint gate, the 64-slot palette, termcolor, the colours
example, black's bright slot. We have all seven, because we are zero
behind dev. So our real base once aldum#45 lands is dev + aldum#45, and the patch
set must be generated against that: against bare aldum#45 it would carry seven
commits that are already upstream. The stack trial already used this
shape, since merging aldum#45 into our branch gives dev + aldum#45 + ours.

The second correction is a method error. I counted "commits in A not in
B", which answers a question about hashes, while the two lines cherry-pick
between each other. git cherry compares patch-ids: four of the edge's
fifteen are already ours under different hashes, and three more are
alternate versions of colour work we carry — which is why the colours
example collides as an add/add instead of applying. Eleven changes, not
fifteen commits.

It also raises R2 rather than lowering it: aldum#45 must be caught up at least
once, and it is behind two drawing changes in a rework that is itself
about drawing. That is a merge with content on both sides, not a hash
rewrite — so "aldum#45 changes" is scheduled, not hazardous.

Suite 1055 / 0 / 0 / 10 (LuaJIT 2.1).
hleb-rubanau added a commit to hleb-rubanau/compy-dev that referenced this pull request Sep 4, 2026
…form is built

The worry was that aldum#45's shape cannot be known until it is force-pushed
because it now collides with dev. It does not collide. Both forms were
constructed and run: dev alone is 693/0, dev with aldum#45 merged is 760/0
with no conflicts, and aldum#45 rebased onto dev replays all 52 commits with
zero conflicts to the same 760/0 — dropping two itself, reporting their
contents already upstream, which is the cherry-pick duplication confirmed
by the tool rather than by inspection.

The two results are not merely both green. The trees are byte-identical,
so the integrated content of aldum#45 on current dev is fully determined; only
its commit shape is not. And our branch meets the rebased form with
exactly the same four conflicts it meets today's head, so nothing in the
analysis is contingent on the force-push.

What is left unpredictable is narrower than it looked: commit boundaries
and hashes, which nothing we ship depends on, and any new content added
for review feedback, which is the only thing that can move our numbers.
Re-running the stack costs minutes.

Suite 1055 / 0 / 0 / 10 (LuaJIT 2.1) in /repo, untouched.
hleb-rubanau added a commit to hleb-rubanau/compy-dev that referenced this pull request Sep 4, 2026
The owner's scheme — squash aldum#45's content into the branch, work against
the future-merged tree, deliver patches derived from content — is sound,
and the unconventional part is safe for a stated reason: delivery by
content makes our ancestry no part of the product, so the squash's usual
cost is one we never pay because we never merge this branch anywhere.

Three additions. Use merge --squash, not diff-and-apply: a two-way apply
has no base and fails in the four collision files. Adopt the invariant
that makes the scheme checkable — diff(updev+aldum#45, HEAD) is our work and
nothing else — and re-check it after every corrective commit, which is
also the generation command for the deliverable.

And the one that is not optional. Where our reconciliation drops aldum#45's
content, the patch set carries a silent revert of upstream work,
delivered as our change, and no conflict marker warns about it: the
merge is resolved, the suite is green, and the deletion reads as an
ordinary line of our diff. Demonstrated on the trial tree, where taking
our side of controller.lua wholesale produces 340 deleted lines including
aldum#45's checkpoint and Ctrl+Shift+S handling. The gate is to read and
justify every deletion in the ten shared files.

Also flagged for the owner: the import commit is red by construction
against a rule that says commits are green, and slicing by file is a
different product from the concern-sliced scheme this phase built.

Suite 1055 / 0 / 0 / 10 (LuaJIT 2.1).
hleb-rubanau added a commit to hleb-rubanau/compy-dev that referenced this pull request Sep 4, 2026
The owner was right to be confused: "the conflict surface is small" and
"hundreds of removed rows" cannot both be true, and the metric was wrong,
not the surface.

git diff <updev+aldum#45> HEAD counts every line our branch removes, and
almost all of those are our own restructuring removing pre-existing code
— the inline keypressed body that became the reservation table. Those
deletions are our work and belong in the patch set. The number said
nothing about upstream work being lost.

The right question is how many of aldum#45's own additions are absent from our
result. Measured at the crudest defensible resolution — merge --squash,
keep our side of every conflicted hunk — it is 49 lines out of the 1005
aldum#45 adds to those four files: 35 their Enter rewrite, which must be kept;
10 the editing flag and their set_text branch, part of which we drop on
purpose and measured; 3 the Ctrl+S comment, which is the decision in
prose; 1 a mock export we simply forgot to union.

So the gate becomes a command rather than a habit, and a review of a page
rather than of a patch set: for each shared file, list the lines aldum#45
added that are missing from ours, and account for each.

Suite 1055 / 0 / 0 / 10 (LuaJIT 2.1).
hleb-rubanau added a commit to hleb-rubanau/compy-dev that referenced this pull request Sep 4, 2026
Corrects my own advice. I said "merge --squash, not diff|apply"; the
owner asked whether that was merely about inlining divergence instead of
rejecting hunks, and suspected the recommendation dragged git machinery
into a content-level operation. Measured, and the instinct was right.

The patch is 19 files and 92 hunks. Bare git apply refuses five files —
with --reject, eight hunks — and one of those five, consoleController,
has no real conflict at all: the rejection is context lost to our own
edits nearby. It is also all-or-nothing, so one bad file discards the
whole application.

git apply --3way and git merge --squash produce the same tree and the
same four conflicted files with the same six hunks. So staying in the
patch idiom is fine; what must not be used is the two-way form, which
invents conflicts.

The reassurance the question was really asking for: merge --squash
records no merge parent and no MERGE_HEAD. The result is an ordinary
commit whose content includes aldum#45's changes. It does not make git believe
aldum#45 is merged, and it does not contaminate the content-level model.

Suite 1055 / 0 / 0 / 10 (LuaJIT 2.1).
hleb-rubanau added a commit to hleb-rubanau/compy-dev that referenced this pull request Sep 4, 2026
…file

Owner asked for the anchors we worked through to be recorded before the
wrap. They were scattered across five notes and a dozen commit messages,
which is where facts go to rot.

Companion to TAGS.md rather than a second registry: that file stays
authoritative for the local tags, this one carries the remotes, the
upstream refs, the four open PR heads and where they are fetched to, the
four merge bases, the derived dev+aldum#45 tree that everything is generated
against, and the example repos with the keyboard local-branch trap that
has now survived two sessions.

Three things in it are the kind that get re-derived wrongly later: the
seven commits dev has that aldum#45 lacks and we already carry, the two git
itself drops on rebase, and the four edge commits that are patch-equal to
ours under different shas. Each is listed by sha.

It also carries the submodule recipe, because a fresh clone fails 38
specs without it and reads like a defect, and the suite numbers for every
tree we built, each stating its interpreter.

Every sha is dated a snapshot; the commands are the durable part.

Suite 1055 / 0 / 0 / 10 (LuaJIT 2.1).
hleb-rubanau added a commit to hleb-rubanau/compy-dev that referenced this pull request Sep 4, 2026
…ine moved

Session71 executes MERGE-01-05: import PR aldum#45 as one commit, allowed to
be red by owner ruling, then reconcile one concern at a time. The prompt
carries the four resolutions rather than leaving them to be re-derived,
both mechanical gates, and the one that no tool prompts for — the audit
for aldum#45 content our resolution drops, which would ship as a silent revert
of upstream work inside our own patch.

The baseline line gains the warning that matters more than the number:
1055 is about to change by design, the rehearsed tree ran 1122 cases, and
the session that lands the import states the arithmetic and updates that
line in the same commit that reaches green. A stale baseline is read here
as a go-signal.

The report keeps four things worth more than the outcome: a change can
merge cleanly and still stop working, and the defect hides in the line
that does not conflict; count changes rather than commits when two lines
cherry-pick; two of this session's own metrics were wrong and both were
caught by the owner asking how a number was derived; and a commit message
asserting an edit is worth nothing without the diff.

Suite 1055 / 0 / 0 / 10 (LuaJIT 2.1).
hleb-rubanau added a commit to hleb-rubanau/compy-dev that referenced this pull request Sep 4, 2026
Verdict: yes on purpose and conduct, no on the state the roadmap was left
in. Every transition was owner-directed and the dispositions were worked,
but MERGE-01-05 was redefined mid-session from "the edge" to "the aldum#45
import" and its section's standing prose was never re-read — so the
sprint told the successor the opposite of what the successor's own prompt
tells it, on the one step it exists to execute.

Applied before handover, because these are corrections to documents this
session wrote hours ago and a successor should not inherit known-wrong
text: the merge plan's row 3 (a merge commit gated at zero failures, both
halves superseded by the squash and the red-commit ruling), the import
strategy's red-versus-green question the owner has already answered,
MERGE-01's two closing paragraphs and its standing mechanic, the REC-01
and MERGE-01 headers and summary cells, the stale Session-55 status
paragraph, DOC-01-07's detachment from its own table, and the two PROP-01
carve-outs that were owned by no row — the Escape ruling is now PR-01-07
and reachable before the release rather than only after it.

Four counts of the same shape the session had already corrected twice:
114 to 113, ten risks to twelve, sixteen commits to fifteen, and
DOC-01-07's "two more" unwanted hits to three — the third being a heading
this same session added later the same day. Each now carries its command
and a do-not-cite rather than a replacement number.

Two that outlive this session: F6's proposed pre-PR gate is recorded on
T-EPHEMERAL-IDS so it survives a break in the prompt chain, and S69's F8
finally reaches a successor prompt, with the evidence that it was needed.

Suite 1055 / 0 / 0 / 10 (LuaJIT 2.1).
dsent and others added 9 commits September 4, 2026 10:29
The Keys tables in README.md and doc/EDITOR.md were stale and mutually
inconsistent. Correct them to the behavior verified against the shipped
editor build 1b86c90: toggle edit/run = Ctrl+T (was F8/F9), stop project
= Ctrl+S (was Ctrl+Shift+S), quit project = Ctrl+Q (EDITOR had
Ctrl+Shift+Q), leave editor = Shift+Esc / Ctrl+Shift+S, drop the stale
editor Esc/Ctrl+S/Ctrl+Y rows, and add the input clipboard/selection
keys.

These tables document commit 1b86c90. The editor-stage1 (2eed100) keymap
update follows as a separate change.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_0177PwS4Xws9YFAGqJMADY5c
…work spec

The editor's implicit `edit` flag becomes two first-class
modes: `nav`, where the arrows walk the file, and `edit`,
where an open block sits in the input strip. Transitions
are declared in one table in set_mode: nav reaches edit,
reorder and search; the special modes and edit only return
to nav. A freshly opened buffer always starts in nav.

Keys, per spec 2.2/2.3:
- Enter on an empty input opens the selected block; Enter
  while editing accepts it
- bare Esc does nothing (it also defuses the device's
  RMB->Esc binding silently replacing typed input)
- Shift+Esc discards the edit; on an empty input it leaves
  the editor. Ctrl+W no longer leaves a block
- Ctrl+S is reserved for the checkpoint: saving is
  automatic on accept, so bare Ctrl+S does nothing
- Ctrl+Delete drops a block in navigation only; while
  editing it stays the widget's delete-next-word
- typing straight into a fresh file inserts a new block
  and never replaces the first one (replace needs a block
  deliberately opened)

Files open at the first block, the view following the
selection; the cursor position is remembered per file for
the session and restored on reopen.

The statusline receives the real mode. A mode tag was
tried and dropped: the mode is evident from the input
strip itself, and the tag collided with the left label.

Co-Authored-By: Claude <[email protected]>
The buffer model gains an active line under the block
selection: an absolute source line, always inside the
selected block, clamped on every selection mutation, with
move_line stepping across block boundaries. The view
draws it and follows it on scroll.

Keys, per spec 2.2/2.7:
- bare Up/Down move the active line; PageUp/PageDown
  move it by a viewport page; Home/End go to the file's
  first and last line; opening a block lands the cursor
  on the active line, not on line 1
- Ctrl+Up/Down jump block-wise with the paragraph
  convention: Down to the next block's first line, Up
  first to the current block's first line
- Alt is the peek: Alt+Up/Down scroll a line, Alt+
  PageUp/PageDown a page (Alt+Left/Right too — PgUp/PgDn
  is a four-key chord on the device keyboard), Alt+Home/
  End to the file's edges, the selection staying put in
  both modes; typing after a peek returns the view.
  Ctrl+Alt keeps working as a synonym. Alt does not move
  blocks: that is the reorder mode's job (Ctrl+M)
- inside the input widget bare Home/End are line-scoped
  and Ctrl+Home/End reach the whole block (dsent)
- Ctrl+J follows the require under the selection; Ctrl+O
  is left free for a conventional "open file"
- returning from a followed require restores the view to
  the stored position, as opening a file does

The textinput guard widens to any Ctrl/Alt chord: device
chords leak glyphs, only Shift composes.

Co-Authored-By: Claude <[email protected]>
Co-Authored-By: dsent <[email protected]>
…ith a knock

One path out of an open block, shared by Enter, the arrow
edge crossing, Ctrl+Up/Down from editing and the mouse:
dirty-check, validate, auto-format, re-chunk, size check,
write. Acceptance in place keeps the block (2.4.4); the
arrow transition opens the neighbor — downward on its
first line, upward on its last (2.4.1). An untouched
block leaves freely and writes nothing; an invalid one
refuses and stays.

- opening a Lua block runs it through the pretty-printer
  (9.4): a sloppy block is reshaped in the input, dirty
  from birth, while the file stays untouched until
  acceptance
- the block size limit is the input strip's height (14
  lines); the refusal names how many lines to remove and
  the way out, per 1.4
- an eval refusal moves the cursor to the first error's
  line and shows the parser's message until the first
  fixing keystroke (2.5); the refusing keypress is blocked
  from the widget so it does not clear its own message
- every refusal knocks (2.4.3): a rejected block, a jump
  or page move at the file's edge, a click away from an
  invalid block, Ctrl+J with nothing to follow — one
  neutral sound, since walking off the end of a file is
  not a mistake
- typing in navigation opens the block at the active line
  with a blank line to type into (2.1); on a blank line it
  composes a new block. Ctrl+Enter opens a fresh block
  below (Ctrl+Shift+Enter above) and accepts while editing
- the acceptance pipeline lives in controller methods
  (accept_block and friends), reachable from any input,
  and reports its verdict so a refusal blocks the widget
- writes are durable: the accept path fsyncs the file, and
  a failed write refuses with a message instead of reading
  as accepted (dsent, after real data loss on the device's
  exfat SD card)
- typing right after deleting the last block anchors the
  input properly (dsent)

Co-Authored-By: Claude <[email protected]>
Co-Authored-By: dsent <[email protected]>
…) in the REPL

Ctrl+K copies the file to {name}.~save; Ctrl+Shift+K copies
it back and reloads the open buffer. Both raise a dialog
when there is something to lose — an existing checkpoint,
a file about to be overwritten — showing the timestamps of
the checkpoint and the file (FS.getInfo). While editing,
Ctrl+K accepts the open block first, so the checkpoint
reflects the screen; a refused block aborts it.

The REPL gains revert(name) (default main.lua): restore
without a prompt, false when no checkpoint exists.

Co-Authored-By: Claude <[email protected]>
…double click opens

BufferView:line_at(y) maps a pixel row through the scroll
offset and the wrap to a source line; BufferModel:
block_at_line finds its block. In navigation a click
selects the block and line; a double click opens it, the
mouse counterpart of Enter. While editing, a click inside
the open block places the input cursor on that line, and
a click outside goes through the acceptance gate exactly
as an arrow transition does: an untouched block leaves,
a changed one is accepted and written, an invalid one
refuses and keeps the editor.

Buffer-area clicks route to the editor through the
console; input-strip clicks keep going to the widget.

Co-Authored-By: Claude <[email protected]>
Text level, inside an open block: EditHistory keeps a
32-step ring of {text, cursor} snapshots taken before each
mutation. Consecutive same-kind edits at the expected
cursor coalesce and a typed whitespace starts a new step,
so Ctrl+Z removes the word just typed — no wall-clock
timers, deterministic and testable. The history is born
with the block (set_text resets it) and never leaks across
blocks or into the file.

Block level, in navigation: a 32-step ring on the buffer
records every file write as a trimmed diff — the common
prefix and suffix are cut, leaving exactly the affected
line range. One recording wrapper (record_write) serves
every write site: acceptance, insertion, deletion, the
reorder move. Applying a step is a splice plus the usual
re-chunk and save; selection restores to the step's
remembered side. The history lives on the buffer, so it
survives a follow-require round trip and dies with the
file; a checkpoint restore rebuilds the buffer, which is
the boundary. A new write kills the redo tail.

Keys: Ctrl+Z / Ctrl+Y in both modes; bare Delete drops a
block in navigation now that it is undoable (2.7), without
touching the clipboard — cutting is Ctrl+X alone, since on
the device every clipboard write pops the share overlay.

Dialogs are repeat-proof by construction, as agreed after
the device pass: the confirming key differs from the
invoking one. Discard (Shift+Esc on a changed block),
checkpoint overwrite and restore all confirm on Enter or
Space and cancel on anything else — the invoking chord and
Esc included — so a held key oscillates ask/cancel and
never fires. One executor runs the confirmed action; the
handlers only raise. A parseable draft discarded with
confirmation leaves a recoverable pair in the block
history: one Ctrl+Z puts it in the file, another takes it
out. A plain error message closes on Enter, Esc or
Shift+Esc without re-submitting, matching the REPL.

Co-Authored-By: Claude <[email protected]>
…editing flag

UserInputModel:backspace_word eats the word before the
cursor together with the whitespace in front of it, as
readline does, on Ctrl+Backspace and Ctrl+W (spec 2.7).

UserInputModel is the whole platform's input — the
console, the project inputs the games read, and search
all build on it — so the editor's extras must not leak.
An explicit `editing` flag on the model (off by default;
only the editor model opts in) scopes them: word deletion
and the refusal frame gate on it, the edit history
records nothing without it, and the widget's Ctrl+Y
delete-line stays for the console (in the editor the
controller takes Ctrl+Y for redo first). Regression specs
pin the contract from the console's side: a flagless
model records nothing, Ctrl+Backspace deletes one
character, Ctrl+W does nothing, Ctrl+Y deletes the line.

Co-Authored-By: Claude <[email protected]>
README.md and doc/EDITOR.md describe the final layout:
line-wise navigation with Ctrl-arrow block jumps, Home/End
to the file's edges, the Alt peek, Ctrl+Enter blocks,
undo/redo, bare Delete, word deletion, the dialog keys,
checkpoints and Ctrl+J. The input widget's Home/End rows
follow the line-scoped fix.

Co-Authored-By: Claude <[email protected]>
Co-Authored-By: dsent <[email protected]>
@Vadim1987
Vadim1987 force-pushed the deliver/editor-stage1 branch from 16eb33d to f4cf338 Compare September 5, 2026 09:32
hleb-rubanau added a commit to hleb-rubanau/compy-dev that referenced this pull request Sep 9, 2026
Owner ruling, 2026-09-04, on the recon's own findings: maze did not move
ahead and keyboard moved ahead only by an unrelated packaging commit, so
neither smoke pass has to be gated on a merge, and neither merge is this
release's work.

(a) MERGE-01-01/-02/-03 CLOSE WITHOUT ACTION — skipped as a result of
    recon, recorded as a ruling rather than dropped (rules/roadmap.md §5:
    omission is not a ruling). What is left in MERGE-01 is the platform:
    -04 done, -05 the aldum#45 import, -06 the edge remainder.

(b) PREX is new, at the tail, after the platform PR — preparation of the
    external examples' own pull requests. It is not a rename of the closed
    rows: it holds work that had no home because it sat inside rows about
    something else. Five rows, and the owner states its scope may widen:
    keyboard's packaging commit (was MERGE-01-02, retires T-DRIFT-KEYBOARD
    there), the keyboard remote rename (was MERGE-01's R10), FIX-02-09's
    nested-repo remainder (was FIX-02 (b)), maze/balloons PR preparation,
    and opening the three PRs (was the example half of PR-01-04).

Consequences read and carried, rather than left in their other homes:

- ACC-02's ordering paragraph — the conflict it resolved is gone. The
  example repos are cleared to smoke now; what still precedes every device
  pass is the platform import, which moves keys the checklists exercise.
  REC-01's "owes that document" condition is marked discharged.
- FIX-02 (b) loses -09 entirely, and the constraint that put it there
  ("the merge would bring fresh violations in behind the sweep") is struck
  with its reason, not silently.
- PR-01-04 narrows to the platform PR; what stays is the R1 landing order.
- The summary table's MERGE-01 and FIX-02 (b) cells, and the one-line
  sequence, which gains PREX.
- T-DRIFT-KEYBOARD stays ACTIVE and its Revisit now names the example PR
  as the moment it is paid, in words rather than by sprint id.
- S70-merge-plan.md §1 rows 1-2 struck with the ruling, and the "free
  because they go first" ordering claim superseded: free was the wrong
  axis, whose release owns the work is the right one.

No .lua touched. Suite unchanged at 1055/0/0/10 (busted tests, container
LuaJIT 2.1; the owner runs PUC Lua).
hleb-rubanau added a commit to hleb-rubanau/compy-dev that referenced this pull request Sep 9, 2026
Owner ruling, 2026-09-04, superseding the 2026-09-03 placement: the
release absorbs the proposals that survive weighing, so PROP-01 runs
between the platform merge and ACC-02 rather than after the PR. In plain
language the near sequence is now: owed rulings -> the aldum#45 import and its
conflicts -> green again -> the promoted proposals (disposition, design,
implementation) -> smoke.

Why the earlier ruling is not simply reversed: its concern was that rows
before PR-01 are sized against the current surface. The move answers that
by placement -- PROP-01 now runs ahead of every row that is sized against
the surface -- rather than by deferral. What it costs instead is that rows
already finished against the old surface are re-read, and PROP-01-07 owns
that re-read by name (CHG-01's changelog, the guide FEAT-01/-02/-03 wrote).

The sprint's first row gains a second axis: accept/dispute/reshape/decline
now also carries in-this-release or after it. Deferral stays a legitimate
outcome with a stated reason; it is not a silent drop.

Consequences carried into the rows' other homes, not left behind:

- PR-01-07, the Escape ruling, dissolves into PROP-01-01/-05 and is struck
  with a crosswalk. It was carved out only because the implementation sat
  after the PR. Which sprint holds it was explicitly left to the owner
  (S70 delivery review, F5), so this is flagged for confirmation.
- PR-01-06 keeps the block's destination, narrowed: PROP-01 rewrites what
  the block says, so assembly disposes of the remainder.
- DEC-02 gains the reason it now runs after: if PROP-01-03 lands, Decision
  37 is reshaped before release, and the vacuum inherits that prose rather
  than sweeping prose about to be overwritten.
- ACC-02's section and summary cell: two surface-moving sprints precede it
  now, and the smoke checklists are re-read after both.
- FIX-02 (b) states why the move does not disturb it.
- The section itself moved to sit between MERGE-01 and PR-01, so the file's
  order matches execution order here (rules/roadmap.md §2).
- S70-proposal-block-placement.md carries a supersession note; its
  classification stands, its placement answer does not.

No .lua touched. Suite unchanged at 1055/0/0/10 (busted tests, container
LuaJIT 2.1; the owner runs PUC Lua).
hleb-rubanau added a commit to hleb-rubanau/compy-dev that referenced this pull request Sep 9, 2026
…y contradiction

Owner, 2026-09-06: "we are not moving forward on roadmap and I am not asking
Vadim until I fully understand the merge impact, its reasons and possible
mitigation."

`MERGE-01-05` landed green and surfaced more than it fixed. `OP-03` is opened
as a GATE -- analysis, discussion and judgement, producing mitigation options
rather than a verdict alone -- and nothing advances past it.

`MERGE-01-09` is HELD, its date withdrawn the morning it came due. The reason
is now on the entry as `A3`, and it is the most useful thing in the
inventory: old aldum#45's keymap tables were stale against old aldum#45's own CODE, and
the force-push corrected the tables while changing no executable content. So
the two questions for upstream are instances of one habit -- documentation
and comments lagging behaviour -- rather than two accidents. Asking from a
half-understood premise is exactly how the last false premise entered this
corpus.

The inventory (`validation/reviews/S75-merge-contradictions-inventory.md`)
separates three things the conversation had been mixing:

- A: contradictions inside aldum#45 (theirs, held).
- B: contradictions the import created in OUR corpus, including one nobody
  had recorded -- aldum#45 also moved the editor's opening selection from the last
  block to the first, which is why a re-pinned test's `up` keystroke is now
  dead and its assertion was loosened.
- C: the product owner's OWN proposal contradicting what we merged. "Escape
  hides, and does not clear" calls clearing on Escape "the P1 data-loss
  hazard" and says the editor's widget keeps its own Escape -- which is
  exactly what the fall-through took away. Neither author asked for the
  merged behaviour; it is an emergent property of the union.
- D: SIX classes nobody has looked at, named as unexamined rather than as
  findings. The heaviest is D1, the systematic version of the one decision
  we found false by accident, and D3, which explains why none of this can be
  felt: no device pass enters the platform editor at all.
- E: the severity question stated plainly -- two behaviour losses on the
  editor route, neither covered by any planned pass.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01K9QM2sW9jq5a4F1KAwnHmo
hleb-rubanau added a commit to hleb-rubanau/compy-dev that referenced this pull request Sep 9, 2026
`validation/outcomes/S75-post-wrap-peer-review.md`. All four verified against
the tree before acting. The reviewer confirmed every load-bearing claim the
roadmap stop rests on -- Ctrl+K vs Ctrl+S, the byte-identical comment, the
zero executable-content delta, the 7/9 attribution, the proposal quotes,
1123/0/0/10 -- and then found four defects in how they were written down.

1. THE WITHDRAWAL BLOCK MISDESCRIBED ITS OWN TARGET, and this is the one that
   matters. It said "this report states, twice, that aldum#45 reserves bare Ctrl+S
   for its checkpoint". The report never said that: `grep -nic
   "reserves.*checkpoint"` over its body returns 0, here and at the
   pre-correction commit. The false claim reached four CORPUS sites, not this
   document. A withdrawal that misdescribes what it withdraws is the same
   believed-not-checked failure this range exists to correct, committed
   inside the correction.

2. THE "51 FUNCTIONS" COMMAND WAS NOT RUNNABLE. The note elided its middle
   with `...`; the reviewer reconstructed it and got 56. The figure is right
   -- the full pipeline, now written out, returns 51 -- but an elided command
   is an unreproducible number, which is the defect this session has already
   been caught on twice.

3. THE EDGE'S ORPHAN RESULT IS UNINFORMATIVE, NOT CLEAN, and the correction
   is worth more than the result. The edge adds four functions -- `FS.exists`,
   `FS.getInfo`, `love.draw`, `love.keypressed` -- names generic enough that
   our tree calls something of that name for unrelated reasons, and its
   Android work lives in `src/util/application.lua`, which does not exist in
   our tree at all. The check is only as strong as the specificity of the
   names it walks: informative against aldum#45's `mouse_select` and
   `backspace_word`, worthless against `love.draw`. `MERGE-01-06`'s cell now
   says so.

4. D6 WAS FILED AS UNEXAMINED WHEN IT IS ALREADY MEASURED. The `81` sweep
   figure is the delivery review's F2, complete with both counts. Removed
   from the inventory's unexamined section with a note saying why -- it stays
   a real correction owed at F2, and listing it under D would have sent the
   next session to re-measure finished work.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01K9QM2sW9jq5a4F1KAwnHmo
hleb-rubanau added a commit to hleb-rubanau/compy-dev that referenced this pull request Sep 9, 2026
…work did not

`validation/reviews/S75-post-wrap-delivery-review.md`. Verdict: the gate is
proportionate, but for a reason nobody wrote down -- SEQUENCING. The next row
is `PROP-01`, whose job is deciding what the release absorbs from proposal #4,
which is materially the same question `MERGE-01-07` must rule. Advancing would
have decided one question in two rows and smoked the tree at `ACC-02` before
knowing whether two behaviours revert.

Six findings applied, all verified against the tree first. Four are defects in
the handover session76 boots on, which is the worst place for them.

F1, and it is the one that would have hurt: the prompt filed "R11 and R12 are
EDGE risks and are not in our tree" under "must not be re-derived". FALSE for
R12. Its named instance is edge-only, but its hazard class is in our tree
already -- aldum#45 changed three view files (+55/-2) and our merge took them whole
(`git diff f4cf338 HEAD -- src/view/editor/bufferView.lua` is empty). The
bullet would have cancelled D4, a class the same prompt tells session76 to
close. A false claim filed as un-re-derivable, in the document that opens a
gate against exactly that failure.

F3: "six unexamined classes" was wrong three ways. D holds two genuinely open
items -- D1 and D2. D3 was answered by this session's own `R4` re-read, D4 and
D5 by the first delivery review's F5, and D6 was already withdrawn yesterday.
Three of six miscategorised is itself a finding about how the inventory was
assembled: at speed, from a conversation, without re-reading what the session
had already measured. The inventory, the prompt and the `OP-03` cell all say so
now.

F4 cuts AGAINST the revert, which is why it matters: B1 attached a nine-citation
blast radius to a claim the merge CONFIRMS. The nine citations reach
`D-EDIT-LIFECYCLE` for "Enter and Escape are ordinary keys, not a framework
tier" -- and a key no participant claims now falls through, which is exactly
what that decision says should happen. The false sentence is one paragraph,
`decisions/input.md:372`. Nine was a 9x thumb on the scale.

F6: D2 named two documents; there are three. `README.md` -- the repository's
front door -- was rewritten 41/41 by our own merge and now carries aldum#45's keymap
tables, with no row for bare Escape in nav and none for Ctrl+Shift+S.

F2 and F8, both in the prompt: it forbade opening `PROP-01` while requiring a
recommendation that is materially proposal #4's substance (resolved by carrying
`MERGE-01-07`'s own line, "evidence for the ruling, not the ruling"), and the
rewrite dropped "open by executing the delivery review's dispositions", leaving
F2-F8 owned by nobody.

F7: `MERGE-01`'s summary cell still said two rows remained after four were open,
and the sequence line read circularly. Both corrected.

Not applied, and recorded as the reviewer's own defence rather than a finding:
the twice-corrected withdrawal block is healthy self-correction. The second
correction cost a paragraph; not making the first would have shipped a false
premise in a ratified decision and the shipping guide.

busted tests -> 1123 successes / 0 failures / 0 errors / 10 pending.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01K9QM2sW9jq5a4F1KAwnHmo
hleb-rubanau added a commit to hleb-rubanau/compy-dev that referenced this pull request Sep 9, 2026
… the guard

The entry said "aldum#45 has no cancel_flow". Imprecise in the way that matters:
upstream cancels on Escape and excludes the editor route by a conditional in
the same function — keypressed branches on `love.state.app_state == 'editor'`
and the editor arm omits cancel() deliberately. Identical in upstream dev
(af9a578) and in aldum#45 (f4cf338).

Our own affc932 (Phase R, "the app_state un-fork, option E") deleted that
branch and, in the same commit, moved the exclusion into editorController's
block_input(). The guard was not removed, it was relocated into a subsystem
this branch does not own and upstream was actively reworking. aldum#45 then changed
that controller and the expectation stopped holding — silently, because an
expectation cannot fail.

Consequence for MERGE-01-07: a third option exists beside "ship it" and "the
editor claims the key" — restore the exclusion on the widget side, where
upstream kept it. It is a design ruling (D-CHAIN-OF-3 and option E both argue
against a route test in the widget), but it was never on the table.

Suite unchanged at 1123 / 0 / 0 / 10; this commit touches no .lua.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01K9QM2sW9jq5a4F1KAwnHmo
hleb-rubanau added a commit to hleb-rubanau/compy-dev that referenced this pull request Sep 9, 2026
…questions

A3 was the hold on MERGE-01-09. The inventory stated it as "aldum#45's comments and
tables lag its behaviour as a habit", which treats both as weak evidence. The
measurement says the lag is asymmetric:

- The force-push was a documentation-correction pass. In doc/ and at the root it
  changed exactly two files, both the same keymap table, and no executable
  content. The corrected tables hold: 18 rows of doc/EDITOR.md's editor section
  walked against the code, 18 match.
- The comment that misled this corpus survived that pass untouched.
  `git diff --stat 16eb33d f4cf338 -- src/controller/controller.lua` is empty;
  the comment dates to 5125d36, 2026-07-11, and was wrong when written.

So a table at the current head is good evidence and an in-code comment is none.
That removes the "which artifact do we believe" question entirely and leaves
only intent, which rewrites all three questions:

- Q1 (bare Ctrl+S) becomes a product question, not a documentation one.
- Q2 (Ctrl+Shift+S) flips. The binding is upstream dev's, not aldum#45's, and the row
  was dropped BY the correction pass — so deliberate retirement is now likelier
  than accidental loss, the reverse of what T-PR45-ASK-UPSTREAM argues. The
  entry's evidence (the edge still carries the row) is chronology, not intent.
- Q3 is new and is the one under MERGE-01-07: upstream excludes the editor route
  from cancel() by an explicit conditional, so bare Escape there is not unowned.

Nothing is sent. MERGE-01-09 stays held until the owner approves the wording.

Suite unchanged at 1123 / 0 / 0 / 10; this commit touches no .lua.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01K9QM2sW9jq5a4F1KAwnHmo
hleb-rubanau added a commit to hleb-rubanau/compy-dev that referenced this pull request Sep 9, 2026
…count exposed in our tree

The owner supplied two attestations to OP-03 on 2026-09-06, both in Russian.
Summarised in English under validation/notes/ — this corpus is English and an
attestation nobody can read is not evidence. They speak to different things and
the note keeps them apart: @dsent states where the product is going, Vadim1987
states what aldum#45's code does today.

Vadim's account gives us the routing contract we were about to derive by
sweeping: the editor always consumes Shift+Esc, consumes bare Escape only in the
dialog/error/reorder/search states, and Ctrl+S and Ctrl+Shift+S live above the
editor. Bare Escape in nav and edit does nothing, probed by him. That confirms
T-NAV-ESCAPE from the other side — our behaviour appears where its author
expects silence — and it overturns this session's own A3 inference that dropping
the Ctrl+Shift+S doc row was deliberate: the binding is inherited cruft that
predates the spec.

It also exposes a defect that is ours. Vadim reports that Ctrl+Shift+S bypasses
the acceptance gate on his layer. Our _leave_keys supplies the same chord at
route level and reaches the same finish_edit(), and the path is confirmed:
close() clears the input and drops the buffer dequeue with no acceptance step,
so an open changed block is lost silently. Registered as
T-LEAVE-KEYS-LOSES-BLOCK. The binding is upstream's and carried the defect
there, but this branch re-homed it during MERGE-01-05 while arguing only about
which layer owns the reservation, never about what the call loses. Nothing in
the suite covers it.

Suite unchanged at 1123 / 0 / 0 / 10; this commit touches no .lua.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01K9QM2sW9jq5a4F1KAwnHmo
hleb-rubanau added a commit to hleb-rubanau/compy-dev that referenced this pull request Sep 9, 2026
…ecommended

The gate's output. Three controversies exist between aldum#45 and this feature, all
three are defects in our files, all three have fixes that touch no editor
dispatch, keymap or spec. The release ships both features together without the
migration that would formally close the seam class.

Root defect: D-EDIT-LIFECYCLE says "the console sets no lifecycle callbacks so
its flows are no-ops", and that mechanism does not exist — run_callback cannot
veto on an unset callback and cancel_flow's model:cancel() is hardwired past it.
The design named the hazard and specified a defence that never functioned,
leaving every host on block_input(), which lives in the other feature's file.

Two things recorded as NOT the cause, both proposed during the session: the
agent/component duality is second-order and is the ratified interim state per
D-ROUTE-OWNS; and the open seam is how the editor gets its text field, not a bug
— what was wrong is that the widget smuggled its lifecycle through it.

Decision: cancel_flow delegates (editor becomes inert with zero editor edits,
console re-supplies its Escape, project contract preserved); Ctrl+Shift+S KEPT
under the owner's evidence test — the guide, the README walkthrough and one of
our specs all carry it — and its data loss fixed instead; D-EDIT-LIFECYCLE
amended; an editor section added to the smoke checklists, which also pays F5's
unsmoked view-file debt.

Deferred by owner ruling: the flags matrix, (cancel, submit) x (hide, clear),
which resolves PROP items 3, 4 and 6 as one design rather than three bullets.
A single clear_on_cancel property would answer one cell and pre-empt three.

Migration recommended with four measurements rather than an intention: the
machinery was extracted for it, hooks carry the non-combo-shaped logic, the edge
touches editorController by zero lines, and 140 of the author's own test cases
came with the import. Two earlier drafts of this analysis were wrong on points 2
and 3 and are corrected in place.

Also records D2's first concrete hit: README and doc/EDITOR.md teach different
exits for the same walkthrough step, and the front door teaches the one that
loses an open changed block.

Suite unchanged at 1123 / 0 / 0 / 10; this commit touches no .lua.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01K9QM2sW9jq5a4F1KAwnHmo
hleb-rubanau added a commit to hleb-rubanau/compy-dev that referenced this pull request Sep 9, 2026
…ome)

Owner ruling, 2026-09-06. The widget's two lifecycle verbs have two possible
outcomes each, and the four combinations become four independent project-owned
settings: clear_on_submit, hide_on_submit, clear_on_cancel, hide_on_cancel.

The space was always four cells and the shipped API covered two of them by
unrelated means — auto_hide was hide_on_submit under another name, and cancel's
clear was hardwired inside cancel_flow rather than being a setting at all. The
two empty cells are exactly proposals #3 and #4, so naming the matrix answers
three proposal bullets as one design instead of three surface additions.

Every flag defaults off, so a bare widget with no flags and no callbacks does
nothing on either verb. That is the property D-EDIT-LIFECYCLE already claimed
and did not have, and making it the base case matters as much as the four names.
Owners seat their own defaults per instance, which is the ratified idiom
(disable_selection, allow_duplicate_line).

The clear keeps model:cancel()'s remember-then-clear semantics — the abandoned
text reaches the input history before the line is emptied, which the console
depends on. The destruction is a flag rather than a default callback because
callbacks must stay additive: if the clear became the default after_cancel, a
project setting its own would silently lose it.

Supersedes D-AUTO-HIDE, overruling its statement 3 (cancel gains a hide cell);
auto_hide stays a deprecated alias for one release because a downstream this
release cannot test already writes it. Amends D-EDIT-LIFECYCLE, whose false
sentence becomes true once the hardwired clear is flag-driven.

Six acceptance criteria, the first the owner's: the editor's Escape must match
aldum#45's expectations — bare Escape inert in navigation and editing, per its
author's attested routing contract.

Status is DECIDED, NOT IMPLEMENTED. Registered as T-LIFECYCLE-FLAGS, and
T-NO-CALLBACKS-IS-NOT-A-NOOP is restated as a defect with its fix shape, per the
owner's second ruling: we already decided it must be inert and did not respect
it, so no new decision is owed.

Suite unchanged at 1123 / 0 / 0 / 10; this commit touches no .lua.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01K9QM2sW9jq5a4F1KAwnHmo
hleb-rubanau added a commit to hleb-rubanau/compy-dev that referenced this pull request Sep 9, 2026
README contradicted itself. The MERGE-01-05 import rewrote its keymap tables —
dropping the row "Load selected content to input (discards previous content) —
Esc", because aldum#45 moved the load onto Enter — and left the walkthrough prose
untouched, four lines of which still read "Then load the text by pressing Esc".

So the repository's front door instructed the reader to press a key that no
longer does that, in contradiction with its own table on the same page.
doc/EDITOR.md's parallel walkthrough said Enter all along, which is what made
the divergence visible.

Corrected to the merged behaviour, matching doc/EDITOR.md and the code
(editorController's open path: Enter or typing opens the selected block).

This is D2's first executed finding, and the class is worth naming: an import
that rewrites a document's tables and not its prose leaves the two halves
disagreeing, and nothing fails. The third shipping document, doc/input_api.md,
has not yet been read against the merged tree with that specific failure in
mind.

Suite unchanged at 1123 / 0 / 0 / 10.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01K9QM2sW9jq5a4F1KAwnHmo
hleb-rubanau added a commit to hleb-rubanau/compy-dev that referenced this pull request Sep 9, 2026
Owner ruling, 2026-09-06: "keep shift+ctrl+s active but mark it deprecated both
in README and in code." The chord stays bound and both sites now say it is on
its way out.

Kept, under the owner's own evidence test, because three shipped surfaces carry
it: doc/input_api.md documents it, the README walkthrough teaches it, and
tests/input/input_global_shortcuts_spec.lua pins it. It is also pre-feature
capability — _save_keys had it, and upstream dev before that.

Deprecated because everything else points the other way: its author calls it
inherited and absent from the editor spec and is raising it with the product
owner; aldum#45's own keymap no longer documents it; the product owner is collapsing
the exit combinations; and it LOSES an open changed block, since the path
reaches finish_edit with no acceptance step (T-LEAVE-KEYS-LOSES-BLOCK). The
README now names that loss rather than only the deprecation, because a reader
deciding whether to keep using it needs the reason.

The README walkthrough now teaches Shift+Esc, which is what doc/EDITOR.md
teaches for the same step and what the editor spec calls the supported way out.

Placement is unchanged and deliberately so: route-level in EditorController, not
a gate reservation. D-EXACT-RESERVE's Scope paragraph binds the gate to what
competes with a running project, and session75 already reverted one attempt to
express this chord as a gate entry.

The judgement's D2 section records both README findings, since an import that
rewrites tables and not prose is a class rather than one slip.

Suite unchanged at 1123 / 0 / 0 / 10; the only .lua edit is a comment, and the
three over-64 lines in that file are pre-existing (aldum#45's, unmoved by this edit).

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01K9QM2sW9jq5a4F1KAwnHmo
hleb-rubanau added a commit to hleb-rubanau/compy-dev that referenced this pull request Sep 9, 2026
Delivery review F3. When the aldum#45 import broke this case, the assertion was
replaced with a shape — assert.is_true(#loaded > 0 and loaded[1] ~= '') — on
the reasoning that WHICH block Enter opens is the editor's business. The
reasoning is sound and the literal was still available: the case is green with
assert.same({ 'first line' }, loaded), which is strictly stronger and says what
the tree does instead of that it does something.

That assertion is also the measurement. The claim that aldum#45 moved the editor's
opening selection from the last block to the first was probed by the review and
is now pinned by the suite: pre-merge the doc opened at block 3 and 'up' walked
to 'second line'; today it opens at 'first line'.

Which makes the 'up' keystroke in this case dead — it navigates from the top of
the document to the top of the document. Dropped here rather than left silent,
with the comment saying why and naming the two Escape cases above that still
carry one; they are harmless and out of this row's scope.

The behaviour change behind all of this is recorded in the debt register in the
next commit, per the owner's ruling that it is a defect in its own right rather
than a clause on T-NAV-ESCAPE.

Suite 1123 / 0 / 0 / 10, unchanged — this replaces an assertion and removes a
keystroke, adding no case. No new 64-column violations in the file.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01K9QM2sW9jq5a4F1KAwnHmo
hleb-rubanau added a commit to hleb-rubanau/compy-dev that referenced this pull request Sep 9, 2026
…documented

Owner ruling, 2026-09-06: this is an active defect in its own right, not a
clause on T-NAV-ESCAPE. A documentation obligation buried inside another
entry's "what changed" is not findable by anyone looking for it.

Before the import the editor opened with the last block selected; at HEAD it
opens with the first. Upstream's change, taken faithfully — our bufferModel is
byte-identical to aldum#45's — so nothing here proposes reversing it. What is owed
is the record, under MERGE-01-05's own rule that a behaviour change gets one
and a renaming does not. That rule was followed once in the same range, which
is why T-NAV-ESCAPE exists; here the re-pin called it a moved setup.

Where the record belongs is deliberately left open, per the same ruling: the
editor internals doc, CHANGELOG.md, or the guide. Choosing from a session that
is not fixing it would be guessing.

The cost is ours rather than users': navigation cases written before the import
have setups that no longer navigate, and three of them press an 'up' that walks
from the top of the document to the top of the document.

ACTIVE count corrected to ten in the same edit, since the section header states
it and a stale count is the same class of defect this entry registers.

Suite unchanged at 1123 / 0 / 0 / 10; this commit touches no .lua.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01K9QM2sW9jq5a4F1KAwnHmo
hleb-rubanau added a commit to hleb-rubanau/compy-dev that referenced this pull request Sep 9, 2026
Owner ruling, 2026-09-06: retrofitting the editor's key contract as a decision
spawns a defect, because behaviour that was decided is possibly not covered by
tests, or covered partially, and nobody has checked.

The entry claims the coverage is UNKNOWN rather than missing. That distinction
is the entry: a decision whose coverage is unknown is an obligation, and the
row that closes it is the one allowed to state results.

A survey rather than the measurement (grep over tests/) gives it shape and one
finding worth having early: our own suite currently pins the contract's
OPPOSITE. "editor Escape falls through to the widget" asserts the widget's
cancel did run in navigation, which row 1 says must not happen. It is a correct
pin of today's defect and becomes a wrong one the moment the defect is paid, so
it moves with the fix rather than after it.

Why a defect and not a chore: the contract's own history. The exclusion
upstream expressed in code, this branch re-expressed as a cross-controller
expectation; aldum#45 changed that controller and nothing failed, because an
expectation cannot fail. A ratified contract with no case behind it is the same
construction with a ledger entry in front of it.

ACTIVE count to eleven in the same edit.

Suite unchanged at 1123 / 0 / 0 / 10; this commit touches no .lua.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01K9QM2sW9jq5a4F1KAwnHmo
hleb-rubanau added a commit to hleb-rubanau/compy-dev that referenced this pull request Sep 9, 2026
Six decisions were raised and never ruled, and five share one shape: a
session registered the question correctly — in the debt ledger, on a
roadmap cell, in a decision's own statement — and treated having filed it
as the end of its obligation. The filing is durable; the asking never
happened.

Two sit on ACTIVE debt entries with no roadmap row at all (the editor
seam's open default, and always_shown), which is the visible gap
ledgers.md §5 names. One is a hold whose release condition fired and
nobody noticed: the two questions for aldum#45's author were held until OP-03
was understood, OP-03 was judged 2026-09-06, and both the entry and the
cell still read "do not ask yet". One is a removal trigger nobody pulled
— whether any off-repo consumer writes auto_hide, which decides whether
this release ships a key born deprecated. One is the agents/ scope split,
carried verbatim in "Left open" since session70 and deferred on a
condition that was spent when the import landed. The sixth needs no
ruling, only an edit: the Escape placement confirmation reached PR-01-07
and not PROP-01's carve-out 3, so the roadmap asks the owner a question
they answered on 2026-09-04.

Sessions 72-75 are clean on rulings — eight leads opened, six closed on
evidence, all listed with their commands. I re-ran the six decisive
checks myself before carrying any of it up; all six hold as stated.

Read-only audit, nothing else in the tree touched. Suite unchanged at
1123 / 0 / 0 / 10 (busted tests, container LuaJIT 2.1 — the owner runs
PUC Lua).

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01K9QM2sW9jq5a4F1KAwnHmo
hleb-rubanau added a commit to hleb-rubanau/compy-dev that referenced this pull request Sep 9, 2026
…nothing

Owner, 2026-09-06: "escalate with me. Maybe we need to discuss it as
separate step" — and then, when the first draft was a step that would
contact upstream: "I've meant separate discussion on topic between LLM
and me."

So the row is a working conversation, not an outbound message. No draft
for @dsent, none for aldum#45's author, nothing sent.

It exists because the owner's standing instruction on the chord — ship
exactly what aldum#45 ships, escalate if that behaviour is destructive — came
back satisfied on its first half and triggered on its second. Both were
settled by measurement rather than argument. Ours loses an open changed
block: _leave_keys reaches finish_edit(), which calls save_state() and
close(), and save_state() stores the clipboard, not the buffer. Theirs
does too, attested by its author about his own layer. The only divergence
is which layer owns the chord, so parity is already met and there is no
behaviour to change.

The debt entry takes the same ruling and stops being unslugged; its
roadmap line now points at OP-04. The three questions the discussion has
to settle are written on the row, marked as the ones live when it was
opened rather than as its mandate.

Placed outside the release path: it blocks nothing, delays nothing, and
EDKEYS-01-04 pins where the chord lives regardless of how it is answered.

Suite unchanged at 1123 / 0 / 0 / 10 (busted tests, container LuaJIT 2.1
— the owner runs PUC Lua); no .lua touched.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01K9QM2sW9jq5a4F1KAwnHmo
hleb-rubanau added a commit to hleb-rubanau/compy-dev that referenced this pull request Sep 9, 2026
Owner, 2026-09-06: "I provided complete answer from author already."
Nothing was sent to aldum#45's author and nothing will be. The hold on the row
had been released by OP-03's judgement the same day, and the release
condition firing was itself unnoticed until this session's audit — both
the cell and the entry still read "do not ask yet" two screens under a
gate marked judged.

Neither question is settled the way the entry predicted. Question 2 is
answered and our reading of it is overturned: Ctrl+Shift+S is inherited
cruft that predates the editor spec and nobody owns, not the deliberate
retirement we inferred from the force-push's shape — and its author is
raising the gate bypass with @dsent himself, so the follow-up is his.
Question 1 is moot rather than answered: he describes Ctrl+S as reserved
at application level, never reaching the editor controller, and says
nothing about a checkpoint, so the comment stays unsupported by its own
author's account. That question changes owner to @dsent and to the
rework, and MERGE-01-08 is ruled without it.

Worth recording because it is the useful shape of this outcome: we asked
for confirmation of a comment and got the routing contract we did not
have, which D-EDITOR-KEYS now carries.

The entry moves to RETIRED rather than being deleted — vacuuming is a
move — and its own retirement condition is what fired, with the twist
that the answers landed without the questions being sent. Its two halves
are re-homed: the Ctrl+S half on T-CTRL-S-UNCLAIMED where the decision it
feeds lives, the Ctrl+Shift+S half on T-LEAVE-KEYS-LOSES-BLOCK and OP-04,
because what it turned into is a data-loss question rather than a
documentation one.

Suite unchanged at 1123 / 0 / 0 / 10 (busted tests, container LuaJIT 2.1
— the owner runs PUC Lua); no .lua touched.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01K9QM2sW9jq5a4F1KAwnHmo
hleb-rubanau added a commit to hleb-rubanau/compy-dev that referenced this pull request Sep 9, 2026
…ces clear

The risk register's "they changed a line we moved" entry -- high impact,
observed once -- has had no statement either way at the row that owns it,
so a reader could not tell whether it was run and cleared or never run. It
was never run. It is now.

(1) Reservation table -- clear. git diff 21eb7f5^ -- src/controller/controller.lua
is empty, and the one hunk aldum#45 makes in that file IS a line this branch had
moved, so it was checked on the merits and not on the diff: aldum#45 stops bare
Ctrl+S calling close_buffer() in the editor branch, and our relocated form
reaches the same behaviour, because ['ctrl+s'] = reserved_stop_run acts only
when app_state == 'running'.

(2) Hook seeding -- clear by absence. aldum#45 touches 19 files and the seeding
path is not among them; seed_hooks is projectInputController.lua:65, called
at :170, and no hunk names it or any compy.input.hooks call site.

(3) Routing grid -- clear on the one rewritten cell. aldum#45 rewrites the editor
branch of ConsoleController:mousepressed from self.editor.input:mousepressed
to self.editor:mousepressed. This branch had not moved that line -- the
pre-merge tree carries the old form at the same site -- the merged tree
carries the new one, and EditorController:mousepressed exists to receive it.

One asymmetry recorded on the cell and it is upstream's, not a merge loss:
aldum#45 rewrote the pressed cell only, so six sibling pointer/touch handlers
still call self.editor.input:<event>. The whole editor-pointer cell is off
in the shipped configuration (mouse_enabled = false, src/main.lua), which is
why input_routing_spec.lua leaves that cell pending.

A clean R11 is a useful record; the disposition asked for it either way.

Suite unchanged: 1123 successes / 0 failures / 0 errors / 10 pending.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01K9QM2sW9jq5a4F1KAwnHmo
hleb-rubanau added a commit to hleb-rubanau/compy-dev that referenced this pull request Sep 9, 2026
…runs

Delivery review F4. Two cells described Ctrl+Shift+S in terms this phase's
own rulings had already replaced, and one of them pre-empted a release-path
step whose entire purpose is to decide that question.

FLAGS-01's "two things this sprint does NOT carry": the header said both
were unruled and both are ruled. Its clause credited the owner with "keep
and deprecate" -- a phrase that appears in no ledger; grep -rn showed it
only in this cell and in the review that first coined it. What the entry
records instead is two rulings of 2026-09-06: behavioural parity with aldum#45
is ruled AND already met, so there is no behaviour to change on parity
grounds, and the escalation condition fires because the attested behaviour
on the other layer is destructive too.

"Ships deprecated and still lossy" is not a fact about the release; it is
one of the three outcomes OP-04's own cell lists (fixed, gated, or knowingly
shipped), asserted before that step runs. Removed from both cells that
carried it.

EDKEYS-01 gains the other end of a constraint that was asserted only on
OP-04's cell: OP-04 runs immediately in front of the sprint, and any
implementation it rules lands inside or behind it, never in front. That is
what keeps the net a net -- EDKEYS-01-04 pins where the chord lives, not
what the path saves, so an added acceptance step turns red only what it was
meant to change.

Suite unchanged: 1123 successes / 0 failures / 0 errors / 10 pending.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01K9QM2sW9jq5a4F1KAwnHmo
hleb-rubanau added a commit to hleb-rubanau/compy-dev that referenced this pull request Sep 9, 2026
… gate is upstream's

The owner asked whether the passthrough/block_input machinery was ours or
pre-existing. Measured, and the entry was wrong on both halves of its
provenance bullet.

  git show 3256aac:src/controller/editorController.lua
    | grep -c "passthrough\|block_input"   -> 14
  same at af9a578                          -> 14
  f4cf338 and HEAD                         -> 29 (aldum#45 grew it)

So the gate is UPSTREAM's and pre-dates this branch. We did not turn an
allow-list into a block-list; the block-list was always the outer gate and is
still theirs.

Nor was the widget's own app_state fork an allow-list. At the base its editor
arm ran nearly the whole handler set -- removers, horizontal, vertical,
newline, modify, copypaste, selection, submit -- so an unclaimed key did
almost everything, not nothing. The one functional difference was cancel,
which the editor arm omitted.

What Phase R actually changed is therefore ONE handler. affc932 removed the
fork, modify() kept its editor-only scope as a constructor flag, and cancel
joined the editor route. That handler is T-NAV-ESCAPE, and it was latent
until aldum#45 stopped claiming bare Escape -- the R11 class exactly, they changed
a line we moved.

The consequence for release scope is the part that matters: Ctrl+D and
Shift+Enter in nav are pre-existing upstream behaviour, not regressions this
branch introduced, and FLAGS-01 removes our only contribution. After it, this
branch adds NOTHING to this class. So the decline is not "ship our defect and
hope"; it is declining to restructure a pass-through that is upstream's,
pre-dates us, and whose live members are their own documented behaviour.

The residual risk is stated as what it actually is -- invisibility, not
damage -- and a mitigation cheaper than the migration is proposed rather than
scheduled: pin the measured members in a spec so the next change on either
side fails a test instead of being found by an unrelated one. That is what
this class has never had, and it is why T-NAV-ESCAPE was found the way it
was. It belongs with EDKEYS-01, which already writes the editor's net.

Suite unchanged: 1123 successes / 0 failures / 0 errors / 10 pending.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01K9QM2sW9jq5a4F1KAwnHmo
hleb-rubanau added a commit to hleb-rubanau/compy-dev that referenced this pull request Sep 9, 2026
… suite is 1123

`Where things stand`'s suite row has been stale since `MERGE-01-05` landed the
upstream import: it read **1055 / 0 / 0 / 10** while every sprint cell in the
same file, `agents/validation.md`'s baseline and `busted tests` itself say
**1123**.

The S78 delivery review found this and filed it under *Omissions* rather than as
a finding, correctly — it is pre-existing rather than that session's defect — so
no disposition was written and the post-wrap sweep did not reach it. Session79's
spot-check of that review is what surfaced it as still-open.

The row now carries the import step in **cases**, because the import landed red
and a successes-only chain cannot express it: 1065 cases before, 1132 at the
import commit (aldum#45 brought 67), 1133 after reconciliation turned six failures
into passes and added `T-NAV-ESCAPE`'s Shift+Esc half. The pre-import chain is
kept below it, labelled as successes.

Suite 1123 / 0 / 0 / 10 (container LuaJIT), unchanged — documentation only.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01K9QM2sW9jq5a4F1KAwnHmo
hleb-rubanau added a commit to hleb-rubanau/compy-dev that referenced this pull request Sep 9, 2026
…s unwired

Owner question at OP-04: finish_edit() does not do what its name says, the
defect precedes the feature -- so what did aldum#45 do instead?

Measured. aldum#45 touches neither `finish_edit` nor `save_state`: the
consoleController diff for `finish_edit` is empty and `save_state` is
byte-identical across the import. What it added is the whole
acceptance-and-confirmation discipline inside EditorController, none of it
present at `af9a5782` -- `accept_block` with a refusal path that keeps the
block open when the write fails, `discard_edit` which compares draft against
original and asks "discard the changes? Confirm [Enter] / Cancel [Esc]" when
they differ, plus `leave_edit`, `_confirm`, `refuse`, `record_write`.

So the editor knows how to protect a dirty block and the three exits never ask
it. That makes the fix a routing question rather than a policy one -- the
policy is aldum#45's `discard_edit` and matches @dsent's stated direction. The cost
is named instead: the guard is modal (`pending_confirm` is consumed on the next
keypressed) while the exits are synchronous, so `reserved_quickswitch` can no
longer assume the editor is gone when `finish_edit` returns.

Suite 1123 / 0 / 0 / 10 (container LuaJIT), unchanged -- documentation only.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01K9QM2sW9jq5a4F1KAwnHmo
hleb-rubanau added a commit to hleb-rubanau/compy-dev that referenced this pull request Sep 9, 2026
…opened one of them

Owner question at OP-04: were these doors left open at the tip of aldum#45, before
the import?

Measured at `f4cf338c` (dev + aldum#45). Yes, all three, and `finish_edit` is
identical to ours line for line:

  - Ctrl+Shift+S at controller.lua:592-601, application-level, carrying aldum#45's
    own comment that leaving is Shift+Esc;
  - Ctrl+T at :562-583, unchanged from the base;
  - Shift+Esc via close_buffer at editorController.lua:1317-1322.

So we imported three open doors and invented none; what is ours is the layer
they now sit at.

Door 3 is aldum#45's own. At `af9a5782` `close_buffer` already carried the
`finish_edit()` call but nothing called it -- one grep hit, the definition
alone. aldum#45 gave the dead sink a key, via its spec-2.3 discard path, in the same
pull request that built `discard_edit`'s confirmation prompt.

This settles the parity half of the standing ruling: at aldum#45's tip all three
doors lose the block, so shipping all three is exact parity, and an acceptance
step is a deliberate divergence from aldum#45 rather than a correction toward it.

Suite 1123 / 0 / 0 / 10 (container LuaJIT), unchanged -- documentation only.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01K9QM2sW9jq5a4F1KAwnHmo
hleb-rubanau added a commit to hleb-rubanau/compy-dev that referenced this pull request Sep 9, 2026
…and door 3 loses nothing

Owner question at OP-04: "so they added shift+esc but did not wire it to guard
and wired directly to project exit?" Checking it corrects a claim this session
put in the ledger two commits ago.

They DID wire it to the guard. `discard()` branches: `is_empty and mode ==
'nav'` goes to `close_buffer()`, and everything else goes to `discard_edit()`,
which confirms when the draft differs from the original. The carve-out cannot
carry a dirty block: the only edit->nav route is `leave_edit()`, which runs
`buf:clear_loaded()` and `input:clear()` before setting the mode, so an empty
widget in nav mode means nothing is loaded.

So the withdrawn claim is "emptying a block that had content is the change that
is then not written" -- wrong; that state is `edit` mode, where Shift+Esc goes
to the guard. Door 3 reaches `finish_edit` and loses nothing, and is kept in the
entry only because a fix sited at `finish_edit` must account for the caller.

The data-loss surface is two doors, not three, and the mode guard is what
separates them: `_leave_keys` has none and fires under `Key.ctrl()` ahead of
mode dispatch; `is_normal_mode()` admits `edit`; `discard()` excludes it.

The transferable point survives and is narrower: aldum#45 fixed this class at the
block level everywhere it owns the key, and left the two whole-editor exits it
did not author calling `finish_edit` as before. Entry retitled to say so.

Suite 1123 / 0 / 0 / 10 (container LuaJIT), unchanged -- documentation only.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01K9QM2sW9jq5a4F1KAwnHmo
hleb-rubanau added a commit to hleb-rubanau/compy-dev that referenced this pull request Sep 9, 2026
…ne parity gap of ours

Owner question at OP-04: which keys are the two whole-editor exits wired to at
the tip of aldum#45?

Both are application-level Ctrl chords in `controller.lua`, and neither passes
through EditorController -- which is why neither can reach a guard that lives
inside it. There is no `_leave_keys` at their tip; that method is ours.

  - Ctrl+Shift+S: Key.ctrl() -> k == "s" -> app_state == 'editor' ->
    Key.shift() -> CC:finish_edit()
  - Ctrl+T: Key.ctrl() and not Key.alt() and k == 't' -> app_state == 'editor'
    -> is_normal_mode() -> finish_edit() -> run_project()

aldum#45's own comment sits on the first and names a different key as the exit --
"saving is automatic, leaving is Shift+Esc" -- and Shift+Esc is guarded twice: a
dirty block meets discard_edit's confirmation, and only after leave_edit has
cleared it does a second press reach finish_edit. So the editor cannot be left
with unwritten changes via the key aldum#45 documents as leaving, which matches its
author calling the chord inherited and absent from the editor spec.

Also records a parity divergence of ours that was not written down anywhere:
their door 1 has no Alt guard, our `_leave_keys` has `not Key.alt()`, so
Ctrl+Alt+Shift+S leaves the editor at f4cf338 and does nothing here. Ours is
narrower and defensible; it is still a deviation from "exact behaviour aldum#45
ships" that came from the re-homing rather than from a ruling, and no test
covers it.

Suite 1123 / 0 / 0 / 10 (container LuaJIT), unchanged -- documentation only.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01K9QM2sW9jq5a4F1KAwnHmo
hleb-rubanau added a commit to hleb-rubanau/compy-dev that referenced this pull request Sep 9, 2026
…nwritten block

Owner directive at OP-04: ensure aldum#45's Shift+Esc is covered by tests.

It was covered in one direction only -- the discard-ask cases under `block undo
(1.1)` exercise a dirty draft -- and nothing pinned the property that makes the
key safe: it is the only editor exit that consults the discard guard, so an
unwritten block cannot leave through it.

Four cases, all characterization of behaviour aldum#45 already gets right:

  - a dirty block asks instead of leaving (pending_confirm == 'discard', the
    console's finish_edit is not called, mode stays 'edit');
  - a clean block leaves the block and not the editor (nav, no ask);
  - nav with nothing loaded reaches finish_edit -- the one branch that does
    exit, and the reason it is safe is that nothing is loaded;
  - confirming the discard leaves the block only, not the editor.

The two Ctrl chords that leave without the guard are deliberately NOT pinned
here: they lose data, and a passing test would fix that in place.
T-LEAVE-KEYS-LOSES-BLOCK holds them.

Suite 1123 -> 1127 / 0 / 0 / 10 (container LuaJIT); four added, none replaced.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01K9QM2sW9jq5a4F1KAwnHmo
hleb-rubanau added a commit to hleb-rubanau/compy-dev that referenced this pull request Sep 9, 2026
…ives

Owner directive at OP-04: move aldum#45's comment to where it belongs.

aldum#45's comment sits inside its `k == "s"` block and explains why bare Ctrl+S no
longer closes the buffer -- a statement about an absent binding, placed in the
block of a still-live one, directly above the Ctrl+Shift+S leave it does not
mention. Our merge split that block in two: bare Ctrl+S became
`reserved_stop_run` in controller.lua, Ctrl+Shift+S became `_leave_keys` in
editorController.lua. The explanation stayed with the chord and not with the key
it is about, so our tree inherited the same misplacement.

It moves to `reserved_stop_run`, which is where a future author would add the
editor branch it forbids, and it is rewritten to the form
`agents/rules/commenting.md` asks for: a prohibition rather than an absence, and
no history narration ("it used to close the buffer here" is git's to hold). The
Ctrl+K correction and the T-CTRL-S-UNCLAIMED pointer travel with it.

`_leave_keys` keeps what is about `_leave_keys`: the deprecation, why the chord
is still bound, the block loss, and the route-level-not-gate rationale.

Suite 1127 / 0 / 0 / 10 (container LuaJIT), unchanged; no behaviour touched.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01K9QM2sW9jq5a4F1KAwnHmo
hleb-rubanau added a commit to hleb-rubanau/compy-dev that referenced this pull request Sep 9, 2026
…ts cannot reach it

Owner directive at OP-04: "record the debt on these two keys, probably
generalized to need of re-architecturing or reimplementing the guard so that
they could use it too."

T-LEAVE-KEYS-LOSES-BLOCK records a chord. The general fact is that
EditorController holds a working discard guard -- `discard_edit` asks before
losing a draft, `accept_block` refuses to read a failed write as accepted --
while both keys that leave the editor live above the editor in controller.lua
and call the console. A guard inside the editor cannot be consulted by a key
that never enters it.

The architectural half is why this is not a missing `if`: the guard is modal and
deferred (pending_confirm is consumed on the next keypressed) and the exits are
synchronous, with `reserved_quickswitch` calling finish_edit() and run_project()
in one breath. Routing them through the guard means callers stop being able to
assume the editor is gone when the call returns -- a contract change at three
sites.

The entry carries a recommended direction and not a commitment, in the shape
T-EDITOR-SEAM-DEFAULT-OPEN uses: a refusal return, or an editor-side
request_leave, with the policy left where aldum#45 put it.

Filed BACKLOG, and that placement is mine rather than a ruling: an ACTIVE slug
commits to fixing before the PR, and shipping both doors is exact parity with
aldum#45. If the owner rules the re-architecture into the release it moves to ACTIVE
and needs a roadmap row (ledgers.md §5). Stated on the entry.

Suite 1127 / 0 / 0 / 10 (container LuaJIT), unchanged -- documentation only.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01K9QM2sW9jq5a4F1KAwnHmo
hleb-rubanau added a commit to hleb-rubanau/compy-dev that referenced this pull request Sep 9, 2026
…ow is undecided

The row was written against material that has since changed in four ways: the
loss is at finish_edit with three callers of which two lose a block, neither
losing key is ours or aldum#45's, aldum#45 fixed the class at block level so the policy
exists and the exits cannot reach it, and shipping both doors is exact parity.

The cell states all four and explicitly does not state the outcome -- the S77
delivery review's F4 class, which this phase has already paid for once.

Also records the three owner directives executed today (the generalized debt
entry, the comment re-siting, the Shift+Esc coverage) as material produced by
the row rather than as its conclusion.

Suite 1127 / 0 / 0 / 10 (container LuaJIT), unchanged in this commit.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01K9QM2sW9jq5a4F1KAwnHmo
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants