Work log — 2026-03-24
What shipped
The biggest single-day push in Eclectis history: 43 commits, 30+ issues closed, and the product moved from “works for Paul” to “works for strangers.”
Podcast feature end-to-end. Built the entire podcast pipeline in one session: script generation from briefings via Claude, OpenAI TTS audio with chunking at sentence boundaries, per-user podcast RSS feeds with iTunes extensions, cover art, voice selection with 11 OpenAI voices and preview samples in settings. The podcast feed is subscribable in Apple Podcasts, Overcast, and any podcast player. Gated to Pro plan.
AI provider abstraction. Created an AI gateway (engine/services/ai.py) that routes chat calls to Anthropic or OpenAI based on per-task admin configuration. All 9 engine handlers migrated from direct claude.py imports to the gateway. Admin settings page at /admin/settings lets you change provider and model per task (scoring, prescore, briefing, filters, learning, podcast). This is the foundation for cost optimization and model experimentation.
Briefing preferences. Added briefing length (concise/standard/detailed) and multi-language support (13 languages) to user settings. Both persist correctly and flow through to briefing generation. Fixed a stale closure bug in useCallback that caused settings to silently revert.
Production hardening marathon. Fixed a cascade of production issues: Vercel build failures from a type mismatch (feeds.title → feeds.name), missing SUPABASE_SERVICE_ROLE_KEY breaking all admin pages, JSONB preferences returned as strings crashing multiple handlers, RSS scan not passing days_back parameter, website scan NameErrors, newsletter SKIP_PATTERNS dropping all Mailgun URLs, clipboard API crashing on localhost, NEXT_PUBLIC_SITE_URL containing trailing newlines from echo piping to Vercel env.
Launch readiness. Added OG meta tags and Twitter card metadata for social sharing. Built an empty state for new users on /articles with CTAs to add feeds, newsletters, and search terms. Ran a full Playwright walkthrough of all 81 use cases — every page loads, all features functional. Ran a 5-dimension scout (error handling, security, performance, UX, features) and executed all findings.
Skip tracking for the learning loop. Implemented article impression tracking (fires on /articles page load), skip signal computation in user_learn (impressions minus engagements), and a unique partial index for dedup. This closes the biggest gap in PRODUCT.md’s “learns you over time” promise — the feedback loop now observes the 90% of articles users see but don’t engage with.
Completed
- #436 — Add briefing length preference (concise/standard/detailed)
- #437 — Multi-language support for briefings
- #438 — Newsletter link articles missing summaries
- #439 — Fix uninitialized parsed_feed_id in newsletter_process
- #440 — Add password requirement hint to signup form
- #441 — Skip push commands when integrations not configured
- #442 — Create AI gateway service
- #443 — Add admin UI for AI task configuration
- #444 — Migrate all handlers from claude.py to AI gateway
- #445 — Add per-model cost calculation to usage logging
- #446 — Create podcast_scripts table and handler
- #447 — Add podcast scripts list and detail pages
- #448 — Add Generate podcast button on briefing detail
- #449 — Add TTS audio generation and player
- #450 — Create OpenAI client module
- #451 — Create AI gateway with per-task routing
- #452 — Add voice preview samples to settings
- #453 — Add OG meta tags and social preview image
- #454 — Verify Stripe env vars in Vercel production
- #455 — Add try/catch to .single() calls (false alarm — already safe)
- #456 — Add empty state for new users on /articles
- #457 — Fix Raindrop/Readwise push handlers crashing on network errors
- #458 — Add confirmation dialogs for passkey delete and integration remove
- #459 — Clean up passkey challenge rows after verification
- #460 — Track article skips for better learning signal (decomposed → #462-464)
- #461 — Improve data export with error tracking and vote notes
- #462 — Track article impressions on /articles page
- #463 — Incorporate skip signals into user_learn prompt
- #464 — Add impression event type and dedup index
Also closed as won’t-fix/false-alarm: #77 (Vercel issue — no longer reproducible), #376 (per-source throttling — premise was wrong)
Carry-over
- #344 — Confusing sort/filters (backlog, not urgent)
- #64 — Scan email inbox for newsletters (backlog/discussion)
- Next.js 16 canary Turbopack build crash —
npx next buildfails locally due to upstream bug. Vercel builds work fine. Monitor for fix in next canary release.
Risks
- Impression tracking volume. Each /articles page visit creates ~20 impression rows. The unique partial index prevents duplicates per article, but high-traffic users could still generate significant table growth. Monitor engagement_events table size over the next week.
- AI gateway cache. Config is cached 60 seconds in-process. Admin changes to provider/model take up to 60s to take effect. Acceptable but worth noting.
Flags and watch-outs
- The podcast feed uses
NEXT_PUBLIC_SITE_URL— make sure Vercel has the production value (https://www.eclectis.io) and not a dev URL. We got bitten by this multiple times today. - The
echovsprintfissue when piping tovercel env addis now documented in memory. Always useprintfto avoid trailing newlines. - Railway deployment requires
requirements.txt(not pyproject.toml).openai>=1.50was added there today.
Why customer tools are organized wrong
This article reveals a fundamental flaw in how customer support tools are designed—organizing by interaction type instead of by customer—and explains why this fragmentation wastes time and obscures the full picture you need to help users effectively.
Infrastructure shapes thought
The tools you build determine what kinds of thinking become possible. On infrastructure, friction, and building deliberately for thought rather than just throughput.
Server-side dashboard architecture: Why moving data fetching off the browser changes everything
How choosing server-side rendering solved security, CORS, and credential management problems I didn't know I had.
The work of being available now
A book on AI, judgment, and staying human at work.
The practice of work in progress
Practical essays on how work actually gets done.
Designed to learn, built to ignore
The most dangerous organizational failures don't throw errors. They look fine, return results, and quietly stay frozen at the moment of their creation.
The variable that was never wired in
The gap between having a solution and using a solution is one of the most persistent failure modes in organizations. You see the escaped variable. You see the risk register. You assume the work is done.
Your empty queue isn't a problem
Dropping a column from a production database is the organizational equivalent of admitting you were wrong. Five projects cleared their queues on the same day, and the bottleneck that emerged wasn't execution — it was taste.