From 22bf4c101464b24d4f77bc2fac8d4141b84407d0 Mon Sep 17 00:00:00 2001 From: Giancarmine Salucci Date: Mon, 23 Mar 2026 09:07:13 +0100 Subject: [PATCH] feat(TRUEREF-0016): implement web UI search explorer - Two-step search workflow: resolve library then query documentation - URL state sync (/search?lib=...&q=...) - LibraryResult, SnippetCard, SearchInput components - Code/info snippet display with breadcrumbs and token counts - Copy-as-markdown button for full response Co-Authored-By: Claude Sonnet 4.6 --- .../components/search/LibraryResult.svelte | 33 ++ src/lib/components/search/SearchInput.svelte | 56 ++++ src/lib/components/search/SnippetCard.svelte | 55 ++++ src/lib/utils/copy-to-clipboard.ts | 36 +++ src/routes/search/+page.svelte | 292 ++++++++++++++++++ src/routes/search/+page.ts | 1 + 6 files changed, 473 insertions(+) create mode 100644 src/lib/components/search/LibraryResult.svelte create mode 100644 src/lib/components/search/SearchInput.svelte create mode 100644 src/lib/components/search/SnippetCard.svelte create mode 100644 src/lib/utils/copy-to-clipboard.ts create mode 100644 src/routes/search/+page.svelte create mode 100644 src/routes/search/+page.ts diff --git a/src/lib/components/search/LibraryResult.svelte b/src/lib/components/search/LibraryResult.svelte new file mode 100644 index 0000000..febebf8 --- /dev/null +++ b/src/lib/components/search/LibraryResult.svelte @@ -0,0 +1,33 @@ + + + diff --git a/src/lib/components/search/SearchInput.svelte b/src/lib/components/search/SearchInput.svelte new file mode 100644 index 0000000..df70a71 --- /dev/null +++ b/src/lib/components/search/SearchInput.svelte @@ -0,0 +1,56 @@ + + +
+ + +
diff --git a/src/lib/components/search/SnippetCard.svelte b/src/lib/components/search/SnippetCard.svelte new file mode 100644 index 0000000..3bf7dc0 --- /dev/null +++ b/src/lib/components/search/SnippetCard.svelte @@ -0,0 +1,55 @@ + + +
+
+
+ {#if isCode} + code + {:else} + info + {/if} + {#if title} + {title} + {/if} +
+ {tokenCount} tokens +
+ + {#if breadcrumb} +

{breadcrumb}

+ {/if} + +
+ {#if isCode} +
{content}
+ {:else} +
{content}
+ {/if} +
+
diff --git a/src/lib/utils/copy-to-clipboard.ts b/src/lib/utils/copy-to-clipboard.ts new file mode 100644 index 0000000..dd86b87 --- /dev/null +++ b/src/lib/utils/copy-to-clipboard.ts @@ -0,0 +1,36 @@ +/** + * Copy text to the system clipboard. + * + * Falls back gracefully when the Clipboard API is unavailable (e.g. non-HTTPS + * or older browsers) by using a temporary textarea element. + * + * @returns true when the copy succeeded, false otherwise. + */ +export async function copyToClipboard(text: string): Promise { + if (typeof navigator === 'undefined') return false; + + if (navigator.clipboard?.writeText) { + try { + await navigator.clipboard.writeText(text); + return true; + } catch { + // Fall through to legacy path. + } + } + + // Legacy fallback via execCommand. + try { + const textarea = document.createElement('textarea'); + textarea.value = text; + textarea.style.position = 'fixed'; + textarea.style.opacity = '0'; + document.body.appendChild(textarea); + textarea.focus(); + textarea.select(); + const ok = document.execCommand('copy'); + document.body.removeChild(textarea); + return ok; + } catch { + return false; + } +} diff --git a/src/routes/search/+page.svelte b/src/routes/search/+page.svelte new file mode 100644 index 0000000..d585f8f --- /dev/null +++ b/src/routes/search/+page.svelte @@ -0,0 +1,292 @@ + + + + Search — TrueRef + + +
+
+

Search Documentation

+

+ Find libraries and query their indexed documentation. +

+
+ + {#if step === 'library'} + + + +
+

Step 1 — Select a library

+

+ Enter a library name to find matching indexed repositories. +

+ + +
+ + {#if libraryError} +
+

{libraryError}

+
+ {/if} + + {#if loadingLibraries} +
+ Searching libraries... +
+ {:else if hasLibraryResults} +
+ {#each libraryResults as result (result.id)} + + {/each} +
+ {:else if libraryName && !loadingLibraries && libraryResults.length === 0 && !libraryError} + + {/if} + {:else} + + + +
+
+
+ + Selected + + {selectedLibraryTitle} +
+ +
+ +

Step 2 — Query documentation

+

Ask a question about this library.

+ + +
+ + {#if snippetError} +
+

{snippetError}

+
+ {/if} + + {#if loadingSnippets} +
+ Fetching documentation snippets... +
+ {:else if hasSnippets} + +
+

+ {snippets.length} snippet{snippets.length === 1 ? '' : 's'} · + {totalTokens.toLocaleString()} tokens +

+ +
+ + +
+ {#each snippets as snippet, i (i)} + + {/each} +
+ {:else if query && !loadingSnippets && snippets.length === 0 && !snippetError} +
+

No snippets found for that query.

+

Try a different question or select another library.

+
+ {/if} + {/if} +
diff --git a/src/routes/search/+page.ts b/src/routes/search/+page.ts new file mode 100644 index 0000000..a3d1578 --- /dev/null +++ b/src/routes/search/+page.ts @@ -0,0 +1 @@ +export const ssr = false;