Skip to content

Rank visible title matches above hidden metadata in workspace switcher - #5148

Merged
lawrencecchen merged 7 commits into
mainfrom
task-switcher-title-rank
Jun 2, 2026
Merged

lawrencecchen merged 7 commits into
mainfrom
task-switcher-title-rank

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Jun 1, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • The workspace switcher ranked a row whose visible title did not match the query above a clean title prefix. Typing "ios" put "fork session not found" (matched only on a hidden branch/description token "ios", no highlighted title) above "iOS Mobile Terminal".
  • Root cause is the Rust nucleo scorer (Native/CommandPaletteNucleoFFI/src/lib.rs). For a short query, an exact match on any hidden search-field line scored 30,030, while a visible title prefix only reached nucleo(~88) + 2,000, so the hidden match always won.
  • Added a whole-query title-literal tier layered into weighted_query_score: a title equal to the query scores 34,000 and a title prefix scores a flat 32,000, both above keyword-exact's 30,030. A workspace whose visible title starts with the query now wins. Per-token and multi-token sums are untouched; this only adds a max() term when the whole query is a title prefix. This matches the Swift CommandPaletteSearchEngine reference ordering (title prefix > exact keyword), so the two engines now agree.
  • Prefix matches share one flat score on purpose. A per-length penalty would invent a "shortest title wins" rule that mis-ranks when one title is a character-prefix of another (e.g. "workspace 1901" preferring "Workspace 19010 ..." over "Workspace 1901 ..."). Ties defer to the existing (rank, index) order, preserving the switcher's natural order.

Testing

  • cargo build --release (offline) clean, no warnings.
  • New regression test testNucleoFFIPrefersVisibleTitlePrefixOverHiddenMetadataKeyword in cmuxTests/CommandPaletteNucleoFFITests.swift, run by the CI tests job (which builds the dylib). Two-commit red/green: the test lands first, the scoring fix second.
  • Replayed the exact existing CommandPaletteNucleoFFITests fixtures (open-folder top-2, initialism, café accent, deep-workspace numeric prefix) through the real scorer to confirm no ordering regressions; the deep-workspace replay is what caught and killed the length-penalty mis-ranking.

Notes

  • "fork session not found" still appears at Disambiguate Sentry environment between production and dev #2: its hidden exact-keyword match outranks scattered title-fuzzy rows, matching the Swift reference. If you want every visible-title match to rank above every hidden-metadata match, that is a larger change to both engines; say the word.

Issues

  • Reported via screenshot (no GitHub issue).

Summary by CodeRabbit

  • New Features

    • Search ranking now favors visible titles that exactly match or start with your trimmed, case-insensitive query (including diacritic-normalized matches), allowing whole-title exact/prefix matches to outrank token-only or metadata matches.
  • Tests

    • Added regression tests ensuring single- and multi-token queries prefer visible title matches over hidden-metadata-only matches while still including metadata-only results.

Note

Medium Risk
Changes command-palette/workspace switcher result ordering in production scoring; well-tested but affects core UX for all palette searches.

Overview
Fixes workspace/command-palette ranking where hidden metadata (branch/description tokens) could beat a visible title that actually matches what the user typed—e.g. query ios putting a row with only a hidden ios keyword above iOS Mobile Terminal.

The Rust nucleo FFI scorer adds a whole-query title literal tier: case-insensitive exact or prefix match on the candidate title (via Atom + Smart diacritic folding), combined with existing token scores as max(token, literal). Literal tiers sit above the short-query keyword-exact jackpot (~30_030) and scale by query token count so multi-token queries like ios app still lose to summed hidden keyword lines.

Prefix matches use a flat score (no length penalty) so numeric/workspace titles don’t mis-order; ties still use existing rank/index ordering. Swift FFI tests cover single-token, multi-token, and diacritic regressions.

Reviewed by Cursor Bugbot for commit d4718dc. Bugbot is set up for automated code reviews on this repo. Configure here.

lawrencecchen and others added 2 commits June 1, 2026 16:41
…word

Query "ios" should rank a workspace titled "iOS Mobile Terminal" above one
that only matched a hidden branch/description token "ios". The nucleo scorer
ranks the hidden exact-keyword match first today, so this test is red until
the scoring fix lands.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… scorer

A short query that exactly matched a hidden search-field line (branch,
directory, or description token) scored 30_030 and beat a visible title prefix,
which only reached nucleo(~88) + 2_000. In the workspace switcher this put a row
with no highlighted title at the top for queries like "ios".

Add a whole-query title-literal tier (exact 34_000, prefix 32_000, floored at
31_000 so even a long prefix clears keyword-exact's 30_030) layered into
weighted_query_score, so a workspace whose visible title starts with the query
wins. Per-token and multi-token sums are untouched; this only adds a max() term
when the whole query is a title prefix. Matches the Swift
CommandPaletteSearchEngine reference ordering (title prefix > exact keyword).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 1, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment Jun 2, 2026 12:53am
cmux-staging Building Building Preview, Comment Jun 2, 2026 12:53am

@coderabbitai

coderabbitai Bot commented Jun 1, 2026 •

Copy link
Copy Markdown

Review Change Stack

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 exact-and-prefix whole-query title literal scoring (precompiled Atoms), integrates that tier into weighted query scoring (selecting the higher available token-based or title-literal score), and adds three regression tests asserting visible-title-prefix matches outrank metadata-only matches (including diacritic normalization).

Changes

Title Literal Scoring for Command Palette Ranking

Layer / File(s) Summary
Imports and TitleLiteralQuery
Native/CommandPaletteNucleoFFI/src/lib.rs
Adds Atom-related imports and TitleLiteralQuery which precompiles case-insensitive Smart-normalized exact and prefix Atoms and records query token count.
SearchState helper & scorer integration
Native/CommandPaletteNucleoFFI/src/lib.rs
Adds SearchState::atom_score and updates weighted_query_score to accept TitleLiteralQuery, compute token-derived and title-literal scores, and return the higher available score with explicit Some/None handling.
Title literal scoring constants & implementation
Native/CommandPaletteNucleoFFI/src/lib.rs
Introduces keyword-derived scoring constants and implements title_literal_score which checks exact then prefix Atom matches and returns token-count-scaled tier scores or None.
Title prefix & diacritic regression tests
cmuxTests/CommandPaletteNucleoFFITests.swift
Adds three tests that build two-entry NucleoIndex fixtures and assert visible-title-prefix (including diacritic-normalized) matches rank above metadata-only matches for queries "ios", "ios app", and "e".
sequenceDiagram
  participant Client
  participant WeightedScorer
  participant TokenScorer
  participant TitleLiteralScorer
  Client->>WeightedScorer: search(query, candidate)
  WeightedScorer->>TokenScorer: compute token_sum_score
  WeightedScorer->>TitleLiteralScorer: compute title_literal_score
  TokenScorer-->>WeightedScorer: token score (Some/None)
  TitleLiteralScorer-->>WeightedScorer: title score (Some/None)
  WeightedScorer->>WeightedScorer: select higher available score
  WeightedScorer-->>Client: return ranked score
Loading

🎯 3 (Moderate) | ⏱️ ~20 minutes

A rabbit peers at titles, hopping through the list,
Exact matches gleam and prefixes can't be missed,
Hidden tags still whisper, but the title leads the chase,
Search hops to order, with newfound gentle grace. 🐇


Caution

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

  • Ignore

❌ Failed checks (1 error)

Check name Status Explanation Resolution
Cmux Swiftui State Layout ❌ Error New file CommandPaletteOverlay.swift uses Task { @MainActor } to schedule state mutations called from .onChange() handlers in ContentView, violating the rule against render-time state writes. Move state mutations to explicit lifecycle callbacks or event handlers outside of .onChange context; or refactor to ensure mutations happen outside the render phase without Task scheduling from view modifiers.
✅ Passed checks (17 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.
Cmux Swift Actor Isolation ✅ Passed PR contains only Rust code and test file changes; no production Swift code was modified. Test file changes are exempt from Swift actor isolation checks per the rule ("Pass for tests").
Cmux Swift Blocking Runtime ✅ Passed No production Swift changes detected; only Rust and test-only scaffolding. Test methods contain no blocking synchronization patterns.
Cmux No Hacky Sleeps ✅ Passed PR changes only Rust native code and Swift test files; the "cmux no hacky sleeps" check applies only to TypeScript, JavaScript, shell, and non-Swift build/runtime scripts, explicitly excluding Swift.
Cmux Algorithmic Complexity ✅ Passed PR adds 2 atom_score calls per candidate with precompiled query; no nested loops, rescans, or repeated operations; valid linear-time change to 1000-candidate search.
Cmux Swift Concurrency ✅ Passed Three new XCTest methods added contain only synchronous code with no legacy async patterns (DispatchQueue, DispatchGroup, Combine, completion handlers).
Cmux Swift @Concurrent ✅ Passed All three new test methods are synchronous with no async/await or @concurrent annotations, following standard XCTest patterns with no concurrent annotation violations.
Cmux Swift File And Package Boundaries ✅ Passed PR only changes Rust code and test file additions (96 lines of regression tests in cmuxTests). No production Swift files modified. Test code is explicitly allowed per rules as test fixtures.
Cmux Swift Logging ✅ Passed PR adds only Rust code and test code. Swift logging rules explicitly allow logging in tests. The 3 new test methods added contain no logging violations.
Cmux User-Facing Error Privacy ✅ Passed Internal Rust library code and unit tests only; no user-facing errors, alerts, or prohibited content found.
Cmux Full Internationalization ✅ Passed PR modifies only backend algorithm code and test fixtures, both exempt from internationalization per rules. Test fixtures use plain English like existing tests; no user-facing strings added.
Cmux Architecture Rethink ✅ Passed PR adds FFI bridge and test regressions with no timing, locks, observers, or lifecycle issues. Required platform bridge with documented safety rationale per allowed cases.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed Rust FFI backend code and test-only Swift regression tests; no window declarations added. Test-only fixtures explicitly allowed by rule.
Title check ✅ Passed The title clearly and concisely summarizes the main change: improving result ranking to prioritize visible title matches over hidden metadata in the workspace switcher.
Description check ✅ Passed The description covers all required template sections: Summary explains what changed and why, Testing details verification approach, and a Checklist is included with completed items.
✨ 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 task-switcher-title-rank

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.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d337bb97ca

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

}

fn chars_equal_ignoring_case(lhs: char, rhs: char) -> bool {
lhs == rhs || lhs.to_ascii_lowercase() == rhs.to_ascii_lowercase()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Handle Unicode case in title prefixes

When the visible title prefix differs from the query only by non-ASCII case, this comparator returns false because to_ascii_lowercase() leaves characters like É unchanged. In that scenario (for example title Éclair with query é, while another row has hidden metadata line é) the new literal-title tier is not applied, so the hidden short exact match still gets the 30,010 keyword score and can remain above the visible title prefix this change is meant to prefer. Use Unicode-aware case folding here, or normalize title/query the same way the Swift search path does, so localized workspace titles get the same ranking fix.

Useful? React with 👍 / 👎.

@greptile-apps

greptile-apps Bot commented Jun 1, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

Fixes a scoring bug in the Rust nucleo FFI scorer where hidden metadata exact-keyword matches (scoring up to 30,030 per token) outranked visible title prefix matches, causing e.g. "Fork Session Not Found" (matched only on a hidden "ios" branch token) to appear above "iOS Mobile Terminal" for query "ios". The fix layers a whole-query title-literal tier into weighted_query_score by taking the max of the per-token fuzzy/keyword path and a new title_literal_score that scores above the keyword ceiling.

  • A new TitleLiteralQuery struct (built once per search) holds an Atom for each of two tiers — exact (34,030 × token count) and prefix (32,030 × token count) — and title_literal_score awards the appropriate tier when the whole query matches the candidate title; three Swift regression tests are added covering single-token, multi-token, and diacritic cases.
  • The exact atom in TitleLiteralQuery uses AtomKind::Exact, which in nucleo is a substring match (fzf's 'foo operator), not full-string equality; this makes every prefix match also a substring match, so the prefix atom branch in title_literal_score is permanently unreachable and TITLE_PREFIX_TOKEN_SCORE (32,030) is dead code — the intended design likely requires AtomKind::Equal for the top tier.

Confidence Score: 4/5

Safe to merge for the reported bug fix, but the two-tier title scoring design is not implemented as described — the lower prefix tier is dead code.

The reported ranking inversion (hidden metadata beating a visible title prefix) is correctly fixed: for the 'ios' / 'iOS Mobile Terminal' case, the title now scores 34,030 vs the hidden keyword's 30,030. However, AtomKind::Exact is a substring matcher in nucleo (fzf's 'foo), not a full-string equality matcher. This means the prefix branch in title_literal_score is permanently unreachable code, and any title that merely contains the query anywhere receives the highest tier (34,030) rather than only titles that start with or equal the query. The TITLE_PREFIX_TOKEN_SCORE constant serves no purpose. The fix is a one-word change (Exact → Equal), and the existing tests would still pass but would then also exercise the previously dead prefix tier.

Native/CommandPaletteNucleoFFI/src/lib.rs — specifically the TitleLiteralQuery::new constructor and title_literal_score function.

Important Files Changed

Filename Overview
Native/CommandPaletteNucleoFFI/src/lib.rs Adds TitleLiteralQuery + title_literal_score to fix visible-title ranking, but AtomKind::Exact is a substring matcher so TITLE_PREFIX_TOKEN_SCORE (32k) is dead code — any prefix match also fires the exact (substring) check first, giving 34k to all titles that contain the query anywhere.
cmuxTests/CommandPaletteNucleoFFITests.swift Adds three regression tests covering single-token, multi-token, and diacritic title-prefix-over-hidden-metadata ordering; tests correctly verify observable ranking outcomes but do not distinguish between the 34k and 32k tiers so they pass regardless of the AtomKind::Exact vs AtomKind::Equal issue.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Search query arrives] --> B[Normalize query]
    B --> C[Build search_tokens per word]
    B --> D[Build TitleLiteralQuery\nexact + prefix Atom]
    C --> E[token_sum_score\nper-token fuzzy + keyword path]
    D --> F[title_literal_score\ncheck whole query vs title]
    E --> G{token result}
    F --> H{literal result}
    G -->|Some score| I[max of both]
    H -->|Some score| I
    G -->|None| J{literal result?}
    H -->|None| K[token result only]
    J -->|Some literal| L[literal score only]
    J -->|None| M[candidate excluded]
    I --> N[ScoredCandidate appended to BinaryHeap]
    L --> N
    K --> N

    subgraph title_literal_score
        F --> F1{Atom::Exact fires?\nquery is substring of title}
        F1 -->|Yes| F2[TITLE_EXACT_TOKEN_SCORE\n34030 x token_count]
        F1 -->|No| F3{Atom::Prefix fires?\ntitle starts with query}
        F3 -->|Yes| F4[TITLE_PREFIX_TOKEN_SCORE\n32030 x token_count\nCurrently unreachable]
        F3 -->|No| F5[None]
    end
Loading

Reviews (5): Last reviewed commit: "Match title-literal tier with matcher no..." | Re-trigger Greptile

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

🤖 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 `@cmuxTests/CommandPaletteNucleoFFITests.swift`:
- Line 111: Update the inline comment that references the placeholder PR URL
"pull/0000" to the real PR number by replacing "pull/0000" with "pull/5148" in
the comment inside CommandPaletteNucleoFFITests.swift (the line that currently
reads "// no highlighted title yet sat at the top.
https://github.com/manaflow-ai/cmux/pull/0000").
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 26fe426f-b150-45e6-970d-b81c8114be1e

📥 Commits

Reviewing files that changed from the base of the PR and between 8ba0ee5 and d337bb9.

📒 Files selected for processing (2)
  • Native/CommandPaletteNucleoFFI/src/lib.rs
  • cmuxTests/CommandPaletteNucleoFFITests.swift

Comment thread cmuxTests/CommandPaletteNucleoFFITests.swift Outdated

@cubic-dev-ai cubic-dev-ai 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.

1 issue found across 2 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="Native/CommandPaletteNucleoFFI/src/lib.rs">

<violation number="1" location="Native/CommandPaletteNucleoFFI/src/lib.rs:547">
P2: Unicode titles lose the new literal-prefix boost because case matching is ASCII-only (`to_ascii_lowercase`).</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread Native/CommandPaletteNucleoFFI/src/lib.rs Outdated
The earlier per-extra-character penalty made "shortest title wins" among prefix
matches, which mis-ranks when one title is a character-prefix of another: query
"workspace 1901" preferred "Workspace 19010 ..." over "Workspace 1901 ...".
Verified against the real CommandPaletteNucleoFFITests fixtures (open-folder
top-2, initialism, cafe, deep-workspace) replayed through the actual scorer.

Give every title prefix the same 32_000 and let scored_candidate_order's
(rank, index) tiebreak decide, preserving the switcher's natural order. Exact
title (34_000) still beats prefix.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b0f4fd5a07

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// jackpot in `exact_search_text_line_score`. `title_literal_score` sits above every keyword
// tier so the visible title wins. This mirrors the Swift `CommandPaletteSearchEngine`
// reference ordering (title prefix > exact keyword).
match (token_score, title_literal_score(candidate, query)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve title-prefix priority for short multi-token queries

For multi-word queries made of short tokens, this max still lets hidden metadata win because token_sum_score adds the 30,030 exact-line jackpot once per token before comparing with the flat 32,000 title-prefix tier. In the workspace switcher, description metadata is indexed both as the original text and as component tokens, so a row whose hidden description contributes lines like ios and dev scores about 60,060 for query ios dev and can still rank above a visible title prefix such as iOS Dev Tools, leaving the same “top row has no highlighted title” failure for two short words.

Useful? React with 👍 / 👎.

@cubic-dev-ai cubic-dev-ai 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.

1 issue found across 1 file (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread Native/CommandPaletteNucleoFFI/src/lib.rs Outdated
Codex review caught that the fix only handled single-token queries. The per-token
keyword path is summed in weighted_query_score, so a multi-token query like
"ios app" lets a hidden row score an exact line per token (~30_030 each, ~60_060
summed), which beat a flat title-literal score and reproduced the same inversion
for "iOS App".

Derive the title tiers from KEYWORD_EXACT_CEILING and multiply by the query token
count so a literal title match dominates token_count * ceiling for any query
length. Add a multi-token regression test
(testNucleoFFIPrefersTitleOverSummedHiddenKeywordsForMultiTokenQuery). Verified
single-token, multi-token, open-folder top-2, initialism, café, and the numeric
prefix-collision case through the real scorer.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3d01461338

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

}

fn chars_equal_ignoring_case(lhs: char, rhs: char) -> bool {
lhs == rhs || lhs.to_ascii_lowercase() == rhs.to_ascii_lowercase()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Use Unicode-aware title prefix matching

When the visible title prefix differs from the query only by non-ASCII case, this helper returns false because to_ascii_lowercase() leaves characters such as É unchanged. In that scenario (for example title Éclair with query é, while another row has a hidden metadata line é) the new title-literal tier is skipped, so the hidden exact keyword can still outrank the visible title prefix this change is meant to prefer; normalize title/query the same way as the Swift search path or use Unicode-aware case folding here.

Useful? React with 👍 / 👎.

… review)

Codex review caught that the hand-rolled ASCII prefix check missed normalized
non-ASCII title prefixes: a localized title like "Éclair" was not recognized as a
prefix of "e", so a row with a hidden exact "e"/"é" metadata token kept the
30_0xx jackpot and still outranked the visible title.

Replace the char-by-char comparison with nucleo `Atom`s (Exact + Prefix) built
once per query and scored against each title, so case and Smart diacritic
normalization are identical to the fuzzy matcher (e.g. "e" prefix-matches
"Éclair", "пр" matches "Привет"). Add a diacritic regression test. Verified
single/multi-token, diacritic/Cyrillic/café, open-folder top-2, initialism, and
the numeric prefix-collision cases through the real scorer.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment on lines +575 to +588
fn title_literal_score(
state: &mut SearchState,
query: &TitleLiteralQuery,
candidate: &Candidate,
) -> Option<f64> {
let token_count = query.token_count as f64;
if state.atom_score(&query.exact, &candidate.title).is_some() {
return Some(TITLE_EXACT_TOKEN_SCORE * token_count);
}
if state.atom_score(&query.prefix, &candidate.title).is_some() {
return Some(TITLE_PREFIX_TOKEN_SCORE * token_count);
}
None
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 TITLE_PREFIX_TOKEN_SCORE tier is unreachable dead code

In nucleo, AtomKind::Exact is a verbatim substring match (equivalent to fzf's 'foo operator) — it returns Some whenever the query appears as a contiguous substring anywhere in the haystack. Because every prefix is also a substring, any title that would fire the prefix Atom will already have fired the exact Atom on the line above, making the second if branch permanently unreachable.

The PR description says "a title equal to the query scores 34,000 and a title prefix scores a flat 32,000," but the current implementation gives 34,030 to any title that contains the query as a substring (including mid-title positions like "My iOS Project" for query "ios"), and the 32,030 tier is never awarded. To match the stated two-tier design, the exact atom should use AtomKind::Equal (which nucleo defines as full-string equality, equivalent to fzf's ^foo$). Using AtomKind::Prefix for the lower tier would then work as intended.

Comment on lines +67 to +73
exact: Atom::new(
query,
CaseMatching::Ignore,
Normalization::Smart,
AtomKind::Exact,
true,
),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 The exact atom should use AtomKind::Equal (full-string equality, fzf's ^foo$) rather than AtomKind::Exact (verbatim substring, fzf's 'foo). With AtomKind::Exact, any title that merely contains the query as a contiguous substring (e.g. "My iOS Project" for query "ios") fires the top tier and scores 34,030. AtomKind::Equal restricts the top tier to titles that equal the query (case-insensitive), while the prefix atom still handles the "starts-with" case at 32,030.

Suggested change
exact: Atom::new(
query,
CaseMatching::Ignore,
Normalization::Smart,
AtomKind::Exact,
true,
),
exact: Atom::new(
query,
CaseMatching::Ignore,
Normalization::Smart,
AtomKind::Equal,
true,
),

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@lawrencecchen
lawrencecchen merged commit c46b110 into main Jun 2, 2026
23 checks passed
@lawrencecchen
lawrencecchen deleted the task-switcher-title-rank branch June 2, 2026 01:13
@austinywang austinywang mentioned this pull request Jun 2, 2026

This branch was successfully deployed

1 active deployment
Preview – cmux — d4718dc9 Deployed Jun 2, 2026 by vercel[bot]
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