If the GraphQL-intercepted caption ends with '….' (Instagram's truncation
marker), skip it and fall through to HTML Section extraction which clicks
the '… more' button in the DOM to get the complete, untruncated caption.
Previously the 327-char truncated caption for DWWxiymssxE was returned
immediately, causing the LLM to say 'no recipe' even though the full
description had all ingredients and steps.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Always extract the full caption via Playwright (browser sees the
untruncated text). yt-dlp runs in parallel only to get the thumbnail
CDN URL quickly; its result for the description is discarded.
This eliminates the truncation problem at the source without needing
a fallback heuristic.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When yt-dlp returns a caption ending with the truncation marker '….'
(GraphQL API caps the text), automatically retry with the Playwright
extractor, which intercepts the full caption from live GraphQL network
traffic.
Falls back gracefully to the partial yt-dlp caption if Playwright fails.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Instagram truncates long captions server-side (ends with '…').
When yt-dlp returns a truncated caption, automatically fall back to
the Playwright extractor which runs JS in a real browser and can
click the 'more' button to expand the full caption.
Falls back gracefully: if Playwright fails, the truncated text is
still used rather than failing the whole extraction.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- +layout.svelte: replace Svelte logo favicon with actual InstaChef icons;
add two <meta name="theme-color"> tags with media queries so the browser
chrome (mobile top bar) matches --bg for light (#FFF8F5) and dark (#110510);
add <meta name="color-scheme" content="dark light">
- manifest.json: split 'any maskable' into separate 'any' and 'maskable' entries;
maskable uses icon-512-maskable.png (icon with 10% safe-zone padding on gradient bg)
- New icons:
- icon-256/512.png → replaced with transparent-background versions
- icon-256/512-transparent.png → white bg removed via flood-fill BFS
- icon-256/512-dark.png → transparent icon on brand gradient (#833AB4→#E1306C)
- icon-512-maskable.png → 80% icon centered on gradient (PWA maskable safe zone)
- favicon-32.png → 32x32 transparent icon for browser tab
- favicon.png (192×192) → updated to transparent InstaChef icon
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Previously cookies.txt was only regenerated when auth.json was newer. But yt-dlp
overwrites cookies.txt during extraction with its own header ('generated by yt-dlp')
and potentially fewer/different cookies, losing the sessionid from auth.json.
Fix: remove mtime comparison — always regenerate cookies.txt from auth.json on each
extraction call. This ensures the full session cookie set is always present.
Also remove the now-unused statSync import.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- POST /api/queue now returns the full QueueItem (with createdAt, phases, etc.)
instead of a stripped {id,url,status,enqueuedAt} subset
- TimelineRow.relTime() now handles undefined/NaN gracefully, falls back to 'just now'
- TimelineRow timestamp uses item.createdAt ?? item.enqueuedAt as fallback
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
submitUrl() was using the full {duplicate, item} response object
as the queue item, causing 'Cannot read properties of undefined
(reading length)' crash when rendering phases in RecipeSheet/
TimelineRow.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- layout.css: add button.ic-btn-reset rule so all icon buttons
(bell, back, close, retry, etc.) get proper background:none reset
instead of browser-default white/grey appearance in dark mode
- instagram-extractor.ts: auto-convert secrets/auth.json
(Playwright storage format) to Netscape cookies.txt at runtime
whenever auth.json is newer; ensures sessionid and all Instagram
session cookies are passed to yt-dlp, fixing empty media response
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Tests passed locally because .env provided OPENAI_BASE_URL and
OPENAI_API_KEY. In the Docker build stage there is no .env, so
createLLM() threw 'OPENAI_BASE_URL environment variable is not set'
before the mocked OpenAI client ever ran, causing 3 test failures.
Add vi.mock('$env/dynamic/private', ...) with stub values so the
tests are self-contained and environment-independent.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Playwright Chromium is not available in node:24-alpine, causing the
vitest 'client' project (browser tests) to fail with an unhandled
browserType.launch error and exit code 1.
- Dockerfile: switch tester stage command to
'npm run test:unit -- --run --project=server'
so only Node.js unit tests run during Docker builds
- page.svelte.spec.ts: update stale 'renders h1' assertion to match
the new InstaChef design (no h1; check for 'InstaChef' logo text)
Browser component tests still run locally when Playwright/Chromium
is available.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Increase max_tokens from 10 to 1024 for detection so thinking
models have room to reason. Also fall back to reasoning_content
if content is empty, since some local models (e.g. Gemma 4
thinking variants) put their answer there.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add instagram-extractor.ts: yt-dlp subprocess backend for Instagram
caption extraction. No in-process browser state, maintained against
Instagram frontend churn, supports cookies.txt for auth-walled reels.
- Add feature flag EXTRACTOR_BACKEND (ytdlp|playwright) in QueueProcessor
so the old Playwright path remains available as fallback.
- Add 9 unit tests and 2 live-network integration tests for the new extractor.
- Dockerfile: install yt-dlp via pip3 alongside existing Chromium deps.
- docker-compose: expose EXTRACTOR_BACKEND env var (default: ytdlp).
Also in this commit:
- LLM: configurable per-request timeout via LLM_REQUEST_TIMEOUT_MS (default 120s);
set maxRetries=0 to surface errors immediately; llama-swap /running health probe.
- QueueProcessor: thread progress callback through parser phase.
- LlmHealthIndicator: surface llama-swap loaded-model name.
- Logging: improve error serialization in queue-processor tests.
- .env.example: document llama-swap endpoint and model options.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Exported cleanText() and extractFromDOM() for unit testing
- Fixed metadata prefix regex to handle optional quotes
- Created comprehensive unit tests with mocked Playwright Page (15 tests, 12ms)
- All 275 tests passing
- Fixed NodeJS.Timer → NodeJS.Timeout in scheduler.ts line 13
- Fixed NodeJS.Timer[] → NodeJS.Timeout[] in fixtures.ts line 151
- Resolves TypeScript compile errors from iteration 0 review
- All 260 tests passing, build succeeds with no errors
- Fixed health endpoint to use getAll() instead of getAllItems()
- Removed call to non-existent getStats() method
- Added local stats computation with total count
- Health endpoint now returns 200 OK (was returning 500)
- Docker healthcheck now passes successfully
- No more TypeError in Docker logs
Resolves health check failure that was blocking Docker monitoring.
- Fix InvalidCharacterError in push notifications with proper VAPID key validation
- Add attractive PWA install prompt component with cross-browser support
- Make notification settings always visible regardless of queue status
- Implement PWA install manager with user engagement detection
- Use SvelteKit navigation APIs instead of browser history API
- Add comprehensive error handling and logging
- Include cross-browser compatibility and responsive design
- Add development tooling improvements
Fixes push notification bugs and significantly improves PWA user experience
with modern, accessible interface components and proper error handling.
- Remove dev-dist/registerSW.js (no longer needed without vite-pwa plugin)
- Fix import order in layout.svelte
- Complete migration to native SvelteKit PWA