<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: dsplce.co</title>
    <description>The latest articles on DEV Community by dsplce.co (@dsplce-co).</description>
    <link>https://dev.to/dsplce-co</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3980080%2F1fef55d1-b7ab-4758-b48c-b8538b54f525.png</url>
      <title>DEV Community: dsplce.co</title>
      <link>https://dev.to/dsplce-co</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dsplce-co"/>
    <language>en</language>
    <item>
      <title>Kimi K3 vs Claude Fable 5 and Opus 4.8: a benchmark you can run yourself</title>
      <dc:creator>dsplce.co</dc:creator>
      <pubDate>Tue, 21 Jul 2026 22:37:37 +0000</pubDate>
      <link>https://dev.to/dsplce-co/kimi-k3-vs-claude-fable-5-and-opus-48-a-benchmark-you-can-run-yourself-4b15</link>
      <guid>https://dev.to/dsplce-co/kimi-k3-vs-claude-fable-5-and-opus-48-a-benchmark-you-can-run-yourself-4b15</guid>
      <description>&lt;p&gt;Kimi K3 was released this week, and like every model release it's being judged on leaderboard scores and screenshots. But a score is a bit like a football result, in that it tells you who won, not how the game was &lt;em&gt;played&lt;/em&gt;. You wouldn't sign a player off a scoreline alone — you'd want to watch the tape. With code models, the tape is the code itself: how it's structured, whether you'd actually want to maintain it. That's what leaderboards can't show you.&lt;/p&gt;

&lt;p&gt;So we built a benchmark that compares &lt;strong&gt;Kimi K3&lt;/strong&gt;, &lt;strong&gt;Claude Fable 5&lt;/strong&gt; and &lt;strong&gt;Claude Opus 4.8&lt;/strong&gt; on exactly that — and it's open source, so you can run it yourself at &lt;a href="https://github.com/dsplce-co/kimi-vs-fable-vs-opus" rel="noopener noreferrer"&gt;github.com/dsplce-co/kimi-vs-fable-vs-opus&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This note summarises our findings after running it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The task
&lt;/h2&gt;

&lt;p&gt;Given a double-entry ledger module with an existing test suite, make it production-ready and add two features: &lt;strong&gt;transaction reversal&lt;/strong&gt; and &lt;strong&gt;statement generation&lt;/strong&gt;. There's no hint that there are any bugs to find.&lt;/p&gt;

&lt;p&gt;But the existing tests aren't actually good — they don't test for real money-safety bugs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A transfer that can lose money if its second half doesn't complete (the transfer isn't atomic).&lt;/li&gt;
&lt;li&gt;Amounts drifting by fractions of a cent due to floating-point precision.&lt;/li&gt;
&lt;li&gt;An accessor that exposes internals and lets callers corrupt the ledger.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So passing these tests proves only &lt;em&gt;demo&lt;/em&gt; quality — that the core functionality works, nothing more. To grade the results we used a second, &lt;strong&gt;hidden&lt;/strong&gt; test suite that checks the actual money safety — one the models never see.&lt;/p&gt;

&lt;h2&gt;
  
  
  Same harness, on purpose
&lt;/h2&gt;

&lt;p&gt;All three models were run in the same harness:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Agentically inside Claude Code (opening and editing files, running tests, reacting to failures).&lt;/li&gt;
&lt;li&gt;In a high-effort setting.&lt;/li&gt;
&lt;li&gt;Three runs each, to see some run-to-run variance.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We made sure to run Kimi K3 inside Claude Code too, just like the Claudes, so the harness itself isn't a variable here. The results reflect how well each model does &lt;em&gt;as an agent inside Claude Code&lt;/em&gt; — how it would work with a dev team using it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;p&gt;We're framing the analysis around &lt;em&gt;how each model wrote the code&lt;/em&gt;, rather than the pass/fail binary.&lt;/p&gt;

&lt;h3&gt;
  
  
  What every model got right
&lt;/h3&gt;

&lt;p&gt;All three, across all nine runs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Properly fixed the atomicity bug — first validate all the transaction's lines (this pass can throw), then commit in a second pass that cannot throw, forbidding partial transactions:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// validate everything first — this part is allowed to throw&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;line&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;acc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;accounts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;open&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`account not open: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// then commit — from here nothing throws, so it's all-or-nothing&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;line&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;makeEntry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Implemented the &lt;code&gt;reverse&lt;/code&gt; feature as a compensating transaction routed through the existing &lt;code&gt;post&lt;/code&gt; function, inheriting its atomicity and lifecycle checks.&lt;/li&gt;
&lt;li&gt;Fixed the accessor leak that would let outside callers mutate the ledger's history.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So fixing the known bug and adding the features is a solved problem for all of them. They're interchangeable on that basic bar — the differentiation only starts to appear with the harder questions about the code's long-term behaviour.&lt;/p&gt;

&lt;h3&gt;
  
  
  Claude Fable 5
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Most consistent&lt;/strong&gt; across all three runs; the same load-bearing choices each time.&lt;/li&gt;
&lt;li&gt;Wrote a self-test in each run that verifies encapsulation &lt;em&gt;as an invariant&lt;/em&gt; — it tries to corrupt the ledger through the returned object and asserts that it fails, so if a future dev removes the defensive copy, the test breaks immediately instead of a silent leak shipping to production — which is like the difference between doing the safe thing and making the safe thing hard to undo.&lt;/li&gt;
&lt;li&gt;The only differences between runs were in degree of caution (one run reserved a namespace, guarded against overflow, threw on unknown accounts instead of returning zero). No run shipped a bug.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Its limitation: it stores money as a bare &lt;code&gt;number&lt;/code&gt; type — nothing enforces dollars vs cents at the type level; it's all runtime checks. And its reversal linkage uses a string-prefix convention, which is fragile if the naming convention isn't followed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Claude Opus 4.8
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Wrote the most &lt;strong&gt;self-documenting&lt;/strong&gt; code — comments exactly at the points where invariants hold, entries frozen at write time to guard against mutation, and detailed reports including root-cause writeups and a &lt;em&gt;"deliberately not changed"&lt;/em&gt; section outlining conscious choices.&lt;/li&gt;
&lt;li&gt;Produced the most instructive failure of the exercise, in one run. It tightened the &lt;code&gt;post&lt;/code&gt; function to reject non-integer amounts, but left the dollars-to-cents conversion unrounded — a floating-point issue, like &lt;code&gt;0.07 * 100 = 7.000000000000001&lt;/code&gt;:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// tightened check inside post: reject non-integer minor units&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isInteger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;amount must be whole minor units&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// but the conversion, in another file, was left unrounded&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;toMinor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;major&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;major&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// 0.07 * 100 -&amp;gt; 7.000000000000001&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;0.07&lt;/code&gt; transfer doesn't work now, because the stricter integer check collides with the floating-point imprecision from the unrounded dollars-to-cents conversion. Worse yet, the run's own tests stayed green, because it never created a test that transfers a fractional amount; it was like its safety net had the very hole its own change fell through — a landmine that looks green. The other two Opus runs rounded the conversion and tested it, so this is not a constant weakness of the model.&lt;/p&gt;

&lt;h3&gt;
  
  
  Kimi K3
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Produced &lt;strong&gt;structurally consistent&lt;/strong&gt; code across all three runs — atomic &lt;code&gt;post&lt;/code&gt;, idempotent reversal, copy-at-the-boundary pattern.&lt;/li&gt;
&lt;li&gt;Some runs added rejection of a reused transaction id with conflicting content, catching a double-spend class of bug that the reference solution doesn't.&lt;/li&gt;
&lt;li&gt;The boldest of the three: two of its runs changed the money type to &lt;code&gt;bigint&lt;/code&gt;, which is technically the right way to represent currency in JavaScript. But Kimi's &lt;code&gt;bigint&lt;/code&gt; conversion was inconsistent at the edges — the transfer function still accepted floating-point numbers, and the balance reader converted back via floating point (&lt;code&gt;Number(minor) / 100&lt;/code&gt;), reintroducing the very error &lt;code&gt;bigint&lt;/code&gt; was adopted to remove.&lt;/li&gt;
&lt;li&gt;In one run, Kimi froze the array returned by &lt;code&gt;getEntries&lt;/code&gt; to protect the ledger's internal state, but that broke callers who legitimately append to their own local copy of that array; the internal state stays safe, but the public contract quietly changed underneath everyone using it — it is the mirror image of a leak (a leak lets a caller corrupt your state, this protects your state by breaking the caller).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The remaining risk is at the edges
&lt;/h3&gt;

&lt;p&gt;What these findings tell us is that all three models have mostly solved the core problem — they fixed the bug and added in the features. The remaining risk is at the edges:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Untyped money values.&lt;/li&gt;
&lt;li&gt;Safety mechanisms located separately from the code they protect.&lt;/li&gt;
&lt;li&gt;Fragile, naming-based reversal linkage.&lt;/li&gt;
&lt;li&gt;Comments that don't match the actual behaviour.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's also worth noting that even for the &lt;em&gt;same model with the same prompt&lt;/em&gt;, the public-contract behaviour differed across runs — whether a duplicate transaction throws or silently no-ops, whether a reversal is allowed after an account is closed, whether querying a statement for a nonexistent account throws or returns an empty list. This variance was present in all three models.&lt;/p&gt;

&lt;p&gt;Which means that &lt;strong&gt;if a particular edge-case behaviour is crucial, it's best to pin it with an explicit test&lt;/strong&gt; rather than assuming it's consistent across model runs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The hidden test results
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Clean runs&lt;/th&gt;
&lt;th&gt;The one failure&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Claude Fable 5&lt;/td&gt;
&lt;td&gt;3 / 3&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Opus 4.8&lt;/td&gt;
&lt;td&gt;2 / 3&lt;/td&gt;
&lt;td&gt;a fractional transfer throws incorrectly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kimi K3&lt;/td&gt;
&lt;td&gt;2 / 3&lt;/td&gt;
&lt;td&gt;a frozen returned array breaks a caller&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Both Opus's and Kimi's failures were about judgment and edge cases, not fundamental capability. This aligns with the earlier finding that all three have largely solved the core problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  The restraint trap
&lt;/h2&gt;

&lt;p&gt;We also included a &lt;em&gt;restraint trap&lt;/em&gt;: an existing function rounds money to whole units, which is lossy — but it's well-documented, tested behaviour that some other code depends on. The senior move is to leave the rounding as-is, add a warning comment, and provide a new, exact accessor next to it. The junior move is to silently "fix" the rounding, breaking the existing callers that depend on it.&lt;/p&gt;

&lt;p&gt;Fable and Opus made the senior move in every run — they preserved the existing rounding behaviour, added a warning comment, and added an exact alternative accessor. Kimi was the least disciplined here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Silently changed the rounding behaviour.&lt;/li&gt;
&lt;li&gt;Produced a different rounding result in each run.&lt;/li&gt;
&lt;li&gt;Left the comment claiming &lt;em&gt;"whole major units"&lt;/em&gt; despite the change.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We're sharing this because it's revealing — how a model handles a contract it didn't write, when nobody forces it to, says a lot about what it'll do to the contracts already in your codebase.&lt;/p&gt;

&lt;h2&gt;
  
  
  The verdict
&lt;/h2&gt;

&lt;p&gt;The verdict is two-sided. On one hand, &lt;strong&gt;Fable wins on consistency&lt;/strong&gt; — no shipped bugs, and the most reliable. On the other, this is a single, small task, and the other models' failures weren't substantial enough to rule out the results shifting on a different problem.&lt;/p&gt;

&lt;p&gt;So what we'd recommend is to treat this benchmark as a &lt;strong&gt;template&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Define which properties of correctness matter for your use case.&lt;/li&gt;
&lt;li&gt;Run many more than three attempts per model.&lt;/li&gt;
&lt;li&gt;Test across diverse types of task, not just a ledger.&lt;/li&gt;
&lt;li&gt;Include long-horizon tasks.&lt;/li&gt;
&lt;li&gt;Measure consistency separately, rather than folding it into pass/fail.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Run it yourself
&lt;/h2&gt;

&lt;p&gt;You can run the benchmark on your own codebase, with your own hidden tests, using the code (including the exact prompt) published at &lt;a href="https://github.com/dsplce-co/kimi-vs-fable-vs-opus" rel="noopener noreferrer"&gt;github.com/dsplce-co/kimi-vs-fable-vs-opus&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Building with AI is our actual day job.&lt;/strong&gt; We &lt;a href="https://dsplce.co/" rel="noopener noreferrer"&gt;build software with AI&lt;/a&gt; for other companies — this benchmark was just us kicking the tyres on the tools we use every day.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;New to Kimi K3?&lt;/strong&gt; Here's &lt;a href="https://dsplce.co/blog/kimi-k3-claude-code" rel="noopener noreferrer"&gt;how to run it inside Claude Code&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>github</category>
      <category>llm</category>
      <category>opensource</category>
    </item>
    <item>
      <title>How to use Kimi K3 with Claude Code (subscription, not API)</title>
      <dc:creator>dsplce.co</dc:creator>
      <pubDate>Fri, 17 Jul 2026 21:47:10 +0000</pubDate>
      <link>https://dev.to/dsplce-co/how-to-use-kimi-k3-with-claude-code-subscription-not-api-kol</link>
      <guid>https://dev.to/dsplce-co/how-to-use-kimi-k3-with-claude-code-subscription-not-api-kol</guid>
      <description>&lt;p&gt;Moonshot AI shipped &lt;strong&gt;Kimi K3&lt;/strong&gt; today — a 2.8-trillion-parameter open-weight model that, on the benchmarks, is trading blows with the frontier: &lt;a href="https://venturebeat.com/technology/chinas-moonshot-ai-releases-kimi-k3-the-largest-open-source-model-ever-rivaling-top-u-s-systems" rel="noopener noreferrer"&gt;Claude Fable 5 and GPT-5.6 Sol&lt;/a&gt;. On one frontend-coding arena it actually &lt;a href="https://www.tomshardware.com/tech-industry/artificial-intelligence/moonshot-releases-2-8-trillion-parameter-kimi-k3" rel="noopener noreferrer"&gt;took the top spot&lt;/a&gt;, a notch above Fable 5.&lt;/p&gt;

&lt;p&gt;Benchmarks are one thing. Whether a model is good &lt;em&gt;for you&lt;/em&gt; is another — and the only honest way to settle that is to point it at your own code and watch it work for an afternoon. Claude Code is where a lot of us already do that work, so the fastest way to give K3 a go is to run it &lt;em&gt;inside Claude Code&lt;/em&gt;: same cockpit you already know, different engine under the hood.&lt;/p&gt;

&lt;p&gt;Here's the whole setup. It takes about two minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this works at all
&lt;/h2&gt;

&lt;p&gt;Claude Code is a client. A very good one — the agent loop, the tool use, the diffs, the terminal UX — but underneath, it just speaks the Anthropic API protocol to some backend. Point it at Anthropic's servers and you get Claude. Point it somewhere else that speaks the same protocol and you get whatever's there.&lt;/p&gt;

&lt;p&gt;Kimi's coding endpoint speaks that protocol. So you keep the cockpit you already like and swap the engine — no new tool to learn, no workflow to rebuild. And because you're bringing your own backend, &lt;strong&gt;you don't need an Anthropic subscription or a Claude API key at all.&lt;/strong&gt; The only account in play is on the Kimi side.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you'll need
&lt;/h2&gt;

&lt;p&gt;One thing: a paid &lt;strong&gt;&lt;a href="https://www.kimi.com/code/docs/en/" rel="noopener noreferrer"&gt;Kimi Code&lt;/a&gt;&lt;/strong&gt; plan that includes K3 — the subscription, not pay-as-you-go API credits, which of course are more expensive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1 — generate a Kimi API key
&lt;/h2&gt;

&lt;p&gt;Log in to the Kimi coding console:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://www.kimi.com/code/console
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open the &lt;strong&gt;API Keys&lt;/strong&gt; page, create a new key, and copy it — it's shown to you exactly once, so paste it somewhere safe. It'll look like &lt;code&gt;sk-kimi-…&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;(Yes, it's called an "API key" even though you're on a subscription. That's just how you authenticate; your usage still counts against your plan, not a metered bill. More on that further down.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2 — set the environment variables
&lt;/h2&gt;

&lt;p&gt;Claude Code reads its backend from environment variables. Open your shell's startup file — &lt;code&gt;~/.zshrc&lt;/code&gt;, &lt;code&gt;~/.bashrc&lt;/code&gt;, or whatever your shell uses — and add:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANTHROPIC_BASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://api.kimi.com/coding/
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANTHROPIC_AUTH_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sk-kimi-…            &lt;span class="c"&gt;# the key from step 1&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANTHROPIC_DEFAULT_HAIKU_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;kimi-k3
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANTHROPIC_DEFAULT_OPUS_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;kimi-k3
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANTHROPIC_DEFAULT_SONNET_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;kimi-k3
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANTHROPIC_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;kimi-k3
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;CLAUDE_CODE_SUBAGENT_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;kimi-k3
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ENABLE_TOOL_SEARCH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What each one is doing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;ANTHROPIC_BASE_URL&lt;/code&gt;&lt;/strong&gt; — where Claude Code sends its requests. This is the actual switch: Kimi's coding endpoint instead of Anthropic's.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;ANTHROPIC_AUTH_TOKEN&lt;/code&gt;&lt;/strong&gt; — your Kimi key. If you've ever logged into Claude Code the normal way, this stands in for that.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;ANTHROPIC_MODEL&lt;/code&gt;&lt;/strong&gt; — the main model for the session.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;ANTHROPIC_DEFAULT_{HAIKU,SONNET,OPUS}_MODEL&lt;/code&gt;&lt;/strong&gt; — Claude Code reaches for different Claude tiers for different jobs: a small fast one for background chores, the mid one for the main work, the big one for heavy reasoning. Kimi's endpoint only serves &lt;code&gt;kimi-k3&lt;/code&gt;, so you map all three onto it — otherwise Claude Code tries to call a Claude model that isn't there, and that request fails.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;CLAUDE_CODE_SUBAGENT_MODEL&lt;/code&gt;&lt;/strong&gt; — the subagents Claude Code spawns for sub-tasks run on &lt;code&gt;kimi-k3&lt;/code&gt; too, instead of defaulting back to a Claude tier.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;ENABLE_TOOL_SEARCH=false&lt;/code&gt;&lt;/strong&gt; — Tool Search is a newer Claude Code feature that loads tool definitions on demand. It leans on Anthropic-specific behaviour, so it's the first thing to misbehave against a third-party backend. Turn it off for a clean run.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 3 — reload and launch
&lt;/h2&gt;

&lt;p&gt;Pick up the new variables — either reload the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;source&lt;/span&gt; ~/.zshrc      &lt;span class="c"&gt;# or ~/.bashrc, or your shell's equivalent&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;…or just open a fresh terminal. Then start Claude Code as usual:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. You're driving Kimi K3 through Claude Code.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to check which model you're actually running
&lt;/h2&gt;

&lt;p&gt;Two reliable ways, and one that lies to you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The welcome screen.&lt;/strong&gt; When Claude Code starts, it prints the active model bottom-left. On a working setup it reads &lt;code&gt;kimi-k3&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1ghvlz6gnajthmpxep8e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1ghvlz6gnajthmpxep8e.png" alt="Claude Code's welcome screen showing kimi-k3 as the active model" width="800" height="124"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You'll notice it says &lt;strong&gt;API Usage Billing&lt;/strong&gt; right next to it. Don't let that spook you — it doesn't mean you're being metered. Claude Code labels any token-based auth that way (as opposed to a subscription &lt;em&gt;login&lt;/em&gt;); on Kimi's side your usage still draws from your flat plan. Essentially it's a label about how you &lt;em&gt;authenticated&lt;/em&gt;, not how you &lt;em&gt;pay&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;/status&lt;/code&gt;.&lt;/strong&gt; Inside a session, type &lt;code&gt;/status&lt;/code&gt;. If the Base URL reads &lt;code&gt;https://api.kimi.com/coding/&lt;/code&gt;, the switch took.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Asking the agent — don't.&lt;/strong&gt; You'd think you could just ask it "which model are you?" — but this is the one check that misleads you. Claude Code injects a system prompt telling the model it &lt;em&gt;is&lt;/em&gt; Claude, so K3, being an obedient junior, will often cheerfully answer "I'm Claude." It isn't lying to you on purpose; it's doing exactly what its instructions told it to. Which is the whole thing in miniature: a model's output is a product of the conditions you put it in, not some fixed truth you can interrogate out of it. Trust the UI, not the confession.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is it worth it?
&lt;/h2&gt;

&lt;p&gt;For the price of finding out — two minutes and a subscription you can cancel — yes, run the experiment. But run the &lt;em&gt;right&lt;/em&gt; one. A leaderboard tells you a model can be good in the aggregate; it can't tell you whether it's good at &lt;em&gt;your&lt;/em&gt; stack, &lt;em&gt;your&lt;/em&gt; patterns, the specific mess in &lt;em&gt;your&lt;/em&gt; repo. That's not something you read off a benchmark — it's something you feel after an afternoon of real work.&lt;/p&gt;

&lt;p&gt;So point it at a real task, not a toy. Watch where it's sharp and where it flails. That's the only comparison that decides anything, and now it costs you almost nothing to run.&lt;/p&gt;

&lt;p&gt;The bigger point sits one level up: the model is the interchangeable part. Being able to swap the engine under your tools — Claude this week, Kimi the next, whatever's ahead of the pack after that — is worth more than any single model being "the best," because "best" now has a shelf life measured in weeks. The skill that lasts isn't picking the winner; it's knowing how to put whichever model you're holding to work.&lt;/p&gt;

&lt;p&gt;That's the same instinct behind everything we build: the model is the fast, tireless junior — the judgement about what actually ships is yours. If you've got an AI-built app that dazzles in a demo and then buckles the moment real users touch it, taking it &lt;a href="https://dsplce.co/demo-was-the-easy-part" rel="noopener noreferrer"&gt;the last mile to production&lt;/a&gt; is exactly what we do.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Do I need a Claude or Anthropic subscription to use Kimi K3 in Claude Code?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No. Claude Code is just a client that speaks the Anthropic API protocol; pointing it at Kimi's coding endpoint means you authenticate entirely with a Kimi key. All you need is a paid Kimi Code plan that includes K3 — no Anthropic account, no Claude API key.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is a Kimi subscription cheaper than API usage for Claude Code?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For agent-style coding, yes. A single Claude Code session can burn millions of tokens, and metered API pricing scales with every one of them. A flat monthly Kimi Code membership fixes the cost no matter how hard you drive it — the same reason heavy Claude Code users pick a Claude subscription over the raw API.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is Kimi K3 as good as Claude for coding?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;On its launch benchmarks it's frontier-level — Kimi K3 topped one frontend-coding arena ahead of Claude Fable 5, and it ships a 1M-token context window. But a leaderboard isn't your codebase. The only comparison that settles it is running it on your real projects for a few sessions and assessing the outcomes yourself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why does Claude Code show "API Usage Billing" if I'm on a Kimi subscription?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Because that label reflects how you authenticated (with a token), not how you're billed. Claude Code shows "API Usage Billing" for any token-based backend and "subscription" only for its own login flow. On Kimi's side, your usage still draws from your flat plan.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I trust the agent when I ask which model it is?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No — that's the one check that misleads you. Claude Code's system prompt tells the model it is Claude, so Kimi K3 will often answer "I'm Claude." Verify with the welcome screen or &lt;code&gt;/status&lt;/code&gt; (Base URL should read &lt;code&gt;https://api.kimi.com/coding/&lt;/code&gt;) instead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I switch back to Claude?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Comment out or unset the environment variables in your shell profile — &lt;code&gt;ANTHROPIC_BASE_URL&lt;/code&gt; and &lt;code&gt;ANTHROPIC_AUTH_TOKEN&lt;/code&gt; are the two that matter — then reload it or open a new terminal, and Claude Code talks to Anthropic again. If you flip back and forth a lot, keep the Kimi block behind a shell alias or a separate profile you can toggle instead of editing the file each time.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>HTML/CSS Animation to Video (MP4): the Headless, Deterministic Way (incl. Claude)</title>
      <dc:creator>dsplce.co</dc:creator>
      <pubDate>Fri, 12 Jun 2026 06:30:03 +0000</pubDate>
      <link>https://dev.to/dsplce-co/htmlcss-animation-to-video-mp4-the-headless-deterministic-way-incl-claude-4a2</link>
      <guid>https://dev.to/dsplce-co/htmlcss-animation-to-video-mp4-the-headless-deterministic-way-incl-claude-4a2</guid>
      <description>&lt;p&gt;To convert an HTML/CSS animation — including one Claude generated — to MP4, render it &lt;strong&gt;headlessly&lt;/strong&gt; instead of screen-recording it: a headless Chrome instance drives the page's clock, captures each frame at an exact timestamp, and &lt;a href="https://ffmpeg.org/documentation.html" rel="noopener noreferrer"&gt;FFmpeg&lt;/a&gt; stitches the frames into video. That makes the output frame-perfect and identical on every run. The one-command path is &lt;a href="https://crates.io/crates/htmlrec" rel="noopener noreferrer"&gt;htmlrec&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;dsplce-co/tap/htmlrec ffmpeg
hrec render animation.html &lt;span class="nt"&gt;-o&lt;/span&gt; out.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Defaults:&lt;/strong&gt; 1280×720, 30fps, 5s, MP4. &lt;strong&gt;Formats:&lt;/strong&gt; MP4, WebM (VP9, optional alpha), and MOV (ProRes 4444, alpha). Override resolution and timing with &lt;code&gt;--width&lt;/code&gt;, &lt;code&gt;--height&lt;/code&gt;, &lt;code&gt;--fps&lt;/code&gt;, and &lt;code&gt;--duration&lt;/code&gt;, and add &lt;code&gt;--transparent&lt;/code&gt; for an alpha channel.&lt;/p&gt;




&lt;p&gt;So you asked Claude to animate something. Maybe a logo, a loading screen, a data viz. It spat out a neat HTML file with CSS keyframes, everything looks crisp in the browser — and now you need it as an MP4.&lt;/p&gt;

&lt;p&gt;The obvious approach is screen recording. Open QuickTime or OBS, hit record, play the animation, stop, trim. Works, kind of. Except it's not frame-perfect. If your machine lags for half a second, that lag is baked into the video. The animation runs at whatever speed your CPU felt like that afternoon. Completely non-deterministic. And the moment you tweak something — wrong colour, timing off by 200ms — you're setting the whole thing up again, which is just tiring. Not to mention that every time you hit record you start at a slightly different frame, so swapping the asset in your video editor becomes a pain because nothing lines up the same way twice.&lt;/p&gt;

&lt;p&gt;There's a better way.&lt;/p&gt;

&lt;p&gt;You can use &lt;a href="https://crates.io/crates/htmlrec" rel="noopener noreferrer"&gt;htmlrec&lt;/a&gt; — a CLI tool that renders HTML animations to video frame by frame, without touching your screen. It controls the browser clock directly, so every frame is captured at exactly the right moment regardless of your machine's load. Pixel-perfect, every single time.&lt;/p&gt;

&lt;p&gt;Install it with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;dsplce-co/tap/htmlrec ffmpeg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  How do I convert an HTML/CSS animation to MP4?
&lt;/h2&gt;

&lt;p&gt;The reliable way to convert an HTML animation to video is to render it headlessly, frame by frame, instead of screen-recording it. Point a tool at your HTML file, let it drive the browser clock, and capture each frame at an exact timestamp:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hrec render animation.html &lt;span class="nt"&gt;-o&lt;/span&gt; out.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works for any self-contained HTML/CSS animation — a logo reveal, a loading screen, a chart, or anything an LLM like Claude generated for you. The full step-by-step is below.&lt;/p&gt;




&lt;h2&gt;
  
  
  The workflow
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Get your animation from Claude&lt;/strong&gt; &lt;em&gt;(skip if you already have an HTML animation)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Ask Claude for whatever you need. Something like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Create an HTML/CSS animation of a logo appearing with a fade and slight upward motion, black background, 3 seconds"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You'll get back a self-contained HTML file. Save it — let's call it &lt;code&gt;animation.html&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Render it&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hrec render animation.html &lt;span class="nt"&gt;-o&lt;/span&gt; out.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. By default you get a 1280×720, 30fps, 5-second MP4.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Adjust if needed&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Custom resolution and duration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hrec render animation.html &lt;span class="nt"&gt;-o&lt;/span&gt; out.mp4 &lt;span class="nt"&gt;--width&lt;/span&gt; 1920 &lt;span class="nt"&gt;--height&lt;/span&gt; 1080 &lt;span class="nt"&gt;--duration&lt;/span&gt; 3 &lt;span class="nt"&gt;--fps&lt;/span&gt; 60
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Need transparency (for overlaying on other footage)?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hrec render animation.html &lt;span class="nt"&gt;-o&lt;/span&gt; out.webm &lt;span class="nt"&gt;--transparent&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;WebM with VP9 preserves the alpha channel. Works with &lt;code&gt;.mov&lt;/code&gt; (ProRes 4444) too if you're in a video editing pipeline.&lt;/p&gt;




&lt;h2&gt;
  
  
  Example
&lt;/h2&gt;

&lt;p&gt;Here's a minimal animation Claude might generate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="py"&gt;place-items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100vh&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nc"&gt;.logo&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;sans-serif&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;64px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#fff&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;translateY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;20px&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nl"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;appear&lt;/span&gt; &lt;span class="m"&gt;1s&lt;/span&gt; &lt;span class="n"&gt;ease&lt;/span&gt; &lt;span class="n"&gt;forwards&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;@keyframes&lt;/span&gt; &lt;span class="n"&gt;appear&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nt"&gt;to&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;translateY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"logo"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;dsplce&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Render it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hrec render animation.html &lt;span class="nt"&gt;-o&lt;/span&gt; logo.mp4 &lt;span class="nt"&gt;--duration&lt;/span&gt; 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Clean MP4, no screen recording, no lag, no manual trimming.&lt;/p&gt;




&lt;p&gt;The whole point of htmlrec is to remove the friction between "Claude gave me an animation" and "I have a video file I can actually use." One command, done.&lt;/p&gt;

&lt;p&gt;If you're generating HTML assets regularly — ad creatives, onboarding animations, motion graphics for social — this becomes part of your pipeline rather than a manual step every time. Turning something an AI generated into a deterministic, repeatable artifact you can actually ship is the same instinct we bring to whole products — it's the idea behind taking an &lt;a href="https://dsplce.co/demo-was-the-easy-part" rel="noopener noreferrer"&gt;AI prototype to production&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Source and docs: &lt;a href="https://github.com/dsplce-co/htmlrec" rel="noopener noreferrer"&gt;github.com/dsplce-co/htmlrec&lt;/a&gt;. More about &lt;a href="https://dsplce.co/our-work/htmlrec" rel="noopener noreferrer"&gt;htmlrec on our site&lt;/a&gt;, alongside the rest of our open-source work.&lt;/p&gt;




&lt;h2&gt;
  
  
  How do I convert a CSS animation to MP4?
&lt;/h2&gt;

&lt;p&gt;CSS keyframe animations render to MP4 exactly like everything else — there's nothing special to configure. As long as the motion is driven by &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_animations/Using_CSS_animations" rel="noopener noreferrer"&gt;CSS animations&lt;/a&gt; (&lt;code&gt;@keyframes&lt;/code&gt;, &lt;code&gt;transition&lt;/code&gt;, &lt;code&gt;animation&lt;/code&gt;), a headless renderer captures it deterministically. Match the duration to your animation and you're done:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hrec render animation.html &lt;span class="nt"&gt;-o&lt;/span&gt; out.mp4 &lt;span class="nt"&gt;--duration&lt;/span&gt; 3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The default is 30fps; pass &lt;code&gt;--fps 60&lt;/code&gt; for extra-smooth fast motion when your animation needs it. Either way the frames come from the browser's own clock, so a 3-second animation is exactly 3 seconds of video — no drift, no dropped frames.&lt;/p&gt;




&lt;h2&gt;
  
  
  Is the output deterministic (including in CI)?
&lt;/h2&gt;

&lt;p&gt;"Deterministic" is the whole point: the same HTML always produces the same video, regardless of what your machine was doing while it rendered. A screen recorder can't promise that — it captures in real time, so any hiccup is baked into the file. A headless renderer steps the page forward one frame at a time and grabs each frame at a precise timestamp, then hands the sequence to FFmpeg:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hrec render animation.html &lt;span class="nt"&gt;-o&lt;/span&gt; out.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That determinism is what makes this practical in a real pipeline. Tweak a colour or a timing value, re-render, and only the parts you changed change — the rest of the frames are identical to last time. Swapping the clip into a video editor lines up perfectly every time because nothing is recorded "live."&lt;/p&gt;

&lt;p&gt;The same property makes it CI-friendly: it runs headless with no display, so the identical &lt;code&gt;hrec render&lt;/code&gt; command produces the identical video on a build server as it does on your laptop — install Chromium and FFmpeg on the runner and wire it into your pipeline.&lt;/p&gt;

&lt;p&gt;That same instinct — do the rigorous version, not the flaky one — is what separates an AI-built &lt;em&gt;demo&lt;/em&gt; from an AI-built &lt;em&gt;product&lt;/em&gt;. A demo comes together astonishingly fast; making it hold up under real users and constant change is the actual work. If you're staring at an AI-built app that won the demo and now fights every change, &lt;a href="https://dsplce.co/demo-was-the-easy-part" rel="noopener noreferrer"&gt;taking it the last mile to production&lt;/a&gt; is exactly what we do.&lt;/p&gt;




&lt;h2&gt;
  
  
  Do you need a framework like HyperFrames?
&lt;/h2&gt;

&lt;p&gt;Short answer: it depends on how much you're building.&lt;/p&gt;

&lt;p&gt;There's a growing category of open-source tools that turn HTML into video. HeyGen recently open-sourced &lt;strong&gt;HyperFrames&lt;/strong&gt;, a renderer that converts HTML to video and is positioned as "built for agents" — meant to be driven programmatically inside LLM workflows. Under the hood it's the same core idea as the approach here: a headless browser renders the page and FFmpeg encodes the frames, deterministically. It runs locally (Node 22+ and FFmpeg) — there's no hosted service or signup.&lt;/p&gt;

&lt;p&gt;So the real question isn't "platform vs. local tool" — both run on your machine. It's &lt;strong&gt;scope&lt;/strong&gt;. HyperFrames is a larger Node framework: it ships adapters for GSAP, Lottie, and Three.js plus a timeline model for composing more complex, programmatic animations. If that's the kind of thing you're building, a framework that size earns its keep.&lt;/p&gt;

&lt;p&gt;But for the common case — you already have a self-contained HTML/CSS animation and you just want a clean MP4 — you don't need a framework at all. The job is two well-understood pieces:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://developer.chrome.com/docs/chromium/new-headless" rel="noopener noreferrer"&gt;Headless Chrome&lt;/a&gt;&lt;/strong&gt; renders the page and steps the animation clock forward frame by frame.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FFmpeg&lt;/strong&gt; stitches those frames into a video.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's exactly what &lt;a href="https://crates.io/crates/htmlrec" rel="noopener noreferrer"&gt;htmlrec&lt;/a&gt; is: a single-purpose Rust CLI wrapping those two. Point it at an HTML file, get a deterministic MP4 — one command, nothing to compose, frame-perfect.&lt;/p&gt;




&lt;h2&gt;
  
  
  How to convert a Claude animation to MP4
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hrec render animation.html &lt;span class="nt"&gt;-o&lt;/span&gt; out.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;MP4 is the safest choice for most use cases — universally supported, small file size, plays everywhere. No extra flags needed.&lt;/p&gt;




&lt;h2&gt;
  
  
  How do I convert a Claude animation to WebM?
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hrec render animation.html &lt;span class="nt"&gt;-o&lt;/span&gt; out.webm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;WebM is a good fit if you're embedding the animation on the web. Smaller than MP4 at comparable quality, and supported natively in Chrome, Firefox, and Edge (Safari's VP9 support is patchier, so test there).&lt;/p&gt;




&lt;h2&gt;
  
  
  How do I get a transparent (alpha) video from an HTML animation?
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hrec render animation.html &lt;span class="nt"&gt;-o&lt;/span&gt; out.mov &lt;span class="nt"&gt;--transparent&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;MOV with ProRes 4444 is the most reliable format for transparency — virtually every video editor (Premiere, After Effects, Final Cut) handles it without issues.&lt;/p&gt;

&lt;p&gt;WebM also supports an alpha channel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hrec render animation.html &lt;span class="nt"&gt;-o&lt;/span&gt; out.webm &lt;span class="nt"&gt;--transparent&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But in practice, a lot of editing software either doesn't support WebM at all, or silently drops the alpha channel when importing it. If you're taking the file into a video editor, stick with MOV.&lt;/p&gt;

&lt;p&gt;Still, if you're embedding the animation directly on a web page rather than taking it into an editor, transparent WebM is a strong option — Chrome and Firefox handle it natively and the file size is a fraction of MOV. Safari's support for alpha-channel WebM is unreliable, though, so don't count on it there.&lt;/p&gt;




&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;How do I get a transparent (alpha) video from an HTML animation?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Add &lt;code&gt;--transparent&lt;/code&gt; and pick an alpha-capable container: &lt;code&gt;hrec render animation.html -o out.mov --transparent&lt;/code&gt; gives MOV (ProRes 4444), the most reliable in video editors, and &lt;code&gt;-o out.webm --transparent&lt;/code&gt; gives VP9 with alpha for the web. Using &lt;code&gt;--transparent&lt;/code&gt; with an &lt;code&gt;.mp4&lt;/code&gt; is rejected, because H.264 has no alpha channel. Many editors silently drop WebM's alpha on import, so prefer MOV for Premiere, After Effects, or Final Cut.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I set a custom resolution and frame rate?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes. &lt;code&gt;--width&lt;/code&gt;, &lt;code&gt;--height&lt;/code&gt;, and &lt;code&gt;--fps&lt;/code&gt; override the defaults of 1280×720 at 30fps — e.g. &lt;code&gt;hrec render animation.html -o out.mp4 --width 1920 --height 1080 --fps 60&lt;/code&gt;. Match &lt;code&gt;--duration&lt;/code&gt; to your animation's length so nothing is cut off or padded.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What about long animations?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Set &lt;code&gt;--duration&lt;/code&gt; to the real length in seconds; the default is 5. Frames are captured by stepping a virtual clock rather than playing back in real time, so a longer animation simply means more frames — not a longer wait tied to playback speed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does it work headless in CI?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes. It drives headless Chromium and shells out to FFmpeg with no display, so the same &lt;code&gt;hrec render&lt;/code&gt; command runs in a CI container once Chromium and FFmpeg are installed on the runner. Because capture is clock-driven rather than real-time, the video is identical on every run regardless of CI load.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What about fonts and external assets?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Make the animation self-contained — inline the CSS, embed fonts, and base64 or co-locate images — so the headless browser renders exactly what you saw. A web font that hasn't finished loading when the first frame is captured falls back to a system face, so embed or preload anything the animation depends on.&lt;/p&gt;

</description>
      <category>automation</category>
      <category>claude</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to toggle realtime on a table in Supabase migrations</title>
      <dc:creator>dsplce.co</dc:creator>
      <pubDate>Thu, 11 Jun 2026 21:28:54 +0000</pubDate>
      <link>https://dev.to/dsplce-co/how-to-toggle-realtime-on-a-table-in-supabase-migrations-388m</link>
      <guid>https://dev.to/dsplce-co/how-to-toggle-realtime-on-a-table-in-supabase-migrations-388m</guid>
      <description>&lt;p&gt;You set up a table locally, opened it in Supabase Studio, flipped the Realtime toggle on, and everything worked — your frontend was getting live updates, subscriptions firing perfectly. Then you ran &lt;code&gt;supabase db diff&lt;/code&gt;, generated your migration, pushed it all to production… and Realtime is dead. No events, no subscriptions, nothing.&lt;/p&gt;

&lt;p&gt;You check the table — it's there. Columns, RLS policies, indexes, all present. But the Realtime toggle? Off. As if you never touched it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why?
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;supabase db diff&lt;/code&gt; command compares database schemas. Realtime configuration is not a schema change — it's a publication membership. Under the hood, flipping that toggle in Studio just adds your table to a Postgres publication called &lt;code&gt;supabase_realtime&lt;/code&gt;. That's it. And because it's neither a schema change nor a data change, &lt;code&gt;db diff&lt;/code&gt; is completely blind to it. It won't show up in the diff output, and it won't end up in your migration file.&lt;/p&gt;

&lt;p&gt;If you want Realtime to travel with your migrations, you just simply need to write the SQL yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  So what's the SQL?
&lt;/h2&gt;

&lt;p&gt;It's just one line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="n"&gt;PUBLICATION&lt;/span&gt; &lt;span class="n"&gt;supabase_realtime&lt;/span&gt; &lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That adds your table to the publication. Any client subscribed to it will start receiving changes.&lt;/p&gt;

&lt;p&gt;Multiple tables at once:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="n"&gt;PUBLICATION&lt;/span&gt; &lt;span class="n"&gt;supabase_realtime&lt;/span&gt; &lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;notifications&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Removing a table from Realtime:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="n"&gt;PUBLICATION&lt;/span&gt; &lt;span class="n"&gt;supabase_realtime&lt;/span&gt; &lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Turning it into a migration
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;supabase migration new enable_realtime_on_messages
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Paste the &lt;code&gt;ALTER PUBLICATION&lt;/code&gt; statement into the generated file and you're done. Next time you deploy, the table will be part of the &lt;code&gt;supabase_realtime&lt;/code&gt; publication in the target environment.&lt;/p&gt;

&lt;p&gt;One thing to watch out for — if someone already enabled Realtime on that table manually in production, the migration will fail with a duplicate table error.&lt;/p&gt;

&lt;h2&gt;
  
  
  The faster way — supabase-plus
&lt;/h2&gt;

&lt;p&gt;Keeping track of which tables have Realtime enabled, remembering the publication name, checking the current state before writing the migration — it adds up. &lt;a href="https://github.com/dsplce-co/supabase-plus" rel="noopener noreferrer"&gt;supabase-plus&lt;/a&gt; handles all of that with a single command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sbp manage realtime
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdsplce.co%2Fmanage-rt-demo.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdsplce.co%2Fmanage-rt-demo.gif" alt="sbp manage realtime demo" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It connects to your local database, lists every table in the schema, and shows you which ones are currently subscribed to &lt;code&gt;supabase_realtime&lt;/code&gt;. From there it's an interactive multi-select — toggle tables on or off, and the tool generates the &lt;code&gt;ALTER PUBLICATION&lt;/code&gt; statements for you, drops them into a timestamped migration file in &lt;code&gt;supabase/migrations/&lt;/code&gt;, and optionally applies the migration to your local database right away.&lt;/p&gt;

&lt;p&gt;You can also scope it to a specific schema:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sbp manage realtime &lt;span class="nt"&gt;--schema&lt;/span&gt; private
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No looking up syntax, no guessing what's already enabled — the tool shows you the state and lets you change it in one step.&lt;/p&gt;

&lt;p&gt;One toggle in Studio, zero lines in your migration history. Now you know how to fix that.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Related:&lt;/strong&gt; working closer to the database? See &lt;a href="https://dsplce.co/blog/postgres-running-queries" rel="noopener noreferrer"&gt;how to see running queries in Postgres and kill them&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>database</category>
      <category>postgres</category>
      <category>sql</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to see running queries in Postgres and kill them</title>
      <dc:creator>dsplce.co</dc:creator>
      <pubDate>Thu, 11 Jun 2026 21:28:08 +0000</pubDate>
      <link>https://dev.to/dsplce-co/how-to-see-running-queries-in-postgres-and-kill-them-j5i</link>
      <guid>https://dev.to/dsplce-co/how-to-see-running-queries-in-postgres-and-kill-them-j5i</guid>
      <description>&lt;p&gt;Something is slow. Maybe a page takes forever to load, maybe a migration is hanging, maybe your Supabase dashboard just spins. You suspect a query is stuck somewhere in your database, but you can't see what's happening — Postgres doesn't exactly surface this on its own.&lt;/p&gt;

&lt;p&gt;Turns out it does. You just need to ask.&lt;/p&gt;

&lt;h2&gt;
  
  
  Seeing what's running
&lt;/h2&gt;

&lt;p&gt;Postgres keeps track of every active connection and what it's doing in a system view called &lt;code&gt;pg_stat_activity&lt;/code&gt;. You can query it like any table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clock_timestamp&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;query_start&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;duration&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;pg_stat_activity&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;state&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s1"&gt;'idle'&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;duration&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That gives you every non-idle process — its process ID, current state, the SQL it's running, and how long it's been at it. If something has been running for minutes when it should take milliseconds, you've found your problem.&lt;/p&gt;

&lt;p&gt;A few things worth knowing about the columns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;pid&lt;/code&gt; — the process ID, which you'll need if you want to kill it&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;state&lt;/code&gt; — usually &lt;code&gt;active&lt;/code&gt; (running right now), &lt;code&gt;idle in transaction&lt;/code&gt; (sitting inside an open transaction doing nothing), or &lt;code&gt;idle&lt;/code&gt; (waiting for work)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;query&lt;/code&gt; — the actual SQL text&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;query_start&lt;/code&gt; — when the current query began&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want to include the user and database to narrow things down:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;usename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;datname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clock_timestamp&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;query_start&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;duration&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;pg_stat_activity&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;state&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s1"&gt;'idle'&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;duration&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The dangerous one — idle in transaction
&lt;/h2&gt;

&lt;p&gt;An &lt;code&gt;active&lt;/code&gt; query that's been running for a while is usually just slow. An &lt;code&gt;idle in transaction&lt;/code&gt; connection is a different kind of problem — it means someone (or some code) opened a transaction and never committed or rolled it back. The connection is doing nothing, but it's still holding locks, which can block other queries from running.&lt;/p&gt;

&lt;p&gt;These are the ones that tend to cause cascading slowdowns. If you see one that's been sitting there for longer than expected, it's almost certainly a bug in application code — a missing &lt;code&gt;COMMIT&lt;/code&gt;, an unhandled exception that skipped the cleanup, or a connection pool that didn't reclaim the session properly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Killing a process
&lt;/h2&gt;

&lt;p&gt;Once you've identified the offending &lt;code&gt;pid&lt;/code&gt;, you have two options.&lt;/p&gt;

&lt;p&gt;The gentle approach — ask the query to cancel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;pg_cancel_backend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;12345&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This sends a cancel signal to the running query. If the process is &lt;code&gt;active&lt;/code&gt;, the query stops and the connection goes back to idle. It's the equivalent of hitting Ctrl+C — the session stays alive, no harm done.&lt;/p&gt;

&lt;p&gt;The forceful approach — terminate the connection entirely:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;pg_terminate_backend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;12345&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This kills the entire backend process. The connection is dropped, any open transaction is rolled back, and the client gets disconnected. Use this when &lt;code&gt;pg_cancel_backend&lt;/code&gt; doesn't work — which tends to happen with &lt;code&gt;idle in transaction&lt;/code&gt; sessions, since there's no active query to cancel.&lt;/p&gt;

&lt;p&gt;Replace &lt;code&gt;12345&lt;/code&gt; with the actual &lt;code&gt;pid&lt;/code&gt; from your &lt;code&gt;pg_stat_activity&lt;/code&gt; query.&lt;/p&gt;

&lt;h2&gt;
  
  
  Killing in bulk
&lt;/h2&gt;

&lt;p&gt;If you've got several stuck connections and want to clear them all at once:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;pg_terminate_backend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;pg_stat_activity&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'idle in transaction'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;query_start&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;interval&lt;/span&gt; &lt;span class="s1"&gt;'5 minutes'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That terminates every connection that's been idle in a transaction for more than five minutes. Adjust the interval to taste.&lt;/p&gt;

&lt;h2&gt;
  
  
  On Supabase specifically
&lt;/h2&gt;

&lt;p&gt;If you're on Supabase, you can run all of this through the SQL Editor in the dashboard. The same &lt;code&gt;pg_stat_activity&lt;/code&gt; view is available, and &lt;code&gt;pg_cancel_backend&lt;/code&gt; / &lt;code&gt;pg_terminate_backend&lt;/code&gt; both work. No extra permissions needed — the default &lt;code&gt;postgres&lt;/code&gt; role has access.&lt;/p&gt;

&lt;p&gt;One thing to keep in mind: Supabase runs background processes for Realtime, Auth, and PostgREST. You'll see these in &lt;code&gt;pg_stat_activity&lt;/code&gt; too. Don't kill them — they'll usually show up with usernames like &lt;code&gt;supabase_admin&lt;/code&gt; or &lt;code&gt;authenticator&lt;/code&gt;. Stick to terminating connections from your own application's role.&lt;/p&gt;




&lt;p&gt;Working in Supabase a lot? We built &lt;a href="https://dsplce.co/our-work/supabase-plus" rel="noopener noreferrer"&gt;supabase-plus&lt;/a&gt; to smooth over exactly this kind of CLI friction — and wrote up &lt;a href="https://dsplce.co/blog/supabase-realtime-migration" rel="noopener noreferrer"&gt;how to toggle realtime on a table in Supabase migrations&lt;/a&gt;, another thing the dashboard makes harder than it should be.&lt;/p&gt;

</description>
      <category>database</category>
      <category>performance</category>
      <category>postgres</category>
      <category>sql</category>
    </item>
    <item>
      <title>Kubernetes kills your pod? Here's why</title>
      <dc:creator>dsplce.co</dc:creator>
      <pubDate>Thu, 11 Jun 2026 21:27:31 +0000</pubDate>
      <link>https://dev.to/dsplce-co/kubernetes-kills-your-pod-heres-why-25a7</link>
      <guid>https://dev.to/dsplce-co/kubernetes-kills-your-pod-heres-why-25a7</guid>
      <description>&lt;p&gt;Pods keep dying in k8s, not crashing but being killed. They work fine and then disappear, k8s creates new ones and the logs doesn't give any useful info. That's sth that k8s does on purpose, as you haven't ever told it how much memory your app needs. Until you do, it keeps killing them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Kubernetes evicts pods
&lt;/h2&gt;

&lt;p&gt;k8s runs on nodes — physical or virtual machines that host your containers — and each node has a finite amount of CPU and memory. When a node runs low on resources, k8s needs to decide which pods stay and which are being evicted to free up some space.&lt;/p&gt;

&lt;p&gt;That decision comes down to QoS classes — quality-of-service tiers k8s assigns every pod based on how you've set its resource requests and limits. There are three:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;BestEffort&lt;/strong&gt; — no resource requests or limits defined. k8s has no idea how much CPU or memory the pod needs. These get killed first.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Burstable&lt;/strong&gt; — requests and limits are defined, but they're different (e.g., &lt;code&gt;requests: 256Mi&lt;/code&gt;, &lt;code&gt;limits: 512Mi&lt;/code&gt;). The pod is guaranteed the request amount, but can burst up to the limit. Killed second.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Guaranteed&lt;/strong&gt; — requests and limits are set to the same value. k8s reserves exactly that amount of resources for the pod. Killed last.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you don't have any resource configuration for your pods, they run as BestEffort. And when a node hits memory pressure, BestEffort pods are the first ones to go, no questions asked.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Guaranteed class
&lt;/h2&gt;

&lt;p&gt;Setting up a pod to get into the Guaranteed class is as easy as adding the following to your deployment configuration — you define requests and limits for both CPU and memory and set them to be identical:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;512Mi"&lt;/span&gt;
    &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;500m"&lt;/span&gt;
  &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;512Mi"&lt;/span&gt;
    &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;500m"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;k8s knows that this pod needs exactly that much of RAM and half of a CPU. That's why it reserves that capacity when it schedules it on the node. If some node doesn't have 512 MiB free, the pod won't be placed there. And if the node will run into memory pressure later, this pod is being evicted last — after every BestEffort and Burstable pod.&lt;/p&gt;

&lt;h2&gt;
  
  
  The side effect — better autoscaling
&lt;/h2&gt;

&lt;p&gt;Another advantage of managed k8s platforms like &lt;a href="https://aws.amazon.com/eks/" rel="noopener noreferrer"&gt;EKS&lt;/a&gt; is that the Cluster Autoscaler looks at resource requests when deciding whether to add new nodes. If your pods are set to BestEffort with no resource configuration, the autoscaler thinks they need zero resources. So ten pods on one node is all good for it — even if that node is at 90 percent memory, it won't create another node, as it doesn't see any demand. But if those same pods have a Guaranteed setting with 512Mi requests, and the node has less than 512 MiB free, the autoscaler will see a pod it can't schedule, so it adds a new node, and your pods spread across multiple nodes instead of piling up on one.&lt;/p&gt;

&lt;p&gt;It's especially strict on EKS — other providers are more lenient, but EKS follows the scheduler's resource math to the letter. So if you have no requests, the autoscaler will never trigger, and your pods will cram onto a single node until it runs out of memory and starts evicting.&lt;/p&gt;

&lt;h2&gt;
  
  
  The trade-off
&lt;/h2&gt;

&lt;p&gt;The downside of using Guaranteed is that you need to commit to some memory limit, so if your app grows and uses more memory than you have set, then instead of being burstable it will get OOMKilled by k8s — so with Burstable, for example, you could do 256Mi requests and 1Gi limit to give your app some room to spike, but in return you lose the scheduling guarantee, as k8s reserves only 256 MiB, so the pod might end up on a node which doesn't have a full gigabyte free. But with Guaranteed you just need to keep an eye on how much memory your app uses and occasionally increase the limit — a little more maintenance, but in exchange you get predictable scheduling, protection against eviction, and autoscaling which actually works.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to set it
&lt;/h2&gt;

&lt;p&gt;In your Kubernetes deployment manifest, add the &lt;code&gt;resources&lt;/code&gt; block under &lt;code&gt;containers&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;your-app&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app&lt;/span&gt;
        &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;your-image:latest&lt;/span&gt;
        &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;512Mi"&lt;/span&gt;
            &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;500m"&lt;/span&gt;
          &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;512Mi"&lt;/span&gt;
            &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;500m"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Apply it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; deployment.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check the QoS class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get pod &amp;lt;pod-name&amp;gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{.status.qosClass}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it says &lt;code&gt;Guaranteed&lt;/code&gt;, you're set.&lt;/p&gt;

&lt;h2&gt;
  
  
  Picking the right values
&lt;/h2&gt;

&lt;p&gt;Start by looking at what your pods are actually using. Get current memory consumption:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl top pods
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Take the highest memory value you see, add 20–30% of headroom, and use this for both request and limit. If a pod is sitting at 400 MiB set it to 512 MiB; if it's consistently hitting 800 MiB then go with 1 GiB. For CPU, half a core (500m) is a good starting point for most apps — increase it if your metrics are showing throttling on the CPU level. Then monitor: OOMKills in the pod events mean the limit is too low, so you raise it. And if memory usage is growing over time as you ship new features, just update the config to match.&lt;/p&gt;

&lt;p&gt;Once your pods are Guaranteed, k8s stops killing them at random — but you still have to tell it what Guaranteed actually means. That's the entire job of requests and limits.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>infrastructure</category>
      <category>kubernetes</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
