Skip to content

feat: TUI cosmetics v1.8.0 — purple theme, progress bar, position indicator, line-anchored links#83

Merged
shouze merged 11 commits intomainfrom
feat/tui-cosmetics
Mar 4, 2026
Merged

feat: TUI cosmetics v1.8.0 — purple theme, progress bar, position indicator, line-anchored links#83
shouze merged 11 commits intomainfrom
feat/tui-cosmetics

Conversation

@shouze
Copy link
Contributor

@shouze shouze commented Mar 4, 2026

What's in this PR

9 commits bringing a cohesive visual overhaul to the interactive TUI, plus a few UX improvements.

Purple TUI theme

  • Active row gets a full-width dark purple background (48;5;53) with a saturated purple left bar (38;5;129)
  • Repo names use bright purple bold (38;5;129) on inactive rows, bold white on the active row
  • Match count badge uses muted purple (38;5;99) on all rows
  • Path and :line:col suffix on active extract rows use bold white for visual homogeneity

Fetch progress bar

A paged progress bar is displayed during the GitHub API search:

Fetching results ████████████████░░░░░░░░  8/13 pages

Position indicator

A ↕ row N of total indicator at the bottom of the TUI always reflects the cursor position (not scroll offset), so it updates on every keypress.

Line-anchored file links (o)

Pressing o on an extract row now opens the file at the matched line (#L42 anchor). Repo rows still open the repository home page.

Esc to close help overlay

The help overlay can now be dismissed with Esc (in addition to h / ?).


How to verify

bun github-code-search.ts query --org <org> <term>
  • Navigate the TUI and check that ↕ row N of total updates on every keypress, including upward navigation after scrolling down
  • Press o on an extract row — browser should open at :42 (or whichever line)
  • Open help with h, close with Esc
  • Watch the progress bar fill during a multi-page search

Checklist

  • bun test — 465 pass, 0 fail
  • bun run lint — 0 errors
  • bun run format:check — no diff
  • bun run knip — no unused exports
  • Blog post docs/blog/release-v1-8-0.md written
  • docs/blog/index.md updated
  • CHANGELOG.md updated

After merge: bump minor1.8.0, cut release branch, tag, push.

Copilot AI review requested due to automatic review settings March 4, 2026 22:19
@github-actions
Copy link

github-actions bot commented Mar 4, 2026

Coverage after merging feat/tui-cosmetics into main will be

96.57%

Coverage Report
FileStmtsBranchesFuncsLinesUncovered Lines
src
   aggregate.ts100%100%100%100%
   api-utils.ts97.50%100%100%97.18%52, 60
   api.ts93.86%100%100%93.03%266–270, 326, 343, 63–69
   cache.ts98.08%100%100%97.87%28
   completions.ts99.35%100%100%99.29%252
   group.ts100%100%100%100%
   output.ts99.12%100%94.74%99.52%58
   render.ts94.05%100%88.89%94.33%153, 177–182, 184–186, 188–189, 216, 380–381, 424–427
   upgrade.ts88.06%100%94.12%87.50%122–123, 143–150, 153–159, 164, 169, 205–208
src/render
   filter-match.ts97.44%100%92.31%100%
   filter.ts100%100%100%100%
   highlight.ts96.62%100%90.40%99.31%284–285
   rows.ts97.87%100%100%97.73%54–55
   selection.ts100%100%100%100%
   summary.ts100%100%100%100%

@github-actions
Copy link

github-actions bot commented Mar 4, 2026

Coverage after merging feat/tui-cosmetics into main will be

96.57%

Coverage Report
FileStmtsBranchesFuncsLinesUncovered Lines
src
   aggregate.ts100%100%100%100%
   api-utils.ts97.50%100%100%97.18%52, 60
   api.ts93.86%100%100%93.03%266–270, 326, 343, 63–69
   cache.ts98.08%100%100%97.87%28
   completions.ts99.35%100%100%99.29%252
   group.ts100%100%100%100%
   output.ts99.12%100%94.74%99.52%58
   render.ts94.05%100%88.89%94.33%153, 177–182, 184–186, 188–189, 216, 380–381, 424–427
   upgrade.ts88.06%100%94.12%87.50%122–123, 143–150, 153–159, 164, 169, 205–208
src/render
   filter-match.ts97.44%100%92.31%100%
   filter.ts100%100%100%100%
   highlight.ts96.62%100%90.40%99.31%284–285
   rows.ts97.87%100%100%97.73%54–55
   selection.ts100%100%100%100%
   summary.ts100%100%100%100%

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR delivers a v1.8.0 interactive TUI visual refresh (purple theme + active-row styling), adds UX improvements (cursor-based position indicator, Esc help dismissal, line-anchored o links), and introduces a GitHub search fetch progress bar, with corresponding tests and release docs.

Changes:

  • Add a paged fetch progress bar during GitHub code search, plus unit tests.
  • Update TUI rendering (purple active-row styling, help overlay box + Esc close, cursor-based position indicator) and add focused rendering tests.
  • Add v1.8.0 release blog post + update blog index and changelog entry.

Reviewed changes

Copilot reviewed 8 out of 9 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
src/tui.ts Adds Esc help dismissal logic and uses buildFileUrl() for line-anchored o behavior on extract rows.
src/render.ts Implements buildFileUrl(), new help overlay box rendering, active-row styling, updated palette, and cursor-based position indicator.
src/render.test.ts Adds unit tests for buildFileUrl(), help overlay box, active-row styling, palette, and position indicator.
src/api.ts Adds buildFetchProgress() and integrates progress updates into fetchAllResults().
src/api.test.ts Adds unit tests for the progress bar string formatting.
docs/blog/release-v1-8-0.md Adds the v1.8.0 release post describing the new TUI/progress features.
docs/blog/index.md Adds the v1.8.0 entry to the blog index table.
CHANGELOG.md Adds the v1.8.0 entry linking to the blog post.

Comment on lines +160 to +163
// \x1b[38;5;129m — bright purple (filled blocks)
// \x1b[38;5;240m — dark grey (empty blocks)
const bar = `\x1b[38;5;129m${"▓".repeat(filled)}\x1b[38;5;240m${"░".repeat(empty)}\x1b[0m`;
return `\r Fetching results from GitHub… ${bar} page ${currentPage}/${totalPages}`;
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

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

buildFetchProgress() emits raw 256-colour ANSI sequences unconditionally, which ignores NO_COLOR / non-TTY environments (picocolors would otherwise disable styling). Consider gating the coloured bar on pc.isColorSupported/process.stderr.isTTY and returning a plain-text progress string when colour isn’t supported.

Suggested change
// \x1b[38;5;129m — bright purple (filled blocks)
// \x1b[38;5;240m — dark grey (empty blocks)
const bar = `\x1b[38;5;129m${"▓".repeat(filled)}\x1b[38;5;240m${"░".repeat(empty)}\x1b[0m`;
return `\r Fetching results from GitHub… ${bar} page ${currentPage}/${totalPages}`;
const plainBar = `[${"#".repeat(filled)}${".".repeat(empty)}]`;
if (pc.isColorSupported && process.stderr.isTTY) {
// \x1b[38;5;129m — bright purple (filled blocks)
// \x1b[38;5;240m — dark grey (empty blocks)
const coloredBar = `\x1b[38;5;129m${"▓".repeat(filled)}\x1b[38;5;240m${"░".repeat(empty)}\x1b[0m`;
return `\r Fetching results from GitHub… ${coloredBar} page ${currentPage}/${totalPages}`;
}
return `\r Fetching results from GitHub… ${plainBar} page ${currentPage}/${totalPages}`;

Copilot uses AI. Check for mistakes.
Comment on lines +42 to +45
// Box geometry: inner visible width = 80 (between │ chars), including 1-space
// padding on each side → usable content width = 78.
// Total box line visible length = 82 (╭ + 80×─ + ╮).
const INNER = 80; // visible chars between │ and │
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

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

renderHelpOverlay() hard-codes INNER=80, producing 82-column lines (╭ + 80×─ + ╮). This will wrap on common 80-column terminals and break the overlay layout. Compute the box width from process.stdout.columns (or accept termWidth) and cap it to the available width.

Suggested change
// Box geometry: inner visible width = 80 (between │ chars), including 1-space
// padding on each side → usable content width = 78.
// Total box line visible length = 82 (╭ + 80×─ + ╮).
const INNER = 80; // visible chars between │ and │
// Box geometry:
// - Total line width = INNER + 2 (border chars)
// - INNER is capped to the available terminal width (process.stdout.columns - 2)
// - On wide terminals, we keep a maximum INNER of 80 to preserve the layout
const DEFAULT_TERM_WIDTH = 80;
const MAX_INNER = 80; // preserve existing design on wide terminals
const MIN_INNER = 20; // avoid degenerate layouts on very narrow terminals
const termWidth =
typeof process !== "undefined" &&
process.stdout &&
typeof process.stdout.columns === "number" &&
process.stdout.columns > 0
? process.stdout.columns
: DEFAULT_TERM_WIDTH;
// Ensure the box (INNER + 2) does not exceed the terminal width
const INNER = Math.min(MAX_INNER, Math.max(MIN_INNER, termWidth - 2)); // visible chars between │ and │

Copilot uses AI. Check for mistakes.
Comment on lines +129 to +133
// \x1b[38;5;129m — saturated purple foreground for the ▌ bar
// \x1b[39m — reset foreground only (background stays active)
// \x1b[49m — reset background at the end of the line
return `\x1b[48;5;53m\x1b[38;5;129m▌\x1b[39m${content}\x1b[49m`;
}
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

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

renderActiveLine() uses raw 256-colour ANSI escapes (48;5;53 / 38;5;129) which will still be emitted when colours are disabled (NO_COLOR / non-TTY), unlike picocolors styling elsewhere. Consider routing these through a helper that returns unstyled text when pc.isColorSupported is false (or stdout isn’t a TTY).

Copilot uses AI. Check for mistakes.
Comment on lines +448 to +451
: highlightText(group.repoFullName, "repo", (s) => `\x1b[38;5;129m${pc.bold(s)}\x1b[39m`);
// Use muted purple for the match count (both active and inactive rows).
const count = `\x1b[38;5;99m${buildMatchCountLabel(group)}\x1b[39m`;
// Right-align the match count flush to the terminal edge.
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

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

Inactive repo rows hard-code 256-colour escapes (38;5;129 / 38;5;99). This bypasses picocolors’ NO_COLOR / non-TTY handling and may leak escape codes into logs. Consider guarding these colour sequences behind pc.isColorSupported (or a TTY check) and falling back to plain/bold text when unsupported.

Copilot uses AI. Check for mistakes.
Comment on lines +474 to +478
const extractLineContent = `${INDENT}${checkbox} ${filePath}`;
lines.push(
isCursor
? renderActiveLine(extractLineContent)
: `${INDENT}${INDENT}${checkbox} ${filePath}`,
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

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

Active extract rows build extractLineContent with only one INDENT (${INDENT}${checkbox}…), but inactive extract rows render with two indents (${INDENT}${INDENT}${checkbox}…). This shifts the checkbox/path left on the active row. Make the active and inactive extract row prefixes consistent (and, if you want a true full-width highlight, pad the active extract line to termWidth - ACTIVE_BAR_WIDTH).

Suggested change
const extractLineContent = `${INDENT}${checkbox} ${filePath}`;
lines.push(
isCursor
? renderActiveLine(extractLineContent)
: `${INDENT}${INDENT}${checkbox} ${filePath}`,
const extractLineContent = `${INDENT}${INDENT}${checkbox} ${filePath}`;
lines.push(
isCursor
? renderActiveLine(extractLineContent)
: extractLineContent,

Copilot uses AI. Check for mistakes.
Comment on lines +158 to +159
const filled = totalPages > 0 ? Math.round((currentPage / totalPages) * PROGRESS_BAR_WIDTH) : 0;
const empty = PROGRESS_BAR_WIDTH - filled;
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

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

filled is computed with Math.round((currentPage / totalPages) * PROGRESS_BAR_WIDTH) and empty = PROGRESS_BAR_WIDTH - filled. If currentPage > totalPages (possible with GitHub’s 1,000-result cap: totalPages clamped to 10 but paginatedFetch will still request page 11), empty becomes negative and .repeat(empty) will throw. Clamp filled to [0, PROGRESS_BAR_WIDTH] (and empty to >= 0) and/or stop rendering once page > totalPages.

Suggested change
const filled = totalPages > 0 ? Math.round((currentPage / totalPages) * PROGRESS_BAR_WIDTH) : 0;
const empty = PROGRESS_BAR_WIDTH - filled;
const rawFilled = totalPages > 0 ? Math.round((currentPage / totalPages) * PROGRESS_BAR_WIDTH) : 0;
const filled = Math.max(0, Math.min(PROGRESS_BAR_WIDTH, rawFilled));
const empty = Math.max(0, PROGRESS_BAR_WIDTH - filled);

Copilot uses AI. Check for mistakes.
Comment on lines +171 to +173
// Write the initial progress line (no newline — will be overwritten by \r).
process.stderr.write(pc.dim(" Fetching results from GitHub…"));
let totalPages = 0;
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

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

fetchAllResults() now writes an initial progress line without a trailing newline and only terminates it after paginatedFetch completes. If paginatedFetch/searchCode throws, the newline won’t be written, leaving the user’s terminal prompt on the same line. Consider wrapping the pagination in a try/finally and always writing a terminating newline (or clearing the line) in the finally block.

Copilot uses AI. Check for mistakes.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@github-actions
Copy link

github-actions bot commented Mar 4, 2026

Coverage after merging feat/tui-cosmetics into main will be

96.57%

Coverage Report
FileStmtsBranchesFuncsLinesUncovered Lines
src
   aggregate.ts100%100%100%100%
   api-utils.ts97.50%100%100%97.18%52, 60
   api.ts93.86%100%100%93.03%266–270, 326, 343, 63–69
   cache.ts98.08%100%100%97.87%28
   completions.ts99.35%100%100%99.29%252
   group.ts100%100%100%100%
   output.ts99.12%100%94.74%99.52%58
   render.ts94.05%100%88.89%94.33%153, 177–182, 184–186, 188–189, 216, 380–381, 424–427
   upgrade.ts88.06%100%94.12%87.50%122–123, 143–150, 153–159, 164, 169, 205–208
src/render
   filter-match.ts97.44%100%92.31%100%
   filter.ts100%100%100%100%
   highlight.ts96.62%100%90.40%99.31%284–285
   rows.ts97.87%100%100%97.73%54–55
   selection.ts100%100%100%100%
   summary.ts100%100%100%100%

@shouze shouze merged commit 72d1a68 into main Mar 4, 2026
6 checks passed
@shouze shouze deleted the feat/tui-cosmetics branch March 4, 2026 22: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.

2 participants