TUI: decide what lowercase and uppercase mean, then make the bindings agree - #121
Merged
Conversation
#327 gave the key line one label format for a lowercase key and its shifted sibling, but left the convention behind that format unstated — so `d/D dispatch` promised a kinship nothing in the doc defined, and the uppercase glosses in the `?` map were worded to a shape rather than to a rule. The rule is foreground versus background: the lowercase key acts immediately and headlessly and the operator never leaves the TUI, and the uppercase key opens an interactive surface — an agent session or a picker. Taking a line of text inline is not opening a surface; it is how a lowercase key takes its argument, which is what keeps `r` and `a` on the lowercase side. It binds pairs and only pairs, which is the same line §9 already draws between a shifted sibling and a key that merely shares a letter. That puts `C`, the projects screen's `A`, `J`/`K` and the Config screen's `V`/`A` outside it rather than in breach of it, and none is worth rebinding to buy a consistency nobody reads. The audit found no binding to move: `d`/`D`, `r`/`R` and `a`/`A` already conform (the picker is `D`'s interactive surface, and `r`'s backgrounding is what the lowercase key means, not the defect #325 read it as), and `n`/`N` is the one pair still out of line — both halves are foreground today, which #315 fixes by making `n` a typed line expanded by a background agent. #315 is now ordered behind this task so the rule lands first. A test replaces the audit going forward: every uppercase key in every screen's map must be half of a pair or one of the exceptions named in §9, so a new uppercase binding cannot be added without deciding which. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SB1A345BtsrvujQR3njwx9
MJohnson459
force-pushed
the
key-case-rule
branch
from
August 5, 2026 17:58
3a29944 to
122c97b
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Settle what the case of a key means, and audit every binding against it.
#327 gave the key line one label format for a lowercase key and its shifted
sibling but deliberately left the convention behind it unstated. This states
it, in the foreground/background formulation triage decided: the lowercase
key acts immediately and headlessly and the operator never leaves the TUI; the
uppercase key opens an interactive surface — an agent session the terminal is
handed over to, or a picker answered before anything happens.
Two clarifications the rule needed to be usable:
the queue is how a lowercase key takes its argument. That is what keeps
r(and #369's
a) on the lowercase side.draws between a shifted sibling and a key that merely shares a letter. What
it binds going forward is that a heavier, interactive variant of an existing
action takes that action's shifted key rather than a fresh letter.
The audit
Every uppercase binding in
key_normal,key_projectsandkey_config,against the rule:
Ddispatch-via-pickerRrefine in a sessionNplan in a sessionnnew task in$EDITORCcancel a refineclinks documents, so there is no pairAarchiveaadds a projectV/AdefaultsV, andaadds a viewer; both happen to open pickers anywayJ/K, PgUp/PgDnNo binding had to move.
d/Dconforms once the picker counts as theinteractive surface, and #325's finding that
r"launches a backgrounded namedsession anyway" is not a defect under this rule — backgrounding is precisely
what the lowercase key means. The exceptions are named in the doc rather than
rebound: the letters they share carry no kinship, and moving a key the
operator's fingers already know would buy a consistency nobody reads.
Changes
in its foreground/background form, and its scope with every exception named.
?glosses (DISPATCH_KEYS,REFINE_KEYS,NEW_KEYSincrates/voro/src/ui.rs) are reworded to one shape the rule dictates —lowercase ends "headless", uppercase names the surface it opens:
ddispatchto the resolved agent, headless /
Ddispatch, choosing the agent in apicker;
rrefine a proposal from a note, headless /Rrefine a proposal inan agent session;
Nnew task, planned in an agent session.n's gloss isleft alone — it is honest about
$EDITORuntil #315 changes the behaviour.Lengths were kept at or under the existing longest gloss so the overlay does
not widen past 80 columns.
(
every_uppercase_key_is_paired_or_a_named_exception): every uppercase key inevery screen's key map must be half of one of the three pairs or listed in
CASE_EXCEPTIONS, which mirrors the doc's exception list screen by screen.A new uppercase binding cannot be added without deciding which it is, and the
failure message points at §9.
Reconciliation with #315 and #369
#315 (
ntakes a quick prompt and backgrounds an agent) is thenhalf of thisrule and its body already cites it; it is now
--blocks-ordered behind thistask so the rule lands first. Note that added a blocker and the store demoted
#315
ready → parked; it returns to ready when this task is accepted.#369 (
asends headlessly,Aattaches) is in flight and taken as given — Idid not touch
a/Ain either the bindings or the map. Two things for whoeverlands it: the §9 paragraph already describes
a/Aas an instance of the rule,and the new test will fail on a bare
Aentry unless it is added as a pairconstant alongside
DISPATCH_KEYS/REFINE_KEYS/NEW_KEYS— which is theintended prompt, not an obstacle.
Verification
cargo test --workspace(614 tests) andcargo clippy --workspace --all-targets -- -D warningsclean;cargo fmt --all --checkclean. The existing overlay render test asserts the new glosses appearwhole at 80 columns, and its rendered output was inspected to confirm the
cockpit map still fits its column.
Branch:
key-case-rule(worktree, not pushed).