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

2026-03-28 — Analytics instrumentation sprint

What shipped today

Today was a focused analytics cleanup session. The PostHog instrumentation that was audited and specced out across 15+ issues earlier this week got systematically implemented, verified, and closed. The work fell into three themes: fixing what was broken, removing what was phantom, and adding what was missing.

The biggest fix was discovering that onboarding_status was never programmatically set to 'completed' for new users (#1830). The onboarding wizard page had been removed months ago, but the bootstrap handler — which runs after wizard completion — never actually flipped the status. This was a real bug affecting user lifecycle tracking, not just a telemetry gap. The fix went into onboarding_bootstrap.py alongside the onboarding.completed PostHog event.

The re-engagement drip system (#1816) got full telemetry wiring: reengagement.email_sent fires server-side after each successful send, and CTA clicks now route through a new tracking redirect endpoint (/api/v2/track/reengagement/click) that captures reengagement.cta_clicked before redirecting. This mirrors the existing briefing email click pattern. The auth flows (#1810) also got signup/login/reset tracking events added to the web app.

Several issues turned out to be phantom — events documented in TELEMETRY.md that referenced features that don’t exist in the current app. Article bookmarks (#1825) are a legacy Rails feature never ported. Feed management (#1824) has no web UI. The PostHog identify timing concern (#1815) was a non-issue since cookies persist through OAuth redirects on the same domain. The proxy verification (#1819) confirmed zero PostHog requests leak to external domains. All of these were closed with explanations and TELEMETRY.md was updated to reflect reality.

Completed

  • #1810 — Add PostHog tracking to signup/login/reset flows
  • #1811 — Fix event name mismatches between TELEMETRY.md and code
  • #1812 — Add missing properties to PostHog events
  • #1813 — Remove phantom events documented but never implemented
  • #1814 — Sync 11+ undocumented events into TELEMETRY.md
  • #1815 — PostHogIdentify timing (closed: not a real issue)
  • #1816 — Re-engagement funnel events (email_sent + cta_clicked)
  • #1817 — Clean up legacy underscore-named events
  • #1818 — Disable autocapture (already done, verified)
  • #1819 — Verify /ingest proxy in production
  • #1820 — Unused Eclectis PostHog project (closed: separate project)
  • #1821 — Add changed_fields to settings.profile_updated
  • #1822 — Remove stale content_type refs from TELEMETRY.md
  • #1824 — Feed management events (closed: phantom, no UI exists)
  • #1825 — Article bookmark events (closed: phantom, legacy feature)
  • #1826 — Add onboarding.completed event
  • #1827 — Delete test/junk events from PostHog
  • #1828 — Audit lost tracking from legacy event rename
  • #1830 — Fix onboarding_status never set to completed

Release progress

  • v1.5: 49/50 closed (1 remaining: #1805 — parent issue for analytics instrumentation)
  • v2.0 — Product simplification: complete (25/25)
  • v2.1 — Content model refactor: complete (15/15)
  • v2.2 — Simplified pipeline: complete (3/3)

Carry-over

  • #1805 (ensure PostHog fully instrumented) can likely be closed now — all child issues are done
  • #1829 (backlog) — archive legacy underscore events in PostHog dashboard
  • #1823 (backlog) — deferred re-engagement events (user_reactivated, sequence_completed, opted_out)

Risks

  • No web test suite: The web app has no test script in package.json. All web-side tracking changes (auth events, settings tracking, CTA redirect endpoint) are verified by lint only. A future regression could silently break tracking.
  • Production database in dev: All PostHog event names were verified against production data. Any accidental tracking calls during dev would hit the real PostHog project.

Flags and watch-outs

  • The re-engagement CTA redirect endpoint uses NEXT_PUBLIC_POSTHOG_KEY (same pattern as briefing click tracker). If this key rotates, both endpoints need updating.
  • TELEMETRY.md is now the accurate source of truth — phantom events have been cleaned out. Future features should add events to the catalog before implementing.

Next session

  • Close #1805 (parent analytics issue) — all child work is done, verify nothing remains
  • Consider promoting #1829 (archive legacy underscore events) from backlog if PostHog dashboard cleanup is desired
  • The analytics sprint is complete — shift focus to whatever Paul queues next (likely apple app work per v1-apple milestone, or new feature development)

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.

Everything pointed at ghosts

Most organizations are measuring work they stopped doing years ago. The dashboard is green. The reports are filed. Nobody realizes the entire apparatus is pointed at ghosts.

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.