--- phase: 05-dashboard-calendar plan: 02 subsystem: dashboard tags: [dashboard, widgets, search, notes, markdown, settings, prisma, i18n] dependency_graph: requires: [DashboardModule, WidgetRegistry, SettingsLayout] provides: [SearchWidget, NoteWidget, WidgetSettingsPanel, SearchProviderForm, SearchProviderModel] affects: [apps/api/prisma/schema.prisma, apps/web/src/components/dashboard/widget-registry.tsx, apps/web/src/lib/dashboard-api.ts, apps/web/src/messages/en.json, apps/web/src/messages/de.json] tech_stack: added: ["@uiw/react-md-editor@4.1.1", rehype-sanitize] patterns: [AbortController for in-flight request cancellation, debounced autosave, wireWidget deferred wiring, SearchProvider defaults-as-constants merged with user rows] key_files: created: - apps/web/src/components/dashboard/widgets/search-widget.tsx - apps/web/src/components/dashboard/widgets/note-widget.tsx - apps/web/src/components/dashboard/widgets/search-widget.test.tsx - apps/web/src/components/dashboard/widgets/note-widget.test.tsx - apps/web/src/app/(portal)/settings/dashboard/page.tsx - apps/web/src/components/settings/widget-settings-panel.tsx - apps/web/src/components/settings/search-provider-form.tsx - apps/api/src/dashboard/dto/create-search-provider.dto.ts modified: - apps/web/src/components/dashboard/widget-registry.tsx - apps/web/src/lib/dashboard-api.ts - apps/web/package.json - apps/web/src/messages/en.json - apps/web/src/messages/de.json - apps/api/prisma/schema.prisma - apps/api/src/dashboard/dashboard.controller.ts - apps/api/src/dashboard/dashboard.service.ts decisions: - "useRef with explicit undefined initial value for React 19 strict TypeScript compatibility" - "SearchProvider defaults as code constants merged with user DB rows (no seed migration needed)" - "typeof narrowing for unknown config.title to satisfy ReactNode type constraint" - "prisma db push via host-to-container IP (DB port not exposed to host)" metrics: duration: 4min completed: 2026-06-24T12:57:00Z tasks: 4 files_created: 8 files_modified: 8 --- # Phase 05 Plan 02: Search + Notes Widgets + Widget Settings Summary Search widget with provider dropdown and new-tab open via window.open(url, '_blank', 'noopener,noreferrer'), Notes widget with MDEditor + rehype-sanitize + debounced autosave using AbortController, SearchProvider Prisma model with defaults-as-constants CRUD, and Widget Settings panel (Settings > Dashboard) with clock timezone/date toggle, note title editor, and custom search provider management with {query} client-side validation. ## Tasks Completed | Task | Name | Commit | Key Files | |------|------|--------|-----------| | 1 (RED) | Failing tests for search + note widgets | 97c1380 | search-widget.test.tsx, note-widget.test.tsx | | 1 (GREEN) | Search + Notes widgets implementation | dd02098 | search-widget.tsx, note-widget.tsx, widget-registry.tsx, dashboard-api.ts | | 2 | SearchProvider backend (model + CRUD + defaults) | 38dcfdf | schema.prisma, dashboard.controller.ts, dashboard.service.ts, create-search-provider.dto.ts | | 3 | Widget settings panel (Settings > Dashboard) | 7e2592b | page.tsx, widget-settings-panel.tsx, search-provider-form.tsx, en.json, de.json | | 4 | Prisma schema push | (no code change) | Database SearchProvider table created | ## Verification Results - `prisma validate`: PASS - `tsc --noEmit` (apps/api): PASS - `tsc --noEmit` (apps/web): PASS - `vitest run src/components/dashboard`: 14/14 PASS (4 test files) - `prisma db push`: "database is already in sync" (second run confirms) ## Deviations from Plan ### Auto-fixed Issues **1. [Rule 1 - Bug] useRef missing initial value for React 19 strict mode** - **Found during:** Task 3 (tsc --noEmit verification) - **Issue:** `useRef>()` without an argument fails in React 19 with strict TypeScript -- `Expected 1 arguments, but got 0` - **Fix:** Changed to `useRef | undefined>(undefined)` for both timerRef and abortRef - **Files modified:** apps/web/src/components/dashboard/widgets/note-widget.tsx - **Commit:** 7e2592b (included in Task 3 commit) **2. [Rule 1 - Bug] unknown type not assignable to ReactNode in JSX** - **Found during:** Task 3 (tsc --noEmit verification) - **Issue:** `widget.config.title` is `unknown`, and using it in a `&&` JSX chain caused TS2322: `Type 'unknown' is not assignable to type 'ReactNode'` - **Fix:** Changed to `typeof widget.config.title === 'string' && widget.config.title !== ''` narrowing - **Files modified:** apps/web/src/components/settings/widget-settings-panel.tsx - **Commit:** 7e2592b (included in Task 3 commit) **3. [Rule 2 - Missing critical] i18n keys for search provider management** - **Found during:** Task 3 (search-provider-form.tsx creation) - **Issue:** SearchProviderForm uses t('search.providers'), t('search.addProvider'), t('search.addProviderButton') which did not exist in the translation files - **Fix:** Added 3 new i18n keys to both en.json and de.json - **Files modified:** apps/web/src/messages/en.json, apps/web/src/messages/de.json - **Commit:** 7e2592b (included in Task 3 commit) ## Decisions Made | Decision | Context | Outcome | |----------|---------|---------| | useRef explicit undefined init | React 19 strict TS requires argument | Added `undefined` initial value with union type | | typeof narrowing for config.title | unknown not assignable to ReactNode in JSX | Used typeof string check instead of truthiness | | DB push via container IP | DB port not exposed to host, docker exec path doesn't have schema | Used Docker internal IP 172.22.0.2 with DATABASE_URL from host | ## Known Stubs | File | Line | Stub | Reason | |------|------|------|--------| | widget-registry.tsx | calendar entry (line 181) | PlaceholderWidget for calendar | Implemented in plan 05-03 | ## Self-Check: PASSED