diff --git a/plans/015-serper-integration-ci-failure-adr.md b/plans/015-serper-integration-ci-failure-adr.md
new file mode 100644
index 00000000..10ffc911
--- /dev/null
+++ b/plans/015-serper-integration-ci-failure-adr.md
@@ -0,0 +1,20 @@
+# ADR 015: Serper Integration CI Failure Triage
+
+## Status
+Blocked
+
+## Context
+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
+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.
+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)
+1. Configure dummy/mocked query responses for the release and integration tests when live keys are absent.
+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
+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.
diff --git a/plans/GOAP_STATE.md b/plans/GOAP_STATE.md
index e3d478b8..571a32b7 100644
--- a/plans/GOAP_STATE.md
+++ b/plans/GOAP_STATE.md
@@ -103,3 +103,12 @@ Allowed scopes: `resolver, cli, web, ci, docs, deps, security, release, agents`
- `.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
+- **Status**: Blocked (Pre-existing on main)
+- **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)
diff --git a/web/app/components/History.tsx b/web/app/components/History.tsx
index 265d283c..ca4b7a3d 100644
--- a/web/app/components/History.tsx
+++ b/web/app/components/History.tsx
@@ -106,7 +106,7 @@ export default function History({ onLoad }: HistoryProps) {
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"
>
@@ -124,7 +124,7 @@ 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 && (
×
@@ -152,13 +152,13 @@ export default function History({ onLoad }: HistoryProps) {
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}
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"
diff --git a/web/app/components/MainHeader.tsx b/web/app/components/MainHeader.tsx
index e2b8cb18..0a0255b2 100644
--- a/web/app/components/MainHeader.tsx
+++ b/web/app/components/MainHeader.tsx
@@ -19,7 +19,7 @@ export function MainHeader(props: MainHeaderProps) {
{/* Hamburger menu - mobile only */}
Shortcuts
-
+
Help
diff --git a/web/app/components/MetadataBar.tsx b/web/app/components/MetadataBar.tsx
index c218f90b..cfa493b3 100644
--- a/web/app/components/MetadataBar.tsx
+++ b/web/app/components/MetadataBar.tsx
@@ -53,7 +53,7 @@ export function MetadataBar({
@@ -61,7 +61,7 @@ export function MetadataBar({
@@ -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"}
diff --git a/web/app/components/ProfileCombobox.tsx b/web/app/components/ProfileCombobox.tsx
index fa0e4ee9..2f2c92cf 100644
--- a/web/app/components/ProfileCombobox.tsx
+++ b/web/app/components/ProfileCombobox.tsx
@@ -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}
diff --git a/web/app/components/ResultCard.tsx b/web/app/components/ResultCard.tsx
index a32403f3..8820cceb 100644
--- a/web/app/components/ResultCard.tsx
+++ b/web/app/components/ResultCard.tsx
@@ -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}
@@ -76,7 +76,7 @@ export default function ResultCard({ result, onCopy, onHelpfulToggle, helpful }:
{copying ? "Copied" : "Copy markdown"}
@@ -86,7 +86,7 @@ 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
@@ -94,7 +94,7 @@ export default function ResultCard({ result, onCopy, onHelpfulToggle, helpful }:
{onHelpfulToggle && (
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}
diff --git a/web/app/components/SearchSection.tsx b/web/app/components/SearchSection.tsx
index c5553cf4..52e3bacd 100644
--- a/web/app/components/SearchSection.tsx
+++ b/web/app/components/SearchSection.tsx
@@ -38,7 +38,7 @@ 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"}
@@ -46,7 +46,7 @@ const SearchActions = ({
Clear
@@ -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 && (
×
diff --git a/web/app/components/Sidebar.tsx b/web/app/components/Sidebar.tsx
index 47731989..d1dc5731 100644
--- a/web/app/components/Sidebar.tsx
+++ b/web/app/components/Sidebar.tsx
@@ -74,7 +74,7 @@ export default function Sidebar({
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"
@@ -85,7 +85,7 @@ export default function Sidebar({
Configuration
-
+
Keys
Hide
@@ -102,12 +102,12 @@ export default function Sidebar({
Configuration
-
+
Keys
{ 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
@@ -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"
/>
@@ -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";
@@ -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"
>
{apiKeysOpen ? "▼" : "▶"} API Keys
@@ -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
@@ -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
@@ -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]"
/>
);