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
|
||||
**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
|
||||
<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
|
||||
|
||||
Reference in New Issue
Block a user