Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions plans/015-serper-integration-ci-failure-adr.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# ADR 015: Serper Integration CI Failure Triage

## Status

Check notice on line 3 in plans/015-serper-integration-ci-failure-adr.md

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

plans/015-serper-integration-ci-failure-adr.md#L3

Expected: 1; Actual: 0; Below
Blocked

## Context

Check notice on line 6 in plans/015-serper-integration-ci-failure-adr.md

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

plans/015-serper-integration-ci-failure-adr.md#L6

Expected: 1; Actual: 0; Below
The `Serper Integration` GitHub Actions check run fails when executed on pull request branches or fork environments that lack access to the `SERPER_API_KEY` repository secret. Because Serper is a paid provider requiring this API key, its availability is set to false in key-less environments.

While the query resolver cascade is designed to gracefully fallback to DuckDuckGo, the GHA runner shared IP pool faces heavy rate-limiting and CAPTCHA blocking when attempting unauthenticated/free queries to DuckDuckGo via the Jina Reader proxy (`https://r.jina.ai/https://html.duckduckgo.com/html/?q=...`). This results in a final resolution timeout and raises the error `No query resolution method available`.

## Root Cause

Check notice on line 11 in plans/015-serper-integration-ci-failure-adr.md

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

plans/015-serper-integration-ci-failure-adr.md#L11

Expected: 1; Actual: 0; Below
1. **Repository Secret Access**: The GHA workflow runs integration tests on external pull request/head branches where secrets like `SERPER_API_KEY` are not populated, disabling the Serper provider.

Check notice on line 12 in plans/015-serper-integration-ci-failure-adr.md

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

plans/015-serper-integration-ci-failure-adr.md#L12

Lists should be surrounded by blank lines
2. **DuckDuckGo/Jina Rate-Limiting**: The fallback DuckDuckGo provider is blocked or rate-limited under GHA shared IP pools, returning a resolution failure and failing the entire query cascade with no active providers left.

## Mitigation / Action Items (Future Scope)

Check notice on line 15 in plans/015-serper-integration-ci-failure-adr.md

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

plans/015-serper-integration-ci-failure-adr.md#L15

Expected: 1; Actual: 0; Below
1. Configure dummy/mocked query responses for the release and integration tests when live keys are absent.

Check notice on line 16 in plans/015-serper-integration-ci-failure-adr.md

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

plans/015-serper-integration-ci-failure-adr.md#L16

Lists should be surrounded by blank lines
2. Update the `ci-integration.yml` GHA workflow to gracefully skip the live `Smoke test CLI (Serper)` step if `SERPER_API_KEY` is not present, or if it has a masked/empty value.

## Scope of Current Task

Check notice on line 19 in plans/015-serper-integration-ci-failure-adr.md

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

plans/015-serper-integration-ci-failure-adr.md#L19

Expected: 1; Actual: 0; Below
This pre-existing issue is out of scope for the current micro-UX accessibility focus indicators improvement task. This ADR is documented under the `AGENTS.md` triage protocol for unfixable pre-existing failures.
9 changes: 9 additions & 0 deletions plans/GOAP_STATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,12 @@
- `.agents/skills/do-wdr-issue-swarm/references/agent-prompt.md` § CRITICAL

**RULE**: Never merge, auto-merge, or admin-merge a PR with ANY failing CI check. No exceptions.

---

## Triage State — 2026-07-31

### Serper Integration Test Failure

Check notice on line 111 in plans/GOAP_STATE.md

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

plans/GOAP_STATE.md#L111

Expected: 1; Actual: 0; Below
- **Status**: Blocked (Pre-existing on main)

Check notice on line 112 in plans/GOAP_STATE.md

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

plans/GOAP_STATE.md#L112

Lists should be surrounded by blank lines
- **Root Cause**: GHA runner lacks access to `SERPER_API_KEY` on PR head branches, and fallback free DuckDuckGo is blocked/rate-limited by the GHA IP pool.
- **ADR Link**: [ADR 015: Serper Integration CI Failure Triage](015-serper-integration-ci-failure-adr.md)
10 changes: 5 additions & 5 deletions web/app/components/History.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ export default function History({ onLoad }: HistoryProps) {
<div className="border-t-2 border-border-muted">
<button
onClick={() => setIsOpen(!isOpen)}
className="w-full p-4 flex items-center justify-between hover:bg-[#141414] transition-colors text-[11px] text-text-muted min-h-[44px]"
className="w-full p-4 flex items-center justify-between hover:bg-[#141414] transition-colors text-[11px] text-text-muted min-h-[44px] focus-visible:outline-2 focus-visible:outline-accent focus-visible:outline-offset-2"
aria-expanded={isOpen}
aria-controls="history-panel"
>
Expand All @@ -124,15 +124,15 @@ export default function History({ onLoad }: HistoryProps) {
value={search}
onChange={(e) => setSearch(e.target.value)}
placeholder="Search history..."
className="w-full bg-[#141414] border-2 border-border-muted px-2 py-2 text-[11px] text-foreground placeholder:text-text-dim focus:border-accent focus:outline-none min-h-[44px] pr-10"
className="w-full bg-[#141414] border-2 border-border-muted px-2 py-2 text-[11px] text-foreground placeholder:text-text-dim focus:border-accent focus:outline-none focus-visible:outline-2 focus-visible:outline-accent focus-visible:outline-offset-2 min-h-[44px] pr-10"
/>
{search && (
<button
onClick={() => {
setSearch("");
searchInputRef.current?.focus();
}}
className="absolute right-0 top-0 h-full px-3 text-text-dim hover:text-foreground transition-colors"
className="absolute right-0 top-0 h-full px-3 text-text-dim hover:text-foreground transition-colors focus-visible:outline-2 focus-visible:outline-accent focus-visible:outline-offset-2"
aria-label="Clear search"
>
×
Expand All @@ -152,13 +152,13 @@ export default function History({ onLoad }: HistoryProps) {
<div className="flex items-start justify-between gap-2">
<button
onClick={() => handleLoad(entry)}
className="text-left text-[11px] text-foreground hover:text-accent flex-1"
className="text-left text-[11px] text-foreground hover:text-accent flex-1 focus-visible:outline-2 focus-visible:outline-accent focus-visible:outline-offset-2"
>
{entry.query}
</button>
<button
onClick={() => handleDelete(entry.id)}
className={`text-[10px] min-h-[32px] min-w-[32px] flex items-center justify-center transition-all ${
className={`text-[10px] min-h-[32px] min-w-[32px] flex items-center justify-center transition-all focus-visible:outline-2 focus-visible:outline-accent focus-visible:outline-offset-2 ${
confirmDeleteId === entry.id
? "text-[#ff4444] font-bold"
: "text-text-muted hover:text-[#ff4444] opacity-0 group-hover:opacity-100 focus-visible:opacity-100"
Expand Down
6 changes: 3 additions & 3 deletions web/app/components/MainHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export function MainHeader(props: MainHeaderProps) {
{/* Hamburger menu - mobile only */}
<button
onClick={handleMenuOpen}
className="lg:hidden p-2 text-text-muted hover:text-foreground min-h-[44px] min-w-[44px] flex items-center justify-center"
className="lg:hidden p-2 text-text-muted hover:text-foreground min-h-[44px] min-w-[44px] flex items-center justify-center focus-visible:outline-2 focus-visible:outline-accent focus-visible:outline-offset-2"
aria-label="Open menu"
aria-expanded={props.mobileMenuOpen}
aria-controls="sidebar-navigation"
Expand All @@ -33,12 +33,12 @@ export function MainHeader(props: MainHeaderProps) {
<div className="flex items-center gap-2">
<button
onClick={props.onShowShortcuts}
className="text-[11px] text-text-muted hover:text-accent min-h-[44px] flex items-center px-2 focus:outline-none focus:text-accent"
className="text-[11px] text-text-muted hover:text-accent min-h-[44px] flex items-center px-2 focus:outline-none focus:text-accent focus-visible:outline-2 focus-visible:outline-accent focus-visible:outline-offset-2"
aria-label="Show keyboard shortcuts"
>
Shortcuts
</button>
<Link href="/help" className="text-[11px] text-text-muted hover:text-accent min-h-[44px] flex items-center px-2 focus:outline-none focus:text-accent">
<Link href="/help" className="text-[11px] text-text-muted hover:text-accent min-h-[44px] flex items-center px-2 focus:outline-none focus:text-accent focus-visible:outline-2 focus-visible:outline-accent focus-visible:outline-offset-2">
Help
</Link>
</div>
Expand Down
6 changes: 3 additions & 3 deletions web/app/components/MetadataBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,15 @@ export function MetadataBar({
<div className="flex items-center gap-2">
<button
onClick={handleCardsClick}
className={`px-3 py-1 border border-border-muted ${!viewRaw ? "text-accent border-accent" : "text-text-muted"}`}
className={`px-3 py-1 border border-border-muted focus-visible:outline-2 focus-visible:outline-accent focus-visible:outline-offset-2 ${!viewRaw ? "text-accent border-accent" : "text-text-muted"}`}
aria-pressed={!viewRaw}
title="View results as individual structured cards"
>
Cards
</button>
<button
onClick={handleRawClick}
className={`px-3 py-1 border border-border-muted ${viewRaw ? "text-accent border-accent" : "text-text-muted"}`}
className={`px-3 py-1 border border-border-muted focus-visible:outline-2 focus-visible:outline-accent focus-visible:outline-offset-2 ${viewRaw ? "text-accent border-accent" : "text-text-muted"}`}
aria-pressed={viewRaw}
title="View results as raw markdown text"
>
Expand All @@ -71,7 +71,7 @@ export function MetadataBar({
onClick={handleCopyResult}
aria-label={copied ? "Copied to clipboard" : "Copy to clipboard"}
aria-live="polite"
className="hover:text-foreground transition-colors min-h-[36px] px-2"
className="hover:text-foreground transition-colors min-h-[36px] px-2 focus-visible:outline-2 focus-visible:outline-accent focus-visible:outline-offset-2"
>
{copied ? "Copied" : "Copy"}
</button>
Expand Down
2 changes: 1 addition & 1 deletion web/app/components/ProfileCombobox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ export default function ProfileCombobox({ id: providedId, value, onChange, optio
ref={triggerRef}
onClick={() => (open ? handleClose() : handleOpen())}
onKeyDown={handleKeyDown}
className="w-full bg-[#141414] border-2 border-border-muted px-3 py-2 text-left flex items-center justify-between text-[12px] min-h-[44px] hover:border-border-strong focus:border-accent"
className="w-full bg-[#141414] border-2 border-border-muted px-3 py-2 text-left flex items-center justify-between text-[12px] min-h-[44px] hover:border-border-strong focus:border-accent focus-visible:outline-2 focus-visible:outline-accent focus-visible:outline-offset-2"
aria-haspopup="listbox"
aria-expanded={open}
aria-controls={open ? listboxId : undefined}
Expand Down
8 changes: 4 additions & 4 deletions web/app/components/ResultCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const ResultHeader = ({ id, title, url, normalizedUrl }: { id: string; title: st
href={url}
target="_blank"
rel="noreferrer"
className="text-accent text-[15px] hover:underline"
className="text-accent text-[15px] hover:underline focus-visible:outline-2 focus-visible:outline-accent focus-visible:outline-offset-2"
>
{title}
</a>
Expand Down Expand Up @@ -76,7 +76,7 @@ export default function ResultCard({ result, onCopy, onHelpfulToggle, helpful }:
<footer className="flex flex-wrap gap-2 text-[11px]">
<button
onClick={handleCopy}
className="px-3 py-2 border-2 border-border-muted hover:border-accent text-text-muted"
className="px-3 py-2 border-2 border-border-muted hover:border-accent text-text-muted focus-visible:outline-2 focus-visible:outline-accent focus-visible:outline-offset-2"
aria-live="polite"
>
{copying ? "Copied" : "Copy markdown"}
Expand All @@ -86,15 +86,15 @@ export default function ResultCard({ result, onCopy, onHelpfulToggle, helpful }:
href={result.url}
target="_blank"
rel="noreferrer"
className="px-3 py-2 border-2 border-border-muted hover:border-accent text-text-muted"
className="px-3 py-2 border-2 border-border-muted hover:border-accent text-text-muted focus-visible:outline-2 focus-visible:outline-accent focus-visible:outline-offset-2"
>
Open
</a>
)}
{onHelpfulToggle && (
<button
onClick={() => onHelpfulToggle(result.id)}
className={`px-3 py-2 border-2 ${
className={`px-3 py-2 border-2 focus-visible:outline-2 focus-visible:outline-accent focus-visible:outline-offset-2 ${
helpful ? "border-accent text-accent" : "border-border-muted text-text-dim"
}`}
aria-pressed={helpful}
Expand Down
8 changes: 4 additions & 4 deletions web/app/components/SearchSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,15 @@ const SearchActions = ({
disabled={loading}
aria-label={loading ? "Fetching results..." : "Fetch results"}
title="Fetch results"
className="bg-accent text-background px-4 py-2 text-[13px] font-bold hover:bg-[#00cc33] disabled:opacity-50 min-w-[60px] min-h-[44px]"
className="bg-accent text-background px-4 py-2 text-[13px] font-bold hover:bg-[#00cc33] disabled:opacity-50 min-w-[60px] min-h-[44px] focus-visible:outline-2 focus-visible:outline-accent focus-visible:outline-offset-2"
>
{loading ? "..." : "Fetch"}
</button>
)}
<button
onClick={onClear}
aria-label="Clear input and results"
className="bg-transparent text-text-dim px-4 py-2 text-[13px] border-2 border-border-muted hover:border-accent hover:text-accent min-h-[44px]"
className="bg-transparent text-text-dim px-4 py-2 text-[13px] border-2 border-border-muted hover:border-accent hover:text-accent min-h-[44px] focus-visible:outline-2 focus-visible:outline-accent focus-visible:outline-offset-2"
>
Clear
</button>
Expand Down Expand Up @@ -122,13 +122,13 @@ export function SearchSection({
aria-invalid={Boolean(error)}
aria-errormessage={error ? "search-error" : undefined}
enterKeyHint="search"
className="w-full bg-transparent text-[20px] sm:text-[24px] text-foreground placeholder:text-text-dim tracking-tight pr-10"
className="w-full bg-transparent text-[20px] sm:text-[24px] text-foreground placeholder:text-text-dim tracking-tight pr-10 focus-visible:outline-2 focus-visible:outline-accent focus-visible:outline-offset-2"
/>
{query && (
<button
type="button"
onClick={handleClearQuery}
className="absolute right-0 top-1/2 -translate-y-1/2 p-2 text-text-dim hover:text-accent transition-colors"
className="absolute right-0 top-1/2 -translate-y-1/2 p-2 text-text-dim hover:text-accent transition-colors focus-visible:outline-2 focus-visible:outline-accent focus-visible:outline-offset-2"
aria-label="Clear query"
>
×
Expand Down
20 changes: 10 additions & 10 deletions web/app/components/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export default function Sidebar({
<button
data-testid="sidebar-toggle"
onClick={() => setSidebarOpen(!sidebarOpen)}
className="hidden lg:flex w-full p-4 items-center justify-between hover:bg-[#141414] transition-colors min-h-[44px]"
className="hidden lg:flex w-full p-4 items-center justify-between hover:bg-[#141414] transition-colors min-h-[44px] focus-visible:outline-2 focus-visible:outline-accent focus-visible:outline-offset-2"
aria-label={sidebarOpen ? "Collapse sidebar" : "Expand sidebar"}
aria-expanded={sidebarOpen}
aria-controls="sidebar-navigation"
Expand All @@ -85,7 +85,7 @@ export default function Sidebar({
Configuration
</span>
<div className="flex items-center gap-2">
<Link href="/settings" className="text-[11px] text-text-muted hover:text-accent">
<Link href="/settings" className="text-[11px] text-text-muted hover:text-accent focus-visible:outline-2 focus-visible:outline-accent focus-visible:outline-offset-2">
Keys
</Link>
<span className="text-[10px] text-text-dim">Hide</span>
Expand All @@ -102,12 +102,12 @@ export default function Sidebar({
Configuration
</span>
<div className="flex items-center gap-3">
<Link href="/settings" className="text-[11px] text-text-muted hover:text-accent">
<Link href="/settings" className="text-[11px] text-text-muted hover:text-accent focus-visible:outline-2 focus-visible:outline-accent focus-visible:outline-offset-2">
Keys
</Link>
<button
onClick={() => { setMobileMenuOpen(false); }}
className="text-[11.5px] text-text-muted hover:text-[#ff4444] focus:text-[#ff4444] font-bold min-h-[30px] px-2 flex items-center focus:outline-none"
className="text-[11.5px] text-text-muted hover:text-[#ff4444] focus:text-[#ff4444] font-bold min-h-[30px] px-2 flex items-center focus:outline-none focus-visible:outline-2 focus-visible:outline-accent focus-visible:outline-offset-2"
aria-label="Close configuration"
>
Close
Expand Down Expand Up @@ -149,7 +149,7 @@ export default function Sidebar({
step="1000"
value={maxChars}
onChange={(e) => setMaxChars(parseInt(e.target.value))}
className="w-full h-1 bg-border-muted accent-accent appearance-none cursor-pointer"
className="w-full h-1 bg-border-muted accent-accent appearance-none cursor-pointer focus-visible:outline-2 focus-visible:outline-accent focus-visible:outline-offset-2"
/>
</div>
</div>
Expand All @@ -167,7 +167,7 @@ export default function Sidebar({
const tooltipId = `provider-hint-${provider.id}`;
const showHint = needsKey || (provider.id === "duckduckgo" && mistralActive);

let buttonClasses = "px-2 py-1 text-[10px] border-2 transition-colors min-h-[36px] ";
let buttonClasses = "px-2 py-1 text-[10px] border-2 transition-colors min-h-[36px] focus-visible:outline-2 focus-visible:outline-accent focus-visible:outline-offset-2 ";
if (isActive) {
if (isManual) {
buttonClasses += "bg-accent text-background border-accent font-bold";
Expand Down Expand Up @@ -227,7 +227,7 @@ export default function Sidebar({
onClick={() => setApiKeysOpen(!apiKeysOpen)}
aria-expanded={apiKeysOpen}
aria-controls="api-keys-panel"
className="text-[11px] text-text-muted hover:text-foreground text-left min-h-[44px] py-2"
className="text-[11px] text-text-muted hover:text-foreground text-left min-h-[44px] py-2 focus-visible:outline-2 focus-visible:outline-accent focus-visible:outline-offset-2"
>
<span aria-hidden="true">{apiKeysOpen ? "▼" : "▶"}</span> API Keys
</button>
Expand All @@ -239,7 +239,7 @@ export default function Sidebar({
type="checkbox"
checked={skipCache}
onChange={(e) => setSkipCache(e.target.checked)}
className="w-5 h-5 bg-[#141414] border-2 border-border-muted accent-accent"
className="w-5 h-5 bg-[#141414] border-2 border-border-muted accent-accent focus-visible:outline-2 focus-visible:outline-accent focus-visible:outline-offset-2"
/>
Skip cache
</label>
Expand All @@ -249,7 +249,7 @@ export default function Sidebar({
type="checkbox"
checked={deepResearch}
onChange={(e) => setDeepResearch(e.target.checked)}
className="w-5 h-5 bg-[#141414] border-2 border-border-muted accent-accent"
className="w-5 h-5 bg-[#141414] border-2 border-border-muted accent-accent focus-visible:outline-2 focus-visible:outline-accent focus-visible:outline-offset-2"
/>
Deep research
</label>
Expand All @@ -267,7 +267,7 @@ export default function Sidebar({
value={value}
onChange={(e) => handleKeyChange(key, e.target.value)}
placeholder={hasServer && !value ? "Using server key" : "sk-..."}
className="bg-[#141414] border-2 border-border-muted px-2 py-2 text-[12px] text-foreground placeholder:text-text-dim focus:border-accent min-h-[44px]"
className="bg-[#141414] border-2 border-border-muted px-2 py-2 text-[12px] text-foreground placeholder:text-text-dim focus:border-accent focus-visible:outline-2 focus-visible:outline-accent focus-visible:outline-offset-2 min-h-[44px]"
/>
</div>
);
Expand Down
Loading