# Execution Plan: Refactor Share Page and Enhance Thumbnails **Outcome Name:** RefactorSharePageAndEnhanceThumbnails **Created:** 2025-12-21 **Status:** Ready for Implementation --- ## Overview This plan addresses three key improvements to the InstaChef PWA: 1. **Component Modularization**: Split the monolithic 306-line share page into focused, reusable components 2. **LLM Health Monitoring**: Add visual health status indicator for the LLM service 3. **Stealthy Thumbnail Extraction**: Enhance thumbnail extraction with Instagram-friendly stealth techniques --- ## Problem Statement ### Current Issues 1. **Share Page Complexity**: The `+page.svelte` file contains 306 lines with mixed concerns (state management, UI rendering, business logic), making it difficult to maintain and test 2. **No LLM Visibility**: Users have no way to know if the LLM service is healthy before attempting extraction 3. **Basic Thumbnail Extraction**: Current screenshot-based approach is detectable and may trigger Instagram's anti-bot measures ### User Impact - Difficult to maintain and extend the share page functionality - Poor user experience when LLM service is down (only discover during extraction) - Risk of Instagram blocking due to detectable automation patterns --- ## Technical Context ### Current Architecture **Frontend:** - Svelte 5.43.8 with modern runes (`$state`, `$derived`, `$effect`) - TailwindCSS 4.1.17 for styling - Share page uses snippets for UI modularity **Backend:** - Playwright 1.56.1 for browser automation - Existing `/api/llm-health` endpoint for service monitoring - `extractThumbnail()` function uses screenshot-based approach ### Hexagonal Architecture Alignment - **Domain**: extraction.ts contains business logic for thumbnail extraction - **Adapters**: - Primary (Driving): Svelte components, API routes - Secondary (Driven): Playwright Page interface - **Ports**: Clear interfaces between components and domain logic --- ## Stories ### Story 1: Refactor Share Page into Modular Components **Priority:** High **Complexity:** Medium **Estimated Effort:** 4 hours #### Description Extract the current snippets from `+page.svelte` into standalone, reusable Svelte components. This improves maintainability, testability, and follows single responsibility principle. #### Acceptance Criteria - [ ] Create `src/routes/share/components/` directory - [ ] Extract 6 components from current snippets: 1. `UrlInputSection.svelte` - URL input and extraction trigger 2. `ProgressIndicator.svelte` - Loading state display 3. `ExtractedTextViewer.svelte` - Collapsible text preview 4. `RecipeCard.svelte` - Recipe display with Tandoor integration 5. `ErrorState.svelte` - Error handling UI 6. `LogViewer.svelte` - System logs display - [ ] Parent `+page.svelte` orchestrates state and passes props to components - [ ] Reduced `+page.svelte` from 306 to ~100 lines - [ ] All components use Svelte 5 runes (`$state`, `$props`) - [ ] Maintain existing functionality with no regressions - [ ] TailwindCSS styling preserved #### Technical Specifications **Component Interfaces:** ```typescript // UrlInputSection.svelte interface Props { targetUrl: string | null; sharedText: string; sharedUrl: string; status: string; onProcess: () => void; } // ProgressIndicator.svelte interface Props { status: string; } // ExtractedTextViewer.svelte interface Props { bodyText: string; } // RecipeCard.svelte interface Props { recipe: Recipe | null; tandoorEnabled: boolean; tandoorImporting: boolean; tandoorError: string | null; onRetry: () => void; onImportToTandoor: () => void; } // ErrorState.svelte interface Props { status: string; bodyText: string; onRetry: () => void; } // LogViewer.svelte interface Props { logs: string[]; currentMethod: string; status: string; } ``` #### Implementation Steps 1. Create `src/routes/share/components/` directory 2. For each component: - Create new `.svelte` file - Extract relevant snippet code - Define props interface using `let { prop1, prop2 } = $props()` - Convert callbacks to prop functions - Preserve TailwindCSS classes 3. Update `+page.svelte`: - Import all components - Remove snippet definitions - Replace `{@render snippet()}` with `` - Pass state and callbacks as props #### Testing Strategy - Visual regression testing (manual verification) - Test each component in isolation - Verify state flow from parent to children - Verify callbacks work correctly - Test with real Instagram URL extraction #### Files Modified - `src/routes/share/+page.svelte` #### Files Created - `src/routes/share/components/UrlInputSection.svelte` - `src/routes/share/components/ProgressIndicator.svelte` - `src/routes/share/components/ExtractedTextViewer.svelte` - `src/routes/share/components/RecipeCard.svelte` - `src/routes/share/components/ErrorState.svelte` - `src/routes/share/components/LogViewer.svelte` - `src/routes/share/components/ThumbnailPreview.svelte` (Story 4) - `src/routes/share/components/LlmHealthIndicator.svelte` (Story 2) --- ### Story 2: Add LLM Health Status Component **Priority:** Medium **Complexity:** Low **Estimated Effort:** 2 hours #### Description Create a component that monitors the LLM service health using the existing `/api/llm-health` endpoint and displays a visual indicator to users. #### Acceptance Criteria - [ ] Create `LlmHealthIndicator.svelte` component - [ ] Component polls `/api/llm-health` every 30 seconds - [ ] Visual indicator shows service status: - 🟢 Green: healthy - 🟡 Yellow: checking/loading - 🔴 Red: unhealthy/error - [ ] Tooltip/hover shows detailed status message - [ ] Polling starts on mount and cleans up on unmount - [ ] Component is non-blocking (doesn't prevent extraction) - [ ] Integrated into share page header area #### Technical Specifications **API Contract:** ```typescript // GET /api/llm-health response { status: 'healthy' | 'unhealthy' | 'error'; message: string; } ``` **Component Interface:** ```typescript // LlmHealthIndicator.svelte interface Props { pollInterval?: number; // default: 30000ms } interface HealthState { status: 'checking' | 'healthy' | 'unhealthy' | 'error'; message: string; lastChecked: Date | null; } ``` **Implementation Pattern:** ```svelte
{#if health.status === 'checking'} 🟡 Checking LLM... {:else if health.status === 'healthy'} 🟢 LLM Ready {:else if health.status === 'unhealthy'} 🔴 LLM Unavailable {:else} 🔴 LLM Error {/if}
{health.lastChecked ? `Last: ${health.lastChecked.toLocaleTimeString()}` : ''}
``` #### Implementation Steps 1. Create `src/routes/share/components/LlmHealthIndicator.svelte` 2. Implement health checking logic with polling 3. Add visual status indicator with appropriate colors 4. Implement cleanup in `$effect` return 5. Add component to share page header 6. Test polling behavior and visual states #### Testing Strategy - Test all health states (checking, healthy, unhealthy, error) - Verify polling interval works correctly - Verify cleanup on component unmount - Test network error handling - Manual testing with LM Studio running/stopped #### Files Created - `src/routes/share/components/LlmHealthIndicator.svelte` #### Files Modified - `src/routes/share/+page.svelte` (add health indicator to header) --- ### Story 3: Enhance Thumbnail Extraction with Stealth Techniques **Priority:** High **Complexity:** High **Estimated Effort:** 6 hours #### Description Replace the basic screenshot-based thumbnail extraction with a multi-layered stealth approach that tries less detectable methods first, falling back to screenshots only when necessary. #### Acceptance Criteria - [ ] Implement `extractThumbnailStealth()` function in `extraction.ts` - [ ] Try 4 extraction methods in order: 1. Meta tags (og:image, twitter:image) 2. Video poster attribute 3. Instagram window data structures 4. Screenshot fallback (improved) - [ ] Each method logged for debugging - [ ] Return base64 data URI for consistency - [ ] No new dependencies added - [ ] Backward compatible with existing code - [ ] Handle all edge cases (missing elements, CORS, etc.) - [ ] Add 'thumbnail' to ProgressEventType union - [ ] Emit progress event when thumbnail is extracted - [ ] Frontend receives thumbnail data in real-time via SSE #### Technical Specifications **Research Findings:** From web research, Instagram thumbnails can be extracted using: 1. **Meta Tags** (Most Stealthy): - `og:image` - OpenGraph thumbnail - `twitter:image` - Twitter card thumbnail - No detection risk, reads HTML only 2. **Video Poster Attribute**: - `