From 07481f4d2cc6db3554c5d87744d37ca6af523a62 Mon Sep 17 00:00:00 2001 From: Ariane Emory Date: Sun, 14 Dec 2025 17:31:11 -0500 Subject: [PATCH 1/7] Show recent and favorite models in both dedicated sections and provider lists Fixes #3874 by removing filter that excluded recent/favorite models from their provider sections. Now models appear in: - Recent section (if recently used, not a favorite) - Favorites section (if favorited) - Their provider section (e.g., Zen, Anthropic, etc.) Favorited models in provider lists still show (Favorite) indicator. --- .../src/cli/cmd/tui/component/dialog-model.tsx | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-model.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-model.tsx index 38fd5745858..dd03403e438 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-model.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-model.tsx @@ -151,19 +151,7 @@ export function DialogModel(props: { providerID?: string }) { }, } }), - filter((x) => { - if (query) return true - const value = x.value - const inFavorites = favorites.some( - (item) => item.providerID === value.providerID && item.modelID === value.modelID, - ) - if (inFavorites) return false - const inRecents = recents.some( - (item) => item.providerID === value.providerID && item.modelID === value.modelID, - ) - if (inRecents) return false - return true - }), + sortBy( (x) => x.footer !== "Free", (x) => x.title, From 71093cbf700a702223dbebfe96047108cba03cb2 Mon Sep 17 00:00:00 2001 From: Ariane Emory Date: Sun, 14 Dec 2025 17:40:41 -0500 Subject: [PATCH 2/7] Fix double-highlighting for duplicate model entries in picker Added category field to value objects in dialog-model.tsx to make each option unique when same model appears in multiple sections (Recent, Favorites, Provider lists). Only one item highlights now when navigating with keyboard. Changes: - Added category to favoriteOptions value object - Added category to recentOptions value object - Added category to provider list options value object - Updated toggleFavorite keybind to handle new structure --- .../opencode/src/cli/cmd/tui/component/dialog-model.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-model.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-model.tsx index dd03403e438..98bcff9722d 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-model.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-model.tsx @@ -54,6 +54,7 @@ export function DialogModel(props: { providerID?: string }) { value: { providerID: provider.id, modelID: model.id, + category: "Favorites", }, title: model.name ?? item.modelID, description: provider.name, @@ -87,6 +88,7 @@ export function DialogModel(props: { providerID?: string }) { value: { providerID: provider.id, modelID: model.id, + category: "Recent", }, title: model.name ?? item.modelID, description: provider.name, @@ -127,6 +129,7 @@ export function DialogModel(props: { providerID?: string }) { const value = { providerID: provider.id, modelID: model, + category: provider.name, } return { value, @@ -198,7 +201,8 @@ export function DialogModel(props: { providerID?: string }) { title: "Favorite", disabled: !connected(), onTrigger: (option) => { - local.model.toggleFavorite(option.value as { providerID: string; modelID: string }) + const val = option.value as { providerID: string; modelID: string; category: string } + local.model.toggleFavorite({ providerID: val.providerID, modelID: val.modelID }) }, }, ]} From c97077806a40e6e96805fa989bc008a0b158bbd8 Mon Sep 17 00:00:00 2001 From: Ariane Emory Date: Sun, 14 Dec 2025 17:55:09 -0500 Subject: [PATCH 3/7] tidy: whitespace. --- packages/opencode/src/cli/cmd/tui/component/dialog-model.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-model.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-model.tsx index 98bcff9722d..8d8680dcaad 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-model.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-model.tsx @@ -154,7 +154,6 @@ export function DialogModel(props: { providerID?: string }) { }, } }), - sortBy( (x) => x.footer !== "Free", (x) => x.title, From d55ca03376fc41d6ecda1bb8a93660006cba7a66 Mon Sep 17 00:00:00 2001 From: Ariane Emory Date: Tue, 23 Dec 2025 19:38:21 -0500 Subject: [PATCH 4/7] Fix merge conflicts in dialog-model.tsx --- .../cli/cmd/tui/component/dialog-model.tsx | 130 +++--------------- 1 file changed, 21 insertions(+), 109 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-model.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-model.tsx index 240f37e7286..32ee8c694df 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-model.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-model.tsx @@ -166,118 +166,30 @@ export function DialogModel(props: { providerID?: string }) { ), ), ) - }, -<<<<<<< HEAD - }, - ] - }) - : [] - - const recentOptions = !query - ? recentList.flatMap((item) => { - const provider = sync.data.provider.find((x) => x.id === item.providerID) - if (!provider) return [] - const model = provider.models[item.modelID] - if (!model) return [] - return [ - { - key: item, - value: { - providerID: provider.id, - modelID: model.id, - category: "Recent", - }, - title: model.name ?? item.modelID, - description: provider.name, - category: "Recent", - disabled: provider.id === "opencode" && model.id.includes("-nano"), - footer: model.cost?.input === 0 && provider.id === "opencode" ? "Free" : undefined, - onSelect: () => { - dialog.clear() - local.model.set( - { - providerID: provider.id, - modelID: model.id, - }, - { recent: true }, - ) - }, - }, - ] - }) - : [] - return [ - ...favoriteOptions, - ...recentOptions, - ...pipe( - sync.data.provider, - sortBy( - (provider) => provider.id !== "opencode", - (provider) => provider.name, - ), - flatMap((provider) => - pipe( - provider.models, - entries(), - filter(([_, info]) => info.status !== "deprecated"), - filter(([_, info]) => (props.providerID ? info.providerID === props.providerID : true)), - map(([model, info]) => { - const value = { - providerID: provider.id, - modelID: model, - category: provider.name, - } - return { - value, - title: info.name ?? model, - description: favorites.some( - (item) => item.providerID === value.providerID && item.modelID === value.modelID, - ) - ? "(Favorite)" - : undefined, - category: connected() ? provider.name : undefined, - disabled: provider.id === "opencode" && model.includes("-nano"), - footer: info.cost?.input === 0 && provider.id === "opencode" ? "Free" : undefined, - onSelect() { - dialog.clear() - local.model.set( - { - providerID: provider.id, - modelID: model, - }, - { recent: true }, - ) - }, - } - }), - sortBy( - (x) => x.footer !== "Free", - (x) => x.title, - ), -======= + const popularProviders = !connected() + ? pipe( + providers(), + map((option) => { + return { + ...option, + category: "Popular providers", } }), - filter((x) => { - const value = x.value - const inFavorites = favorites.some( - (item) => item.providerID === value.providerID && item.modelID === value.modelID, - ) - if (inFavorites) return false - const inRecents = recents.some( - (item) => item.providerID === value.providerID && item.modelID === value.modelID, - ) - if (inRecents) return false - return true - }), - sortBy( - (x) => x.footer !== "Free", - (x) => x.title, ->>>>>>> dev - ), - ), - ), - ) + take(6), + ) + : [] + + // Apply fuzzy filtering to each section separately, maintaining section order + if (q) { + const filteredFavorites = fuzzysort.go(q, favoriteOptions, { keys: ["title"] }).map((x) => x.obj) + const filteredRecents = fuzzysort.go(q, recentOptions, { keys: ["title"] }).map((x) => x.obj) + const filteredProviders = fuzzysort.go(q, providerOptions, { keys: ["title", "category"] }).map((x) => x.obj) + const filteredPopular = fuzzysort.go(q, popularProviders, { keys: ["title"] }).map((x) => x.obj) + return [...filteredFavorites, ...filteredRecents, ...filteredProviders, ...filteredPopular] + } + + return [...favoriteOptions, ...recentOptions, ...providerOptions, ...popularProviders] const popularProviders = !connected() ? pipe( From d630aa6fd7b197c5c04832daff0fe3ae4766d0b6 Mon Sep 17 00:00:00 2001 From: Ariane Emory Date: Tue, 23 Dec 2025 19:40:43 -0500 Subject: [PATCH 5/7] Fix duplicate code and TypeScript errors in dialog-model.tsx --- .../cli/cmd/tui/component/dialog-model.tsx | 25 ------------------- 1 file changed, 25 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-model.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-model.tsx index 32ee8c694df..83e7116ed5c 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-model.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-model.tsx @@ -67,7 +67,6 @@ export function DialogModel(props: { providerID?: string }) { { providerID: provider.id, modelID: model.id, - category: "Favorites", }, { recent: true }, ) @@ -190,30 +189,6 @@ export function DialogModel(props: { providerID?: string }) { } return [...favoriteOptions, ...recentOptions, ...providerOptions, ...popularProviders] - - const popularProviders = !connected() - ? pipe( - providers(), - map((option) => { - return { - ...option, - category: "Popular providers", - } - }), - take(6), - ) - : [] - - // Apply fuzzy filtering to each section separately, maintaining section order - if (q) { - const filteredFavorites = fuzzysort.go(q, favoriteOptions, { keys: ["title"] }).map((x) => x.obj) - const filteredRecents = fuzzysort.go(q, recentOptions, { keys: ["title"] }).map((x) => x.obj) - const filteredProviders = fuzzysort.go(q, providerOptions, { keys: ["title", "category"] }).map((x) => x.obj) - const filteredPopular = fuzzysort.go(q, popularProviders, { keys: ["title"] }).map((x) => x.obj) - return [...filteredFavorites, ...filteredRecents, ...filteredProviders, ...filteredPopular] - } - - return [...favoriteOptions, ...recentOptions, ...providerOptions, ...popularProviders] }) const provider = createMemo(() => From eb91f959163a2cdf5d25e3b98ee9cef5b6539bc6 Mon Sep 17 00:00:00 2001 From: Ariane Emory Date: Mon, 29 Dec 2025 13:54:26 -0500 Subject: [PATCH 6/7] Fix Link component to use TextAttributes.UNDERLINE instead of underline prop --- packages/opencode/src/cli/cmd/tui/ui/link.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/cli/cmd/tui/ui/link.tsx b/packages/opencode/src/cli/cmd/tui/ui/link.tsx index 1f798c54cca..cc3938796cf 100644 --- a/packages/opencode/src/cli/cmd/tui/ui/link.tsx +++ b/packages/opencode/src/cli/cmd/tui/ui/link.tsx @@ -1,5 +1,6 @@ import type { JSX } from "solid-js" import type { RGBA } from "@opentui/core" +import { TextAttributes } from "@opentui/core" import open from "open" export interface LinkProps { @@ -18,7 +19,7 @@ export function Link(props: LinkProps) { return ( { open(props.href).catch(() => {}) }} From dc812c33eb45b43580d3f5f6c2e1eb2a16e15c9f Mon Sep 17 00:00:00 2001 From: Ariane Emory Date: Mon, 29 Dec 2025 13:57:11 -0500 Subject: [PATCH 7/7] Revert "Fix Link component to use TextAttributes.UNDERLINE instead of underline prop" This reverts commit eb91f959163a2cdf5d25e3b98ee9cef5b6539bc6. --- packages/opencode/src/cli/cmd/tui/ui/link.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/ui/link.tsx b/packages/opencode/src/cli/cmd/tui/ui/link.tsx index cc3938796cf..1f798c54cca 100644 --- a/packages/opencode/src/cli/cmd/tui/ui/link.tsx +++ b/packages/opencode/src/cli/cmd/tui/ui/link.tsx @@ -1,6 +1,5 @@ import type { JSX } from "solid-js" import type { RGBA } from "@opentui/core" -import { TextAttributes } from "@opentui/core" import open from "open" export interface LinkProps { @@ -19,7 +18,7 @@ export function Link(props: LinkProps) { return ( { open(props.href).catch(() => {}) }}