# Search Chip Context Contract ## Purpose - Define one deterministic contract for page-aware search chips. - Let feature teams add page suggestions without editing `AmbientContextService` logic. - Blend route context, last few page actions, and bounded suggestion randomization. - Answer-first self-serve questions and fallback states are governed separately in `docs/modules/ui/search-self-serve-contract.md`. ## Rule (mandatory for page teams) - Every page that needs custom search chips must declare a context entry in `SEARCH_CONTEXT_DEFINITIONS`. - Page components should implement `SearchContextComponent` with `searchContextId` so ownership is explicit. - Context definitions must provide: - `id` - `routePrefixes` - `presentation` (`titleKey/titleFallback`, `descriptionKey/descriptionFallback`) for the search context rail - optional `domain` - optional `searchSuggestions[]` - optional `chatSuggestions[]` - optional `chatRoutePattern` - Search suggestion entries should provide: - `key` / `fallback` for the executable query text - `reasonKey` / `reasonFallback` for the visible "why this suggestion" line - optional `kind` (`page`, `recent`, `strategy`) when page teams need non-default styling/priority - optional `preferredModes` (`find`, `explain`, `act`) when a chip should rank higher for a specific operator intent - Suggestion arrays must stay deterministic and bounded: - at most 3 base chips per page context - short, action-oriented text - no tenant/user secrets in fallback text - keep the query label distinct from rationale text so suggestion clicks always submit the intended query only - fallback copy should still make sense after mode-aware reordering; do not encode mode-specific rationale into the raw query text ## Source of truth - Contract and registry: - `src/Web/StellaOps.Web/src/app/core/services/search-context.registry.ts` - Runtime composition: - `src/Web/StellaOps.Web/src/app/core/services/ambient-context.service.ts` ## Runtime behavior - The empty-state search panel renders a context rail with: - page title/description from `presentation` - active domain token when available - last-action token when recent scoped action history exists - Base chips come from the page context array. - A deterministic rotation (session + route scope + 5-minute bucket) varies chip order. - Last few actions for the current page scope are tracked (bounded history, TTL 15 minutes). - Up to 2 `follow up: ...` chips are generated from recent actions and prioritized above base chips. - One strategic chip is generated from dominant/recent action intent. - Generated chips must also expose rationale metadata: - `recent` chips explain they came from last-page actions - `strategy` chips explain they came from recent intent on the same page scope - Mode-aware ranking: - the shared `SearchExperienceModeService` owns the active operator mode - chips marked with `preferredModes` are ranked ahead of otherwise-equal chips for that mode - page teams should use this sparingly to express clear intent differences, not to create large per-mode chip forks - Search-surface control rule: - buttons inside the search surface (mode switch, scope toggle, rescue cards, filters) are part of the same command workspace - focus transitions into those controls must not collapse the search panel - teams adding new controls inside the panel must preserve this containment rule in tests ## Page ownership workflow 1. Add/adjust a context in `SEARCH_CONTEXT_DEFINITIONS`. 2. Ensure page component exposes the same `searchContextId` (implements `SearchContextComponent`). 3. Define or update `presentation` copy for the context rail. 4. Add or update `reasonFallback` text and any `preferredModes` metadata for each base chip. 5. Add/adjust unit tests in `ambient-context.service.spec.ts`. 6. Add/adjust Playwright tests for route chips + action-driven chips. 7. If the page adds custom controls inside the search panel, add focus-containment coverage so those controls do not dismiss the panel. ## Non-goals - No unbounded per-page suggestion memory. - No runtime remote fetch for chip definitions. - No randomization based on `Math.random()` (must remain replayable).