chore: initial project scaffold

This commit is contained in:
Giancarmine Salucci
2026-03-22 17:08:15 +01:00
commit 18437dfa7c
53 changed files with 12002 additions and 0 deletions

View File

@@ -0,0 +1,194 @@
# TRUEREF-0016 — Web UI: Search Explorer
**Priority:** P2
**Status:** Pending
**Depends On:** TRUEREF-0010, TRUEREF-0015
**Blocks:**
---
## Overview
An interactive search interface within the web UI that lets users test the documentation retrieval system directly from the browser. Mirrors the two-step context7 flow: first resolve a library ID, then query documentation. Results are displayed with syntax highlighting. Useful for validating indexing quality and demonstrating the system to stakeholders.
---
## Acceptance Criteria
- [ ] Search page at `/search` with two-step workflow
- [ ] Step 1: Library name input → displays matching libraries with IDs and scores
- [ ] Step 2: Click library → query input → displays ranked snippets
- [ ] Syntax-highlighted code blocks (using a lightweight highlighter)
- [ ] Snippet type badge (code vs info)
- [ ] Breadcrumb display per snippet
- [ ] Token count per snippet and total
- [ ] "Copy as Markdown" button for the full response
- [ ] Library switcher (return to step 1 without full page reload)
- [ ] URL reflects current state (`/search?lib=/facebook/react&q=useState`)
- [ ] No server-side rendering needed for this page (can be client-side)
---
## Page Layout
```
/search
├── Header: "Search Documentation"
├── Step 1: Library Search
│ ├── Input: "Library name..." + Search button
│ └── Results list: library cards with ID, description, snippet count, trust score
│ └── [Click to select]
├── Step 2: Documentation Query (shown after library selected)
│ ├── Selected library badge + "Change" button
│ ├── Input: "What would you like to know?" + Search button
│ └── Results:
│ ├── Token count summary
│ ├── "Copy as Markdown" button
│ └── Snippet list:
│ ├── Code snippets: syntax-highlighted code block
│ └── Info snippets: formatted markdown
└── (loading states + empty states throughout)
```
---
## Component: LibrarySearchResult
```svelte
<!-- src/lib/components/search/LibraryResult.svelte -->
<script lang="ts">
let { result, onSelect } = $props<{
result: { id: string; title: string; description: string; totalSnippets: number; trustScore: number };
onSelect: (id: string) => void;
}>();
</script>
<button
onclick={() => onSelect(result.id)}
class="w-full rounded-xl border border-gray-200 bg-white p-4 text-left shadow-sm hover:border-blue-300 hover:shadow-md transition-all"
>
<div class="flex items-center justify-between">
<span class="font-semibold text-gray-900">{result.title}</span>
<span class="text-xs text-gray-400">Trust {result.trustScore.toFixed(1)}/10</span>
</div>
<p class="font-mono text-xs text-gray-400">{result.id}</p>
{#if result.description}
<p class="mt-1.5 text-sm text-gray-600 line-clamp-2">{result.description}</p>
{/if}
<p class="mt-2 text-xs text-gray-400">{result.totalSnippets.toLocaleString()} snippets</p>
</button>
```
---
## Component: SnippetCard
```svelte
<!-- src/lib/components/search/SnippetCard.svelte -->
<script lang="ts">
import type { Snippet } from '$lib/types';
let { snippet } = $props<{ snippet: Snippet }>();
</script>
<div class="rounded-xl border border-gray-200 bg-white overflow-hidden">
<div class="flex items-center justify-between border-b border-gray-100 px-4 py-2.5">
<div class="flex items-center gap-2">
{#if snippet.type === 'code'}
<span class="rounded bg-purple-100 px-1.5 py-0.5 text-xs text-purple-700">code</span>
{:else}
<span class="rounded bg-blue-100 px-1.5 py-0.5 text-xs text-blue-700">info</span>
{/if}
{#if snippet.title}
<span class="text-sm font-medium text-gray-800">{snippet.title}</span>
{/if}
</div>
<span class="text-xs text-gray-400">{snippet.tokenCount} tokens</span>
</div>
{#if snippet.breadcrumb}
<p class="bg-gray-50 px-4 py-1.5 text-xs text-gray-500 italic">{snippet.breadcrumb}</p>
{/if}
<div class="p-4">
{#if snippet.type === 'code'}
<pre class="overflow-x-auto rounded bg-gray-950 p-4 text-sm text-gray-100"><code>{snippet.content}</code></pre>
{:else}
<div class="prose prose-sm max-w-none text-gray-700">{snippet.content}</div>
{/if}
</div>
</div>
```
---
## Search Page Logic
```svelte
<!-- src/routes/search/+page.svelte -->
<script lang="ts">
import { page } from '$app/stores';
import { goto } from '$app/navigation';
let libraryName = $state('');
let selectedLibraryId = $state<string | null>(null);
let query = $state('');
let libraryResults = $state<LibrarySearchResult[]>([]);
let snippets = $state<Snippet[]>([]);
let loadingLibraries = $state(false);
let loadingSnippets = $state(false);
async function searchLibraries() {
loadingLibraries = true;
const res = await fetch(
`/api/v1/libs/search?libraryName=${encodeURIComponent(libraryName)}&query=${encodeURIComponent(query)}`
);
const data = await res.json();
libraryResults = data.results;
loadingLibraries = false;
}
async function searchDocs() {
if (!selectedLibraryId) return;
loadingSnippets = true;
const url = new URL('/api/v1/context', window.location.origin);
url.searchParams.set('libraryId', selectedLibraryId);
url.searchParams.set('query', query);
const res = await fetch(url);
const data = await res.json();
snippets = data.snippets;
loadingSnippets = false;
// Update URL
goto(`/search?lib=${encodeURIComponent(selectedLibraryId)}&q=${encodeURIComponent(query)}`, {
replaceState: true,
keepFocus: true,
});
}
</script>
```
---
## Syntax Highlighting
Use a minimal, zero-dependency approach for v1 — wrap code blocks in `<pre><code>` with a CSS-based theme. Optionally integrate `highlight.js` (lightweight) as an enhancement:
```typescript
// Optional: lazy-load highlight.js only when code snippets are present
async function highlightCode(code: string, language: string): Promise<string> {
const hljs = await import('highlight.js/lib/core');
// Register only needed languages
return hljs.highlight(code, { language }).value;
}
```
---
## Files to Create
- `src/routes/search/+page.svelte`
- `src/lib/components/search/LibraryResult.svelte`
- `src/lib/components/search/SnippetCard.svelte`
- `src/lib/components/search/SearchInput.svelte`
- `src/lib/utils/copy-to-clipboard.ts`