feat(search): pipeline-stage fuzzy match shortcut
Typing a stage name in the topbar search now surfaces a "Stage: <Label>"
shortcut row that lands the rep on the interests list filtered by that
stage. Previously reps had to know the navigation path and either click
through the kanban board or hand-type the URL filter.
Match flavours (case-insensitive, query tokens split on whitespace):
1. Modern label prefix — every query token must prefix a token in
`STAGE_LABELS[stage]` or the raw enum slug. "eoi" → EOI, "dep" →
Deposit Paid, "qua" → Qualified.
2. Stage-key substring on the raw enum slug.
3. Legacy aliases via `LEGACY_STAGE_REMAP` — "eoi_signed" /
"deposit_10pct" / "contract_signed" lands on the modern 7-stage
equivalent so reps with muscle memory still find a useful target.
Each row carries a live COUNT(*) of non-archived interests in that
stage (single grouped query — O(stages)). Empty queries skip the
bucket entirely.
- `searchStages(portId, query, limit)` in search.service.ts with the
scoring logic + count query.
- New `StageSuggestionResult` type added to SearchResults + the
client-side mirror in use-search.ts.
- `searchStages` wired into the parallel `Promise.all` block of the
main `search()` and the single-bucket runSingleBucket dispatch
(exhaustive ts-pattern match required the new branch).
- Gated on `interests.view` — destination of the filter.
- New 'stages' bucket in command-search.tsx BUCKETS list (between
Tags and Notes) + a `buildFlatRows` arm that pushes one row per
matched stage. Mobile overlay reuses `buildFlatRows`, so the new
rows appear there too once BUCKET_LABELS picks up the entry.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -23,7 +23,8 @@ export type BucketType =
|
||||
| 'brochures'
|
||||
| 'tags'
|
||||
| 'navigation'
|
||||
| 'notes';
|
||||
| 'notes'
|
||||
| 'stages';
|
||||
|
||||
/**
|
||||
* Provenance hint for a result row that surfaced via graph expansion
|
||||
@@ -147,6 +148,16 @@ export interface NavResult {
|
||||
label: string;
|
||||
category: 'settings' | 'admin' | 'dashboard';
|
||||
}
|
||||
export interface StageSuggestionResult {
|
||||
/** Canonical pipeline-stage value (matches PIPELINE_STAGES). */
|
||||
stage: string;
|
||||
/** Human label (STAGE_LABELS[stage]). */
|
||||
label: string;
|
||||
/** Live count of non-archived interests in this stage. */
|
||||
count: number;
|
||||
/** Slug-less href. CommandSearch prefixes the portSlug at render time. */
|
||||
href: string;
|
||||
}
|
||||
export interface OtherPortResult {
|
||||
portId: string;
|
||||
portSlug: string;
|
||||
@@ -174,6 +185,7 @@ export interface SearchResults {
|
||||
tags: TagResult[];
|
||||
navigation: NavResult[];
|
||||
notes: NoteResult[];
|
||||
stages: StageSuggestionResult[];
|
||||
totals: Record<BucketType, number>;
|
||||
otherPorts?: OtherPortResult[];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user