Skip to content

feat(tui): crush-inspired gradient UI, session header, and prompt styling - #89

Merged
DevFlex-AI merged 6 commits into
devfrom
crush-style-ui
Jul 31, 2026
Merged

feat(tui): crush-inspired gradient UI, session header, and prompt styling#89
DevFlex-AI merged 6 commits into
devfrom
crush-style-ui

Conversation

@DevFlex-AI

@DevFlex-AI DevFlex-AI commented Jul 31, 2026

Copy link
Copy Markdown

Issue for this PR

No linked issue; standalone UI polish and redesign for the TUI.

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

Keeps the existing bolt UI and color palette, adds gradient text accents, a session header, and a session sidebar with related UX improvements. All colors resolve from runtime theme tokens; no theme changes.

  1. Gradient wordmark: the original big ASCII "BOLT CLI" logo renders with a horizontal accent-to-primary gradient (home screen and terminal splash), replacing the hardcoded blue/red. Below ~60 columns it collapses to a one-line gradient brand row instead of clipping.

  2. Three-line session header (routes/session/header.tsx): gradient brand row with a thin rule, current session plus session count, and live subagent activity.

  3. Session sidebar (component/session-sidebar.tsx): recent top-level sessions with live busy spinners, quick-slot numbers, relative timestamps, and click-to-switch. On the home screen it is a 42-column panel (auto-shown above 120 columns, dimmed overlay below); in a chat it appears as a Sessions section at the top of the existing sidebar via a builtin feature plugin. Closable three ways: the sidebar_toggle keybind, the new /sidebar slash command, and Esc or backdrop click in overlay mode (backdrop click-close also added to the existing session sidebar overlay). Preference persists via kv:

const [sidebarPref, setSidebarPref] = kv.signal<"auto" | "hide">("home_sidebar", "auto")
const sidebarVisible = createMemo(() => {
  if (sidebarOpen()) return true
  return sidebarPref() === "auto" && wide()
})
  1. Smaller improvements: Locale.relative() compact relative-time helper; /compact-view (alias /density) toggles timestamps and tool details together; LSP count indicator in the home footer; removed dead routes/session/footer.tsx and a stray theme.tsx.bak.

How did you verify your code works?

Ran the live TUI in tmux at 150x40 and 70x25: home sidebar auto-show, /sidebar toggle on and off, session view with the Sessions section and relative times, and the compact logo on narrow terminals. bun typecheck passes in packages/tui and packages/opencode.

Screenshots / recordings

Terminal UI change; verified visually in live tmux sessions as described above.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

Summary by CodeRabbit

  • New Features
    • Added gradient-colored CLI and session wordmarks.
    • Added session headers with titles, counts, and subagent activity.
    • Added session sidebar with pinned/recent sessions, status indicators, and navigation.
    • Added responsive sidebar behavior for wide and narrow terminals.
    • Added compact session mode and sidebar controls.
    • Added LSP count to the home footer.
  • UI Improvements
    • Added relative timestamps and clearer empty states.
    • Improved sidebar overlays, hover interactions, and session highlighting.

@vercel

vercel Bot commented Jul 31, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
bolt-cli-app Skipped Skipped Jul 31, 2026 4:54pm

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The CLI and TUI now use gradient wordmarks. The TUI adds session headers, compact controls, session sidebars, responsive sidebar overlays, relative timestamps, and an LSP footer indicator.

Changes

Branding and session interface

Layer / File(s) Summary
Shared wordmark and renderers
packages/opencode/src/cli/ui.ts, packages/tui/src/component/logo.tsx
The CLI and TUI render the shared wordmark with interpolated RGB gradients and terminal-specific styling.
Session sidebar data and plugin
packages/tui/src/component/session-sidebar.tsx, packages/tui/src/util/locale.ts, packages/tui/src/feature-plugins/sidebar/sessions.tsx, packages/tui/src/feature-plugins/builtins.ts
The sidebar orders sessions, displays activity and relative timestamps, supports navigation, and registers as a built-in plugin.
Home and session sidebar integration
packages/tui/src/routes/home.tsx, packages/tui/src/routes/session/index.tsx
Home persists sidebar state and renders inline or overlay layouts. Session commands and narrow-layout dismissal control sidebar visibility.
Session header and compact controls
packages/tui/src/routes/session/header.tsx, packages/tui/src/routes/session/index.tsx
The session route renders session and subagent metadata above the transcript and adds compact-view command aliases.
Home footer indicators
packages/tui/src/feature-plugins/home/footer.tsx, packages/tui/src/routes/session/footer.tsx
The home footer displays configured LSP counts. The session footer component is removed.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant HomeRoute
  participant SessionSidebar
  participant SessionSidebarList
  participant SyncState
  User->>HomeRoute: toggle sidebar
  HomeRoute->>SessionSidebar: render inline or overlay
  SessionSidebar->>SessionSidebarList: request visible sessions
  SessionSidebarList->>SyncState: read session and activity state
  SyncState-->>SessionSidebarList: return session data
  SessionSidebarList-->>User: display sessions and indicators
Loading

Possibly related PRs

Suggested reviewers: thdxr

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main TUI changes, including gradient styling and the session header.
Description check ✅ Passed The description follows the template and provides detailed scope, verification steps, and checklist completion; only screenshots or recordings are absent.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ 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 crush-style-ui

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration.


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

@github-actions

Copy link
Copy Markdown

The following comment was made by an LLM, it may be inaccurate:

@github-actions

Copy link
Copy Markdown

Thanks for updating your PR! It now meets our contributing guidelines. 👍

@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 (2)
packages/opencode/src/cli/ui.ts (2)

54-59: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Avoid the double cast through unknown in mix.

mix builds the RGB array with .map(), which types as number[], then forces it into a tuple with as unknown as [number, number, number]. This cast bypasses type checking entirely. If the source array ever changes shape, TypeScript will not catch the mismatch.

Build the tuple directly instead, without the cast.

♻️ Proposed fix to avoid the unsafe cast
-  const mix = (t: number) =>
-    [0, 1, 2].map((i) => Math.round(GRADIENT_FROM[i]! + (GRADIENT_TO[i]! - GRADIENT_FROM[i]!) * t)) as unknown as [
-      number,
-      number,
-      number,
-    ]
+  const mix = (t: number): readonly [number, number, number] => [
+    Math.round(GRADIENT_FROM[0] + (GRADIENT_TO[0] - GRADIENT_FROM[0]) * t),
+    Math.round(GRADIENT_FROM[1] + (GRADIENT_TO[1] - GRADIENT_FROM[1]) * t),
+    Math.round(GRADIENT_FROM[2] + (GRADIENT_TO[2] - GRADIENT_FROM[2]) * t),
+  ]
🤖 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 `@packages/opencode/src/cli/ui.ts` around lines 54 - 59, Update the mix
function to construct and return the three RGB channel values as an explicit
[number, number, number] tuple rather than using map with an unknown double
cast, preserving the existing interpolation and rounding behavior.

46-47: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove the aliased import.

./logo is a local re-export, and @opencode-ai/tui is already a declared dependency. Rename the conflicting local export or expose the wordmark under a distinct name, then import it without as glyphs.

🤖 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 `@packages/opencode/src/cli/ui.ts` around lines 46 - 47, Update the logo
function’s wordmark import and local export naming to eliminate the aliased
glyphs import: expose the local ./logo re-export under a distinct name, then
import the `@opencode-ai/tui` wordmark directly without “as glyphs” and update the
reference in logo accordingly.

Source: Coding guidelines

🤖 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 `@packages/opencode/src/cli/ui.ts`:
- Around line 54-59: Update the mix function to construct and return the three
RGB channel values as an explicit [number, number, number] tuple rather than
using map with an unknown double cast, preserving the existing interpolation and
rounding behavior.
- Around line 46-47: Update the logo function’s wordmark import and local export
naming to eliminate the aliased glyphs import: expose the local ./logo re-export
under a distinct name, then import the `@opencode-ai/tui` wordmark directly
without “as glyphs” and update the reference in logo accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b87e259f-07d5-4cd5-b7d4-4b3b57de72e1

📥 Commits

Reviewing files that changed from the base of the PR and between 04ef8bf and 6f830dc.

📒 Files selected for processing (6)
  • packages/opencode/src/cli/ui.ts
  • packages/tui/src/component/logo.tsx
  • packages/tui/src/component/prompt/index.tsx
  • packages/tui/src/logo.ts
  • packages/tui/src/routes/session/header.tsx
  • packages/tui/src/routes/session/index.tsx

@deepsource-io

deepsource-io Bot commented Jul 31, 2026

Copy link
Copy Markdown

DeepSource Code Review

We reviewed changes in 04ef8bf...670a60e on this pull request. Below is the summary for the review, and you can see the individual issues we found as inline review comments.

See full review on DeepSource ↗

Important

Some issues found as part of this review are outside of the diff in this pull request and aren't shown in the inline review comments due to GitHub's API limitations. You can see those issues on the DeepSource dashboard.

PR Report Card

Overall Grade  

Focus Area: Reliability
Security  

Reliability  

Complexity  

Hygiene  

Feedback

Type-safety and nullability shortcuts

  • The any, repeated non-null assertions, and using scroll before it’s defined all lean on “I know this is safe” instead of encoding that in types or ordering.
  • Tightening types and initialization order in this file would likely clear most of these at once and make the UI behavior easier to trust.

React lifecycle and list rendering details

  • The hook-in-callback issues and the missing key on a list item are small, but both are about React needing stable, predictable structure.
  • Keeping hooks at the top level and keys on iterated elements helps avoid subtle UI glitches as this TUI grows.

Code Review Summary

Analyzer Status Updated (UTC) Details
JavaScript Jul 31, 2026 4:14p.m. Review ↗
Shell Jul 31, 2026 4:14p.m. Review ↗
Secrets Jul 31, 2026 4:14p.m. Review ↗
Docker Jul 31, 2026 4:14p.m. Review ↗
Python Jul 31, 2026 4:14p.m. Review ↗
CSS Jul 31, 2026 4:14p.m. Review ↗
Rust Jul 31, 2026 4:14p.m. Review ↗
Ruby Jul 31, 2026 4:14p.m. Review ↗
Swift Jul 31, 2026 4:14p.m. Review ↗
PHP Jul 31, 2026 4:14p.m. Review ↗
Lua Jul 31, 2026 4:14p.m. Review ↗
Java Jul 31, 2026 4:14p.m. Review ↗
Go Jul 31, 2026 4:14p.m. Review ↗
C & C++ Jul 31, 2026 4:14p.m. Review ↗
Ansible Jul 31, 2026 4:14p.m. Review ↗
Apex Jul 31, 2026 4:14p.m. Review ↗
Elixir Jul 31, 2026 4:14p.m. Review ↗
Groovy Jul 31, 2026 4:14p.m. Review ↗
Objective-C Jul 31, 2026 4:14p.m. Review ↗
PowerShell Jul 31, 2026 4:14p.m. Review ↗
Terraform Jul 31, 2026 4:14p.m. Review ↗
VB.NET Jul 31, 2026 4:14p.m. Review ↗
SQL Jul 31, 2026 4:14p.m. Review ↗
Scala Jul 31, 2026 4:14p.m. Review ↗
Perl Jul 31, 2026 4:14p.m. Review ↗
Kotlin Jul 31, 2026 4:14p.m. Review ↗
Helm Jul 31, 2026 4:14p.m. Review ↗
Erlang Jul 31, 2026 4:14p.m. Review ↗
Dart Jul 31, 2026 4:14p.m. Review ↗
C# Jul 31, 2026 4:14p.m. Review ↗

Important

AI Review is run only on demand for your team. We're only showing results of static analysis review right now. To trigger AI Review, comment @deepsourcebot review on this thread.

Comment thread packages/opencode/src/cli/ui.ts Outdated
}

export function logo(pad?: string) {
const leftWidth = glyphs.left[0]!.length

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Forbidden non-null assertion


Using non-null assertions cancels out the benefits of strict null-checking, and introduces the possibility of runtime errors. Avoid non-null assertions unless absolutely necessary. If you still need to use one, write a skipcq comment to explain why it is safe.

Comment thread packages/opencode/src/cli/ui.ts Outdated

export function logo(pad?: string) {
const leftWidth = glyphs.left[0]!.length
const totalWidth = leftWidth + 1 + glyphs.right[0]!.length

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Forbidden non-null assertion


Using non-null assertions cancels out the benefits of strict null-checking, and introduces the possibility of runtime errors. Avoid non-null assertions unless absolutely necessary. If you still need to use one, write a skipcq comment to explain why it is safe.

Comment thread packages/opencode/src/cli/ui.ts Outdated
result.push(EOL)
}
return result.join("").trimEnd()
return glyphs.left.map((row, index) => (pad ?? "") + row + " " + (glyphs.right[index] ?? "")).join(EOL)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Unexpected string concatenation


In ES2015 (ES6), we can use template literals instead of string concatenation.

Comment thread packages/opencode/src/cli/ui.ts Outdated
const shadow = "\x1b[38;5;236m"
const fg = (rgb: readonly [number, number, number]) => `\x1b[38;2;${rgb[0]};${rgb[1]};${rgb[2]}m`
const gradient = (column: number) => {
const t = column / (totalWidth - 1)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Variable name is too small


Short variable names affect code readability and complicate code refactoring, because of the difficulty in searching and replacing such short characters.

Comment thread packages/opencode/src/cli/ui.ts Outdated
const gradient = (column: number) => {
const t = column / (totalWidth - 1)
return fg(
[0, 1, 2].map((i) => Math.round(GRADIENT_FROM[i]! + (GRADIENT_TO[i]! - GRADIENT_FROM[i]!) * t)) as unknown as [

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Forbidden non-null assertion


Using non-null assertions cancels out the benefits of strict null-checking, and introduces the possibility of runtime errors. Avoid non-null assertions unless absolutely necessary. If you still need to use one, write a skipcq comment to explain why it is safe.

@@ -1244,6 +1245,7 @@ export function Session() {
<box flexDirection="row" flexGrow={1} minHeight={0}>
<box flexGrow={1} minHeight={0} paddingBottom={1} paddingLeft={2} paddingRight={2} gap={1}>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Unknown property 'minHeight' found


React components use JSX, not HTML.
So we need to use JSX attributes and React replicate the respective HTML property/attribute while rendering.
Use of HTML property in JSX can sometimes lead to errors.
For example, class is a keyword in JavaScript (JSX is an extension of JavaScript), so it will throw an error.
However, in HTML it is a valid attribute.

Note: If you use React with Web Components, use the class attribute instead.

@@ -1244,6 +1245,7 @@ export function Session() {
<box flexDirection="row" flexGrow={1} minHeight={0}>
<box flexGrow={1} minHeight={0} paddingBottom={1} paddingLeft={2} paddingRight={2} gap={1}>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Unknown property 'paddingBottom' found


React components use JSX, not HTML.
So we need to use JSX attributes and React replicate the respective HTML property/attribute while rendering.
Use of HTML property in JSX can sometimes lead to errors.
For example, class is a keyword in JavaScript (JSX is an extension of JavaScript), so it will throw an error.
However, in HTML it is a valid attribute.

Note: If you use React with Web Components, use the class attribute instead.

@@ -1244,6 +1245,7 @@ export function Session() {
<box flexDirection="row" flexGrow={1} minHeight={0}>
<box flexGrow={1} minHeight={0} paddingBottom={1} paddingLeft={2} paddingRight={2} gap={1}>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Unknown property 'paddingLeft' found


React components use JSX, not HTML.
So we need to use JSX attributes and React replicate the respective HTML property/attribute while rendering.
Use of HTML property in JSX can sometimes lead to errors.
For example, class is a keyword in JavaScript (JSX is an extension of JavaScript), so it will throw an error.
However, in HTML it is a valid attribute.

Note: If you use React with Web Components, use the class attribute instead.

@@ -1244,6 +1245,7 @@ export function Session() {
<box flexDirection="row" flexGrow={1} minHeight={0}>
<box flexGrow={1} minHeight={0} paddingBottom={1} paddingLeft={2} paddingRight={2} gap={1}>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Unknown property 'paddingRight' found


React components use JSX, not HTML.
So we need to use JSX attributes and React replicate the respective HTML property/attribute while rendering.
Use of HTML property in JSX can sometimes lead to errors.
For example, class is a keyword in JavaScript (JSX is an extension of JavaScript), so it will throw an error.
However, in HTML it is a valid attribute.

Note: If you use React with Web Components, use the class attribute instead.

@@ -1244,6 +1245,7 @@ export function Session() {
<box flexDirection="row" flexGrow={1} minHeight={0}>
<box flexGrow={1} minHeight={0} paddingBottom={1} paddingLeft={2} paddingRight={2} gap={1}>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Unknown property 'gap' found


React components use JSX, not HTML.
So we need to use JSX attributes and React replicate the respective HTML property/attribute while rendering.
Use of HTML property in JSX can sometimes lead to errors.
For example, class is a keyword in JavaScript (JSX is an extension of JavaScript), so it will throw an error.
However, in HTML it is a valid attribute.

Note: If you use React with Web Components, use the class attribute instead.

Comment thread packages/opencode/src/cli/ui.ts Outdated
}

export function logo(pad?: string) {
const leftWidth = glyphs.left[0]!.length

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Forbidden non-null assertion


Using non-null assertions cancels out the benefits of strict null-checking, and introduces the possibility of runtime errors. Avoid non-null assertions unless absolutely necessary. If you still need to use one, write a skipcq comment to explain why it is safe.

Comment thread packages/opencode/src/cli/ui.ts Outdated

export function logo(pad?: string) {
const leftWidth = glyphs.left[0]!.length
const totalWidth = leftWidth + 1 + glyphs.right[0]!.length

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Forbidden non-null assertion


Using non-null assertions cancels out the benefits of strict null-checking, and introduces the possibility of runtime errors. Avoid non-null assertions unless absolutely necessary. If you still need to use one, write a skipcq comment to explain why it is safe.

Comment thread packages/opencode/src/cli/ui.ts Outdated
result.push(EOL)
}
return result.join("").trimEnd()
return glyphs.left.map((row, index) => (pad ?? "") + row + " " + (glyphs.right[index] ?? "")).join(EOL)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Unexpected string concatenation


In ES2015 (ES6), we can use template literals instead of string concatenation.

Comment thread packages/opencode/src/cli/ui.ts Outdated
const shadow = "\x1b[38;5;236m"
const fg = (rgb: readonly [number, number, number]) => `\x1b[38;2;${rgb[0]};${rgb[1]};${rgb[2]}m`
const gradient = (column: number) => {
const t = column / (totalWidth - 1)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Variable name is too small


Short variable names affect code readability and complicate code refactoring, because of the difficulty in searching and replacing such short characters.

Comment thread packages/opencode/src/cli/ui.ts Outdated
const gradient = (column: number) => {
const t = column / (totalWidth - 1)
return fg(
[0, 1, 2].map((i) => Math.round(GRADIENT_FROM[i]! + (GRADIENT_TO[i]! - GRADIENT_FROM[i]!) * t)) as unknown as [

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Forbidden non-null assertion


Using non-null assertions cancels out the benefits of strict null-checking, and introduces the possibility of runtime errors. Avoid non-null assertions unless absolutely necessary. If you still need to use one, write a skipcq comment to explain why it is safe.

@@ -1244,6 +1245,7 @@ export function Session() {
<box flexDirection="row" flexGrow={1} minHeight={0}>
<box flexGrow={1} minHeight={0} paddingBottom={1} paddingLeft={2} paddingRight={2} gap={1}>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Unknown property 'minHeight' found


React components use JSX, not HTML.
So we need to use JSX attributes and React replicate the respective HTML property/attribute while rendering.
Use of HTML property in JSX can sometimes lead to errors.
For example, class is a keyword in JavaScript (JSX is an extension of JavaScript), so it will throw an error.
However, in HTML it is a valid attribute.

Note: If you use React with Web Components, use the class attribute instead.

@@ -1244,6 +1245,7 @@ export function Session() {
<box flexDirection="row" flexGrow={1} minHeight={0}>
<box flexGrow={1} minHeight={0} paddingBottom={1} paddingLeft={2} paddingRight={2} gap={1}>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Unknown property 'paddingBottom' found


React components use JSX, not HTML.
So we need to use JSX attributes and React replicate the respective HTML property/attribute while rendering.
Use of HTML property in JSX can sometimes lead to errors.
For example, class is a keyword in JavaScript (JSX is an extension of JavaScript), so it will throw an error.
However, in HTML it is a valid attribute.

Note: If you use React with Web Components, use the class attribute instead.

@@ -1244,6 +1245,7 @@ export function Session() {
<box flexDirection="row" flexGrow={1} minHeight={0}>
<box flexGrow={1} minHeight={0} paddingBottom={1} paddingLeft={2} paddingRight={2} gap={1}>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Unknown property 'paddingLeft' found


React components use JSX, not HTML.
So we need to use JSX attributes and React replicate the respective HTML property/attribute while rendering.
Use of HTML property in JSX can sometimes lead to errors.
For example, class is a keyword in JavaScript (JSX is an extension of JavaScript), so it will throw an error.
However, in HTML it is a valid attribute.

Note: If you use React with Web Components, use the class attribute instead.

@@ -1244,6 +1245,7 @@ export function Session() {
<box flexDirection="row" flexGrow={1} minHeight={0}>
<box flexGrow={1} minHeight={0} paddingBottom={1} paddingLeft={2} paddingRight={2} gap={1}>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Unknown property 'paddingRight' found


React components use JSX, not HTML.
So we need to use JSX attributes and React replicate the respective HTML property/attribute while rendering.
Use of HTML property in JSX can sometimes lead to errors.
For example, class is a keyword in JavaScript (JSX is an extension of JavaScript), so it will throw an error.
However, in HTML it is a valid attribute.

Note: If you use React with Web Components, use the class attribute instead.

@@ -1244,6 +1245,7 @@ export function Session() {
<box flexDirection="row" flexGrow={1} minHeight={0}>
<box flexGrow={1} minHeight={0} paddingBottom={1} paddingLeft={2} paddingRight={2} gap={1}>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Unknown property 'gap' found


React components use JSX, not HTML.
So we need to use JSX attributes and React replicate the respective HTML property/attribute while rendering.
Use of HTML property in JSX can sometimes lead to errors.
For example, class is a keyword in JavaScript (JSX is an extension of JavaScript), so it will throw an error.
However, in HTML it is a valid attribute.

Note: If you use React with Web Components, use the class attribute instead.

Comment thread packages/opencode/src/cli/ui.ts Outdated
}

export function logo(pad?: string) {
const leftWidth = glyphs.left[0]!.length

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Forbidden non-null assertion


Using non-null assertions cancels out the benefits of strict null-checking, and introduces the possibility of runtime errors. Avoid non-null assertions unless absolutely necessary. If you still need to use one, write a skipcq comment to explain why it is safe.

Comment thread packages/opencode/src/cli/ui.ts Outdated

export function logo(pad?: string) {
const leftWidth = glyphs.left[0]!.length
const totalWidth = leftWidth + 1 + glyphs.right[0]!.length

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Forbidden non-null assertion


Using non-null assertions cancels out the benefits of strict null-checking, and introduces the possibility of runtime errors. Avoid non-null assertions unless absolutely necessary. If you still need to use one, write a skipcq comment to explain why it is safe.

Comment thread packages/opencode/src/cli/ui.ts Outdated
result.push(EOL)
}
return result.join("").trimEnd()
return glyphs.left.map((row, index) => (pad ?? "") + row + " " + (glyphs.right[index] ?? "")).join(EOL)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Unexpected string concatenation


In ES2015 (ES6), we can use template literals instead of string concatenation.

Comment thread packages/opencode/src/cli/ui.ts Outdated
const shadow = "\x1b[38;5;236m"
const fg = (rgb: readonly [number, number, number]) => `\x1b[38;2;${rgb[0]};${rgb[1]};${rgb[2]}m`
const gradient = (column: number) => {
const t = column / (totalWidth - 1)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Variable name is too small


Short variable names affect code readability and complicate code refactoring, because of the difficulty in searching and replacing such short characters.

Comment thread packages/opencode/src/cli/ui.ts Outdated
const gradient = (column: number) => {
const t = column / (totalWidth - 1)
return fg(
[0, 1, 2].map((i) => Math.round(GRADIENT_FROM[i]! + (GRADIENT_TO[i]! - GRADIENT_FROM[i]!) * t)) as unknown as [

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Forbidden non-null assertion


Using non-null assertions cancels out the benefits of strict null-checking, and introduces the possibility of runtime errors. Avoid non-null assertions unless absolutely necessary. If you still need to use one, write a skipcq comment to explain why it is safe.

@@ -1244,6 +1245,7 @@ export function Session() {
<box flexDirection="row" flexGrow={1} minHeight={0}>
<box flexGrow={1} minHeight={0} paddingBottom={1} paddingLeft={2} paddingRight={2} gap={1}>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Unknown property 'minHeight' found


React components use JSX, not HTML.
So we need to use JSX attributes and React replicate the respective HTML property/attribute while rendering.
Use of HTML property in JSX can sometimes lead to errors.
For example, class is a keyword in JavaScript (JSX is an extension of JavaScript), so it will throw an error.
However, in HTML it is a valid attribute.

Note: If you use React with Web Components, use the class attribute instead.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

False positive: minHeight is a valid layout prop on the opentui box component; this is SolidJS with a custom TUI renderer, not React DOM.

@@ -1244,6 +1245,7 @@ export function Session() {
<box flexDirection="row" flexGrow={1} minHeight={0}>
<box flexGrow={1} minHeight={0} paddingBottom={1} paddingLeft={2} paddingRight={2} gap={1}>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Unknown property 'paddingBottom' found


React components use JSX, not HTML.
So we need to use JSX attributes and React replicate the respective HTML property/attribute while rendering.
Use of HTML property in JSX can sometimes lead to errors.
For example, class is a keyword in JavaScript (JSX is an extension of JavaScript), so it will throw an error.
However, in HTML it is a valid attribute.

Note: If you use React with Web Components, use the class attribute instead.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

False positive: paddingBottom is a valid layout prop on the opentui box component; this is SolidJS with a custom TUI renderer, not React DOM.

@@ -1244,6 +1245,7 @@ export function Session() {
<box flexDirection="row" flexGrow={1} minHeight={0}>
<box flexGrow={1} minHeight={0} paddingBottom={1} paddingLeft={2} paddingRight={2} gap={1}>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Unknown property 'paddingLeft' found


React components use JSX, not HTML.
So we need to use JSX attributes and React replicate the respective HTML property/attribute while rendering.
Use of HTML property in JSX can sometimes lead to errors.
For example, class is a keyword in JavaScript (JSX is an extension of JavaScript), so it will throw an error.
However, in HTML it is a valid attribute.

Note: If you use React with Web Components, use the class attribute instead.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

False positive: paddingLeft is a valid layout prop on the opentui box component; this is SolidJS with a custom TUI renderer, not React DOM.

@@ -1244,6 +1245,7 @@ export function Session() {
<box flexDirection="row" flexGrow={1} minHeight={0}>
<box flexGrow={1} minHeight={0} paddingBottom={1} paddingLeft={2} paddingRight={2} gap={1}>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Unknown property 'paddingRight' found


React components use JSX, not HTML.
So we need to use JSX attributes and React replicate the respective HTML property/attribute while rendering.
Use of HTML property in JSX can sometimes lead to errors.
For example, class is a keyword in JavaScript (JSX is an extension of JavaScript), so it will throw an error.
However, in HTML it is a valid attribute.

Note: If you use React with Web Components, use the class attribute instead.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

False positive: paddingRight is a valid layout prop on the opentui box component; this is SolidJS with a custom TUI renderer, not React DOM.

@@ -1244,6 +1245,7 @@ export function Session() {
<box flexDirection="row" flexGrow={1} minHeight={0}>
<box flexGrow={1} minHeight={0} paddingBottom={1} paddingLeft={2} paddingRight={2} gap={1}>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Unknown property 'gap' found


React components use JSX, not HTML.
So we need to use JSX attributes and React replicate the respective HTML property/attribute while rendering.
Use of HTML property in JSX can sometimes lead to errors.
For example, class is a keyword in JavaScript (JSX is an extension of JavaScript), so it will throw an error.
However, in HTML it is a valid attribute.

Note: If you use React with Web Components, use the class attribute instead.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

False positive: gap is a valid layout prop on the opentui box component; this is SolidJS with a custom TUI renderer, not React DOM.

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

🧹 Nitpick comments (1)
packages/tui/src/component/session-sidebar.tsx (1)

115-124: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Export the sidebar width as a shared constant.

width={42} is hardcoded here. packages/tui/src/routes/session/index.tsx line 280 also hardcodes 42 when it computes contentWidth. A change to one value silently breaks the layout math in the other place. Export a constant from this module and use it in both places.

♻️ Proposed refactor
+export const SESSION_SIDEBAR_WIDTH = 42
+
 // Standalone 42-column sidebar panel for the home screen.
 export function SessionSidebar(props: { overlay?: boolean }) {
   const { theme } = useTheme()
 
   return (
     <box
       backgroundColor={theme.backgroundPanel}
-      width={42}
+      width={SESSION_SIDEBAR_WIDTH}
🤖 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 `@packages/tui/src/component/session-sidebar.tsx` around lines 115 - 124,
Export a shared sidebar-width constant from the module containing the sidebar
component, replace the hardcoded width in the sidebar <box> with that constant,
and update the session route’s contentWidth calculation to use the same exported
symbol instead of 42.
🤖 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 `@packages/tui/src/routes/home.tsx`:
- Around line 45-60: The sidebar dismissal paths persistently change the
preference instead of only closing transient overlays. In
packages/tui/src/routes/home.tsx lines 45-60, add a dismissSidebar action that
only calls setSidebarOpen(false), use it for the Escape binding and backdrop
handler, and update toggleSidebar so opening on a wide terminal does not leave
sidebarOpen true. In packages/tui/src/routes/session/index.tsx lines 1471-1483,
remove setSidebar(() => "hide") from the backdrop handler and retain only
setSidebarOpen(false).
- Around line 131-146: Set the narrow-terminal overlay container in the !wide()
Match block above the content pane by adding zIndex={1001} to its outer box,
ensuring it renders above the prompt and receives mouse events while leaving the
inner click-capture box and SessionSidebar unchanged.

In `@packages/tui/src/routes/session/index.tsx`:
- Around line 760-776: Update the slash-command autocomplete ranking logic to
prioritize an exact slash-name match before applying fuzzy ranking or prefix
boosts. Ensure entering `/compact` selects the `session.compact` command while
`/compact-view` remains available for its exact name, using the command’s slash
name rather than aliases for exact-match precedence.

---

Nitpick comments:
In `@packages/tui/src/component/session-sidebar.tsx`:
- Around line 115-124: Export a shared sidebar-width constant from the module
containing the sidebar component, replace the hardcoded width in the sidebar
<box> with that constant, and update the session route’s contentWidth
calculation to use the same exported symbol instead of 42.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 3ddd9e2e-c0f3-4a88-bd95-44dcd0ad55ab

📥 Commits

Reviewing files that changed from the base of the PR and between 6f830dc and 02b4a80.

📒 Files selected for processing (12)
  • packages/opencode/src/cli/ui.ts
  • packages/tui/src/component/logo.tsx
  • packages/tui/src/component/session-sidebar.tsx
  • packages/tui/src/context/theme.tsx.bak
  • packages/tui/src/feature-plugins/builtins.ts
  • packages/tui/src/feature-plugins/home/footer.tsx
  • packages/tui/src/feature-plugins/sidebar/sessions.tsx
  • packages/tui/src/routes/home.tsx
  • packages/tui/src/routes/session/footer.tsx
  • packages/tui/src/routes/session/header.tsx
  • packages/tui/src/routes/session/index.tsx
  • packages/tui/src/util/locale.ts
💤 Files with no reviewable changes (2)
  • packages/tui/src/routes/session/footer.tsx
  • packages/tui/src/context/theme.tsx.bak
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/tui/src/routes/session/header.tsx
  • packages/opencode/src/cli/ui.ts
  • packages/tui/src/component/logo.tsx

Comment on lines +45 to +60
const kv = useKV()
const dialog = useDialog()
const [sidebarPref, setSidebarPref] = kv.signal<"auto" | "hide">("home_sidebar", "auto")
const [sidebarOpen, setSidebarOpen] = createSignal(false)
const wide = createMemo(() => dimensions().width > 120)
const sidebarVisible = createMemo(() => {
if (sidebarOpen()) return true
return sidebarPref() === "auto" && wide()
})
const toggleSidebar = () => {
batch(() => {
const isVisible = sidebarVisible()
setSidebarPref(() => (isVisible ? "hide" : "auto"))
setSidebarOpen(!isVisible)
})
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Transient overlay dismissal persists the sidebar preference in both routes.

On terminals of 120 columns or less, both routes render the sidebar as a transient overlay. Both dismissal paths write the persisted preference key to "hide". After one dismissal, the automatic sidebar no longer appears when the user resizes to a wide terminal. Separate the persisted preference from the transient open state: write the preference only for an explicit toggle, and clear only the open signal on dismissal.

  • packages/tui/src/routes/home.tsx#L45-L60: add a dismissSidebar action that calls only setSidebarOpen(false), and use it for the Escape binding at line 88 and the backdrop at line 141. Also change setSidebarOpen(!isVisible) to avoid leaving sidebarOpen true after the sidebar opens on a wide terminal.
  • packages/tui/src/routes/session/index.tsx#L1471-L1483: remove setSidebar(() => "hide") from the backdrop handler and keep only setSidebarOpen(false).
📍 Affects 2 files
  • packages/tui/src/routes/home.tsx#L45-L60 (this comment)
  • packages/tui/src/routes/session/index.tsx#L1471-L1483
🤖 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 `@packages/tui/src/routes/home.tsx` around lines 45 - 60, The sidebar dismissal
paths persistently change the preference instead of only closing transient
overlays. In packages/tui/src/routes/home.tsx lines 45-60, add a dismissSidebar
action that only calls setSidebarOpen(false), use it for the Escape binding and
backdrop handler, and update toggleSidebar so opening on a wide terminal does
not leave sidebarOpen true. In packages/tui/src/routes/session/index.tsx lines
1471-1483, remove setSidebar(() => "hide") from the backdrop handler and retain
only setSidebarOpen(false).

Comment on lines +131 to +146
<Match when={!wide()}>
<box
position="absolute"
top={0}
left={0}
right={0}
bottom={0}
alignItems="flex-start"
backgroundColor={RGBA.fromInts(0, 0, 0, 70)}
>
<box position="absolute" top={0} left={0} right={0} bottom={0} onMouseDown={() => toggleSidebar()} />
<SessionSidebar overlay />
</box>
</Match>
</Switch>
</Show>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Inspect zIndex usage and overlay patterns in the TUI to determine stacking rules.
set -euo pipefail

# Find every zIndex usage in the TUI package with context.
rg -n -C 4 'zIndex' --glob '*.tsx' --glob '*.ts' packages/tui/src

# Find existing absolute overlay patterns to compare stacking conventions.
rg -n -C 6 'position="absolute"' --glob '*.tsx' packages/tui/src

Repository: bolt-builder/bolt-cli

Length of output: 18077


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- TUI package and renderer references ---'
rg -n -C 3 'opentui|zIndex|z-index|stacking|render.*order|mouse.*event|onMouseDown' \
  packages/tui package.json packages/*/package.json bun.lockb bun.lock package-lock.json pnpm-lock.yaml 2>/dev/null || true

printf '%s\n' '--- candidate renderer/source files ---'
git ls-files | rg '(^|/)(opentui|terminal|renderer|renderable|layout|mouse|input|package\.json|bun\.lock)' | head -200

printf '%s\n' '--- home and session layout context ---'
sed -n '105,175p' packages/tui/src/routes/home.tsx
sed -n '1445,1495p' packages/tui/src/routes/session/index.tsx

Repository: bolt-builder/bolt-cli

Length of output: 50377


🌐 Web query:

@opentui/core 0.4.5 zIndex render order mouse event hit testing

💡 Result:

In @opentui/core 0.4.5, z-index and rendering order are tightly coupled. Components maintain two separate orderings for their children: layout order and z-index order [1]. Rendering Order The rendering order is governed by the z-index value, where higher values render on top [2][3][1]. Each renderable contains a list of children sorted by z-index (_childrenInZIndexOrder) [4][5][1]. When a renderable's z-index changes, the parent is marked as dirty, and the list is lazily re-sorted during the render command generation process to ensure efficiency [5][1]. Mouse Event Hit Testing Hit testing is handled by the renderer through the hitTest(x, y) method, which determines which component is located at a specific terminal position [6][7][8]. This relies on an internal "hit grid" that the renderer maintains [9][8]. Key technical points: 1. Event Processing: When a mouse event occurs, the renderer uses hitTest to identify the appropriate target component [9]. Once identified, it creates a MouseEvent instance and invokes the processMouseEvent method on that component [9][8]. 2. Event Handling: Developers can handle interactions by overriding onMouseEvent (or specific handlers like onMouseDown, onMouseUp, etc.) within their custom renderables [4][10]. 3. Hover States: The renderer automatically manages over and out events by re-checking the hover state whenever the hit grid changes, such as after a layout update or when a component's position/visibility changes [9][8]. 4. Hit Grid Maintenance: If a component is dynamically moved or updated, it is crucial to ensure the hit grid is updated—often handled automatically by calling requestRender() to trigger a new frame if the renderer is not in a live state [9]. If you find that mouse events are not triggering as expected, ensure that your components are correctly registered and that the hit grid has been updated by triggering a re-render [9].

Citations:


🌐 Web query:

site:github.com/anomalyco/opentui zIndex Renderable mouse event hit testing

💡 Result:

In the context of rendering systems like OpenTUI, zIndex is a critical property used to determine the stacking order of renderable elements, which directly influences how mouse event hit testing resolves [1][2]. When multiple elements overlap, the rendering system performs hit testing to identify which element resides at the specific pointer (mouse) coordinates [3]. The zIndex property dictates the visual hierarchy: elements with a higher zIndex are drawn "on top" of those with a lower one, and hit testing typically follows this same order to ensure that the topmost interactive element receives mouse events [1][2]. Key principles of zIndex-based hit testing include: 1. Stacking Order: Renderable objects maintain a stacking order where the zIndex is the primary sorting criterion [1]. When a parent container needs to determine the order of its children for hit testing, it performs a sort: higher zIndex values appear later in the rendering order (and thus are visually on top), ensuring they are evaluated first during hit detection [1]. 2. Default Fallback: When zIndex values are equal or absent, systems often fallback to the layout or insertion order to determine the final stacking position, ensuring predictable behavior even without explicit zIndex assignments [4]. 3. Hit Testing Resolution: The hit testing mechanism (often implemented as a hitTest(x, y) method) queries the internal representation of the scene graph [3]. By sorting renderables by zIndex before performing the hit check, the system ensures that the query returns the identifier of the topmost element under the cursor [3][1]. 4. Event Propagation: Once the topmost element is identified, mouse events (like 'down', 'move', 'up') are processed by that renderable [3]. Modern implementations often mirror DOM-like event dispatch, supporting capture and bubble phases, allowing events to propagate through the hierarchy after the initial hit test identifies the target [5]. This architecture ensures that interactive elements like overlays, dialogs, or floating buttons—which are assigned higher zIndex values—correctly intercept mouse events before they reach elements positioned "behind" them [6].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

base='https://raw.githubusercontent.com/anomalyco/opentui/406bed2d791e62ba600a5c9f2c072a3b246b6b01/packages/core/src'
tmpdir="$(mktemp -d)"
trap 'rm -rf "$tmpdir"' EXIT

curl -fsSL "$base/Renderable.ts" -o "$tmpdir/Renderable.ts"
curl -fsSL "$base/renderer.ts" -o "$tmpdir/renderer.ts"

printf '%s\n' '--- z-index ordering and child traversal ---'
rg -n -C 8 '_childrenInZIndexOrder|zIndex|hitTest|processMouseEvent' \
  "$tmpdir/Renderable.ts" "$tmpdir/renderer.ts"

printf '%s\n' '--- relevant source sizes ---'
wc -l "$tmpdir/Renderable.ts" "$tmpdir/renderer.ts"

Repository: bolt-builder/bolt-cli

Length of output: 30689


Set the narrow-terminal overlay zIndex above the content pane.

The overlay and content pane both default to zIndex={0}. The content pane is declared later, so it paints above the overlay. The prompt's zIndex={1000} is local to the content pane. Set the overlay to zIndex={1001} so it paints above the prompt and receives mouse events.

🤖 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 `@packages/tui/src/routes/home.tsx` around lines 131 - 146, Set the
narrow-terminal overlay container in the !wide() Match block above the content
pane by adding zIndex={1001} to its outer box, ensuring it renders above the
prompt and receives mouse events while leaving the inner click-capture box and
SessionSidebar unchanged.

Comment on lines +760 to +776
{
title: compactMode() ? "Disable compact mode" : "Enable compact mode",
value: "session.toggle.compact",
category: "Session",
slash: {
name: "compact-view",
aliases: ["density"],
},
run: () => {
batch(() => {
const compact = compactMode()
setTimestamps(() => (compact ? "show" : "hide"))
setShowDetails(() => compact)
})
dialog.clear()
},
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Locate the slash-command resolution logic and check exact-match priority.
set -euo pipefail

# Find where slashName and slashAliases are consumed.
rg -n -C 8 'slashName|slashAliases' --glob '*.ts' --glob '*.tsx' packages/tui/src

# Find the matching/filtering logic for slash commands.
rg -n -C 10 -P 'slash.*(startsWith|includes|filter|find|match)' --glob '*.ts' --glob '*.tsx' packages/tui/src

Repository: bolt-builder/bolt-cli

Length of output: 32995


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 12 \
  'CommandSlashEntry|slash.*(command|entry)|commands.*filter|filter.*commands|fuzzy|startsWith|matchSorter|command palette' \
  packages/tui/src packages --glob '*.ts' --glob '*.tsx' \
  | head -n 1200

printf '\n--- keymap.tsx relevant sections ---\n'
cat -n packages/tui/src/keymap.tsx | sed -n '1,330p'

printf '\n--- session command definitions ---\n'
cat -n packages/tui/src/routes/session/index.tsx | sed -n '600,800p'

Repository: bolt-builder/bolt-cli

Length of output: 50378


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- autocomplete filtering and command options ---'
cat -n packages/tui/src/component/prompt/autocomplete.tsx | sed -n '450,535p'

printf '%s\n' '--- autocomplete command source and selection paths ---'
rg -n -C 10 \
  'useCommandSlashes|commandsValue|onSelect|dispatchCommand|display.*slash|value.*display' \
  packages/tui/src/component/prompt/autocomplete.tsx packages/tui/src/component/prompt/index.tsx packages/tui/src/ui/dialog-select.tsx

printf '%s\n' '--- all compact command registrations ---'
rg -n -C 8 \
  'session\.compact|compact-view|session\.toggle\.compact|slashName: "compact"|name: "compact"' \
  packages/tui/src --glob '*.ts' --glob '*.tsx'

Repository: bolt-builder/bolt-cli

Length of output: 42813


🌐 Web query:

fuzzysort npm source exact match score prefix shorter target ranking

💡 Result:

In the current version of fuzzysort (v3.0.0+), the scoring system outputs a number between 0 and 1, where 1 represents a perfect match and 0 represents no match [1][2]. The ranking algorithm is designed such that shorter targets are generally prioritized over longer ones, as excessive length can negatively impact a score [3]. Key factors influencing the ranking include: Exact and Prefix Matches: The algorithm strongly favors substrings and matches that appear early in the target string [2][4]. Substring matches are highly relevant, and the system is tuned to favor matches that start near the beginning of the target [2][4]. Target Length: Fuzzysort naturally prioritizes shorter targets [3]. Because longer strings have more potential for non-matching characters, they are implicitly penalized compared to shorter, more precise matches [3]. Customization: If the default ranking does not meet specific requirements, you can implement a custom scoreFn [1][2]. This allows you to combine scores from different keys, apply weights, or adjust the importance of specific properties [1][5]. For example, you can use scoreFn to boost results based on internal object metadata (e.g., boosting bookmarked items) or to adjust for the length of specific fields [1][3]. Performance Tip: For better results and performance, it is recommended to filter out excessively long targets before searching and, if the data is static, to use fuzzysort.prepare on your targets to speed up the scoring process [1]. Historical Note: In older versions (pre-v3.0.0), scores were negative numbers, where higher (closer to zero) scores were better [6][7]. This was changed to the current 0-to-1 positive scale to improve intuitiveness [2][4].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- fuzzysort dependency version ---'
rg -n -C 2 '"fuzzysort"' --glob 'package.json' --glob 'bun.lock*' --glob 'package-lock.json' --glob 'yarn.lock' --glob 'pnpm-lock.yaml'

printf '%s\n' '--- deterministic ranking for the two slash entries ---'
node - <<'JS'
const source = await (await fetch("https://cdn.jsdelivr.net/npm/fuzzysort@3.0.0/fuzzysort.js")).text()
const fuzzysort = new Function(`${source}; return fuzzysort`)()
const options = [
  { display: "/compact" },
  { display: "/compact-view" },
]
const result = fuzzysort.go("/compact", options, {
  keys: [(obj) => obj.display],
  scoreFn: (objResults) => {
    const displayResult = objResults[0]
    let score = objResults.score
    if (displayResult && displayResult.target.startsWith("/compact")) score *= 2
    return score
  },
})
console.log(result.map((item) => ({ display: item.obj.display, score: item.score })))
JS

Repository: bolt-builder/bolt-cli

Length of output: 196


🏁 Script executed:

#!/bin/bash
set -u

printf '%s\n' '--- fuzzysort dependency declarations ---'
rg -n -C 2 'fuzzysort' --glob 'package.json' --glob 'bun.lock*' --glob 'package-lock.json' --glob 'yarn.lock' --glob 'pnpm-lock.yaml' || true

printf '%s\n' '--- deterministic ranking for the two slash entries ---'
node - <<'JS'
const source = await (await fetch("https://cdn.jsdelivr.net/npm/fuzzysort@3.0.0/fuzzysort.js")).text()
const fuzzysort = new Function(`${source}; return fuzzysort`)()
const options = [
  { display: "/compact" },
  { display: "/compact-view" },
]
const result = fuzzysort.go("/compact", options, {
  keys: [(obj) => obj.display],
  scoreFn: (objResults) => {
    const displayResult = objResults[0]
    let score = objResults.score
    if (displayResult && displayResult.target.startsWith("/compact")) score *= 2
    return score
  },
})
console.log(result.map((item) => ({ display: item.obj.display, score: item.score })))
JS

Repository: bolt-builder/bolt-cli

Length of output: 670


Add exact slash-name precedence.

The autocomplete fuzzy-ranks /compact and /compact-view. The prefix boost applies to both entries. Rank exact slash names before fuzzy results so /compact always selects session.compact.

🤖 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 `@packages/tui/src/routes/session/index.tsx` around lines 760 - 776, Update the
slash-command autocomplete ranking logic to prioritize an exact slash-name match
before applying fuzzy ranking or prefix boosts. Ensure entering `/compact`
selects the `session.compact` command while `/compact-view` remains available
for its exact name, using the command’s slash name rather than aliases for
exact-match precedence.

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.

1 participant