feat(tui): always-visible capture bar with focus cycling

Replace drawer-based input with permanent capture bar at bottom.
Focus defaults to capture on startup — open nib, start typing.

- Remove stateInput; route via focusCapture/focusList/focusDetail
- Tab cycles: capture → list → detail (split) → capture
- Esc cascades: clear search → clear filter → focus capture
- Capture bar shows blinking cursor when focused, dims when not
- Intent cycling moved from tab to i (tab now cycles focus)
- Parse preview shown inline in status bar while typing
- Content area constant height (no layout thrash from drawer)
This commit is contained in:
2026-05-20 14:11:46 -04:00
parent 3daa5a2e11
commit a2dac64d1f
5 changed files with 141 additions and 132 deletions
+15 -12
View File
@@ -30,12 +30,10 @@ func renderTab(label, key string, active bool) string {
func renderStatusBar(m model, width int) string {
var leftParts []string
if m.state == stateList {
leftParts = append(leftParts, renderTab("capture", "a", false))
}
if m.status != "" {
leftParts = append(leftParts, statusStyle.Render(m.status))
} else if preview := m.input.previewText(); m.focus == focusCapture && preview != "" {
leftParts = append(leftParts, drawerPreviewStyle.Render(preview))
} else {
leftParts = append(leftParts, statusStyle.Render(countText(m)))
}
@@ -76,8 +74,6 @@ func contextHints(m model) []hint {
default:
return []hint{{"p", "promote"}, {"D", "demote"}, {"c", "copy"}, {"e", "edit"}, {"r", "run"}, {"f", "fill"}, {"!", "pin"}, {"esc", "back"}}
}
case stateInput:
return nil
case stateTagFilter:
return []hint{{"j/k", "nav"}, {"enter", "select"}, {"esc", "cancel"}}
case stateConfirm:
@@ -86,16 +82,23 @@ func contextHints(m model) []hint {
return []hint{{"j/k", "nav"}, {"enter", "select"}, {"esc", "cancel"}}
case stateAbsorb:
return []hint{{"j/k", "nav"}, {"enter", "absorb"}, {"esc", "cancel"}}
}
switch m.focus {
case focusCapture:
return []hint{{"enter", "submit"}, {"esc", "browse"}, {"?…", "search"}, {"-", "todo"}, {"@", "event"}}
case focusDetail:
if m.splitDetail {
return []hint{{"h", "list"}, {"c", "copy"}, {"e", "edit"}, {"p", "promote"}, {"!", "pin"}, {"tab", "capture"}}
}
return []hint{{"c", "copy"}, {"e", "edit"}, {"p", "promote"}, {"!", "pin"}, {"esc", "back"}}
default:
if m.splitDetail {
if m.focus == focusDetail {
return []hint{{"h", "list"}, {"c", "copy"}, {"e", "edit"}, {"p", "promote"}, {"D", "demote"}, {"!", "pin"}, {"esc", "back"}}
}
return []hint{{"l", "detail"}, {"d", "del"}, {"#", "filter"}, {"esc", "close"}, {"?", "help"}, {"q", "quit"}}
return []hint{{"l", "detail"}, {"d", "del"}, {"#", "filter"}, {"tab", "capture"}, {"?", "help"}}
}
if m.mode == modeCards {
return []hint{{"s", "sort"}, {"tab", "intent"}, {"?", "help"}, {"q", "quit"}}
return []hint{{"s", "sort"}, {"i", "intent"}, {"tab", "capture"}, {"?", "help"}}
}
return []hint{{"?", "search"}, {"m", "absorb"}, {"d", "del"}, {"#", "filter"}, {"?", "help"}, {"q", "quit"}}
return []hint{{"m", "absorb"}, {"d", "del"}, {"#", "filter"}, {"tab", "capture"}, {"?", "help"}}
}
}