-
Notifications
You must be signed in to change notification settings - Fork 1
fix(tui): settings overlays — /cost in/out tokens, /profiles row fit, /config ellipsis + [RO] legend + server-default model, /permissions separator (#1405) #1406
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
0d782b8
ee6e8ae
65420f1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| package tui_test | ||
|
|
||
| import ( | ||
| "encoding/json" | ||
| "strings" | ||
| "testing" | ||
|
|
||
| "github.com/charmbracelet/lipgloss" | ||
|
|
||
| "go-agent-harness/cmd/harnesscli/tui" | ||
| ) | ||
|
|
||
| // Issue #1405: settings overlays must read correctly to a first-time user. | ||
|
|
||
| // /cost must show prompt tokens as "in" and completion tokens as "out". | ||
| func TestCost_ShowsPromptAndCompletionTokens(t *testing.T) { | ||
| m := initModel(t, 120, 40) | ||
| raw := `{"turn_usage":{"prompt_tokens":15000,"completion_tokens":700,"total_tokens":15700},` + | ||
| `"cumulative_usage":{"prompt_tokens":15000,"completion_tokens":700,"total_tokens":15700},"cumulative_cost_usd":0.0069}` | ||
| m2, _ := m.Update(tui.SSEEventMsg{EventType: "usage.delta", Raw: json.RawMessage(raw), RunID: "run-1"}) | ||
| m = m2.(tui.Model) | ||
| m = sendSlashCommand(m, "/cost") | ||
| view := m.View() | ||
| if !strings.Contains(view, "15,000 in") || !strings.Contains(view, "700 out") { | ||
| t.Fatalf("/cost must show 15,000 in and 700 out, got:\n%s", view) | ||
| } | ||
| if strings.Contains(view, "↑ 0 in") { | ||
| t.Fatalf("/cost must not report 0 input tokens after a run with prompt tokens:\n%s", view) | ||
| } | ||
| } | ||
|
|
||
| // /profiles must never wrap its highlighted row. | ||
| func TestProfilePicker_SelectedRowFitsWidth(t *testing.T) { | ||
| m := initModel(t, 120, 40) | ||
| m = sendSlashCommand(m, "/profiles") | ||
| m2, _ := m.Update(tui.ProfilesLoadedMsg{Entries: []tui.ProfileEntry{ | ||
| {Name: "bash-runner", Model: "gpt-4.1-mini", SourceTier: "built-in", Description: "Script execution, pipeline tasks"}, | ||
| {Name: "full", Model: "gpt-4.1-mini", SourceTier: "built-in", Description: "Default — all tools available"}, | ||
| }}) | ||
| m = m2.(tui.Model) | ||
| for _, line := range strings.Split(m.View(), "\n") { | ||
| if w := lipgloss.Width(line); w > 120 { | ||
| t.Fatalf("profiles row wider than the terminal (%d): %q", w, line) | ||
| } | ||
| } | ||
| if !strings.Contains(m.View(), "built-in") || strings.Contains(m.View(), "built-\n") { | ||
| t.Fatalf("highlighted profile row must not wrap mid-word:\n%s", m.View()) | ||
| } | ||
| } | ||
|
|
||
| // /config must not cut values silently and must explain [RO]. | ||
| func TestConfigPanel_ValuesEllipsisAndROLegend(t *testing.T) { | ||
| m := initModel(t, 120, 40) | ||
| m2, _ := m.Update(tui.ModelSelectedMsg{ModelID: "deepseek/deepseek-v4-pro-with-a-long-suffix-x", Provider: "openrouter"}) | ||
| m = m2.(tui.Model) | ||
| m = sendSlashCommand(m, "/config") | ||
| view := m.View() | ||
| if strings.Contains(view, "deepseek/deepseek-v4 ") && !strings.Contains(view, "…") { | ||
| t.Fatalf("/config must not cut the model id silently:\n%s", view) | ||
| } | ||
| if !strings.Contains(view, "deepseek/deepseek-v4-pro") { | ||
| t.Fatalf("/config should have room for the model id at 120 columns:\n%s", view) | ||
| } | ||
| if !strings.Contains(view, "read-only") { | ||
| t.Fatalf("/config must explain the [RO] badge:\n%s", view) | ||
| } | ||
| } | ||
|
|
||
| // /config must not show an empty model cell before a model is chosen. | ||
| func TestConfigPanel_ModelPlaceholderWhenUnset(t *testing.T) { | ||
| m := initModel(t, 120, 40) | ||
| m = sendSlashCommand(m, "/config") | ||
| if !strings.Contains(m.View(), "server default") { | ||
| t.Fatalf("/config must say the server default applies when no model is chosen:\n%s", m.View()) | ||
| } | ||
| } | ||
|
|
||
| // /permissions must not draw a stray separator. | ||
| func TestPermissionsPanel_NoStraySeparator(t *testing.T) { | ||
| m := initModel(t, 120, 40) | ||
| m = sendSlashCommand(m, "/permissions") | ||
| for _, line := range strings.Split(m.View(), "\n") { | ||
| trimmed := strings.TrimSpace(strings.Trim(strings.TrimSpace(line), "│")) | ||
| if trimmed == "──" || trimmed == "─" { | ||
| t.Fatalf("stray separator line in /permissions: %q\n%s", line, m.View()) | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,10 @@ | ||
| # Engineering Log | ||
|
|
||
| ## 2026-09-06 — Settings overlays read wrong to a first-time user (#1405) | ||
|
|
||
| - Symptom: `/cost` showed `↑ 0 in ↓ 15,760 out` after a run (the TUI only tracked a single total and passed it as output); `/profiles` wrapped its highlighted row mid-word; `/config` cut values at 20 characters with no ellipsis (the model id read as `deepseek/deepseek-v4`) and never explained `[RO]`, and showed an empty model cell before a model was chosen; `/permissions` drew a stray `──` line because its separator was as wide as the terminal inside a narrower box. | ||
| - Fix: `applyUsageDelta` keeps cumulative prompt/completion tokens for the cost snapshot; the profile picker sizes rows to the box content area; the config panel widens the value column to the dialog, ends cut values with `…`, adds `[RO] read-only` to the footer and a "(server default — use /model to choose)" placeholder; the permissions panel is sized to the overlay box. Config snapshot goldens regenerated. Live tmux captures in PR. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
A repository-wide search finds no #1405 entry or corresponding success definition in AGENTS.md reference: AGENTS.md:L23-L23 Useful? React with 👍 / 👎. |
||
|
|
||
| ## 2026-09-06 — A chat message could be saved as an API key (#1403) | ||
|
|
||
| - Symptom: in the TUI, selecting a model whose provider had no key jumped to the API Keys panel with no explanation; letters typed while the panel was open fell through into the chat input; Enter then opened the key form, and the next text plus Enter (`/model`) was stored as the DeepSeek key both client-side (`~/.config/harnesscli/config.json`) and on the daemon. Keys rows also wrapped inside the box and `kimi-subscription` was labelled "ChatGPT subscription"; the picker had no legend for `●/○/(n)` and sorted providers case-sensitively. | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This commit adds the durable #1405 engineering-log entry while leaving
docs/logs/INDEX.mdunchanged, so the folder index does not expose the new record. Update the logs index in the same change.AGENTS.md reference: AGENTS.md:L56-L56
Useful? React with 👍 / 👎.