F-key lane: a directional pair rises toward its outer key

F4/F5 keeps up on the right; an F1/F2 pair puts up on F1, so a select
list reads Sect ↑ then Sect ↓ — the hand anchors on the lane's edge and
rocks inward, whichever side the pair lives on.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Jean-Pierre MartineauandClaude Fable 5 committed 2026-08-08 19:10:50 -04:00
1 parent cbdabef174
commit 6787aace3c
4 files changed
+23 -20

No files matched your search

+6 -5
View File
@@ -203,11 +203,12 @@ branch on the platform per frame, and never `from meshterm.platforms import PLAT
wanted a chip for consistency. That leaves **F1–F3 free on every screen** for its own
verbs, and `EMPTY_LANE` for a screen with none at all (every dialog). **A chip names an
action, never a key** — `Page ↑`, not `PgUp`; `Latest` on a transcript; `Region` on the
map, whose Home reframes and whose paging zooms. **A directional pair rises to the
right**: where two adjacent chips are opposite ends of one axis (F4/F5's paging, the
map's zoom, a select list's section jumps), the *up · in · more* end takes the right
slot — `Zoom -` then `Zoom +`, a rocker — and a slot's Shift companion follows the same
rule along its own slot. The lane *is* the footer here, so it obeys the hint line's rule
map, whose Home reframes and whose paging zooms. **A directional pair rises toward its
outer key**: where two adjacent chips are opposite ends of one axis, the *up · in ·
more* end takes the slot nearer the lane's edge — F5 on the right-hand pair (F4/F5's
paging, the map's zoom: `Zoom -` then `Zoom +`, a rocker), F1 on a left-hand pair (a
select list's `Sect ↑` then `Sect ↓`) — and a slot's Shift companion follows its own
slot's direction. The lane *is* the footer here, so it obeys the hint line's rule
— never advertise a key that would do nothing. Empty and dim are different claims: leave
the slot **empty** when the action isn't a thing on this screen (Retry in a channel), and
clear `enabled`/`opp_enabled` to draw it **dim** (label kept, fill dropped) when it's a
+9 -7
View File
@@ -104,13 +104,15 @@ Lane = Sequence[Optional[FPair]]
#: none leaves them blank rather than filling the lane out. A screen whose body is not one
#: scrolling column says so in its own words (see :class:`ChatScreen`, ``MapScreen``).
#:
#: **A directional pair rises to the right** (JP, 2026-08-08): wherever two adjacent chips
#: are opposite ends of one axis — the F4/F5 pager here, the map's zoom, a select list's
#: section jumps — the *up · in · more* end takes the right slot and its opposite the left,
#: so every such pair reads like a rocker (``−`` then ``+``) and one handedness carries
#: across screens. A slot's own Shift companion follows the same rule *along its slot*: the
#: jump behind ``Page ↑`` is the one Page ↑ is heading for, so F10 is Top and F9 Bottom. A
#: screen relabelling a pair keeps that order (a chat's ``Latest``/``Oldest``).
#: **A directional pair rises toward its outer key** (JP, 2026-08-08): wherever two
#: adjacent chips are opposite ends of one axis, the *up · in · more* end takes the slot
#: nearer the lane's own edge. On the right-hand F4/F5 pair that is F5 — the pager here,
#: the map's zoom, ``Zoom -`` then ``Zoom +``, a rocker — and on a left-hand F1/F2 pair it
#: is F1, so a select list reads ``Sect ↑`` then ``Sect ↓``: ergonomically the hand
#: anchors on the lane's edge and rocks inward, whichever side the pair lives on. A slot's
#: own Shift companion follows the same rule *along its slot*: the jump behind ``Page ↑``
#: is the one Page ↑ is heading for, so F10 is Top and F9 Bottom. A screen relabelling a
#: pair keeps its order (a chat's ``Latest``/``Oldest``).
DEFAULT_LANE: tuple[Optional[FPair], ...] = (
None,
None,
+4 -4
View File
@@ -213,8 +213,8 @@ class SelectScreen(Screen):
Ctrl+PageUp/PageDown step the highlight heading by heading — and on the PicoCalc
they are the one binding in the app that is *physically unreachable*: paging has
no key there to hold Ctrl over. So a grouped list promotes them onto the lane,
keeping the shared handedness (down left, up right — see
:data:`~meshterm.ui.tui.fkeys.DEFAULT_LANE`).
keeping the shared handedness — a pair rises toward its outer key, so on this
left-edge pair *up* takes F1 (see :data:`~meshterm.ui.tui.fkeys.DEFAULT_LANE`).
Their two gates say different things, per the lane's empty-versus-dim rule. A list
that has no headings *at all* leaves both slots blank: sections aren't a thing
@@ -237,8 +237,8 @@ class SelectScreen(Screen):
lane = list(default_lane(nav=len(self._choices(rows)) > 1))
if len(self._all_section_starts()) >= 2:
live = len(self._section_starts(rows)) > 1
lane[0] = FPair("Sect ↓", "ctrl_pagedown", enabled=live)
lane[1] = FPair("Sect ↑", "ctrl_pageup", enabled=live)
lane[0] = FPair("Sect ↑", "ctrl_pageup", enabled=live)
lane[1] = FPair("Sect ↓", "ctrl_pagedown", enabled=live)
if self._delete_hint:
choices = self._choices(rows)
current = choices[max(0, min(self._index, len(choices) - 1))] if choices else None
+4 -4
View File
@@ -181,9 +181,9 @@ def test_select_lane_promotes_the_section_jumps_only_where_there_are_sections()
Separator("── Far ──"), Choice("gamma", 3),
])
lane = grouped.fkey_lane
assert [pair.label for pair in lane[:2]] == ["Sect ↓", "Sect ↑"]
# Down left, up right — the same handedness the pager below them reads with.
assert action_for(lane, 1) == "ctrl_pagedown" and action_for(lane, 2) == "ctrl_pageup"
assert [pair.label for pair in lane[:2]] == ["Sect ↑", "Sect ↓"]
# A pair rises toward its outer key: on this left-edge pair, up takes F1.
assert action_for(lane, 1) == "ctrl_pageup" and action_for(lane, 2) == "ctrl_pagedown"
assert all(pair.enabled for pair in lane[:2])
# A filter that collapses the list onto one section keeps the labels and dims them:
@@ -191,7 +191,7 @@ def test_select_lane_promotes_the_section_jumps_only_where_there_are_sections()
for ch in "gam":
grouped.handle("text", ch)
dim = grouped.fkey_lane
assert [pair.label for pair in dim[:2]] == ["Sect ↓", "Sect ↑"]
assert [pair.label for pair in dim[:2]] == ["Sect ↑", "Sect ↓"]
assert not any(pair.enabled for pair in dim[:2])