chore(RECIPE-0009): update FINDINGS.md for iteration 1 planning
This commit is contained in:
311
docs/FINDINGS.md
311
docs/FINDINGS.md
@@ -2835,6 +2835,313 @@ Current filter tabs take significant horizontal space (5 buttons). Consolidating
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Document Version:** 2.0
|
### [Planner] Research Notes - RECIPE-0009 Iteration 1 (2026-02-18)
|
||||||
**Last Updated by:** Planner Agent (RECIPE-0009 Iteration 0)
|
|
||||||
|
**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
|
||||||
|
<button class="flex items-center space-x-2 px-4 py-2 ...">
|
||||||
|
<svg class="w-4 h-4" ... />
|
||||||
|
<span>Button Text</span>
|
||||||
|
</button>
|
||||||
|
```
|
||||||
|
|
||||||
|
- 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
|
||||||
|
<button
|
||||||
|
title="Button description"
|
||||||
|
aria-label="Button description"
|
||||||
|
class="p-2 ..."
|
||||||
|
>
|
||||||
|
<svg class="w-5 h-5" ... />
|
||||||
|
</button>
|
||||||
|
```
|
||||||
|
|
||||||
|
**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
|
||||||
|
<button
|
||||||
|
title="Refresh queue"
|
||||||
|
aria-label="Refresh queue"
|
||||||
|
class="p-2 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 ..."
|
||||||
|
>
|
||||||
|
<svg class="w-5 h-5" ... />
|
||||||
|
</button>
|
||||||
|
```
|
||||||
|
|
||||||
|
Add Recipe button:
|
||||||
|
```svelte
|
||||||
|
<a
|
||||||
|
href="/share"
|
||||||
|
title="Add recipe URL"
|
||||||
|
aria-label="Add recipe URL"
|
||||||
|
class="p-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 ..."
|
||||||
|
>
|
||||||
|
<svg class="w-5 h-5" ... />
|
||||||
|
</a>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 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}
|
||||||
|
<a href="/share" title="Add recipe URL" aria-label="Add recipe URL" ...>
|
||||||
|
<svg ... />
|
||||||
|
</a>
|
||||||
|
{/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<NotificationState | null>(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'}
|
||||||
|
<!-- Show disabled icon -->
|
||||||
|
{:else if notificationViewModel.subscribed}
|
||||||
|
<!-- Show enabled icon -->
|
||||||
|
{:else}
|
||||||
|
<!-- Show available icon -->
|
||||||
|
{/if}
|
||||||
|
{:else}
|
||||||
|
<!-- Loading state - show gray icon -->
|
||||||
|
<svg class="w-5 h-5 text-gray-400" ... />
|
||||||
|
{/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
|
**Next Update:** Developer Agent
|
||||||
|
|||||||
Reference in New Issue
Block a user