F-key lane: the F3 toggles were better - restored
The per-item chips (node page F1 Info / F2 Routes, Time machine F1-F3 spans) revert to the single next-thing toggle on F3, per JP's call. The PicoCalc window ring still stops at 30 d; its F3 chip just cycles the three spans that remain. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f5244eb4f5
commit
70fb0ceea3
4 files changed
+72
-73
No files matched your search
@@ -340,15 +340,17 @@ class NodeDetailScreen(Screen):
|
||||
|
||||
@property
|
||||
def fkey_lane(self):
|
||||
"""The shared pager over the route list, plus one chip per tab on F1/F2.
|
||||
"""The shared pager over the route list, plus the tab switch on F3.
|
||||
|
||||
The strip shows *that* there are two views; nothing on screen says the key that
|
||||
moves between them, and on a platform with no hint line the chips are the only
|
||||
place to learn it. Each tab takes its own slot in strip order — F1 ``Info``, F2
|
||||
``Routes`` (JP, 2026-08-08) — so a view is one press away rather than a toggle to
|
||||
cycle. The chip you are already on dims: the view is a thing here, pressing its
|
||||
key just changes nothing this paint. A page with one tab has nothing to switch,
|
||||
so the slots stay empty.
|
||||
moves between them, and on a platform with no hint line the chip is the only place
|
||||
to learn it. It names the tab it would take you *to*, never the one you are on —
|
||||
the strip already marks that, and a chip repeating it would say nothing about what
|
||||
pressing it does. (A per-tab chip on F1/F2 was tried and reverted — JP, 2026-08-09:
|
||||
the toggle was better.) Unlike the Time Machine's window chip, this one carries no
|
||||
``▸`` lead-in: ``Routes`` is exactly six cells on its own, and the strip beside it
|
||||
makes the direction plain anyway. A page with one tab has nothing to switch, so
|
||||
the slot stays empty.
|
||||
|
||||
The pager gates on the route list actually being windowed: on the Info tab, and on
|
||||
a node whose routes all fit, the keys move nothing.
|
||||
@@ -357,8 +359,8 @@ class NodeDetailScreen(Screen):
|
||||
|
||||
lane = list(default_lane(nav=self._list_hidden))
|
||||
if len(self._tabs) >= 2:
|
||||
for i, tab in enumerate(self._tabs[:3]):
|
||||
lane[i] = FPair(tab.name, f"tab_{i}", enabled=i != self._tab_index)
|
||||
nxt = self._tabs[(self._tab_index + 1) % len(self._tabs)]
|
||||
lane[2] = FPair(nxt.name, "tab")
|
||||
return lane
|
||||
|
||||
def __init__(
|
||||
@@ -558,9 +560,6 @@ class NodeDetailScreen(Screen):
|
||||
self._switch_tab(1)
|
||||
elif action == "shift_tab":
|
||||
self._switch_tab(-1)
|
||||
elif action.startswith("tab_") and action[4:].isdigit():
|
||||
# The lane's per-tab chips (F1 Info, F2 Routes) land on a view directly.
|
||||
self._select_tab(int(action[4:]))
|
||||
elif action == "up":
|
||||
if n:
|
||||
self._row_index = (self._row_index - 1) % n
|
||||
@@ -611,20 +610,10 @@ class NodeDetailScreen(Screen):
|
||||
return self._cursor
|
||||
|
||||
def _switch_tab(self, delta: int) -> None:
|
||||
"""Cycle the active tab (``Tab``/``Shift+Tab``); a lone tab has nowhere to go."""
|
||||
"""Move the active tab, resetting the cursor and list window to that tab's top."""
|
||||
if len(self._tabs) < 2:
|
||||
return
|
||||
self._select_tab((self._tab_index + delta) % len(self._tabs))
|
||||
|
||||
def _select_tab(self, index: int) -> None:
|
||||
"""Land the stage on tab ``index``, resetting the cursor and list window to its top.
|
||||
|
||||
A no-op off the strip or on the tab already showing, so the lane's dimmed
|
||||
current-tab chip stays presentational — ``handle`` remains the authority.
|
||||
"""
|
||||
if not (0 <= index < len(self._tabs)) or index == self._tab_index:
|
||||
return
|
||||
self._tab_index = index
|
||||
self._tab_index = (self._tab_index + delta) % len(self._tabs)
|
||||
self._row_index = 0
|
||||
self._route_sel = 0
|
||||
self._list_top = 0
|
||||
|
||||
@@ -5,7 +5,7 @@ overheard packet since the first session, but nothing surfaced that history beyo
|
||||
dashboard's two-hour window — this screen is the archaeology dig. A picker offers the
|
||||
whole mesh or any node ever heard; each subject renders as a scrollable page of braille
|
||||
charts and stats over a switchable window (``w`` cycles 24 h → 7 d → 30 d → all time —
|
||||
the PicoCalc stops at 30 d, its F1–F3 chips carrying the three spans directly):
|
||||
the PicoCalc's ring stops at 30 d, its F3 chip cycling the same three spans):
|
||||
|
||||
* a **node** shows its reception volume over the window, its median-SNR band (coloured
|
||||
by quality), its hour-of-day rhythm (when does this node talk?), and the roll-up
|
||||
@@ -68,9 +68,10 @@ _ALL_WINDOWS: tuple[tuple[str, Optional[timedelta]], ...] = (
|
||||
("all time", None),
|
||||
)
|
||||
|
||||
#: The offered windows. The PicoCalc stops at 30 d (JP, 2026-08-08): its three window
|
||||
#: chips on F1–F3 *are* the offer there, and all-time — the one span whose scan grows
|
||||
#: with the whole history — stays a desktop affordance. Bound at platform-switch time.
|
||||
#: The offered windows. The PicoCalc's ring stops at 30 d (JP, 2026-08-08): all time —
|
||||
#: the one span whose scan grows with the whole history — stays a desktop affordance,
|
||||
#: and the F3 chip there cycles the three spans that remain. Bound at platform-switch
|
||||
#: time.
|
||||
_WINDOWS: tuple[tuple[str, Optional[timedelta]], ...] = _ALL_WINDOWS
|
||||
|
||||
|
||||
@@ -80,6 +81,7 @@ def _bind_windows(platform: Platform) -> None:
|
||||
global _WINDOWS
|
||||
_WINDOWS = _ALL_WINDOWS[:3] if platform.footer_fkeys else _ALL_WINDOWS
|
||||
|
||||
|
||||
#: Picker sentinel for the whole-mesh overview page.
|
||||
MESH = ("mesh",)
|
||||
|
||||
@@ -221,21 +223,26 @@ class TimeMachineScreen(Screen):
|
||||
|
||||
@property
|
||||
def fkey_lane(self):
|
||||
"""The shared pager, plus one chip per span on F1–F3.
|
||||
"""The shared pager, plus the window cycle on F3.
|
||||
|
||||
The window is the whole point of this screen — the same history at several spans
|
||||
— and on a platform with no hint line the chips are the only place to learn the
|
||||
spans exist. Each takes its own slot in ring order — F1 ``24 h``, F2 ``7 d``, F3
|
||||
``30 d`` (JP, 2026-08-08) — so any span is one press away rather than a cycle to
|
||||
chase with ``w`` (which still cycles). The span on screen dims: it is a thing
|
||||
here, its key just changes nothing this paint. All time is not in this platform's
|
||||
ring at all (see :func:`_bind_windows`) — three chips, three spans.
|
||||
``w`` is the whole point of this screen — the same history at several spans — and
|
||||
it is exactly the kind of affordance that vanishes on a platform with no hint line
|
||||
to read it off. The chip is the only place the PicoCalc can learn the key exists,
|
||||
so it earns the free F3 slot even though the letter itself is easy to press. (One
|
||||
chip per span on F1–F3 was tried and reverted — JP, 2026-08-09: the cycle was
|
||||
better.)
|
||||
|
||||
The chip names the span it would *take you to*, not the one on screen: the title
|
||||
already says where you are, so a chip repeating it would be the same claim twice
|
||||
and would never tell you what pressing it does. ``all time`` shortens to ``all``
|
||||
to stay inside the 6-cell chip — the only span whose name doesn't already fit,
|
||||
and one this platform's ring may not even offer (see :func:`_bind_windows`).
|
||||
"""
|
||||
from .tui.fkeys import FPair, default_lane
|
||||
|
||||
lane = list(default_lane(nav=self.content_overflows))
|
||||
for i, (name, _delta) in enumerate(_WINDOWS[:3]):
|
||||
lane[i] = FPair(name, f"window_{i}", enabled=i != self._window_index)
|
||||
nxt, _delta = _WINDOWS[(self._window_index + 1) % len(_WINDOWS)]
|
||||
lane[2] = FPair(f"▸ {'all' if nxt == 'all time' else nxt}", "window")
|
||||
return lane
|
||||
|
||||
def __init__(
|
||||
@@ -267,12 +274,11 @@ class TimeMachineScreen(Screen):
|
||||
self.title = f"{self._label} · {name}"
|
||||
|
||||
def handle(self, action: str, data: str = "") -> None:
|
||||
"""Scroll, switch the window, or dismiss.
|
||||
"""Scroll, cycle the window, or dismiss.
|
||||
|
||||
Two ways onto a span, one behaviour: ``w`` (and the legacy ``window`` action)
|
||||
cycles the platform's ring, while the lane's per-span chips land on a window
|
||||
directly (``window_0`` … ``window_2``) — so a chip is never a second
|
||||
implementation of the letter.
|
||||
The window cycles on either the ``w`` key or the ``window`` action the F-key lane
|
||||
dispatches — one behaviour, two ways in, so the chip is not a second implementation
|
||||
of the letter. The ring it cycles is the platform's (see :func:`_bind_windows`).
|
||||
"""
|
||||
if action == "up":
|
||||
self.scroll_lines(-1)
|
||||
@@ -287,21 +293,13 @@ class TimeMachineScreen(Screen):
|
||||
elif action in ("end", "ctrl_end"):
|
||||
self.scroll_to_bottom()
|
||||
elif action == "window" or (action == "text" and data.lower() == "w"):
|
||||
self._set_window((self._window_index + 1) % len(_WINDOWS))
|
||||
elif action.startswith("window_") and action[7:].isdigit():
|
||||
self._set_window(int(action[7:]))
|
||||
self._window_index = (self._window_index + 1) % len(_WINDOWS)
|
||||
self._set_title()
|
||||
self.scroll_to_top()
|
||||
self._session.invalidate()
|
||||
elif action == "escape":
|
||||
self.resolve(None)
|
||||
|
||||
def _set_window(self, index: int) -> None:
|
||||
"""Land the page on window ``index`` (a no-op off the ring or already there)."""
|
||||
if not (0 <= index < len(_WINDOWS)) or index == self._window_index:
|
||||
return
|
||||
self._window_index = index
|
||||
self._set_title()
|
||||
self.scroll_to_top()
|
||||
self._session.invalidate()
|
||||
|
||||
def render_body(self, width: int) -> list[str]:
|
||||
"""Render (or reuse) the current window's sections."""
|
||||
key = (self._window_index, width)
|
||||
|
||||
@@ -844,17 +844,13 @@ def test_node_detail_screen_tabs_switch_the_stage() -> None:
|
||||
assert "│ Info │" in body and "centred here" in body
|
||||
assert screen.consume_edge_scrub() == 2
|
||||
|
||||
# One F-key chip per tab, in strip order; the tab on screen dims (its key would
|
||||
# change nothing), and pressing the other's chip lands on that view directly.
|
||||
assert [pair.label for pair in screen.fkey_lane[:2]] == ["Info", "Routes"]
|
||||
assert screen.fkey_lane[0].enabled is False and screen.fkey_lane[1].enabled is True
|
||||
screen.handle("tab_1") # the F2 chip: land on the Routes tab
|
||||
# The F-key chip names where the switch would take you, so it flips with the stage.
|
||||
assert screen.fkey_lane[2].label == "Routes"
|
||||
screen.handle("tab") # switch to the Routes tab
|
||||
body = _plain(screen.render_body(72))
|
||||
assert "│ Routes │" in body and "no route observed yet" in body
|
||||
assert screen.consume_edge_scrub() == 0 # the braille preview isn't showing now
|
||||
assert screen.fkey_lane[0].enabled is True and screen.fkey_lane[1].enabled is False
|
||||
screen.handle("tab") # Tab still cycles, wrapping back to Info
|
||||
assert screen.fkey_lane[0].enabled is False
|
||||
assert screen.fkey_lane[2].label == "Info"
|
||||
|
||||
|
||||
def test_node_detail_single_tab_hides_the_switch_hint() -> None:
|
||||
|
||||
+26
-10
@@ -909,17 +909,33 @@ def test_screen_cycles_windows_and_caches(tmp_path: Path) -> None:
|
||||
screen.render_body(80)
|
||||
assert len(calls) == 2
|
||||
|
||||
# One chip per span on F1–F3 — the only way a platform that draws no hint line can
|
||||
# learn the spans exist. The span on screen dims; the direct chips land on their
|
||||
# window without cycling; `w` (and the legacy "window" action) still cycles the ring.
|
||||
assert [pair.label for pair in screen.fkey_lane[:3]] == ["24 h", "7 d", "30 d"]
|
||||
assert screen.fkey_lane[2].enabled is False # 30 d is what's on screen
|
||||
# The F-key chip is the same behaviour under another name — the only way a platform
|
||||
# that draws no hint line can learn that `w` exists at all. It names the span it takes
|
||||
# you *to*, since the title already says where you are.
|
||||
assert screen.fkey_lane[2].label == "▸ all"
|
||||
screen.handle("window")
|
||||
assert "all time" in screen.title # the desktop ring still carries all time
|
||||
assert all(pair.enabled for pair in screen.fkey_lane[:3])
|
||||
screen.handle("window_0")
|
||||
assert "24 h" in screen.title
|
||||
assert screen.fkey_lane[0].enabled is False
|
||||
assert "all time" in screen.title
|
||||
assert screen.fkey_lane[2].label == "▸ 24 h" # the ring wraps back around
|
||||
|
||||
|
||||
def test_picocalc_window_ring_stops_at_30_days() -> None:
|
||||
"""The handheld's ring skips all time: `w` wraps 24 h → 7 d → 30 d → 24 h.
|
||||
|
||||
All time is the one span whose scan grows with the whole history — a desktop
|
||||
affordance (see ``_bind_windows``); the chip cycles the three spans that remain.
|
||||
"""
|
||||
from meshterm.platforms import PICOCALC, set_platform
|
||||
|
||||
set_platform(PICOCALC)
|
||||
screen = TimeMachineScreen(
|
||||
session=_FakeSession(), label="YUL", build=lambda window, width: []
|
||||
)
|
||||
assert "7 d" in screen.title # opens on 7 d, as everywhere
|
||||
screen.handle("text", "w")
|
||||
assert "30 d" in screen.title
|
||||
assert screen.fkey_lane[2].label == "▸ 24 h" # never "▸ all" on this ring
|
||||
screen.handle("window")
|
||||
assert "24 h" in screen.title # wrapped straight past where all time would sit
|
||||
|
||||
|
||||
# --- the own-node page ------------------------------------------------------------------
|
||||
|
||||
Reference in new issue
Block a user