Skip to content

feat(tui): add /unread and a /doubletab setting; /settings only - #34

Merged
t0d0r merged 7 commits into
masterfrom
feature/doubletab-setting
Nov 3, 2026
Merged

t0d0r merged 7 commits into
masterfrom
feature/doubletab-setting

Conversation

@t0d0r

@t0d0r t0d0r commented Nov 3, 2026

Copy link
Copy Markdown
Owner

What

  • /unread: focuses the next window with unread messages after the current one, in window order and wrapping round. Window 0 is never a target. It focuses as /win N does (read receipts, the last window, restored counts). With none it gives no unread window.
  • /doubletab [off|last|unread|<ms>]: off by default and kept between starts.
    • off: every empty-prompt Tab goes to the next window, so fast Tabs walk the windows.
    • last: a fast second Tab goes to the last window, as /win last does.
    • unread: a fast second Tab goes where /unread would from the window before the first Tab. The window the first Tab passed through gets its count back first, so it can be the target. With nothing unread the second Tab is a plain Tab.
    • <ms>: a time from 150 to 1000 ms (default 400) replaces the fixed 400 ms everywhere. That includes the protection that keeps a window a Tab only passed through unread, which stays on in every mode.
  • /settings: two new rows, doubletab (off, last, unread) and doubletab-time (200 to 800 ms). A time set by the command that isn't in the list shows as it is.
  • Only /settings: /set and /options are removed and are now unknown commands.

/help, /keys, two start hints, specs/ui.md (with Decision lines), specs/overview.md and docs/usage.md describe it all.

Review

  • One implementing agent built it, then three reviews (correctness, spec conformance, test strength), with each finding verified by a second agent.
  • 2 confirmed and fixed:
    • A huge /doubletab number wrapped round into the valid range and was kept.
    • The spec claimed fast Tabs never walk in unread mode, but they do when nothing is unread.
  • 3 refuted. Two were /help texts no test pinned; they now have a test anyway.
  • Every new test was checked against a mutant of the code it guards.
  • Checked in the real demo (lazygram -data <scratch> demo).

make check and go test -race ./internal/tui/ ./internal/command/ pass.

Security: the two new state keys hold a word and a number. A window a Tab only passes through still sends no read receipt in any mode.

t0d0r added 7 commits November 4, 2026 01:54
/set and /options, the short forms of /settings, are gone from the
command table and from /help. Typing either is now an unknown command,
handled as any other: "unknown command /set, try /help", no menu, no
focus change. Tab completes /set to /settings, since that is the one
command it begins.

specs/ui.md (Commands, its short forms, Settings menu) and
docs/usage.md say so, with the reason as a Decision: one name for the
menu, so the command list has one way to it. The parser and settings
menu tests cover the removed spellings, their outcome next to an
unknown command's, and the completion.
/unread focuses the first chat window after the current one, in window
order and wrapping round, whose unread count is above 0: the windows
[Act: ...] lists, in turn. Window 0 keeps no count of its own and is
never one, even after [unread] on its /win row; the current window
comes last, so it is the target only when it alone is marked unread.
With none it is the error "no unread window" and nothing changes.

It is a focus like /win N (switchTo): the read receipt goes at once,
the last focused window follows, and a window an empty-prompt Tab
showed that it leaves within the Tab's time gets its unread count back
and is not reported. nextUnread in internal/tui/lastwin.go finds the
target, for the double Tab to use as well.

/help lists it and its notes mention it; Tab completes the name; there
are no aliases. specs/ui.md (Windows, Commands, Read receipts),
specs/overview.md and docs/usage.md describe it. Tests cover the
order, the wrap, window 0, the current window, the error, the receipt
and counts against /win N, and the window a Tab passed through.
Two fast empty-prompt Tabs used to jump back to the last window, so fast
Tabs no longer walked the windows. What the second Tab does is now a
kept setting, /doubletab [off|last|unread|<ms>], off by default:

- off: no double Tab; every Tab goes to the next window, however fast.
- last: the second Tab goes where /win last goes from the window the
  first Tab left, as before.
- unread: the second Tab goes where /unread goes from there. The window
  the first Tab passed through gets its unread count back first, so it
  is the target when it is the next one with unread messages; with no
  unread window the second Tab is a plain one.

The time window, 400 ms before, is kept too (150 to 1000 ms, "300" or
"300ms") and replaces the tabGap constant everywhere: the double Tab,
the held read receipt, the restored counts and the after timer. A held
receipt keeps the time it was held with. The pass-through protection
(a window a Tab showed and left within that time keeps its unread
count and sends no receipt) stays in every mode. No argument shows the
setting; on and any other word are an error that keeps nothing. The
mode is stored under "doubletab" as its word, the time under
"doubletab-time"; anything else reads as off and 400 ms.

The settings menu gets two rows after vim, doubletab (off, last,
unread) and doubletab-time (200ms to 800ms); a time set by the command
that the row does not list shows as it is and steps to its listed
neighbours. The setting code is in internal/tui/doubletab.go. Tab
completes off, last and unread; /help, /keys, the start hints,
specs/ui.md and docs/usage.md describe it, with the decisions: off by
default because it changes what fast Tabs do, last or unread because
both are a jump one may want, the protection in every mode because a
window flashed for a moment was not read.

Tests set /doubletab last where they use the gesture and cover the
default walk, both jumps, the time window and the held receipt's timer,
the command forms and their persistence, the menu rows, completion,
help and hints.
parseTabGap and loadDoubleTab multiplied the number of milliseconds by
time.Millisecond before checking the 150..1000 ms range. A large number
wraps int64 there: 2^58 + 300 (288230376151712044) becomes exactly
300 ms, so /doubletab 288230376151712044 was accepted and kept as 300,
and the same value in the store read as 300 ms instead of the 400 ms
default for an out-of-range time.

Both paths now go through tabGapOf, which checks the range on the int64
number itself and only then converts it. The command tests reject
2^58 + 300 (with and without ms), the int64 maximum and a number past
int64; the store tests read 2^58 + 300, -2^58 + 300 and a too-large
JSON number as 400 ms. specs/ui.md says that any larger number is the
error, that a kept time out of range means 400, and why the range is
checked before the conversion.
specs/ui.md said that with /doubletab last or unread fast Tabs do not
walk two windows. The second Tab is a plain Tab when it has no target
(no last window, or no window with unread messages), and for unread
having no unread window is the usual state, so in that mode fast Tabs
mostly do walk. The Windows section now limits the claim to a second
Tab with a target, says that without one (and always with off) fast
Tabs walk, and that with unread they usually walk. The off-by-default
Decision, the Read receipts paragraph and the /unread entry say the
same.

The unread status texts of /doubletab now end their jump with "if
any" (doubleTab.jump), and /help, docs/usage.md and the commands and
Keys tables of the spec say that with no unread window the double Tab
goes to the next window. The command and help tests assert the new
texts.
/help's /doubletab line must give the range and default the code uses
(minTabGap, maxTabGap, defaultTabGap), and no /help line may offer /set
or /options for the settings menu; neither was checked before.
Mark the item done in todo/todo.md and add session/2026-11-04-doubletab.md:
what /unread and /doubletab do, the review results, the decisions and
the open note on window 0's unread mark.
@t0d0r
t0d0r merged commit a0832e0 into master Nov 3, 2026
4 checks passed
@t0d0r
t0d0r deleted the feature/doubletab-setting branch November 3, 2026 23:59
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.

1 participant