@@ -581,19 +581,32 @@ export function scoreSectionItems<T>(
581581}
582582
583583/**
584- * Rank offset added to every matched action. Actions are the palette's few
584+ * Rank offset added to a matched action. Actions are the palette's few
585585 * runnable verbs, so a matched action outranks entity rows of the same match
586586 * quality — a name-matched action beats name-matched entities, a
587587 * keyword-matched action beats other secondary-text matches — while the
588588 * half-tier offset deliberately cannot bridge into the next tier up
589- * ({@link SECTION_MATCH_TIER}, {@link PAGE_MATCH_TIER}).
589+ * ({@link SECTION_MATCH_TIER}, {@link PAGE_MATCH_TIER}). A name hit that
590+ * starts mid-word ("h" in "New chat") is NOT the same quality as the
591+ * word-start matches the offset would leapfrog, so it forgoes the bias.
590592 */
591593export const ACTION_MATCH_BIAS = 500_000
592594
595+ /**
596+ * Whether a match begins where a word begins — the string start, right after a
597+ * separator, or at a camelCase hump. The empty query (no positions) counts as
598+ * a word start.
599+ */
600+ function isWordStartMatch ( text : string , positions : readonly number [ ] ) : boolean {
601+ if ( positions . length === 0 ) return true
602+ return isHardBoundary ( text . toLowerCase ( ) , positions [ 0 ] ) || isCamelBoundary ( text , positions [ 0 ] )
603+ }
604+
593605/**
594606 * Scores actions by visible name before falling back to their keywords.
595- * Every match is lifted by {@link ACTION_MATCH_BIAS}; a query listed in the
596- * action's `exactQueries` ranks it like a page row instead.
607+ * Word-start matches are lifted by {@link ACTION_MATCH_BIAS}; a mid-word name
608+ * hit keeps its honest score so word-start entity matches outrank it; a query
609+ * listed in the action's `exactQueries` ranks it like a page row instead.
597610 */
598611export function scoreActions (
599612 actions : ActionItem [ ] ,
@@ -609,10 +622,15 @@ export function scoreActions(
609622 search ,
610623 ( action ) => `${ toSearchToken ( action . name ) } ${ action . keywords ?? '' } ` ,
611624 maxResults
612- ) . map ( ( { item, score } ) => ( {
613- item,
614- score : item . exactQueries ?. includes ( query ) ? PAGE_MATCH_TIER : score + ACTION_MATCH_BIAS ,
615- } ) )
625+ ) . map ( ( { item, score } ) => {
626+ if ( item . exactQueries ?. includes ( query ) ) return { item, score : PAGE_MATCH_TIER }
627+ /* Section-lifted rows (the query IS the group label) keep the bias
628+ wholesale — only plain name-tier scores are quality-checked. */
629+ const byName = fuzzyMatch ( item . name , query )
630+ const midWordNameMatch =
631+ score < SECTION_MATCH_TIER && byName . matched && ! isWordStartMatch ( item . name , byName . positions )
632+ return { item, score : midWordNameMatch ? score : score + ACTION_MATCH_BIAS }
633+ } )
616634}
617635
618636/**
0 commit comments