Conversation
* adding favorite model picker
…rite recents, instead of counting the favorites against the recents if a model is both
WalkthroughThis PR introduces a favorites feature to the OpenCode TUI, enabling users to mark preferred models as favorites and cycle through them using new keybinds. The changes add a new Changes
Sequence DiagramsequenceDiagram
actor User
participant TUI
participant Dialog
participant LocalModel
participant Storage
User->>TUI: Press ctrl+up (cycle favorite forward)
TUI->>LocalModel: cycleFavorite(1)
LocalModel->>LocalModel: Find next valid favorite<br/>with wrap-around
LocalModel->>TUI: Switch to next favorite model
Note over Dialog: Model Dialog Flow
User->>Dialog: Open model selection dialog
Dialog->>LocalModel: Get favorites, recents, all models
Dialog->>Dialog: Build options (favorites,<br/>recents, all, providers)
Dialog->>User: Display prioritized options
User->>Dialog: Highlight option + Press ctrl+f
Dialog->>LocalModel: toggleFavorite(model)
LocalModel->>LocalModel: Toggle in favorites[]
LocalModel->>Storage: save() to model.json
Storage-->>LocalModel: Persisted
User->>Dialog: Select model + Submit
Dialog->>LocalModel: set(model) + addRecent(model)
LocalModel->>LocalModel: Dedup & cap recent items
LocalModel->>Storage: save() to model.json
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes
Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
packages/opencode/src/cli/cmd/tui/context/local.tsx (1)
130-138: Fire-and-forget persistence is acceptable but consider error handling.
Bun.writereturns a Promise that isn't awaited, which is fine for non-critical persistence. However, if writes fail silently, users may lose favorites unexpectedly. Consider adding a.catch()to log errors or show a toast on failure.function save() { Bun.write( file, JSON.stringify({ recent: modelStore.recent, favorite: modelStore.favorite, }), - ) + ).catch(() => { + // Optional: log or show toast on save failure + }) }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
packages/sdk/js/src/gen/types.gen.tsis excluded by!**/gen/**
📒 Files selected for processing (12)
packages/opencode/src/cli/cmd/tui/app.tsx(1 hunks)packages/opencode/src/cli/cmd/tui/component/dialog-model.tsx(3 hunks)packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx(1 hunks)packages/opencode/src/cli/cmd/tui/context/local.tsx(4 hunks)packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx(1 hunks)packages/opencode/src/config/config.ts(1 hunks)packages/plugin/package.json(1 hunks)packages/sdk/go/config.go(2 hunks)packages/sdk/js/package.json(1 hunks)packages/sdk/python/src/opencode_ai/models/keybinds_config.py(6 hunks)packages/ui/src/components/select-dialog.tsx(2 hunks)packages/web/src/content/docs/keybinds.mdx(1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{js,jsx,ts,tsx}: DO NOT do unnecessary destructuring of variables
DO NOT useelsestatements unless necessary
DO NOT usetry/catchif it can be avoided
AVOIDtry/catchwhere possible
AVOIDletstatements
PREFER single word variable names where possible
Use as many Bun APIs as possible like Bun.file()
Files:
packages/opencode/src/cli/cmd/tui/component/prompt/index.tsxpackages/opencode/src/cli/cmd/tui/app.tsxpackages/opencode/src/config/config.tspackages/ui/src/components/select-dialog.tsxpackages/opencode/src/cli/cmd/tui/ui/dialog-select.tsxpackages/opencode/src/cli/cmd/tui/component/dialog-model.tsxpackages/opencode/src/cli/cmd/tui/context/local.tsx
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
AVOID using
anytype
Files:
packages/opencode/src/cli/cmd/tui/component/prompt/index.tsxpackages/opencode/src/cli/cmd/tui/app.tsxpackages/opencode/src/config/config.tspackages/ui/src/components/select-dialog.tsxpackages/opencode/src/cli/cmd/tui/ui/dialog-select.tsxpackages/opencode/src/cli/cmd/tui/component/dialog-model.tsxpackages/opencode/src/cli/cmd/tui/context/local.tsx
packages/opencode/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (packages/opencode/AGENTS.md)
Use Bun with TypeScript ESM modules as the runtime environment
Files:
packages/opencode/src/cli/cmd/tui/component/prompt/index.tsxpackages/opencode/src/cli/cmd/tui/app.tsxpackages/opencode/src/config/config.tspackages/opencode/src/cli/cmd/tui/ui/dialog-select.tsxpackages/opencode/src/cli/cmd/tui/component/dialog-model.tsxpackages/opencode/src/cli/cmd/tui/context/local.tsx
packages/opencode/**/*.{ts,tsx}
📄 CodeRabbit inference engine (packages/opencode/AGENTS.md)
packages/opencode/**/*.{ts,tsx}: Use relative imports for local modules with named imports preferred
Use Zod schemas for validation
Use TypeScript interfaces for defining data structures
Use camelCase for variable and function names
Use PascalCase for class and namespace names
Use Result patterns for error handling; avoid throwing exceptions in tools
Organize code using namespace-based structure (e.g.,Tool.define(),Session.create())
PasssessionIDin tool context and useApp.provide()for dependency injection
Validate all inputs with Zod schemas
UseLog.create({ service: "name" })pattern for logging
UseStoragenamespace for data persistence
Files:
packages/opencode/src/cli/cmd/tui/component/prompt/index.tsxpackages/opencode/src/cli/cmd/tui/app.tsxpackages/opencode/src/config/config.tspackages/opencode/src/cli/cmd/tui/ui/dialog-select.tsxpackages/opencode/src/cli/cmd/tui/component/dialog-model.tsxpackages/opencode/src/cli/cmd/tui/context/local.tsx
🧠 Learnings (1)
📚 Learning: 2025-11-24T22:47:41.129Z
Learnt from: CR
Repo: kcrommett/opencode PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2025-11-24T22:47:41.129Z
Learning: Applies to packages/opencode/**/*.{ts,tsx} : Use `Storage` namespace for data persistence
Applied to files:
packages/opencode/src/cli/cmd/tui/context/local.tsx
🧬 Code graph analysis (1)
packages/opencode/src/cli/cmd/tui/component/dialog-model.tsx (1)
packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx (1)
filter(165-167)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Upload results
🔇 Additional comments (21)
packages/sdk/js/package.json (1)
29-29: ✅ Formatting cleanup approved.The addition of the trailing newline aligns with standard formatting conventions and best practices.
packages/plugin/package.json (1)
27-27: LGTM! Standard formatting.Adding a trailing newline at the end of the file is standard practice and improves consistency.
packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx (1)
256-256: LGTM! Improved spacing for keybind bar.The increased gap improves readability, especially with the addition of new favorite cycling keybinds in the broader feature set.
packages/web/src/content/docs/keybinds.mdx (1)
41-42: LGTM! Documentation updated consistently.The new keybind entries follow the established pattern and are documented with sensible defaults. Setting them to "none" by default allows users to opt-in to the favorite cycling feature without keyboard shortcut conflicts.
packages/sdk/go/config.go (1)
1938-1941: LGTM! Auto-generated code follows established pattern.The new keybind fields are properly structured with correct Go naming conventions and JSON tags. Since this file is auto-generated from the OpenAPI spec (line 1), ensure the source specification is also updated correctly.
Also applies to: 2015-2016
packages/ui/src/components/select-dialog.tsx (1)
17-17: LGTM! Good extensibility and optimization.The addition of the optional
onKeyEventprop is a non-breaking enhancement that provides parent components with more control over keyboard interactions. The refactoring ofhandleKeyto computeselectedonce and reuse it (lines 69-75) is an improvement over the previous implementation that would have recomputed it for the Enter key case.Also applies to: 69-79
packages/opencode/src/config/config.ts (1)
431-432: LGTM! Schema additions follow established pattern.The new keybind configurations are properly defined using Zod schema with:
- Optional fields with sensible defaults ("none")
- Clear descriptions
- Consistency with existing model cycling keybinds
The default value of "none" allows users to opt-in to the feature without automatic keyboard shortcut assignments, which prevents conflicts.
packages/opencode/src/cli/cmd/tui/app.tsx (1)
251-268:local.model.cycleFavoritemethod is properly defined and exists.The method is implemented in
packages/opencode/src/cli/cmd/tui/context/local.tsx(lines 228-250) with the correct signaturecycleFavorite(direction: 1 | -1). The implementation handles cycling through favorite models with appropriate wrapping behavior, matching the pattern used in the new commands at lines 251-268.packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx (1)
478-479: TheaddRecentmethod exists and is correctly used with the proper type signature.Verification confirms:
addRecentmethod is defined at line 251 inpackages/opencode/src/cli/cmd/tui/context/local.tsxwith signature:addRecent(model: { providerID: string; modelID: string })local.model.currentreturns the result of acreateMemothat yields{ providerID: string; modelID: string }- The call at lines 478-479 correctly passes an object matching the expected type
- The placement after message submission is appropriate for tracking recently used models
packages/sdk/python/src/opencode_ai/models/keybinds_config.py (2)
46-47: LGTM!New keybind attributes follow the existing pattern with appropriate docstrings and default values of
"none"(disabled by default), consistent with other keybinds in the class.Also applies to: 100-101
183-191: Serialization and deserialization correctly extended.The
to_dictandfrom_dictmethods are properly updated to include the newmodel_cycle_favoriteandmodel_cycle_favorite_reversefields, following the established pattern for handlingUNSETvalues.Also applies to: 288-291, 396-399, 469-470
packages/opencode/src/cli/cmd/tui/component/dialog-model.tsx (5)
10-10: Good introduction ofModelReftype and helper predicates.The
ModelReftype and associated predicates (isMatch,isFavorite,isRecent,isCurrent) improve type safety and readability. The predicates encapsulate comparison logic cleanly.Also applies to: 30-33
40-55: Well-structuredtoOptionhelper.The function centralizes option creation with proper null checks for missing provider/model. Using
flatMapwith?? []downstream correctly handles theundefinedreturn case.
61-65: Note: Recents are capped at 5 items here but 10 in local.tsx.The
recentListis sliced to 5 items for display (line 64), whileaddRecentinlocal.tsxcaps storage at 10 items. This is likely intentional to show fewer items in the UI while preserving more history, but verify this is the desired behavior.
67-96: Query filtering correctly excludes favorites/recents when no query is active.The logic at line 81 ensures favorites and recents aren't duplicated in
allModelswhen browsing without a query, but are included when searching. This provides a good UX where search results include all matching models.
119-125: Based on my verification, I can now provide the final rewritten review comment.The cast to
ModelRefis unsafe and can receive invalid option types.The
DialogSelectcomponent is instantiated without an explicit type parameter (defaulting tounknown), and theoptions()array combines incompatible option types:
- Model options with
value: ModelRef- Provider options with
value: string(provider ID, added when!connected())When a user presses
ctrl+fon a provider option, the cast bypasses type checking. WhiletoggleFavoriteincludes runtime validation viaisModelValid()that prevents crashes and shows a warning, this defeats compile-time type safety.Suggested fixes:
- Use a discriminated union type to distinguish model vs. provider options
- Add an explicit type guard in the
onTriggerhandler to validate option type before toggling favorite- Filter provider options out of the keybind handler, or only enable the favorite keybind for model options
packages/opencode/src/cli/cmd/tui/context/local.tsx (5)
117-126: Favorite collection correctly added to store.The
favoritearray is properly typed and initialized, following the same structure asrecent. This aligns with theStoragenamespace pattern for data persistence as per coding guidelines.
143-144: Loading handles both arrays with proper type guards.The
Array.isArray()checks ensure backward compatibility with existingmodel.jsonfiles that don't have thefavoritefield.
228-250:cycleFavoriteimplementation looks correct.The method properly:
- Filters invalid models before cycling
- Shows informative toast when no favorites exist
- Handles wrap-around in both directions
- Falls back to first/last item when current model isn't in favorites
One note: cycling to a favorite also adds it to recent (via
{ recent: true }). Verify this is intentional UX behavior.
251-256: Clean extraction ofaddRecentmethod.Extracting the recent-model logic into a dedicated method improves reusability. The deduplication via
uniqueByand cap at 10 items is appropriate.
271-290:toggleFavoritecorrectly validates and persists.The implementation:
- Validates the model exists before toggling
- Properly adds/removes from favorites array
- Persists changes immediately via
save()- Uses
batch()appropriately for atomic store updates
Summary by CodeRabbit
New Features
Configuration
Style
✏️ Tip: You can customize this high-level summary in your review settings.