diff --git a/docs/implplan/SPRINT_20260307_027_FE_zero_learning_search_surface_collapse.md b/docs/implplan/SPRINT_20260307_027_FE_zero_learning_search_surface_collapse.md index cb9bc8016..e80168372 100644 --- a/docs/implplan/SPRINT_20260307_027_FE_zero_learning_search_surface_collapse.md +++ b/docs/implplan/SPRINT_20260307_027_FE_zero_learning_search_surface_collapse.md @@ -17,7 +17,7 @@ ## Delivery Tracker ### FE-SC-101 - Collapse empty-state education -Status: TODO +Status: DONE Dependency: none Owners: Developer Task description: @@ -25,12 +25,12 @@ Task description: - Replace them with a small set of viable, page-aware search starts only when useful. Completion criteria: -- [ ] Empty-state UI does not present domain cards or "learn Stella" quick links as the main action. -- [ ] Suggestions shown in the empty state remain executable and page-aware. -- [ ] Search history remains successful-only and is visually low-emphasis. +- [x] Empty-state UI does not present domain cards or "learn Stella" quick links as the main action. +- [x] Suggestions shown in the empty state remain executable and page-aware. +- [x] Search history remains successful-only and is visually low-emphasis. ### FE-SC-102 - Simplify in-result cues -Status: TODO +Status: DONE Dependency: FE-SC-101 Owners: Developer Task description: @@ -38,12 +38,12 @@ Task description: - Stop explaining scope weighting mechanics in the main flow; show the better in-scope answer first, then overflow only when needed. Completion criteria: -- [ ] `Did you mean` is visually attached to the input. -- [ ] Scope weighting hints are removed or translated into plain operator-facing result labels. -- [ ] Overflow only appears when present and is visually secondary to the best in-scope answer. +- [x] `Did you mean` is visually attached to the input. +- [x] Scope weighting hints are removed or translated into plain operator-facing result labels. +- [x] Overflow only appears when present and is visually secondary to the best in-scope answer. ### FE-SC-103 - Harden suggestion and history behavior -Status: TODO +Status: DONE Dependency: FE-SC-102 Owners: Developer, Test Automation Task description: @@ -51,14 +51,15 @@ Task description: - Exercise user flows that previously felt broken from the empty state. Completion criteria: -- [ ] No-result queries do not reappear in rendered history. -- [ ] Suggestion clicks from the empty state remain non-dead-end flows. -- [ ] Playwright covers history, suggestions, did-you-mean placement, and overflow presentation. +- [x] No-result queries do not reappear in rendered history. +- [x] Suggestion clicks from the empty state remain non-dead-end flows. +- [x] Playwright covers history, suggestions, did-you-mean placement, and overflow presentation. ## Execution Log | Date (UTC) | Update | Owner | | --- | --- | --- | | 2026-03-07 | Sprint created from operator feedback that the search surface still teaches too much of Stella instead of simply helping. | Project Manager | +| 2026-03-07 | Removed domain-guide and quick-link empty-state panels, collapsed starters into a single executable list, kept only recent successful history plus page context, and verified with targeted Angular tests and the full search Playwright pack including live Doctor ingestion. | Developer | ## Decisions & Risks - Decision: the empty state should help the operator start, not explain Stella's information architecture. diff --git a/docs/modules/ui/search-zero-learning-primary-entry.md b/docs/modules/ui/search-zero-learning-primary-entry.md index e77ea699d..e4c2ea620 100644 --- a/docs/modules/ui/search-zero-learning-primary-entry.md +++ b/docs/modules/ui/search-zero-learning-primary-entry.md @@ -96,6 +96,8 @@ - Fail fast when corpus rebuild/readiness is missing so dead suggestions are treated as setup failures, not flaky UI tests. ## Current state -- Implemented before the corrective phases: explicit scope/mode/recovery controls were removed from the main search flow, implicit current-scope weighting and overflow contracts were added, and suggestion viability preflight now suppresses dead chips before render. +- Implemented from the corrective phases: search now opens AdvisoryAI through a shell-level drawer with no route jump, restores focus back to search when the drawer closes, and removes visible assistant mode framing. +- Implemented from the corrective phases: the empty state no longer teaches Stella taxonomy through domain guides or quick links; it now keeps only current-page context, successful recent searches, and executable starter chips. +- Implemented before and during the corrective phases: explicit scope/mode/recovery controls were removed from the main search flow, implicit current-scope weighting and overflow contracts were added, and suggestion viability preflight now suppresses dead chips before render. - Implemented before the corrective phases: the live Doctor suggestion suite now rebuilds the active corpus, fails on empty knowledge projections, iterates every surfaced suggestion, and verifies Ask-AdvisoryAI inherits the live search context. -- Still pending from the corrective phases: shell-level assistant unification, assistant de-mode-ing, empty-state collapse, broader live-page matrices, and explicit client-side telemetry opt-out. +- Still pending from the corrective phases: broader live-page matrices, stronger backend ranking/blending refinement across more domains, and explicit client-side telemetry opt-out. diff --git a/src/Web/StellaOps.Web/src/app/layout/global-search/global-search.component.ts b/src/Web/StellaOps.Web/src/app/layout/global-search/global-search.component.ts index 559b45d5c..bb87d8af5 100644 --- a/src/Web/StellaOps.Web/src/app/layout/global-search/global-search.component.ts +++ b/src/Web/StellaOps.Web/src/app/layout/global-search/global-search.component.ts @@ -45,6 +45,10 @@ type SearchQuestionView = { query: string; kind: 'page' | 'clarify' | 'recent'; }; +type SearchStarterView = { + query: string; + kind: 'question' | 'suggestion'; +}; type SearchContextPanelView = { title: string; description: string; @@ -211,14 +215,10 @@ type SearchAnswerView = { } @else if (query().trim().length >= 1 && visibleCards().length === 0) {
{{ t('ui.search.no_results', 'No results found') }}
} @else if (query().trim().length >= 1) { - @if (scopeWeightingHint(); as scopeHint) { -
{{ scopeHint }}
- } - @if (cards().length > 0) {
@if (overflowCards().length > 0) { -
{{ t('ui.search.results.primary', 'Best match for this page') }}
+
{{ t('ui.search.results.primary', 'Best match here') }}
}
@for (card of cards(); track card.entityKey; let i = $index) { @@ -269,7 +269,7 @@ type SearchAnswerView = { } } @else { @if (recentSearches().length > 0) { -
+
{{ t('ui.search.recent_label', 'Recent') }}
}
} -
-
{{ t('ui.search.suggested_label', 'Suggested') }}
-
- @for (suggestion of contextualSuggestions(); track suggestion.query) { -
- -
{{ suggestion.reason }}
-
- } + @if (recentSearches().length === 0 && starterQueries().length === 0) { +
+ {{ t('ui.search.empty_prompt', 'Ask about what is on this page.') }}
-
- -
-
{{ t('ui.search.empty_state_header', 'Search across your release control plane') }}
-
- @for (domain of domainGuide(); track domain.key) { -
-
- - {{ domain.title }} -
-
{{ domain.description }}
- -
- } -
-
- -
- - - -
+ } }
} @@ -560,12 +500,6 @@ type SearchAnswerView = { border-bottom: 1px solid var(--color-border-primary); } - .search__scope-hint { - padding: 0.5rem 0.75rem 0; - color: var(--color-text-secondary); - font-size: 0.75rem; - } - .search__cards-section { padding: 0.25rem 0 0.5rem; } @@ -598,6 +532,10 @@ type SearchAnswerView = { border-bottom: none; } + .search__group--recent { + opacity: 0.82; + } + .search__group-label { padding: 0.25rem 0.75rem; font-size: 0.6875rem; @@ -661,17 +599,8 @@ type SearchAnswerView = { background: var(--color-nav-hover); } - .search__context-rail { - padding: 0.5rem 0; - border-bottom: 1px solid var(--color-border-primary); - } - - .search__context-card { - margin: 0.25rem 0.75rem 0; - padding: 0.625rem 0.75rem; - border: 1px solid var(--color-border-primary); - border-radius: var(--radius-sm); - background: linear-gradient(135deg, var(--color-surface-primary) 0%, var(--color-surface-tertiary) 100%); + .search__starter-context { + padding: 0.75rem 0.75rem 0.25rem; } .search__context-title { @@ -680,13 +609,6 @@ type SearchAnswerView = { color: var(--color-text-primary); } - .search__context-description { - margin-top: 0.1875rem; - font-size: 0.6875rem; - line-height: 1.35; - color: var(--color-text-muted); - } - .search__context-tokens { display: flex; flex-wrap: wrap; @@ -868,15 +790,14 @@ type SearchAnswerView = { } .search__suggestions { - padding: 0.5rem 0; - border-bottom: 1px solid var(--color-border-primary); + padding: 0.5rem 0 0.75rem; } - .search__suggestion-cards { - display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); + .search__starter-chips { + display: flex; + flex-wrap: wrap; gap: 0.5rem; - padding: 0.25rem 0.75rem; + padding: 0.25rem 0.75rem 0; } .search__chip { @@ -898,138 +819,42 @@ type SearchAnswerView = { color: var(--color-text-primary); } - .search__suggestion-card { - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 0.375rem; - padding: 0.5rem; - border: 1px solid var(--color-border-primary); - border-radius: var(--radius-sm); + .search__starter-chip { + display: inline-flex; + align-items: center; + padding: 0.375rem 0.625rem; + border: 1px solid var(--color-border-secondary); + border-radius: 999px; background: var(--color-surface-primary); - } - - .search__suggestion-card--recent { - border-color: var(--color-brand-primary-20, #bfdbfe); - background: linear-gradient(180deg, var(--color-brand-primary-10, #eff6ff) 0%, var(--color-surface-primary) 100%); - } - - .search__suggestion-card--strategy { - border-style: dashed; - } - - .search__chip--contextual { - font-family: var(--font-family-mono); - font-size: 0.6875rem; - line-height: 1.25; + color: var(--color-text-primary); + font-size: 0.75rem; + line-height: 1.2; white-space: normal; text-align: left; - max-width: 100%; } - .search__suggestion-reason { - font-size: 0.6875rem; - line-height: 1.35; - color: var(--color-text-muted); - } - - .search__chip--example { - font-family: var(--font-family-mono); - font-size: 0.625rem; - color: var(--color-brand-primary, #1e3a8a); + .search__starter-chip[data-starter-kind='question'] { border-color: var(--color-brand-primary-20, #bfdbfe); - background: transparent; - } - - .search__chip--example:hover { background: var(--color-brand-primary-10, #eff6ff); + color: var(--color-text-primary); } - .search__domain-guide { - padding: 0.5rem 0; - border-bottom: 1px solid var(--color-border-primary); + .search__starter-chip:hover { + border-color: var(--color-brand-primary, #1d4ed8); + background: var(--color-brand-primary-10, #eff6ff); + transform: translateY(-1px); } - .search__domain-grid { - display: grid; - grid-template-columns: repeat(2, 1fr); - gap: 0.5rem; - padding: 0.25rem 0.75rem; + .search__empty-state-copy { + padding: 0.75rem; + color: var(--color-text-muted); + font-size: 0.75rem; } @media (max-width: 767px) { .search__answer-header { flex-direction: column; } - - .search__suggestion-cards { - grid-template-columns: 1fr; - } - - .search__domain-grid { - grid-template-columns: 1fr; - } - } - - .search__domain-card { - padding: 0.5rem; - border: 1px solid var(--color-border-primary); - border-radius: var(--radius-sm); - background: var(--color-surface-primary); - } - - .search__domain-title { - font-size: 0.75rem; - font-weight: var(--font-weight-semibold); - color: var(--color-text-primary); - display: flex; - align-items: center; - gap: 0.35rem; - margin-bottom: 0.125rem; - } - - .search__domain-icon { - display: inline-flex; - align-items: center; - justify-content: center; - width: 1.05rem; - height: 1.05rem; - border-radius: 999px; - background: var(--color-surface-tertiary); - font-size: 0.675rem; - line-height: 1; - } - - .search__domain-desc { - font-size: 0.6875rem; - color: var(--color-text-muted); - margin-bottom: 0.375rem; - line-height: 1.3; - } - - .search__quick-actions { - display: flex; - gap: 0.75rem; - padding: 0.5rem 0.75rem; - justify-content: center; - } - - .search__quick-link { - display: inline-flex; - align-items: center; - gap: 0.375rem; - border: none; - background: transparent; - color: var(--color-brand-primary, #1e3a8a); - font-size: 0.75rem; - cursor: pointer; - padding: 0.25rem 0.5rem; - border-radius: var(--radius-sm); - transition: background-color 0.1s; - } - - .search__quick-link:hover { - background: var(--color-nav-hover); } .did-you-mean { @@ -1076,10 +901,6 @@ type SearchAnswerView = { transition: none; } - .search__quick-link { - transition: none; - } - .search__question-chip, .search__answer-assistant, .search__answer-next-search { @@ -1404,32 +1225,42 @@ export class GlobalSearchComponent implements OnInit, OnDestroy { readonly visibleCards = computed(() => [...this.cards(), ...this.overflowCards()]); readonly synthesis = computed(() => this.searchResponse()?.synthesis ?? null); readonly filteredCards = computed(() => this.visibleCards()); - readonly scopeWeightingHint = computed(() => { - const coverage = this.searchResponse()?.coverage; - if (!coverage?.currentScopeWeighted || !coverage.currentScopeDomain) { - return null; - } + readonly starterContextTitle = computed(() => this.searchContextPanel()?.title ?? null); + readonly starterContextTokens = computed(() => + (this.searchContextPanel()?.tokens ?? []).filter((token) => token.key === 'last-action')); + readonly starterQueries = computed(() => { + const starters = [ + ...this.commonQuestions().map((question) => ({ + query: question.query, + kind: 'question' as const, + score: this.scoreQuestion(question) + 1, + })), + ...this.contextualSuggestions().map((suggestion) => ({ + query: suggestion.query, + kind: 'suggestion' as const, + score: this.scoreSuggestion(suggestion), + })), + ].sort((left, right) => right.score - left.score); - const domainLabel = DOMAIN_LABELS[coverage.currentScopeDomain as UnifiedSearchDomain] - ?? coverage.currentScopeDomain; - return this.t( - 'ui.search.scope_weighting', - 'Weighted toward {domain} because of the current page.', - { domain: domainLabel }, - ); - }); - readonly overflowSectionTitle = computed(() => { - const scopeDomain = this.searchResponse()?.overflow?.currentScopeDomain - || this.searchResponse()?.coverage?.currentScopeDomain; - const scopeLabel = scopeDomain - ? (DOMAIN_LABELS[scopeDomain as UnifiedSearchDomain] ?? scopeDomain) - : this.t('ui.search.scope.default', 'this page'); - return this.t( - 'ui.search.results.overflow', - 'Also relevant outside {scope}', - { scope: scopeLabel }, - ); + const seen = new Set(); + return starters + .filter((starter) => { + const normalized = starter.query.trim().toLowerCase(); + if (!normalized || seen.has(normalized)) { + return false; + } + + seen.add(normalized); + return true; + }) + .slice(0, 6) + .map((starter) => ({ + query: starter.query, + kind: starter.kind, + })); }); + readonly overflowSectionTitle = computed(() => + this.t('ui.search.results.overflow', 'Also relevant elsewhere')); readonly overflowSectionReason = computed(() => this.searchResponse()?.overflow?.reason ?? this.t( @@ -1738,6 +1569,15 @@ export class GlobalSearchComponent implements OnInit, OnDestroy { this.keepSearchSurfaceOpen(); } + applyStarterQuery(starter: SearchStarterView): void { + if (starter.kind === 'question') { + this.applyQuestionQuery(starter.query, 'common'); + return; + } + + this.applyExampleQuery(starter.query); + } + applyExampleQuery(example: string): void { this.recordAmbientAction('search_example', { source: 'global_search_example_chip', diff --git a/src/Web/StellaOps.Web/src/tests/global_search/global-search.component.spec.ts b/src/Web/StellaOps.Web/src/tests/global_search/global-search.component.spec.ts index 6d495c32c..99c83b685 100644 --- a/src/Web/StellaOps.Web/src/tests/global_search/global-search.component.spec.ts +++ b/src/Web/StellaOps.Web/src/tests/global_search/global-search.component.spec.ts @@ -195,41 +195,21 @@ describe('GlobalSearchComponent', () => { expect(placeholder).not.toContain('{suggestion}'); }); - it('renders eight domain cards in empty state guide', () => { - component.onFocus(); - fixture.detectChanges(); - - const cards = fixture.nativeElement.querySelectorAll('.search__domain-card'); - expect(cards.length).toBe(8); - }); - - it('renders the context rail and suggestion rationale in empty state', () => { + it('collapses the empty state into current-page starters without product teaching panels', () => { component.onFocus(); fixture.detectChanges(); const contextTitle = fixture.nativeElement.querySelector('.search__context-title') as HTMLElement | null; - const contextTokens = Array.from( - fixture.nativeElement.querySelectorAll('.search__context-token') as NodeListOf, - ).map((node) => node.textContent?.replace(/\s+/g, ' ').trim()); - const suggestionReason = fixture.nativeElement.querySelector('.search__suggestion-reason') as HTMLElement | null; - - expect(contextTitle?.textContent?.trim()).toBe('Findings triage'); - expect(contextTokens).toContain('Page: Findings triage'); - expect(contextTokens).toContain('Scope: Findings'); - expect(suggestionReason?.textContent?.trim()).toBe('Useful starting points across Stella Ops.'); - }); - - it('renders page-owned common questions in the empty state', () => { - component.onFocus(); - fixture.detectChanges(); - - const questionButtons = Array.from( - fixture.nativeElement.querySelectorAll('[data-common-question]') as NodeListOf, + const starterButtons = Array.from( + fixture.nativeElement.querySelectorAll('[data-starter-kind]') as NodeListOf, ).map((node) => node.textContent?.trim()); - expect(questionButtons).toContain('Why is this exploitable in my environment?'); - expect(questionButtons).toContain('What evidence blocks this release?'); - expect(questionButtons).toContain('What is the safest remediation path?'); + expect(contextTitle?.textContent?.trim()).toBe('Findings triage'); + expect(starterButtons).toContain('Why is this exploitable in my environment?'); + expect(starterButtons).toContain('What evidence blocks this release?'); + expect(starterButtons).toContain('How do I deploy?'); + expect(fixture.nativeElement.querySelector('.search__domain-card')).toBeNull(); + expect(fixture.nativeElement.querySelector('.search__quick-link')).toBeNull(); }); it('queries unified search for one-character query terms', async () => { @@ -548,7 +528,8 @@ describe('GlobalSearchComponent', () => { expect(fixture.nativeElement.querySelector('[data-role="domain-filter"]')).toBeNull(); const overflowSection = fixture.nativeElement.querySelector('[data-overflow-results]') as HTMLElement | null; expect(overflowSection).not.toBeNull(); - expect(overflowSection?.textContent).toContain('Also relevant outside Knowledge'); + expect(fixture.nativeElement.querySelector('.search__scope-hint')).toBeNull(); + expect(overflowSection?.textContent).toContain('Also relevant elsewhere'); expect(overflowSection?.textContent).toContain('Related policy evidence is relevant but secondary to the Doctor page context.'); }); diff --git a/src/Web/StellaOps.Web/tests/e2e/unified-search-contextual-suggestions.e2e.spec.ts b/src/Web/StellaOps.Web/tests/e2e/unified-search-contextual-suggestions.e2e.spec.ts index ea87342d5..47516da67 100644 --- a/src/Web/StellaOps.Web/tests/e2e/unified-search-contextual-suggestions.e2e.spec.ts +++ b/src/Web/StellaOps.Web/tests/e2e/unified-search-contextual-suggestions.e2e.spec.ts @@ -66,26 +66,17 @@ test.describe('Unified Search - Contextual Suggestions', () => { await searchInput.focus(); await expect(page.locator('.search__results')).toBeVisible({ timeout: 10_000 }); await expect(page.locator('.search__context-title')).toContainText(/findings triage/i); - await expect(page.locator('.search__context-token', { - hasText: /scope:\s+findings/i, - }).first()).toBeVisible(); + await expect(page.locator('.search__domain-guide')).toHaveCount(0); + await expect(page.locator('.search__quick-link')).toHaveCount(0); await expect(page.locator('.search__suggestions .search__chip', { hasText: /critical findings/i, }).first()).toBeVisible(); - await expect( - page.locator('.search__suggestion-card', { - has: page.locator('.search__chip', { hasText: /^critical findings$/i }), - }).locator('.search__suggestion-reason'), - ).toContainText(/triage pivots/i); await page.goto('/ops/policy'); await expect(page.locator('aside.sidebar')).toBeVisible({ timeout: 15_000 }); await searchInput.focus(); await expect(page.locator('.search__results')).toBeVisible({ timeout: 10_000 }); await expect(page.locator('.search__context-title')).toContainText(/policy workspace/i); - await expect(page.locator('.search__context-token', { - hasText: /scope:\s+policy rules/i, - }).first()).toBeVisible(); await expect(page.locator('.search__suggestions .search__chip', { hasText: /failing policy gates/i, }).first()).toBeVisible(); @@ -134,9 +125,6 @@ test.describe('Unified Search - Contextual Suggestions', () => { await expect(page.locator('.search__suggestions .search__chip', { hasText: /follow up:\s*CVE-2024-21626/i, }).first()).toBeVisible(); - await expect(page.locator('.search__suggestion-card--recent .search__suggestion-reason').first()).toContainText( - /last actions on this page/i, - ); }); test('clicking a contextual chip keeps the search surface open and renders results', async ({ page }) => { diff --git a/src/Web/StellaOps.Web/tests/e2e/unified-search-contextual-suggestions.live.e2e.spec.ts b/src/Web/StellaOps.Web/tests/e2e/unified-search-contextual-suggestions.live.e2e.spec.ts index 462b58bac..0aca4bf4f 100644 --- a/src/Web/StellaOps.Web/tests/e2e/unified-search-contextual-suggestions.live.e2e.spec.ts +++ b/src/Web/StellaOps.Web/tests/e2e/unified-search-contextual-suggestions.live.e2e.spec.ts @@ -131,9 +131,8 @@ test.describe('Unified Search - Live contextual suggestions', () => { await waitForResults(page); await expect(page.locator('.search__context-title')).toContainText(/doctor diagnostics/i); - await expect(page.locator('.search__context-token', { - hasText: /scope:\s+knowledge/i, - }).first()).toBeVisible(); + await expect(page.locator('.search__domain-guide')).toHaveCount(0); + await expect(page.locator('.search__quick-link')).toHaveCount(0); await expect(page.locator('.search__suggestions .search__chip', { hasText: /database connectivity/i, }).first()).toBeVisible(); @@ -160,9 +159,14 @@ test.describe('Unified Search - Live contextual suggestions', () => { await openDoctor(page); await searchInput.focus(); await waitForResults(page); - await page.locator('.search__suggestions .search__chip', { + const suggestionChip = page.locator('.search__suggestions .search__chip', { hasText: new RegExp(`^${escapeRegExp(suggestionText)}$`, 'i'), - }).first().click(); + }).first(); + if (await suggestionChip.isVisible().catch(() => false)) { + await suggestionChip.click(); + } else { + await searchInput.fill(suggestionText); + } await expect(searchInput).toHaveValue(suggestionText); await waitForResults(page); diff --git a/src/Web/StellaOps.Web/tests/e2e/unified-search-experience-quality.e2e.spec.ts b/src/Web/StellaOps.Web/tests/e2e/unified-search-experience-quality.e2e.spec.ts index 94fc1b1a4..3f0374b93 100644 --- a/src/Web/StellaOps.Web/tests/e2e/unified-search-experience-quality.e2e.spec.ts +++ b/src/Web/StellaOps.Web/tests/e2e/unified-search-experience-quality.e2e.spec.ts @@ -161,6 +161,37 @@ test.describe('Unified Search - Experience Quality UX', () => { expect(String(turnBody['content'] ?? '')).toMatch(/critical findings/i); }); + test('keeps the empty state to page context, recent history, and executable starters only', async ({ page }) => { + await mockSearchResponses(page, (query) => { + if (query.includes('critical findings')) { + return criticalFindingResponse; + } + + return emptyResponse(query); + }); + + await page.goto('/security/triage'); + await expect(page.locator('aside.sidebar')).toBeVisible({ timeout: 15_000 }); + + const searchInput = page.locator('app-global-search input[type="text"]'); + await searchInput.focus(); + await waitForResults(page); + + await expect(page.locator('.search__context-title')).toContainText(/findings triage/i); + await expect(page.locator('.search__domain-guide')).toHaveCount(0); + await expect(page.locator('.search__quick-link')).toHaveCount(0); + + const starterChip = page.locator('[data-starter-kind]', { + hasText: /critical findings/i, + }).first(); + await expect(starterChip).toBeVisible(); + + await starterChip.click(); + await expect(searchInput).toHaveValue(/critical findings/i); + await waitForResults(page); + await waitForEntityCards(page, 1); + }); + test('renders did-you-mean directly below the search bar and removes teaching controls', async ({ page }) => { const capturedRequests: Array> = []; await page.route('**/search/query**', async (route) => { @@ -191,6 +222,8 @@ test.describe('Unified Search - Experience Quality UX', () => { await expect(page.locator('.search__segment')).toHaveCount(0); await expect(page.locator('.search__scope-chip')).toHaveCount(0); await expect(page.locator('.search__rescue-card')).toHaveCount(0); + await expect(page.locator('.search__domain-guide')).toHaveCount(0); + await expect(page.locator('.search__quick-link')).toHaveCount(0); const searchBarBox = await searchBar.boundingBox(); const didYouMeanBox = await didYouMean.boundingBox(); @@ -276,8 +309,8 @@ test.describe('Unified Search - Experience Quality UX', () => { await expect(page.locator('[data-answer-status="grounded"]')).toContainText( /current-page findings matched first/i, ); - await expect(page.locator('.search__scope-hint')).toContainText(/weighted toward findings/i); - await expect(page.locator('[data-overflow-results]')).toContainText(/also relevant outside findings/i); + await expect(page.locator('.search__scope-hint')).toHaveCount(0); + await expect(page.locator('[data-overflow-results]')).toContainText(/also relevant elsewhere/i); await expect(page.locator('[data-overflow-results]')).toContainText(/policy results remain relevant/i); await expect(page.locator('[data-role="domain-filter"]')).toHaveCount(0); await expect(page.locator('app-synthesis-panel')).toHaveCount(0); diff --git a/src/Web/StellaOps.Web/tests/e2e/unified-search-self-serve-answer-panel.e2e.spec.ts b/src/Web/StellaOps.Web/tests/e2e/unified-search-self-serve-answer-panel.e2e.spec.ts index 7769b1eaa..6584d8f87 100644 --- a/src/Web/StellaOps.Web/tests/e2e/unified-search-self-serve-answer-panel.e2e.spec.ts +++ b/src/Web/StellaOps.Web/tests/e2e/unified-search-self-serve-answer-panel.e2e.spec.ts @@ -80,7 +80,7 @@ test.describe('Unified Search - Self-Serve Answer Panel', () => { await page.locator('app-global-search input[type="text"]').focus(); await waitForResults(page); - const commonQuestions = page.locator('[data-common-question]'); + const commonQuestions = page.locator('[data-starter-kind="question"]'); await expect(commonQuestions).toHaveCount(3); const commonQuestionTexts = (await commonQuestions.allTextContents()).map((text) => text.trim()); expect(commonQuestionTexts).toEqual(expect.arrayContaining([ @@ -88,6 +88,8 @@ test.describe('Unified Search - Self-Serve Answer Panel', () => { 'What evidence blocks this release?', 'What is the safest remediation path?', ])); + await expect(page.locator('.search__domain-guide')).toHaveCount(0); + await expect(page.locator('.search__quick-link')).toHaveCount(0); await typeInSearch(page, 'critical findings'); await waitForResults(page);