diff --git a/docs/FINDINGS.md b/docs/FINDINGS.md index 1846c24..3834bae 100644 --- a/docs/FINDINGS.md +++ b/docs/FINDINGS.md @@ -2835,6 +2835,313 @@ Current filter tabs take significant horizontal space (5 buttons). Consolidating --- -**Document Version:** 2.0 -**Last Updated by:** Planner Agent (RECIPE-0009 Iteration 0) +### [Planner] Research Notes - RECIPE-0009 Iteration 1 (2026-02-18) + +**Task:** UI enhancements - footer status bar, icon-only buttons, toggle Add Recipe visibility + +#### Current Homepage UI Structure Analysis + +**Research Date:** 2026-02-18 +**Source:** Analysis of [src/routes/+page.svelte](src/routes/+page.svelte), iteration 0 implementation + +**Current Implementation (Iteration 0)**: + +1. **Connection Status Widget** (lines 369-383): + - Fixed position: bottom-right (`fixed bottom-4 right-4`) + - Shows connection status with colored dot + text label + - Shows last ping timestamp + - Will be REMOVED and replaced with footer bar + +2. **Action Bar** (lines 263-297): + - Filter dropdown (lines 266-276) + - Refresh button with icon + text (lines 277-285) + - Add Recipe button with icon + text (lines 288-297) + - Currently: Add Recipe button ALWAYS visible (iteration 0 requirement) + +3. **Empty State** (lines 310-342): + - Shows when `!loading && filteredItems.length === 0` + - Contains "Add Recipe URL" link + +**Changes Required for Iteration 1**: + +1. Remove floating connection status widget +2. Add footer status bar (icons only) +3. Convert refresh button to icon-only +4. Convert Add Recipe button to icon-only +5. Toggle Add Recipe button visibility (hide when empty, show when has items) + +--- + +#### Footer Status Bar Design - RECIPE-0009 Iteration 1 + +**Research Date:** 2026-02-18 +**Source:** Web PWA patterns, existing codebase styling patterns + +**Design Requirements**: + +- **Position**: Fixed at bottom (`fixed bottom-0 left-0 right-0`) +- **Layout**: Full width with max-width container matching page layout (`max-w-6xl`) +- **Content**: Two sections (notification status left, live updates right) +- **Display**: Icons only, no text labels +- **Accessibility**: title and aria-label attributes on interactive elements +- **Z-index**: `z-50` to ensure visibility above all content +- **Visual**: White background, top border, shadow for lift effect + +**State Integration**: + +Footer needs access to two state sources: + +1. **Notification Status**: Via `pushNotificationManager.getState()` + - Need to add `notificationViewModel` state variable in +page.svelte + - Subscribe to state changes in `onMount` + - Cleanup subscription in `onDestroy` + +2. **Connection Status**: Already exists as `connectionStatus` state + - Reuse existing variable + - States: 'connecting' | 'connected' | 'disconnected' + +**Notification Icon Logic**: + +```typescript +if (!supported || permission === 'denied') { + // Show bell with slash (not supported/denied) + icon = 'bell-slash'; + color = 'text-gray-400'; +} else if (subscribed) { + // Show bell icon (enabled) + icon = 'bell'; + color = 'text-green-600'; +} else { + // Show bell icon (available but not enabled) + icon = 'bell'; + color = 'text-gray-400'; +} +``` + +**Live Update Indicator Logic**: + +```typescript +if (connectionStatus === 'connected') { + dotColor = 'bg-green-400'; + title = 'Live updates active'; +} else if (connectionStatus === 'connecting') { + dotColor = 'bg-yellow-400'; + title = 'Connecting to live updates...'; +} else { + dotColor = 'bg-red-400'; + title = 'Live updates disconnected'; +} +``` + +**Click Behavior**: + +Clicking notification icon scrolls to NotificationSettings component: +```typescript +onclick={() => { + document.querySelector('[data-notification-settings]')?.scrollIntoView({ behavior: 'smooth' }); +}} +``` + +Requires adding `data-notification-settings` attribute to NotificationSettings wrapper. + +--- + +#### Icon-Only Button Patterns - RECIPE-0009 Iteration 1 + +**Research Date:** 2026-02-18 +**Source:** Existing codebase button styles, Tailwind CSS documentation, WCAG 2.1 guidelines + +**Current Button Pattern (with text)**: + +```svelte + +``` + +- Padding: `px-4 py-2` (horizontal + vertical) +- Icon size: `w-4 h-4` (16x16px) +- Spacing: `space-x-2` (gap between icon and text) + +**Icon-Only Button Pattern**: + +```svelte + +``` + +**Changes**: +- Padding: `p-2` (square/circular button) +- Icon size: `w-5 h-5` (20x20px - slightly larger for better visibility) +- Remove: `space-x-2` class (no text to space from) +- Add: `title` attribute (tooltip on hover) +- Add: `aria-label` attribute (screen reader accessibility) + +**Accessibility Requirements** (WCAG 2.1): + +1. **Title Attribute**: Provides tooltip text for sighted users on hover +2. **Aria-label Attribute**: Provides accessible name for screen readers +3. **Minimum Touch Target**: 24x24px recommended (20x20px icon + 8px padding = 36x36px total ✓) +4. **Color Contrast**: Must meet 3:1 ratio for non-text (icons) + +**Examples**: + +Refresh button: +```svelte + +``` + +Add Recipe button: +```svelte + + + +``` + +--- + +#### Add Recipe Button Visibility Logic - RECIPE-0009 Iteration 1 + +**Research Date:** 2026-02-18 +**Source:** context_compact.yaml requirement analysis, UX patterns + +**Iteration 0 Implementation**: +- Add Recipe button ALWAYS visible in controls bar +- Rationale: User complained "do not hide the add recipe component when there are items in the queue" + +**Iteration 1 Requirement**: +> "Toggle "Add Recipe" button visibility in controls bar (hide when queue empty, show when items exist - opposite of placeholder rule)" + +**Interpretation**: + +"Opposite of placeholder rule": +- Placeholder (empty state) shows when: `items.length === 0` +- Add Recipe button in controls shows when: `items.length > 0` (opposite condition) + +**Logic**: + +```svelte +{#if items.length > 0} + + + +{/if} +``` + +**Rationale**: + +1. **Empty State**: When queue is empty, user sees empty state with centered "Add Recipe URL" link +2. **Non-Empty State**: When queue has items, controls bar shows Add Recipe button (icon-only) +3. **No Redundancy**: Button doesn't appear when empty state link is already visible +4. **Consistent Access**: User always has access to "Add Recipe" via either empty state link OR controls bar button + +**UX Benefits**: + +- Cleaner UI when queue is empty (no redundant button) +- Convenient access when queue has items (quick add more recipes) +- Fulfills opposite condition of empty state placeholder + +--- + +#### Svelte 5 Notification State Management + +**Research Date:** 2026-02-18 +**Source:** Existing iteration 0 implementation, [PushNotificationManager.ts](src/lib/client/PushNotificationManager.ts) + +**NotificationState Type**: + +```typescript +interface NotificationState { + supported: boolean; + permission: NotificationPermission; // 'default' | 'granted' | 'denied' + subscribed: boolean; + loading: boolean; + error: string | null; +} +``` + +**State Subscription Pattern**: + +```typescript +// Import type +import type { NotificationState } from '$lib/client/PushNotificationManager'; + +// Declare state +let notificationViewModel = $state(null); + +// Subscribe in onMount +onMount(() => { + // ... existing code ... + + const unsubscribeNotifications = pushNotificationManager.onStateChange((newState) => { + notificationViewModel = newState; + }); + + return () => { + unsubscribeNotifications?.(); + }; +}); +``` + +**Cleanup in onDestroy**: + +Current onDestroy only cleans up `eventSource`. Need to also cleanup notification subscription: + +```typescript +onDestroy(() => { + if (eventSource) { + console.log('[SSE] Closing connection on component destroy'); + eventSource.close(); + connectionStatus = 'disconnected'; + } + // No cleanup needed - handled by onMount return callback +}); +``` + +**Note**: Svelte 5's `onMount` return function handles cleanup automatically when component unmounts. + +**State Access in Footer**: + +Footer component needs null-safe access since initial state is `null`: + +```svelte +{#if notificationViewModel} + {#if !notificationViewModel.supported || notificationViewModel.permission === 'denied'} + + {:else if notificationViewModel.subscribed} + + {:else} + + {/if} +{:else} + + +{/if} +``` + +**Initial State Handling**: + +`pushNotificationManager.onStateChange()` sends initial state immediately on subscription, so `notificationViewModel` will be populated almost instantly after component mount. + +--- + +**Document Version:** 3.0 +**Last Updated by:** Planner Agent (RECIPE-0009 Iteration 1) **Next Update:** Developer Agent