- Updated Dockerfile base image: node:22-alpine → node:24-alpine - Regenerated package-lock.json to sync with package.json Tailwind v4 - Docker build now completes successfully (npm ci no longer fails) - Docker compose with .env.example runs without errors - Application verified accessible and functional in Docker - Instagram extraction pipeline tested successfully Resolves package-lock.json sync issue that blocked iteration 1.
958 lines
33 KiB
Markdown
958 lines
33 KiB
Markdown
# Findings & Research Documentation
|
||
|
||
**Last Updated:** 2026-02-15T00:00:00.000Z
|
||
**JIRA:** RECIPE-0001
|
||
**Status:** Initialized
|
||
|
||
---
|
||
|
||
## Purpose
|
||
|
||
This document tracks research findings, analysis results, and technical discoveries made during development. Each agent (Planner, Developer, Reviewer) appends findings as they work through the pipeline.
|
||
|
||
---
|
||
|
||
## Initial Codebase Analysis
|
||
|
||
### Language & Framework
|
||
- **Primary Language**: TypeScript 5.9.3
|
||
- **Framework**: SvelteKit 2.48.5 with Svelte 5.43.8
|
||
- **Runtime**: Node.js 22+
|
||
- **Package Manager**: npm
|
||
|
||
### Project Type
|
||
Progressive Web Application (PWA) for extracting recipes from Instagram posts and uploading them to Tandoor Recipe Manager.
|
||
|
||
### Architecture Style
|
||
**Hexagonal Architecture** (Ports and Adapters):
|
||
- Domain logic in `src/lib/server/`
|
||
- External system adapters: Instagram, Tandoor, LLM, Browser
|
||
- Clear separation between client and server code
|
||
|
||
### Key Technical Components
|
||
1. **Queue Management System**: In-memory FIFO queue with async processing
|
||
2. **Three-Phase Pipeline**: Extraction → Parsing → Uploading
|
||
3. **Real-Time Updates**: Server-Sent Events (SSE) for progress tracking
|
||
4. **Push Notifications**: Web Push API for background notifications
|
||
5. **PWA Features**: Service worker, manifest, install prompts
|
||
|
||
### Design Patterns Identified
|
||
- **Singleton**: QueueManager, QueueProcessor, PushNotificationService
|
||
- **Factory**: createLLM(), createBrowserContext(), initializeBrowser()
|
||
- **Observer**: Queue subscription system, SSE streaming
|
||
- **Adapter**: Instagram, Tandoor, LLM, Browser adapters
|
||
- **Strategy**: Multiple extraction methods with fallback
|
||
|
||
### Dependencies Overview
|
||
**Production** (6 dependencies):
|
||
- Browser automation: `playwright`
|
||
- LLM integration: `openai`
|
||
- Utilities: `uuid`, `date-fns`, `zod`
|
||
|
||
**Development** (26+ dependencies):
|
||
- Framework: `@sveltejs/kit`, `svelte`, `vite`
|
||
- Testing: `vitest`, `@vitest/browser-playwright`
|
||
- Styling: `tailwindcss`
|
||
- Tooling: `typescript`, `eslint`, `prettier`
|
||
|
||
### File Structure
|
||
```
|
||
52 total TypeScript/JavaScript files
|
||
├── 39 TypeScript files (.ts)
|
||
├── 10+ Svelte components (.svelte)
|
||
├── 3 JavaScript config files (.js)
|
||
└── Multiple test files (.spec.ts)
|
||
```
|
||
|
||
### Code Quality Indicators
|
||
- **Strict TypeScript**: `strict: true` enabled
|
||
- **Comprehensive Testing**: 138 tests across unit, integration, and browser tests
|
||
- **Linting**: ESLint with TypeScript and Svelte plugins
|
||
- **Formatting**: Prettier with Svelte and Tailwind plugins
|
||
- **Type Safety**: Zod schemas for runtime validation
|
||
|
||
### Environment Configuration
|
||
Required variables:
|
||
- `OPENAI_API_KEY` - LLM access
|
||
- `TANDOOR_URL` - Recipe manager URL (optional)
|
||
- `TANDOOR_TOKEN` - API authentication (optional)
|
||
- `QUEUE_CONCURRENCY` - Processing limit (default: 2)
|
||
- `QUEUE_MAX_RETRIES` - Retry attempts (default: 3)
|
||
|
||
### Deployment Setup
|
||
- **Docker**: Dockerfile with Node.js 22 Alpine + Chromium
|
||
- **HTTPS**: Local SSL certificates for PWA features
|
||
- **Production**: Node.js adapter for SvelteKit
|
||
|
||
### Notable Features
|
||
1. **Multi-Method Extraction**: 4-strategy cascade with intelligent fallback
|
||
2. **Progress Tracking**: Real-time callbacks throughout extraction pipeline
|
||
3. **Thumbnail Validation**: HTTP status code checking for image URLs
|
||
4. **Retry Logic**: Configurable retry attempts for failed extractions
|
||
5. **Scheduler**: Background task execution with authentication
|
||
|
||
---
|
||
|
||
## Technical Debt & Opportunities
|
||
|
||
### Identified Issues
|
||
1. **Deprecated Endpoints**: `/api/extract` returns 410 Gone (migration helper)
|
||
2. **In-Memory Queue**: No persistence - items lost on server restart
|
||
3. **Single Instance**: Queue state not shared across multiple server instances
|
||
|
||
### Potential Improvements
|
||
1. **Queue Persistence**: Redis or database-backed queue for durability
|
||
2. **Horizontal Scaling**: Shared queue state for multi-instance deployments
|
||
3. **Rate Limiting**: Instagram request throttling to avoid blocks
|
||
4. **Caching**: Extracted content caching to reduce redundant processing
|
||
|
||
---
|
||
|
||
## Research Findings
|
||
|
||
*This section will be populated by the Planner agent during task analysis.*
|
||
|
||
### [Planner] Research Notes - RECIPE-0001 (2026-02-15)
|
||
|
||
**Task:** Fix model loading issue and frontend error display
|
||
|
||
#### Issue 1: Model Loading - "400 No models loaded"
|
||
**Research Date:** 2026-02-15
|
||
**Source:** Stack trace analysis, OpenAI SDK documentation, LM Studio/LiteLLM API patterns
|
||
|
||
**Problem Analysis:**
|
||
- Error occurs at `detectRecipe()` in [src/lib/server/parser.ts](src/lib/server/parser.ts#L30)
|
||
- OpenAI-compatible APIs (LM Studio, LiteLLM, Ollama, etc.) often require models to be explicitly loaded
|
||
- Current implementation assumes model is already loaded
|
||
- Error message contains provider-specific instructions ("use the 'lms load' command")
|
||
|
||
**OpenAI-Compatible Model Loading Patterns:**
|
||
1. **LM Studio**: Uses `/v1/models` endpoint to list available models
|
||
- Loaded models appear in response with `"id": "model-name"`
|
||
- No programmatic loading endpoint (manual load in UI)
|
||
|
||
2. **LiteLLM**: Uses `/v1/models` to list loaded models
|
||
- Models must be configured in server startup
|
||
- No dynamic loading endpoint
|
||
|
||
3. **Ollama**: Uses `/api/tags` for model list and `/api/pull` for loading
|
||
- Different API structure (not `/v1` prefix)
|
||
|
||
4. **Generic OpenAI-compatible**: Most follow OpenAI's `/v1/models` endpoint
|
||
- No standard for dynamic model loading
|
||
- Usually require pre-configuration
|
||
|
||
**Solution Approach:**
|
||
- Check if model exists via `client.models.list()`
|
||
- If model not found/loaded, provide clear user-facing error
|
||
- Remove provider-specific error messages
|
||
- Add notification when model check succeeds
|
||
- Consider future enhancement: detect provider type and attempt auto-load if supported
|
||
|
||
**Files Affected:**
|
||
- [src/lib/server/llm.ts](src/lib/server/llm.ts) - Add model availability check
|
||
- [src/lib/server/parser.ts](src/lib/server/parser.ts) - Handle model not loaded error
|
||
- [src/lib/server/queue/QueueProcessor.ts](src/lib/server/queue/QueueProcessor.ts) - User notification
|
||
|
||
---
|
||
|
||
#### Issue 2: Frontend Error Display - "[object Object]"
|
||
**Research Date:** 2026-02-15
|
||
**Source:** Code analysis of QueueItemCard.svelte, types.ts, QueueManager.ts
|
||
|
||
**Problem Analysis:**
|
||
- Error structure is an object: `{ phase, message, recoverable, timestamp }`
|
||
- Frontend displays `{item.error}` directly (line 205 of QueueItemCard.svelte)
|
||
- Svelte renders object.toString() → "[object Object]"
|
||
|
||
**Current Implementation:**
|
||
```typescript
|
||
// types.ts - Error is an object
|
||
error?: {
|
||
phase: ProcessingPhase;
|
||
message: string;
|
||
recoverable: boolean;
|
||
timestamp: string;
|
||
}
|
||
|
||
// QueueItemCard.svelte line 205 - Displays object directly
|
||
<div class="text-sm text-red-700 mt-1">{item.error}</div>
|
||
```
|
||
|
||
**Solution:**
|
||
Change to: `{item.error?.message || item.error}`
|
||
- Handles object error (gets .message)
|
||
- Handles legacy string errors (fallback)
|
||
- Type-safe with optional chaining
|
||
|
||
**Files Affected:**
|
||
- [src/routes/components/QueueItemCard.svelte](src/routes/components/QueueItemCard.svelte#L205) - Display error.message
|
||
|
||
---
|
||
|
||
#### Dependencies & Constraints (from ARCHITECTURE.md)
|
||
- Using `openai@^4.20.0` SDK
|
||
- Environment: `OPENAI_BASE_URL`, `OPENAI_API_KEY`, `LLM_MODEL`
|
||
- Current config example: `http://192.168.1.10:1234/v1` (LM Studio)
|
||
- Must maintain OpenAI-compatible API contract
|
||
- No assumption about specific provider implementation
|
||
|
||
#### Code Style Requirements (from CODE_STYLE.md)
|
||
- Use SvelteKit `$env/dynamic/private` for env vars (already correct)
|
||
- Error handling: try-catch with descriptive messages
|
||
- Console logging: `[Component] Message` format
|
||
- Type safety: TypeScript strict mode enabled
|
||
|
||
<!-- Planner appends findings here -->
|
||
|
||
---
|
||
|
||
### [Developer] Implementation Notes
|
||
|
||
<!-- Developer appends findings here -->
|
||
|
||
---
|
||
|
||
### [Reviewer] Review Notes
|
||
|
||
<!-- Reviewer appends findings here -->
|
||
|
||
---
|
||
|
||
## API Endpoint Catalog
|
||
|
||
### Active Endpoints
|
||
|
||
#### Queue Management
|
||
- `POST /api/queue` - Enqueue Instagram URL for processing
|
||
- `GET /api/queue` - List queue items (supports filtering, pagination)
|
||
- `GET /api/queue/stream` - SSE stream for real-time updates
|
||
- `GET /api/queue/{id}` - Get specific queue item details
|
||
- `DELETE /api/queue/{id}` - Remove item from queue
|
||
- `POST /api/queue/{id}/retry` - Retry failed extraction
|
||
|
||
#### Push Notifications
|
||
- `POST /api/notifications/subscribe` - Subscribe to push notifications
|
||
- `DELETE /api/notifications/subscribe` - Unsubscribe from notifications
|
||
- `GET /api/notifications/vapid-key` - Get VAPID public key
|
||
|
||
#### Health & Status
|
||
- `GET /api/health` - Application health check
|
||
- `GET /api/llm-health` - LLM service availability check
|
||
|
||
#### Tandoor Integration
|
||
- `POST /api/tandoor` - Upload recipe to Tandoor
|
||
- `GET /api/tandoor-config` - Get Tandoor configuration status
|
||
|
||
#### Legacy/Deprecated
|
||
- `POST /api/extract` - ⚠️ Deprecated (returns 410 Gone)
|
||
|
||
---
|
||
|
||
## Known Constraints
|
||
|
||
### Browser Automation
|
||
- Requires Chromium/Chrome installation
|
||
- Headless mode used in production
|
||
- Cookie handling for authenticated Instagram content
|
||
|
||
### LLM Integration
|
||
- Requires OpenAI-compatible API endpoint
|
||
- Configurable model selection
|
||
- Structured output using Zod schemas
|
||
|
||
### Tandoor Integration
|
||
- Optional feature (disabled without credentials)
|
||
- Requires Tandoor API token
|
||
- Supports ingredient partitioning across steps
|
||
|
||
### SSL Requirements
|
||
- HTTPS required for Service Worker registration
|
||
- Local development uses self-signed certificates
|
||
- Certificates managed via external Caddy CA
|
||
|
||
---
|
||
|
||
## Testing Coverage
|
||
|
||
### Test Distribution
|
||
- **Unit Tests**: Core logic validation
|
||
- **Integration Tests**: Multi-component workflows
|
||
- **API Tests**: Endpoint behavior verification
|
||
- **Browser Tests**: Svelte component rendering
|
||
|
||
### Test Files
|
||
- `queue-manager.spec.ts`
|
||
- `queue-processor.spec.ts`
|
||
- `queue-api.spec.ts`
|
||
- `queue-sse.spec.ts`
|
||
- `scheduler.spec.ts`
|
||
- `instagram-url-validation.spec.ts`
|
||
- `thumbnail-validation.spec.ts`
|
||
- `extraction-url-validation.integration.spec.ts`
|
||
- `page.svelte.spec.ts`
|
||
|
||
### Mock Strategy
|
||
- Environment variables mocked via `vi.mock('$env/dynamic/private')`
|
||
- External services mocked at module level
|
||
- Browser automation mocked for unit tests
|
||
|
||
---
|
||
|
||
## Documentation Inventory
|
||
|
||
### Existing Documentation
|
||
- `README.md` - Project overview and setup
|
||
- `docs/API.md` - API endpoint specifications
|
||
- `docs/MIGRATION.md` - Migration guides
|
||
- `docs/SVELTEKIT_SSR_GUIDE.md` - SSR implementation notes
|
||
- `docs/TESTING.md` - Testing guide and mocking patterns
|
||
- `docs/Tandoor (2.3.6).yaml` - OpenAPI spec for Tandoor
|
||
|
||
### Plan Documentation
|
||
`docs/plans/` contains 20+ implementation plans:
|
||
- Execution plans for completed features
|
||
- Technical specifications
|
||
- Story breakdowns with acceptance criteria
|
||
|
||
### Outcome Documentation
|
||
`docs/outcomes/` contains 20+ outcome reports:
|
||
- Implementation summaries
|
||
- Changes made
|
||
- Testing results
|
||
- Lessons learned
|
||
|
||
---
|
||
|
||
## Agent Pipeline Notes
|
||
|
||
### Build Commands
|
||
- **Build**: `npm run build`
|
||
- **Test**: `npm test` (alias for `npm run test:unit -- --run`)
|
||
- **Dev**: `npm run dev`
|
||
- **Lint**: `npm run lint`
|
||
- **Format**: `npm run format`
|
||
|
||
### Development Workflow
|
||
1. Make changes in `src/`
|
||
2. Run tests: `npm test`
|
||
3. Verify build: `npm run build`
|
||
4. Test locally: `npm run dev`
|
||
|
||
### Continuous Integration
|
||
- ESLint checks code quality
|
||
- Prettier enforces formatting
|
||
- TypeScript checks type safety
|
||
- Vitest runs test suite
|
||
|
||
---
|
||
|
||
## Next Steps
|
||
|
||
This document will be updated by subsequent agents:
|
||
1. **Planner**: Append research findings and analysis
|
||
2. **Developer**: Document implementation discoveries
|
||
3. **Reviewer**: Record review observations and recommendations
|
||
|
||
---
|
||
|
||
### [Planner] Research Notes - RECIPE-0002 (2026-02-16)
|
||
|
||
**Task:** Complete PWA implementation (installability, push notifications, share target)
|
||
|
||
#### PWA Documentation Research
|
||
**Research Date:** 2026-02-16
|
||
**Sources:** MDN Web Docs, web.dev, W3C specifications
|
||
|
||
**Progressive Web Apps (PWA) - Key Requirements:**
|
||
1. **Web App Manifest** (`manifest.json`)
|
||
- Required members: `name` or `short_name`, `icons` (192x192 PNG minimum), `start_url`, `display`
|
||
- Share target support via `share_target` member (method, action, params)
|
||
- Icons should include 192x192 and 512x512 sizes for optimal display
|
||
- Browser compatibility: Chrome/Edge (full), Firefox/Safari (limited for share_target)
|
||
|
||
2. **Service Worker**
|
||
- Must be registered to enable offline functionality
|
||
- Lifecycle: install → activate → fetch events
|
||
- Required for push notifications
|
||
- Must be served over HTTPS (or localhost)
|
||
|
||
3. **HTTPS Requirement**
|
||
- Mandatory for service worker registration
|
||
- Required for push notifications and other secure contexts
|
||
- Local development: `http://localhost` is treated as secure
|
||
|
||
4. **Installability Criteria** (from MDN/web.dev):
|
||
- Valid manifest with required members
|
||
- Service worker registered with fetch event handler
|
||
- Served over HTTPS
|
||
- At least one 192x192 PNG or SVG icon
|
||
- Display mode set (fullscreen, standalone, minimal-ui)
|
||
|
||
**Push Notifications (Web Push API):**
|
||
- Requires service worker to receive push events
|
||
- VAPID authentication (application server keys) required for Chrome
|
||
- Subscription process: permission → subscribe → store subscription → send push
|
||
- Push service (browser vendor controlled) routes messages
|
||
- Notification permissions: default, granted, denied
|
||
- Best practice: request permission after user interaction
|
||
|
||
**Web Share Target API:**
|
||
- Registers PWA as share destination
|
||
- Configuration via manifest `share_target` member
|
||
- Supports GET or POST methods
|
||
- `params` define query string mapping (title, text, url)
|
||
- Files can be shared via POST with `multipart/form-data`
|
||
- Currently Chrome/Edge only (experimental)
|
||
- App must be installed to appear in share sheet
|
||
|
||
#### Current Implementation Analysis
|
||
**Research Date:** 2026-02-16
|
||
**Files Analyzed:** manifest.json, service-worker.ts, app.html, svelte.config.js, PWAInstallManager.ts, PushNotificationManager.ts
|
||
|
||
**Manifest Analysis (`static/manifest.json`):**
|
||
- ✅ Has all required PWA members (name, short_name, start_url, display, scope, theme_color, background_color)
|
||
- ✅ Share target configured correctly (GET /share with title/text/url params)
|
||
- ⚠️ Icons reference `/favicon.png` but file does NOT exist in static folder
|
||
- ⚠️ Uses same icon path for both 192x192 and 512x512 sizes
|
||
- ℹ️ Missing optional but recommended members: `description`, `screenshots`, `categories`
|
||
|
||
**Service Worker Analysis (`src/service-worker.ts`):**
|
||
- ✅ Native SvelteKit service worker (migrated from vite-pwa plugin)
|
||
- ✅ Install event: caches all build assets and static files
|
||
- ✅ Activate event: cleans up old caches
|
||
- ✅ Fetch event: cache-first for assets, network-first with cache fallback for others
|
||
- ✅ Push event handler: processes push messages, shows notifications with actions
|
||
- ✅ Notification click handler: opens/focuses app, handles action buttons
|
||
- ✅ Notification close handler: tracks dismissals
|
||
- ✅ Background sync handler: supports retry operations
|
||
- ✅ Message handler: supports service worker communication
|
||
- ✅ Global error handlers present
|
||
|
||
**Service Worker Registration (`svelte.config.js`):**
|
||
- ✅ `serviceWorker.register: true` enabled
|
||
- ✅ SvelteKit handles registration automatically
|
||
|
||
**Manifest Link (`src/app.html`):**
|
||
- ✅ `<link rel="manifest" href="/manifest.json">` present in head
|
||
|
||
**Client-Side Managers:**
|
||
- ✅ `PushNotificationManager.ts`: Full implementation with permission, subscribe, unsubscribe
|
||
- ✅ `PWAInstallManager.ts`: beforeinstallprompt handling, install prompt triggering
|
||
- ✅ Both are SSR-safe with browser guards
|
||
|
||
**Share Target (`/share` route):**
|
||
- ✅ Route exists at `src/routes/share/+page.svelte`
|
||
- ✅ Parses query params (text, url) from share target
|
||
- ✅ Extracts Instagram URLs from shared text
|
||
- ✅ Auto-processes URLs on mount
|
||
- ✅ Enqueues items and redirects to dashboard
|
||
|
||
**Icons/Assets Issue:**
|
||
- ⚠️ **CRITICAL**: `manifest.json` references `/favicon.png` but file doesn't exist
|
||
- ✅ `src/lib/assets/favicon.svg` exists (used in layout)
|
||
- ⚠️ No PNG icons in `static/` folder
|
||
- ⚠️ Service worker references `/favicon.png` for notifications
|
||
|
||
**Push Notifications Infrastructure:**
|
||
- ✅ VAPID keys configured in `queueConfig.push` (uses env vars or defaults)
|
||
- ✅ Server endpoint: `/api/notifications/vapid-key` (GET)
|
||
- ✅ Server endpoint: `/api/notifications/subscribe` (POST/DELETE)
|
||
- ✅ PushNotificationService stores subscriptions in-memory
|
||
- ℹ️ Note: Subscriptions are not persisted (lost on restart)
|
||
|
||
#### What Works Already:
|
||
1. **PWA Structure**: Complete Native SvelteKit PWA implementation
|
||
2. **Service Worker**: Fully functional with caching, push, notifications
|
||
3. **Push Notifications**: Client and server infrastructure in place
|
||
4. **Share Target**: Configured in manifest and `/share` route working
|
||
5. **Install Prompts**: PWAInstallManager ready to trigger install
|
||
6. **HTTPS**: App served at https://localhost:5173/
|
||
|
||
#### What Needs Attention:
|
||
1. **Icons**: Create PNG icons (192x192, 512x512) from existing SVG
|
||
2. **Icon Verification**: Ensure icons are properly sized and optimized
|
||
3. **Installability Testing**: Verify all criteria met via chrome://pwa-internals
|
||
4. **Push Notification Testing**: Verify VAPID key generation and push flow
|
||
5. **Share Target Testing**: Test share from external apps (Instagram)
|
||
6. **Manifest Enhancement**: Add description, categories for better discoverability
|
||
|
||
#### Dependencies & Constraints (from ARCHITECTURE.md, CODE_STYLE.md):
|
||
- Using native SvelteKit PWA (no plugins needed)
|
||
- Service worker: `$service-worker` module provides build, files, version
|
||
- Environment: uses `$env/dynamic/private` for server configs
|
||
- HTTPS required (already configured at https://localhost:5173/)
|
||
- TypeScript strict mode enabled
|
||
- All file paths must use SvelteKit path aliases (`$lib`, `$service-worker`)
|
||
|
||
#### Code Style Requirements (from CODE_STYLE.md):
|
||
- FilesNaming: manifest.json, service-worker.ts, lowercase for utilities
|
||
- Type annotations required for public APIs
|
||
- SSR-safe code: all browser API usage must be guarded with `browser` check
|
||
- Error handling: try-catch with descriptive messages
|
||
- Comments: JSDoc for public APIs, inline for complex logic
|
||
|
||
---
|
||
|
||
### [Planner] Research Notes - RECIPE-0003 (2026-02-16)
|
||
|
||
**Task:** Update application icon and configure Docker deployment
|
||
|
||
#### PWA Icon Generation - icon-source.png
|
||
**Research Date:** 2026-02-16
|
||
**Source:** Project analysis, PWA best practices, sharp documentation
|
||
|
||
**Icon Source File:**
|
||
- Location: `static/icon-source.png`
|
||
- Size: 672KB PNG file
|
||
- Format: PNG with transparency (confirmed via file analysis)
|
||
- Destination sizes: 192x192 (favicon.png), 512x512 (icon-512.png)
|
||
|
||
**PWA Icon Requirements:**
|
||
From RECIPE-0002 research and W3C Web App Manifest specification:
|
||
1. **Minimum Size**: 192x192 pixels (required for PWA installability)
|
||
2. **Recommended Size**: 512x512 pixels (for splash screens, high-DPI displays)
|
||
3. **Format**: PNG with transparency support
|
||
4. **Purpose**: "any maskable" for optimal Android compatibility
|
||
5. **Location**: static/ directory (served at root path)
|
||
|
||
**Sharp Library Configuration:**
|
||
- Version: 0.34.5 (already in dependencies)
|
||
- Method: resize() with fit: 'contain' to preserve aspect ratio
|
||
- Background: transparent (rgba 0,0,0,0)
|
||
- Format: PNG with optimization
|
||
- Quality: Default compression for web delivery
|
||
|
||
**Implementation Pattern:**
|
||
```javascript
|
||
await sharp('static/icon-source.png')
|
||
.resize(192, 192, {
|
||
fit: 'contain',
|
||
background: { r: 0, g: 0, b: 0, alpha: 0 }
|
||
})
|
||
.png()
|
||
.toFile('static/favicon.png');
|
||
```
|
||
|
||
**Rationale:**
|
||
- `fit: 'contain'` preserves aspect ratio without cropping
|
||
- Transparent background maintains icon transparency
|
||
- PNG format required by Web App Manifest spec
|
||
- Same approach for both 192x192 and 512x512 variants
|
||
|
||
---
|
||
|
||
#### Docker Volume Configuration
|
||
**Research Date:** 2026-02-16
|
||
**Source:** Codebase analysis, Dockerfile, scheduler.ts, extraction.ts
|
||
|
||
**Volume Requirements Analysis:**
|
||
From code analysis, only one persistent volume is required:
|
||
|
||
**1. /app/secrets - Instagram Authentication Storage**
|
||
- **Purpose**: Persist Instagram session cookies across container restarts
|
||
- **File**: auth.json (Playwright storage state)
|
||
- **Usage**:
|
||
- scheduler.ts: Checks `/app/secrets/auth.json` for Docker deployments
|
||
- extraction.ts: Loads authentication from `/app/secrets/auth.json`
|
||
- gen-auth.js: Browser automation saves session to secrets/auth.json
|
||
- **Rationale**: Prevents re-login on every container restart
|
||
- **Docker Path**: /app/secrets
|
||
- **Host Path**: ./secrets (relative to docker-compose.yml)
|
||
|
||
**Volumes NOT Required:**
|
||
- **Database**: Queue uses in-memory storage (QueueManager.ts)
|
||
- **Cache**: Service worker cache is ephemeral
|
||
- **Uploads**: No file upload functionality
|
||
- **Logs**: Console logs to stdout/stderr (Docker logging)
|
||
- **Build artifacts**: Built into image at build time
|
||
|
||
**VOLUME Directive:**
|
||
```dockerfile
|
||
VOLUME ["/app/secrets"]
|
||
```
|
||
|
||
**docker-compose.yml Volume Mount:**
|
||
```yaml
|
||
volumes:
|
||
- ./secrets:/app/secrets
|
||
```
|
||
|
||
---
|
||
|
||
#### Environment Variable Inventory
|
||
**Research Date:** 2026-02-16
|
||
**Source:** queue/config.ts, llm.ts, tandoor-config.ts, scheduler.ts
|
||
|
||
**Comprehensive Variable List:**
|
||
|
||
**LLM Configuration (REQUIRED):**
|
||
- `OPENAI_BASE_URL` - OpenAI-compatible API endpoint
|
||
- `OPENAI_API_KEY` - API authentication key
|
||
- `LLM_MODEL` - Model identifier (default: gpt-4o)
|
||
|
||
**Queue Configuration (OPTIONAL):**
|
||
- `QUEUE_CONCURRENCY` - Parallel processing limit (default: 2)
|
||
- `QUEUE_MAX_RETRIES` - Retry attempts (default: 3)
|
||
|
||
**Tandoor Integration (OPTIONAL):**
|
||
- `TANDOOR_ENABLED` - Enable Tandoor upload (default: false)
|
||
- `TANDOOR_SERVER_URL` - Tandoor base URL
|
||
- `TANDOOR_SPACE` - Space ID (default: 1)
|
||
- `TANDOOR_TOKEN` - API token
|
||
|
||
**Push Notifications (OPTIONAL):**
|
||
- `VAPID_PUBLIC_KEY` - Web Push public key (has default)
|
||
- `VAPID_PRIVATE_KEY` - Web Push private key (has default)
|
||
|
||
**Authentication Scheduler (OPTIONAL):**
|
||
- `AUTH_SCHEDULER_ENABLED` - Enable auto-renewal (default: false)
|
||
- `AUTH_SCHEDULER_INTERVAL_MINUTES` - Renewal interval (default: 720)
|
||
|
||
**Runtime Configuration:**
|
||
- `NODE_ENV` - Environment mode (production/development)
|
||
- `PORT` - SvelteKit port (default: 3000)
|
||
- `DISPLAY` - X11 display for Playwright (set to :99 in docker-compose.yml)
|
||
|
||
**Default Values:**
|
||
All variables have sensible defaults except:
|
||
- OPENAI_BASE_URL (required)
|
||
- OPENAI_API_KEY (required)
|
||
|
||
**VAPID Keys:**
|
||
Current defaults in queue/config.ts:
|
||
- Public: BNextdcB_fQ0BVvyGioM5L8Tf9vKQjs-WnF-rUbnU8MdWIZQYfggIHxBnW21I-lq_0HykLCdMpYj8d5joavWdxQ
|
||
- Private: JwxI_KcsBcehYcTOufMcbVWJjCq1QbH5FJmSyQuG680
|
||
- Note: These should be regenerated for production deployments
|
||
|
||
**Variable Access Pattern:**
|
||
- Server-side only: Uses `$env/dynamic/private` from SvelteKit
|
||
- No client-side environment variable exposure
|
||
- Runtime configuration (no build-time substitution)
|
||
|
||
---
|
||
|
||
#### Docker Health Check Configuration
|
||
**Research Date:** 2026-02-16
|
||
**Source:** routes/api/health/+server.ts analysis
|
||
|
||
**Health Check Endpoint:**
|
||
- Path: `/api/health`
|
||
- Method: GET
|
||
- Response: 200 OK with JSON body
|
||
- Implementation: `src/routes/api/health/+server.ts`
|
||
|
||
**Health Check Response:**
|
||
```json
|
||
{
|
||
"status": "ok",
|
||
"timestamp": "2026-02-16T..."
|
||
}
|
||
```
|
||
|
||
**Docker Health Check Configuration:**
|
||
```yaml
|
||
healthcheck:
|
||
test: ["CMD", "node", "-e", "fetch('http://localhost:3000/api/health').then(r => r.ok ? process.exit(0) : process.exit(1)).catch(() => process.exit(1))"]
|
||
interval: 30s
|
||
timeout: 10s
|
||
retries: 3
|
||
start_period: 40s
|
||
```
|
||
|
||
**Rationale:**
|
||
- `interval: 30s` - Balance between responsiveness and overhead
|
||
- `timeout: 10s` - Sufficient for app initialization
|
||
- `retries: 3` - Allow transient failures
|
||
- `start_period: 40s` - Accounts for Playwright browser initialization
|
||
- Uses internal fetch to avoid curl dependency
|
||
|
||
---
|
||
|
||
#### Docker Deployment Constraints
|
||
**Research Date:** 2026-02-16
|
||
**Source:** Dockerfile, app.server.ts, browser.ts
|
||
|
||
**Current Dockerfile Analysis:**
|
||
- Base: node:22-alpine (minimal, production-ready)
|
||
- Chromium: Installed via apk (headless browser for Instagram extraction)
|
||
- Fonts: liberation-fonts, noto, noto-cjk (text rendering)
|
||
- Build: npm ci + npm run build
|
||
- Runtime: Node.js ESM import
|
||
- Port: 3000 (EXPOSE)
|
||
- Environment: NODE_ENV=production
|
||
|
||
**Browser Initialization:**
|
||
From app.server.ts:
|
||
- initializeBrowser() called on server start
|
||
- Graceful shutdown handlers (SIGTERM, SIGINT)
|
||
- Critical for extraction.ts Playwright usage
|
||
|
||
**Security Options:**
|
||
- `seccomp=unconfined` - Required for Chromium sandbox
|
||
- `--no-sandbox` in browser.ts launch args
|
||
- Necessary for containerized Chromium
|
||
|
||
**No Changes Required:**
|
||
Current Dockerfile is production-ready, only needs VOLUME addition.
|
||
|
||
---
|
||
|
||
### [Planner] Research Notes - RECIPE-0003 Iteration 1 (2026-02-16)
|
||
|
||
**Task:** Fix Docker deployment issues (Alpine packages, Playwright installation)
|
||
|
||
#### Alpine Linux Font Packages
|
||
**Research Date:** 2026-02-16
|
||
**Source:** https://wiki.alpinelinux.org/wiki/Fonts, Alpine package database
|
||
|
||
**Incorrect Package Names in Current Dockerfile:**
|
||
1. `liberation-fonts` → No such package (ERROR)
|
||
2. `noto` → No such package (ERROR)
|
||
3. `noto-cjk` → No such package (ERROR)
|
||
|
||
**Correct Alpine Font Package Names:**
|
||
1. `font-liberation` → Correct (already in Dockerfile)
|
||
2. `font-noto` → Correct name for Noto fonts
|
||
3. `font-noto-cjk` → Correct name for Noto CJK (Chinese, Japanese, Korean) fonts
|
||
|
||
**Rationale:**
|
||
- Alpine Linux uses `font-*` prefix for all font packages
|
||
- Common mistake: using Debian/Ubuntu package names which differ from Alpine
|
||
- These fonts are essential for rendering text in Instagram content extraction
|
||
|
||
**Recommended Font Installation:**
|
||
```dockerfile
|
||
RUN apk add --no-cache \
|
||
chromium \
|
||
font-liberation \
|
||
font-noto \
|
||
font-noto-cjk
|
||
```
|
||
|
||
---
|
||
|
||
#### Playwright on Alpine Linux
|
||
**Research Date:** 2026-02-16
|
||
**Source:** https://playwright.dev/docs/docker, Playwright GitHub issues
|
||
|
||
**Official Playwright + Alpine Status:**
|
||
- **Not officially supported**: Browser builds require glibc, Alpine uses musl
|
||
- **Firefox/WebKit**: Cannot run on Alpine (glibc dependency)
|
||
- **Chromium**: Can work using system chromium package
|
||
|
||
**Problem Analysis:**
|
||
- Current Dockerfile installs system chromium via `apk add chromium`
|
||
- Playwright's `chromium.launch()` expects Playwright's own Chromium binary
|
||
- Playwright's Chromium is built for glibc environments (Ubuntu/Debian)
|
||
- `npx playwright install chromium` will download glibc binary that won't run on Alpine
|
||
|
||
**Solution: Configure Playwright to Use System Chromium**
|
||
|
||
**Approach A - Use System Chromium (Recommended):**
|
||
```typescript
|
||
// src/lib/server/browser.ts
|
||
browser = await chromium.launch({
|
||
executablePath: '/usr/bin/chromium-browser',
|
||
headless: true,
|
||
args: [...]
|
||
});
|
||
```
|
||
|
||
**Environment Variable Approach:**
|
||
```dockerfile
|
||
ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1
|
||
ENV PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/usr/bin/chromium-browser
|
||
```
|
||
|
||
**Approach B - Switch to Debian Base:**
|
||
```dockerfile
|
||
FROM node:22-bookworm
|
||
RUN npx -y playwright@1.56.1 install --with-deps chromium
|
||
```
|
||
|
||
**Recommendation:**
|
||
- Use Approach A (system chromium with executablePath)
|
||
- Minimal changes to existing Alpine setup
|
||
- System chromium is already installed and working
|
||
- Avoids full base image migration
|
||
|
||
**Chromium System Dependencies:**
|
||
When using system chromium on Alpine, these packages are auto-installed as dependencies:
|
||
- ca-certificates, mesa-gbm, wayland-libs-server, libxkbcommon
|
||
- ffmpeg-libs, gtk+3.0, libexif, libevent, nss, etc. (64 total dependencies)
|
||
|
||
---
|
||
|
||
#### Playwright Version Compatibility
|
||
**Research Date:** 2026-02-16
|
||
**Source:** package.json analysis
|
||
|
||
**Current Version:** playwright@1.56.1 (production dependency)
|
||
**Chromium Version:** Bundled with Playwright 1.56.1
|
||
|
||
**System Chromium Compatibility:**
|
||
- Alpine edge: chromium 145.0.7632.75 (as of 2026-02-15)
|
||
- Playwright 1.56.1 expects: Chromium ~133.x
|
||
- **Version mismatch OK**: Playwright API is compatible across minor Chromium versions
|
||
- System chromium is newer, should work without issues
|
||
|
||
**executablePath Configuration:**
|
||
- Path on Alpine: `/usr/bin/chromium-browser`
|
||
- Must be set in browser.ts or via environment variable
|
||
- No additional Playwright installation needed when using system browser
|
||
|
||
---
|
||
|
||
#### Docker Compose Configuration for Playwright
|
||
**Research Date:** 2026-02-16
|
||
**Source:** resolution_context.yaml, docker-compose.yml analysis
|
||
|
||
**Current Configuration Analysis:**
|
||
```yaml
|
||
environment:
|
||
- DISPLAY=:99 # X11 display (not needed for headless)
|
||
security_opt:
|
||
- seccomp=unconfined # Required for Chromium sandbox
|
||
```
|
||
|
||
**Issues:**
|
||
- `DISPLAY=:99` set but no X11 server (Xvfb) running
|
||
- Headless mode doesn't need DISPLAY
|
||
- docker-compose.yml has DISPLAY but it's unused
|
||
|
||
**Recommendation:**
|
||
- Keep `DISPLAY=:99` as harmless fallback (no changes needed)
|
||
- `seccomp=unconfined` is necessary for Chromium sandbox (keep as-is)
|
||
- No additional configuration needed for Playwright
|
||
|
||
---
|
||
|
||
---
|
||
|
||
### [Planner] Node.js Versions and npm Lockfile Compatibility - RECIPE-0003 Iteration 2 (2026-02-16)
|
||
|
||
**Research Date:** 2026-02-16T17:00:00.000Z
|
||
**Source:** Node.js Release Schedule, npm documentation (v10 & v11), Docker Hub
|
||
|
||
#### Problem Analysis
|
||
Docker build fails at `npm ci` with error: "package-lock.json and package.json are out of sync"
|
||
- **Root Cause**: package.json updated to Tailwind v4, but package-lock.json still contains Tailwind v3 dependencies (@csstools/*)
|
||
- **Secondary Issue**: npm version mismatch - local (npm 11.6.2) vs Docker (npm 10.9.4)
|
||
|
||
#### Node.js LTS Status Research
|
||
**Source:** https://github.com/nodejs/release, https://nodejs.org/en/about/previous-releases
|
||
|
||
**Currently Supported Versions:**
|
||
- **Node.js 20 (Iron)**: Maintenance LTS - EOL 2026-04-30
|
||
- **Node.js 22 (Jod)**: Maintenance LTS - EOL 2027-04-30 ← Current Dockerfile
|
||
- **Node.js 24 (Krypton)**: Active LTS - EOL 2028-04-30 ← Best choice
|
||
- **Node.js 25**: Current (not LTS) - EOL 2026-06-01
|
||
|
||
**LTS Phase Definitions:**
|
||
1. **Current**: Latest features, 6-month cycle for odd versions
|
||
2. **Active LTS**: Audited features and updates (18 months for even versions since v12)
|
||
3. **Maintenance**: Critical fixes only (12 months)
|
||
|
||
**Conclusion**: Node.js 24 is Active LTS (until Oct 2026) providing better support than Node.js 22 (already in Maintenance).
|
||
|
||
#### npm Lockfile Version Compatibility
|
||
**Source:** https://docs.npmjs.com/cli/v10/configuring-npm/package-lock-json, https://docs.npmjs.com/cli/v11/configuring-npm/package-lock-json
|
||
|
||
**Lockfile Version History:**
|
||
- `lockfileVersion: 1` - npm v5-v6
|
||
- `lockfileVersion: 2` - npm v7-v8 (backwards compatible with v1)
|
||
- `lockfileVersion: 3` - npm v9+ (backwards compatible with v7)
|
||
|
||
**npm Version Bundled with Node.js:**
|
||
- node:22-alpine → npm 10.9.4 (uses lockfileVersion: 3)
|
||
- node:24-alpine → npm 11.x (uses lockfileVersion: 3)
|
||
- Local environment → npm 11.6.2 (uses lockfileVersion: 3)
|
||
|
||
**Compatibility Analysis:**
|
||
- Current package-lock.json has `"lockfileVersion": 3` ✓
|
||
- npm 10 and npm 11 both support lockfileVersion: 3 ✓
|
||
- The issue is NOT version incompatibility but **stale dependency data**
|
||
|
||
**npm ci Strict Behavior:**
|
||
`npm ci` performs strict validation:
|
||
1. Requires exact match between package.json and package-lock.json
|
||
2. Does not update lockfile automatically (unlike `npm install`)
|
||
3. Fails if dependencies are missing or mismatched
|
||
4. This is intentional for reproducible builds in CI/CD
|
||
|
||
#### Tailwind CSS v3 → v4 Migration Impact
|
||
**Source:** package.json analysis, package-lock.json inspection
|
||
|
||
**Current State:**
|
||
```json
|
||
// package.json (Tailwind v4)
|
||
"@tailwindcss/vite": "^4.1.17",
|
||
"tailwindcss": "^4.1.17"
|
||
|
||
// package-lock.json (still has Tailwind v3 transitive deps)
|
||
"@csstools/css-parser-algorithms": "3.0.5",
|
||
"@csstools/css-tokenizer": "3.0.4"
|
||
```
|
||
|
||
**Why This Happened:**
|
||
- package.json was updated to Tailwind v4
|
||
- package-lock.json was NOT regenerated afterward
|
||
- Tailwind v4 has different dependency tree than v3 (no @csstools/*)
|
||
- `npm ci` detects mismatch and fails
|
||
|
||
#### Solution Options Analysis
|
||
|
||
**Option A: Regenerate with Docker node:22-alpine (Review's RECOMMENDED)**
|
||
```bash
|
||
docker run --rm -v "$PWD":/app -w /app node:22-alpine sh -c "rm package-lock.json && npm install"
|
||
```
|
||
- ✓ Ensures exact npm version match with deployment
|
||
- ✗ Stays on Maintenance LTS (Node 22)
|
||
- ✗ Doesn't align with local development (node 24)
|
||
|
||
**Option B: Update to node:24-alpine**
|
||
```dockerfile
|
||
FROM node:24-alpine
|
||
```
|
||
```bash
|
||
rm package-lock.json && npm install
|
||
```
|
||
- ✓ Uses Active LTS (better support)
|
||
- ✓ Aligns Docker with local development
|
||
- ✗ Changes base image (minimal risk)
|
||
|
||
**Option C: Hybrid (BEST SOLUTION)**
|
||
1. Update Dockerfile to node:24-alpine
|
||
2. Regenerate package-lock.json locally (npm 11.x matches node:24)
|
||
- ✓ Active LTS with longer support window
|
||
- ✓ Perfect alignment between local dev and Docker
|
||
- ✓ Single lockfile regeneration
|
||
- ✓ Future-proof (Active LTS until Oct 2026)
|
||
|
||
**Chosen Approach: Option C**
|
||
|
||
#### Implementation Details
|
||
|
||
**Files to Modify:**
|
||
1. `Dockerfile` - Change FROM node:22-alpine → node:24-alpine
|
||
2. `package-lock.json` - Regenerate to sync with package.json
|
||
|
||
**Verification Steps:**
|
||
1. `npm install` - Regenerate lockfile
|
||
2. `npm run build` - Verify local build
|
||
3. `npm test` - Verify all tests pass
|
||
4. `docker build` - Verify Docker build succeeds
|
||
5. `docker compose up` - Verify runtime
|
||
|
||
**No Code Changes Needed:**
|
||
- All application code remains unchanged
|
||
- .env.example already complete (no new variables)
|
||
- docker-compose.yml does not need changes (node version transparent)
|
||
|
||
---
|
||
|
||
**Document Version:** 1.4
|
||
**Last Updated by:** Planner Agent (RECIPE-0003 Iteration 2)
|
||
**Next Update:** Developer Agent
|