Skip to content

Polish main TUI surface#168

Merged
anandh8x merged 16 commits into
mainfrom
feat/tui-main-screen-polish
Jun 11, 2026
Merged

Polish main TUI surface#168
anandh8x merged 16 commits into
mainfrom
feat/tui-main-screen-polish

Conversation

@anandh8x

@anandh8x anandh8x commented Jun 11, 2026

Copy link
Copy Markdown
Collaborator

Summary:

  • Simplifies the empty chat surface by removing starter prompt chips and duplicate model text.
  • Frames the composer with a stronger border and shows model + permission mode in the frame.
  • Quiets the bottom status line and improves alignment.
  • Adds terminal-style composer word editing for Alt/Ctrl word movement and deletion.
  • Adds app-managed mouse selection/copy for chat transcript text; this intentionally captures mouse input while the TUI is active, so native terminal selection remains available through the terminal escape path such as Shift-drag where supported.

Tests:

  • GOCACHE=/tmp/zero-go-cache go test ./internal/tui
  • GOCACHE=/tmp/zero-go-cache go test ./...
  • git diff --check

More UI polish can continue in follow-up PRs.

Summary by CodeRabbit

  • New Features
    • Word-wise cursor navigation, smarter word deletion, mention-aware backspace, transcript text selection & copy, mouse interactions for overlays.
  • UI/UX Changes
    • Bordered composer with stronger divider showing model/mode, inline command-argument hints, simplified empty state, centered/capped suggestion & picker overlays, model-picker loading/status views, refined footer/status rendering and theme accents.
  • Bug Fixes
    • More robust suggestion completion/dismissal, stale discovery guarding, cursor/selection edge-case fixes.
  • Tests
    • Large expansion across autocomplete, mouse, picker, wizard, rendering, onboarding, and config tests.
  • Config
    • Favorite models preference added with normalization and persistence.

Simplify the empty chat surface, replace starter prompt chips with a cleaner brand state, move model and permission state into the composer frame, quiet the footer status, and improve composer word-editing keybindings.

Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/tui

Tested: GOCACHE=/tmp/zero-go-cache go test ./...

Tested: git diff --check
@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: a8c655be-354a-47d6-9088-b0330ec4e513

📥 Commits

Reviewing files that changed from the base of the PR and between 0e85ab1 and 82557df.

📒 Files selected for processing (18)
  • internal/cli/app.go
  • internal/cli/app_test.go
  • internal/config/resolver.go
  • internal/config/resolver_test.go
  • internal/config/types.go
  • internal/tui/autocomplete.go
  • internal/tui/autocomplete_test.go
  • internal/tui/commands.go
  • internal/tui/model.go
  • internal/tui/model_test.go
  • internal/tui/mouse.go
  • internal/tui/mouse_test.go
  • internal/tui/options.go
  • internal/tui/provider_wizard.go
  • internal/tui/provider_wizard_discovery.go
  • internal/tui/provider_wizard_test.go
  • internal/tui/scroll_test.go
  • internal/tui/width_tiers_test.go
🚧 Files skipped from review as they are similar to previous changes (12)
  • internal/tui/options.go
  • internal/cli/app.go
  • internal/config/types.go
  • internal/config/resolver_test.go
  • internal/config/resolver.go
  • internal/tui/commands.go
  • internal/tui/width_tiers_test.go
  • internal/tui/mouse.go
  • internal/cli/app_test.go
  • internal/tui/mouse_test.go
  • internal/tui/provider_wizard.go
  • internal/tui/model.go

Walkthrough

Adds composer word navigation/mention-aware edits, dual command/file suggestion palettes with cached file indexing and ranked file completions, centered bordered suggestion/picker UI, model-picker loading/favorites persistence, provider-wizard gating, mouse-driven overlay hit-testing and transcript selection, and many aligned tests.

Changes

Composer UI and interaction overhaul

Layer / File(s) Summary
Word-boundary cursor movement & mention deletion
internal/tui/composer.go, internal/tui/composer_test.go
deleteComposerWordBefore skips trailing whitespace then removes the preceding non-whitespace run; adds moveComposerWordBefore/moveComposerWordAfter; backspace prefers deleting a completed @... mention before the cursor when suggestions are inactive; insertion may prepend a space for command-argument contexts; tests added.
Command usage parsing
internal/tui/commands.go, internal/tui/commands_test.go
Adds helpers to detect required command input from usage strings and to format a bracketed required-argument hint; table-driven tests added.
Autocomplete state, path query, and cached file index
internal/tui/autocomplete.go, internal/tui/autocomplete_test.go, internal/tui/model.go
Splits command/file palette state; cursor-aware @ extraction and path-query helpers; TTL-cached bounded filesystem index with ranking and caps; completion replaces only the active @ token for files or inserts command + spacing; many suggestion interaction tests added.
Suggestion/picker rendering & selectable list windowing
internal/tui/view.go, internal/tui/selectable_list.go, internal/tui/selectable_list_test.go, internal/tui/picker_test.go
Centered, bordered suggestion/picker overlays with title/query/footer, capped widths/rows, selection markers, rune-truncated columns, normalized file label display, and shifted anchor row for visible-window anchoring with tests.
Composer box, divider, transcript splicing & theme
internal/tui/model.go, internal/tui/view.go, internal/tui/theme.go, internal/tui/rendering_lime_test.go
Composer rendered inside composerBox(width) with framed composerDividerLine that can embed model & permission metadata at exact fit; transcript viewport can splice overlay content using ANSI-aware cutting; new lineStrong theme token and border color tweak; rendering tests updated.
Model picker loading & favorites persistence
internal/tui/picker.go, internal/tui/picker_test.go, internal/tui/options.go
Model-picker loading lifecycle and discovery gating rewritten; row metadata normalized; toggling favorites persists immediately via config.SetFavoriteModels; picker tests updated for loading/fallback/favorites and Options-provided favorites.
Provider wizard flow & tests
internal/tui/provider_wizard.go, internal/tui/provider_wizard_test.go, internal/tui/provider_wizard_discovery.go
Adds retreat() and guarded Right advance, blocks model-step advance while loading, introduces model-loading step and step-aware overlay sizing/footer, tightens discovery application to model step, and updates tests including a helper to finish discovery in tests.
Mouse handling & transcript selection
internal/tui/mouse.go, internal/tui/transcript_selection.go, internal/tui/mouse_test.go
Adds mouse routing, overlay hit-testing, wheel navigation, transcript text selection with clipboard copy command, mouse-capture policy, and a comprehensive mouse/selection test suite.
Startup/empty-state simplification & other tests
internal/tui/startup.go, internal/tui/startup_test.go, internal/tui/width_tiers_test.go
Removes starter suggestion chips and digit-shortcut behavior; empty state simplified to wordmark + tagline; width-tier/composer-divider tests updated and new exact-fit divider test added.
Config writer & resolver changes
internal/config/types.go, internal/config/resolver.go, internal/config/writer.go, internal/config/writer_test.go, internal/config/resolver_test.go, internal/cli/app.go
Adds PreferencesConfig and fields on FileConfig/ResolvedConfig; Resolve/mergeConfig propagate normalized favorite models; adds SetFavoriteModels normalization+persist helper; CLI forwards FavoriteModels into TUI options; tests added for precedence and persistence.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • Gitlawb/zero#138: Related file-mention detection and @ token completion logic.
  • Gitlawb/zero#160: Overlaps composer key-handling and word-edit navigation changes.
  • Gitlawb/zero#126: Related suggestion/picker overlay framework and rendering changes.

Suggested reviewers

  • gnanam1990
  • Vasanthdev2004
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/tui-main-screen-polish

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
internal/tui/view.go (1)

123-146: 💤 Low value

Update outdated comment.

The comment at line 124 mentions "surface name," but the code only adds provider and usage segments. Update the comment to match the actual behavior.

📝 Proposed fix
-// statusLine renders the bottom readout as ` │ `-separated groups: provider
-// on the left, then a flexible gap, then tokens/cost and the surface name.
-// Groups drop with the width tier: narrow keeps provider+tokens only, tiny
-// shows the provider only.
+// statusLine renders the bottom readout as ` │ `-separated groups: provider
+// on the left, then a flexible gap, then tokens/cost.
+// Groups drop with the width tier: narrow keeps provider+tokens only, tiny
+// shows the provider only.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/tui/view.go` around lines 123 - 146, The top comment for the
statusLine function is outdated: update the comment to accurately describe that
the bottom readout renders provider on the left and usage/tokens on the right
(no "surface name"), and clarify the width-tier behavior (tiny shows provider
only, narrow shows provider+tokens/usage). Edit the comment above func
statusLine(width int) to remove "surface name" and adjust the description of
groups and tier behavior to match the implementation using left, rightGroups,
and usageStatusSegment().
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@internal/tui/view.go`:
- Around line 123-146: The top comment for the statusLine function is outdated:
update the comment to accurately describe that the bottom readout renders
provider on the left and usage/tokens on the right (no "surface name"), and
clarify the width-tier behavior (tiny shows provider only, narrow shows
provider+tokens/usage). Edit the comment above func statusLine(width int) to
remove "surface name" and adjust the description of groups and tier behavior to
match the implementation using left, rightGroups, and usageStatusSegment().

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: e83c5750-dbc7-4b5b-a861-81a4f7fecaac

📥 Commits

Reviewing files that changed from the base of the PR and between f32d07b and 99ca933.

📒 Files selected for processing (10)
  • internal/tui/composer.go
  • internal/tui/composer_test.go
  • internal/tui/model.go
  • internal/tui/model_test.go
  • internal/tui/rendering_lime_test.go
  • internal/tui/startup.go
  • internal/tui/startup_test.go
  • internal/tui/theme.go
  • internal/tui/view.go
  • internal/tui/width_tiers_test.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/tui/view.go (1)

122-126: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Render the divider metadata when it exactly fits.

When width == metaWidth+4, the "╰ "+meta+" ╯" variant fits exactly, but the <= check still falls back to a blank rule. That drops the model/mode label one column earlier than necessary.

💡 Proposed fix
-	if width <= metaWidth+4 {
+	if width < metaWidth+4 {
 		return zeroTheme.lineStrong.Render("╰" + strings.Repeat("─", width-2) + "╯")
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/tui/view.go` around lines 122 - 126, The divider logic currently
treats width == metaWidth+4 as too small and renders a blank rule; change the
guard in view.go from "if width <= metaWidth+4" to "if width < metaWidth+4" so
the exact-fit case renders the "╰ "+meta+" ╯" variant; update the branch using
width, metaWidth, meta and zeroTheme.lineStrong.Render accordingly so the meta
label appears when width equals metaWidth+4.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/tui/autocomplete.go`:
- Around line 267-277: The Esc branch removes a trailing @ token by mutating
m.input via removeTrailingAtToken(...) but doesn't update the composer state, so
subsequent word-edit keys (which call applyComposerKey()) operate on stale
composerState; after adjusting m.input (inside the if m.suggestionsAreFiles
block) call resetComposerFromInput() to sync composerState (and ensure cursor
positioning via m.input.CursorEnd() remains correct), leaving the rest of the
cleanup (clearComposer(), suggestions niling, flags resetting) unchanged.

---

Outside diff comments:
In `@internal/tui/view.go`:
- Around line 122-126: The divider logic currently treats width == metaWidth+4
as too small and renders a blank rule; change the guard in view.go from "if
width <= metaWidth+4" to "if width < metaWidth+4" so the exact-fit case renders
the "╰ "+meta+" ╯" variant; update the branch using width, metaWidth, meta and
zeroTheme.lineStrong.Render accordingly so the meta label appears when width
equals metaWidth+4.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 7436017d-5cf3-416d-98d7-db5b487291d1

📥 Commits

Reviewing files that changed from the base of the PR and between 99ca933 and 89bbbb7.

📒 Files selected for processing (7)
  • internal/tui/autocomplete.go
  • internal/tui/autocomplete_test.go
  • internal/tui/model.go
  • internal/tui/selectable_list.go
  • internal/tui/selectable_list_test.go
  • internal/tui/startup.go
  • internal/tui/view.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/tui/startup.go

Comment thread internal/tui/autocomplete.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/tui/autocomplete_test.go (1)

277-286: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

This scrolled-window assertion is the CI failure and it’s too specific.

The second half of this test hard-codes ❯ clear/provider visibility after suggestionPaletteMaxVisible + 1 Down events, but CI is already showing that the renderer now chooses a different valid window on every platform. That makes the PR red even when selection movement itself works.

Assert that the selected suggestion is visible and that the legacy “more” row stays absent, instead of pinning the viewport to one command name.

Suggested fix
 	for range suggestionPaletteMaxVisible + 1 {
 		updated, _ := m.Update(tea.KeyMsg{Type: tea.KeyDown})
 		m = updated.(model)
 	}
 	plain = plainRender(t, m.View())
 	if strings.Contains(plain, "more") {
 		t.Fatalf("scrolled palette should not render a more-count row, got %q", plain)
 	}
-	if !strings.Contains(plain, "│ ❯ clear") || strings.Contains(plain, "│ ❯ provider") || strings.Contains(plain, "│   provider") {
-		t.Fatalf("scrolled palette should move the visible command window, got %q", plain)
-	}
+	selected := strings.TrimPrefix(m.suggestions[m.suggestionIdx].Name, "/")
+	if !strings.Contains(plain, "│ ❯ "+selected) {
+		t.Fatalf("scrolled palette should keep the selected command visible, got %q", plain)
+	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/tui/autocomplete_test.go` around lines 277 - 286, The test currently
hard-codes which items must be visible after suggestionPaletteMaxVisible+1 Down
events; change it to only assert that the legacy "more" row is absent and that
the currently-selected suggestion is visible. After the loop that calls
m.Update(tea.KeyMsg{Type: tea.KeyDown}) suggestionPaletteMaxVisible+1 times,
render with plain := plainRender(t, m.View()) and replace the strict checks for
"│ ❯ clear" / "provider" with two assertions: 1) strings.Contains(plain, "more")
is false (keep the existing "more" check), and 2) the rendered output contains
the selected suggestion text or selection marker by deriving the selection from
the model (use the model's public accessors or fields used in this test—e.g.,
the current suggestion via m.Suggestions()/m.Cursor() or the presence of the
selection marker "❯" in plain) so the test only verifies visibility of the
selected item rather than a specific window layout.

Source: Pipeline failures

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/tui/autocomplete.go`:
- Around line 512-516: The Esc branch that handles file suggestions (when
m.suggestionsAreFiles) currently calls removeTrailingAtToken(m.input.Value())
and moves the cursor to the end, which removes the wrong `@-mention` when the
cursor is inside an earlier path query; change this to use the active path query
range returned by extractPathQuery(value, m.input.Position()) (the same data
recomputeSuggestions() uses): remove the substring covering that pathQuery range
from the input value, set the input value to the new string with that range
removed, and restore the cursor to the start index of that removed range
(instead of CursorEnd()), then call m.resetComposerFromInput() so the composer
reflects the updated cursor/contents. Ensure you reference
recomputeSuggestions(), m.suggestionsAreFiles, extractPathQuery,
removeTrailingAtToken (remove its use here), input.SetValue, input.CursorEnd
(replace with cursor set to removed range start), and resetComposerFromInput in
your changes.

---

Outside diff comments:
In `@internal/tui/autocomplete_test.go`:
- Around line 277-286: The test currently hard-codes which items must be visible
after suggestionPaletteMaxVisible+1 Down events; change it to only assert that
the legacy "more" row is absent and that the currently-selected suggestion is
visible. After the loop that calls m.Update(tea.KeyMsg{Type: tea.KeyDown})
suggestionPaletteMaxVisible+1 times, render with plain := plainRender(t,
m.View()) and replace the strict checks for "│ ❯ clear" / "provider" with two
assertions: 1) strings.Contains(plain, "more") is false (keep the existing
"more" check), and 2) the rendered output contains the selected suggestion text
or selection marker by deriving the selection from the model (use the model's
public accessors or fields used in this test—e.g., the current suggestion via
m.Suggestions()/m.Cursor() or the presence of the selection marker "❯" in plain)
so the test only verifies visibility of the selected item rather than a specific
window layout.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 4b36b850-d190-49d5-bcfa-af127672c68d

📥 Commits

Reviewing files that changed from the base of the PR and between 89bbbb7 and a3f5396.

📒 Files selected for processing (7)
  • internal/tui/autocomplete.go
  • internal/tui/autocomplete_test.go
  • internal/tui/composer.go
  • internal/tui/composer_test.go
  • internal/tui/model.go
  • internal/tui/view.go
  • internal/tui/width_tiers_test.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • internal/tui/width_tiers_test.go
  • internal/tui/view.go
  • internal/tui/model.go

Comment thread internal/tui/autocomplete.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/tui/model.go`:
- Around line 858-859: The footer-anchored palette can overflow on short
terminals; modify the logic around suggestionInFooter (and the block using
suggestionOverlay/footer) to guard against footer height exceeding the terminal:
compute footerLines := len(viewLines(footer)) (or use
scrollableTranscriptView.View() line count) and only set suggestionInFooter =
true when suggestionOverlay != "" AND !(m.transcriptEmpty() && !m.pending) AND
footerLines < m.height (or footerLines <= m.height-1 if you need room for the
palette); otherwise fall back to body anchoring or clamp the footer height
(reduce footerLines and render a shortened footer) so the palette/composer stack
never pushes past m.height. Ensure you update both the suggestionInFooter
assignment and the subsequent branch that renders the footer/palette (lines
~907-912) to use this guarded value.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: e1a042fd-4305-40b5-9656-0284597883c8

📥 Commits

Reviewing files that changed from the base of the PR and between a3f5396 and 34b649e.

📒 Files selected for processing (2)
  • internal/tui/autocomplete_test.go
  • internal/tui/model.go

Comment thread internal/tui/model.go Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
internal/tui/model.go (1)

943-963: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Clamp the footer stack before slicing the alt-screen viewport.

Lines 946-949 still force available = 1 when len(footerLines) >= m.height, and Line 963 then appends the full footer anyway. That makes View() return more than m.height lines on short alt-screen terminals, so the composer/palette stack still spills past the screen instead of fitting inside it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/tui/model.go` around lines 943 - 963, In scrollableTranscriptView,
prevent the footer from overflowing the alt-screen by capping footerLines before
computing the viewport: clamp footerLines to at most m.height-1 entries (e.g.
take the last min(len(footerLines), m.height-1) lines), then set available =
m.height - len(footerLines) (and ensure available >= 1), compute
start/end/maxOffset using that available, and finally append the already-clamped
footerLines; this keeps the total lines returned ≤ m.height and preserves
existing logic in viewLines, overlayViewportLines, and the chatScrollOffset
calculations.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@internal/tui/model.go`:
- Around line 943-963: In scrollableTranscriptView, prevent the footer from
overflowing the alt-screen by capping footerLines before computing the viewport:
clamp footerLines to at most m.height-1 entries (e.g. take the last
min(len(footerLines), m.height-1) lines), then set available = m.height -
len(footerLines) (and ensure available >= 1), compute start/end/maxOffset using
that available, and finally append the already-clamped footerLines; this keeps
the total lines returned ≤ m.height and preserves existing logic in viewLines,
overlayViewportLines, and the chatScrollOffset calculations.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 59fd6206-380d-4402-bac5-2d61873bb64f

📥 Commits

Reviewing files that changed from the base of the PR and between 34b649e and c76eb15.

📒 Files selected for processing (2)
  • internal/tui/autocomplete_test.go
  • internal/tui/model.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
internal/tui/commands_test.go (1)

94-110: ⚡ Quick win

Add direct parser edge-case tests for optional-depth behavior.

You’re testing command-level outcomes, but not the parser contract itself (commandUsageRequiredPlaceholder). Add a small table-driven test for mixed optional/required patterns to harden regressions.

Proposed test addition
+func TestCommandUsageRequiredPlaceholder(t *testing.T) {
+	cases := []struct {
+		usage string
+		want  string
+	}{
+		{usage: "/spec <task>", want: "task"},
+		{usage: "/rewind [latest|<sequence>]", want: ""},
+		{usage: "/x [opt] <required>", want: "required"},
+		{usage: "/x [a [b <c>]] <d>", want: "d"},
+		{usage: "/x", want: ""},
+	}
+	for _, tc := range cases {
+		if got := commandUsageRequiredPlaceholder(tc.usage); got != tc.want {
+			t.Fatalf("commandUsageRequiredPlaceholder(%q) = %q, want %q", tc.usage, got, tc.want)
+		}
+	}
+}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/tui/commands_test.go` around lines 94 - 110, Add a small
table-driven unit test exercising the parser function
commandUsageRequiredPlaceholder (not just commandRequiredInputHint) to cover
mixed optional/required patterns and edge cases: create cases with usages like
"cmd [opt] <req>", "cmd <req> [opt]", nested or multiple optionals like "cmd [a]
[b] <req>", and fully-optional patterns to assert the returned placeholder
matches expected (e.g., "<req>" vs "" or "[opt]"). Put the test alongside
existing tests in the same test file, iterate cases, call
commandUsageRequiredPlaceholder for each pattern, and use t.Fatalf/t.Errorf when
the result differs from the expected value so regressions in optional-depth
parsing are caught.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@internal/tui/commands_test.go`:
- Around line 94-110: Add a small table-driven unit test exercising the parser
function commandUsageRequiredPlaceholder (not just commandRequiredInputHint) to
cover mixed optional/required patterns and edge cases: create cases with usages
like "cmd [opt] <req>", "cmd <req> [opt]", nested or multiple optionals like
"cmd [a] [b] <req>", and fully-optional patterns to assert the returned
placeholder matches expected (e.g., "<req>" vs "" or "[opt]"). Put the test
alongside existing tests in the same test file, iterate cases, call
commandUsageRequiredPlaceholder for each pattern, and use t.Fatalf/t.Errorf when
the result differs from the expected value so regressions in optional-depth
parsing are caught.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 7c2ea047-bed5-48fe-be8c-1680ee243484

📥 Commits

Reviewing files that changed from the base of the PR and between 834a2dc and 0b27a61.

📒 Files selected for processing (7)
  • internal/tui/autocomplete.go
  • internal/tui/autocomplete_test.go
  • internal/tui/commands.go
  • internal/tui/commands_test.go
  • internal/tui/composer.go
  • internal/tui/model.go
  • internal/tui/rendering_lime_test.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • internal/tui/rendering_lime_test.go
  • internal/tui/autocomplete.go
  • internal/tui/autocomplete_test.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
internal/tui/view.go (1)

657-683: 💤 Low value

Width calculation iterates all picker items on every render.

The modelPickerOverlayWidth function loops through all picker.items to determine the widest label. For typical model catalogs this is negligible, but if the item count grows large, consider caching the computed width when items change rather than recalculating per-frame.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/tui/view.go` around lines 657 - 683, modelPickerOverlayWidth
currently iterates picker.items and calls modelPickerItemDetail on every render
which is wasteful for large catalogs; modify commandPicker to cache the computed
widest label (e.g., add fields like cachedOverlayWidth and a change marker such
as itemsVersion or lastItemsHash) and update that cache only when the items
slice or item properties change (update the marker whenever items are mutated or
replaced); then have modelPickerOverlayWidth read the cached width from the
picker when available instead of recomputing, falling back to the existing loop
only if the cache is missing or stale.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@internal/tui/view.go`:
- Around line 657-683: modelPickerOverlayWidth currently iterates picker.items
and calls modelPickerItemDetail on every render which is wasteful for large
catalogs; modify commandPicker to cache the computed widest label (e.g., add
fields like cachedOverlayWidth and a change marker such as itemsVersion or
lastItemsHash) and update that cache only when the items slice or item
properties change (update the marker whenever items are mutated or replaced);
then have modelPickerOverlayWidth read the cached width from the picker when
available instead of recomputing, falling back to the existing loop only if the
cache is missing or stale.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: adc73514-d8e4-46d6-98da-2ee33048adba

📥 Commits

Reviewing files that changed from the base of the PR and between 55d9be7 and e89b22b.

📒 Files selected for processing (16)
  • internal/cli/app.go
  • internal/cli/app_test.go
  • internal/config/resolver.go
  • internal/config/resolver_test.go
  • internal/config/types.go
  • internal/config/writer.go
  • internal/config/writer_test.go
  • internal/tui/model.go
  • internal/tui/onboarding.go
  • internal/tui/options.go
  • internal/tui/picker.go
  • internal/tui/picker_test.go
  • internal/tui/provider_wizard.go
  • internal/tui/provider_wizard_test.go
  • internal/tui/rendering_lime_test.go
  • internal/tui/view.go
💤 Files with no reviewable changes (2)
  • internal/tui/onboarding.go
  • internal/tui/provider_wizard.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • internal/tui/provider_wizard_test.go
  • internal/tui/model.go

@anandh8x
anandh8x marked this pull request as ready for review June 11, 2026 16:10
@anandh8x

Copy link
Copy Markdown
Collaborator Author

@coderabbitai re review

@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown

@anandh8x Sure, kicking off a full re-review of PR #168 now.

✅ Action performed

Full review finished.

@github-actions

github-actions Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Zero automated PR review

Verdict: No blockers found

Blockers

  • None found.

Validation

  • [pass] Diff hygiene: git diff --check
  • [pass] Tests: go test ./...
  • [pass] Build: go run ./cmd/zero-release build
  • [pass] Smoke build: go run ./cmd/zero-release smoke

Scope

Head: 82557df1bb5b
Changed files (36): go.mod, internal/cli/app.go, internal/cli/app_test.go, internal/config/resolver.go, internal/config/resolver_test.go, internal/config/types.go, internal/config/writer.go, internal/config/writer_test.go, internal/tui/autocomplete.go, internal/tui/autocomplete_test.go, internal/tui/commands.go, internal/tui/commands_test.go, and 24 more

This deterministic review checks validation status and basic diff hygiene. A human reviewer still owns product judgment and design quality.

@Vasanthdev2004 Vasanthdev2004 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes for one test reliability issue.

Finding:

  • internal/tui/provider_wizard_test.go TestProviderWizardSupportsLeftAndGuardedRightNavigation assumes the selected provider has no credential already available in the process environment. On a normal developer machine with a provider key set (for example OPENAI_API_KEY/OLLAMA_API_KEY), credentialReadyForRight() returns true, so Right advances to the model step and the test fails:
    right from empty credential step = 2, want credential.

This should be made hermetic. Please clear the selected provider's AuthEnvVars with t.Setenv(env, "") before the empty-credential assertion, or use a helper that clears all catalog auth env vars for this test. Keep TestProviderWizardRightAllowsExistingCredentialEnv as the explicit positive coverage for env-backed credentials.

Validation I ran:

  • go test ./internal/tui ./internal/config ./internal/cli fails in my normal environment because of the test above.
  • With provider API env vars cleared: go test ./internal/tui passes.
  • With provider API env vars cleared: go test ./... passes.
  • git diff --check origin/main...HEAD passes.
  • go run ./cmd/zero-release build passes.
  • go run ./cmd/zero-release smoke passes.

The implementation otherwise looks sound from my pass; this is a bounded fix.

@gnanam1990 gnanam1990 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the current state (post un-draft, +4074/−485). Solid and genuinely well-tested work — flagging a few things for a team decision rather than blocking, since none are correctness bugs.

Scope — this is now 4+ features in one ~4,000-line PR

It grew from "polish empty state/composer" to also include two-step mouse selection, OSC-52 clipboard, provider-wizard/picker mouse hit-testing, and favorite-models persistence. That's hard to review atomically, bisect, or revert. Strong suggestion: split the mouse-selection + clipboard work into its own PR on top of the polish. At minimum it must rebase on #170, which also rewrote renderPermissionRow in rendering.go.

Mouse capture disables native terminal text selection

wantsMouseCapture enables EnableMouseCellMotion across the whole chat surface (chatWantsMouseCapture = !setup.visible). Once mouse is captured, the terminal's own drag-to-copy stops working — users who rely on native selection (or copying into another app) lose it, replaced by the PR's own selection. This is the defining UX tradeoff of the feature and should be a deliberate, documented decision (and worth noting the Shift-drag escape hatch most terminals honor). Several users will read this as "I can't select text in my terminal anymore."

Hit-testing duplicates layout math (maintainability trap)

overlayMouseHit/overlayMouseTop re-derive each overlay's on-screen position by replicating the render path's centering logic, and each select*AtMouse hand-counts row offsets to skip headers/separators (hit.y - 3, rowStart := baseRow + 2, +1 for error/status lines, group-header increments). The renderer computes those positions one way; the hit-tester re-counts them another, linked only by magic numbers. Any layout tweak (and this PR tweaks layouts) will silently map clicks to the wrong row. mouse_test.go helps, but these offsets should derive from the same constants the renderer uses rather than being re-counted by hand.

OSC-52 write bypasses the Bubble Tea render loop

os.Stdout.WriteString(ansi.SetSystemClipboard(text)) writes raw escapes to stdout directly from an update handler, outside tea's managed output — in alt-screen that can race/corrupt the frame; the idiomatic path is a tea.Cmd/tea.Printf. Also OSC-52 is a silent no-op on terminals that don't support it (and has a payload cap) — no fallback or user feedback when a copy doesn't land.

Still open from the earlier pass

  • fileIndexTTL = 30 * time.Second is unchanged — a file created mid-session is invisible to @-completion for up to 30s. Shorten it or invalidate the cwd entry on write-tool activity.
  • Worth re-confirming the dismissSuggestions mid-prompt token removal and ANSI-overlay wide-rune (CJK/emoji) correctness — the overlay code now also backs mouse hit-testing, so wide-rune alignment matters more.

Verdict

Strong, well-tested feature work; nothing here is a data-correctness bug. But it's large and tightly coupled — I'd want a rebase on #170, an explicit team decision on the mouse-capture/native-selection tradeoff, and ideally the mouse/clipboard work split out before merge. Happy to dig into any specific spot.

@gnanam1990 gnanam1990 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes (see my detailed comment above for the full list). Nothing here is a correctness bug — the work is strong and well-tested — but two items should gate the merge:

  1. Rebase on #170 first. #170 rewrote renderPermissionRow in rendering.go; this PR touches the same file and is later in the merge order, so it needs to rebase or it'll conflict/break.
  2. Decide the mouse-capture tradeoff explicitly. Enabling mouse capture across the chat removes the terminal's native drag-to-copy; that's a product decision (with a documented Shift-drag note), not a silent default.

Everything else in the comment (splitting the mouse/clipboard work out, the hit-test layout duplication, the OSC-52 stdout write, the 30s file-index TTL) is non-blocking polish. Happy to re-approve once the rebase lands and the capture tradeoff is signed off.

@anandh8x
anandh8x requested a review from Vasanthdev2004 June 11, 2026 16:25
@gnanam1990

Copy link
Copy Markdown
Collaborator

Re-review after #170 merged: correcting my earlier review.

So the only thing I'd still want before approving is an explicit call on the mouse-capture/native-selection tradeoff (a line in the PR description or a code comment documenting it, ideally with the Shift-drag note). The rest — splitting mouse/clipboard out, the hit-test layout-offset duplication, the OSC-52 stdout write, the 30s file-index TTL — remains non-blocking polish. Clear that one tradeoff and I'm happy to approve.

@Vasanthdev2004 Vasanthdev2004 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved on rereview. The provider-wizard test is now hermetic in 6e088c3; it passes in my normal environment with API key env vars present.

Validation:

  • current PR head: go test ./internal/tui ./internal/config ./internal/cli
  • clean merge onto latest origin/main: git diff --check
  • clean merge onto latest origin/main: go test ./internal/tui ./internal/config ./internal/cli
  • clean merge onto latest origin/main: go test ./...
  • clean merge onto latest origin/main: go run ./cmd/zero-release build
  • clean merge onto latest origin/main: go run ./cmd/zero-release smoke

GitHub checks are also green. My previous blocker is resolved; no blockers from my side.

@anandh8x

Copy link
Copy Markdown
Collaborator Author

@gnanam1990 addressed the remaining mouse-capture point in the PR description.

The explicit product call is now documented there: app-managed mouse selection/copy intentionally captures mouse input while the TUI is active, and native terminal selection remains available through the terminal escape path such as Shift-drag where supported.

Also merged latest origin/main; all checks are green now.

@anandh8x
anandh8x requested a review from gnanam1990 June 11, 2026 16:47
@anandh8x

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

The requested changes have been addressed in the latest branch updates:

  • dismissSuggestions now removes the cursor-local @ file query instead of the trailing token, with coverage for compare @old with @new.
  • Provider model discovery now carries a request token and ignores stale same-provider responses, with regression coverage.
  • The mouse Type compatibility path is documented, redundant deprecated Type usage was removed from tests where safe, and the width-tier test now mirrors production styling.
  • The small-terminal footer overflow case is clamped and covered.

Please re-review when ready.

@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown

@anandh8x, kicking off the re-review now. Thanks for the detailed summary of what changed — the dismissSuggestions cursor-local fix, discovery request token, mouse Type cleanup, and footer overflow clamp all look like solid targeted fixes. Review coming up.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@anandh8x
anandh8x merged commit fcfdb87 into main Jun 11, 2026
6 checks passed
@Vasanthdev2004
Vasanthdev2004 deleted the feat/tui-main-screen-polish branch June 28, 2026 08:27
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.

3 participants