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:
2026-05-21 19:45:50 +02:00
parent 235e0645cb
commit d912f02b97
4 changed files with 171 additions and 1 deletions

View File

@@ -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[];
}