<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>murkshelf</title><link>https://murkshelf.com/</link><description>Recent content on murkshelf</description><generator>Hugo</generator><language>en-us</language><lastBuildDate>Sun, 25 Oct 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://murkshelf.com/index.xml" rel="self" type="application/rss+xml"/><item><title>JSON Formatter</title><link>https://murkshelf.com/tools/json-formatter/</link><pubDate>Sun, 25 Oct 2026 00:00:00 +0000</pubDate><guid>https://murkshelf.com/tools/json-formatter/</guid><description>&lt;p&gt;Paste JSON, press a button. Everything happens locally in this page — no&#10;requests, no storage.&lt;/p&gt;&#10;&lt;textarea id="json-in" rows="12" placeholder='{"paste":"your JSON here"}'&gt;&lt;/textarea&gt;&#10;&lt;div class="btnrow"&gt;&#10; &lt;button id="btn-pretty" type="button"&gt;Pretty-print&lt;/button&gt;&#10; &lt;button id="btn-minify" type="button"&gt;Minify&lt;/button&gt;&#10; &lt;button id="btn-clear" type="button"&gt;Clear&lt;/button&gt;&#10; &lt;button id="btn-copy" type="button"&gt;Copy result&lt;/button&gt;&#10;&lt;/div&gt;&#10;&lt;pre id="json-out" hidden&gt;&lt;/pre&gt;&#10;&lt;p id="json-err" class="err" hidden&gt;&lt;/p&gt;&#10;&lt;script&gt;&#10;(function () {&#10; var input = document.getElementById('json-in');&#10; var out = document.getElementById('json-out');&#10; var err = document.getElementById('json-err');&#10; var result = null;&#10;&#10; function run(minify) {&#10; out.hidden = true;&#10; err.hidden = true;&#10; var text = input.value.trim();&#10; if (!text) { showErr('Nothing to parse — paste some JSON first.'); return; }&#10; try {&#10; result = minify ? JSON.stringify(JSON.parse(text))&#10; : JSON.stringify(JSON.parse(text), null, 2);&#10; out.textContent = result;&#10; out.hidden = false;&#10; } catch (e) {&#10; result = null;&#10; showErr(String(e.message || e));&#10; }&#10; }&#10;&#10; function showErr(msg) {&#10; err.textContent = '✗ ' + msg;&#10; err.hidden = false;&#10; }&#10;&#10; document.getElementById('btn-pretty').addEventListener('click', function () { run(false); });&#10; document.getElementById('btn-minify').addEventListener('click', function () { run(true); });&#10; document.getElementById('btn-clear').addEventListener('click', function () {&#10; input.value = '';&#10; out.hidden = true;&#10; err.hidden = true;&#10; input.focus();&#10; });&#10; document.getElementById('btn-copy').addEventListener('click', function () {&#10; if (result === null) { showErr('Nothing to copy yet.'); return; }&#10; navigator.clipboard.writeText(result);&#10; });&#10;})();&#10;&lt;/script&gt;</description></item><item><title>SQL: window functions in five examples</title><link>https://murkshelf.com/notes/sql-window-functions/</link><pubDate>Sun, 11 Oct 2026 00:00:00 +0000</pubDate><guid>https://murkshelf.com/notes/sql-window-functions/</guid><description>&lt;p&gt;Window functions feel like a dialect of SQL you learn in a parallel&#10;universe, but they come down to one idea: &lt;strong&gt;aggregate without collapsing&#10;rows&lt;/strong&gt;. A &lt;code&gt;GROUP BY&lt;/code&gt; squashes the table; a window function keeps every row&#10;and adds the aggregate beside it.&lt;/p&gt;&#10;&lt;p&gt;All examples below work in PostgreSQL; most are identical elsewhere.&lt;/p&gt;&#10;&lt;h2 id="1-a-total-next-to-every-row"&gt;1. A total next to every row&lt;/h2&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;SELECT&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; employee,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; department,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; salary,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;SUM&lt;/span&gt;(salary) OVER () &lt;span style="color:#66d9ef"&gt;AS&lt;/span&gt; payroll_total&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;FROM&lt;/span&gt; salaries;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Every row survives, and each carries the same grand total.&lt;/p&gt;</description></item><item><title>Docker: the cleanup commands I always forget</title><link>https://murkshelf.com/notes/docker-cleanup/</link><pubDate>Thu, 03 Sep 2026 00:00:00 +0000</pubDate><guid>https://murkshelf.com/notes/docker-cleanup/</guid><description>&lt;p&gt;Every few weeks Docker eats 30 GB and I can&amp;rsquo;t remember the exact flags.&#10;Writing them down once and for all.&lt;/p&gt;&#10;&lt;h2 id="look-before-you-delete"&gt;Look before you delete&lt;/h2&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;docker system df # how much Docker is actually using&#10;docker ps -a # containers, including stopped ones&#10;docker images -a # images, including intermediate layers&#10;docker volume ls # volumes&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;docker system df -v&lt;/code&gt; gives the per-item breakdown when you need to know&#10;exactly what is eating the space.&lt;/p&gt;</description></item><item><title>Git: rebase vs merge — a practical rule of thumb</title><link>https://murkshelf.com/notes/git-rebase-vs-merge/</link><pubDate>Fri, 14 Aug 2026 00:00:00 +0000</pubDate><guid>https://murkshelf.com/notes/git-rebase-vs-merge/</guid><description>&lt;p&gt;Every team argues about this. The rule that ends the argument:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&lt;strong&gt;Rebase commits that only you have seen. Merge commits that anyone&#10;else has already seen.&lt;/strong&gt;&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;Rebase rewrites history — it takes your commits and replays them on top of&#10;a new base, creating new commits with new hashes. If a hash you rewrote&#10;exists on someone else&amp;rsquo;s machine, you now have two divergent histories and&#10;a bad afternoon. If nobody ever pulled those hashes, rewriting them costs&#10;nothing.&lt;/p&gt;</description></item><item><title>tmux: the five commands that cover 95% of usage</title><link>https://murkshelf.com/notes/tmux-five-commands/</link><pubDate>Mon, 20 Jul 2026 00:00:00 +0000</pubDate><guid>https://murkshelf.com/notes/tmux-five-commands/</guid><description>&lt;p&gt;I resisted tmux for years because every cheat sheet starts with forty key&#10;bindings. In practice five cover almost everything:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;tmux new -s work # start a named session&#10;Ctrl-b d # detach (everything keeps running)&#10;tmux attach -t work # come back later&#10;Ctrl-b % # split left/right&#10;Ctrl-b &amp;#34; # split top/bottom&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Everything else is bonus. The point of those five: your session survives a&#10;dropped SSH connection, a closed terminal window, or a laptop sleep. Start&#10;the session on the machine where the work runs, detach, reattach from&#10;anywhere.&lt;/p&gt;</description></item></channel></rss>