From e74e1eaf709ee4a5998d0fd265949d3170aa5d5e Mon Sep 17 00:00:00 2001 From: duffyduck Date: Thu, 14 May 2026 16:06:21 +0200 Subject: [PATCH] =?UTF-8?q?fix(app):=20URLSearchParams=20crasht=20in=20Her?= =?UTF-8?q?mes=20=E2=80=94=20durch=20Mini-Query-Builder=20ersetzt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Inbox-Crash gefunden via App-Crash-Reporter (commit 21a315c): "URLSearchParams.set is not implemented" at MemoryBrowser → brainApi.listMemories React Native's Hermes-Polyfill kennt zwar new URLSearchParams() aber nicht die .set()-Methode darauf. Pickup-Bug — auf iOS / aelteren Versionen geht's, Stefan's Android-Build crasht. Fix: kleine _qs()-Helper im brainApi.ts der einen Query-String aus einem flachen Object baut, ohne URLSearchParams: _qs({q:'cessna', k:5, type:'fact'}) → "?q=cessna&k=5&type=fact" Plus: undefined/null/empty Werte werden ausgelassen — saubererer als URLSearchParams.set wo man manuell prefilten muss. ErrorBoundary aus 21a315c hat den Crash sauber abgefangen, statt der App-Tot war ne Error-Box im Inbox-Modal mit der vollen Stack-Trace. Stefan konnte den Log via tools/fetch-app-logs.sh holen ohne ADB. Co-Authored-By: Claude Opus 4.7 (1M context) --- android/src/services/brainApi.ts | 44 +++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/android/src/services/brainApi.ts b/android/src/services/brainApi.ts index 5069b6f..e051594 100644 --- a/android/src/services/brainApi.ts +++ b/android/src/services/brainApi.ts @@ -54,6 +54,18 @@ function _newRequestId(): string { return `brain_${Date.now().toString(36)}_${_nextId}`; } +/** Mini-Query-String-Builder ohne URLSearchParams (Hermes-Polyfill kennt + * kein URLSearchParams.set, crasht). Akzeptiert object mit string/number/ + * bool-Values; undefined/null/leere Strings werden ausgelassen. */ +function _qs(params: Record): string { + const parts: string[] = []; + for (const [k, v] of Object.entries(params)) { + if (v === undefined || v === null || v === '') continue; + parts.push(`${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`); + } + return parts.length ? `?${parts.join('&')}` : ''; +} + interface SendOpts { method?: 'GET' | 'POST' | 'PATCH' | 'DELETE'; body?: AnyJson; @@ -119,29 +131,31 @@ export const brainApi = { /** Liste aller Memories, optional nach Type gefiltert. */ listMemories(opts: { type?: string; limit?: number } = {}): Promise { - const qs = new URLSearchParams(); - if (opts.type) qs.set('type', opts.type); - qs.set('limit', String(opts.limit || 500)); - return _send(`/memory/list?${qs.toString()}`); + const qs = _qs({ type: opts.type, limit: opts.limit || 500 }); + return _send(`/memory/list${qs}`); }, /** Volltext-Substring-Suche. */ searchText(q: string, opts: { type?: string; includePinned?: boolean; k?: number } = {}): Promise { - const qs = new URLSearchParams({ q }); - if (opts.type) qs.set('type', opts.type); - qs.set('include_pinned', String(opts.includePinned !== false)); - qs.set('k', String(opts.k || 50)); - return _send(`/memory/search-text?${qs.toString()}`); + const qs = _qs({ + q, + type: opts.type, + include_pinned: opts.includePinned !== false, + k: opts.k || 50, + }); + return _send(`/memory/search-text${qs}`); }, /** Semantische Suche (Embedder). */ searchSemantic(q: string, opts: { type?: string; includePinned?: boolean; k?: number; threshold?: number } = {}): Promise { - const qs = new URLSearchParams({ q }); - if (opts.type) qs.set('type', opts.type); - qs.set('include_pinned', String(opts.includePinned !== false)); - qs.set('k', String(opts.k || 10)); - qs.set('score_threshold', String(opts.threshold ?? 0.30)); - return _send(`/memory/search?${qs.toString()}`); + const qs = _qs({ + q, + type: opts.type, + include_pinned: opts.includePinned !== false, + k: opts.k || 10, + score_threshold: opts.threshold ?? 0.30, + }); + return _send(`/memory/search${qs}`); }, /** Memory anlegen. */