<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>The Op-Ed — Avery Quill</title>
    <link>https://newspaper-template-zeta.vercel.app/#blog</link>
    <atom:link href="https://newspaper-template-zeta.vercel.app/rss.xml" rel="self" type="application/rss+xml" />
    <description>Case studies and technical deep-dives by Avery Quill, full-stack developer.</description>
    <language>en</language>
    <lastBuildDate>Fri, 12 Jun 2026 09:00:00 GMT</lastBuildDate>
    <item>
      <title>Why I Keep the Model on a Short Leash</title>
      <link>https://newspaper-template-zeta.vercel.app/blog/why-i-keep-the-model-on-a-short-leash</link>
      <guid isPermaLink="true">https://newspaper-template-zeta.vercel.app/blog/why-i-keep-the-model-on-a-short-leash</guid>
      <pubDate>Fri, 12 Jun 2026 09:00:00 GMT</pubDate>
      <category>AI</category>
      <description>A sample article. Notes on building AI features where the model supplies judgement and phrasing, and ordinary code holds every guarantee.</description>
      <content:encoded><![CDATA[<p>This is a sample article that ships with the template — replace it with your own writing. The text below is here so you can see how a finished post reads: the layout, the pull of the first paragraph, and how long a post feels at the default reading speed.</p><p>When I first wired a language model into Quillboard, I did what everyone does: I wrote a long, polite prompt and trusted it. It worked in the demo. It also, on the third day, answered a question about a document that had never been uploaded, with total confidence.</p><p>The fix was not a better prompt. It was a smaller job for the model. Retrieval happens first, in ordinary code, and returns a handful of passages with their sources. The model is handed only those passages and one instruction: answer from them or say there is nothing relevant. Then a second, equally ordinary step checks that every quoted phrase in the answer really appears in the passages it cites, and drops the ones that do not.</p><p>None of that is clever. That is the point. Code is good at counting, matching and refusing; models are good at reading, summarising and phrasing. Letting each do its own job meant the failures became boring and findable, which is the best thing a failure can be.</p><p>Two habits came out of this. First, write the guarantee down as a test before you write the prompt: &quot;an answer never cites a source that was not retrieved&quot; is a test you can run on every change. Second, keep a small file of real questions that once went wrong and replay it whenever anything upstream changes — the model, the chunking, the prompt.</p><p>If you take one thing from this sample: decide what must always be true, and then put it somewhere a model cannot argue with it.</p>]]></content:encoded>
    </item>
    <item>
      <title>Balances Are Derived, Not Stored</title>
      <link>https://newspaper-template-zeta.vercel.app/blog/balances-are-derived-not-stored</link>
      <guid isPermaLink="true">https://newspaper-template-zeta.vercel.app/blog/balances-are-derived-not-stored</guid>
      <pubDate>Fri, 03 Apr 2026 09:00:00 GMT</pubDate>
      <category>Full Stack</category>
      <description>A sample article. Why Ledgerly keeps an append-only ledger and computes every balance from it — and what that bought in correctness.</description>
      <content:encoded><![CDATA[<p>Another sample post to show the layout; swap it for something you have actually built.</p><p>The first version of Ledgerly stored a balance on each user. It was fast and simple, and it drifted: a retried request here, a half-finished settlement there, and soon the numbers no longer added up to zero, which in a shared-expense app is the one thing they must do.</p><p>The second version stores only facts. Every expense and every payment is a row in an append-only ledger, and a balance is simply the sum of the rows that mention you. There is nothing to drift, because there is nothing to keep in sync. If two sums disagree, one of them is wrong in a way you can find.</p><p>Idempotency came along for free once there was a ledger. Every write carries a client-generated key, and a unique index on that key means a double-tap, a flaky connection and an impatient retry all collapse into one row. The settlement screen can be as eager as it likes.</p><p>The cost is that reads do more work. For a household of six this is irrelevant; for a trip of sixty with a year of history it is not, so there is a materialised summary that is rebuilt from the ledger and can always be thrown away and rebuilt. The ledger is the truth; everything else is a cache.</p><p>The lesson is general. When you can choose between storing a conclusion and storing the evidence for it, store the evidence.</p>]]></content:encoded>
    </item>
    <item>
      <title>Accessibility Is a Feature, Not a Pass</title>
      <link>https://newspaper-template-zeta.vercel.app/blog/accessibility-is-a-feature-not-a-pass</link>
      <guid isPermaLink="true">https://newspaper-template-zeta.vercel.app/blog/accessibility-is-a-feature-not-a-pass</guid>
      <pubDate>Fri, 20 Feb 2026 09:00:00 GMT</pubDate>
      <category>Web</category>
      <description>A sample article. Treating keyboard, screen-reader and reduced-motion behaviour as part of each component rather than an audit at the end.</description>
      <content:encoded><![CDATA[<p>A third sample post, this time on design. Replace it, or delete it and the Blog section will simply show what is left.</p><p>Harbor Kit started as a collection of components I kept rewriting. The thing that made it a library rather than a folder was deciding that every component would come with a behaviour contract: what happens on Tab, on Escape, on arrow keys, and what a screen reader hears when it opens.</p><p>Writing the contract first changed the code. A dialog is not a div with a backdrop; it is a place focus moves to, stays inside, and returns from. Once that sentence is written down, it becomes a handful of tests, and the tests keep working long after you have forgotten why the code looks the way it does.</p><p>Reduced motion got the same treatment. Instead of sprinkling checks through the animations, the library exposes one setting that every animated component reads, so a visitor who asks for less movement gets less movement everywhere, including in places I would have forgotten.</p><p>The unexpected benefit was speed. Components with clear contracts are easier to compose, easier to review and easier to hand to someone else. Accessibility did not slow the work down; it was the specification the work had been missing.</p>]]></content:encoded>
    </item>
  </channel>
</rss>
