Skip to content

feat: make /models searchable#642

Closed
brrock wants to merge 11 commits intoPiebald-AI:mainfrom
brrock:feat/model-search
Closed

feat: make /models searchable#642
brrock wants to merge 11 commits intoPiebald-AI:mainfrom
brrock:feat/model-search

Conversation

@brrock
Copy link
Copy Markdown
Contributor

@brrock brrock commented Mar 29, 2026

Quite a big pr makes /models fuzzy searchable, with a nice search box. Is useful espically as we add some models. And I plan to make other patches which add more models, and this will become even more useful.
Usage video:

Screen.Recording.2026-03-29.at.15.11.45.mov

Summary by CodeRabbit

  • New Features

    • Searchable model selector UI with input, fuzzy filtering, scoring, and improved keyboard interaction; enabled by default for supported bundle shape.
  • Settings

    • New misc toggle to enable/disable the searchable model selector (defaults ON); config parsing backfills and preserves this value.
  • Tests

    • Added tests covering the searchable picker, writer behavior, toggle-driven scenarios, and error/unsupported cases.
  • Documentation

    • README and changelog updated with feature entry, usage, and toggle location.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 29, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a searchable /model picker: new misc config flag and UI toggle, a patch writer that injects search state/filtering into Claude Code 2.1.86 model-picker bundles, registers the patch with gating, updates types/defaults, and adds tests, docs, and changelog entries.

Changes

Cohort / File(s) Summary
Configuration & Types
src/defaultSettings.ts, src/types.ts
Added misc.enableModelSelectorSearch defaulting to true and MiscConfig.enableModelSelectorSearch: boolean.
Patch Registry
src/patches/index.ts
Registered new patch search-model-selector, added gate modelSelectorSearchEnabled (from settings) and import for the writer; patch requires model-customizations and the new gate.
Patch Implementation
src/patches/modelSelectorSearch.ts
New exported `writeModelSelectorSearch(oldFile: string): string
Tests
src/patches/modelSelectorSearch.test.ts, src/patches/modelCustomizationsToggle.test.ts, src/tests/config.test.ts
Added unit tests for writer behavior on Claude Code 2.1.86 and unsupported shapes; extended toggle tests to include search-model-selector gating; added config parsing tests for default/preserved enableModelSelectorSearch.
UI
src/ui/components/MiscView.tsx
Added "Searchable model selector" toggle bound to settings.misc?.enableModelSelectorSearch ?? true with write-back via ensureMisc().
Docs & Changelog
README.md, CHANGELOG.md
Documented the searchable /model picker feature (enabled by default, scoped to Claude Code 2.1.86), added README section and changelog entry.

Sequence Diagram

sequenceDiagram
    participant User as User
    participant UI as MiscView
    participant Config as Config System
    participant Registry as Patch Registry
    participant Writer as writeModelSelectorSearch
    participant File as Model Picker File

    User->>UI: toggle enableModelSelectorSearch
    UI->>Config: update settings.misc.enableModelSelectorSearch
    Config->>Registry: load settings
    Registry->>Registry: evaluate gates (modelCustomizationsEnabled, modelSelectorSearchEnabled)
    alt search enabled & customizations enabled
        Registry->>Writer: call writeModelSelectorSearch(oldFile)
        Writer->>Writer: perform sequential replacements (state, UI, filtering, scoring, windowing)
        Writer->>Registry: return transformed file
        Registry->>File: install patched file
    else skip
        Registry->>Registry: skip search-model-selector patch
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • bl-ue
  • georpar

Poem

🐰 I nudged a tiny searchbox bright,
Hopped through snippets, patched the sight,
Flags set true, the tests took flight,
Models found by morning light,
A happy rabbit coded right.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding search functionality to the /models feature, which is the primary objective across all modified files.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@brrock
Copy link
Copy Markdown
Contributor Author

brrock commented Mar 29, 2026

All fixed

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
README.md (1)

885-897: Consider clarifying that these flags are already enabled by default.
The config.json snippet is useful, but adding a short “defaults are true” note would avoid implying this is required for baseline behavior.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` around lines 885 - 897, The README snippet for config.json omits
that the flags enableModelCustomizations and enableModelSelectorSearch are true
by default; update the documentation near the JSON example to add a short note
stating that these two settings default to true so users know adding the snippet
is optional for baseline behavior, and reference the specific keys
("enableModelCustomizations" and "enableModelSelectorSearch") so readers can
locate them quickly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@README.md`:
- Line 876: The heading "#### Configuration" should be changed to "###
Configuration" to repair the heading-level jump and satisfy MD001; update the
README heading token (replace '#### Configuration' with '### Configuration') so
it sits correctly under the surrounding '##' feature section and preserves the
document hierarchy.

---

Nitpick comments:
In `@README.md`:
- Around line 885-897: The README snippet for config.json omits that the flags
enableModelCustomizations and enableModelSelectorSearch are true by default;
update the documentation near the JSON example to add a short note stating that
these two settings default to true so users know adding the snippet is optional
for baseline behavior, and reference the specific keys
("enableModelCustomizations" and "enableModelSelectorSearch") so readers can
locate them quickly.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d92facb3-f9be-4bdd-afd9-111e94836351

📥 Commits

Reviewing files that changed from the base of the PR and between ba3fdca and 83fe334.

⛔ Files ignored due to path filters (1)
  • assets/modelSel.png is excluded by !**/*.png
📒 Files selected for processing (3)
  • README.md
  • src/patches/modelSelectorSearch.test.ts
  • src/patches/modelSelectorSearch.ts
✅ Files skipped from review due to trivial changes (1)
  • src/patches/modelSelectorSearch.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/patches/modelSelectorSearch.ts

@brrock
Copy link
Copy Markdown
Contributor Author

brrock commented Mar 29, 2026

Before merging, I have a few things left :

  • remove backwards compatibility, it is pain
  • can you clarify if you want this enabled by default, I think it is sensible because we add a ton of models

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@CHANGELOG.md`:
- Line 10: Fix the spelling in the changelog entry by replacing "functonalilty"
with "functionality" in the line that currently reads "- Add search box
functonalilty to /models with new patch (`#462`) - `@brrock`"; also confirm whether
the PR reference "#462" is correct for this change (update to the current PR
number or leave as-is if it intentionally points to the original PR) and adjust
the trailing attribution "@brrock" only if the author handle needs correction.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4f898243-9d5d-4d0f-84ee-635354877b9c

📥 Commits

Reviewing files that changed from the base of the PR and between 83fe334 and 094833e.

📒 Files selected for processing (1)
  • CHANGELOG.md

brrock and others added 3 commits March 29, 2026 15:57
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@brrock
Copy link
Copy Markdown
Contributor Author

brrock commented Mar 29, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 29, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

2 similar comments
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 29, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 29, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
README.md (1)

878-878: ⚠️ Potential issue | 🟡 Minor

Heading level still skips from ## to ####.

Line 878 is still a two-level jump, so markdownlint MD001 will keep flagging this until #### Configuration becomes ### Configuration.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` at line 878, The README has a skipped heading level: change the
heading text '#### Configuration' to '### Configuration' so the heading sequence
no longer jumps from '##' to '####' and resolves markdownlint MD001; update the
single heading token in the file (the line currently containing '####
Configuration') to use three hashes instead of four.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/patches/modelSelectorSearch.ts`:
- Around line 5-24: The function replaceLiteralOnce currently replaces the first
match even if the oldSnippet appears multiple times; update replaceLiteralOnce
to verify the snippet is unique before patching: search for oldSnippet
occurrences (e.g., compare file.indexOf(oldSnippet) and
file.lastIndexOf(oldSnippet) or count occurrences) and if there are zero or more
than one matches, log/return the provided errorMessage and do not modify the
file; only when there is exactly one match proceed to compute endIndex, create
newFile, call showDiff(file, newFile, newSnippet, startIndex, endIndex) and
return newFile. Ensure the uniqueness check references the parameters file and
oldSnippet and preserves the existing signature and behavior on success.

---

Duplicate comments:
In `@README.md`:
- Line 878: The README has a skipped heading level: change the heading text
'#### Configuration' to '### Configuration' so the heading sequence no longer
jumps from '##' to '####' and resolves markdownlint MD001; update the single
heading token in the file (the line currently containing '#### Configuration')
to use three hashes instead of four.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d35b4dd2-faf1-470c-a082-55bbac2ee018

📥 Commits

Reviewing files that changed from the base of the PR and between 094833e and 3d2a699.

📒 Files selected for processing (3)
  • README.md
  • src/patches/modelSelectorSearch.test.ts
  • src/patches/modelSelectorSearch.ts
✅ Files skipped from review due to trivial changes (1)
  • src/patches/modelSelectorSearch.test.ts

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@brrock
Copy link
Copy Markdown
Contributor Author

brrock commented Mar 29, 2026

can i get a review on this @bl-ue

Comment on lines +71 to +147
const MODEL_PICKER_2186_CALLSITE =
'let E6=A??SYz,T6;if(K[49]!==J6||K[50]!==a||K[51]!==C||K[52]!==X||K[53]!==p||K[54]!==E6||K[55]!==F)T6=zK.createElement(m,{flexDirection:"column"},zK.createElement(J1,{defaultValue:X,defaultFocusValue:C,options:p,onChange:a,onFocus:J6,onCancel:E6,visibleOptionCount:F})),K[49]=J6,K[50]=a,K[51]=C,K[52]=X,K[53]=p,K[54]=E6,K[55]=F,K[56]=T6;else T6=K[56];let R6;if(K[57]!==g)R6=g>0&&zK.createElement(m,{paddingLeft:3},zK.createElement(v,{dimColor:!0},"and ",g," more…")),K[57]=g,K[58]=R6;else R6=K[58];let y6;if(K[59]!==T6||K[60]!==R6)y6=zK.createElement(m,{flexDirection:"column",marginBottom:1},T6,R6),K[59]=T6,K[60]=R6,K[61]=y6;else y6=K[61];';
const MODEL_PICKER_2187_CALLSITE =
'let E6=A??hYz,T6;if(K[49]!==J6||K[50]!==a||K[51]!==C||K[52]!==X||K[53]!==p||K[54]!==E6||K[55]!==F)T6=zK.createElement(m,{flexDirection:"column"},zK.createElement(J1,{defaultValue:X,defaultFocusValue:C,options:p,onChange:a,onFocus:J6,onCancel:E6,visibleOptionCount:F})),K[49]=J6,K[50]=a,K[51]=C,K[52]=X,K[53]=p,K[54]=E6,K[55]=F,K[56]=T6;else T6=K[56];let R6;if(K[57]!==g)R6=g>0&&zK.createElement(m,{paddingLeft:3},zK.createElement(v,{dimColor:!0},"and ",g," more…")),K[57]=g,K[58]=R6;else R6=K[58];let y6;if(K[59]!==T6||K[60]!==R6)y6=zK.createElement(m,{flexDirection:"column",marginBottom:1},T6,R6),K[59]=T6,K[60]=R6,K[61]=y6;else y6=K[61];';

const isSupportedModelPickerVersion = (file: string): boolean =>
file.includes(MODEL_PICKER_2186_CALLSITE) ||
file.includes(MODEL_PICKER_2187_CALLSITE);

const addSearchStateToModelPicker = (file: string): string | null =>
replaceAnyLiteralOnce(
file,
[
{
oldSnippet: '[W,Z]=SB8.useState(!1),f=M8(bYz),G;if(',
newSnippet:
'[W,Z]=SB8.useState(!1),[L7,b7]=SB8.useState(""),f=M8(bYz),G;if(',
},
{
oldSnippet: '[W,Z]=SB8.useState(!1),f=M8(CYz),G;if(',
newSnippet:
'[W,Z]=SB8.useState(!1),[L7,b7]=SB8.useState(""),f=M8(CYz),G;if(',
},
],
'patch: modelSelectorSearch: failed to add model picker search state'
);

const addSearchUiToModelPicker = (file: string): string | null => {
let nextFile = replaceAnyLiteralOnce(
file,
[
{
oldSnippet: MODEL_PICKER_2186_CALLSITE,
newSnippet:
'let E6=A??SYz,T6=zK.createElement(m,{flexDirection:"column"},p.length===0?zK.createElement(v,{dimColor:!0,italic:!0},"No models match \u201c",L7,"\u201d"):zK.createElement(J1,{defaultValue:X,defaultFocusValue:C,options:p,onChange:a,onFocus:J6,onCancel:E6,visibleOptionCount:F,highlightText:L7}));let R6=g>0&&zK.createElement(m,{paddingLeft:3},zK.createElement(v,{dimColor:!0},"and ",g," more…"));let y6=zK.createElement(m,{flexDirection:"column",marginBottom:1},T6,R6);let n6=zK.createElement(m,{marginTop:1,width:"100%",minWidth:48,flexGrow:1,borderStyle:"round",borderColor:"suggestion",paddingX:1,flexDirection:"row"},zK.createElement(v,{dimColor:!0},"\u2315 "),zK.createElement(m,{flexGrow:1},zK.createElement(x3,{value:L7,onChange:b7,onSubmit:()=>{if(C!==void 0)a(C)},onExit:E6,placeholder:"Search models..."+" ".repeat(48),focus:!0,showCursor:!0,multiline:!1,cursorOffset:L7.length,onChangeCursorOffset:()=>{},columns:Math.max(42,(process.stdout.columns||80)-10)})));',
},
{
oldSnippet: MODEL_PICKER_2187_CALLSITE,
newSnippet:
'let E6=A??hYz,T6=zK.createElement(m,{flexDirection:"column"},p.length===0?zK.createElement(v,{dimColor:!0,italic:!0},"No models match \u201c",L7,"\u201d"):zK.createElement(J1,{defaultValue:X,defaultFocusValue:C,options:p,onChange:a,onFocus:J6,onCancel:E6,visibleOptionCount:F,highlightText:L7}));let R6=g>0&&zK.createElement(m,{paddingLeft:3},zK.createElement(v,{dimColor:!0},"and ",g," more…"));let y6=zK.createElement(m,{flexDirection:"column",marginBottom:1},T6,R6);let n6=zK.createElement(m,{marginTop:1,width:"100%",minWidth:48,flexGrow:1,borderStyle:"round",borderColor:"suggestion",paddingX:1,flexDirection:"row"},zK.createElement(v,{dimColor:!0},"\u2315 "),zK.createElement(m,{flexGrow:1},zK.createElement(x3,{value:L7,onChange:b7,onSubmit:()=>{if(C!==void 0)a(C)},onExit:E6,placeholder:"Search models..."+" ".repeat(48),focus:!0,showCursor:!0,multiline:!1,cursorOffset:L7.length,onChangeCursorOffset:()=>{},columns:Math.max(42,(process.stdout.columns||80)-10)})));',
},
],
'patch: modelSelectorSearch: failed to update model picker J1 callsite'
);

if (!nextFile) {
return null;
}

nextFile = replaceLiteralOnce(
nextFile,
'let K8;if(K[69]!==f6||K[70]!==y6||K[71]!==S6||K[72]!==s6)K8=zK.createElement(m,{flexDirection:"column"},f6,y6,S6,s6),K[69]=f6,K[70]=y6,K[71]=S6,K[72]=s6,K[73]=K8;else K8=K[73];',
'let K8=zK.createElement(m,{flexDirection:"column",width:"100%"},f6,y6,S6,s6,n6);',
'patch: modelSelectorSearch: failed to add model picker search box'
);

return nextFile;
};

const addFuzzyFilteringToModelPicker = (file: string): string | null => {
let nextFile = replaceLiteralOnce(
file,
'let p=I,B;if(K[14]!==X||K[15]!==p)B=p.some((A6)=>A6.value===X)?X:p[0]?.value??void 0,K[14]=X,K[15]=p,K[16]=B;else B=K[16];',
'let p=L7.trim()?I.map((A6)=>{let y6=L7.toLowerCase().trim(),c6=y6.replace(/[^a-z0-9]/g,""),g6=`${typeof A6.label==="string"?A6.label:""} ${A6.value??""}`.toLowerCase(),F6=g6.replace(/[^a-z0-9]/g,""),B6=`${typeof A6.label==="string"?A6.label:""} ${A6.description??""} ${A6.value??""}`.toLowerCase().replace(/[^a-z0-9]/g,"");if(c6==="")return{option:A6,score:3};let N6;if(F6===c6)N6=0;else if(F6.includes(c6))N6=1;else if(B6.includes(c6))N6=2;else{let e6=0;for(let t6 of c6){e6=B6.indexOf(t6,e6);if(e6===-1)return null;e6++;}N6=3}return{option:A6,score:N6};}).filter((A6)=>A6!==null).sort((A6,y6)=>A6.score-y6.score).map((A6)=>A6.option):I,B=L7.trim()?p[0]?.value??void 0:p.some((A6)=>A6.value===X)?X:p[0]?.value??void 0;',
'patch: modelSelectorSearch: failed to add fuzzy filtering'
);

if (!nextFile) {
return null;
}

nextFile = replaceLiteralOnce(
nextFile,
'let C=B,F=Math.min(10,p.length),g=Math.max(0,p.length-F),Q;',
'let C=B,F=Math.min(25,Math.max(1,p.length)),g=Math.max(0,p.length-F),Q;',
'patch: modelSelectorSearch: failed to update filtered visible count'
);
Copy link
Copy Markdown
Member

@mike1858 mike1858 Mar 29, 2026

Choose a reason for hiding this comment

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

Oops, this'll never carry across versions. See the note in ./index 😄

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Working on way to make it compat for all

@brrock
Copy link
Copy Markdown
Contributor Author

brrock commented Mar 31, 2026

Btw I'm working on the Claude code leaked code base so haven't found time. I think it is a great resource for this repo and I'm releasing a fork soon

@brrock brrock closed this Apr 3, 2026
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