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

Eclectis — 2026-03-27

What shipped today

Test coverage blitz

Today was almost entirely focused on getting the test suite up to standard. A full batch of engine handler tests landed — covering briefing.generate (skip/send/render paths), newsletter.process (idempotency and content/link modes), and user.learn (persistence and learned search terms). On the web side, billing webhook regression tests and article mutation tests (vote toggles, archiving, vote notes) were added. The Stripe webhook mock was also corrected to include getStripeWebhookSecret in the vi.mock call, which was silently allowing tests to pass without testing real behavior.

Two test files needed post-merge fixes: the briefing.generate fixtures had mismatched URLs in the source-marker tests (the article_by_url lookup was returning None because item URLs didn’t match fixture URLs), and the push handler network-error tests were using pytest.raises(httpx.ConnectError) against handlers that had been changed to return {"status": "failed"} rather than re-raise. Both were corrected.

Sentry triage and production fixes

A batch of Sentry issues from the production environment were investigated and resolved. Several (#521, #522, #523) were stale — caused by old preview deployments with missing env vars or ESM compatibility quirks that don’t affect the current deploy. These were closed as non-actionable.

The two real bugs: a foreign key constraint violation (#524) traced to deleteFeed firing a fire-and-forget trackEvent(feedId) before the DELETE completed — fixed by omitting feedId for delete events since the feed is being removed anyway. And a set of bare except blocks in Python handlers (#527) that were swallowing exception details entirely, causing Sentry to show “(No error message)” for errors in _extract_domain helpers across briefing_generate.py, google_search_scan.py, and website_scan.py, plus a webhook JSON parse warning in main.py. All four were patched with as e and log.debug()/log.warning() to surface the actual error.

Sentry user context (#526) was also wired up — SentryIdentify client component now loads in the root layout so errors in production show the affected user count and can be filtered by user.

UI and admin polish

Several UI issues from the previous milestone’s tail were resolved: dead source-management CTAs and broken feed row links, mobile source navigation and scan feedback, admin usage aggregation moved server-side (was loading raw ai_usage_logs and computing in the browser), and newsletter/podcast detail histories now paginate instead of loading everything at once.

Completed

  • #492 — Fix dead source-management CTAs and broken feed row links
  • #493 — Aggregate admin usage server-side instead of loading raw ai_usage_logs
  • #494 — Paginate newsletter and podcast detail histories
  • #495 — Fix mobile source navigation and scan feedback on source pages
  • #496 — Stop overfetching rows and auth users in getAdminUsers
  • #498 — Add billing webhook regression tests for subscription state changes
  • #499 — Add article mutation tests for vote toggles, archiving, and vote notes
  • #500 — Add briefing.generate handler coverage for skip, send, and render paths
  • #501 — Add newsletter.process handler tests for idempotency and content/link modes
  • #502 — Add user.learn handler tests for persistence and learned search terms
  • #504 — Fix briefing.generate test fixtures for last-briefing and auth lookups
  • #505 — Align push handler network-error tests with current non-raising behavior
  • #508 — Fix Stripe webhook mock: add getStripeWebhookSecret to vi.mock
  • #521 — Frontend receives HTML instead of JS/JSON (ECLECTIS-2) — stale, closed
  • #522 — NEXT_PUBLIC_SUPABASE_URL missing in a deployment environment (ECLECTIS-3) — stale, closed
  • #523 — ESM/require() compatibility error in Node.js runtime (ECLECTIS-5) — stale, closed
  • #524 — Foreign key constraint violation on insert (ECLECTIS-6) — fixed
  • #526 — Add Sentry user context so errors show affected user count
  • #527 — Add descriptive messages to Python exception handling

Release progress

No open milestones. All milestones through M4 are closed.

Carry-over

None. The queue is clear — only backlog items remain (signal amplification, AI-generated search terms, browser extension, shared intelligence layer, homepage social proof).

Risks

  • The Sentry DSN for eclectis-engine was recently updated. Verify that errors from the Python engine are actually appearing in Sentry (not just the Next.js front end) after the DSN change.

Flags and watch-outs

  • PostHog and Sentry configuration correctness (env vars firing, error capture working in production) remains unverified. Worth a manual check next session.
  • Several open PRs from 2026-03-26 remain unmerged (#512, #513, #515, #516). These are from earlier work and may need review/merge.

Next session

  1. Check if PRs #512, #513, #515, #516 are still open and merge if ready
  2. Verify Sentry engine errors are flowing with the new DSN (check Sentry for a Python event from eclectis-engine)
  3. Verify PostHog events are firing (check PostHog for a page_view or feed_add event from production)
  4. If no blocking issues found, start work on the backlog — homepage social proof (#471) is the most user-facing item

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.

Silence by design

Most systems have more suppression than their owners realize. It gets installed for good reasons. The cost accumulates slowly, in the form of systems you can't operate because you've removed the signals that would let you understand them.

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.