Run same workloads as TypeScript in parallel, compare results.
Shadow never writes authoritative state — results logged for comparison.
Candidates: heartbeat, friction, digest, proposal triage, content-sync.
Comparison metrics: latency, error rate, output quality, recovery time.
Shadow comparison becomes the graduation exam for ADR-0114.
Oban + OTP + Phoenix is the recommended stack if migrating.
Jido adds agent abstractions but young/dependency risk.
Oban is the mature Inngest equivalent (6+ yrs, funded, dashboards).
- Email read now caches threads to ~/.cache/joelclaw/email/threads/
- Attachment download + local file caching
- --refresh flag to bypass cache, --cache-ttl-minutes for TTL control
- Stale fallback when Front API is unreachable
- ADR-0112 proposes system-wide caching: hot (Redis), warm (file), cold (NAS)
- Covers Front, Todoist, Granola, Typesense, PDS with per-service TTLs
- Webhook-driven invalidation + CLI --refresh on all cached commands
- New StatusLed component: colored dot only, no text/pill background
- Sizes sm/md/lg (6/8/10px) with pulse animation for healthy
- Subtle glow effect matching status color
- Left-aligned: [LED] name across nodes, pods, daemons
- StatusBadge kept for backward compat
Friction-fix no longer calls gateway.notify() at all for per-pattern
results. Removed the silent option from gateway middleware — if you
don't want to notify, don't notify.
- gateway.notify(type, payload, { silent: true }) logs but doesn't push to Joel
- All friction-fix per-pattern notifications now silent
- Only parent friction.ts summary ('10 patterns → N tasks') reaches gateway
- Individual fix outcomes logged via console for OTEL observability
- Eliminates all friction-fix gateway noise (was 10 events per analysis run)
Friction analysis emits 10 patterns → 10 friction-fix runs → 10 gateway
notifications. Most are 'skipped' (codex produces no commits). These flood
the gateway with noise. Now only actual fixes and Todoist escalations notify.
- Lease vercel_webhook_secret in start.sh (was silently dropping all Vercel signatures)
- POST-startup PUT sync to prevent stale Inngest registry after every restart
- Add media/received to SUPPRESSED_TYPES (5+ duplicate progress events per image)
- Fix friction-fix.ts TS error (implicit any)
- Buttons now show '1', '2 ★', '3' instead of truncated option text
- Full numbered option descriptions in message body above buttons
- Recommendation reason shown as italic text below options
- 'Other' gets a number too for consistency
- callback_data unchanged, no behavior change
- Add account-scoped concurrency (granola-mcp, limit:1) to all Granola functions
- Add rate-limit error detection with retriable throws
- Suppress Todoist task.completed webhook echoes via Redis set (60s TTL)
- Agent marks task IDs before API close, webhook handler checks before processing
- Add archive-ids command: parallel batched archive by comma-separated IDs
- Add --from, --before, --after flags to inbox command (structured query building)
- Add --page-token for pagination through large result sets
- Fix: before:/after: now correctly convert YYYY-MM-DD to unix timestamps
(Front API requires seconds, not date strings)
- Fix: Remove is:unread from suggestions (not a valid Front status)
- Add buildFrontQuery() with full Front Search API reference in docblock
(authoritative filter list: is/from/to/cc/bcc/recipient/inbox/tag/
before/after/during/assignee/participant/author/mention/commenter/
contact/link/custom_field)
- Update help text with valid query examples
- Fix formatDateStatic import → toDateString (function was removed by prior commit)
- Remove proxy.ts (conflicts with middleware.ts in Next.js 16)
- Clean up untracked (owner) route group and leftover files from gateway agent
LazyReviewGate and ConvexReviewGate were untracked but imported by
[slug]/page.tsx and adrs/[slug]/page.tsx. Lazy-loads Convex client
via useEffect to avoid Date.now() during static prerender.
- Add connection() before MDX rendering in /[slug] and /adrs/[slug]
- Wrap AdrListWithFilters in Suspense on /adrs index
- next-mdx-remote uses Date.now() internally, requires dynamic opt-in
Covers: Entry+Static+Slots route architecture, 'use cache' boundaries
with cacheLife/cacheTag/updateTag, provider islands, RSC boundary rules,
async patterns, Suspense requirements, link preloading strategy,
unstable_cache migration, and failure mode catalog.
Incorporates reference material from next-best-practices and
next-cache-components skills into a self-contained architecture guide.
auth.isOwner: wrap getAuthUser in try/catch — returns false instead
of throwing when no session token exists.
ReviewGate: treat undefined (loading) same as false — render children
without wrapper to prevent layout shift.
ReviewWrapper: remove -mx-2/px-2/py-0.5 from paragraph hover styles
that caused vertical CLS when wrapper mounted after auth resolved.
Nav: sliding active indicator bar (CSS transition, no framer-motion).
Measures active link position, slides on route change. 200ms
cubic-bezier(0.22, 1, 0.36, 1).
Mobile nav: exit animation mirrors entrance. Links slide out right
with reverse stagger (30ms), overlay fades out 150ms.
Fix transition-all violations (emilkowal props-transform-opacity):
- signin button: explicit property list
- review FAB: explicit + scale(0.97) per spec
- review submit: explicit + motion-reduce
All animations respect prefers-reduced-motion.
useAutoResize hook: measures scrollHeight, transitions height at
150ms with cubic-bezier(0.22, 1, 0.36, 1). Expands as you type,
collapses when content is deleted. Applied to both compose and
edit textareas. Max height 240px then scrolls.
- Plain click: single select (toggle)
- Cmd/Ctrl+click: add/remove from selection
- Shift+click: range select (DOM order)
- Cmd+Shift: range added to existing selection
- Multi-select shows '3 paragraphs' badge in comment header
- Comment prefixed with selection context for agent
- Hint bar shows keyboard shortcuts