Files
git.stella-ops.org/docs/modules/ui/search-chip-context-contract.md
2026-03-07 20:58:52 +02:00

71 lines
3.9 KiB
Markdown

# 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
- 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 stand on its own; do not encode hidden ranking or workflow instructions 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
- Ranking behavior:
- chip order is driven by page context, recent scoped actions, deterministic rotation, and backend viability
- page teams should not create hidden forks that assume the user chose an internal search mode
- Search-surface control rule:
- buttons inside the search surface (assistant launcher, correction suggestions, starter chips, answer follow-ups, card actions) 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 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).