<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Notes by Dil</title>
    <link>https://dil.dev/notes/</link>
    <atom:link href="https://dil.dev/notes/rss.xml" rel="self" type="application/rss+xml" />
    <description>Engineering notes from building production AI and web systems.</description>
    <language>en-gb</language>
    <lastBuildDate>Tue, 03 Nov 2026 00:00:00 GMT</lastBuildDate>
    <item>
      <title>Resumable Line Provisioning: Save the Assistant ID Before the SIP Call</title>
      <link>https://dil.dev/notes/resumable-line-provisioning/</link>
      <guid isPermaLink="true">https://dil.dev/notes/resumable-line-provisioning/</guid>
      <pubDate>Tue, 03 Nov 2026 00:00:00 GMT</pubDate>
      <description>A line create command for AI phone receptionists that resumes each step from what the client file already proves, with one critical ordering fix: persist the assistant ID before the SIP create.</description>
      <category>voice-agents</category>
      <category>provisioning</category>
      <category>vapi</category>
      <category>automation</category>
    </item>
    <item>
      <title>PixiJS Sets touch-action: none, and Phones Stopped Scrolling Over the Photos</title>
      <link>https://dil.dev/notes/pixi-canvas-touch-action-scroll/</link>
      <guid isPermaLink="true">https://dil.dev/notes/pixi-canvas-touch-action-scroll/</guid>
      <pubDate>Mon, 02 Nov 2026 00:00:00 GMT</pubDate>
      <description>PixiJS defaults to touch-action: none on its canvas, which silently kills page scroll on phones. Three CSS properties and a Playwright audit fix it for good.</description>
      <category>pixi.js</category>
      <category>WebGL</category>
      <category>mobile</category>
      <category>playwright</category>
    </item>
    <item>
      <title>PostgREST Stops at 1,000 Rows and the Dashboard Never Says So</title>
      <link>https://dil.dev/notes/supabase-1000-row-cap-silent-count/</link>
      <guid isPermaLink="true">https://dil.dev/notes/supabase-1000-row-cap-silent-count/</guid>
      <pubDate>Sun, 01 Nov 2026 00:00:00 GMT</pubDate>
      <description>PostgREST silently caps unpaged selects at 1,000 rows. An admin dashboard counting synced jobs in memory showed 0 for newer users once the table grew past that limit.</description>
      <category>supabase</category>
      <category>postgrest</category>
      <category>debugging</category>
      <category>admin</category>
    </item>
    <item>
      <title>Pulling n8n Executions into Postgres: Oldest First, Replay-Safe, Blind to Hand Edits</title>
      <link>https://dil.dev/notes/n8n-executions-puller-postgres/</link>
      <guid isPermaLink="true">https://dil.dev/notes/n8n-executions-puller-postgres/</guid>
      <pubDate>Tue, 27 Oct 2026 00:00:00 GMT</pubDate>
      <description>An edge function on a five-minute cron pulls n8n workflow executions into Postgres oldest-first, with replay safety proven across two backfills and 209 executions writing zero duplicate events.</description>
      <category>n8n</category>
      <category>postgres</category>
      <category>pg_cron</category>
      <category>event-store</category>
    </item>
    <item>
      <title>A Fake SMS YES Could Have Won a Lead and Charged a Card</title>
      <link>https://dil.dev/notes/twilio-reply-webhook-basic-auth/</link>
      <guid isPermaLink="true">https://dil.dev/notes/twilio-reply-webhook-basic-auth/</guid>
      <pubDate>Sun, 25 Oct 2026 00:00:00 GMT</pubDate>
      <description>An open webhook on a plumber lead network accepted any POST, meaning a spoofed YES could claim a lead and trigger a card charge. Basic auth fixed it with a zero-downtime rollout.</description>
      <category>webhooks</category>
      <category>security</category>
      <category>twilio</category>
      <category>n8n</category>
    </item>
    <item>
      <title>A Broadcast Tool for 860 Members Now Needs a Typed SEND</title>
      <link>https://dil.dev/notes/broadcast-typed-send-confirm/</link>
      <guid isPermaLink="true">https://dil.dev/notes/broadcast-typed-send-confirm/</guid>
      <pubDate>Thu, 22 Oct 2026 00:00:00 GMT</pubDate>
      <description>One misclick in a members composer could mail 860 people instantly. Two commits fixed that: a typed confirm step and a split send-test endpoint.</description>
      <category>safety</category>
      <category>admin-ui</category>
      <category>api-design</category>
      <category>email</category>
    </item>
    <item>
      <title>Months of Green Ticks over a Cleanup Job That Never Deleted a File</title>
      <link>https://dil.dev/notes/pm2-runtime-secret-silent-cleanup/</link>
      <guid isPermaLink="true">https://dil.dev/notes/pm2-runtime-secret-silent-cleanup/</guid>
      <pubDate>Mon, 19 Oct 2026 00:00:00 GMT</pubDate>
      <description>A scheduled cleanup reported success for months while doing nothing. pm2 never read the droplet .env, so CLEANUP_SECRET was undefined, every call got a 403, and an echo fallback swallowed the error.</description>
      <category>pm2</category>
      <category>devops</category>
      <category>node</category>
      <category>debugging</category>
    </item>
    <item>
      <title>Stripe Redelivered a Stale payment_failed After the Customer Had Already Paid</title>
      <link>https://dil.dev/notes/stripe-stale-payment-failed-replay/</link>
      <guid isPermaLink="true">https://dil.dev/notes/stripe-stale-payment-failed-replay/</guid>
      <pubDate>Sun, 18 Oct 2026 00:00:00 GMT</pubDate>
      <description>A checkout decline raised invoice.payment_failed before the subscription row existed, the handler threw, and Stripe redelivered the event after the customer paid, flipping them to past_due.</description>
      <category>stripe</category>
      <category>webhooks</category>
      <category>billing</category>
      <category>saas</category>
    </item>
    <item>
      <title>Choosing a Voice Agent&#39;s Transcriber by Measured Turn Latency</title>
      <link>https://dil.dev/notes/voice-agent-transcriber-latency/</link>
      <guid isPermaLink="true">https://dil.dev/notes/voice-agent-transcriber-latency/</guid>
      <pubDate>Fri, 16 Oct 2026 00:00:00 GMT</pubDate>
      <description>Three transcribers failed or lagged before landing on Soniox primary with Deepgram fallback. The 1.8 s average from one provider caused the agent to cut callers off nine times.</description>
      <category>voice-ai</category>
      <category>transcription</category>
      <category>latency</category>
      <category>sip</category>
      <category>multilingual</category>
    </item>
    <item>
      <title>Tell a Model to Aim at the Floor and Half Its Drafts Fall Under It</title>
      <link>https://dil.dev/notes/llm-length-target-was-the-floor/</link>
      <guid isPermaLink="true">https://dil.dev/notes/llm-length-target-was-the-floor/</guid>
      <pubDate>Wed, 14 Oct 2026 00:00:00 GMT</pubDate>
      <description>When the length target and the throw floor are the same number, the model clusters at it. Separating aim, expansion threshold, throw floor and ceiling fixed the distribution.</description>
      <category>llm</category>
      <category>content-pipeline</category>
      <category>n8n</category>
      <category>prompt-engineering</category>
    </item>
    <item>
      <title>Haiku Invented the Date on 10 of 18 Nightly Notes, So the Script Owns It Now</title>
      <link>https://dil.dev/notes/llm-capture-job-invented-dates/</link>
      <guid isPermaLink="true">https://dil.dev/notes/llm-capture-job-invented-dates/</guid>
      <pubDate>Tue, 13 Oct 2026 00:00:00 GMT</pubDate>
      <description>A nightly capture job let Haiku write the H1 date and it hallucinated it on 10 of 18 notes.</description>
      <category>automation</category>
      <category>llm</category>
      <category>claude</category>
      <category>shell-scripting</category>
    </item>
    <item>
      <title>The Idle Watchdog That Killed Healthy Reasoning Models at 300 Seconds</title>
      <link>https://dil.dev/notes/idle-watchdog-killed-thinking-models/</link>
      <guid isPermaLink="true">https://dil.dev/notes/idle-watchdog-killed-thinking-models/</guid>
      <pubDate>Mon, 12 Oct 2026 00:00:00 GMT</pubDate>
      <description>A file-size watchdog could not tell a thinking model from a wedged process. A throttled heartbeat sidecar fixed it without letting genuine wedges survive.</description>
      <category>shell</category>
      <category>ai-tooling</category>
      <category>debugging</category>
      <category>streaming</category>
    </item>
    <item>
      <title>My Agent Loop Marked 15 Tasks Done With No Tests Running</title>
      <link>https://dil.dev/notes/agent-loop-hollow-gate-stack/</link>
      <guid isPermaLink="true">https://dil.dev/notes/agent-loop-hollow-gate-stack/</guid>
      <pubDate>Sun, 11 Oct 2026 00:00:00 GMT</pubDate>
      <description>Every gate stage defaulted to an empty string, so 15 tasks logged gate:none and the harness called the run clean. A severity-ladder preflight now blocks a hollow gate stack before task selection.</description>
      <category>agent-loop</category>
      <category>ci</category>
      <category>testing</category>
      <category>automation</category>
    </item>
    <item>
      <title>Two Mobile Scroll-Video Bugs That Only Show Up on Real Phones</title>
      <link>https://dil.dev/notes/ios-scroll-seek-and-stale-scrolltrigger/</link>
      <guid isPermaLink="true">https://dil.dev/notes/ios-scroll-seek-and-stale-scrolltrigger/</guid>
      <pubDate>Sat, 10 Oct 2026 00:00:00 GMT</pubDate>
      <description>iOS holds a paused video at readyState 1 so every seek stays pending and the frame paints black. A second bug: ScrollTrigger points computed once go stale when page content grows.</description>
      <category>ios</category>
      <category>video</category>
      <category>scroll</category>
      <category>IntersectionObserver</category>
    </item>
    <item>
      <title>Six Local Supabase Stacks, 61 Containers, and 96% Swap</title>
      <link>https://dil.dev/notes/six-local-supabase-stacks-resource-exhaustion/</link>
      <guid isPermaLink="true">https://dil.dev/notes/six-local-supabase-stacks-resource-exhaustion/</guid>
      <pubDate>Thu, 08 Oct 2026 00:00:00 GMT</pubDate>
      <description>Running six local Supabase stacks simultaneously pushed swap to 96% and caused repeated OOM failures during db reset. The real lesson is surfacing infrastructure exhaustion before blaming your code.</description>
      <category>supabase</category>
      <category>docker</category>
      <category>local-dev</category>
      <category>debugging</category>
    </item>
    <item>
      <title>The Silent Redirect That Made Uploads Vanish</title>
      <link>https://dil.dev/notes/silent-redirect-swallowed-uploads/</link>
      <guid isPermaLink="true">https://dil.dev/notes/silent-redirect-swallowed-uploads/</guid>
      <pubDate>Tue, 06 Oct 2026 00:00:00 GMT</pubDate>
      <description>A submitted upload looked lost because a route redirect swallowed it into a magazine edition. Here is how the redirect was found and removed.</description>
      <category>routing</category>
      <category>nuxt</category>
      <category>debugging</category>
      <category>uploads</category>
    </item>
    <item>
      <title>Notes Is Prerendered Markdown, Not a CMS</title>
      <link>https://dil.dev/notes/notes-prerendered-markdown-not-a-cms/</link>
      <guid isPermaLink="true">https://dil.dev/notes/notes-prerendered-markdown-not-a-cms/</guid>
      <pubDate>Sat, 03 Oct 2026 00:00:00 GMT</pubDate>
      <description>How the dil.dev/notes pipeline works: markdown files, a Vite plugin, strict frontmatter validation, and no CMS in sight.</description>
      <category>vite</category>
      <category>markdown</category>
      <category>json-ld</category>
      <category>static-site</category>
    </item>
    <item>
      <title>The Skull Turns Once Across Eight Stops, Newest First</title>
      <link>https://dil.dev/notes/skull-lab-one-turn-eight-stops/</link>
      <guid isPermaLink="true">https://dil.dev/notes/skull-lab-one-turn-eight-stops/</guid>
      <pubDate>Fri, 02 Oct 2026 00:00:00 GMT</pubDate>
      <description>The home page is now the skull lab: a Tripo sugar-skull bust rotates one full revolution across eight project stops, driven entirely by scroll position.</description>
      <category>three.js</category>
      <category>Lenis</category>
      <category>scroll</category>
      <category>WebGL</category>
    </item>
    <item>
      <title>Reduced Motion Skips the Skull Engine and the Split Titles</title>
      <link>https://dil.dev/notes/reduced-motion-skips-the-lab-engine/</link>
      <guid isPermaLink="true">https://dil.dev/notes/reduced-motion-skips-the-lab-engine/</guid>
      <pubDate>Thu, 01 Oct 2026 00:00:00 GMT</pubDate>
      <description>Reduced-motion visitors skip engine loading and SplitText entirely. The smoke transition falls back to plain navigation so nothing breaks.</description>
      <category>accessibility</category>
      <category>gsap</category>
      <category>webgl</category>
      <category>page-transitions</category>
    </item>
    <item>
      <title>Every Canonical on https://dil.dev, Including the Sitemap</title>
      <link>https://dil.dev/notes/seo-every-url-on-the-apex-host/</link>
      <guid isPermaLink="true">https://dil.dev/notes/seo-every-url-on-the-apex-host/</guid>
      <pubDate>Thu, 01 Oct 2026 00:00:00 GMT</pubDate>
      <description>Every URL on dil.dev points its canonical, og:url and sitemap loc at the apex host. A postbuild script enforces this so a stray Netlify subdomain never leaks into the index.</description>
      <category>seo</category>
      <category>canonical</category>
      <category>sitemap</category>
      <category>netlify</category>
    </item>
    <item>
      <title>A Thank-You Page Visit Is Not a Lead</title>
      <link>https://dil.dev/notes/thank-you-page-is-not-a-lead/</link>
      <guid isPermaLink="true">https://dil.dev/notes/thank-you-page-is-not-a-lead/</guid>
      <pubDate>Wed, 30 Sep 2026 00:00:00 GMT</pubDate>
      <description>Counting a thank-you page visit as a conversion inflates your lead numbers. Fire tracking only after Netlify acknowledges a real submission.</description>
      <category>netlify</category>
      <category>analytics</category>
      <category>forms</category>
      <category>conversion</category>
    </item>
    <item>
      <title>Consent Under the Mark, Once, Never a Banner</title>
      <link>https://dil.dev/notes/consent-under-the-mark-not-a-banner/</link>
      <guid isPermaLink="true">https://dil.dev/notes/consent-under-the-mark-not-a-banner/</guid>
      <pubDate>Sun, 27 Sep 2026 00:00:00 GMT</pubDate>
      <description>The preloader asks for analytics consent once, under the mark. No banner, no fixed control. Here is how the pattern works and why it holds.</description>
      <category>analytics</category>
      <category>consent</category>
      <category>GA4</category>
      <category>privacy</category>
    </item>
    <item>
      <title>Three Labels on a Tilted Ring Around the Skull, Dimming Behind It</title>
      <link>https://dil.dev/notes/skull-orbit-year-count-view/</link>
      <guid isPermaLink="true">https://dil.dev/notes/skull-orbit-year-count-view/</guid>
      <pubDate>Sat, 26 Sep 2026 00:00:00 GMT</pubDate>
      <description>Year, stop count and VIEW orbit the skull on a tilted ring that dims labels as they pass behind it. This is the only place translate and parallax are permitted.</description>
      <category>three.js</category>
      <category>WebGL</category>
      <category>scroll</category>
      <category>orbit</category>
    </item>
    <item>
      <title>A Hidden Instrument: Self-Hosted MediaPipe Hands, Camera Never Shown</title>
      <link>https://dil.dev/notes/play-hand-tracking-local-instrument/</link>
      <guid isPermaLink="true">https://dil.dev/notes/play-hand-tracking-local-instrument/</guid>
      <pubDate>Fri, 25 Sep 2026 00:00:00 GMT</pubDate>
      <description>dil.dev has a browser instrument hidden behind three entry points. Start loads ~20 MB of self-hosted MediaPipe and Tone.js; the camera feed is never rendered or sent anywhere.</description>
      <category>mediapipe</category>
      <category>tone.js</category>
      <category>webgl</category>
      <category>privacy</category>
    </item>
    <item>
      <title>58MB to 1.6MB: shipping a 3D face mesh with glTF-Transform and scroll-driven three.js</title>
      <link>https://dil.dev/notes/3d-face-mesh-58mb-to-1-6mb/</link>
      <guid isPermaLink="true">https://dil.dev/notes/3d-face-mesh-58mb-to-1-6mb/</guid>
      <pubDate>Wed, 23 Sep 2026 00:00:00 GMT</pubDate>
      <description>An AI-generated head mesh came out at 57.97MB and 987,094 vertices. One glTF-Transform command took it to 1.61MB and 89,200 vertices for a scroll-driven three.js hero.</description>
      <category>three.js</category>
      <category>WebGL</category>
      <category>glTF</category>
      <category>performance</category>
    </item>
    <item>
      <title>Smoke page transitions: domain-warped fbm from the click point, checked in real Chrome</title>
      <link>https://dil.dev/notes/smoke-page-transitions-webgl/</link>
      <guid isPermaLink="true">https://dil.dev/notes/smoke-page-transitions-webgl/</guid>
      <pubDate>Tue, 22 Sep 2026 00:00:00 GMT</pubDate>
      <description>A raw WebGL 1 shader blooms smoke from the click, swaps the page behind it without a reload, then drains toward the pointer. Plus the Playwright timing bug that made my checks sample 1.4s late.</description>
      <category>WebGL</category>
      <category>GLSL</category>
      <category>Playwright</category>
      <category>testing</category>
    </item>
    <item>
      <title>Netlify config.path vs a netlify.toml redirect: why /api/* 404&#39;d</title>
      <link>https://dil.dev/notes/netlify-config-path-vs-redirect/</link>
      <guid isPermaLink="true">https://dil.dev/notes/netlify-config-path-vs-redirect/</guid>
      <pubDate>Mon, 21 Sep 2026 00:00:00 GMT</pubDate>
      <description>A forced /api/* rewrite pointed at /.netlify/functions/api, a URL that stops existing once a v2 function sets config.path. Every data route 404&#39;d in production. The fix was deleting one redirect rule.</description>
      <category>Netlify</category>
      <category>serverless</category>
      <category>debugging</category>
    </item>
  </channel>
</rss>
