<?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>Writing | deniz.dev</title>
<link>https://terminal.themes.kilim.dev/posts/</link>
<description>Writing and projects about the web, tools and slow software.</description>
<atom:link href="https://terminal.themes.kilim.dev/posts/feed.xml" rel="self" type="application/rss+xml"/>
<lastBuildDate>Wed, 21 Oct 2026 00:00:00 GMT</lastBuildDate>
<item>
<title>The case for zero JavaScript</title>
<link>https://terminal.themes.kilim.dev/posts/zero-javascript/</link>
<guid isPermaLink="true">https://terminal.themes.kilim.dev/posts/zero-javascript/</guid>
<pubDate>Wed, 21 Oct 2026 00:00:00 GMT</pubDate>
<description>Most pages are documents. Shipping them without a script is a feature, not a limitation.</description>
<content:encoded><![CDATA[<p>Most of the web is still documents: an article, a menu, a price list, a page of docs. They load a framework anyway, and the reader pays for it on a train with one bar of signal.</p>
<h2 id="what-you-give-up">What you give up</h2>
<p>Less than you think. A details element opens and closes, a form posts, a link prefetches the next page with Speculation Rules. The browser has learned a great deal since we last checked.</p>
<ul>
<li>Menus and accordions: <code>&#x3C;details></code> and <code>&#x3C;summary></code></li>
<li>Dark mode: <code>prefers-color-scheme</code></li>
<li>Instant navigation: prefetching on hover</li>
</ul>
<h2 id="what-you-get">What you get</h2>
<p>Pages that render on the first round trip, that a screen reader understands, and that still work in ten years. When a page truly needs a script, it should say so out loud.</p>
<blockquote>
<p>The fastest script is the one you never send.</p>
</blockquote>
<p>Start from zero and add what the page asks for. It is a better default than starting from everything and trying to take it away.</p>]]></content:encoded>
</item>
<item>
<title>Typing your content with schemas</title>
<link>https://terminal.themes.kilim.dev/posts/typing-content/</link>
<guid isPermaLink="true">https://terminal.themes.kilim.dev/posts/typing-content/</guid>
<pubDate>Thu, 08 Oct 2026 00:00:00 GMT</pubDate>
<description>A typo in a date should stop the build, with the file and the line, before it reaches readers.</description>
<content:encoded><![CDATA[<p>Frontmatter is a tiny database that nobody validates. A missing title, a date written as <code>10/08</code>, a tag list that turned into a string: each one quietly ships.</p>
<h2 id="a-schema-per-collection">A schema per collection</h2>
<p>Describe the fields once, next to the collection, and let the build read every file through it.</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> posts</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> z.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">object</span><span style="color:#24292E;--shiki-dark:#E1E4E8">({</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">  title: z.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">string</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(),</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">  date: z.coerce.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">date</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(),</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">  tags: z.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">array</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(z.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">string</span><span style="color:#24292E;--shiki-dark:#E1E4E8">()).</span><span style="color:#6F42C1;--shiki-dark:#B392F0">default</span><span style="color:#24292E;--shiki-dark:#E1E4E8">([]),</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">});</span></span></code></pre>
<p>The same schema gives the editor its form and the layouts their types. A field that does not exist is a type error in the template, not a blank space on the page.</p>
<h2 id="errors-that-point-somewhere">Errors that point somewhere</h2>
<p>An error message is a user interface. "Invalid input" helps nobody; <code>content/posts/hello.md:3: date: Invalid date</code> sends you straight to the line.</p>]]></content:encoded>
</item>
<item>
<title>Incremental builds, one read at a time</title>
<link>https://terminal.themes.kilim.dev/posts/incremental-builds/</link>
<guid isPermaLink="true">https://terminal.themes.kilim.dev/posts/incremental-builds/</guid>
<pubDate>Thu, 24 Sep 2026 00:00:00 GMT</pubDate>
<description>Which pages change when you fix a typo? Record what each page reads, and you know.</description>
<content:encoded><![CDATA[<p>A full build of a small site takes a second. Saving a file forty times an hour turns that second into a habit of waiting.</p>
<h2 id="track-reads-not-files">Track reads, not files</h2>
<p>Every layout reads something: a title, a date, the list of posts. If the build records those reads, it can answer a precise question after each save: which pages read the thing that changed?</p>
<ol>
<li>Render each page through a proxy that notes the keys it touches.</li>
<li>Fingerprint the values behind those keys.</li>
<li>On the next save, compare fingerprints and render only the pages whose reads changed.</li>
</ol>
<h2 id="the-boring-part">The boring part</h2>
<p>The proxy is twenty lines. The hard part is making sure the incremental output is byte for byte the full build's, which is why every test runs both and compares them.</p>]]></content:encoded>
</item>
<item>
<title>An admin panel for plain files</title>
<link>https://terminal.themes.kilim.dev/posts/admin-for-files/</link>
<guid isPermaLink="true">https://terminal.themes.kilim.dev/posts/admin-for-files/</guid>
<pubDate>Thu, 10 Sep 2026 00:00:00 GMT</pubDate>
<description>A local editor that writes the same Markdown you commit, and changes only the lines you touched.</description>
<content:encoded><![CDATA[<p>Content in Git is a gift to developers and a wall for everyone else. An admin panel is the usual answer, and it usually brings a database with it.</p>
<h2 id="edit-the-files-themselves">Edit the files themselves</h2>
<p>The admin runs only on your machine, next to the dev server. It reads the collection schemas, draws a form for each field, and saves back into the same Markdown file.</p>
<h2 id="keep-the-diff-small">Keep the diff small</h2>
<p>The test of a file editor is the diff it leaves behind. Change one title and the commit should show one line, with your comments, quotes and key order untouched.</p>
<ul>
<li>Unchanged blocks are kept byte for byte</li>
<li>New keys land where the schema puts them</li>
<li>A conflict shows up as a conflict, not as lost work</li>
</ul>
<p>When the panel is done, it is gone: nothing of it reaches the published site.</p>]]></content:encoded>
</item>
<item>
<title>What a kilim teaches about grids</title>
<link>https://terminal.themes.kilim.dev/posts/weaving-grids/</link>
<guid isPermaLink="true">https://terminal.themes.kilim.dev/posts/weaving-grids/</guid>
<pubDate>Thu, 27 Aug 2026 00:00:00 GMT</pubDate>
<description>A weaver plans every row before the first knot. A layout grid is the same promise.</description>
<content:encoded><![CDATA[<p>A kilim is woven row by row, and every motif is counted in threads before it appears. There is no undo: the grid is decided before the work begins.</p>
<h2 id="constraints-that-make-patterns">Constraints that make patterns</h2>
<p>The weave only allows certain angles, so diamonds and steps appear everywhere. The limitation is the style. CSS grid gives us the opposite problem: anything is possible, so nothing is decided.</p>
<h2 id="decide-the-rows-first">Decide the rows first</h2>
<ul>
<li>Pick a column count and a gap, and keep them</li>
<li>Let content span columns instead of inventing new widths</li>
<li>Repeat a motif rather than adding a new one</li>
</ul>
<p>A page that repeats three widths reads calmer than one that uses eleven. The weaver knew this centuries before we had a word for design systems.</p>
<blockquote>
<p>Pattern is a promise kept many times.</p>
</blockquote>]]></content:encoded>
</item>
<item>
<title>Serif type on screens</title>
<link>https://terminal.themes.kilim.dev/posts/serif-on-screens/</link>
<guid isPermaLink="true">https://terminal.themes.kilim.dev/posts/serif-on-screens/</guid>
<pubDate>Wed, 12 Aug 2026 00:00:00 GMT</pubDate>
<description>Screens got sharp enough for serifs years ago. Long reading deserves them back.</description>
<content:encoded><![CDATA[<p>For a decade, screens were too coarse for serifs and we learned to read in sans. Then pixels got small, and the reason went away while the habit stayed.</p>
<h2 id="use-the-fonts-already-there">Use the fonts already there</h2>
<p>Every system ships a good serif. A stack like this one costs nothing to download and looks deliberate everywhere:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">font-family: Charter, "Bitstream Charter", "Iowan Old Style",</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">  "Sitka Text", Cambria, Georgia, serif;</span></span></code></pre>
<h2 id="set-the-measure-then-the-size">Set the measure, then the size</h2>
<p>A line of 60 to 75 characters is the oldest rule in typography for a reason. Choose the column width first, then the size that fills it, then a line height around 1.6.</p>
<p>Serifs will not save a page with a 120 character line. A good measure will save almost any typeface.</p>]]></content:encoded>
</item>
<item>
<title>Designing for 375 pixels first</title>
<link>https://terminal.themes.kilim.dev/posts/designing-for-375/</link>
<guid isPermaLink="true">https://terminal.themes.kilim.dev/posts/designing-for-375/</guid>
<pubDate>Wed, 29 Jul 2026 00:00:00 GMT</pubDate>
<description>The narrowest common phone is the honest test of a layout. Everything wider is a bonus.</description>
<content:encoded><![CDATA[<p>Open your site on a phone, in the sun, with one thumb. That is the real design review.</p>
<h2 id="start-narrow">Start narrow</h2>
<p>At 375 pixels there is room for one idea per row. Anything that survives there will rearrange itself gracefully on a wide screen; the reverse is rarely true.</p>
<h2 id="the-usual-suspects">The usual suspects</h2>
<ul>
<li>A long URL or a code line that refuses to wrap</li>
<li>A table wider than the screen</li>
<li>A fixed width somebody typed in a hurry</li>
</ul>
<p>Each one creates the sideways scroll that makes a page feel broken. <code>overflow-wrap: anywhere</code> and a table that scrolls inside its own box fix most of them.</p>
<h2 id="check-it-every-time">Check it every time</h2>
<p>A layout that worked last month can break with one new post. Make the narrow check part of the build, not a thing you remember on launch day.</p>]]></content:encoded>
</item>
<item>
<title>Quiet tools</title>
<link>https://terminal.themes.kilim.dev/posts/quiet-tools/</link>
<guid isPermaLink="true">https://terminal.themes.kilim.dev/posts/quiet-tools/</guid>
<pubDate>Tue, 14 Jul 2026 00:00:00 GMT</pubDate>
<description>The best tools say nothing until something is wrong, and then they say exactly what.</description>
<content:encoded><![CDATA[<p>My favorite command prints nothing. It runs, it succeeds, and it gets out of the way. When it fails, it prints one line with a file name and a reason.</p>
<h2 id="noise-is-a-cost">Noise is a cost</h2>
<p>Every progress bar, emoji and banner asks for a glance. Multiply that by a hundred runs a day and the tool has become a colleague who never stops talking.</p>
<h2 id="a-checklist-for-quiet">A checklist for quiet</h2>
<ul>
<li>Silent on success, specific on failure</li>
<li>One line per problem, with where to look</li>
<li>Colors only when they carry meaning</li>
<li>A <code>--json</code> flag for the machines</li>
</ul>
<p>Quiet is not the same as hidden. A quiet tool can explain itself at length the moment you ask; it just does not assume you want to hear it every time.</p>]]></content:encoded>
</item>
<item>
<title>An autumn reading list</title>
<link>https://terminal.themes.kilim.dev/posts/autumn-reading/</link>
<guid isPermaLink="true">https://terminal.themes.kilim.dev/posts/autumn-reading/</guid>
<pubDate>Tue, 30 Jun 2026 00:00:00 GMT</pubDate>
<description>Five books about making things slowly and well, for the long evenings.</description>
<content:encoded><![CDATA[<p>The evenings get long and the screen gets less interesting. Here are five books that made me want to build things more carefully.</p>
<ol>
<li><strong>The Craftsman</strong> by Richard Sennett, on the skill that lives in the hands.</li>
<li><strong>Thinking with Type</strong> by Ellen Lupton, the friendliest typography book there is.</li>
<li><strong>A Pattern Language</strong> by Christopher Alexander, which every software person eventually finds.</li>
<li><strong>The Design of Everyday Things</strong> by Don Norman, for the doors you push that should be pulled.</li>
<li><strong>Shop Class as Soulcraft</strong> by Matthew Crawford, on the dignity of fixing things.</li>
</ol>
<h2 id="how-i-read-them">How I read them</h2>
<p>Slowly, with a pencil, one chapter an evening. None of them is about software, and all of them are.</p>]]></content:encoded>
</item>
<item>
<title>Notes from a slow morning</title>
<link>https://terminal.themes.kilim.dev/posts/slow-mornings/</link>
<guid isPermaLink="true">https://terminal.themes.kilim.dev/posts/slow-mornings/</guid>
<pubDate>Tue, 16 Jun 2026 00:00:00 GMT</pubDate>
<description>An hour before the first message, and what it does to the rest of the day.</description>
<content:encoded><![CDATA[<p>For a month I kept the first hour of the day free of messages. No inbox, no chat, no news. Just coffee, a notebook and whatever I was building.</p>
<h2 id="what-changed">What changed</h2>
<p>The first hour became the most productive of the day, which surprised nobody but me. More interesting was the second hour: the messages were still there, and none of them had become worse by waiting.</p>
<h2 id="what-did-not">What did not</h2>
<p>Some days the hour went to staring out of the window. That counts too. The point was never to squeeze more work into the morning, but to start the day with something I chose.</p>
<p>I still check my phone too often. But I check it second.</p>]]></content:encoded>
</item>
</channel>
</rss>
