Skip to content

zeroline TUI: fix slash palette, token readout, @ files, ! bash#138

Merged
gnanam1990 merged 6 commits into
mainfrom
zeroline-tui-fixes
Jun 8, 2026
Merged

zeroline TUI: fix slash palette, token readout, @ files, ! bash#138
gnanam1990 merged 6 commits into
mainfrom
zeroline-tui-fixes

Conversation

@gnanam1990

@gnanam1990 gnanam1990 commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator

zeroline TUI: fix slash palette, token readout, @ files, ! bash

The zeroline home footer advertised six affordances but the code didn't keep four of them. Found via a multi-agent audit of internal/tui + internal/zeroline; each fix is verified against the code path.

Fixes

  • / command paletteautocomplete.go explicitly suppressed a bare / (you had to type /h first), yet the footer says "/ commands". Now a bare / lists the full palette (capped at 8).
  • Token / cost readout — the zeroline header was built without Cost/TotalTokens, so the bars always read $ 0.00 with no token total. zerolineHeader() now threads usageTracker.Summary() (cost + cumulative tokens, with the unpriced fallback); the bottom bar gains a tok <N> segment.
  • @ files@ now drives a workspace file picker in the existing suggestion overlay (bounded walk; skips .git/node_modules/vendor/dist/hidden). Selecting inserts the @path, preserving any preceding prompt text. Previously @… was silently sent to the agent as chat.
  • ! bash!cmd now runs as a shell escape (async, 30s timeout, in the workspace), output shown in the transcript. Previously !cmd was sent to the agent as chat.
  • 1-5 theme — left as-is: it works when the input is empty (the home screen), and gating on empty input keeps digits typable in prompts.

Testing

bare-/ palette, @-token detection/replacement, file listing+filter+skip-dirs, !cmd parsing, header usage; all existing autocomplete tests still pass. build / vet / go test ./... / -race / Windows green. No new deps.

Note: the @/!// interactions are unit-tested at the logic layer; a quick manual zero zeroline pass (type /, @, !ls) is worth doing before merge since Bubble Tea key-routing isn't exercised by unit tests.

Summary by CodeRabbit

  • New Features

    • Workspace file-path autocomplete via trailing @ (mid-prompt, case-insensitive substring matches, skips VCS/deps/hidden dirs, bounded results)
    • Shell-escape via ! to run a shell command and append formatted output/notes (exec only in unsafe permission mode)
    • CLI flag to launch interactive TUI in unsafe mode (--skip-permissions-unsafe)
  • Bug Fixes

    • Improved autocomplete mode switching and replacement (preserves surrounding text, appends space)
    • Header shows cumulative token totals with compact formatting and sensible fallback
  • Tests

    • Regression and unit tests for parsing, suggestions, permission gating, and token-formatting

The zeroline home footer advertised affordances the code didn't keep. Fixes:

- '/' palette: autocomplete.go suppressed a bare '/' ('/' alone showed nothing). Now a bare '/' lists the full command palette (capped at 8). The footer's '/ commands' works.

- Token readout: the zeroline header was built without Cost/TotalTokens, so the bars always showed $0.00 and no token total. zerolineHeader() now threads m.usageTracker.Summary() (TotalCost + TotalTokens, with the unpriced-token fallback); the bottom bar shows a cumulative 'tok <N>' segment (zeroline.Header gains TotalTokens; humanTokens formats it).

- '@ files': '@' input now drives a workspace file picker in the existing overlay (trailingAtToken/fileSuggestions/replaceTrailingAtToken; bounded, skips .git/node_modules/vendor/dist/hidden). Selecting inserts the @path, preserving preceding prompt text. Previously '@…' was sent to the agent as a chat prompt.

- '! bash': '!cmd' now runs as a shell escape (commandBash) — executed async with a 30s timeout in the workspace, output shown in the transcript. Previously '!cmd' was sent to the agent as chat.

- '1-5 theme': left as-is — it works when the input is empty (the home screen's state); gating on empty input keeps digits typable in prompts.

Tests: bare-/ palette, @-token detection/replacement, file listing+filter+skip-dirs, !cmd parsing, header usage. build/vet/-race/full-suite + GOOS=windows green; no new deps.
@github-actions

github-actions Bot commented Jun 8, 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: a0c0239531d7
Changed files (12): internal/cli/app.go, internal/cli/app_test.go, internal/cli/zeroline.go, internal/tui/autocomplete.go, internal/tui/autocomplete_test.go, internal/tui/command_bash.go, internal/tui/commands.go, internal/tui/model.go, internal/tui/tui_fixes_test.go, internal/tui/zeroline_view.go, internal/zeroline/render.go, internal/zeroline/tokens_test.go

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

@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Adds !<cmd> shell-escape support (parsed and optionally executed via a platform shell with timeout and transcript plumbing), trailing-@ workspace file-path autocomplete with bounded walking and directory exclusions, CLI flag wiring for unsafe permission mode, and cumulative token totals surfaced in the zeroline header/status bar with tests.

Changes

TUI Shell Escape, File Completion, and Token Metrics

Layer / File(s) Summary
Shell escape command execution
internal/tui/command_bash.go, internal/tui/commands.go, internal/tui/model.go, internal/tui/tui_fixes_test.go
Adds runBashEscape (executes platform shell with 30s timeout and returns trimmed combined output annotated for timeouts/errors), recognizes ! as commandBash in the parser, wires bashResultMsg into the model transcript, and includes permission-gated execution and parsing tests.
File path completion via @token
internal/tui/autocomplete.go, internal/tui/model.go, internal/tui/autocomplete_test.go, internal/tui/tui_fixes_test.go
recomputeSuggestions detects a trailing whitespace-delimited @token and enables file-suggestion mode; fileSuggestionsBounded walks cwd with visit/suggestion bounds, skips .git, node_modules, vendor, dist, and dot-prefixed dirs, matches by case-insensitive substring and emits @<relpath> entries; completeSuggestion replaces only the trailing @token. Tests cover detection, replacement, filtering, and walk-budget behavior.
Cumulative token count display
internal/zeroline/render.go, internal/tui/zeroline_view.go, internal/zeroline/tokens_test.go
Adds Header.TotalTokens, humanTokens formatter, and zerolineHeader() to populate totals (with fallback to unpriced tokens); status bar renders cumulative "tok" alongside "tok/s"; includes unit tests for humanTokens and header total fallback.
CLI flag -> TUI permission propagation
internal/cli/app.go, internal/cli/zeroline.go, internal/cli/app_test.go
Introduces --skip-permissions-unsafe routing to launch the interactive TUI in PermissionModeUnsafe, threads a permissionMode argument through TUI launch helpers, and adds tests asserting the CLI flag sets PermissionModeUnsafe (including zeroline skin path).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • Gitlawb/zero#127: Touches internal/tui/autocomplete.go and relates to slash-command/autocomplete behavior extended here.
  • Gitlawb/zero#126: Also modifies TUI autocomplete implementation; changes are related at the autocomplete code level.
  • Gitlawb/zero#114: Overlaps with internal/zeroline/render.go changes around header/token rendering.

Suggested reviewers

  • Vasanthdev2004
  • anandh8x
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: slash command palette fixes, token readout improvements, @-file completion, and !bash shell escape support.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 zeroline-tui-fixes

Comment @coderabbitai help to get the list of available commands and usage tips.

@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: 2

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

185-187: ⚡ Quick win

Clarify intentional error swallowing.

The linter flags returning nil when err != nil. For a file picker, skipping inaccessible entries is reasonable, but the intent isn't obvious to future readers. A brief comment cements that this is deliberate.

♻️ Suggested clarification
 	_ = filepath.WalkDir(cwd, func(path string, d fs.DirEntry, err error) error {
 		if err != nil {
-			return nil
+			return nil // skip inaccessible entries; continue walking
 		}
🤖 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.go` around lines 185 - 187, The early return that
swallows errors at the "if err != nil { return nil }" site in
internal/tui/autocomplete.go should be made explicit: add a short comment above
that conditional explaining that inaccessible files are intentionally skipped by
the file picker (e.g., "intentionally ignore inaccessible entries; skip rather
than surface error"), and optionally convert this to a debug log call if you
want traceability; leave the code path and behavior in the surrounding function
(the file-picker/autocomplete routine) unchanged.
🤖 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 860-869: The commandBash branch currently invokes
runBashEscape(m.cwd, cmdText) without checking permissions; add a permission
gate using m.permissionMode (or equivalent) so that commandBash is only executed
when permissionMode==Auto or when Ask is accepted by the user, otherwise
reject/queue the command and append a system transcript message; update the
commandBash case in internal/tui/model.go to consult m.permissionMode and call
runBashEscape only after explicit allow, and add unit tests covering Auto
(runs), Ask (prompts and runs only on accept), and Unsafe (never runs)
behaviors, referencing the commandBash branch and runBashEscape for placement
and test targets.

In `@internal/zeroline/render.go`:
- Around line 1120-1129: Add a table-driven unit test for the humanTokens
function (e.g., TestHumanTokens) that verifies negative clamping and
rounding/boundary behavior: include cases for 0 -> "0", -1 -> "0", 999 -> "999",
1000 -> "1k", 1234 -> "1.2k", 999999 -> "1000k", and at least one very large
value (e.g., 1500000 or math.MaxInt32/MaxInt64) to ensure no panic and correct
formatting; iterate cases, call humanTokens(n), and assert equality with the
expected string for each case.

---

Nitpick comments:
In `@internal/tui/autocomplete.go`:
- Around line 185-187: The early return that swallows errors at the "if err !=
nil { return nil }" site in internal/tui/autocomplete.go should be made
explicit: add a short comment above that conditional explaining that
inaccessible files are intentionally skipped by the file picker (e.g.,
"intentionally ignore inaccessible entries; skip rather than surface error"),
and optionally convert this to a debug log call if you want traceability; leave
the code path and behavior in the surrounding function (the
file-picker/autocomplete routine) unchanged.
🪄 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: 3e52750c-d51b-4d52-8d85-0e81bf4a371b

📥 Commits

Reviewing files that changed from the base of the PR and between df56b0d and 78e0a54.

📒 Files selected for processing (7)
  • internal/tui/autocomplete.go
  • internal/tui/command_bash.go
  • internal/tui/commands.go
  • internal/tui/model.go
  • internal/tui/tui_fixes_test.go
  • internal/tui/zeroline_view.go
  • internal/zeroline/render.go

Comment thread internal/tui/model.go
Comment thread internal/zeroline/render.go Outdated
fileSuggestions used filepath.Rel, which returns backslash paths on Windows, so the inserted @path token and the test assertions diverged (TestFileSuggestionsListsAndFiltersWorkspaceFiles failed on the Windows smoke). Normalize with filepath.ToSlash so @paths are forward-slash on every platform.

@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.

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.go (1)

182-199: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Bounded walk is not actually bounded by traversed nodes.

visited increments only for files, so a repo with many directories can still be fully traversed per keystroke, causing UI stalls. Count every visited entry (or at least directories too), and stop at >= maxVisited.

Suggested fix
-	visited := 0
+	visited := 0
 	const maxVisited = 4000
 	_ = filepath.WalkDir(cwd, func(path string, d fs.DirEntry, err error) error {
 		if err != nil {
 			return nil
 		}
-		if visited > maxVisited || len(out) >= maxCommandSuggestions {
+		visited++
+		if visited >= maxVisited || len(out) >= maxCommandSuggestions {
 			return fs.SkipAll
 		}
 		if d.IsDir() {
 			name := d.Name()
 			if path != cwd && (name == ".git" || name == "node_modules" || name == "vendor" || name == "dist" || strings.HasPrefix(name, ".")) {
 				return fs.SkipDir
 			}
 			return nil
 		}
-		visited++
 		rel, relErr := filepath.Rel(cwd, path)
🤖 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.go` around lines 182 - 199, The WalkDir callback in
internal/tui/autocomplete.go only increments visited for files, so directories
aren’t counted and the traversal can exceed maxVisited; update the callback used
by filepath.WalkDir to increment visited for every DirEntry visit (i.e., move or
add visited++ at the top of the closure) and change the stopping check to use >=
maxVisited (and still check len(out) >= maxCommandSuggestions) so the walk is
actually bounded; adjust logic around d.IsDir() to return fs.SkipDir after
counting the entry when appropriate.
🤖 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.

Outside diff comments:
In `@internal/tui/autocomplete.go`:
- Around line 182-199: The WalkDir callback in internal/tui/autocomplete.go only
increments visited for files, so directories aren’t counted and the traversal
can exceed maxVisited; update the callback used by filepath.WalkDir to increment
visited for every DirEntry visit (i.e., move or add visited++ at the top of the
closure) and change the stopping check to use >= maxVisited (and still check
len(out) >= maxCommandSuggestions) so the walk is actually bounded; adjust logic
around d.IsDir() to return fs.SkipDir after counting the entry when appropriate.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 419f061a-3586-467e-8832-a102c65a72aa

📥 Commits

Reviewing files that changed from the base of the PR and between 78e0a54 and 974d4ac.

📒 Files selected for processing (1)
  • internal/tui/autocomplete.go

CodeRabbit major: the !cmd shell escape ran bash -c directly with no permission check, bypassing the sandbox. It now runs ONLY in unsafe permission mode; in auto/ask it is not executed and the user is told to relaunch with --skip-permissions-unsafe. Adds TestBashEscapeGatedByPermissionMode (auto/ask gated, unsafe runs).

CodeRabbit minor: adds TestHumanTokens covering negative clamp, 0, sub-1k, 1k trim, fractional, and the 999999->1000k rounding edge.
@gnanam1990

Copy link
Copy Markdown
Collaborator Author

Both fixed:

  1. !cmd safety gate (major): the shell escape now runs only in unsafe permission mode. In auto/ask it is not executed — the user gets a message to relaunch with --skip-permissions-unsafe. (shift+tab only reaches auto↔ask by design, so unsafe stays an explicit opt-in.) Added TestBashEscapeGatedByPermissionMode covering auto/ask (gated) vs unsafe (runs).
  2. humanTokens tests (minor): added TestHumanTokens for negative clamp, 0, sub-1k, 1k trim, fractional, and the 999999→1000k rounding edge.

-race + full suite + Windows green. @coderabbitai @Vasanthdev2004 review

@gnanam1990
gnanam1990 requested a review from Vasanthdev2004 June 8, 2026 04:24
@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Read key files from the PR branch
echo "=== command_bash.go ==="
cat internal/tui/command_bash.go

echo ""
echo "=== commands.go ==="
cat internal/tui/commands.go

echo ""
echo "=== model.go (relevant sections) ==="
cat internal/tui/model.go

echo ""
echo "=== zeroline/render.go ==="
cat internal/zeroline/render.go

echo ""
echo "=== tui_fixes_test.go ==="
cat internal/tui/tui_fixes_test.go

echo ""
echo "=== Any new test files for humanTokens or permission mode ==="
fd -e go -e _test.go . internal/ | xargs grep -l "TestBashEscapeGated\|TestHumanTokens" 2>/dev/null

echo ""
echo "=== Permission mode type/constants ==="
rg -n "PermissionMode\|permissionMode\|SkipPermissions\|unsafe" --type go -C2

Length of output: 94893


🏁 Script executed:

#!/bin/bash
echo "=== zeroline/tokens_test.go ==="
cat internal/zeroline/tokens_test.go

echo ""
echo "=== agent PermissionMode constants ==="
rg -n "PermissionMode" --type go -C2 internal/agent/

echo ""
echo "=== typeRunes helper (used in test) ==="
rg -n "func typeRunes" --type go -C5

echo ""
echo "=== contains helper (used in test) ==="
rg -n "func contains" internal/tui/ --type go -C3

echo ""
echo "=== autocomplete.go (full, for trailingAtToken / replaceTrailingAtToken / fileSuggestions) ==="
cat internal/tui/autocomplete.go

Length of output: 24106



@gnanam1990 both fixes check out cleanly.

Safety gate (!cmd) ✅

  • Gate logic in handleSubmit is exactly right: m.permissionMode != agent.PermissionModeUnsafe → nil cmd + message containing "disabled" and "--skip-permissions-unsafe" (so both substrings the test asserts on are present for auto/ask modes).
  • PermissionModeUnsafe = "unsafe" confirmed in internal/agent/types.go. The unsafe path correctly returns a non-nil runBashEscape(...) cmd.
  • TestBashEscapeGatedByPermissionMode covers all three arms and the test correctly doesn't execute the command in the unsafe case (just asserts non-nil cmd), which makes the test portable.

humanTokens tests ✅

  • TestHumanTokens in internal/zeroline/tokens_test.go covers all the edges you called out. Spot-checked the 999999 case against the implementation: fmt.Sprintf("%.1fk", 999.999)"1000.0k"strings.Replace(..., ".0k", "k", 1)"1000k" — matches the test expectation and the documented rounding behavior.

One minor runtime note (not a blocker): runBashEscape shells out via bash -c, so in unsafe mode on a stock Windows machine it'll fail at runtime (bash not in PATH). Build/vet/go test will still pass because the gate test never actually executes the command. Worth a comment in the function or the gate message for users who hit this, but it's a pre-existing platform assumption in the codebase, not introduced here.

Everything else (@-file picker, bare / palette, header token readout, all existing autocomplete tests) looks solid from the earlier review. LGTM.

[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.

Verdict: REQUEST CHANGES

Finding:

  • internal/tui/autocomplete.go:184-198: fileSuggestions still does not actually bound the directory walk. visited is checked before it is incremented, and it only increments after directory handling, so directory entries are not counted. In a repo with a large nested directory tree, @ file suggestions can still traverse far more than maxVisited entries on each keystroke and stall the TUI.

Required fix:

  • Increment visited for every WalkDir entry after the error check.
  • Stop on visited >= maxVisited or len(out) >= maxCommandSuggestions.
  • Keep the existing skip-dir behavior for .git, node_modules, vendor, dist, and hidden directories.

CI smoke is green, but this runtime responsiveness issue should be fixed before merge.

@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

🧹 Nitpick comments (1)
internal/zeroline/tokens_test.go (1)

14-16: 💤 Low value

Consider adding rounding boundary tests.

The existing cases (1234→"1.2k", 1500→"1.5k") don't reveal the rounding strategy at boundaries. Adding tests for values like 1245, 1250, 1255 would explicitly verify whether humanTokens uses truncation, round-half-up, or round-half-to-even.

For example:

{1245, "1.2k"}, // or "1.3k" depending on strategy
{1250, "1.3k"}, // round-half-up, or "1.2k" if truncation
{1255, "1.3k"},

Not critical since the function presumably works, but documenting the strategy helps maintainability.

🤖 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/zeroline/tokens_test.go` around lines 14 - 16, Add explicit
rounding-boundary test cases to tokens_test.go to document humanTokens' rounding
strategy: add inputs like 1245, 1250, 1255 (and possibly 1249/1251) with
expected string outputs that match the current implementation behavior; inspect
the humanTokens function to determine whether it uses truncation, round-half-up,
or bankers rounding and set the expected values accordingly (e.g., if
humanTokens currently rounds half-up then {1245,"1.2k"}, {1250,"1.3k"},
{1255,"1.3k"}), and include these cases alongside the existing {1234,"1.2k"} and
{1500,"1.5k"} entries so the test suite documents the chosen rounding rule.
🤖 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/zeroline/tokens_test.go`:
- Around line 5-23: Extend the TestHumanTokens table-driven cases to include
values >= 1_000_000 to assert the function humanTokens remains in "k" notation
and trims ".0k"; e.g., add cases for 1_000_000 expecting "1000k" and 5_000_000
expecting "5000k" (optionally include a non-whole thousand like 1_000_100
expecting "1000.1k") so the tests cover the M-scale boundaries and the ".0k"
stripping behavior.

---

Nitpick comments:
In `@internal/zeroline/tokens_test.go`:
- Around line 14-16: Add explicit rounding-boundary test cases to tokens_test.go
to document humanTokens' rounding strategy: add inputs like 1245, 1250, 1255
(and possibly 1249/1251) with expected string outputs that match the current
implementation behavior; inspect the humanTokens function to determine whether
it uses truncation, round-half-up, or bankers rounding and set the expected
values accordingly (e.g., if humanTokens currently rounds half-up then
{1245,"1.2k"}, {1250,"1.3k"}, {1255,"1.3k"}), and include these cases alongside
the existing {1234,"1.2k"} and {1500,"1.5k"} entries so the test suite documents
the chosen rounding rule.
🪄 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: 591cc24e-1d87-498e-95e8-3c144a6b12f0

📥 Commits

Reviewing files that changed from the base of the PR and between 974d4ac and fee1d11.

📒 Files selected for processing (3)
  • internal/tui/model.go
  • internal/tui/tui_fixes_test.go
  • internal/zeroline/tokens_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/tui/tui_fixes_test.go

Comment thread internal/zeroline/tokens_test.go
Review fixes for #138:
- fileSuggestions now counts every WalkDir entry (directories included), not
  just files, and stops at the budget; a directory-heavy tree could otherwise
  be traversed in full each keystroke and stall the TUI. Extracted
  fileSuggestionsBounded so the per-keystroke budget is unit-tested.
- humanTokens switches to "M" notation past 1,000,000 instead of rendering
  "1000k"+ on large-context models; tests extended to the M boundaries.
@gnanam1990

Copy link
Copy Markdown
Collaborator Author

@Vasanthdev2004 addressed in a263d03. fileSuggestions now increments visited for every WalkDir entry right after the error check (directories included), and bails on visited >= maxVisited || len(out) >= maxCommandSuggestions. The skip-dir behavior for .git/node_modules/vendor/dist/hidden dirs is unchanged. Extracted fileSuggestionsBounded(cwd, partial, maxVisited) so the bound is unit-tested: TestFileSuggestionsBoundCountsDirectories proves a match behind many directories is unreachable under a tiny budget (and reachable with the real budget), which is exactly the directory-heavy case that previously walked unbounded.

@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/autocomplete_test.go (1)

231-258: ⚡ Quick win

Add assertions for vendor, dist, and generic hidden-directory skips.

This test currently proves .git and node_modules, but the file-picker contract also excludes vendor, dist, and dot-prefixed directories. Adding those assertions would better lock in expected behavior.

🧪 Suggested test extension
 func TestFileSuggestionsMatchesAndSkipsVCSDirs(t *testing.T) {
@@
 	mustWrite("cmd/server/main.go")
 	mustWrite(".git/config")             // hidden VCS dir: must be skipped
 	mustWrite("node_modules/dep/index.js") // dependency dir: must be skipped
+	mustWrite("vendor/mod/file.go")      // dependency dir: must be skipped
+	mustWrite("dist/app.js")             // build output dir: must be skipped
+	mustWrite(".cache/state.json")       // hidden dir: must be skipped
@@
 	all := suggestionTokens(fileSuggestions(root, ""))
 	for _, name := range all {
-		if strings.Contains(name, ".git/") || strings.Contains(name, "node_modules/") {
+		if strings.Contains(name, ".git/") ||
+			strings.Contains(name, "node_modules/") ||
+			strings.Contains(name, "vendor/") ||
+			strings.Contains(name, "dist/") ||
+			strings.Contains(name, "@.") || strings.Contains(name, "/.") {
 			t.Fatalf("walk must skip VCS/dependency dirs, got %q", name)
 		}
 	}
 }
🤖 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 231 - 258,
TestFileSuggestionsMatchesAndSkipsVCSDirs currently only asserts .git and
node_modules are skipped; extend it to also create entries under vendor/, dist/,
and another dot-prefixed directory (e.g. .cache/) using the existing mustWrite
helper, then call fileSuggestions(root, "") -> suggestionTokens and assert none
of the returned names contain "vendor/", "dist/", or any ".<name>/" dot-prefixed
directory (or specifically ".cache/"); update failure messages to mention which
extra directory was unexpectedly present. Use the same helper functions
referenced in the test (mustWrite, fileSuggestions, suggestionTokens, contains)
so the new assertions align with the current test structure.
🤖 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/autocomplete_test.go`:
- Around line 231-258: TestFileSuggestionsMatchesAndSkipsVCSDirs currently only
asserts .git and node_modules are skipped; extend it to also create entries
under vendor/, dist/, and another dot-prefixed directory (e.g. .cache/) using
the existing mustWrite helper, then call fileSuggestions(root, "") ->
suggestionTokens and assert none of the returned names contain "vendor/",
"dist/", or any ".<name>/" dot-prefixed directory (or specifically ".cache/");
update failure messages to mention which extra directory was unexpectedly
present. Use the same helper functions referenced in the test (mustWrite,
fileSuggestions, suggestionTokens, contains) so the new assertions align with
the current test structure.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 17adc687-3b25-48dc-9b0a-0f0fbc242f5d

📥 Commits

Reviewing files that changed from the base of the PR and between fee1d11 and a263d03.

📒 Files selected for processing (4)
  • internal/tui/autocomplete.go
  • internal/tui/autocomplete_test.go
  • internal/zeroline/render.go
  • internal/zeroline/tokens_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/zeroline/tokens_test.go

@gnanam1990
gnanam1990 requested a review from Vasanthdev2004 June 8, 2026 06:08
The "!cmd" shell escape is gated behind unsafe permission mode, but the
interactive TUI hardcoded Ask and shift+tab only cycles auto<->ask, so unsafe
was unreachable and the escape was dead code. Worse, the gate's own remediation
message ("Relaunch with --skip-permissions-unsafe") pointed at a flag the
interactive launch did not parse: "zero --skip-permissions-unsafe" fell through
to the unknown-command path.

Honor --skip-permissions-unsafe at interactive launch (both "zero" and
"zero zeroline"), mirroring exec, so unsafe mode -- and the ! escape -- is
reachable via an explicit opt-in. Thread the resolved mode through
runInteractiveTUI(WithSkin); the default stays Ask. Document the flag in
"zero --help" and add launch tests for both entrypoints.
@gnanam1990

Copy link
Copy Markdown
Collaborator Author

Pushed c202232 — fixes a real reachability gap the gate exposed.

Problem: gating !cmd behind unsafe mode made the shell escape unreachable in the interactive TUI:

  • the interactive launch hardcoded PermissionModeAsk, and shift+tab deliberately cycles only auto↔ask (unsafe is never a casual keypress), so m.permissionMode could never be unsafe;
  • worse, the gate's own message says "Relaunch with --skip-permissions-unsafe", but zero --skip-permissions-unsafe fell through to the unknown-command path — the interactive launch never parsed that flag.

So the advertised "! bash" footer printed a remediation pointing at a flag that didn't work.

Fix: honor --skip-permissions-unsafe at interactive launch (both zero and zero zeroline), mirroring exec. Unsafe is now reachable via an explicit launch opt-in (still not via keypress), so !cmd works exactly when the user has opted into unsafe; default stays ask. Threaded the resolved mode through runInteractiveTUI(WithSkin), documented the flag in zero --help, and added launch tests for both entrypoints.

go build/vet/go test ./.../-race (cli+tui+agent)/GOOS=windows all green.

@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/cli/app_test.go (1)

271-274: 💤 Low value

Missing assertion for AgentOptions.PermissionMode in zeroline test.

The companion test TestRunSkipPermissionsUnsafeLaunchesTUIInUnsafeMode (lines 233-238) asserts both PermissionMode and AgentOptions.PermissionMode. This test only checks the former. Adding the assertion keeps test coverage consistent and guards against future regressions where the two could diverge.

🧪 Proposed fix
 	if launchedOptions.PermissionMode != agent.PermissionModeUnsafe {
 		t.Fatalf("PermissionMode = %q, want %q", launchedOptions.PermissionMode, agent.PermissionModeUnsafe)
 	}
+	if launchedOptions.AgentOptions.PermissionMode != agent.PermissionModeUnsafe {
+		t.Fatalf("AgentOptions.PermissionMode = %q, want %q", launchedOptions.AgentOptions.PermissionMode, agent.PermissionModeUnsafe)
+	}
 }
🤖 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/cli/app_test.go` around lines 271 - 274, Add the missing assertion
verifying the nested AgentOptions.PermissionMode in the zeroline test: after the
existing check of launchedOptions.PermissionMode, also assert that
launchedOptions.AgentOptions.PermissionMode == agent.PermissionModeUnsafe
(mirror what TestRunSkipPermissionsUnsafeLaunchesTUIInUnsafeMode does) so both
the top-level PermissionMode and the nested AgentOptions.PermissionMode are
checked.
🤖 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/cli/app_test.go`:
- Around line 271-274: Add the missing assertion verifying the nested
AgentOptions.PermissionMode in the zeroline test: after the existing check of
launchedOptions.PermissionMode, also assert that
launchedOptions.AgentOptions.PermissionMode == agent.PermissionModeUnsafe
(mirror what TestRunSkipPermissionsUnsafeLaunchesTUIInUnsafeMode does) so both
the top-level PermissionMode and the nested AgentOptions.PermissionMode are
checked.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 24adeb9f-cdc0-49eb-bc83-e49067be2aaf

📥 Commits

Reviewing files that changed from the base of the PR and between a263d03 and c202232.

📒 Files selected for processing (3)
  • internal/cli/app.go
  • internal/cli/app_test.go
  • internal/cli/zeroline.go

…bash

Now that --skip-permissions-unsafe makes the "!" escape reachable on every
platform, hardcoding "bash -c" left it broken on stock Windows (no bash on
PATH) and anywhere bash is not at a predictable path -- the advertised
"! bash" footer would just print an exec error.

Resolve the shell the same way the agent bash tool does: cmd.exe /d /s /c on
Windows, /bin/sh -c elsewhere. Add a platform test asserting the command is
wrapped correctly.

@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.

Caution

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

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

38-40: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Normalize CRLF before rendering shell output.

On Windows, cmd.exe emits \r\n; trimming only \n leaves stray \r characters in the transcript (echo hi becomes hi\r). That makes the new platform-shell path render oddly even though execution succeeds.

Proposed fix
-		out, err := cmd.CombinedOutput()
-		text := strings.TrimRight(string(out), "\n")
+		out, err := cmd.CombinedOutput()
+		text := strings.ReplaceAll(string(out), "\r\n", "\n")
+		text = strings.TrimRight(text, "\n")
🤖 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/command_bash.go` around lines 38 - 40, The output normalization
currently does strings.TrimRight(string(out), "\n") which leaves stray '\r' on
Windows; update the handling after cmd.CombinedOutput() so you first normalize
CRLF to LF (e.g., replace "\r\n" with "\n") and then trim trailing newlines
before rendering; locate the code around the CombinedOutput call using the out
variable in internal/tui/command_bash.go (the cmd.CombinedOutput() call and the
text := strings.TrimRight(...) line) and replace the trim-only approach with a
CRLF-normalize-then-trim approach so shell output like "hi\r\n" becomes "hi\n"
(no stray '\r') before rendering.
🧹 Nitpick comments (1)
internal/tui/tui_fixes_test.go (1)

32-33: ⚡ Quick win

Assert the actual opt-in flag in the gated-path message.

This still passes if the system message stops telling users how to enable !cmd. Since the recovery path is part of the UX contract here, assert --skip-permissions-unsafe explicitly.

Suggested test tightening
-		if msg := lastSystemText(updated.(model)); !strings.Contains(msg, "disabled") || !strings.Contains(msg, "unsafe") {
-			t.Fatalf("%s mode: expected a gate message naming unsafe, got %q", mode, msg)
+		if msg := lastSystemText(updated.(model)); !strings.Contains(msg, "disabled") || !strings.Contains(msg, "--skip-permissions-unsafe") {
+			t.Fatalf("%s mode: expected gate message to explain the unsafe opt-in flag, got %q", mode, msg)
 		}
🤖 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/tui_fixes_test.go` around lines 32 - 33, The test currently
checks the system message mentions "disabled" and "unsafe" but doesn't assert
the exact opt-in flag; update the assertion in tui_fixes_test.go (where
lastSystemText(updated.(model)) is used and the variable mode is logged) to also
require that the message contains the literal "--skip-permissions-unsafe" so the
gated-path recovery instruction is preserved; modify the conditional that now
tests for "disabled" and "unsafe" to include a check for
"--skip-permissions-unsafe" and fail the test if it's missing.
🤖 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.

Outside diff comments:
In `@internal/tui/command_bash.go`:
- Around line 38-40: The output normalization currently does
strings.TrimRight(string(out), "\n") which leaves stray '\r' on Windows; update
the handling after cmd.CombinedOutput() so you first normalize CRLF to LF (e.g.,
replace "\r\n" with "\n") and then trim trailing newlines before rendering;
locate the code around the CombinedOutput call using the out variable in
internal/tui/command_bash.go (the cmd.CombinedOutput() call and the text :=
strings.TrimRight(...) line) and replace the trim-only approach with a
CRLF-normalize-then-trim approach so shell output like "hi\r\n" becomes "hi\n"
(no stray '\r') before rendering.

---

Nitpick comments:
In `@internal/tui/tui_fixes_test.go`:
- Around line 32-33: The test currently checks the system message mentions
"disabled" and "unsafe" but doesn't assert the exact opt-in flag; update the
assertion in tui_fixes_test.go (where lastSystemText(updated.(model)) is used
and the variable mode is logged) to also require that the message contains the
literal "--skip-permissions-unsafe" so the gated-path recovery instruction is
preserved; modify the conditional that now tests for "disabled" and "unsafe" to
include a check for "--skip-permissions-unsafe" and fail the test if it's
missing.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 8e96de89-df78-4ac5-bdb9-a26a9b4f75f8

📥 Commits

Reviewing files that changed from the base of the PR and between c202232 and a0c0239.

📒 Files selected for processing (2)
  • internal/tui/command_bash.go
  • internal/tui/tui_fixes_test.go

@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.

Reviewed the latest PR branch manually.

Verdict: APPROVE.

What I checked:

  • !cmd shell escape is now gated behind explicit unsafe permission mode, so it does not bypass the normal ask/auto safety posture.
  • zero --skip-permissions-unsafe and zero zeroline --skip-permissions-unsafe correctly thread unsafe mode into the TUI.
  • The @file picker is bounded by visited filesystem entries and normalizes inserted paths to forward slashes.
  • Slash palette, usage token readout, and human token formatting have reasonable tests.

Validation run locally:

  • go test ./internal/tui ./internal/cli ./internal/zeroline
  • go test ./internal/tui -run 'Test(Bash|Escape|FileSuggestions|BareSlash|ParseCommand|ZerolineHeader)'
  • go test ./internal/cli -run 'Test.*Unsafe|Test.*Zeroline|Test.*Help'
  • go test ./...

No blocking findings from my pass. CI smoke is green; CodeRabbit was still pending when I checked.

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.

2 participants