feat(05-02): add search and note widgets with tests

- SearchWidget: provider dropdown, text input, button; opens search in new tab via window.open (D-14/D-15)
- NoteWidget: MDEditor with compact toolbar, debounced autosave (1500ms), AbortController for in-flight cancellation (D-16/D-17/D-18)
- rehype-sanitize enabled for Markdown XSS prevention (T-05-05)
- Widget registry updated with wireSearchWidget/wireNoteWidget (no more placeholders)
- dashboard-api.ts: added fetchSearchProviders, addSearchProvider, removeSearchProvider, signal support on updateWidgetConfig
- 9 new tests passing (search: 5, note: 4)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-24 11:33:01 +02:00
parent 97c13809f5
commit dd0209898b
9 changed files with 1616 additions and 14 deletions
+42
View File
@@ -59,12 +59,54 @@ export async function removeWidget(id: string): Promise<void> {
export async function updateWidgetConfig(
id: string,
config: Record<string, unknown>,
signal?: AbortSignal,
): Promise<void> {
const res = await fetch(`${API_URL}/dashboard/widgets/${id}/config`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ config }),
signal,
});
if (!res.ok) throw new Error('Failed to update widget config');
}
// --- Search Provider API (05-02) ---
export interface SearchProviderResponse {
id: string;
name: string;
urlTemplate: string;
isDefault: boolean;
userId?: string | null;
}
export async function fetchSearchProviders(): Promise<SearchProviderResponse[]> {
const res = await fetch(`${API_URL}/dashboard/search-providers`, {
credentials: 'include',
});
if (!res.ok) throw new Error('Failed to fetch search providers');
return res.json();
}
export async function addSearchProvider(payload: {
name: string;
urlTemplate: string;
}): Promise<SearchProviderResponse> {
const res = await fetch(`${API_URL}/dashboard/search-providers`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify(payload),
});
if (!res.ok) throw new Error('Failed to add search provider');
return res.json();
}
export async function removeSearchProvider(id: string): Promise<void> {
const res = await fetch(`${API_URL}/dashboard/search-providers/${id}`, {
method: 'DELETE',
credentials: 'include',
});
if (!res.ok) throw new Error('Failed to remove search provider');
}