feat(tui-go): command-palette keybinds + configurable status bar
Palette: each command now shows its bare-key shortcut in a key column (and the filter matches on it), so the palette teaches the shortcuts instead of hiding them. Status bar: a new "status bar" palette command opens a toggle overlay to show/hide the non-essential segments (current stage, session status, workspace path, model, last-event clock, resource gauge); correx/connection/session-name/spinner stay. Choices persist TUI-local in tui-prefs.json (JSON in the config dir — display state, kept out of the shared/replayed server config) and reload on launch. Also trims the in-session footer (drops stats/model — both reachable via `p cmds`) so it no longer overflows + truncates `q quit` at ~100 cols after the transcript-nav additions. Tests cover toggle→render gating and prefs persistence; verified via cmd/preview renders. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -95,29 +95,31 @@ func (m Model) renderStatus() string {
|
||||
|
||||
if s := m.session(m.selectedID); s != nil && m.displayState() != StateIdle {
|
||||
parts = append(parts, span(s.Name, t.P.FgStrong))
|
||||
if s.CurrentStage != "" {
|
||||
if s.CurrentStage != "" && m.sbShow("stage") {
|
||||
parts = append(parts, span("⟐ "+s.CurrentStage, t.P.Accent2))
|
||||
}
|
||||
if s.Status != "" {
|
||||
if s.Status != "" && m.sbShow("status") {
|
||||
parts = append(parts, lipgloss.NewStyle().Foreground(statusColor(t, s.Status)).Background(bg).Render(statusLabel(s.Status)))
|
||||
}
|
||||
if s.WorkspaceRoot != "" && !nrw {
|
||||
if s.WorkspaceRoot != "" && !nrw && m.sbShow("workspace") {
|
||||
parts = append(parts, span("⌂ "+shortPath(s.WorkspaceRoot), t.P.Faint))
|
||||
}
|
||||
}
|
||||
|
||||
model := m.currentModel
|
||||
if model == "" {
|
||||
model = "no model"
|
||||
}
|
||||
if nrw {
|
||||
parts = append(parts, span(model, t.P.Fg))
|
||||
} else {
|
||||
loc := "local"
|
||||
if m.providerType == "REMOTE" {
|
||||
loc = "remote"
|
||||
if m.sbShow("model") {
|
||||
model := m.currentModel
|
||||
if model == "" {
|
||||
model = "no model"
|
||||
}
|
||||
if nrw {
|
||||
parts = append(parts, span(model, t.P.Fg))
|
||||
} else {
|
||||
loc := "local"
|
||||
if m.providerType == "REMOTE" {
|
||||
loc = "remote"
|
||||
}
|
||||
parts = append(parts, span(model, t.P.Fg)+span(" ("+loc+")", t.P.Faint))
|
||||
}
|
||||
parts = append(parts, span(model, t.P.Fg)+span(" ("+loc+")", t.P.Faint))
|
||||
}
|
||||
left := strings.Join(parts, sep)
|
||||
|
||||
@@ -125,7 +127,7 @@ func (m Model) renderStatus() string {
|
||||
// Last-event clock (§2): always visible in-session, so a silent agent is never
|
||||
// mistaken for a healthy one. Updates every frame tick; turns warn-colored when an
|
||||
// active session has gone quiet past the stale threshold.
|
||||
if s := m.session(m.selectedID); s != nil && m.displayState() != StateIdle {
|
||||
if s := m.session(m.selectedID); s != nil && m.displayState() != StateIdle && m.sbShow("clock") {
|
||||
gap := nowMillis() - s.LastEventAt
|
||||
clockFg := t.P.Faint
|
||||
if s.Active && gap > staleEventThresholdMs {
|
||||
@@ -137,7 +139,7 @@ func (m Model) renderStatus() string {
|
||||
}
|
||||
right = append(right, span(label, clockFg))
|
||||
}
|
||||
if g := m.gaugeText(); g != "" && !nrw {
|
||||
if g := m.gaugeText(); g != "" && !nrw && m.sbShow("gauge") {
|
||||
right = append(right, span(g, t.P.Faint))
|
||||
}
|
||||
if s := m.session(m.selectedID); s != nil && s.Active {
|
||||
@@ -249,7 +251,9 @@ func (m Model) renderFooter() string {
|
||||
if nrw {
|
||||
hints = []string{hint("i", "msg"), hint("^↑↓", "msgs"), hint("y", "copy"), hint("e", "events"), hint("l", "back"), hint("p", "cmds"), hint("q", "quit")}
|
||||
} else {
|
||||
hints = []string{hint("i", "message"), hint("^↑↓", "msgs"), hint("y", "copy"), hint("e", "events"), hint("t", "tools"), hint("S", "stats"), hint("m", "model"), hint("l", "back"), hint("p", "cmds"), hint("q", "quit")}
|
||||
// Trimmed to fit ~100 cols: tools/stats/model are all reachable via `p cmds`,
|
||||
// so the footer keeps the high-traffic keys + the new transcript nav/copy.
|
||||
hints = []string{hint("i", "message"), hint("^↑↓", "msgs"), hint("y", "copy"), hint("e", "events"), hint("t", "tools"), hint("l", "back"), hint("p", "cmds"), hint("q", "quit")}
|
||||
}
|
||||
if m.copiedFlash != 0 && m.frame-m.copiedFlash < copiedFlashFrames {
|
||||
hints = append(hints, lipgloss.NewStyle().Foreground(t.P.OK).Background(bg).Bold(true).Render("✓ copied"))
|
||||
|
||||
Reference in New Issue
Block a user