Skip to content

Commit 6620576

Browse files
committed
PATCH Degrade palette search when the query route is not mounted
Signed-off-by: ParleSec <mason@masonparle.com>
1 parent a27269c commit 6620576

2 files changed

Lines changed: 82 additions & 1 deletion

File tree

frontend/src/components/palette/Palette.tsx

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
} from '@/components/palette/paletteUrlState'
2020
import { resolveFlowHandoff } from '@/components/palette/runDispatch'
2121
import {
22+
usePaletteEndpointAvailable,
2223
usePaletteQuery,
2324
usePlatformShortcutLabel,
2425
useRecentSearches,
@@ -143,11 +144,14 @@ export function Palette({ variant, onClose, autoFocus }: PaletteProps) {
143144
return () => window.clearInterval(handle)
144145
}, [q.length, placeholderPool.length])
145146

147+
const paletteAvailable = usePaletteEndpointAvailable()
148+
146149
const { data, loading, error } = usePaletteQuery({
147150
q,
148151
scope,
149152
filters,
150-
enabled: q.trim().length > 0 || filters.length > 0,
153+
enabled:
154+
paletteAvailable && (q.trim().length > 0 || filters.length > 0),
151155
})
152156

153157
const results = useMemo(() => data?.results ?? [], [data])
@@ -537,6 +541,31 @@ export function Palette({ variant, onClose, autoFocus }: PaletteProps) {
537541
? 'w-full'
538542
: 'flex min-h-0 w-full max-w-2xl flex-1 flex-col'
539543

544+
if (variant === 'homepage' && !paletteAvailable) {
545+
return (
546+
<div className="w-full" role="search" aria-labelledby={headingId}>
547+
<h2 id={headingId} className="sr-only">
548+
Explore protocols
549+
</h2>
550+
<p className="mb-2 text-xs text-surface-500">
551+
Content search requires a palette index on the backend (
552+
<code className="text-surface-400">SHOWCASE_PALETTE_DB</code>
553+
). See the{' '}
554+
<a href="https://docs.protocolsoup.com/deploy/palette-index/" className="text-amber-300/90 hover:underline">
555+
palette index
556+
</a>{' '}
557+
deployment guide.
558+
</p>
559+
<EmptyState
560+
variant="homepage"
561+
onPick={setEntryChip}
562+
recent={recentSearches}
563+
onClearRecent={clearRecent}
564+
/>
565+
</div>
566+
)
567+
}
568+
540569
return (
541570
<div className={shellClass} role="search" aria-labelledby={headingId}>
542571
<h2 id={headingId} className="sr-only">Palette search</h2>

frontend/src/components/palette/usePaletteQuery.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,20 @@ export function usePaletteQuery({
6464
body: JSON.stringify(body),
6565
})
6666
if (!response.ok) {
67+
if (response.status === 404 || response.status === 405) {
68+
// Route not mounted — degrade silently; production always exposes this route.
69+
if (seq === requestSeq.current) {
70+
setData({
71+
query: q,
72+
results: [],
73+
refinement_chips: [],
74+
resolved_aliases: [],
75+
total_candidates: 0,
76+
elapsed_micros: 0,
77+
})
78+
}
79+
return
80+
}
6781
throw new Error(`palette query failed (${response.status})`)
6882
}
6983
const payload = (await response.json()) as PaletteResponse
@@ -101,6 +115,44 @@ export function usePaletteQuery({
101115
return { data, loading, error, refresh: issueQuery }
102116
}
103117

118+
let paletteEndpointCache: boolean | null = null
119+
120+
/**
121+
* Probes GET /api once to see whether endpoints.palette is advertised.
122+
* Used to hide live search on self-hosted deployments without an index.
123+
*/
124+
export function usePaletteEndpointAvailable(): boolean {
125+
const [available, setAvailable] = useState(paletteEndpointCache ?? true)
126+
127+
useEffect(() => {
128+
if (paletteEndpointCache !== null) {
129+
setAvailable(paletteEndpointCache)
130+
return
131+
}
132+
let cancelled = false
133+
void (async () => {
134+
try {
135+
const response = await fetch('/api')
136+
if (!response.ok) {
137+
paletteEndpointCache = false
138+
} else {
139+
const payload = (await response.json()) as { endpoints?: { palette?: string } }
140+
paletteEndpointCache = Boolean(payload.endpoints?.palette)
141+
}
142+
} catch {
143+
paletteEndpointCache = false
144+
}
145+
if (!cancelled) {
146+
setAvailable(paletteEndpointCache ?? false)
147+
}
148+
})()
149+
return () => {
150+
cancelled = true
151+
}
152+
}, [])
153+
154+
return available
155+
}
104156

105157
// usePlatformShortcutLabel
106158
export function usePlatformShortcutLabel(): string | null {

0 commit comments

Comments
 (0)