You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The TUI works, but it does not read as one application. Each screen invented its own header height, its own footer format, and its own key set; navigation keys were hand-rolled as literal characters instead of going through TamboUI's binding layer; and the same key means different things on different screens. This issue collects the specific defects, a baseline drawn from TamboUI's own code and from established TUIs, and an ordered plan.
Everything below was checked against the code on main/feat/lst-daemon-11 and against sources I actually fetched (listed at the end).
1. What's wrong today
1.1 Vim navigation is hand-coded, against the framework's own default
Every list screen re-implements j/k by testing raw characters:
atunko-tui/src/main/java/io/github/atunkodev/tui/view/BrowserView.java:162-169 — if (event.isDown() || event.isChar('j'))
.../view/ConfirmRunView.java:151-156
.../view/LoadConfigView.java:87-92
.../view/RecipeOptionsView.java:155-160
.../view/TagBrowserView.java:130-135 and :147-152
.../view/ExecutionResultsView.java:164-169
TamboUI's default binding set is deliberately not vim. From dev/tamboui/tui/bindings/BindingSets.java in tamboui-tui-0.4.0-sources.jar:
standard() — Arrow keys only, no vim/emacs bindings (default) […] No vim or emacs-style bindings. This is the default set, safe for use with text input (no letter keys bound to navigation).
and standard.properties in the same artifact is exactly moveUp = Up, moveDown = Down, select = Enter, Space, cancel = Escape, focusNext = Tab, focusPrevious = Shift+Tab, quit = q, Q, Ctrl+c. A separate vim.properties (moveUp = Up, k, K, …) exists precisely so that apps that want vim opt in. atunko opts in by hand, in seven places, and the owner does not want vim bindings.
event.isUp()/isDown() already delegate to the configured bindings (KeyEvent.java:388-398, return matches(Actions.MOVE_UP)), so the || event.isChar('j') half is pure added vim. Similarly BrowserView.java:229 is event.isQuit() || event.isChar('q') — isQuit() already covers q, Q and Ctrl+C.
1.2 No central binding configuration exists
AtunkoTui.java:76-79 builds the runner without .bindings(...):
ToolkitRunner.Builder.bindings(Bindings) exists (ToolkitRunner.java:505 in the 0.4.0 sources) and is the intended place to declare an app-wide key map. Because atunko never uses it, there is no single list of what the app binds — the answer is spread across 65 isChar(...) calls across ten view classes.
Worth noting: .bindings() used to be silently ignored (tamboui#382). That was fixed by tamboui#383, merged 2026-06-18 20:06 UTC, and v0.4.0 was released 2026-06-18 22:09 UTC — so the version atunko already depends on (gradle/libs.versions.toml:5, tamboui = "0.4.0") has the fix. This is safe to adopt now.
(Aside: CLAUDE.md still says TamboUI 0.2.0-SNAPSHOT under "Key Dependencies". That's two minor versions stale.)
1.3 Tab and Shift-Tab are dead keys, and there is no panel focus model
Each screen marks exactly one element focusable — the root column (BrowserView.java:72, ConfirmRunView.java:71, DetailView.java:122, ExecutionResultsView.java:63,100,143, LoadConfigView.java:61, RecipeOptionsView.java:64, TagBrowserView.java:87, FileDiffView.java:68, ExportConfigView.java:56). The search input is explicitly opted out (BrowserView.java:293, .focusable(false)), as is the tag filter input (TagBrowserView.java:70).
The browser is a genuine two-pane screen — recipe list plus detail pane (BrowserView.java:52) — but there is nothing to move focus between them and nothing rendering which pane is active. focusNext/focusPrevious are bound by default to Tab/Shift-Tab and go nowhere. The one screen where Tab does something is ExportConfigView.java:69, where it toggles Minimal/Full — a meaning that exists on exactly one screen.
Neither themes/dark.tcss nor themes/light.tcss has a focus rule; the only highlight styling is the list row cursor (dark.tcss:69).
1.4 Help is not reachable from most screens
? is handled in three of ten screens: BrowserView.java:258, ConfirmRunView.java:146, DetailView.java:128. It is not handled in TagBrowserView, ExecutionResultsView, FileDiffView, LoadConfigView, RecipeOptionsView or ExportConfigView, and those screens' footers do not mention it either (FileDiffView.java:65 is the whole footer: " Esc/q:back").
1.5 The help "overlay" is not an overlay, and eats the next keystroke
BrowserView.java:48-54 and ConfirmRunView.java:39-41replace the center content with the help panel. The recipe list disappears while you read about it, which is the opposite of what help is for.
Then BrowserView.java:108-111:
if (controller.isShowHelp()) {
controller.toggleHelp();
returnEventResult.HANDLED;
}
Any key closes help and is swallowed. Read the help, learn that r opens the run dialog, press r — help closes and nothing else happens.
TamboUI supports real overlays: the Clear widget exists to blank an area "before rendering overlays" (widgets reference), and the filemanager demo renders its dialogs on top of the finished UI tree rather than in place of it (FileManagerView.java:63-75).
1.6 Key hints are hardcoded strings, triplicated, and already wrong
The same binding is written out in the footer, in HelpOverlay, in README.md and in docs/antora/.../cli.adoc. They have drifted:
README.md:101 — "a | Cycle selection (none → all → none)". The code has a = select all and A = deselect all (BrowserView.java:178-185); cycleSelection() is not bound to anything in the browser.
README.md:106 — "←/c | Collapse composite recipe". c is not bound in the browser (BrowserView.java:217 accepts only ← and <). It is bound in the run dialog (ConfirmRunView.java:183).
BrowserView.java:369 — the browser status bar advertises o:options ?:help q:quit. Roughly a dozen other browser bindings are invisible unless the user finds ?.
There is no TUI page in docs/antora/modules/ROOT/pages/ at all; the binding tables live inside cli.adoc:71-152.
TamboUI upstream has an open issue about exactly this duplication — tamboui#168, "Reverse bindings and display":
In many demos, we actually use hardcoded strings showing [q] Quit or [F1] Something. However, these are directly related to the Bindings in use. There should be a way to get these descriptions from the bindings rather than duplicate.
1.7 The same key means different things on different screens
f = toggle favorite in the browser (BrowserView.java:250), = flatten composite in the run dialog (ConfirmRunView.java:187).
F = cycle favorites filter (BrowserView.java:254), = flatten all (ConfirmRunView.java:191).
e = expand (BrowserView.java:209) while E = expand all and W = collapse all (BrowserView.java:221-228). W has no mnemonic connection to "collapse"; it is E's neighbour on the keyboard.
Case-sensitive pairs carry meaning throughout — a/A, f/F, n/N, E/W, S/L. Shift-as-modifier is invisible in a footer that renders a and A identically to a fast reader.
Ctrl+↑/Ctrl+↓ reorder in the run dialog (ConfirmRunView.java:159-165) — a chord where +/- already exist for the same action.
1.8 Chrome is inconsistent screen to screen
Header height: the browser docks a 3-line header (BrowserView.java:66, Constraint.length(3)); every other screen docks 1 (ConfirmRunView.java:65, ExecutionResultsView.java:133, TagBrowserView.java:84, …). The title bar jumps two rows on every navigation.
Footer formatting is ad hoc: " Esc/q:back" (FileDiffView.java:65), " j/k:navigate Enter:load Esc/q:back" (LoadConfigView.java:54), " g:Gradle m:Maven Tab:Minimal/Full Esc:close" (ExportConfigView.java:51), " ↑↓/jk:navigate Enter:diff Esc/q:back" (ExecutionResultsView.java:130), "… o:options f:flatten F:flatten-all x:export ?:help Esc:back" (ConfirmRunView.java:57). Different separators, different orderings, key:action with no visual distinction between the key and its label.
Two overlays (RecipeOptionsView, ExportConfigView) are dispatched by boolean flags checked at the top of two different render methods (BrowserView.java:41-43, ConfirmRunView.java:27-32) rather than being part of the Screen enum (Screen.java), so "what is on screen" has two competing sources of truth.
2. Best-practice baseline
Marked [TamboUI] where it is framework idiom and [general] where it is a cross-ecosystem TUI convention.
2.1 Layout
Convention
Source
One dock() per screen: .top(header, length(1)), .center(content), .bottom(keyHints, length(1)) — same heights on every screen
[TamboUI] filemanager demo, FileManagerView.java:63-66: dock().top(header(), Constraint.length(1)).center(browserRow(context)).bottom(helpBar(), Constraint.length(1)); jtop demo JTopDemo.java:63-71 uses the same shape
Footer is context-sensitive: it shows information about the current selection and the keys valid right now
[TamboUI]FileManagerView.java:319-336 — row(text(info).fill(), text("[Enter] Open [v] View [Backspace] Up [+] Mark All [-] Unmark").dim())
Key hints styled so key and label are visually separate (key bright/bold, label dim)
Dialogs/overlays render on top of the screen, not instead of it
[TamboUI]FileManagerView.java:69-75 renders the dialog after the main tree; Clear widget documented for exactly this
Persistent focus indication when more than one pane can take focus
[general] lazygit's multi-panel model; Textual's footer/focus model
? opens a keybindings screen, from anywhere
[general] k9s: "? — Show active keyboard mnemonics and help"; lazygit Global section: "? Open keybindings menu"
2.2 Bindings
Declare one Bindings object from BindingSets.standard() and handle events with event.matches(ACTION) — [TamboUI], and exactly what jbang (Max Andersen's own TamboUI consumer) does in ArtifactSearchWidget.java:114-124:
function keys are self-documenting in a footer and never collide with typing; the filemanager demo and jbang both use them
single letters
only as additional accelerators, never the sole way to reach an action, and never case-sensitive pairs
—
[general]
Two consequences worth calling out:
Retire case-sensitive pairs.a/A, f/F, E/W, n/N, S/L should become distinct, spelled-out actions in the help screen with non-shifted or function-key accelerators.
Same verb, same key, every screen.f cannot be "favorite" here and "flatten" there.
2.3 Discoverability
The strong pattern across ecosystems is that key hints are generated from the binding table, not typed twice:
Textual: "The Footer widget can inspect bindings to display available keys"; each binding carries "a short human readable description" [general]
Bubble Tea / bubbles help: "A customizable horizontal mini help view that automatically generates itself from your keybindings", with short and full modes; key.Binding pairs WithKeys(...) and WithHelp(...)[general]
TamboUI has no such widget yet, and knows it — tamboui#168 [TamboUI]
So atunko should build the small version of this itself: one binding registry per screen, with a description per entry; the footer renders a subset, the ? screen renders all of it. That kills the drift in §1.6 by construction.
3. Proposed changes
Cheap wins (no architectural change)
Drop the hand-rolled vim keys. Delete every || event.isChar('j') / isChar('k') and the redundant || event.isChar('q'). BrowserView.java:162-169,229, ConfirmRunView.java:151-156, LoadConfigView.java:87-92, RecipeOptionsView.java:155-160, TagBrowserView.java:130-135,147-152, ExecutionResultsView.java:164-169
Make ? work on every screen, and add ?:help to every footer. TagBrowserView, ExecutionResultsView, FileDiffView, LoadConfigView, RecipeOptionsView, ExportConfigView + HelpOverlay.java (needs the missing per-screen sections)
Stop swallowing the keystroke that closes help. Close on Esc/? only; let other keys fall through to the normal handler. BrowserView.java:108-111, ConfirmRunView.java:146
Unify the header to Constraint.length(1) so the title bar stops jumping. BrowserView.java:66
One footer helper — a single KeyHints.render(List<Entry>) producing bright-key/dim-label pairs in the jbang style, used by all ten screens.
new .../view/KeyHints.java; call sites in all view/*.java
Fix the drifted docs: README.md:98-126 and docs/antora/modules/ROOT/pages/cli.adoc:71-152 (a, c, and the j/k column all wrong once (1) lands). A dedicated docs/antora/modules/ROOT/pages/tui.adoc would be the right home.
Update CLAUDE.md "Key Dependencies" — TamboUI is 0.4.0, not 0.2.0-SNAPSHOT.
Larger restructuring
Introduce an app-wide Bindings from BindingSets.standard(), wired through ToolkitRunner.builder().bindings(...), with atunko's own action constants (atunko.run, atunko.dryRun, atunko.options, atunko.flatten, …). Views switch from isChar('x') to event.matches(Actions.RUN). AtunkoTui.java:74-85 + new .../tui/AtunkoBindings.java + every view/*.java
A binding registry with descriptions, one per screen, as the single source for (a) dispatch, (b) the footer, (c) the ? screen. Replaces the hand-written HelpOverlay tables. HelpOverlay.java (rewritten) + AtunkoBindings.java
Resolve the cross-screen collisions using the table in §2.2: f/F split, E/W retired, Ctrl+↑/↓ retired in favour of +/-, function keys for screen verbs. BrowserView.java:178-270, ConfirmRunView.java:146-216
Real focus model in the browser: make the recipe list and the detail pane independently focusable, wire Tab/Shift-Tab, and add a focus style to themes/dark.tcss / themes/light.tcss. BrowserView.java:52,72, RecipeListRenderer.java, both .tcss files
Render overlays as overlays (help, recipe options, export) on top of the current screen instead of replacing the center, and fold them into Screen or an explicit overlay stack so there is one source of truth for what is displayed. BrowserView.java:41-54, ConfirmRunView.java:27-41, Screen.java, AtunkoTui.java:45-56
Bind the currently unreachable navigation: PgUp/PgDn/Home/End on every list. TuiController.java (new page/jump methods) + list views
Per project convention this needs an OpenSpec change proposed and approved before any of it is implemented.
4. Explicitly out of scope
Vim-style bindings — no hjkl, no gg/G, no : command line, no modal normal/insert distinction. BindingSets.vim() will not be used.
Emacs-style bindings — no Ctrl+n/Ctrl+p/Ctrl+f/Ctrl+b, no Ctrl+g. BindingSets.emacs() will not be used.
Chorded or multi-key sequences generally. Ctrl+C for quit is the one accepted exception because it is a terminal convention, not an app binding.
The existing j/k bindings are the concrete violation and are removed by item (1).
5. How much real-world TamboUI usage exists — honestly
More than I expected, but the sample is still small and none of it is a published style guide.
Found and read:
TamboUI's own demos: filemanager-demo (best layout exemplar), jtop-demo, action-handler-demo — the last shows the @OnAction annotation route
jbangdeps search (dev.jbang.search.ArtifactSearchWidget) — Max Andersen's own consumer, and the single best model for what atunko should do with bindings
Apache Maven Pilot — a TamboUI TUI; the announcement blog confirms it exists and uses context-sensitive help on every screen (bound to h), but shows no layout code
Apache Camel TUI — announced, TamboUI-based
Spring Initializr TUI (Dan Vega) — a TamboUI consumer; the write-up describes keys (Enter, c, x, Tab, Esc) but no layout code
Did not find:
Any showcase/gallery page or layout/keybinding style guide in the TamboUI docs. The docs index has bindings.html and layouts.html but no "how to structure an app screen" guidance and no key-hints or footer widget in the widget reference.
Any Max Andersen post specifically about TUI layout or keybinding design. His announcement post covers motivation and architecture only; the Devoxx France 2026 talk "TamboUI: making 2026 the Year of Java in the Terminal" exists in the schedule but I found no recording or slides. I did not find LinkedIn/Mastodon/Bluesky posts on the topic. What I did find that is directly useful is his code (jbang) and his bug report (tamboui#382), both cited above.
So the §2 baseline is deliberately weighted: TamboUI's own demos and jbang for framework idiom, and k9s / lazygit / Textual / Bubble Tea for the general conventions that TamboUI has no opinion on yet.
Source read locally from the Gradle cache (dev.tamboui:tamboui-tui:0.4.0, dev.tamboui:tamboui-toolkit:0.4.0 sources jars): BindingSets.java, Actions.java, KeyEvent.java, ToolkitRunner.java, standard.properties, vim.properties
Demo sources via the GitHub API: demos/filemanager-demo/.../FileManagerView.java, tamboui-toolkit/demos/jtop-demo/.../JTopDemo.java, tamboui-toolkit/demos/action-handler-demo/...
The TUI works, but it does not read as one application. Each screen invented its own header height, its own footer format, and its own key set; navigation keys were hand-rolled as literal characters instead of going through TamboUI's binding layer; and the same key means different things on different screens. This issue collects the specific defects, a baseline drawn from TamboUI's own code and from established TUIs, and an ordered plan.
Everything below was checked against the code on
main/feat/lst-daemon-11and against sources I actually fetched (listed at the end).1. What's wrong today
1.1 Vim navigation is hand-coded, against the framework's own default
Every list screen re-implements
j/kby testing raw characters:atunko-tui/src/main/java/io/github/atunkodev/tui/view/BrowserView.java:162-169—if (event.isDown() || event.isChar('j')).../view/ConfirmRunView.java:151-156.../view/LoadConfigView.java:87-92.../view/RecipeOptionsView.java:155-160.../view/TagBrowserView.java:130-135and:147-152.../view/ExecutionResultsView.java:164-169TamboUI's default binding set is deliberately not vim. From
dev/tamboui/tui/bindings/BindingSets.javaintamboui-tui-0.4.0-sources.jar:and
standard.propertiesin the same artifact is exactlymoveUp = Up,moveDown = Down,select = Enter, Space,cancel = Escape,focusNext = Tab,focusPrevious = Shift+Tab,quit = q, Q, Ctrl+c. A separatevim.properties(moveUp = Up, k, K, …) exists precisely so that apps that want vim opt in. atunko opts in by hand, in seven places, and the owner does not want vim bindings.event.isUp()/isDown()already delegate to the configured bindings (KeyEvent.java:388-398,return matches(Actions.MOVE_UP)), so the|| event.isChar('j')half is pure added vim. SimilarlyBrowserView.java:229isevent.isQuit() || event.isChar('q')—isQuit()already coversq,QandCtrl+C.1.2 No central binding configuration exists
AtunkoTui.java:76-79builds the runner without.bindings(...):ToolkitRunner.Builder.bindings(Bindings)exists (ToolkitRunner.java:505in the 0.4.0 sources) and is the intended place to declare an app-wide key map. Because atunko never uses it, there is no single list of what the app binds — the answer is spread across 65isChar(...)calls across ten view classes.Worth noting:
.bindings()used to be silently ignored (tamboui#382). That was fixed by tamboui#383, merged 2026-06-18 20:06 UTC, and v0.4.0 was released 2026-06-18 22:09 UTC — so the version atunko already depends on (gradle/libs.versions.toml:5,tamboui = "0.4.0") has the fix. This is safe to adopt now.(Aside:
CLAUDE.mdstill says TamboUI0.2.0-SNAPSHOTunder "Key Dependencies". That's two minor versions stale.)1.3 Tab and Shift-Tab are dead keys, and there is no panel focus model
Each screen marks exactly one element focusable — the root
column(BrowserView.java:72,ConfirmRunView.java:71,DetailView.java:122,ExecutionResultsView.java:63,100,143,LoadConfigView.java:61,RecipeOptionsView.java:64,TagBrowserView.java:87,FileDiffView.java:68,ExportConfigView.java:56). The search input is explicitly opted out (BrowserView.java:293,.focusable(false)), as is the tag filter input (TagBrowserView.java:70).The browser is a genuine two-pane screen — recipe list plus detail pane (
BrowserView.java:52) — but there is nothing to move focus between them and nothing rendering which pane is active.focusNext/focusPreviousare bound by default to Tab/Shift-Tab and go nowhere. The one screen whereTabdoes something isExportConfigView.java:69, where it toggles Minimal/Full — a meaning that exists on exactly one screen.Neither
themes/dark.tcssnorthemes/light.tcsshas a focus rule; the only highlight styling is the list row cursor (dark.tcss:69).1.4 Help is not reachable from most screens
?is handled in three of ten screens:BrowserView.java:258,ConfirmRunView.java:146,DetailView.java:128. It is not handled inTagBrowserView,ExecutionResultsView,FileDiffView,LoadConfigView,RecipeOptionsVieworExportConfigView, and those screens' footers do not mention it either (FileDiffView.java:65is the whole footer:" Esc/q:back").1.5 The help "overlay" is not an overlay, and eats the next keystroke
BrowserView.java:48-54andConfirmRunView.java:39-41replace the center content with the help panel. The recipe list disappears while you read about it, which is the opposite of what help is for.Then
BrowserView.java:108-111:Any key closes help and is swallowed. Read the help, learn that
ropens the run dialog, pressr— help closes and nothing else happens.TamboUI supports real overlays: the
Clearwidget exists to blank an area "before rendering overlays" (widgets reference), and the filemanager demo renders its dialogs on top of the finished UI tree rather than in place of it (FileManagerView.java:63-75).1.6 Key hints are hardcoded strings, triplicated, and already wrong
The same binding is written out in the footer, in
HelpOverlay, inREADME.mdand indocs/antora/.../cli.adoc. They have drifted:README.md:101— "a| Cycle selection (none → all → none)". The code hasa= select all andA= deselect all (BrowserView.java:178-185);cycleSelection()is not bound to anything in the browser.README.md:106— "←/c| Collapse composite recipe".cis not bound in the browser (BrowserView.java:217accepts only←and<). It is bound in the run dialog (ConfirmRunView.java:183).BrowserView.java:369— the browser status bar advertiseso:options ?:help q:quit. Roughly a dozen other browser bindings are invisible unless the user finds?.docs/antora/modules/ROOT/pages/at all; the binding tables live insidecli.adoc:71-152.TamboUI upstream has an open issue about exactly this duplication — tamboui#168, "Reverse bindings and display":
1.7 The same key means different things on different screens
f= toggle favorite in the browser (BrowserView.java:250), = flatten composite in the run dialog (ConfirmRunView.java:187).F= cycle favorites filter (BrowserView.java:254), = flatten all (ConfirmRunView.java:191).e= expand (BrowserView.java:209) whileE= expand all andW= collapse all (BrowserView.java:221-228).Whas no mnemonic connection to "collapse"; it isE's neighbour on the keyboard.a/A,f/F,n/N,E/W,S/L. Shift-as-modifier is invisible in a footer that rendersaandAidentically to a fast reader.Ctrl+↑/Ctrl+↓reorder in the run dialog (ConfirmRunView.java:159-165) — a chord where+/-already exist for the same action.1.8 Chrome is inconsistent screen to screen
BrowserView.java:66,Constraint.length(3)); every other screen docks 1 (ConfirmRunView.java:65,ExecutionResultsView.java:133,TagBrowserView.java:84, …). The title bar jumps two rows on every navigation." Esc/q:back"(FileDiffView.java:65)," j/k:navigate Enter:load Esc/q:back"(LoadConfigView.java:54)," g:Gradle m:Maven Tab:Minimal/Full Esc:close"(ExportConfigView.java:51)," ↑↓/jk:navigate Enter:diff Esc/q:back"(ExecutionResultsView.java:130),"… o:options f:flatten F:flatten-all x:export ?:help Esc:back"(ConfirmRunView.java:57). Different separators, different orderings,key:actionwith no visual distinction between the key and its label.RecipeOptionsView,ExportConfigView) are dispatched by boolean flags checked at the top of two different render methods (BrowserView.java:41-43,ConfirmRunView.java:27-32) rather than being part of theScreenenum (Screen.java), so "what is on screen" has two competing sources of truth.2. Best-practice baseline
Marked [TamboUI] where it is framework idiom and [general] where it is a cross-ecosystem TUI convention.
2.1 Layout
dock()per screen:.top(header, length(1)),.center(content),.bottom(keyHints, length(1))— same heights on every screenFileManagerView.java:63-66:dock().top(header(), Constraint.length(1)).center(browserRow(context)).bottom(helpBar(), Constraint.length(1)); jtop demoJTopDemo.java:63-71uses the same shapeFileManagerView.java:319-336—row(text(info).fill(), text("[Enter] Open [v] View [Backspace] Up [+] Mark All [-] Unmark").dim())ArtifactSearchWidget.java:241-260:text(" ↑↓").yellow().bold(), text(" nav ").dim(), text("⏎").yellow().bold(), text(" select ").dim(), …FileManagerView.java:69-75renders the dialog after the main tree;Clearwidget documented for exactly this?opens a keybindings screen, from anywhere?— Show active keyboard mnemonics and help"; lazygit Global section: "?Open keybindings menu"2.2 Bindings
Declare one
Bindingsobject fromBindingSets.standard()and handle events withevent.matches(ACTION)— [TamboUI], and exactly what jbang (Max Andersen's own TamboUI consumer) does inArtifactSearchWidget.java:114-124:Note what is absent: no
j, nok, nohjkl, no chords beyondCtrl+C. Arrows, Enter, Esc, Tab, F5.Proposed baseline for atunko — all of it satisfies the no-vim/no-emacs constraint:
↑↓standard.properties[TamboUI]PgUpPgDnHomeEndstandard.properties; currently unbound anywhere in atunko [TamboUI]→←Tab/Shift+TabfocusNext/focusPrevious[TamboUI]; makes the browser's two panes realEnterconfirm[TamboUI]Spaceselect[TamboUI]Esccancel[TamboUI]; today Esc in the browser clears everything (TuiController.java:779-787), which is not a cancel?orF1q/Ctrl+Cstandard.propertiesquit [TamboUI]//, near-universal [general]F2…F8Two consequences worth calling out:
a/A,f/F,E/W,n/N,S/Lshould become distinct, spelled-out actions in the help screen with non-shifted or function-key accelerators.fcannot be "favorite" here and "flatten" there.2.3 Discoverability
The strong pattern across ecosystems is that key hints are generated from the binding table, not typed twice:
help: "A customizable horizontal mini help view that automatically generates itself from your keybindings", with short and full modes;key.BindingpairsWithKeys(...)andWithHelp(...)[general]So atunko should build the small version of this itself: one binding registry per screen, with a description per entry; the footer renders a subset, the
?screen renders all of it. That kills the drift in §1.6 by construction.3. Proposed changes
Cheap wins (no architectural change)
|| event.isChar('j')/isChar('k')and the redundant|| event.isChar('q').BrowserView.java:162-169,229,ConfirmRunView.java:151-156,LoadConfigView.java:87-92,RecipeOptionsView.java:155-160,TagBrowserView.java:130-135,147-152,ExecutionResultsView.java:164-169?work on every screen, and add?:helpto every footer.TagBrowserView,ExecutionResultsView,FileDiffView,LoadConfigView,RecipeOptionsView,ExportConfigView+HelpOverlay.java(needs the missing per-screen sections)Esc/?only; let other keys fall through to the normal handler.BrowserView.java:108-111,ConfirmRunView.java:146Constraint.length(1)so the title bar stops jumping.BrowserView.java:66KeyHints.render(List<Entry>)producing bright-key/dim-label pairs in the jbang style, used by all ten screens.new
.../view/KeyHints.java; call sites in allview/*.javaREADME.md:98-126anddocs/antora/modules/ROOT/pages/cli.adoc:71-152(a,c, and thej/kcolumn all wrong once (1) lands). A dedicateddocs/antora/modules/ROOT/pages/tui.adocwould be the right home.CLAUDE.md"Key Dependencies" — TamboUI is0.4.0, not0.2.0-SNAPSHOT.Larger restructuring
BindingsfromBindingSets.standard(), wired throughToolkitRunner.builder().bindings(...), with atunko's own action constants (atunko.run,atunko.dryRun,atunko.options,atunko.flatten, …). Views switch fromisChar('x')toevent.matches(Actions.RUN).AtunkoTui.java:74-85+ new.../tui/AtunkoBindings.java+ everyview/*.java?screen. Replaces the hand-writtenHelpOverlaytables.HelpOverlay.java(rewritten) +AtunkoBindings.javaf/Fsplit,E/Wretired,Ctrl+↑/↓retired in favour of+/-, function keys for screen verbs.BrowserView.java:178-270,ConfirmRunView.java:146-216themes/dark.tcss/themes/light.tcss.BrowserView.java:52,72,RecipeListRenderer.java, both.tcssfilesScreenor an explicit overlay stack so there is one source of truth for what is displayed.BrowserView.java:41-54,ConfirmRunView.java:27-41,Screen.java,AtunkoTui.java:45-56PgUp/PgDn/Home/Endon every list.TuiController.java(new page/jump methods) + list viewsPer project convention this needs an OpenSpec change proposed and approved before any of it is implemented.
4. Explicitly out of scope
hjkl, nogg/G, no:command line, no modal normal/insert distinction.BindingSets.vim()will not be used.Ctrl+n/Ctrl+p/Ctrl+f/Ctrl+b, noCtrl+g.BindingSets.emacs()will not be used.Ctrl+Cfor quit is the one accepted exception because it is a terminal convention, not an app binding.The existing
j/kbindings are the concrete violation and are removed by item (1).5. How much real-world TamboUI usage exists — honestly
More than I expected, but the sample is still small and none of it is a published style guide.
Found and read:
filemanager-demo(best layout exemplar),jtop-demo,action-handler-demo— the last shows the@OnActionannotation routedeps search(dev.jbang.search.ArtifactSearchWidget) — Max Andersen's own consumer, and the single best model for what atunko should do with bindingsh), but shows no layout codeEnter,c,x,Tab,Esc) but no layout codeDid not find:
bindings.htmlandlayouts.htmlbut no "how to structure an app screen" guidance and no key-hints or footer widget in the widget reference.So the §2 baseline is deliberately weighted: TamboUI's own demos and jbang for framework idiom, and k9s / lazygit / Textual / Bubble Tea for the general conventions that TamboUI has no opinion on yet.
6. Sources
TamboUI project:
.bindings()propagation fix, in v0.4.0dev.tamboui:tamboui-tui:0.4.0,dev.tamboui:tamboui-toolkit:0.4.0sources jars):BindingSets.java,Actions.java,KeyEvent.java,ToolkitRunner.java,standard.properties,vim.propertiesdemos/filemanager-demo/.../FileManagerView.java,tamboui-toolkit/demos/jtop-demo/.../JTopDemo.java,tamboui-toolkit/demos/action-handler-demo/...TamboUI consumers:
src/main/java/dev/jbang/search/ArtifactSearchWidget.javaGeneral TUI prior art: