Skip to main content
Paul Welty, PhD AI, WORK, AND STAYING HUMAN

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.titlefeeds.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 build fails 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 echo vs printf issue when piping to vercel env add is now documented in memory. Always use printf to avoid trailing newlines.
  • Railway deployment requires requirements.txt (not pyproject.toml). openai>=1.50 was 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.