<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[Undolog]]></title><description><![CDATA[Research & Development]]></description><link>https://www.undolog.com</link><image><url>https://substackcdn.com/image/fetch/$s_!EAGo!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdd1b185a-904a-4946-868c-959ae0a2ae62_1024x1024.png</url><title>Undolog</title><link>https://www.undolog.com</link></image><generator>Substack</generator><lastBuildDate>Wed, 28 Oct 2026 21:19:59 GMT</lastBuildDate><atom:link href="https://www.undolog.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Giovambattista Fazioli]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[undolog@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[undolog@substack.com]]></itunes:email><itunes:name><![CDATA[Giovambattista Fazioli]]></itunes:name></itunes:owner><itunes:author><![CDATA[Giovambattista Fazioli]]></itunes:author><googleplay:owner><![CDATA[undolog@substack.com]]></googleplay:owner><googleplay:email><![CDATA[undolog@substack.com]]></googleplay:email><googleplay:author><![CDATA[Giovambattista Fazioli]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[Forty-eight releases, and what a tool may do without asking]]></title><description><![CDATA[Lancetta stopped only counting your quota and started saying what to do about it, FinderGit and Lancetta learned to fix what your coding agents read, Netfox to join the Wi-Fi it measures and WP Bones]]></description><link>https://www.undolog.com/p/forty-eight-releases-and-what-a-tool</link><guid isPermaLink="false">https://www.undolog.com/p/forty-eight-releases-and-what-a-tool</guid><dc:creator><![CDATA[Giovambattista Fazioli]]></dc:creator><pubDate>Fri, 16 Oct 2026 10:41:12 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!IVY-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd808920-664f-469b-93c2-70d5647e94be_2000x1282.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Two weeks, forty-eight releases across seven products &#8212; one of them brand new, glancecard, three releases within a day of its first commit &#8212; and on the Mantine side nine releases of their own, riding on three alignment sweeps across all 26 components. The thread this time is a change of verb. Lancetta used to tell you how much quota you had left; since 0.50 it tells you what to do about it. FinderGit&#8217;s new Agents page does not stop at a finding, it offers the fix. Netfox&#8217;s Wi-Fi tool went from reading networks to joining them, and WP Bones 3.0 now decides, for a plugin that never said, who may open its pages. A tool that acts on your behalf has to be stricter than one that only reports, and most of this fortnight went into where to draw that line: a fix is shown line by line and applied whole or not at all, anything that needs judgement goes to an agent that has to propose before it writes, a folder is checked once more in the instant before it is deleted. Twice the line was in the wrong place, and you will find both below: Lancetta&#8217;s Reclaim could stop a session that was still working, and for a day Netfox put a green lock on every open Wi-Fi network.</p><p>A word on the version numbers first, because all three Mac apps jumped to 0.50 within three days &#8212; Lancetta from 0.17, FinderGit from 0.47, Netfox from 0.29. There are no releases in between to go looking for; the number says how much changed. Part of what changed is shared: FinderGit&#8217;s Agents page and Lancetta&#8217;s Maintenance pane are the same dashboard, and the cards that now open in place in all three apps are the same deck, written once in a package the apps share, AgentsKit. So some fixes now arrive everywhere at once: on the 13th, cards that replayed their whole entrance on every window resize stopped doing it in FinderGit and Netfox. The same day, the Discord logo in all three About windows, drawn with its right side missing, was made whole, that one by hand in each app. The oldest shared bug had nothing to do with that package: on the 2nd, all three apps fixed a <em>Check for updates on launch</em> switch whose check had never actually run &#8212; it asked while the updater was still starting up, and was quietly turned away. If you were on an older version, you heard about that fix the old way.</p><h2>Lancetta</h2><p><strong>It tells you what to do, not just what is left (0.50.0).</strong> A quota monitor that shows 82% has done its part and left you the hard one: deciding whether to slow down, switch model or carry on. 0.50.0 puts suggestions at the top of the panel, worked out on your Mac from Lancetta&#8217;s own readings &#8212; the account&#8217;s reports, how fast you are going, when each window resets, any free reset you hold, the effort you have set. No model is asked, and every suggestion shows the readings it rests on. When you are heading for the wall it says when, and what to do about it: <em>At this pace Claude stops in 51m, then waits 1h18m for its reset</em> &#8212; lower the effort, give routine work to a lighter model, or move the next tasks to the agent that still has room. It works the other way too, because a low number on the menu bar can be capacity you are wasting rather than good news: <em>Codex has room: 88% of this 5-hour window is unused, and it resets in 39m.</em> An urgent suggestion arrives once as a notification, and when nothing needs changing, it says so, in green.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!7U0L!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F75de2c3b-33a0-438c-9eb1-5d31f607097b_716x386.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!7U0L!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F75de2c3b-33a0-438c-9eb1-5d31f607097b_716x386.png 424w, https://substackcdn.com/image/fetch/$s_!7U0L!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F75de2c3b-33a0-438c-9eb1-5d31f607097b_716x386.png 848w, https://substackcdn.com/image/fetch/$s_!7U0L!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F75de2c3b-33a0-438c-9eb1-5d31f607097b_716x386.png 1272w, https://substackcdn.com/image/fetch/$s_!7U0L!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F75de2c3b-33a0-438c-9eb1-5d31f607097b_716x386.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!7U0L!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F75de2c3b-33a0-438c-9eb1-5d31f607097b_716x386.png" width="716" height="386" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/75de2c3b-33a0-438c-9eb1-5d31f607097b_716x386.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:386,&quot;width&quot;:716,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;A Lancetta suggestion: At this pace Claude stops in 51m, then waits 1h18m for its reset. Four remedies underneath &#8212; lower the effort, which is set to xhigh; give routine work to a lighter model; there is no free reset to fall back on, so keep what is left for what matters most; move the next tasks to Codex, which has 88% of its 5-hour window and 80% of its week left &#8212; and at the foot the evidence: 5 hours, 82% used, 21% an hour over the last hour, from 14 readings&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="A Lancetta suggestion: At this pace Claude stops in 51m, then waits 1h18m for its reset. Four remedies underneath &#8212; lower the effort, which is set to xhigh; give routine work to a lighter model; there is no free reset to fall back on, so keep what is left for what matters most; move the next tasks to Codex, which has 88% of its 5-hour window and 80% of its week left &#8212; and at the foot the evidence: 5 hours, 82% used, 21% an hour over the last hour, from 14 readings" title="A Lancetta suggestion: At this pace Claude stops in 51m, then waits 1h18m for its reset. Four remedies underneath &#8212; lower the effort, which is set to xhigh; give routine work to a lighter model; there is no free reset to fall back on, so keep what is left for what matters most; move the next tasks to Codex, which has 88% of its 5-hour window and 80% of its week left &#8212; and at the foot the evidence: 5 hours, 82% used, 21% an hour over the last hour, from 14 readings" srcset="https://substackcdn.com/image/fetch/$s_!7U0L!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F75de2c3b-33a0-438c-9eb1-5d31f607097b_716x386.png 424w, https://substackcdn.com/image/fetch/$s_!7U0L!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F75de2c3b-33a0-438c-9eb1-5d31f607097b_716x386.png 848w, https://substackcdn.com/image/fetch/$s_!7U0L!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F75de2c3b-33a0-438c-9eb1-5d31f607097b_716x386.png 1272w, https://substackcdn.com/image/fetch/$s_!7U0L!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F75de2c3b-33a0-438c-9eb1-5d31f607097b_716x386.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">A Lancetta suggestion: At this pace Claude stops in 51m, then waits 1h18m for its reset. Four remedies underneath &#8212; lower the effort, which is set to xhigh; give routine work to a lighter model; there is no free reset to fall back on, so keep what is left for what matters most; move the next tasks to Codex, which has 88% of its 5-hour window and 80% of its week left &#8212; and at the foot the evidence: 5 hours, 82% used, 21% an hour over the last hour, from 14 readings</figcaption></figure></div><p><strong>What your agents read before you type (0.50.0 &#8594; 0.54.0).</strong> The other half of 0.50.0 is a Maintenance pane. Claude Code and Codex read a stack of files at the start of every session &#8212; <code>CLAUDE.md</code>, <code>AGENTS.md</code>, rules, skills, memory &#8212; and nothing tells you when that stack has grown past the size at which Claude Code warns. Maintenance measures each repository&#8217;s start against that size and runs twenty checks. Five kinds of fix are applied whole after a line-by-line preview; the rest open Claude Code in plan mode with the prompt already written. 0.51.0 hands Codex the files that belong to it and makes both agents propose before they change anything: Codex cannot be started in its Plan mode from outside its own window, so it is told to propose and wait, and its sandbox may write only in the repository and the file&#8217;s folder. 0.53.0 adds Fix All &#8212; one preview of every fix that needs no choosing &#8212; and a check for the programs your setup calls that cannot start: a hook, a status line or an MCP server pointing at a program that is missing or cannot run, a path with a space and no quotes, a variable nobody fills. Three kinds it fixes itself, each offered only when the program would then start: quotes round the path, execute permission for a script, and the variable written the way Claude Code fills it in. And 0.54.0 noticed that Maintenance had only ever asked Claude Code where you work, so a repository you only open with Codex was never checked; it now reads Codex&#8217;s last thirty days too. FinderGit has the same page under another name &#8212; more on that below.</p><p><strong>The rate, not the average (0.15.0, 0.16.0).</strong> The line under each bar used to project the window&#8217;s average to its reset &#8212; <em>at this pace 96% by reset</em> &#8212; and late in a week that average spreads one afternoon of work across idle nights, so it mostly repeated the bar. The question you actually have is <em>if I keep going like this, when do I have to stop?</em>, and since 0.15.0 the line answers it in amber, from the last hour or so of readings: <em>at this rate you run out in 2h00m</em>. It speaks only when you would run out before the reset and within a few hours, and only for the window that would stop you first. 0.16.0 was meant to go further, with a forecast each morning of how far into the day the 5-hour window would carry you. It did not ship: over two and a half weeks of readings, none of six kinds of morning forecast beat a single fixed number chosen afterwards, and neither of the two days Codex ran out was foreseen &#8212; the numbers are on the <a href="https://lancetta.app/docs/roadmap#advice-from-your-own-history">roadmap</a>. What shipped instead needs no forecast: working hours, off by default, so the run-out line goes quiet when you would only run out after your day ends and notifications outside your hours go silently to Notification Centre; and a Usage card with each day&#8217;s fullest 5-hour window, Codex and Claude side by side.</p><p><strong>Seven languages (0.14.0 &#8594; 0.17.0).</strong> Lancetta follows your Mac&#8217;s language now &#8212; English, Italian, French, German, Spanish, Portuguese and Dutch &#8212; with nothing to set up. Much of what it says is a sentence about time, <em>at this pace it is spent by tomorrow afternoon, and it does not reset until Thursday afternoon</em>, and those had been assembled a phrase at a time in English word order; they are translated as whole sentences now, with weekdays and dates in the sentence&#8217;s own language. 0.14.0 also shipped a bug no translation explains: a window card, usually Codex&#8217;s, could show its title and labels upside down while its numbers looked fine, until 0.14.1 the next day. 0.14.2 translated the Processes rows the first pass had missed, and 0.17.0 gave Dutch two different words for Alerts and Notifications, which had been sharing one.</p><p><strong>Reclaim, and a folder with a double dash in its name (0.14.1, 0.16.1).</strong> Reclaim stops agent processes that have been left orphaned, and it could stop one that was still working. Lancetta read a process tree&#8217;s folder from the process list, which joins arguments with spaces and quotes nothing, so a folder with <code>--</code> in its name came back cut short, as a path that does not exist, and the tree looked abandoned. 0.14.1 had added a second check at the moment you confirm &#8212; and that check read the folder the same way. Since 0.16.1 the arguments come from macOS one at a time, and a tree Lancetta cannot read is never treated as gone. Two smaller confessions from the same fortnight: the pin on the panel was supposed to let you move it off the menu bar, and the panel never moved (0.15.2); and signing Codex in to another account could carry the old account&#8217;s free resets over: shown for the new one (0.14.1), or announced as a fresh grant if Lancetta was closed at the time (0.16.1).</p><p><strong>And the rest.</strong> A click anywhere on an agent&#8217;s card opens the window (0.15.1). Notification settings had lived in three places &#8212; if you wanted to know why a notification had or had not arrived, you had three places to look &#8212; and now have one pane of their own (0.17.0). The panel&#8217;s footer gave way to a gear menu, with Refresh Now on a right-click of the menu-bar item (0.51.0), and About went to two columns, like FinderGit&#8217;s and Netfox&#8217;s (0.52.0, 0.52.1). Fifteen releases, 0.14.0 &#8594; 0.54.0; free, universal, signed and notarised, on macOS 15 or later.</p><p><a href="https://lancetta.app">https://lancetta.app</a></p><h2>FinderGit</h2><p><strong>What your coding agents read before you type (0.50.0, 0.51.0).</strong> Claude Code, Codex, Cursor, Copilot and Gemini CLI each read a stack of files at the start of every session, and every session pays for all of it again, before your first word &#8212; with nothing to tell you when the stack has grown too big, or when part of it is never read at all. The new Agents page scans those files across your repositories, the folders above them and your home folder, shows what each start costs, and runs checks that each say why they matter: twenty in 0.50.0, twenty-two by 0.51.0. When a finding has exactly one right answer, the fix is shown in full and applied whole or not at all; when it needs judgement, <em>Fix with Claude Code</em> opens it in plan mode, or <em>Fix with Codex</em>, with the prompt already written. 0.51.0 gathers every fix that needs no choosing into one <em>Fix All</em> preview &#8212; every file and every line &#8212; before anything is written. It is the same dashboard as Lancetta&#8217;s Maintenance pane, and Lancetta 0.53.0 shipped the same changes the same day.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!IVY-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd808920-664f-469b-93c2-70d5647e94be_2000x1282.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!IVY-!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd808920-664f-469b-93c2-70d5647e94be_2000x1282.png 424w, https://substackcdn.com/image/fetch/$s_!IVY-!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd808920-664f-469b-93c2-70d5647e94be_2000x1282.png 848w, https://substackcdn.com/image/fetch/$s_!IVY-!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd808920-664f-469b-93c2-70d5647e94be_2000x1282.png 1272w, https://substackcdn.com/image/fetch/$s_!IVY-!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd808920-664f-469b-93c2-70d5647e94be_2000x1282.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!IVY-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd808920-664f-469b-93c2-70d5647e94be_2000x1282.png" width="1456" height="933" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/cd808920-664f-469b-93c2-70d5647e94be_2000x1282.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:933,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;FinderGit's Agents page on a demo set of repositories: 4 warnings and 5 suggestions across 18 files in 4 places, with Fix All and Copy Findings at the top right; the heaviest start, design-system, at 6,983 of the 120,000 characters at which Claude Code warns; and eight cards &#8212; Instructions open on four files with their findings, such as an  that Claude Code skips, then Rules, Skills, Commands and Subagents, Settings and MCP, Memory, a launch weight at 6% of the limit, and 68.2 MB of ended background sessions&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="FinderGit's Agents page on a demo set of repositories: 4 warnings and 5 suggestions across 18 files in 4 places, with Fix All and Copy Findings at the top right; the heaviest start, design-system, at 6,983 of the 120,000 characters at which Claude Code warns; and eight cards &#8212; Instructions open on four files with their findings, such as an  that Claude Code skips, then Rules, Skills, Commands and Subagents, Settings and MCP, Memory, a launch weight at 6% of the limit, and 68.2 MB of ended background sessions" title="FinderGit's Agents page on a demo set of repositories: 4 warnings and 5 suggestions across 18 files in 4 places, with Fix All and Copy Findings at the top right; the heaviest start, design-system, at 6,983 of the 120,000 characters at which Claude Code warns; and eight cards &#8212; Instructions open on four files with their findings, such as an  that Claude Code skips, then Rules, Skills, Commands and Subagents, Settings and MCP, Memory, a launch weight at 6% of the limit, and 68.2 MB of ended background sessions" srcset="https://substackcdn.com/image/fetch/$s_!IVY-!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd808920-664f-469b-93c2-70d5647e94be_2000x1282.png 424w, https://substackcdn.com/image/fetch/$s_!IVY-!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd808920-664f-469b-93c2-70d5647e94be_2000x1282.png 848w, https://substackcdn.com/image/fetch/$s_!IVY-!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd808920-664f-469b-93c2-70d5647e94be_2000x1282.png 1272w, https://substackcdn.com/image/fetch/$s_!IVY-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd808920-664f-469b-93c2-70d5647e94be_2000x1282.png 1456w" sizes="auto, 100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">FinderGit&#8217;s Agents page on a demo set of repositories: 4 warnings and 5 suggestions across 18 files in 4 places, with Fix All and Copy Findings at the top right; the heaviest start, design-system, at 6,983 of the 120,000 characters at which Claude Code warns; and eight cards &#8212; Instructions open on four files with their findings, such as an <code>AGENTS.md</code> that Claude Code skips, then Rules, Skills, Commands and Subagents, Settings and MCP, Memory, a launch weight at 6% of the limit, and 68.2 MB of ended background sessions</figcaption></figure></div><p><strong>Your disk space back (0.44.0, 0.50.0).</strong> Disks fill up with folders you never wrote &#8212; node_modules, .next, DerivedData, the npm, Yarn, Homebrew and Go caches. On my own Mac the first scan found about 30 GB. The new Cleaning page counts them up in eight cards and pre-ticks only what is safe: not in use and, inside a project, untouched for a month; everything else carries a badge saying why it was left alone. Inside a repository a folder qualifies only if the repository&#8217;s own .gitignore ignores it and it sits beside its project file &#8212; nothing Git tracks, no symlinks, nothing inside .git &#8212; and every folder is checked again just before it is deleted. Deleted, not moved to the Trash: the space is the point. The size shown is what deleting that one folder frees, and 0.50.0 stopped quietly leaving out the two kinds of folder where that answer is not simple: node_modules that live in a shared clone store, now listed with a <em>Shared</em> badge and what deleting them would really free, and build folders committed to git, listed with an <em>In git</em> badge and never ticked or cleaned.</p><p><strong>The Overview tab, now a dashboard you can take with you (0.45.0, 0.46.0).</strong> The per-repository Overview was the quietest corner of FinderGit, and nearly empty for any repository that did not live on GitHub. It is a dashboard now: a pulse &#8212; Active, Steady, Quiet or Dormant, from the newest commit &#8212; when history began, the latest tag and the commits waiting on top of it, a headline from the GitHub description or the README&#8217;s first sentence, twelve tiles that each open their tab, a year of commits with its streaks, a weekday-by-hour chart, languages, project hygiene files, recent commits and releases, contributors. Almost all of it is read from the repository itself, so a local-only repository gets the whole thing. A dashboard is something you want to put in a pull request or a status update, and a window screenshot rarely does it justice, so 0.46.0 exports it &#8212; PNG, PDF or the clipboard &#8212; at full height and two columns wide whatever the size of your window, with the repository, branch and date across the top. And 0.45.1 fixed English footnotes that had been printing raw markup, <code>^[of 3 repository](inflect: true)</code>, instead of <em>of 3 repositories</em>.</p><p><strong>Your GitHub profile on one card (0.52.0, 0.52.1).</strong> Some things you keep for yourself; a GitHub profile is made to be shown. The Account page now renders as one picture: ten figures, a year of contributions with the longest streak, best day and busiest weekday, your most-starred repositories and your languages. It comes in nine looks &#8212; Midnight, Aurora, Daylight, Upside Down, Hyperspace, Bridge, Nebula and two seasonal ones: Halloween is in the menu until 2 November, and Christmas arrives on 1 December and leaves after Epiphany. Save it as a PNG at twice its size or a one-page PDF, copy it or share it. Repository figures come from public repositories only, so a private repository&#8217;s name never reaches the picture. 0.52.1 adds a switch that draws the year as a line &#8212; each day the average of the week up to it, so you read a trend instead of a comb of spikes &#8212; and fixes the Account page&#8217;s own contribution graph, whose curves overshot and drew a negative count that never happened.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ULGn!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9fe16004-ec04-49d3-a4c0-5619189a2ead_1760x1948.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ULGn!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9fe16004-ec04-49d3-a4c0-5619189a2ead_1760x1948.png 424w, https://substackcdn.com/image/fetch/$s_!ULGn!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9fe16004-ec04-49d3-a4c0-5619189a2ead_1760x1948.png 848w, https://substackcdn.com/image/fetch/$s_!ULGn!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9fe16004-ec04-49d3-a4c0-5619189a2ead_1760x1948.png 1272w, https://substackcdn.com/image/fetch/$s_!ULGn!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9fe16004-ec04-49d3-a4c0-5619189a2ead_1760x1948.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!ULGn!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9fe16004-ec04-49d3-a4c0-5619189a2ead_1760x1948.png" width="1456" height="1612" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9fe16004-ec04-49d3-a4c0-5619189a2ead_1760x1948.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1612,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;A FinderGit Profile Card for the demo account Ada Lovelace: ten tiles &#8212; 2.4k followers, 87 following, 4.2k stars, 42 public repos, 490 forks, 1.4k pull requests, 287 issues, 3.9k commits last year, 61 repositories contributed to, 14 years on GitHub &#8212; then a year of contributions as green squares with a 15-day longest streak, a best day of 40 on 30 April and Tuesday as the busiest day, and at the foot the six most-starred repositories and a languages bar led by Swift at 35%&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="A FinderGit Profile Card for the demo account Ada Lovelace: ten tiles &#8212; 2.4k followers, 87 following, 4.2k stars, 42 public repos, 490 forks, 1.4k pull requests, 287 issues, 3.9k commits last year, 61 repositories contributed to, 14 years on GitHub &#8212; then a year of contributions as green squares with a 15-day longest streak, a best day of 40 on 30 April and Tuesday as the busiest day, and at the foot the six most-starred repositories and a languages bar led by Swift at 35%" title="A FinderGit Profile Card for the demo account Ada Lovelace: ten tiles &#8212; 2.4k followers, 87 following, 4.2k stars, 42 public repos, 490 forks, 1.4k pull requests, 287 issues, 3.9k commits last year, 61 repositories contributed to, 14 years on GitHub &#8212; then a year of contributions as green squares with a 15-day longest streak, a best day of 40 on 30 April and Tuesday as the busiest day, and at the foot the six most-starred repositories and a languages bar led by Swift at 35%" srcset="https://substackcdn.com/image/fetch/$s_!ULGn!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9fe16004-ec04-49d3-a4c0-5619189a2ead_1760x1948.png 424w, https://substackcdn.com/image/fetch/$s_!ULGn!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9fe16004-ec04-49d3-a4c0-5619189a2ead_1760x1948.png 848w, https://substackcdn.com/image/fetch/$s_!ULGn!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9fe16004-ec04-49d3-a4c0-5619189a2ead_1760x1948.png 1272w, https://substackcdn.com/image/fetch/$s_!ULGn!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9fe16004-ec04-49d3-a4c0-5619189a2ead_1760x1948.png 1456w" sizes="auto, 100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">A FinderGit Profile Card for the demo account Ada Lovelace: ten tiles &#8212; 2.4k followers, 87 following, 4.2k stars, 42 public repos, 490 forks, 1.4k pull requests, 287 issues, 3.9k commits last year, 61 repositories contributed to, 14 years on GitHub &#8212; then a year of contributions as green squares with a 15-day longest streak, a best day of 40 on 30 April and Tuesday as the busiest day, and at the foot the six most-starred repositories and a languages bar led by Swift at 35%</figcaption></figure></div><p><strong>Zoom, pull to refresh, and a sidebar your way (0.41.0 &#8594; 0.47.0).</strong> Zoom now scales the whole content of the window, file browser to dashboards, from 75% to 300% like Safari, while the sidebar and toolbar stay put so the controls never move while you are reaching for them. On macOS 27 every list and the repository panel refresh with a pull, the way Mail does &#8212; and a pull never fetches, pulls or pushes; it reloads what you are looking at. Then 0.47.0, from a reader who wrote in with two small wishes: sidebar sections that collapse like Finder&#8217;s and rows you can hide, in a new Appearance pane. It also shows a detached HEAD plainly, and the buttons that must wait for a running pull or push now dim and say why in a tooltip &#8212; before, they looked ready and refused the click. Fourteen releases, 0.41.0 &#8594; 0.52.1, on macOS 15 or later; pull to refresh needs macOS 27.</p><p><a href="https://findergit.app">https://findergit.app</a></p><h2>WordPress: WP Bones and Scotty</h2><p><strong>When a plugin says nothing (WP Bones 3.0.0).</strong> A framework makes a choice every time a plugin leaves something out, and in 2.x those choices were the convenient ones. 3.0 makes them the safe ones. A page or menu that declares no capability is now for administrators, where any logged-in user could open it before; a REST route with no <code>permission_callback</code> refuses every request instead of being public; every non-GET request to an admin page needs a nonce. Migrations run once per site &#8212; on activation, or on the first request after the plugin&#8217;s version changes, however it got there &#8212; where 2.x ran every migration and every seeder again on each activation and dashboard update, with the old code still loaded; seed data is a migration now. Logs and compiled views move out of the plugin&#8217;s folder into <code>wp-content/uploads/wpbones/</code>. PHP stays 8.1+, and <code>php bones migrate:to-v3</code> converts what it can and lists what it leaves to you.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!mNh4!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70807058-17ad-4089-841a-07ec1a942f11_1400x1580.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!mNh4!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70807058-17ad-4089-841a-07ec1a942f11_1400x1580.png 424w, https://substackcdn.com/image/fetch/$s_!mNh4!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70807058-17ad-4089-841a-07ec1a942f11_1400x1580.png 848w, https://substackcdn.com/image/fetch/$s_!mNh4!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70807058-17ad-4089-841a-07ec1a942f11_1400x1580.png 1272w, https://substackcdn.com/image/fetch/$s_!mNh4!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70807058-17ad-4089-841a-07ec1a942f11_1400x1580.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!mNh4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70807058-17ad-4089-841a-07ec1a942f11_1400x1580.png" width="1400" height="1580" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/70807058-17ad-4089-841a-07ec1a942f11_1400x1580.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1580,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;The What changed table from the WP Bones 3.0 upgrade guide, 2.x against 3.0: migrations once per site instead of on every activation and dashboard update; seeders never, because seed data is a migration; manage_options instead of read for a page or menu without a capability; REST routes without a permission_callback and logged Ajax actions without a nonce, both refusing every request; a POST to an admin page needing csrfField() in the form; useHTTPPost() returning values without slashes; and logs and compiled Blade views moving from the plugin folder to uploads/wpbones&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="The What changed table from the WP Bones 3.0 upgrade guide, 2.x against 3.0: migrations once per site instead of on every activation and dashboard update; seeders never, because seed data is a migration; manage_options instead of read for a page or menu without a capability; REST routes without a permission_callback and logged Ajax actions without a nonce, both refusing every request; a POST to an admin page needing csrfField() in the form; useHTTPPost() returning values without slashes; and logs and compiled Blade views moving from the plugin folder to uploads/wpbones" title="The What changed table from the WP Bones 3.0 upgrade guide, 2.x against 3.0: migrations once per site instead of on every activation and dashboard update; seeders never, because seed data is a migration; manage_options instead of read for a page or menu without a capability; REST routes without a permission_callback and logged Ajax actions without a nonce, both refusing every request; a POST to an admin page needing csrfField() in the form; useHTTPPost() returning values without slashes; and logs and compiled Blade views moving from the plugin folder to uploads/wpbones" srcset="https://substackcdn.com/image/fetch/$s_!mNh4!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70807058-17ad-4089-841a-07ec1a942f11_1400x1580.png 424w, https://substackcdn.com/image/fetch/$s_!mNh4!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70807058-17ad-4089-841a-07ec1a942f11_1400x1580.png 848w, https://substackcdn.com/image/fetch/$s_!mNh4!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70807058-17ad-4089-841a-07ec1a942f11_1400x1580.png 1272w, https://substackcdn.com/image/fetch/$s_!mNh4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70807058-17ad-4089-841a-07ec1a942f11_1400x1580.png 1456w" sizes="auto, 100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">The What changed table from the WP Bones 3.0 upgrade guide, 2.x against 3.0: migrations once per site instead of on every activation and dashboard update; seeders never, because seed data is a migration; manage_options instead of read for a page or menu without a capability; REST routes without a permission_callback and logged Ajax actions without a nonce, both refusing every request; a POST to an admin page needing csrfField() in the form; useHTTPPost() returning values without slashes; and logs and compiled Blade views moving from the plugin folder to uploads/wpbones</figcaption></figure></div><p><strong>The same day, for everyone staying on 2.x (2.1.1 &#8594; 2.1.3).</strong> Two of those defaults were security bugs, and they were fixed in 2.x first, in a form that breaks nothing. Pages from <code>config/routes.php</code> and <code>pages/</code> have no menu entry, and any logged-in user &#8212; subscribers included &#8212; could open them, because the documented <code>capability</code> key was never read; it is read now, with <code>read</code> as the default (2.1.2). And the default logger was also writing a log file into a world-writable folder inside the plugin, which the web server serves, while compiled Blade views could be downloaded as source (2.1.3). If you have run an older 2.x, those log files are still there, and they have to be deleted by hand. 2.1.1, earlier that morning, fixed activation running before the plugin&#8217;s header had been read, which saved the plugin&#8217;s options under a row with no name. Plugins on <code>^2.x</code> stay on 2.1.3 until they change their constraint.</p><p><strong>Your stubs, and your tests (3.1.0, 3.2.0).</strong> The <code>make:*</code> generators read their templates from <code>vendor/</code>, so every <code>composer update</code> threw away whatever you had changed in them; they now look in the plugin&#8217;s own <code>stubs/</code> first, and <code>php bones stub:publish</code> puts them there &#8212; with the catch that a published stub no longer follows the framework&#8217;s. And <code>php bones deploy</code> now leaves test suites out of the package: with <code>--wp</code>, Jest tests had been travelling to <a href="https://wordpress.org/">WordPress.org</a>, and Scotty&#8217;s 2.2.0 package carried four such folders, removed by hand. If the package&#8217;s autoloader still requires a file the deploy left out, the deploy stops and names it, rather than shipping a plugin that fails on every request.</p><p><strong>Nine add-on packages, read again for 3.0.</strong> On the 7th, Geolocalizer 2.0.0 moved its countries table from a seeder to migrations, because 3.0 runs no seeders; wptables 1.1.0 started requiring a nonce on bulk actions &#8212; its form is a GET, which 3.0&#8217;s check does not cover, so a link could trigger one; and two packages fixed fatal errors, one of them in the very calls its README documented. The 14 boilerplates moved with every framework release.</p><p><strong>Scotty 2.2.1.</strong> Scotty is the maintenance plugin built on WP Bones. It now translates whole sentences, plurals included &#8212; 257 of 257 strings in Italian, Spanish, French, German and Brazilian Portuguese &#8212; and its duplicate lists are several times faster: 6.2 seconds down to 1.1 for post meta on 300,000 rows. The Database tools now reach only this site&#8217;s own tables, which matters on a shared database or a multisite network, and Optimize and Reset Auto Increment report an error instead of announcing success. Bannerize had nothing new this fortnight.</p><p><a href="https://wpbones.com">wpbones.com</a> &#183; <a href="https://wordpress.org/plugins/scotty/">Scotty on WordPress.org</a></p><h2>Netfox</h2><p><strong>From reading Wi-Fi to joining it (0.29.0).</strong> Netfox could tell you your signal was weak or your channel crowded, and then leave you to fix it somewhere else. Now Join&#8230; opens a sheet that names the network you are about to leave and warns you when the new one is open; the password goes to macOS for that one join, and Netfox keeps nothing. When the verdict says your link has a problem, a network you already know that is at least ten decibels louder, and good in its own right, is offered with Switch&#8230; &#8212; and nothing is suggested while your Wi-Fi looks fine. The list works like Devices now: filter by band, known networks or networks with no password, and search by name, address, maker or security, so even a hidden network can be found by the maker of its access point. One honest limit: it asks for the password every time, even for a network your Mac knows, because it does not read the one macOS saved.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!htJp!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F83cd1781-1193-4cf0-9860-8ff6d728e1e9_2000x1310.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!htJp!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F83cd1781-1193-4cf0-9860-8ff6d728e1e9_2000x1310.png 424w, https://substackcdn.com/image/fetch/$s_!htJp!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F83cd1781-1193-4cf0-9860-8ff6d728e1e9_2000x1310.png 848w, https://substackcdn.com/image/fetch/$s_!htJp!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F83cd1781-1193-4cf0-9860-8ff6d728e1e9_2000x1310.png 1272w, https://substackcdn.com/image/fetch/$s_!htJp!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F83cd1781-1193-4cf0-9860-8ff6d728e1e9_2000x1310.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!htJp!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F83cd1781-1193-4cf0-9860-8ff6d728e1e9_2000x1310.png" width="1456" height="954" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/83cd1781-1193-4cf0-9860-8ff6d728e1e9_2000x1310.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:954,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Netfox's Wi-Fi tool with the Join sheet open for Network #7: Your Mac will leave Network #9 to join it; a note that this network is in the Mac's known networks, but joining it from Netfox still takes its password; an empty password field with Join greyed out, and the line Netfox hands the password to macOS for this join and does not keep it. Behind it, the list of nearby networks with their Known chips, every one reading WPA2&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Netfox's Wi-Fi tool with the Join sheet open for Network #7: Your Mac will leave Network #9 to join it; a note that this network is in the Mac's known networks, but joining it from Netfox still takes its password; an empty password field with Join greyed out, and the line Netfox hands the password to macOS for this join and does not keep it. Behind it, the list of nearby networks with their Known chips, every one reading WPA2" title="Netfox's Wi-Fi tool with the Join sheet open for Network #7: Your Mac will leave Network #9 to join it; a note that this network is in the Mac's known networks, but joining it from Netfox still takes its password; an empty password field with Join greyed out, and the line Netfox hands the password to macOS for this join and does not keep it. Behind it, the list of nearby networks with their Known chips, every one reading WPA2" srcset="https://substackcdn.com/image/fetch/$s_!htJp!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F83cd1781-1193-4cf0-9860-8ff6d728e1e9_2000x1310.png 424w, https://substackcdn.com/image/fetch/$s_!htJp!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F83cd1781-1193-4cf0-9860-8ff6d728e1e9_2000x1310.png 848w, https://substackcdn.com/image/fetch/$s_!htJp!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F83cd1781-1193-4cf0-9860-8ff6d728e1e9_2000x1310.png 1272w, https://substackcdn.com/image/fetch/$s_!htJp!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F83cd1781-1193-4cf0-9860-8ff6d728e1e9_2000x1310.png 1456w" sizes="auto, 100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Netfox&#8217;s Wi-Fi tool with the Join sheet open for Network #7: Your Mac will leave Network #9 to join it; a note that this network is in the Mac&#8217;s known networks, but joining it from Netfox still takes its password; an empty password field with Join greyed out, and the line Netfox hands the password to macOS for this join and does not keep it. Behind it, the list of nearby networks with their Known chips, every one reading WPA2</figcaption></figure></div><p><strong>The green lock that said too much (0.29.1).</strong> 0.29.0 also learned about Enhanced Open &#8212; networks you join without a password that still encrypt what you send, which deserve a green lock while a plain open network gets a red one. But the check behind it said yes to every network without a password, so for a day every open network wore the green lock, and Join&#8230; told you your traffic would stay encrypted. On an open network, it does not. 0.29.1 calls a network Enhanced Open only when it really encrypts, and Join&#8230; warns that what you send over an open one can be read by anyone nearby. The same misreading made a plain WPA2 network list modes it does not offer &#8212; <em>WPA Mixed, WPA2 Personal, WPA3 Transition</em> &#8212; and it now reads <em>WPA2 Personal</em>. If you joined an open network thinking it was encrypted, look at System Settings &#8594; Wi-Fi &#8594; Known Networks: your Mac can rejoin a known network on its own, open ones included.</p><p><strong>The numbers open (0.50.0).</strong> Netfox would tell you three devices were at risk and make you leave the page to find out which. On the Overview, the Risk, Devices and Alerts cards now open into a panel right underneath; every row opens its device, and a risky one lands on its Ports tab. The page opens on the most urgent card. On Security, the high, medium, low and untested counts are cards too, and low-risk devices finally have a list of their own. And closing the window no longer means looking away: it used to stop discovery, alerts, scheduled port scans and the metrics endpoint without a word, while the menu bar looked alive. Now they run until you quit. Smaller, in the same release: a USB Ethernet adapter on the driver built into macOS no longer gets a false <em>below top speed</em> warning, and This Mac no longer shows 127.0.0.1 as its address.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!O2KH!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9bfa92b-2463-416c-bdc4-34d1af59a3b6_2000x1304.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!O2KH!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9bfa92b-2463-416c-bdc4-34d1af59a3b6_2000x1304.png 424w, https://substackcdn.com/image/fetch/$s_!O2KH!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9bfa92b-2463-416c-bdc4-34d1af59a3b6_2000x1304.png 848w, https://substackcdn.com/image/fetch/$s_!O2KH!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9bfa92b-2463-416c-bdc4-34d1af59a3b6_2000x1304.png 1272w, https://substackcdn.com/image/fetch/$s_!O2KH!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9bfa92b-2463-416c-bdc4-34d1af59a3b6_2000x1304.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!O2KH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9bfa92b-2463-416c-bdc4-34d1af59a3b6_2000x1304.png" width="1456" height="949" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d9bfa92b-2463-416c-bdc4-34d1af59a3b6_2000x1304.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:949,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;The Netfox Overview with the Risk posture card open: 3 devices at risk, 3 medium and 25 low, with Run Smart Scan, and under it Laptop #1, TV #2 and Printer #1, each serving HTTP without HTTPS on port 8080 or 80. Beside it, Devices at 10 of 27 on the network, the public IP on a direct connection, and an alerts inbox of 4 led by a new device; below, the network details and a Link card with its Wired and Wireless sections&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="The Netfox Overview with the Risk posture card open: 3 devices at risk, 3 medium and 25 low, with Run Smart Scan, and under it Laptop #1, TV #2 and Printer #1, each serving HTTP without HTTPS on port 8080 or 80. Beside it, Devices at 10 of 27 on the network, the public IP on a direct connection, and an alerts inbox of 4 led by a new device; below, the network details and a Link card with its Wired and Wireless sections" title="The Netfox Overview with the Risk posture card open: 3 devices at risk, 3 medium and 25 low, with Run Smart Scan, and under it Laptop #1, TV #2 and Printer #1, each serving HTTP without HTTPS on port 8080 or 80. Beside it, Devices at 10 of 27 on the network, the public IP on a direct connection, and an alerts inbox of 4 led by a new device; below, the network details and a Link card with its Wired and Wireless sections" srcset="https://substackcdn.com/image/fetch/$s_!O2KH!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9bfa92b-2463-416c-bdc4-34d1af59a3b6_2000x1304.png 424w, https://substackcdn.com/image/fetch/$s_!O2KH!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9bfa92b-2463-416c-bdc4-34d1af59a3b6_2000x1304.png 848w, https://substackcdn.com/image/fetch/$s_!O2KH!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9bfa92b-2463-416c-bdc4-34d1af59a3b6_2000x1304.png 1272w, https://substackcdn.com/image/fetch/$s_!O2KH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9bfa92b-2463-416c-bdc4-34d1af59a3b6_2000x1304.png 1456w" sizes="auto, 100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">The Netfox Overview with the Risk posture card open: 3 devices at risk, 3 medium and 25 low, with Run Smart Scan, and under it Laptop #1, TV #2 and Printer #1, each serving HTTP without HTTPS on port 8080 or 80. Beside it, Devices at 10 of 27 on the network, the public IP on a direct connection, and an alerts inbox of 4 led by a new device; below, the network details and a Link card with its Wired and Wireless sections</figcaption></figure></div><p><strong>Fold what you do not read, and stay lean (0.51.0).</strong> Sections in the narrow panes fold with a click &#8212; a device&#8217;s details, the Link card, a Wi-Fi network&#8217;s details &#8212; and Netfox remembers the fold on every device and across restarts. A folded Wired or Wireless section shows an orange mark when it is hiding a warning, so folding never means not knowing. Each alert in the notch now says what happened: a new device, one that came back, a port that opened. And a cost 0.50.0 had not paid: an app that now runs for days with its window closed had in-memory lists that grew for as long as it ran. Alerts older than 90 days leave them now, and the signal history of a Wi-Fi network unseen for ten minutes is let go. Five releases, 0.28.1 &#8594; 0.51.0, on macOS 15.6 or later.</p><p><a href="https://netfox.app">https://netfox.app</a></p><h2>octoscope</h2><p><strong>Your last dashboard, at once (0.39.0).</strong> Every launch used to wait on GitHub &#8212; about ten seconds on my account &#8212; before showing anything. Now octoscope opens on the last dashboard it drew, with the footer saying so, <code>cached 3h ago &#183; refreshing&#8230;</code>, and swaps in the live numbers quietly when they arrive: no flash, no star notification. Offline, the dashboard stays up as <code>offline &#183; retrying &#183; cached 3h ago</code>. That fixed a bug as old as the project: since 0.1.0, any failed refresh replaced the whole dashboard with an error screen, so the footer&#8217;s offline message had never once been visible. The cache is a file only you can read, under a week old, written by the same version for the same token and account; it is never used with <code>--public-only</code>, <code>--json</code>, <code>--plain</code> or <code>--scan</code>, and <code>dashboard_cache = false</code> turns it off and deletes it. The first launch after upgrading still waits.</p><p><strong>Is anything of mine compromised? (0.38.0).</strong> After every supply-chain worm the question is the same, and the scan answered it one repository at a time. <code>octoscope --scan</code> sweeps the default branch of every repository you own, plus your <code>watch_repos</code> &#8212; about 17 seconds for 95 repositories &#8212; and a repository it cannot read is reported as <em>not scanned</em>, with the reason, never counted as clean. Its <code>--json</code> output is a versioned contract. The scan was also wrong in a way worth owning: it called unsigned commits by <code>github-actions[bot]</code> forged, and flagged my own Homebrew tap and website. It now reads the recent changes to the files that can actually run code &#8212; hooks, tasks, package manifests, workflows. The repository view gained fourteen days of traffic and the most severe Dependabot alerts, with whether a fix exists, and a pull request that belongs to a stack now shows its layers.</p><p><strong>What is waiting for you (0.37.0).</strong> The Inbox tab lived only in the dashboard, so a script had to call GitHub&#8217;s notifications API itself. <code>--inbox</code> adds up to 50 unread threads to <code>--json</code> and <code>--plain</code>, newest first, each with GitHub&#8217;s reason, the repository, the title and a link. As with <code>--activity</code>, a missing <code>inbox</code> key means you did not ask, <code>[]</code> means inbox zero, and a failed fetch fails the run. <code>octoscope torvalds --json --inbox</code> is refused, since it would print your notifications under someone else&#8217;s name, and a fine-grained token &#8212; which GitHub refuses for notifications &#8212; now gets an error naming the token you need instead of a bare 403. Before all that, 0.36.1 made the What&#8217;s new tab fit a 24-column terminal. Four releases, 0.36.1 &#8594; 0.39.0, also as the gh extension.</p><p><a href="https://gfazioli.github.io/octoscope">https://gfazioli.github.io/octoscope</a> &#183; <code>brew upgrade gfazioli/tap/octoscope</code></p><h2>glancecard</h2><p><strong>Your GitHub, as a card in your README (0.1.0, 0.2.0).</strong> glancecard is new, and this is its first appearance here. It draws daily SVG cards of your GitHub profile and of your repositories, for the READMEs you own: a repository card with its status, age and latest tag, thirty days of commits, twelve stat tiles, a year of activity with its streaks, the weekly rhythm, languages, the latest commits and releases and the contributors; a profile card with followers, stars, pull requests, issues, commits, a year of contributions and your most-starred repositories. The cards are drawn by a GitHub Action in your own repository, with the token every run already has, and committed as plain files &#8212; no server, no third-party service, no dependencies &#8212; and setting it up is one paste into Claude Code or Codex. It is careful by default: a private repository is refused unless you allow it, a history longer than it was told to read is stated on the card instead of being silently undercounted, and on a pull request the cards are rendered but never committed. 0.1.0 and 0.2.0 went out on the 14th, the second with nine looks taken from FinderGit&#8217;s Profile Card, the seasonal ones included. By the next day it was drawing the card under the title of sixty-one of my public repositories, and of every active public WP Bones one.</p><p><strong>A shade is a range (0.3.0).</strong> GitHub&#8217;s green squares tell you which days you worked, not how much: a shade is a range. Since 0.3.0 the year is a line of daily counts over a value axis, on both cards, and <code>calendar: grid</code> brings the squares back. The profile card now asks GitHub for each day&#8217;s count, so the days in the chart add up to the total printed above it. There is no moving <code>v0</code> tag, on purpose: you upgrade by pinning <code>gfazioli/glancecard@v0.3.0</code>, and Dependabot can open that pull request for you.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!1Y_-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb18c5775-e718-444b-b97b-8ce2e6be7723_1200x630.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!1Y_-!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb18c5775-e718-444b-b97b-8ce2e6be7723_1200x630.png 424w, https://substackcdn.com/image/fetch/$s_!1Y_-!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb18c5775-e718-444b-b97b-8ce2e6be7723_1200x630.png 848w, https://substackcdn.com/image/fetch/$s_!1Y_-!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb18c5775-e718-444b-b97b-8ce2e6be7723_1200x630.png 1272w, https://substackcdn.com/image/fetch/$s_!1Y_-!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb18c5775-e718-444b-b97b-8ce2e6be7723_1200x630.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!1Y_-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb18c5775-e718-444b-b97b-8ce2e6be7723_1200x630.png" width="1200" height="630" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b18c5775-e718-444b-b97b-8ce2e6be7723_1200x630.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:630,&quot;width&quot;:1200,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;The glancecard banner: Your GitHub, at a glance &#8212; daily SVG cards of your profile and your repositories, for every README you own &#8212; with chips for GitHub Action, nine looks, no server, no dependencies and MIT. Beside it, my own profile card, with 137 followers, 789 stars and 69 public repositories and 16,726 contributions in the last year drawn as a line chart, and glancecard's own repository card at v0.3.0 behind it&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="The glancecard banner: Your GitHub, at a glance &#8212; daily SVG cards of your profile and your repositories, for every README you own &#8212; with chips for GitHub Action, nine looks, no server, no dependencies and MIT. Beside it, my own profile card, with 137 followers, 789 stars and 69 public repositories and 16,726 contributions in the last year drawn as a line chart, and glancecard's own repository card at v0.3.0 behind it" title="The glancecard banner: Your GitHub, at a glance &#8212; daily SVG cards of your profile and your repositories, for every README you own &#8212; with chips for GitHub Action, nine looks, no server, no dependencies and MIT. Beside it, my own profile card, with 137 followers, 789 stars and 69 public repositories and 16,726 contributions in the last year drawn as a line chart, and glancecard's own repository card at v0.3.0 behind it" srcset="https://substackcdn.com/image/fetch/$s_!1Y_-!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb18c5775-e718-444b-b97b-8ce2e6be7723_1200x630.png 424w, https://substackcdn.com/image/fetch/$s_!1Y_-!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb18c5775-e718-444b-b97b-8ce2e6be7723_1200x630.png 848w, https://substackcdn.com/image/fetch/$s_!1Y_-!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb18c5775-e718-444b-b97b-8ce2e6be7723_1200x630.png 1272w, https://substackcdn.com/image/fetch/$s_!1Y_-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb18c5775-e718-444b-b97b-8ce2e6be7723_1200x630.png 1456w" sizes="auto, 100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">The glancecard banner: Your GitHub, at a glance &#8212; daily SVG cards of your profile and your repositories, for every README you own &#8212; with chips for GitHub Action, nine looks, no server, no dependencies and MIT. Beside it, my own profile card, with 137 followers, 789 stars and 69 public repositories and 16,726 contributions in the last year drawn as a line chart, and glancecard&#8217;s own repository card at v0.3.0 behind it</figcaption></figure></div><p><a href="https://gfazioli.github.io/glancecard/">gfazioli.github.io/glancecard</a> &#183; <a href="https://github.com/marketplace/actions/glancecard">GitHub Marketplace</a></p><h2>Mantine extensions</h2><p>Mantine 9.7 shipped two components of its own that overlap two of these &#8212; a Tour and a JSON viewer &#8212; and the answer was to read core point by point and close what could be closed. Five components with a core counterpart now carry a comparison table in their docs, and three of them &#8212; JSON Tree, Marquee and Window &#8212; have no row left that only core can do.</p><p><strong>JSON Tree learned to edit (3.5.0 &#8594; 3.7.0).</strong> Diff highlighting, long arrays in collapsible groups, long strings behind <em>show more</em>, type badges and sorted keys (3.5.0); then renaming, adding, removing and reordering, from the mouse or the keyboard &#8212; key renaming had been called deliberately out of scope in the 3.4.0 notes (3.6.0); then every built-in string translatable, and an accessible name on every icon button, where before none had one (3.7.0). On the way, an empty object stopped rendering as <code>[object Object]</code> and an empty array stopped rendering as nothing at all.</p><p><strong>Window, on a phone (3.3.0, 3.4.0).</strong> A headless <code>useDragResize</code> hook &#8212; Window itself is now built on it &#8212; an axis lock and keyboard resizing came first. Then a report on the Mantine Extensions Discord from a Samsung S24 Ultra in Firefox and an iPhone 16 Pro Max in Safari: windows left their container when the phone rotated, a window followed the first finger on the screen rather than its own, and the page grew sideways. Windows now return inside their bounds after a rotation or a resize, and a swipe scrolls the content instead of dragging the window. Measured: windows left outside after a rotation went from 30 to 0, and on a 390-pixel screen the page went from 811 pixels wide to 445.</p><p><strong>Marquee 5.0.0, a major.</strong> Built to drop in where core&#8217;s Marquee goes: <code>gap</code> now uses the theme&#8217;s spacing scale (a marquee that never set it stays at 16 pixels), <code>pauseOnHover</code> also pauses on keyboard focus, and <code>fadeEdgeColor</code> works &#8212; it had been accepted and ignored. The tests went from 28 to 68.</p><p><strong>Onboarding Tour 4.1.0.</strong> Arrow keys, Escape to skip, focus that moves into each step and back at the end, and a step whose target is missing is centred &#8212; before, the overlay appeared and the popover did not. It also stopped remounting your components on every step, which had been throwing away form state and focus.</p><p><strong>And the unglamorous half.</strong> Text Animate 4.0.13: <code>component="h1"</code> with <code>by="character"</code> rendered one h1 per character, so the extensions site&#8217;s <em>All Extensions</em> title was fourteen headings &#8212; a crawler check found it. And four dependency-only patches in the 9.7.0 sweep went out carrying an older release&#8217;s notes, picked up from a leftover changelog file: JSON Tree 3.4.4&#8217;s notes describe a feature from August, and the release itself is only the version bump. The sweeps &#8212; Mantine 9.7.0 on the 5th, 9.7.1 on the 9th, 9.7.2 on the 13th &#8212; were one patch per component, 78 in all, nothing for you to do; the extensions site and the two documentation templates moved with them, and the site&#8217;s component pages now open with their video, where there is one, and a link to the component&#8217;s own docs, ahead of the live demos.</p><p><a href="https://mantine-extensions.vercel.app">mantine-extensions.vercel.app</a></p><h2>And the rest</h2><p>There is an Undolog Discord now, opened on 8 October as the home of FinderGit, Netfox, Lancetta and octoscope, and linked from every About window: <a href="https://discord.gg/rdWu5yFCR6">discord.gg/rdWu5yFCR6</a>. <a href="https://gfazioli.github.io/">gfazioli.github.io</a> carries it in the header, the hero and the footer, gained a legal notice and a privacy page, and stopped turning white: the Mantine extensions&#8217; docs live at the same address and stored their light-or-dark choice under the same key, so picking light on an extension&#8217;s page turned the portfolio light until you cleared storage. Scotty 2.2.1 fixed a cousin of that bug, a colour scheme saved under the same shared key on every visit. No Raycast extension and no React library shipped this fortnight; the last release of each still stands.</p><div><hr></div><p><strong>Undolog &#8212; open source studio.</strong> Every project in one place: <a href="https://gfazioli.github.io/">gfazioli.github.io</a> &#183; this newsletter lives at <a href="https://undolog.com">undolog.com</a></p><ul><li><p><strong>FinderGit</strong> &#8212; a Git-aware file browser for macOS &#183; <a href="https://findergit.app">findergit.app</a></p></li><li><p><strong>Lancetta</strong> &#8212; a menu-bar monitor for the quotas of Codex and Claude Code &#183; <a href="https://lancetta.app">lancetta.app</a></p></li><li><p><strong>Netfox</strong> &#8212; a network monitor for macOS &#183; <a href="https://netfox.app">netfox.app</a></p></li><li><p><strong>octoscope</strong> &#8212; a terminal dashboard for GitHub &#183; <a href="https://gfazioli.github.io/octoscope">gfazioli.github.io/octoscope</a></p></li><li><p><strong>glancecard</strong> &#8212; daily SVG cards of your GitHub profile and repositories, for your READMEs &#183; <a href="https://gfazioli.github.io/glancecard/">gfazioli.github.io/glancecard</a></p></li><li><p><strong>WP Bones</strong> &#8212; a Laravel-style framework for WordPress plugins, with Bannerize and Scotty built on it &#183; <a href="https://wpbones.com">wpbones.com</a></p></li><li><p><strong>Mantine extensions</strong> &#8212; 26 components on npm &#183; <a href="https://mantine-extensions.vercel.app">mantine-extensions.vercel.app</a></p></li><li><p><strong>Raycast extensions</strong> &#8212; for the Raycast launcher &#183; <a href="https://www.raycast.com/Undolog">raycast.com/Undolog</a></p></li></ul>]]></content:encoded></item><item><title><![CDATA[The padlock that lied: what CoreWLAN's supportsSecurity really answers]]></title><description><![CDATA[Every open Wi-Fi network came back as Enhanced Open. Why the API said yes, and how to test Wi-Fi security code with no Location permission and no real access point.]]></description><link>https://www.undolog.com/p/the-padlock-that-lied-what-corewlans</link><guid isPermaLink="false">https://www.undolog.com/p/the-padlock-that-lied-what-corewlans</guid><dc:creator><![CDATA[Giovambattista Fazioli]]></dc:creator><pubDate>Sun, 04 Oct 2026 12:07:58 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!CtKx!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab3bfc3c-4d99-416c-a7fa-5e532ae38ff9_2000x1310.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!CtKx!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab3bfc3c-4d99-416c-a7fa-5e532ae38ff9_2000x1310.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!CtKx!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab3bfc3c-4d99-416c-a7fa-5e532ae38ff9_2000x1310.png 424w, https://substackcdn.com/image/fetch/$s_!CtKx!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab3bfc3c-4d99-416c-a7fa-5e532ae38ff9_2000x1310.png 848w, https://substackcdn.com/image/fetch/$s_!CtKx!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab3bfc3c-4d99-416c-a7fa-5e532ae38ff9_2000x1310.png 1272w, https://substackcdn.com/image/fetch/$s_!CtKx!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab3bfc3c-4d99-416c-a7fa-5e532ae38ff9_2000x1310.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!CtKx!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab3bfc3c-4d99-416c-a7fa-5e532ae38ff9_2000x1310.png" width="1456" height="954" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ab3bfc3c-4d99-416c-a7fa-5e532ae38ff9_2000x1310.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:954,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:983064,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.undolog.com/i/218768254?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab3bfc3c-4d99-416c-a7fa-5e532ae38ff9_2000x1310.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!CtKx!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab3bfc3c-4d99-416c-a7fa-5e532ae38ff9_2000x1310.png 424w, https://substackcdn.com/image/fetch/$s_!CtKx!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab3bfc3c-4d99-416c-a7fa-5e532ae38ff9_2000x1310.png 848w, https://substackcdn.com/image/fetch/$s_!CtKx!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab3bfc3c-4d99-416c-a7fa-5e532ae38ff9_2000x1310.png 1272w, https://substackcdn.com/image/fetch/$s_!CtKx!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab3bfc3c-4d99-416c-a7fa-5e532ae38ff9_2000x1310.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Netfox&#8217;s Wi-Fi tool puts a padlock beside every network: green and closed when the traffic is encrypted, red and open when it isn&#8217;t. Version 0.29.0 added a third case, <strong>Enhanced Open</strong> (OWE): a network you join without a password whose traffic is still encrypted. It deserves the green lock, and the join sheet tells you so.</p><p>A day after shipping it, I filtered the list to &#8220;No password&#8221; and saw two networks with a green closed padlock. One, by the look of its name, was a neighbour&#8217;s smart vacuum showing its setup hotspot. Its radio didn&#8217;t even do 802.11n, and it was supposedly speaking a protocol from 2018. <code>system_profiler SPAirPortDataType</code> agreed with my suspicion: <em>Security: None</em>. Netfox said <em>Enhanced Open, Open</em>.</p><h2>The code looked right</h2><p>This is the classifier as it shipped:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;swift&quot;,&quot;nodeId&quot;:&quot;79d0cb0f-afc2-4863-ae41-38f5cdd26aee&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-swift">private static let enhancedOpenModes: [CWSecurity] = [.OWE, .oweTransition]

static func classifyAccess(for cwNetwork: CWNetwork) -&gt; WiFiNetwork.Access {
    WiFiNetwork.Access.classify(
        open: cwNetwork.supportsSecurity(.none),
        enhancedOpen: enhancedOpenModes.contains { cwNetwork.supportsSecurity($0) },
        password: personalModes.contains { cwNetwork.supportsSecurity($0) },
        enterprise: enterpriseModes.contains { cwNetwork.supportsSecurity($0) }
    )
}</code></pre></div><p>OWE has a transition mode: the access point runs an open network plus a hidden OWE one, and a client that speaks OWE quietly uses the encrypted side. So <code>.oweTransition</code> looked like exactly the flag to check. The header documents it as &#8220;OWE Transition&#8221; and nothing more.</p><h2>Asking CoreWLAN directly, with no access point and no permission</h2><p>To see what the API really answers, I needed networks whose security I knew. I didn&#8217;t have an OWE access point, and on recent macOS an app without Location access gets scan results with the SSID, the BSSID and the raw information elements blanked out.</p><p>But <code>CWNetwork</code> keeps everything it parsed in a private dictionary, and <code>supportsSecurity(_:)</code> reads from it. Dumping that dictionary from a process with <strong>no</strong> Location permission showed that the parsed security elements survive the redaction:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;swift&quot;,&quot;nodeId&quot;:&quot;1f2eb9e8-1ef9-44a6-a1c4-9676888ebf52&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-swift">let networks = try CWWiFiClient.shared().interface()!.scanForNetworks(withSSID: nil)
for network in networks where network.supportsSecurity(.wpa2Personal) {
    let record = (network as NSObject).value(forKey: "scanRecord") as? [String: Any] ?? [:]
    print(network.ssid ?? "&lt;redacted&gt;", record["RSN_IE"] ?? "no RSN")
}</code></pre></div><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;swift&quot;,&quot;nodeId&quot;:&quot;7cdf3e82-8d6b-4532-baca-c1b7e92f13c5&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-swift">&lt;redacted&gt; {
    "IE_KEY_RSN_AUTHSELS" = ( 2 );
    "IE_KEY_RSN_MCIPHER" = 4;
    "IE_KEY_RSN_UCIPHERS" = ( 4 );
    "IE_KEY_RSN_VERSION" = 1;
    ...
}</code></pre></div><p>AUTHSELS is the list of AKM suites: 2 is a WPA2 passphrase (PSK), 8 is WPA3&#8217;s SAE, 18 is OWE. That&#8217;s enough to write the record myself and hand it to a fresh <code>CWNetwork</code>:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;swift&quot;,&quot;nodeId&quot;:&quot;a5be167f-f7a2-4e09-9c00-f54f6deb6ba9&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-swift">func network(_ record: [String: Any]) -&gt; CWNetwork {
    let network = CWNetwork()
    network.setValue(record as NSDictionary, forKey: "scanRecord")  // private: probes and tests only
    return network
}

func rsn(_ akms: [Int], mfpRequired: Bool = false) -&gt; [String: Any] {
    ["IE_KEY_RSN_VERSION": 1, "IE_KEY_RSN_MCIPHER": 4, "IE_KEY_RSN_UCIPHERS": [4],
     "IE_KEY_RSN_AUTHSELS": akms,
     "IE_KEY_RSN_CAPS": ["MFP_CAPABLE": 1, "MFP_REQUIRED": mfpRequired ? 1 : 0]]
}

let open = network(["CAPABILITIES": 0x421])            // no RSN, privacy bit off
let owe  = network(["CAPABILITIES": 0x431, "RSN_IE": rsn([18], mfpRequired: true)])
let wpa2 = network(["CAPABILITIES": 0x431, "RSN_IE": rsn([2])])</code></pre></div><p>Then ask every flag. This is what macOS 27 answered:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!-buJ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F23c93df0-03aa-4dc3-bb75-b829567143f2_1962x546.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!-buJ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F23c93df0-03aa-4dc3-bb75-b829567143f2_1962x546.png 424w, https://substackcdn.com/image/fetch/$s_!-buJ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F23c93df0-03aa-4dc3-bb75-b829567143f2_1962x546.png 848w, https://substackcdn.com/image/fetch/$s_!-buJ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F23c93df0-03aa-4dc3-bb75-b829567143f2_1962x546.png 1272w, https://substackcdn.com/image/fetch/$s_!-buJ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F23c93df0-03aa-4dc3-bb75-b829567143f2_1962x546.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!-buJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F23c93df0-03aa-4dc3-bb75-b829567143f2_1962x546.png" width="1456" height="405" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/23c93df0-03aa-4dc3-bb75-b829567143f2_1962x546.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:405,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:85013,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.undolog.com/i/218768254?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F23c93df0-03aa-4dc3-bb75-b829567143f2_1962x546.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!-buJ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F23c93df0-03aa-4dc3-bb75-b829567143f2_1962x546.png 424w, https://substackcdn.com/image/fetch/$s_!-buJ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F23c93df0-03aa-4dc3-bb75-b829567143f2_1962x546.png 848w, https://substackcdn.com/image/fetch/$s_!-buJ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F23c93df0-03aa-4dc3-bb75-b829567143f2_1962x546.png 1272w, https://substackcdn.com/image/fetch/$s_!-buJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F23c93df0-03aa-4dc3-bb75-b829567143f2_1962x546.png 1456w" sizes="auto, 100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>An open network says yes to <code>.oweTransition</code>. So does a record with <strong>nothing in it at all</strong>. A WPA2-only network says yes to WPA3 Transition and to WPA Mixed.</p><h2>It&#8217;s not a bug, it&#8217;s a different question</h2><p>Once the table is in front of you, the pattern is clear. <code>supportsSecurity(_:)</code> doesn&#8217;t answer <em>&#8220;does this access point advertise this mode?&#8221;</em>. It answers <em>&#8220;could a profile saved under this mode join this access point?&#8221;</em>. A WPA3 Transition profile can join a WPA2 network or a WPA3 one. A WPA/WPA2 Mixed profile can join a WPA2 network. An OWE Transition profile can join a plain open network, because that&#8217;s what the open half of a transition pair is.</p><p>The base flags I checked (<code>.none</code>, <code>.wpaPersonal</code>, <code>.wpa2Personal</code>, <code>.wpa3Personal</code>, <code>.OWE</code>) track what the network advertises, because for them the two questions have the same answer. The transition and mixed flags are where they diverge.</p><p>It had been costing Netfox in a second place I hadn&#8217;t noticed: the security label listed every flag that said yes. A plain WPA2 network read &#8220;WPA Mixed, WPA2 Personal, WPA3 Transition&#8221;, and the list, which shows the first mode, said <strong>WPA</strong>. On my own scan that was 17 networks out of 24.</p><p>And a real OWE transition network still answers <code>.OWE</code> itself: an open record carrying the scan key <code>SCAN_RESULT_OWE_MULTI_SSID = 1</code> (the open half of a transition pair, judging by its name) comes back yes to <code>.OWE</code> too. So <code>.oweTransition</code> wasn&#8217;t needed for anything.</p><h2>The fix is small; the lesson is in the tests</h2><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;swift&quot;,&quot;nodeId&quot;:&quot;2cd06cac-2d39-441b-9d94-1bf781028314&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-swift">// 0.29.0
enhancedOpen: [.OWE, .oweTransition].contains { cwNetwork.supportsSecurity($0) },
// 0.29.1
enhancedOpen: cwNetwork.supportsSecurity(.OWE),</code></pre></div><p>The label now names only base modes, so a transition network reads &#8220;WPA2 Personal, WPA3 Personal&#8221;, which says the same thing without claiming anything the network doesn&#8217;t do.</p><p>The more interesting question is why the tests passed. The code that matches a scanned network against saved profiles was tested with a fake:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;swift&quot;,&quot;nodeId&quot;:&quot;bbfdd0f8-8fca-4cab-8b90-bbba2ad9c35a&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-swift">private func advertising(_ modes: [CWSecurity]) -&gt; (CWSecurity) -&gt; Bool {
    { modes.contains($0) }
}

#expect(profileMatches(.wpa2Personal, supports: advertising([.wpa3Transition])))</code></pre></div><p>That test describes an access point that advertises WPA3 Transition and nothing else. CoreWLAN never reports one. The fake encoded my assumption about the API, so it could only ever agree with my code.</p><p>The tests now build real <code>CWNetwork</code>s from scan records, with the same helper as above, and one test pins the premise itself:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;swift&quot;,&quot;nodeId&quot;:&quot;bd4a02d1-8b7c-4fe8-82ff-010429c4e37d&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-swift">@Test func coreWLANAnswersTheTransitionFlagsForNetworksThatDoNotCarryThem() {
    #expect(ScanRecords.open().supportsSecurity(.oweTransition))
    #expect(ScanRecords.wpa2Personal().supportsSecurity(.wpa3Transition))
    #expect(ScanRecords.wpa2Personal().supportsSecurity(.wpaPersonalMixed))
}</code></pre></div><p>If Apple ever changes what the flag means, that test fails first, and its doc comment says which comment in the classifier has become stale.</p><h2>Caveats, honestly</h2><ul><li><p><code>scanRecord</code> is private. Setting it through KVC is fine in a test target or a throwaway probe, and has no place in the app you ship.</p></li><li><p>The keys and the table above were read on macOS 27. They&#8217;re a measurement, not a contract, which is the point of the premise test.</p></li><li><p>None of this needs a real OWE access point or Location access, and that is what makes it worth knowing: you can check what Wi-Fi security code does with any security mode, from a test, in seconds.</p></li></ul><div><hr></div><p>The fix shipped in Netfox 0.29.1: open networks show as open again, and the security label names only the modes a network uses.</p><p>Netfox is free, a universal binary (Apple Silicon + Intel), runs on macOS 15.6+, and is signed and notarized.</p><ul><li><p>Website: <a href="https://netfox.app">netfox.app</a></p></li><li><p>Download: <a href="https://netfox.app/download">https://netfox.app/download</a></p></li></ul>]]></content:encoded></item><item><title><![CDATA[Fifty-four releases, and what a silence means]]></title><description><![CDATA[A new app ships twenty-three times in its first thirteen days, WP Bones has its own CLI read line by line, and across seven products the same bug keeps turning up: a missing answer written down as the]]></description><link>https://www.undolog.com/p/fifty-four-releases-and-what-a-silence</link><guid isPermaLink="false">https://www.undolog.com/p/fifty-four-releases-and-what-a-silence</guid><dc:creator><![CDATA[Giovambattista Fazioli]]></dc:creator><pubDate>Wed, 30 Sep 2026 16:55:19 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!NPbF!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ec5eb83-060b-4e40-bdd1-682f302ec801_760x714.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Two weeks, fifty-four releases across seven products &#8212; one of them brand new, three of them new to this newsletter &#8212; plus a Mantine alignment across all 26 components and one Raycast contribution. The thread this time is one Netfox put in a release title, and once you have seen it you find it everywhere: what a program writes down when it gets no answer. Left to itself, it writes down the most reassuring thing available. A device went to sleep and said nothing, and Netfox wrote every one of its open ports down as filtered &#8212; so an exposed Telnet port quietly stopped counting as a risk. An account refused Lancetta without saying how long to wait, and Lancetta read the missing number as a wait of zero seconds. A WordPress build failed, and the command that ran it printed <code>&#9989; Build completed</code>. Most of this fortnight went into teaching tools to tell <em>nothing happened</em> from <em>nobody answered</em> &#8212; and, where they can, to go and ask.</p><h2>Lancetta</h2><p><strong>Lancetta</strong> is new, and this is its first appearance here: a native macOS menu-bar monitor for the quotas of Codex and Claude Code &#8212; the 5-hour and the weekly window for each agent, when each one resets, and where it is heading at the pace you are going. Reading a quota costs nothing, and that was measured rather than promised: twelve reads left the account&#8217;s lifetime token counter unchanged at 318,009,023, against a day of ordinary use that moved the same counter by 29,188,602. It is free, universal, signed and notarised, and runs on macOS 15 or later. The first public release, 0.2.0, went out on 18 September and 0.13.1 on the 30th. Twenty-three releases in thirteen days is too many to list, so here is their shape.</p><p><strong>An unknown is never a number (0.2.0 &#8594; 0.3.4).</strong> The app exists because of one status line. It showed <code>0%</code> when it had no reading at all, and a three-hour-old number as though it were current &#8212; not an error, not a blank, but a confident, wrong number you plan your afternoon around. So Lancetta never draws an unknown as 0%, and every reading carries its age. Launch day went the way launch days go: 0.2.0 shipped with only the Apple silicon slice while the site said Universal, and 0.2.1 fixed it the same day. The moment a quota monitor earns its place is when an agent refuses you, and that is the moment the first builds handled worst &#8212; the whole bar collapsed into one anonymous sign that read as a minus. A refusal is now a cell of its own, with the agent&#8217;s mark, the window that refused and a countdown, while the other agents keep their turn (0.3.2). An error that used to print raw JSON as the entire Codex card &#8212; it told the developer everything and the person nothing &#8212; now arrives as one sentence with a <em>Why, and what to do&#8230;</em> behind it (0.3.3).</p><p><strong>Claude stays connected (0.3.4 &#8594; 0.11.1).</strong> Claude Code rewrites its keychain item every time it renews its sign-in, and the rewrite wipes the permission you gave anyone else to read it. So macOS kept asking for your <strong>login password</strong> in the background &#8212; six times in three days, with four <em>Always Allow</em> grants wiped within the hour of being given. 0.3.4 stopped the dialog appearing on its own; 0.11.1 reads the sign-in the way Claude Code itself does, and normally there is no dialog at all, not even the first time. The permission was never going to last, however many times you granted it. And the pause from the opening paragraph: the account sends that refusal with no wait attached, and the card sat stuck for a minute and a half at a time. It retries after ten seconds, then twenty, then forty (0.6.2).</p><p><strong>The number you can already see is not the useful one (0.4.0 &#8594; 0.8.0).</strong> A percentage cannot tell you whether <em>today&#8217;s</em> pace empties the window before it resets. A line under every bar now says it &#8212; <em>55% in 2 days &#183; at this pace full by Wednesday afternoon</em> &#8212; and turns amber only when the window would run out early. It shipped against a verdict fixed in advance: if 1,890 readings across twenty-five windows showed the quota draining as a slope, the feature would be deleted rather than softened. It came back a step &#8212; the sharpest boundary went from 100% to zero in forty-two seconds. From there: notifications only for what the bar cannot show, each said once at the moment it changes and never on launch (0.5.0); a model&#8217;s own weekly limit, which the account had been sending all along while Lancetta drew 65% and said nothing (0.6.0, 0.7.0); and a menu-bar lamp that goes green, amber and red &#8212; and grey when nothing could be read, because an unknown is not an empty quota (0.8.0).</p><p><strong>The menu became a panel (0.9.0 &#8594; 0.11.0).</strong> You can pin it, drag it, and close it only with Escape. Durations now follow one rule: an hour and four minutes had been printing as <strong>64m</strong>, and a day and eight minutes as <strong>24h08m</strong>, since the app first shipped. The panel went from seven controls in three places to five in two, with <em>Updated 14s ago</em> across the top and a card&#8217;s own age shown only when it trails (0.10.0). And 0.11.0 shows the free reset Codex sometimes grants &#8212; the one thing in Lancetta that cannot be undone, so <em>Use&#8230;</em> always asks first, with two dates and Cancel as the default.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!NPbF!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ec5eb83-060b-4e40-bdd1-682f302ec801_760x714.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!NPbF!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ec5eb83-060b-4e40-bdd1-682f302ec801_760x714.png 424w, https://substackcdn.com/image/fetch/$s_!NPbF!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ec5eb83-060b-4e40-bdd1-682f302ec801_760x714.png 848w, https://substackcdn.com/image/fetch/$s_!NPbF!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ec5eb83-060b-4e40-bdd1-682f302ec801_760x714.png 1272w, https://substackcdn.com/image/fetch/$s_!NPbF!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ec5eb83-060b-4e40-bdd1-682f302ec801_760x714.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!NPbF!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ec5eb83-060b-4e40-bdd1-682f302ec801_760x714.png" width="760" height="714" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3ec5eb83-060b-4e40-bdd1-682f302ec801_760x714.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:714,&quot;width&quot;:760,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;The Lancetta panel: Updated 16s ago across the top; Claude at 10% of its 5-hour window and 3% of its week, each with a line saying where it ends at the current pace, and its Fable limit at 0%; Codex spent for the week, with the free reset it holds until 22 October and a Use&#8230; button&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="The Lancetta panel: Updated 16s ago across the top; Claude at 10% of its 5-hour window and 3% of its week, each with a line saying where it ends at the current pace, and its Fable limit at 0%; Codex spent for the week, with the free reset it holds until 22 October and a Use&#8230; button" title="The Lancetta panel: Updated 16s ago across the top; Claude at 10% of its 5-hour window and 3% of its week, each with a line saying where it ends at the current pace, and its Fable limit at 0%; Codex spent for the week, with the free reset it holds until 22 October and a Use&#8230; button" srcset="https://substackcdn.com/image/fetch/$s_!NPbF!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ec5eb83-060b-4e40-bdd1-682f302ec801_760x714.png 424w, https://substackcdn.com/image/fetch/$s_!NPbF!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ec5eb83-060b-4e40-bdd1-682f302ec801_760x714.png 848w, https://substackcdn.com/image/fetch/$s_!NPbF!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ec5eb83-060b-4e40-bdd1-682f302ec801_760x714.png 1272w, https://substackcdn.com/image/fetch/$s_!NPbF!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ec5eb83-060b-4e40-bdd1-682f302ec801_760x714.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The Lancetta panel: Updated 16s ago across the top; Claude at 10% of its 5-hour window and 3% of its week, each with a line saying where it ends at the current pace, and its Fable limit at 0%; Codex spent for the week, with the free reset it holds until 22 October and a Use&#8230; button</p><p><strong>Looking back, and everyone else&#8217;s Mac (0.12.0 &#8594; 0.13.1).</strong> A <em>Last week</em> card per agent says how the weekly window ran, how often the 5-hour one stopped you, and the hours nobody read &#8212; a night your Mac spent asleep is a gap, never a zero, and the card waits for one whole week before it says anything. <code>Lancetta usage --json</code> prints the last reading for a tmux status line or SketchyBar, and never reads an agent itself. Then 0.13.0, for machines that are not mine: an agent that is installed but not found looks exactly like one that is not installed, so a Sources pane now shows the binary, how it was found and every place searched. On macOS 27 the app had been drawing macOS 15&#8217;s controls, because Lancetta was telling macOS it had been built for macOS 15, and macOS believed it. And 0.13.1 fixed the Processes pane announcing <em>4 Codex trees &#183; 1.45 GB</em> directly above <em>No Codex process trees are running</em>.</p><p>https://lancetta.app</p><h2>WordPress: WP Bones, Bannerize and Scotty</h2><p>New to this newsletter, and not quiet about it: <strong>WP Bones</strong>, my Laravel-style framework for WordPress plugins, shipped ten releases (2.0.4 &#8594; 2.1.0), and two plugins built on it followed &#8212; <strong>Bannerize</strong> (1.13.3, 1.13.4, 2.0.0) and <strong>Scotty</strong> (2.2.0). The 14 boilerplates and their Playground demos moved with every framework release, so they get this sentence rather than fourteen paragraphs.</p><p><strong>The command every plugin carries, read line by line (2.0.10).</strong> Every WP Bones plugin ships <code>bones</code>, a single PHP file you run for <code>make:controller</code>, <code>version</code>, <code>deploy</code> and the rest. This fortnight it was read end to end and then broken on purpose. The one that could not wait: <code>php bones deploy &lt;path&gt;</code> deleted whatever was at <code>&lt;path&gt;</code> before copying, and a five-second pause was all that stood in the way &#8212; so <code>php bones deploy ..</code> emptied the parent folder, every plugin beside yours and then the plugin itself. <code>php bones deploy build</code> went the other way, copying the plugin into its own <code>build/</code>, then <code>build/build/</code>, until the path was 160 levels deep. The deploy now checks the destination before it builds anything, and the plugin, any folder containing it and any folder inside it are always refused.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!iXGh!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F646779c6-7dd7-4734-8248-05a065b29310_1568x778.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!iXGh!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F646779c6-7dd7-4734-8248-05a065b29310_1568x778.png 424w, https://substackcdn.com/image/fetch/$s_!iXGh!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F646779c6-7dd7-4734-8248-05a065b29310_1568x778.png 848w, https://substackcdn.com/image/fetch/$s_!iXGh!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F646779c6-7dd7-4734-8248-05a065b29310_1568x778.png 1272w, https://substackcdn.com/image/fetch/$s_!iXGh!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F646779c6-7dd7-4734-8248-05a065b29310_1568x778.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!iXGh!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F646779c6-7dd7-4734-8248-05a065b29310_1568x778.png" width="1456" height="722" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/646779c6-7dd7-4734-8248-05a065b29310_1568x778.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:722,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;The Where the deploy goes section of the WP Bones docs: the plugin, any folder that contains it and any folder inside it are always refused, an existing folder is replaced only when it holds a previous deploy of the same plugin, and a warning that up to v2.0.9 php bones deploy .. removed every plugin beside yours&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="The Where the deploy goes section of the WP Bones docs: the plugin, any folder that contains it and any folder inside it are always refused, an existing folder is replaced only when it holds a previous deploy of the same plugin, and a warning that up to v2.0.9 php bones deploy .. removed every plugin beside yours" title="The Where the deploy goes section of the WP Bones docs: the plugin, any folder that contains it and any folder inside it are always refused, an existing folder is replaced only when it holds a previous deploy of the same plugin, and a warning that up to v2.0.9 php bones deploy .. removed every plugin beside yours" srcset="https://substackcdn.com/image/fetch/$s_!iXGh!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F646779c6-7dd7-4734-8248-05a065b29310_1568x778.png 424w, https://substackcdn.com/image/fetch/$s_!iXGh!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F646779c6-7dd7-4734-8248-05a065b29310_1568x778.png 848w, https://substackcdn.com/image/fetch/$s_!iXGh!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F646779c6-7dd7-4734-8248-05a065b29310_1568x778.png 1272w, https://substackcdn.com/image/fetch/$s_!iXGh!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F646779c6-7dd7-4734-8248-05a065b29310_1568x778.png 1456w" sizes="auto, 100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The Where the deploy goes section of the WP Bones docs: the plugin, any folder that contains it and any folder inside it are always refused, an existing folder is replaced only when it holds a previous deploy of the same plugin, and a warning that up to v2.0.9 php bones deploy .. removed every plugin beside yours</p><p><strong>Success, reported by things that had failed (2.0.6 &#8594; 2.1.0).</strong> The rest of the audit reads like a catalogue of this issue&#8217;s thread. A broken production build printed <code>&#9989; Build completed</code>, packaged whatever was already in <code>public/</code> and exited 0. <code>bones version</code> announced <em>Version updated</em> while <code>package.json</code> kept the old number. The <code>make:*</code> generators said <em>Created</em> when the write had failed. An unknown command, a missing name and a prompt you answered &#8220;no&#8221; all exited 0, so a script could not tell any of them from success. And the <code>format:check</code> script the v2 migration writes called a formatter that ignores <code>--check</code> and always writes &#8212; so the &#8220;check&#8221; reformatted the plugin, compiled bundles included, and reported success. Each of them now fails like a failure. <code>rename</code> got the same treatment in 2.1.0: on the Internationalization boilerplate it wrote 187 files, 99 of them identical to what they had been, and corrupted the compiled <code>.mo</code> catalogues on the way, because a longer plugin id shifts every string while the offset table stays put.</p><p><strong>A number that should have been zero (2.0.7).</strong> Checking what <code>bones deploy</code> would send to the <a href="https://wordpress.org/plugins/">WordPress.org plugin directory</a>, PHPUnit was in the package &#8212; with Brain Monkey, Mockery and everything else a test suite pulls in: 31 packages nobody needs on a live site. The deploy now drops Composer&#8217;s dev packages in the copy, never in your checkout, and stops outright when it has no lockfile to do it with: a loud failure beats a quietly heavy package. The next release came from a contributor&#8217;s pull request, and gives every custom <code>bones</code> command its plugin &#8212; the same instance WordPress is already running (2.0.8).</p><p><strong>Eloquent, on the database WordPress actually uses (2.0.11, 2.0.12).</strong> Open the Database boilerplate&#8217;s demo on WordPress Playground, click Eloquent ORM, and the page stopped after its first example, with the error tucked inside a collapsed Output. Eloquent always opened a MySQL connection, and Playground runs WordPress on SQLite; it now uses the same SQLite file WordPress does &#8212; Playground, WordPress Studio, any site on the SQLite plugin. Reading the other half of that function found two more: on MySQL 8.4 an emoji saved through Eloquent failed with error 3988, and an emoji saved by WordPress came back through Eloquent as a question mark, with no error at all. Charset, collation and <code>DB_HOST</code> parsing now come from WordPress itself.</p><p><strong>Bannerize: a new foundation, and a security release for everyone else (1.13.4, 2.0.0).</strong> 2.0.0 moves Bannerize onto WP Bones 2 and needs PHP 8.1 and WordPress 6.6; on screen, nothing changes. Sites that cannot follow stay on 1.13.4 &#8212; and 1.13.4 is where this cycle&#8217;s security work went, so it is worth installing today: the shortcode&#8217;s <code>order</code> option reached the database unchecked, which let anyone who can write a post, Contributors included, run SQL of their own. It also fixes a bug you may have seen without knowing why: a banner with a maximum number of impressions or clicks refused to appear until you opened the banner list in the admin, then vanished again after the next analytics cleanup. A week earlier, 1.13.3 fixed a shortcode naming a campaign that could not be found: it rendered an empty container with no notice and no log entry, indistinguishable from a campaign that simply had no banners. Now it renders nothing, and says why in an HTML comment and a developer notice.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!x5yd!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde430e19-c5a7-4ad0-8de4-ae67e80c941a_2686x1634.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!x5yd!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde430e19-c5a7-4ad0-8de4-ae67e80c941a_2686x1634.png 424w, https://substackcdn.com/image/fetch/$s_!x5yd!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde430e19-c5a7-4ad0-8de4-ae67e80c941a_2686x1634.png 848w, https://substackcdn.com/image/fetch/$s_!x5yd!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde430e19-c5a7-4ad0-8de4-ae67e80c941a_2686x1634.png 1272w, https://substackcdn.com/image/fetch/$s_!x5yd!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde430e19-c5a7-4ad0-8de4-ae67e80c941a_2686x1634.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!x5yd!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde430e19-c5a7-4ad0-8de4-ae67e80c941a_2686x1634.png" width="1456" height="886" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/de430e19-c5a7-4ad0-8de4-ae67e80c941a_2686x1634.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:886,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;The Bannerize Analytics overview, unchanged in 2.0.0: impressions and clicks as two rings broken down by banner, referrer and unique IP, and below them the five most-shown and the five most-clicked banners&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="The Bannerize Analytics overview, unchanged in 2.0.0: impressions and clicks as two rings broken down by banner, referrer and unique IP, and below them the five most-shown and the five most-clicked banners" title="The Bannerize Analytics overview, unchanged in 2.0.0: impressions and clicks as two rings broken down by banner, referrer and unique IP, and below them the five most-shown and the five most-clicked banners" srcset="https://substackcdn.com/image/fetch/$s_!x5yd!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde430e19-c5a7-4ad0-8de4-ae67e80c941a_2686x1634.png 424w, https://substackcdn.com/image/fetch/$s_!x5yd!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde430e19-c5a7-4ad0-8de4-ae67e80c941a_2686x1634.png 848w, https://substackcdn.com/image/fetch/$s_!x5yd!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde430e19-c5a7-4ad0-8de4-ae67e80c941a_2686x1634.png 1272w, https://substackcdn.com/image/fetch/$s_!x5yd!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde430e19-c5a7-4ad0-8de4-ae67e80c941a_2686x1634.png 1456w" sizes="auto, 100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The Bannerize Analytics overview, unchanged in 2.0.0: impressions and clicks as two rings broken down by banner, referrer and unique IP, and below them the five most-shown and the five most-clicked banners</p><p><strong>Scotty 2.2.0.</strong> Scotty is the maintenance plugin: cron, database clean-up, site settings. Its <em>Run now</em> button on the Cron page ran the job and quietly removed its recurring schedule, and the Scheduled Cron Jobs counter counted due times rather than jobs &#8212; 10 on screen where there were 12. Both are fixed; the admin also moved to Mantine 9, which needs a newer React than WordPress ships, so Scotty now carries its own.</p><p><a href="https://wpbones.com">wpbones.com</a> &#183; <a href="https://wordpress.org/plugins/wp-bannerize-pro/">Bannerize on WordPress.org</a> &#183; <a href="https://scotty-plugin.vercel.app">Scotty</a></p><h2>Netfox</h2><p><strong>Asking the devices that never speak (0.23.0, 0.23.1).</strong> Netfox found devices by listening, and a device that is switched on, quiet, and has never had a reason to talk to your Mac produced no signal at all &#8212; a printer asleep, a camera that only ever phones its own cloud. It was not hard to find; it was unreachable by construction. Now you can give Netfox a range and it asks each address one small question, and whatever answers joins the list you already have rather than a parallel one. The range stays inside your own subnet &#8212; one that reaches outside is refused rather than trimmed to fit &#8212; and it doubles as a filter, which is why the footer then shows both counts, <em>2 in range &#183; 33 on the network</em>: a filter you have forgotten looks exactly like a network that has lost thirty devices. This Mac&#8217;s Ports tab also names the process behind each open port and says whether closing it can be undone. On the machine it was measured on, only 2 of 36 running services were set to restart themselves, so a tool that said &#8220;it&#8217;ll come back&#8221; would have been wrong about the other 34.</p><p>Within hours, someone sent a screenshot from a Mac Studio on Ethernet and Wi-Fi at once: the sweep menu offered <code>en0</code> and <code>en1</code>, both covering the same 254 addresses &#8212; two rows that did the same thing, in a vocabulary nobody uses. 0.23.1 shows one row per network, named the way macOS names the interface, in your language, and read from the system rather than guessed: on the laptop I wrote it on, <code>en0</code> is Wi-Fi and <code>en1</code> is Thunderbolt 1.</p><p><strong>What a silence means (0.24.1, 0.26.0).</strong> A MySQL server had been stopped. The terminal said the port was closed. Netfox, open right beside it, still said Open. It was not wrong about what it had seen &#8212; it was wrong about <em>when</em>. 0.24.1 made the Ports list honest about its age: opening the tab re-checks every open port whose answer is more than a minute old, a line says how old the list is, and Full Scan now corrects what it finds instead of keeping only the ports it found open. The rule underneath is that a silence is not a close: no reply keeps the old reading, while a refusal is a real answer and is recorded at once.</p><p>Then the part I would rather tell you than not: that rule held on some paths and not on others. A scheduled scan that caught a device asleep rewrote every open port as Filtered &#8212; so an exposed Telnet port stopped counting as a risk, History logged the ports as closed, and when the device woke up, the next scan announced each of its services as newly opened. Nothing is what a firewall that drops packets sounds like; it is also exactly what a phone sounds like while it is asleep in a pocket. 0.26.0 keeps the answer through the silence, with the time it was given, and makes the menu bar, the notch, the Overview and Security share one verdict: OK means every device Netfox can reach has been scanned and none is at risk. Until now the menu bar could show a green OK before a single device had been scanned.</p><p><strong>What are you called? (0.27.0, 0.28.0).</strong> You used to meet the same stranger three times: <em>Apple, Inc.</em>, with a question mark for an icon &#8212; a Mac, an iPhone and an iPad, and nothing on screen to tell them apart. Plenty of devices never announce a name, and Netfox was only listening; 0.27.0 asks for the <code>.local</code> name, so that row now reads <em>Annas MacBook Pro</em> with a laptop icon, and the name is kept for up to 90 days while the device is off. 0.28.0 then lists each machine once: a Mac on a cable and on Wi-Fi at the same time used to show up twice, and two connections answering to the same name at the same moment &#8212; which only one machine can do &#8212; now fold into one row. Echo speakers are asked on the Alexa ports and named as such; makers that make many kinds of thing keep the question mark, because a wrong picture is worse than none. A test build had drawn two Belkin devices as smart plugs, and they were mesh repeaters.</p><p><strong>The row that vanished with macOS 27 (0.22.1).</strong> Nobody wrote this bug. It arrived on the morning macOS 27 came out, in code that had been correct for years: the table that turns a Mac&#8217;s build number into a system name came up one row short, so every Mac that updated stopped showing what it runs &#8212; and a missing row is indistinguishable from a device that declined to answer. An old test had asserted that the next macOS was unknown, so on the day it shipped the test passed by agreeing with the bug. The new one asks the machine running the suite for its own build number, and goes red the first time anyone runs it on a macOS nobody has added a row for.</p><p><strong>And how the numbers arrive (0.24.0, 0.25.0, 0.25.1).</strong> Help &#8594; Release Notes&#8230; now holds the whole history, newest first, offline and in your language &#8212; every release had notes in six languages inside the app, and you simply could not reach them. On macOS 26 and later the menu-bar panel and the Overview cards are glass, tinted in the colour of the tool each one opens, with the colour under the glass so it survives an inactive window, which is where a network monitor spends most of its life. Numbers roll in after a deliberate beat, cards land on a spring, a refresh presses each card instead of re-running the show, and on macOS 27 you can pull a view down to refresh it, as in Mail. With Reduce Motion on, nothing moves. And 0.27.0 swaps the old notes panel for the same small update card FinderGit got a day later.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!fZVo!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9a5fd66-5c26-4ae4-923f-b619892f2965_812x944.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!fZVo!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9a5fd66-5c26-4ae4-923f-b619892f2965_812x944.png 424w, https://substackcdn.com/image/fetch/$s_!fZVo!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9a5fd66-5c26-4ae4-923f-b619892f2965_812x944.png 848w, https://substackcdn.com/image/fetch/$s_!fZVo!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9a5fd66-5c26-4ae4-923f-b619892f2965_812x944.png 1272w, https://substackcdn.com/image/fetch/$s_!fZVo!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9a5fd66-5c26-4ae4-923f-b619892f2965_812x944.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!fZVo!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9a5fd66-5c26-4ae4-923f-b619892f2965_812x944.png" width="812" height="944" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b9a5fd66-5c26-4ae4-923f-b619892f2965_812x944.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:944,&quot;width&quot;:812,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;The Netfox menu-bar panel in glass: 12 of 36 devices online, a risk ring at 4 with 0 high and 4 medium, the public IP with no VPN, 3 alerts in the inbox and a traffic line, with the violet Open Netfox button at the foot&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="The Netfox menu-bar panel in glass: 12 of 36 devices online, a risk ring at 4 with 0 high and 4 medium, the public IP with no VPN, 3 alerts in the inbox and a traffic line, with the violet Open Netfox button at the foot" title="The Netfox menu-bar panel in glass: 12 of 36 devices online, a risk ring at 4 with 0 high and 4 medium, the public IP with no VPN, 3 alerts in the inbox and a traffic line, with the violet Open Netfox button at the foot" srcset="https://substackcdn.com/image/fetch/$s_!fZVo!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9a5fd66-5c26-4ae4-923f-b619892f2965_812x944.png 424w, https://substackcdn.com/image/fetch/$s_!fZVo!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9a5fd66-5c26-4ae4-923f-b619892f2965_812x944.png 848w, https://substackcdn.com/image/fetch/$s_!fZVo!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9a5fd66-5c26-4ae4-923f-b619892f2965_812x944.png 1272w, https://substackcdn.com/image/fetch/$s_!fZVo!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9a5fd66-5c26-4ae4-923f-b619892f2965_812x944.png 1456w" sizes="auto, 100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The Netfox menu-bar panel in glass: 12 of 36 devices online, a risk ring at 4 with 0 high and 4 medium, the public IP with no VPN, 3 alerts in the inbox and a traffic line, with the violet Open Netfox button at the foot</p><p>https://netfox.app</p><h2>FinderGit</h2><p><strong>The tabs say what is waiting inside them (0.36.0, 0.37.0).</strong> Finding out what was open on a repository meant clicking Issues and counting, then Pull Requests and counting again. Repeat that across a folder of thirty repositories and you have spent a real minute on a question with a one-digit answer. The two tabs now carry their open count, and a tab with nothing open carries no number at all &#8212; Bitbucket&#8217;s Issues tab stays bare rather than claiming a zero nobody ever told it. The count costs nothing, because it is the one the file browser&#8217;s columns already had, and arrowing past a repository fetches nothing. Underneath was the bug this issue is about: a repository whose remote could not be read once &#8212; a lock file left behind by another git action is enough &#8212; was quietly written off for the rest of the session, counts and all, with no way to bring it back. Refresh now asks again. The next release gave every issue and pull request a second line saying who opened it and how long ago, because a title on its own does not tell you whether you are looking at something from this morning or something that has been sitting there since spring. It costs not a single extra request: the data arrived with the list, and was being thrown away.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!qdW_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac698761-c3f5-4378-b7d8-950e96cc8b07_2000x1282.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!qdW_!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac698761-c3f5-4378-b7d8-950e96cc8b07_2000x1282.png 424w, https://substackcdn.com/image/fetch/$s_!qdW_!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac698761-c3f5-4378-b7d8-950e96cc8b07_2000x1282.png 848w, https://substackcdn.com/image/fetch/$s_!qdW_!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac698761-c3f5-4378-b7d8-950e96cc8b07_2000x1282.png 1272w, https://substackcdn.com/image/fetch/$s_!qdW_!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac698761-c3f5-4378-b7d8-950e96cc8b07_2000x1282.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!qdW_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac698761-c3f5-4378-b7d8-950e96cc8b07_2000x1282.png" width="1456" height="933" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ac698761-c3f5-4378-b7d8-950e96cc8b07_2000x1282.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:933,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;The FinderGit detail panel on a repository with open work: the Issues tab carries an orange badge reading 17 and Pull Requests a purple 1, the other seven tabs carry none, and every issue row says who opened it and how long ago&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="The FinderGit detail panel on a repository with open work: the Issues tab carries an orange badge reading 17 and Pull Requests a purple 1, the other seven tabs carry none, and every issue row says who opened it and how long ago" title="The FinderGit detail panel on a repository with open work: the Issues tab carries an orange badge reading 17 and Pull Requests a purple 1, the other seven tabs carry none, and every issue row says who opened it and how long ago" srcset="https://substackcdn.com/image/fetch/$s_!qdW_!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac698761-c3f5-4378-b7d8-950e96cc8b07_2000x1282.png 424w, https://substackcdn.com/image/fetch/$s_!qdW_!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac698761-c3f5-4378-b7d8-950e96cc8b07_2000x1282.png 848w, https://substackcdn.com/image/fetch/$s_!qdW_!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac698761-c3f5-4378-b7d8-950e96cc8b07_2000x1282.png 1272w, https://substackcdn.com/image/fetch/$s_!qdW_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac698761-c3f5-4378-b7d8-950e96cc8b07_2000x1282.png 1456w" sizes="auto, 100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The FinderGit detail panel on a repository with open work: the Issues tab carries an orange badge reading 17 and Pull Requests a purple 1, the other seven tabs carry none, and every issue row says who opened it and how long ago</p><p><strong>Branches, without the terminal (0.38.0).</strong> Create, rename, merge, rebase and delete, beside the listing and switching that have been there since 0.11.0. A remote branch offers only Merge &#8212; a push to a remote that usually belongs to somebody else has a different blast radius. The rebase confirmation says git will check out the branch being rebased before you agree, not after, and a delete git refuses offers the forced option in the same dialog rather than a dead end. Kaleidoscope gets a button beside Refresh Diff, drawn only if you have Kaleidoscope: a permanently greyed control advertising an app you have not bought tells you nothing you can act on. And twenty-eight count strings in Italian, French, German and Spanish had been reading as plurals at one.</p><p><strong>Your own AI key, a whole changeset, and the file that made your repo heavy (0.39.0).</strong> Commit messages can now come from your own provider and key, kept in the macOS Keychain; the diff travels from your Mac straight to the service you picked, and a key that cannot be verified is saved <em>unconfirmed</em>, rather than claiming more than the app knows. Diff All sends staged and unstaged changes to Kaleidoscope as one changeset, and lists underneath whatever could not be sent, and why. And the one I have wanted for years: the twenty largest objects in a repository&#8217;s history, files deleted long ago included &#8212; with the rewrite offered as a runbook rather than a button. FinderGit never runs those commands itself.</p><p><strong>Dashboards that arrive (0.40.0).</strong> The Overview and Account cards land one after another and their figures roll up; they are glass on macOS 26 and later, and on macOS 27 you can pull them down to refresh, as in Mail. With Reduce Motion on, nothing moves. After an update, a small card names the version, and Learn More&#8230; opens the Release Notes window, where a release&#8217;s unread dot now goes the moment you open it.</p><p>https://findergit.app</p><h2>octoscope</h2><p><strong>Your activity, in the order it happened (0.35.0).</strong> octoscope has a quiet half &#8212; <code>--json</code> and <code>--plain</code>, the part you pipe into a status bar or a cron job &#8212; and until now it could tell you everything about your repositories and nothing about <em>you</em>. <code>--activity</code> changes that. One decision worth knowing if you build on it: when you do not ask for the feed, the <code>recent_activity</code> key is <strong>absent</strong>, not empty. An empty list would be a claim &#8212; <em>we looked, there was nothing</em> &#8212; and that claim would be false. A missing key lets a script tell <em>did not fetch</em> from <em>nothing happened</em>.</p><p>Wiring it in turned up the part nobody went looking for: the feed had never been in chronological order. <strong>27 of 99</strong> adjacent events came back out of sequence, then 28 in the next feed, then 25. The events endpoint is not documented as sorted, and it is not; octoscope had been trusting the order it was handed, so the Activity tab has been showing an <em>almost</em> sorted list for months &#8212; worse than an obviously unsorted one, because it reads as correct. It is sorted where events are parsed now, so the tab and the reports are fixed by the same change. The supply-chain scan also started reading the repository&#8217;s own settings: a workflow reacting to discussions, on a repository with discussions turned off, is not an exposure. That moves scores in both directions &#8212; fewer findings where settings make a trigger unreachable, <strong>more</strong> on private and internal repositories, where the axis used to stay quiet.</p><p><strong>Someone to wait with (0.36.0).</strong> Every screen before the dashboard now opens with a small octopus and its periscope, drawn with the terminal&#8217;s own quadrant block characters and wearing your theme&#8217;s colours; once the dashboard is up, a smaller one stays beside the banner and looks around whenever a refresh runs. It moves on the spinner&#8217;s own ticks and stops when the spinner does, so it never adds a moment to the wait it keeps you company through. One honest caveat: a terminal font without those block characters shows boxes where the octopus should be.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!dbpA!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4a3e2a6-f0e4-4e2d-8022-38fb5498418f_3400x3000.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!dbpA!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4a3e2a6-f0e4-4e2d-8022-38fb5498418f_3400x3000.png 424w, https://substackcdn.com/image/fetch/$s_!dbpA!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4a3e2a6-f0e4-4e2d-8022-38fb5498418f_3400x3000.png 848w, https://substackcdn.com/image/fetch/$s_!dbpA!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4a3e2a6-f0e4-4e2d-8022-38fb5498418f_3400x3000.png 1272w, https://substackcdn.com/image/fetch/$s_!dbpA!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4a3e2a6-f0e4-4e2d-8022-38fb5498418f_3400x3000.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!dbpA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4a3e2a6-f0e4-4e2d-8022-38fb5498418f_3400x3000.png" width="1456" height="1285" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f4a3e2a6-f0e4-4e2d-8022-38fb5498418f_3400x3000.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1285,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;octoscope 0.36.0 on the Overview tab: the small octopus beside the banner, then the profile card, the social and activity counters, the languages bar and the top repositories, with the last refresh and the automatic interval in the footer&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="octoscope 0.36.0 on the Overview tab: the small octopus beside the banner, then the profile card, the social and activity counters, the languages bar and the top repositories, with the last refresh and the automatic interval in the footer" title="octoscope 0.36.0 on the Overview tab: the small octopus beside the banner, then the profile card, the social and activity counters, the languages bar and the top repositories, with the last refresh and the automatic interval in the footer" srcset="https://substackcdn.com/image/fetch/$s_!dbpA!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4a3e2a6-f0e4-4e2d-8022-38fb5498418f_3400x3000.png 424w, https://substackcdn.com/image/fetch/$s_!dbpA!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4a3e2a6-f0e4-4e2d-8022-38fb5498418f_3400x3000.png 848w, https://substackcdn.com/image/fetch/$s_!dbpA!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4a3e2a6-f0e4-4e2d-8022-38fb5498418f_3400x3000.png 1272w, https://substackcdn.com/image/fetch/$s_!dbpA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4a3e2a6-f0e4-4e2d-8022-38fb5498418f_3400x3000.png 1456w" sizes="auto, 100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>octoscope 0.36.0 on the Overview tab: the small octopus beside the banner, then the profile card, the social and activity counters, the languages bar and the top repositories, with the last refresh and the automatic interval in the footer</p><p>The less visible half of the release is about time. Pressing <code>r</code> used to leave the automatic refresh on its old schedule, so it could fire seconds after yours, asking GitHub for what you had just fetched &#8212; or, after a rate limit, spend one more request against a limit you had already hit. Now every answer from GitHub decides when the next automatic refresh runs, whoever asked for it. Measured against the real API with a 30-second interval: before, the automatic refresh went out <strong>7 seconds</strong> after a manual one; now it goes out 30 seconds after. And the scan stopped mixing branches: a trigger found in one branch&#8217;s copy of a workflow was being scored together with a secret found in another branch&#8217;s copy of the same file, so two harmless files could add up to a score of 3 for a capability that existed on neither.</p><p><a href="https://gfazioli.github.io/octoscope">https://gfazioli.github.io/octoscope</a> &#183; <code>brew upgrade gfazioli/tap/octoscope</code></p><h2>Mantine extensions</h2><p>A quiet fortnight by design: on the 29th all 26 components moved to Mantine 9.6.3 in one sweep &#8212; one patch each, nothing you have to do &#8212; and the two documentation templates moved with them. The one fix with a story is on the extensions site. In Firefox, hovering a card under All Extensions flooded the whole thumbnail with the BorderAnimate ring&#8217;s gradient instead of drawing the thin travelling ring; it was reported on Discord against Firefox 156, and the card now stacks one transform instead of three.</p><p><a href="https://mantine-extensions.vercel.app">mantine-extensions.vercel.app</a></p><h2>The Raycast extensions</h2><p>One contribution this time, to <strong>Open Folders</strong>: a new <em>Open Folders In</em> preference decides where Finder shows a folder you open from the extension &#8212; a new window, as before, a new tab in the front Finder window, or the current tab. The new-tab mode has a catch worth stating up front: Finder&#8217;s scripting dictionary has no command for tabs, so the only way in is &#8984;T through System Events, which needs Accessibility permission for Raycast &#8212; and the preference says so rather than failing quietly. When Finder cannot be scripted, the folder opens in a new window with a HUD explaining why.</p><p><a href="https://www.raycast.com/timothy_boye/open-folders">Open Folders</a> &#183; <a href="https://www.raycast.com/Undolog">raycast.com/Undolog</a></p><h2>And on the site</h2><p><a href="https://gfazioli.github.io/">gfazioli.github.io</a> has a Lancetta card in the macOS section, the Italian page now tells search engines it is Italian, and switching language no longer makes the page jump 80 pixels. The React component libraries shipped nothing this fortnight; the last release of each still stands.</p><div><hr></div><p><strong>Undolog &#8212; open source studio.</strong> Every project in one place: <a href="https://gfazioli.github.io/">gfazioli.github.io</a> &#183; this newsletter lives at <a href="https://undolog.com">undolog.com</a></p><ul><li><p><strong>FinderGit</strong> &#8212; a Git-aware file browser for macOS &#183; <a href="https://findergit.app">findergit.app</a></p></li><li><p><strong>Lancetta</strong> &#8212; a menu-bar monitor for the quotas of Codex and Claude Code &#183; <a href="https://lancetta.app">lancetta.app</a></p></li><li><p><strong>Netfox</strong> &#8212; a network monitor for macOS &#183; <a href="https://netfox.app">netfox.app</a></p></li><li><p><strong>octoscope</strong> &#8212; a terminal dashboard for GitHub &#183; <a href="https://gfazioli.github.io/octoscope">gfazioli.github.io/octoscope</a></p></li><li><p><strong>WP Bones</strong> &#8212; a Laravel-style framework for WordPress plugins, with Bannerize and Scotty built on it &#183; <a href="https://wpbones.com">wpbones.com</a></p></li><li><p><strong>Mantine extensions</strong> &#8212; 26 components on npm &#183; <a href="https://mantine-extensions.vercel.app">mantine-extensions.vercel.app</a></p></li><li><p><strong>Raycast extensions</strong> &#8212; for the Raycast launcher &#183; <a href="https://www.raycast.com/Undolog">raycast.com/Undolog</a></p></li></ul>]]></content:encoded></item><item><title><![CDATA[Twenty-three releases, and the checks that were not checking]]></title><description><![CDATA[FinderGit learned to report every action it takes, Netfox to withhold a verdict it cannot support, octoscope to sign what it publishes &#8212; and in four projects a check that had been passing turned out t]]></description><link>https://www.undolog.com/p/twenty-three-releases-and-the-checks</link><guid isPermaLink="false">https://www.undolog.com/p/twenty-three-releases-and-the-checks</guid><dc:creator><![CDATA[Giovambattista Fazioli]]></dc:creator><pubDate>Mon, 14 Sep 2026 12:01:15 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!qR4i!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0b10f58-a9e5-4af1-b0e0-0de0d94c2157_2000x1282.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Two weeks, twenty-three releases across three apps. The thread this time is narrower than last issue&#8217;s, and less comfortable. Last time the pattern was tools that stopped claiming more than they had checked. This time it is the checks themselves: in four unrelated projects, something that had been passing for months turned out to have been measuring nothing at all.</p><h2>FinderGit</h2><p><strong>Open a commit like a folder (0.29.0).</strong> Reading a commit list, you want to know one small thing: which files did that one touch? Every commit now has a chevron, and opening it is exactly like opening a folder &#8212; the files, git&#8217;s status letter, the old name where something was renamed, and a read-only diff scoped to what that commit did to that file. Deleted files open too, because what a commit removed is often the part worth reading. Issues and pull requests open in place as well, with the whole conversation: the body a reviewer submitted with their verdict, and the notes pinned to individual lines. A pull request reviewed entirely inline &#8212; which is how most of them are &#8212; used to look as though nobody had said a word.</p><p>The same release fixed something I would rather tell you about than not. Discarding a file whose name contains <code>*</code>, <code>?</code> or square brackets could revert a <strong>different</strong> file, because git reads those characters as a pattern: asking it to restore <code>a[1].txt</code> restored <code>a1.txt</code> instead, and discard has no undo. If you have never named a file that way, this never touched you. If you have, it did so without saying anything, which is the part that bothers me.</p><p><strong>An empty cell is not a zero (0.30.0).</strong> GitLab and Bitbucket now fill in their own issue, pull-request and star counts with no account and no token, joining GitHub and Codeberg. Adding two forges was the easy half. The half that took the time was being honest about what each one <em>cannot</em> answer: Bitbucket retired its issue tracker, so its Issues cell is now <strong>empty</strong> while a GitHub row above it reads <strong>0</strong>, and those are different facts that finally look different. The rule now runs through everything &#8212; a count that could not be obtained shows nothing rather than zero, whatever the reason.</p><p>The one that mattered most was forks. Only GitHub will say how far a fork trails its upstream; the other three answer with a <code>0</code> that means <em>no permission to tell you</em>, and FinderGit had been handing that zero straight through to a banner drawn as a green &#8220;Up to date with upstream&#8221;. A fork three hundred commits behind looked current.</p><p><strong>Your tags and your releases, side by side (0.31.0 &#8594; 0.33.0).</strong> There is a quiet lie every Git tool tells: ask for your tags and you get the ones in <em>your</em> clone. A tag you cut this morning and never pushed looks exactly like one the whole team has had for a year. A new tab pairs every tag with the release cut from it and marks each row <strong>on both</strong>, <strong>local only</strong> or <strong>not fetched</strong> &#8212; and the buttons follow from that, so Push is drawn where there is something to push and nowhere else.</p><p>Two people then found the rest of the release. One opened it on a repository with 715 tags, selected three from four years ago, and found the bar carrying every action several hundred rows above, off the top of the panel &#8212; it is pinned now. The other asked why <code>v1.0.2</code> sat at the bottom of a 207-tag list, under one six days older. The answer is that git has two kinds of tag: an annotated tag is a real object with its own date, while a lightweight tag is a name pointing at a commit, and git records <em>nothing</em> about when it was made &#8212; so the only date available is the commit&#8217;s. The sort was correct, and there was no order in which it read correctly. Rows now say which kind they are, and a commit subject is drawn behind its own mark rather than where a tag message would go: the two were written by different people at different times, and a commit subject printed as a tag message is a caption that lies.</p><p>Deleting a tag everywhere is one action now, and it runs <strong>the remote first, deliberately</strong>: if the remote refuses, your local tag is untouched and retrying costs nothing &#8212; the other order destroys the last reference naming that commit and only then discovers it could not reach origin.</p><p><strong>You should not have to be watching (0.34.0).</strong> You start twenty tag deletions, look away, and never find out how it went. Every action now reports itself in a card in the corner carrying git&#8217;s own words &#8212; not a paraphrase, not &#8220;an error occurred&#8221; &#8212; and the card leaves on its own, because a stack you have to dismiss gets dismissed reflexively without being read. What stays is the bell: a failure lights the badge and keeps it lit, a success does not, so the badge means &#8220;something needs you&#8221; rather than &#8220;something happened&#8221;. Bulk runs show their progress, and a run you stop marks the tags it never reached as <em>not attempted</em> rather than guessing &#8212; the one you may have to check by hand is precisely the one a guess would hide.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!qR4i!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0b10f58-a9e5-4af1-b0e0-0de0d94c2157_2000x1282.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!qR4i!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0b10f58-a9e5-4af1-b0e0-0de0d94c2157_2000x1282.png 424w, https://substackcdn.com/image/fetch/$s_!qR4i!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0b10f58-a9e5-4af1-b0e0-0de0d94c2157_2000x1282.png 848w, https://substackcdn.com/image/fetch/$s_!qR4i!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0b10f58-a9e5-4af1-b0e0-0de0d94c2157_2000x1282.png 1272w, https://substackcdn.com/image/fetch/$s_!qR4i!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0b10f58-a9e5-4af1-b0e0-0de0d94c2157_2000x1282.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!qR4i!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0b10f58-a9e5-4af1-b0e0-0de0d94c2157_2000x1282.png" width="1456" height="933" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a0b10f58-a9e5-4af1-b0e0-0de0d94c2157_2000x1282.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:933,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;A fetch that failed reporting itself in the corner with git's own message, while the bell in the toolbar carries the unread badge&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="A fetch that failed reporting itself in the corner with git's own message, while the bell in the toolbar carries the unread badge" title="A fetch that failed reporting itself in the corner with git's own message, while the bell in the toolbar carries the unread badge" srcset="https://substackcdn.com/image/fetch/$s_!qR4i!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0b10f58-a9e5-4af1-b0e0-0de0d94c2157_2000x1282.png 424w, https://substackcdn.com/image/fetch/$s_!qR4i!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0b10f58-a9e5-4af1-b0e0-0de0d94c2157_2000x1282.png 848w, https://substackcdn.com/image/fetch/$s_!qR4i!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0b10f58-a9e5-4af1-b0e0-0de0d94c2157_2000x1282.png 1272w, https://substackcdn.com/image/fetch/$s_!qR4i!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0b10f58-a9e5-4af1-b0e0-0de0d94c2157_2000x1282.png 1456w" sizes="auto, 100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>A fetch that failed reporting itself in the corner with git&#8217;s own message, while the bell in the toolbar carries the unread badge</p><p><strong>A position is not a name (0.35.0).</strong> Stash moved down to the bar beside Fetch, Pull and Push, where you actually reach for it &#8212; the old placement vanished at exactly the moment you wanted it, because pressing Stage All emptied the list whose header it lived in. But the fix worth reading about is underneath: every row in the stash list carried its <em>position</em>, and a position renumbers when something above it is dropped. A list read a moment earlier could point Pop at a different stash than the one under your cursor, and because the changes it applied looked perfectly plausible, nothing on screen would ever have told you. A stash is identified by its own commit now, resolved at the instant the action runs.</p><p>It took six rounds of review to land, five of them patching one window at a time while a sixth kept opening. What finally worked was changing the question: not &#8220;where is a check missing&#8221; but &#8220;which decision here is being made on data nobody validated&#8221;.</p><p>And the two that belong to this issue&#8217;s thread. A translation coverage report had been reading <strong>100%</strong> while 189 user-facing strings had never entered the catalogue at all &#8212; it was right on its own terms, because a string that never became a key cannot be reported as missing. And a rule about how SwiftUI reports sizes, credited with fixing a clipped panel and then cited by every popover fix since, turned out not to be true: a measurement puts each shape into the real sizing path and reports the same height with the modifier and without it. The measurement is now in the repository, so next time the answer comes from running something rather than remembering a sentence.</p><p><a href="https://findergit.app">https://findergit.app</a></p><h2>Netfox</h2><p><strong>Is it my connection, or the internet? (0.18.0)</strong> A new Link panel reads the layer under the IP address: what the cable or the radio actually negotiated, the error counters, and how much of the delay is your own link against everything past your door. Every latency figure is a <strong>minimum</strong> rather than an average, and that was measured rather than assumed &#8212; across five bursts the minimum moved under 1 ms while the average moved over 100 ms, so an average would have blamed your link for the internet&#8217;s bad day.</p><p>The part I would point at, though, is what the panel refuses to say. A link nothing has crossed gets no clean bill of health: it has not been shown to be fine, it has been shown nothing. A radio that is switched off has no band and no channel, so those are left out rather than filled in with &#8220;Unknown&#8221; &#8212; an absence rendered as two facts is worse than a gap. And a router that ignores pings is reported as untimeable rather than broken, because plenty of them drop pings by policy.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!BeC_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8af8dd7a-a445-44b3-9f16-1f1ea304c273_2000x1304.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!BeC_!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8af8dd7a-a445-44b3-9f16-1f1ea304c273_2000x1304.png 424w, https://substackcdn.com/image/fetch/$s_!BeC_!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8af8dd7a-a445-44b3-9f16-1f1ea304c273_2000x1304.png 848w, https://substackcdn.com/image/fetch/$s_!BeC_!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8af8dd7a-a445-44b3-9f16-1f1ea304c273_2000x1304.png 1272w, https://substackcdn.com/image/fetch/$s_!BeC_!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8af8dd7a-a445-44b3-9f16-1f1ea304c273_2000x1304.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!BeC_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8af8dd7a-a445-44b3-9f16-1f1ea304c273_2000x1304.png" width="1456" height="949" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8af8dd7a-a445-44b3-9f16-1f1ea304c273_2000x1304.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:949,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;The Netfox Overview with the new Link panel: the wired half, the radio and its headroom, the error counters, and the first-hop split&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="The Netfox Overview with the new Link panel: the wired half, the radio and its headroom, the error counters, and the first-hop split" title="The Netfox Overview with the new Link panel: the wired half, the radio and its headroom, the error counters, and the first-hop split" srcset="https://substackcdn.com/image/fetch/$s_!BeC_!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8af8dd7a-a445-44b3-9f16-1f1ea304c273_2000x1304.png 424w, https://substackcdn.com/image/fetch/$s_!BeC_!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8af8dd7a-a445-44b3-9f16-1f1ea304c273_2000x1304.png 848w, https://substackcdn.com/image/fetch/$s_!BeC_!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8af8dd7a-a445-44b3-9f16-1f1ea304c273_2000x1304.png 1272w, https://substackcdn.com/image/fetch/$s_!BeC_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8af8dd7a-a445-44b3-9f16-1f1ea304c273_2000x1304.png 1456w" sizes="auto, 100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The Netfox Overview with the new Link panel: the wired half, the radio and its headroom, the error counters, and the first-hop split</p><p><strong>What a certificate says about itself (0.19.0, 0.19.1).</strong> Every home network has a few: the NAS admin page, the printer, the router, a Home Assistant box. They all speak TLS, and almost nobody ever looks &#8212; until a browser refuses to open one and you are guessing whether the device is broken, misconfigured, or just doing what home devices do. Netfox now reads the certificate and says plainly whether it is expired, expiring within three weeks, self-signed, or not yet valid, which on a home network almost always means the device&#8217;s clock is wrong rather than the certificate. The next release added how the two of you are actually talking &#8212; the negotiated version and cipher, flagging an obsolete version or a key exchange with no forward secrecy, and honest that this is one server&#8217;s answer to one negotiation with your Mac, not everything it could do.</p><p><strong>The server you forgot you started (0.20.0).</strong> A test run finishes, the terminal window closes, and something it started keeps holding port 3000. Netfox scans your network, and a server bound only to loopback is not on your network &#8212; so 0.20.0 looks in the other direction. The finding is not that a port is open: it is that the process was left behind. It waits fifteen minutes from the moment of orphaning, and it only reports listeners every process of which is yours, because one you cannot act on is not a finding, it is a nag. The naive version of that rule fired on three services out of eight on a test machine, all of them Apple&#8217;s own.</p><p><strong>The one port worth asking twice about (0.21.0).</strong> There is one port on a home network more likely than any other to be running something it should not, and it is the boring one: 445, file sharing. What matters is not that it is open but which version the other end speaks &#8212; and a device that speaks only SMB 1 does not reply to a modern request at all. It just says nothing. Ask once and take silence for an answer, and the device most worth warning you about is the one reported as fine. So it asks twice, and where it cannot settle the second question it says nothing rather than telling you SMB 1 is off: a reassuring sentence about a question nobody managed to ask is worse than no sentence.</p><p>The same release found the landing screen burning <strong>9.6% of a processor</strong> against 0.6% on any other screen &#8212; beside a comment in the code claiming &#8220;well under 1%&#8221;.</p><p><strong>A window that fits where you put it (0.22.0).</strong> If you have ever dragged Netfox to one side of the screen to watch it while you worked, you know it refused to go narrower than 960 points and its right edge simply left the display. It now goes down to 500&#215;340 and changes shape rather than squeezing: not a smaller version of the page, a different one, with the figures that matter all on screen at once. Fixing it also turned up a click at that size that selected nothing &#8212; while 959 existing tests passed the entire time. There is now one that clicks a row at that size and fails if nothing opens.</p><p>And the one I would rather have not shipped: every build up to 0.18.0 carried a setting that belongs on a development machine, the one that lets a debugger attach to a running program and read whatever it is holding. Nobody added it &#8212; it arrives automatically on a development build, and the step that prepares the release faithfully preserved everything the build had produced. That is fixed in 0.18.1.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!riS9!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feda23de6-1eae-46a8-8516-67e8e1dcb766_1120x1000.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!riS9!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feda23de6-1eae-46a8-8516-67e8e1dcb766_1120x1000.png 424w, https://substackcdn.com/image/fetch/$s_!riS9!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feda23de6-1eae-46a8-8516-67e8e1dcb766_1120x1000.png 848w, https://substackcdn.com/image/fetch/$s_!riS9!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feda23de6-1eae-46a8-8516-67e8e1dcb766_1120x1000.png 1272w, https://substackcdn.com/image/fetch/$s_!riS9!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feda23de6-1eae-46a8-8516-67e8e1dcb766_1120x1000.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!riS9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feda23de6-1eae-46a8-8516-67e8e1dcb766_1120x1000.png" width="1120" height="1000" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/eda23de6-1eae-46a8-8516-67e8e1dcb766_1120x1000.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1000,&quot;width&quot;:1120,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Netfox in its widget shape: four tiles filling a small window &#8212; risk posture at 3 medium and 29 low, 23 devices of 32 on the network, the public IP on a direct connection, and 3 alerts &#8212; with every figure on screen at once&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Netfox in its widget shape: four tiles filling a small window &#8212; risk posture at 3 medium and 29 low, 23 devices of 32 on the network, the public IP on a direct connection, and 3 alerts &#8212; with every figure on screen at once" title="Netfox in its widget shape: four tiles filling a small window &#8212; risk posture at 3 medium and 29 low, 23 devices of 32 on the network, the public IP on a direct connection, and 3 alerts &#8212; with every figure on screen at once" srcset="https://substackcdn.com/image/fetch/$s_!riS9!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feda23de6-1eae-46a8-8516-67e8e1dcb766_1120x1000.png 424w, https://substackcdn.com/image/fetch/$s_!riS9!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feda23de6-1eae-46a8-8516-67e8e1dcb766_1120x1000.png 848w, https://substackcdn.com/image/fetch/$s_!riS9!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feda23de6-1eae-46a8-8516-67e8e1dcb766_1120x1000.png 1272w, https://substackcdn.com/image/fetch/$s_!riS9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feda23de6-1eae-46a8-8516-67e8e1dcb766_1120x1000.png 1456w" sizes="auto, 100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Netfox in its widget shape: four tiles filling a small window &#8212; risk posture at 3 medium and 29 low, 23 devices of 32 on the network, the public IP on a direct connection, and 3 alerts &#8212; with every figure on screen at once</p><p><a href="https://netfox.app">https://netfox.app</a></p><h2>octoscope</h2><p><strong>The version it prints is the version you have (0.31.1).</strong> Every octoscope release is built with a linker flag whose entire job is to stamp the tag into the binary. It never did anything: the version was declared as a Go constant, and the linker cannot patch a constant. No warning was ever emitted, so nobody knew &#8212; and here is the honest half, measured rather than assumed: across all 42 releases the hand-edited line had matched its tag every single time. What was missing was never correctness. It is that nothing checked. There is a test now, and it was written carefully, because a check that can silently measure nothing is precisely the failure this release is about and it would be a poor joke for it to be one itself.</p><p><strong>Your lockfile, read line by line (0.33.0).</strong> The lockfile is the one part of a pull request nobody reads that way. The scan now does it for you, flagging dependencies that newly run install scripts and versions republished with different bytes. Worth knowing how rare that is: across the last twenty revisions of the lockfiles in axios, npm/cli and undici &#8212; fifty-seven revisions in all &#8212; the files churned the entire time, the subset that runs code at install moved <strong>twice</strong>, and the same version was republished <strong>zero</strong> times. It is npm only, and the report says so on every repository it could not compare, because silence would look exactly like <em>nothing here runs code at install</em>, and that is the one thing it must never imply.</p><p><strong>It stops blaming you for your own refactor (0.34.0).</strong> You reorganise a monorepo and the scan lights up. Nothing started; you moved a folder. Packages are now keyed by their declared name rather than their path, and the fix was measured before it was chosen: across 115 workspace entries in seven npm-workspace monorepos, 85 carried a name, and all 85 differed from the directory they sat in. A baseline recorded in the old format now reads as <strong>not comparable</strong> instead of being diffed against a different naming scheme.</p><p><strong>And a green release that did not mean what it looked like (0.34.1 &#8594; 0.34.3).</strong> If you installed 0.34.0 on macOS and the command did nothing at all, that was real and it was ours: a Homebrew cask&#8217;s download is quarantined, unlike a formula&#8217;s, and Gatekeeper killed the ad-hoc-signed binary at launch. The stopgap stripped the quarantine flag, the next release found that the hook it used had been deprecated in a way that actually broke installs, and 0.34.3 closed it properly &#8212; Developer ID signing and Apple notarization, which also covers what Homebrew never could: a <code>.tar.gz</code> downloaded straight from the releases page.</p><p>The measured finding along the way is the one I would keep: <strong>signing without notarizing changes nothing at all.</strong> The binary is still killed, and <code>spctl</code> still reports it rejected. There is now a CI job that downloads the published binary, checks its notarization, re-quarantines it and actually runs it &#8212; with a negative control, an ad-hoc-resigned binary that must fail, so the check cannot pass silently on a runner that is not enforcing. <code>brew install</code> exiting 0 is exactly what the broken release did.</p><p>Also in the fortnight: octoscope now installs four ways rather than two &#8212; as a <code>gh</code> extension, a roughly 7 MB container image, a standalone platform binary, or through Homebrew &#8212; and CI builds and runs the Windows binary natively for the first time, having shipped one since v0.2.0 on the strength of cross-compilation alone.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!FVcR!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F499bc65b-3574-443b-a9b6-867d6d86c9bf_3400x3000.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!FVcR!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F499bc65b-3574-443b-a9b6-867d6d86c9bf_3400x3000.png 424w, https://substackcdn.com/image/fetch/$s_!FVcR!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F499bc65b-3574-443b-a9b6-867d6d86c9bf_3400x3000.png 848w, https://substackcdn.com/image/fetch/$s_!FVcR!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F499bc65b-3574-443b-a9b6-867d6d86c9bf_3400x3000.png 1272w, https://substackcdn.com/image/fetch/$s_!FVcR!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F499bc65b-3574-443b-a9b6-867d6d86c9bf_3400x3000.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!FVcR!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F499bc65b-3574-443b-a9b6-867d6d86c9bf_3400x3000.png" width="1456" height="1285" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/499bc65b-3574-443b-a9b6-867d6d86c9bf_3400x3000.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1285,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;octoscope 0.34.3 on the Overview tab: the profile card, the social and activity counters, the languages bar, the top repositories by stars and the network row&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="octoscope 0.34.3 on the Overview tab: the profile card, the social and activity counters, the languages bar, the top repositories by stars and the network row" title="octoscope 0.34.3 on the Overview tab: the profile card, the social and activity counters, the languages bar, the top repositories by stars and the network row" srcset="https://substackcdn.com/image/fetch/$s_!FVcR!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F499bc65b-3574-443b-a9b6-867d6d86c9bf_3400x3000.png 424w, https://substackcdn.com/image/fetch/$s_!FVcR!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F499bc65b-3574-443b-a9b6-867d6d86c9bf_3400x3000.png 848w, https://substackcdn.com/image/fetch/$s_!FVcR!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F499bc65b-3574-443b-a9b6-867d6d86c9bf_3400x3000.png 1272w, https://substackcdn.com/image/fetch/$s_!FVcR!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F499bc65b-3574-443b-a9b6-867d6d86c9bf_3400x3000.png 1456w" sizes="auto, 100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>octoscope 0.34.3 on the Overview tab: the profile card, the social and activity counters, the languages bar, the top repositories by stars and the network row</p><p><a href="https://gfazioli.github.io/octoscope">https://gfazioli.github.io/octoscope</a> </p><h2>Mantine extensions</h2><p><strong>A window gesture you can compose (mantine-window 3.2.0).</strong> <code>Window</code> now emits <code>onDragStart</code>, <code>onDragEnd</code>, <code>onResizeStart</code> and <code>onResizeEnd</code>, completing a set that previously had only the two <em>change</em> callbacks &#8212; and those fire on every frame. The edges are what make a gesture composable: persist a layout once when it ends instead of debouncing by hand, pause an expensive child while the user drags, record one undo entry per gesture rather than one per pixel.</p><p>Two guarantees came out of the implementation rather than the API sketch. Every start is paired with exactly one end &#8212; the global listener ends both gestures at once, so a naive implementation would have emitted an unpaired <code>onDragEnd</code> during a plain resize &#8212; and no start is emitted without a real gesture, so a press on a resize handle, an interactive child or a <code>data-no-window-drag</code> region never opens one that would not close. Verified with real pointer events in a browser rather than only in jsdom: across one drag and one resize the continuous callbacks fired <strong>20</strong> times and the lifecycle ones <strong>twice</strong>.</p><p>Beyond that it was a fortnight of alignment: the whole family moved to Mantine 9.6.0 at the start of the period and to 9.6.1 at the end of it, which is two publishes for every component and nothing you have to do.</p><p><a href="https://gfazioli.github.io/mantine-window/">mantine-window</a> &#183; <a href="https://mantine-extensions.vercel.app">mantine-extensions.vercel.app</a></p><h2>The Raycast extensions</h2><p><strong>macOS Tweaks went from 108 tweaks to 129</strong>, with a new Windows &amp; Stage Manager category covering Stage Manager, the Sonoma click-the-wallpaper behaviour and Sequoia&#8217;s window tiling, plus a modifier key for each hot corner &#8212; the corners were already there, the modifiers that stop them firing by accident were not. Four AI tools make a 129-entry catalogue findable, because nobody looking for &#8220;let me see hidden files&#8221; knows to search for <code>com.apple.finder AppleShowAllFiles</code>; the two that write both ask for confirmation showing the exact <code>defaults</code> command and which process will restart.</p><p>Every domain and key was verified against a live system, and each declared default was diffed against the real current value &#8212; which caught one that had been wrong: double-clicking a title bar has defaulted to Fill since macOS 15, not the older Maximize. In the same spirit, the two smart-quote tweaks had been showing as modified when untouched, because <code>defaults read</code> prints non-ASCII characters as escape sequences and the comparison never matched.</p><p><strong>App Updates got its Sparkle scan under control</strong>: reading the <code>Info.plist</code> of every app in <code>/Applications</code> used to fan out one process per installed app. Measured on a 32-app machine, that was 31 concurrent processes before and 8 after, with an identical scan result.</p><p><a href="https://www.raycast.com/Undolog">https://www.raycast.com/Undolog</a></p><h2>And on the site</h2><p><a href="https://gfazioli.github.io/">gfazioli.github.io</a> now opens with the three most recent releases across every project and a count of the last thirty days, so the front page answers &#8220;what has been happening&#8221; before you go looking. The macOS app cards are backed by their public website repositories, which is why they finally carry real descriptions and dates.</p><p>The React component libraries &#8212; flip, tilt, toggle and the guru meditation &#8212; shipped nothing this fortnight; they are stable and the last release of each still stands.</p><div><hr></div><p><strong>Undolog &#8212; open source studio.</strong> Every project in one place: <a href="https://gfazioli.github.io/">gfazioli.github.io</a> &#183; this newsletter lives at <a href="https://undolog.com">undolog.com</a></p><ul><li><p><strong>FinderGit</strong> &#8212; a Git-aware file browser for macOS &#183; <a href="https://findergit.app">findergit.app</a></p></li><li><p><strong>Netfox</strong> &#8212; a network monitor for macOS &#183; <a href="https://netfox.app">netfox.app</a></p></li><li><p><strong>octoscope</strong> &#8212; a terminal dashboard for GitHub &#183; <a href="https://gfazioli.github.io/octoscope">gfazioli.github.io/octoscope</a></p></li><li><p><strong>Mantine extensions</strong> &#8212; 26 components on npm &#183; <a href="https://mantine-extensions.vercel.app">mantine-extensions.vercel.app</a></p></li><li><p><strong>Raycast extensions</strong> &#8212; for the Raycast launcher &#183; <a href="https://www.raycast.com/Undolog">raycast.com/Undolog</a></p></li></ul>]]></content:encoded></item><item><title><![CDATA[Thirteen releases, and one idea running through them]]></title><description><![CDATA[FinderGit learned to stash, Netfox to judge your Wi-Fi, octoscope to tell GitHub's bad day from its own &#8212; and every one of them got better at saying what it hasn't checked.]]></description><link>https://www.undolog.com/p/thirteen-releases-and-one-idea-running</link><guid isPermaLink="false">https://www.undolog.com/p/thirteen-releases-and-one-idea-running</guid><dc:creator><![CDATA[Giovambattista Fazioli]]></dc:creator><pubDate>Mon, 31 Aug 2026 14:40:03 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!2p6P!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff1a08bf5-4058-4a92-8292-e134b7141205_2000x1282.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Two weeks: thirteen releases across four projects, and six Raycast extensions updated. The list is long, but the thing worth reading for is not any single feature &#8212; it is what turned up in project after project without being planned. A tool that stops claiming more than it has actually checked.</p><h2>FinderGit</h2><p><strong>Put the working tree down for a minute (0.28.0).</strong> There is a kind of interruption where a commit is the wrong answer &#8212; you are not finished, and a commit you would have to undo is worse than none. The Unstage tab now carries a <strong>Stash&#8230;</strong> button beside Stage All and Discard All. Give the stash a message or let git name it after your last commit; untracked files come along, so what you get back is a genuinely clean tree rather than a half-tidied one.</p><p>Everything you have set aside is listed under the commit box with the branch it came from and how long it has been waiting &#8212; because the stash that matters is the one you forgot about, and &#8220;last month, on a branch you have since deleted&#8221; is the sentence that tells you what to do with it. Pop, Apply, Drop. A pop that hits a conflict leaves the conflict in your files and <strong>the stash still on the list</strong>.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!2p6P!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff1a08bf5-4058-4a92-8292-e134b7141205_2000x1282.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!2p6P!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff1a08bf5-4058-4a92-8292-e134b7141205_2000x1282.png 424w, https://substackcdn.com/image/fetch/$s_!2p6P!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff1a08bf5-4058-4a92-8292-e134b7141205_2000x1282.png 848w, https://substackcdn.com/image/fetch/$s_!2p6P!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff1a08bf5-4058-4a92-8292-e134b7141205_2000x1282.png 1272w, https://substackcdn.com/image/fetch/$s_!2p6P!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff1a08bf5-4058-4a92-8292-e134b7141205_2000x1282.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!2p6P!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff1a08bf5-4058-4a92-8292-e134b7141205_2000x1282.png" width="1456" height="933" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f1a08bf5-4058-4a92-8292-e134b7141205_2000x1282.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:933,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;The Unstage tab with the stash list open under the commit box: two entries, one badged Automatic, each showing its branch and age, with Pop, Apply and Drop on the right&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="The Unstage tab with the stash list open under the commit box: two entries, one badged Automatic, each showing its branch and age, with Pop, Apply and Drop on the right" title="The Unstage tab with the stash list open under the commit box: two entries, one badged Automatic, each showing its branch and age, with Pop, Apply and Drop on the right" srcset="https://substackcdn.com/image/fetch/$s_!2p6P!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff1a08bf5-4058-4a92-8292-e134b7141205_2000x1282.png 424w, https://substackcdn.com/image/fetch/$s_!2p6P!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff1a08bf5-4058-4a92-8292-e134b7141205_2000x1282.png 848w, https://substackcdn.com/image/fetch/$s_!2p6P!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff1a08bf5-4058-4a92-8292-e134b7141205_2000x1282.png 1272w, https://substackcdn.com/image/fetch/$s_!2p6P!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff1a08bf5-4058-4a92-8292-e134b7141205_2000x1282.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">The Unstage tab with the stash list open under the commit box: two entries, one badged Automatic, each showing its branch and age, with Pop, Apply and Drop on the right</figcaption></figure></div><p><strong>And a failed action no longer goes quiet (0.28.0).</strong> When a git action failed, the red warning in the action bar could vanish a second or two later &#8212; on exactly the actions that had changed something on disk, because re-reading the repository overwrote the message with &#8220;nothing wrong here&#8221;. The failure now outlives every refresh, which is what &#8220;Last action failed&#8221; always claimed to mean.</p><p><strong>Codeberg, with nothing to set up (0.27.0) &#8212; and then the part that did not work (0.27.1).</strong> Codeberg answers questions about public repositories without asking who is asking, so FinderGit can simply ask: issues, pull requests, stars, description, topics, latest release, no account and no token. Except in the file browser, where those columns stayed empty and the fork badge never appeared &#8212; two checks asked &#8220;is this GitHub?&#8221; before the new per-host routing ever got a say. Fixed the next day. If you installed 0.27.0 and wondered why the columns were blank on Codeberg: that was this, not your setup.</p><p>The same release stopped repositories on GitLab and Bitbucket from borrowing another repository&#8217;s numbers. A project mirrored under the same owner and name on two hosts could show counts, issues and pull requests belonging to the <em>other</em> one; those columns now stay empty rather than confidently wrong.</p><p><strong>A slow git operation can be stopped (0.26.0).</strong> Three seconds in, Fetch, Pull and Push turn into a stop control &#8212; and the app tells you what stopping left behind, which is the part that actually matters. A stopped fetch is harmless. A stopped push leaves the remote updated or untouched, never half-way. A stopped pull is the one to know about: if it had started merging you may be left mid-merge, and FinderGit notices and offers Finish and Abort. The three-second delay is deliberate &#8212; without it, a double-click on Fetch would cancel the fetch you just started.</p><p>Every release page now also publishes the SHA-256 of its DMG, back to 0.4.1. If a virus scanner ever flags a download, &#8220;is this really the file that was published?&#8221; is a question that should not depend on asking me.</p><p>https://findergit.app</p><h2>Netfox</h2><p><strong>Is my Wi-Fi the problem? (0.17.0)</strong> The Wi-Fi tab always showed you the numbers. It never told you what they meant. Open the network you are connected to and it now answers in a sentence: the link looks fine, the channel is busy, the signal is weak, or both at once &#8212; which matters, because a busy channel and a weak signal call for opposite moves and the numbers alone never said which you had.</p><p>Behind the verdict: how many networks share your stretch of spectrum, how many of them are close enough in strength to genuinely compete for the channel, and your signal-to-noise ratio. It names a quieter channel when one exists, judged by the width <em>your</em> access point would carry there, and counts an 80 MHz neighbour on channel 36 as the four channels it really occupies.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!npsg!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00850343-c697-4dc7-be2b-84ce64fc0eb0_2000x1310.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!npsg!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00850343-c697-4dc7-be2b-84ce64fc0eb0_2000x1310.png 424w, https://substackcdn.com/image/fetch/$s_!npsg!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00850343-c697-4dc7-be2b-84ce64fc0eb0_2000x1310.png 848w, https://substackcdn.com/image/fetch/$s_!npsg!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00850343-c697-4dc7-be2b-84ce64fc0eb0_2000x1310.png 1272w, https://substackcdn.com/image/fetch/$s_!npsg!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00850343-c697-4dc7-be2b-84ce64fc0eb0_2000x1310.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!npsg!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00850343-c697-4dc7-be2b-84ce64fc0eb0_2000x1310.png" width="1456" height="954" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/00850343-c697-4dc7-be2b-84ce64fc0eb0_2000x1310.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:954,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;The Wi-Fi tab with the verdict open: \&quot;This channel is busy\&quot;, the three numbers behind it &#8212; 16 sharing your spectrum, 5 close enough to compete, 39 dB SNR &#8212; and a chip per channel in the band&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="The Wi-Fi tab with the verdict open: &quot;This channel is busy&quot;, the three numbers behind it &#8212; 16 sharing your spectrum, 5 close enough to compete, 39 dB SNR &#8212; and a chip per channel in the band" title="The Wi-Fi tab with the verdict open: &quot;This channel is busy&quot;, the three numbers behind it &#8212; 16 sharing your spectrum, 5 close enough to compete, 39 dB SNR &#8212; and a chip per channel in the band" srcset="https://substackcdn.com/image/fetch/$s_!npsg!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00850343-c697-4dc7-be2b-84ce64fc0eb0_2000x1310.png 424w, https://substackcdn.com/image/fetch/$s_!npsg!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00850343-c697-4dc7-be2b-84ce64fc0eb0_2000x1310.png 848w, https://substackcdn.com/image/fetch/$s_!npsg!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00850343-c697-4dc7-be2b-84ce64fc0eb0_2000x1310.png 1272w, https://substackcdn.com/image/fetch/$s_!npsg!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00850343-c697-4dc7-be2b-84ce64fc0eb0_2000x1310.png 1456w" sizes="auto, 100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">The Wi-Fi tab with the verdict open: &#8220;This channel is busy&#8221;, the three numbers behind it &#8212; 16 sharing your spectrum, 5 close enough to compete, 39 dB SNR &#8212; and a chip per channel in the band</figcaption></figure></div><p>The competing-neighbour threshold is measured rather than chosen: in a real dense environment &#8212; 33 access points visible at 5 GHz, median &#8722;85 dBm against a &#8722;58 dBm link &#8212; the count sat flat at 2 all the way out to 15 dB and only then climbed. And where macOS gives no noise reading, the signal half is reported as <strong>unknown</strong> rather than assumed to be good. Nothing new is probed for any of it; it all comes from the scan the tab already performed.</p><p>A hidden network is now explained rather than merely labelled, too. Withholding the name is widely believed to be a security measure and is not one &#8212; the devices that already know the network broadcast its name while they look for it.</p><p><strong>Free a port, without a terminal (0.17.0).</strong> Netfox would show you that something was holding port 3000; the next step was still <code>kill</code>. Each Local Services row now asks that process to quit &#8212; politely first, so a dev server can save what it was holding, then offering to force the matter. It asks the whole process family, not just the one that opened the socket, and it re-reads the port immediately before sending anything: PIDs get reused, and a row is a snapshot of a moment that has already passed.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!nYe8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F301205bd-898e-4ea4-bae1-ceb74f489305_2000x1310.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!nYe8!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F301205bd-898e-4ea4-bae1-ceb74f489305_2000x1310.png 424w, https://substackcdn.com/image/fetch/$s_!nYe8!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F301205bd-898e-4ea4-bae1-ceb74f489305_2000x1310.png 848w, https://substackcdn.com/image/fetch/$s_!nYe8!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F301205bd-898e-4ea4-bae1-ceb74f489305_2000x1310.png 1272w, https://substackcdn.com/image/fetch/$s_!nYe8!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F301205bd-898e-4ea4-bae1-ceb74f489305_2000x1310.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!nYe8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F301205bd-898e-4ea4-bae1-ceb74f489305_2000x1310.png" width="1456" height="954" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/301205bd-898e-4ea4-bae1-ceb74f489305_2000x1310.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:954,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;The Local Services tab: every listener on this Mac with its port, a quit control at the end of each row, and the hosts names stated once in the group heading rather than repeated on every line&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="The Local Services tab: every listener on this Mac with its port, a quit control at the end of each row, and the hosts names stated once in the group heading rather than repeated on every line" title="The Local Services tab: every listener on this Mac with its port, a quit control at the end of each row, and the hosts names stated once in the group heading rather than repeated on every line" srcset="https://substackcdn.com/image/fetch/$s_!nYe8!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F301205bd-898e-4ea4-bae1-ceb74f489305_2000x1310.png 424w, https://substackcdn.com/image/fetch/$s_!nYe8!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F301205bd-898e-4ea4-bae1-ceb74f489305_2000x1310.png 848w, https://substackcdn.com/image/fetch/$s_!nYe8!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F301205bd-898e-4ea4-bae1-ceb74f489305_2000x1310.png 1272w, https://substackcdn.com/image/fetch/$s_!nYe8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F301205bd-898e-4ea4-bae1-ceb74f489305_2000x1310.png 1456w" sizes="auto, 100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">The Local Services tab: every listener on this Mac with its port, a quit control at the end of each row, and the hosts names stated once in the group heading rather than repeated on every line</figcaption></figure></div><p><strong>One device, one icon (0.17.1).</strong> The same device could carry a different icon in the device list, in its own alerts, in its History window and in the tag editor &#8212; and could change between launches with no setting touched. Three separate causes, all closed: a stored category decided by whichever concurrent discovery answered first (a HomePod advertises both an audio and a display service, so &#8220;first&#8221; was genuinely random), three surfaces reading the raw stored value instead of the one the list shows, and alerts whose snapshots had dropped the model and the advertised services. Some devices also answer &#8220;what are you?&#8221; with an internal board code; where Netfox recognises the code, the Model row now reads <em>Apple TV</em> instead of a string of letters and digits.</p><p><strong>A hosts name belongs to the address, not to each service (0.16.6).</strong> One name pointing at <code>127.0.0.1</code> reaches everything listening there, so it was printed beside every service &#8212; true of all of them, useful for telling apart none. Those names are now stated once, in the group heading.</p><p>https://netfox.app</p><h2>octoscope</h2><p><strong>Four things that used to send you to a browser (0.29.0).</strong> Gists, with the file contents syntax-highlighted and <code>c</code> copying the <em>code</em> rather than the link. An activity feed beside the contribution heatmap &#8212; pushes, pull requests, reviews, issues, releases, branches &#8212; because the heatmap answers <em>how much</em> and the feed answers <em>what</em>. Sponsors received and given. And your real GitHub inbox: mentions, review requests, assignments, CI. <code>s</code> cuts the noise, and that key exists because of a measurement rather than a preference: of 104 notifications on a real account, <strong>77 were CI activity</strong>.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!8Bw2!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa66f5e01-cdd8-46c3-822f-8174aa638f1a_1400x900.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!8Bw2!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa66f5e01-cdd8-46c3-822f-8174aa638f1a_1400x900.png 424w, https://substackcdn.com/image/fetch/$s_!8Bw2!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa66f5e01-cdd8-46c3-822f-8174aa638f1a_1400x900.png 848w, https://substackcdn.com/image/fetch/$s_!8Bw2!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa66f5e01-cdd8-46c3-822f-8174aa638f1a_1400x900.png 1272w, https://substackcdn.com/image/fetch/$s_!8Bw2!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa66f5e01-cdd8-46c3-822f-8174aa638f1a_1400x900.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!8Bw2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa66f5e01-cdd8-46c3-822f-8174aa638f1a_1400x900.png" width="1400" height="900" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a66f5e01-cdd8-46c3-822f-8174aa638f1a_1400x900.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:900,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;octoscope 0.29.0 on the new Gists tab &#8212; visibility, file count, stars and last change per gist, with enter drilling into the file contents&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="octoscope 0.29.0 on the new Gists tab &#8212; visibility, file count, stars and last change per gist, with enter drilling into the file contents" title="octoscope 0.29.0 on the new Gists tab &#8212; visibility, file count, stars and last change per gist, with enter drilling into the file contents" srcset="https://substackcdn.com/image/fetch/$s_!8Bw2!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa66f5e01-cdd8-46c3-822f-8174aa638f1a_1400x900.png 424w, https://substackcdn.com/image/fetch/$s_!8Bw2!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa66f5e01-cdd8-46c3-822f-8174aa638f1a_1400x900.png 848w, https://substackcdn.com/image/fetch/$s_!8Bw2!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa66f5e01-cdd8-46c3-822f-8174aa638f1a_1400x900.png 1272w, https://substackcdn.com/image/fetch/$s_!8Bw2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa66f5e01-cdd8-46c3-822f-8174aa638f1a_1400x900.png 1456w" sizes="auto, 100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">octoscope 0.29.0 on the new Gists tab &#8212; visibility, file count, stars and last change per gist, with enter drilling into the file contents</figcaption></figure></div><p><strong>When it&#8217;s GitHub, octoscope says so (0.30.0).</strong> octoscope does nothing but talk to GitHub, so when GitHub degrades every symptom &#8212; a timeout, an empty tab, a 502, a scan that will not finish &#8212; is indistinguishable from octoscope being broken. It now asks GitHub&#8217;s own status page and tells you which it is, at the moment you are deciding who to blame. It stays silent while GitHub is healthy, and silent again if the status page itself cannot be reached: it never renders a green &#8220;all systems operational&#8221;, because that would be a clean bill of health it has not verified.</p><p>Three more of exactly that shape shipped in the same release, and all three came from a comment under the 0.27.0 launch &#8212; someone who did not ask for features, but found three places where the report said more than it had checked. The scan now states the window it compared against (<em>&#8220;compared against a baseline recorded 29 days ago&#8221;</em>), including where <strong>nothing</strong> changed, which is the case that matters most and the one a per-finding note could never reach. A <code>uses:</code> into a repository octoscope did not read is now always disclosed, not only where an outsider could reach the caller. And a file that changes back &#8212; A &#8594; B &#8594; A &#8212; is recognised as a return instead of printing a hash nothing remembered.</p><p><strong>And the tab that pushed everything off screen (0.30.1).</strong> On 0.30.0, opening What&#8217;s new carried the banner, the profile card and the tab bar off the top of the terminal, with no way to scroll them back. Reported with a screenshot, which is the only way this kind of defect gets found. Two faults, different in kind: the entry was the longest ever shipped, and the tab had never been able to scroll at all &#8212; the only long static surface in octoscope without a viewport. The shorter text alone would not have fixed it, so there is now a test that measures every What&#8217;s new entry against a line budget.</p><p><a href="https://gfazioli.github.io/octoscope">https://gfazioli.github.io/octoscope</a> &#183; <code>brew upgrade gfazioli/tap/octoscope</code></p><h2>Mantine extensions</h2><p><strong>mantine-json-tree 3.4.0 &#8212; the JSON viewer you can type into.</strong> Opt-in <code>editable</code>: click a string or a number for an inline editor, click a boolean and it toggles outright since there is nothing to type. The component stays <strong>controlled</strong> &#8212; it holds no copy of your data &#8212; and <code>onChange</code> hands you the new data plus a description of what changed. Writes preserve identity: only the spine down to the target is rebuilt, so a <code>Date</code>, <code>Map</code>, <code>Set</code>, <code>RegExp</code>, <code>BigInt</code> or React element elsewhere in the tree survives an edit unchanged, where a <code>structuredClone</code> would have flattened every one of them. Nodes with no real address &#8212; the children of a <code>Map</code> or a <code>Set</code> &#8212; can never be edited, whatever you configure. Renaming keys and adding, removing or reordering nodes are deliberately out: they need a different path format, which is a larger piece of work rather than more of this one.</p><p><strong>mantine-json-tree 3.3.0 &#8212; three ways the tree could hide your data.</strong> A circular reference crashed the component and took the whole page with it; repeats now render as <code>[Circular]</code>. Plain data shaped like <code>{ type, props }</code> &#8212; an everyday shape in form-builder and low-code JSON &#8212; was mistaken for a React element and collapsed into <code>&lt;Component /&gt;</code>, hiding its entire subtree; elements are now identified by their <code>$$typeof</code> marker alone. And <code>Ctrl</code>/<code>Cmd+C</code> copied the root instead of the focused node, including while you were trying to copy text out of the search field.</p><p><strong>mantine-border-animate 3.0.0 &#8212; a border that can measure something.</strong> <code>variant="draw"</code> with <code>progress</code> draws that share of the real perimeter, corners included, so 50% is exactly half the way around whatever the aspect ratio is. A <code>dash</code> variant whose pattern is expressed in perimeter percentages rather than pixels, so it stays even at any size. A <code>comet</code> beam with a fading tail at constant speed on any shape. And <code>trigger</code> &#8212; <code>hover</code>, <code>focus-within</code>, <code>inView</code> &#8212; so a border can wait for an interaction instead of looping forever; <code>focus-within</code> is the answer for form fields, with no state to wire up.</p><p>All three stroke-based additions come from one primitive, a <code>&lt;rect&gt;</code> with <code>pathLength="100"</code>, checked across Chrome, Firefox and Safari <em>before</em> the API was designed around it &#8212; so there is no <code>ResizeObserver</code> and no JavaScript per frame. It is a major for a reason: four props were renamed (<code>angle &#8594; progress</code>, divide the old value by 3.6; <code>delay &#8594; phase</code>; <code>size &#8594; spread</code>; two <code>beamMode</code> values), each with a one-time development warning and an upgrade guide.</p><p>Both have a live demo with every variant on it, which is a better argument than any paragraph here: <a href="https://gfazioli.github.io/mantine-json-tree">mantine-json-tree</a> &#183; <a href="https://gfazioli.github.io/mantine-border-animate">mantine-border-animate</a></p><h2>The Raycast extensions</h2><p>The whole family moved to <strong>Raycast API 2.x</strong>, and the menu-bar icons that broke on Raycast 2 work again. Two got rather more than a migration: <strong>macOS Tweaks</strong> gained a Windows &amp; Stage Manager category and 21 new tweaks, and <strong>App Updates</strong> gained release notes and a shared scan that makes the lists instant. Both are now usable from Raycast AI as tools, not only as commands.</p><p><a href="https://www.raycast.com/Undolog">https://www.raycast.com/Undolog</a></p><div><hr></div><p><strong>Undolog &#8212; open source studio.</strong> Every project in one place: <a href="https://gfazioli.github.io/">gfazioli.github.io</a> &#183; this newsletter lives at <a href="https://undolog.com">undolog.com</a></p><ul><li><p><strong>FinderGit</strong> &#8212; a Git-aware file browser for macOS &#183; <a href="https://findergit.app">findergit.app</a></p></li><li><p><strong>Netfox</strong> &#8212; a network monitor for macOS &#183; <a href="https://netfox.app">netfox.app</a></p></li><li><p><strong>octoscope</strong> &#8212; a terminal dashboard for GitHub &#183; <a href="https://gfazioli.github.io/octoscope">gfazioli.github.io/octoscope</a></p></li><li><p><strong>Mantine extensions</strong> &#8212; 26 components on npm &#183; <a href="https://mantine-extensions.vercel.app">mantine-extensions.vercel.app</a></p></li><li><p><strong>Raycast extensions</strong> &#8212; for the Raycast launcher &#183; <a href="https://www.raycast.com/Undolog">raycast.com/Undolog</a></p></li></ul>]]></content:encoded></item><item><title><![CDATA[mantine-onboarding-tour 4.0.8 — Popovers That Stay on Screen]]></title><description><![CDATA[A default 400px width cap on the tour popover &#8212; applied as a CSS class so it survives your own style overrides &#8212; fixes both runaway-width popovers and the "position looks broken" reports they caused,]]></description><link>https://www.undolog.com/p/mantine-onboarding-tour-408-popovers</link><guid isPermaLink="false">https://www.undolog.com/p/mantine-onboarding-tour-408-popovers</guid><dc:creator><![CDATA[Giovambattista Fazioli]]></dc:creator><pubDate>Sun, 12 Jul 2026 07:02:57 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!Pf4l!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F96399b8e-f8f7-437b-8507-869a50f91b15_1280x640.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Pf4l!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F96399b8e-f8f7-437b-8507-869a50f91b15_1280x640.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Pf4l!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F96399b8e-f8f7-437b-8507-869a50f91b15_1280x640.jpeg 424w, https://substackcdn.com/image/fetch/$s_!Pf4l!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F96399b8e-f8f7-437b-8507-869a50f91b15_1280x640.jpeg 848w, https://substackcdn.com/image/fetch/$s_!Pf4l!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F96399b8e-f8f7-437b-8507-869a50f91b15_1280x640.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!Pf4l!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F96399b8e-f8f7-437b-8507-869a50f91b15_1280x640.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Pf4l!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F96399b8e-f8f7-437b-8507-869a50f91b15_1280x640.jpeg" width="1280" height="640" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/96399b8e-f8f7-437b-8507-869a50f91b15_1280x640.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:640,&quot;width&quot;:1280,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:237599,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.undolog.com/i/206666648?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F96399b8e-f8f7-437b-8507-869a50f91b15_1280x640.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Pf4l!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F96399b8e-f8f7-437b-8507-869a50f91b15_1280x640.jpeg 424w, https://substackcdn.com/image/fetch/$s_!Pf4l!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F96399b8e-f8f7-437b-8507-869a50f91b15_1280x640.jpeg 848w, https://substackcdn.com/image/fetch/$s_!Pf4l!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F96399b8e-f8f7-437b-8507-869a50f91b15_1280x640.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!Pf4l!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F96399b8e-f8f7-437b-8507-869a50f91b15_1280x640.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>Introduction</h2><p>Two bug reports landed within days of each other. One said the tour popover ignored the <code>position</code> prop and clipped off the left edge of the screen. The other said long content blew the popover past the viewport, with no obvious way to rein it in. They looked like two different problems &#8212; until they turned out to be the same missing constraint. Version 4.0.8 adds it, and both reports closed with a single fix.</p><h2>What&#8217;s New</h2><h3>A sensible default width for the tour popover</h3><p>The popover dropdown now has a default <code>max-width</code><strong> of </strong><code>400px</code>. Previously it grew with <code>width: max-content</code> and no ceiling, so wide or non-wrapping content &#8212; an image, a <code>&lt;Code&gt;</code> block, a fixed-width layout &#8212; could push it well past the edge of the screen.</p><p>The cap is applied as a <strong>CSS class</strong> on the dropdown rather than inline styles, which matters more than it sounds: it survives any <code>popoverProps.styles.dropdown</code> override of <em>other</em> properties (padding, background, and so on). You only change the width when you explicitly ask to:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;tsx&quot;,&quot;nodeId&quot;:&quot;43648b55-627a-4381-a60b-abd6817ccd64&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-tsx">&lt;OnboardingTour
  focusRevealProps={{
    popoverProps: {
      // Narrower than the 400px cap:
      width: 320,
      // Or lift the cap for a wider popover (width alone can't exceed max-width):
      styles: { dropdown: { maxWidth: 560 } },
    },
  }}
  tour={steps}
&gt;
  {/* ... */}
&lt;/OnboardingTour&gt;</code></pre></div><h3>The &#8220;broken position&#8221; was really a width problem</h3><p>The second report &#8212; a popover that ignored <code>position</code> and stuck to the left edge &#8212; had the same root cause. While a tour is active, the component sets <code>overflow-x: hidden</code> on the document to keep portal-rendered popovers from creating horizontal scroll. An unconstrained, too-wide dropdown positioned to the left of a left-edge target had nowhere to go, and the hidden overflow clipped it. Floating UI&#8217;s <code>shift</code> middleware can move a popover, but it can&#8217;t shrink one. Cap the width, and positioning behaves again &#8212; with no <code>position</code> changes required.</p><h3>Consistent positioning across Mantine versions</h3><p>Mantine 9.3 flipped the <code>Popover</code> default for <code>preventPositionChangeWhenVisible</code> to <code>true</code>, which pins a popover to the side it opened on and stops it from flipping or shifting while it stays visible. The tour scrolls targets around between steps and relies on continuous re-positioning, so 4.0.8 sets <code>preventPositionChangeWhenVisible: false</code> in its default <code>popoverProps</code>. The behavior is now identical whether you&#8217;re on Mantine 9.2 or 9.3+, and you can still opt into the pinned behavior per tour or per step:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;tsx&quot;,&quot;nodeId&quot;:&quot;d2b77b7d-f399-4aba-965d-45d853ed9a2a&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-tsx">&lt;OnboardingTour
  focusRevealProps={{ popoverProps: { preventPositionChangeWhenVisible: true } }}
  tour={steps}
&gt;
  {/* ... */}
&lt;/OnboardingTour&gt;</code></pre></div><h2>Improvements</h2><h3>Step-level popover config no longer overrides tour-level settings</h3><p>Tour-level and step-level <code>focusRevealProps</code> are now deep-merged for <code>popoverProps</code> and <code>overlayProps</code>. Before, a step that set its own <code>popoverProps</code> replaced the tour-level object wholesale &#8212; so a tour-wide position or style silently vanished for that step. Now a step only overrides the keys it actually sets:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;tsx&quot;,&quot;nodeId&quot;:&quot;abbc2535-db44-4748-8fee-f2cb11ca25d2&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-tsx">&lt;OnboardingTour
  focusRevealProps={{ popoverProps: { position: 'bottom', shadow: 'xl' } }}
  tour={[
    {
      id: 'step-2',
      title: 'Just this step',
      // Only `position` changes here &#8212; the tour-level `shadow: 'xl'` is kept.
      focusRevealProps: { popoverProps: { position: 'right' } },
    },
  ]}
&gt;
  {/* ... */}
&lt;/OnboardingTour&gt;</code></pre></div><h2>Getting Started</h2><p>This is a drop-in patch &#8212; no API changes, nothing to migrate.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:&quot;fba2b69e-6ef6-4c6a-acf6-5f3150ffdaea&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">yarn add @gfazioli/mantine-onboarding-tour@4</code></pre></div><p>Compatible with <strong>Mantine 9.x</strong> and <strong>React 18/19</strong> (the peer range stays <code>&gt;=9.0.0</code>). If you were hard-capping the popover width yourself as a workaround, you can drop it &#8212; or keep it: an explicit <code>styles.dropdown.maxWidth</code> or <code>width</code> still wins over the new default.</p><p>New docs: the <strong>Popover width</strong> section (with a runnable demo) and the <code>preventPositionChangeWhenVisible</code> note in <strong>Scroll Behavior</strong>.</p><h2>Links</h2><ul><li><p>&#128230; npm: <a href="https://www.npmjs.com/package/@gfazioli/mantine-onboarding-tour">https://www.npmjs.com/package/@gfazioli/mantine-onboarding-tour</a></p></li><li><p>&#128214; Docs: <a href="https://gfazioli.github.io/mantine-onboarding-tour">https://gfazioli.github.io/mantine-onboarding-tour</a></p></li><li><p>&#128187; GitHub: <a href="https://github.com/gfazioli/mantine-onboarding-tour">https://github.com/gfazioli/mantine-onboarding-tour</a></p></li><li><p>&#129513; Mantine Extensions Hub: https://mantine-extensions.vercel.app/</p></li></ul>]]></content:encoded></item><item><title><![CDATA[The popover that talks to a window that doesn't exist yet]]></title><description><![CDATA[How to make a SwiftUI MenuBarExtra deep-link into your main window &#8212; even when that window has been closed &#8212; without focused-scene-values, which don't cross scenes.]]></description><link>https://www.undolog.com/p/the-popover-that-talks-to-a-window</link><guid isPermaLink="false">https://www.undolog.com/p/the-popover-that-talks-to-a-window</guid><dc:creator><![CDATA[Giovambattista Fazioli]]></dc:creator><pubDate>Wed, 10 Jun 2026 08:06:11 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!btAw!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59afc7c5-29b4-488e-9734-1a2b349b6f2e_812x876.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!btAw!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59afc7c5-29b4-488e-9734-1a2b349b6f2e_812x876.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!btAw!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59afc7c5-29b4-488e-9734-1a2b349b6f2e_812x876.png 424w, https://substackcdn.com/image/fetch/$s_!btAw!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59afc7c5-29b4-488e-9734-1a2b349b6f2e_812x876.png 848w, https://substackcdn.com/image/fetch/$s_!btAw!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59afc7c5-29b4-488e-9734-1a2b349b6f2e_812x876.png 1272w, https://substackcdn.com/image/fetch/$s_!btAw!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59afc7c5-29b4-488e-9734-1a2b349b6f2e_812x876.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!btAw!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59afc7c5-29b4-488e-9734-1a2b349b6f2e_812x876.png" width="1200" height="1294.5812807881773" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/59afc7c5-29b4-488e-9734-1a2b349b6f2e_812x876.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:876,&quot;width&quot;:812,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:196812,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.undolog.com/i/201150849?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59afc7c5-29b4-488e-9734-1a2b349b6f2e_812x876.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-large" alt="" srcset="https://substackcdn.com/image/fetch/$s_!btAw!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59afc7c5-29b4-488e-9734-1a2b349b6f2e_812x876.png 424w, https://substackcdn.com/image/fetch/$s_!btAw!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59afc7c5-29b4-488e-9734-1a2b349b6f2e_812x876.png 848w, https://substackcdn.com/image/fetch/$s_!btAw!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59afc7c5-29b4-488e-9734-1a2b349b6f2e_812x876.png 1272w, https://substackcdn.com/image/fetch/$s_!btAw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59afc7c5-29b4-488e-9734-1a2b349b6f2e_812x876.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>I build <a href="https://netfox.app/">Netfox</a>, a macOS network monitor. It lives in two places at once: a full window, and a menu bar popover that shows your network at a glance &#8212; devices online, risk level, public IP, a little live traffic chart.</p><p>The popover has four status cards. Someone asked the obvious question: <em>&#8220;can I click a card and have it open that section in the main window?&#8221;</em></p><p>Sure. Easy. Except it wasn&#8217;t, and the reason why is a nice little lesson about how SwiftUI scenes are isolated from each other.</p><h2><strong>Why the obvious approach doesn&#8217;t work</strong></h2><p>In a single-scene SwiftUI app, the way you&#8217;d wire a &#8220;menu command triggers an action in a view&#8221; is <code>@FocusedValue</code> / <code>.focusedSceneValue</code>. The view publishes a closure; a command elsewhere reads it. Clean.</p><p>But a <code>MenuBarExtra</code> is its <strong>own scene</strong>, separate from your <code>WindowGroup</code>/<code>Window</code>. Focused-scene-values are scoped to a scene tree &#8212; they don&#8217;t cross from the popover scene into the main window scene. So the popover literally cannot reach the main window&#8217;s selection state that way.</p><p>And it gets worse. My app is &#8220;dual-mode&#8221;: closing the main window doesn&#8217;t quit it (the <code>MenuBarExtra</code> scene keeps it alive). So at the moment a popover card is clicked, <strong>the main window might not exist at all.</strong> There&#8217;s no view to send a message to.</p><p>You can&#8217;t message a window that isn&#8217;t there.</p><h2><strong>The shape of the fix: a tiny shared router</strong></h2><p>The trick is to stop thinking &#8220;popover &#8594; window&#8221; and start thinking &#8220;popover &#8594; shared state &#8594; window, whenever it shows up.&#8221;</p><p>A ten-line <code>@Observable</code> singleton both scenes can see:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;swift&quot;,&quot;nodeId&quot;:&quot;28e1e6c2-9341-4519-9819-96315cefded5&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-swift">import Observation
&#8203;
@MainActor
@Observable
final class ShellRouter {
    static let shared = ShellRouter()
&#8203;
    /// Section the shell should switch to at the next opportunity.
    /// `nil` = nothing pending.
    var pendingSection: AppSection?
&#8203;
    private init() {}
}</code></pre></div><p>That&#8217;s the whole bridge. The popover writes to it; the window reads from it.</p><h2><strong>The popover side: queue, open, dismiss</strong></h2><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;swift&quot;,&quot;nodeId&quot;:&quot;1dea45b4-62d1-45f4-9296-f521b9fa332f&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-swift">private func openSection(_ section: AppSection) {
    ShellRouter.shared.pendingSection = section   // queue the destination
    openMainWindow()                              // raise / recreate the window
    dismiss()                                     // close the popover
}</code></pre></div><p><code>openMainWindow()</code> is just <code>openWindow(id: "main")</code> plus <code>NSApp.activate(...)</code>. If the window was closed, <code>openWindow</code> recreates it. If it was already open, it comes forward. Either way, a fresh <code>pendingSection</code> is sitting in the router waiting to be consumed.</p><h2><strong>The window side: the part everyone gets wrong</strong></h2><p>Here&#8217;s the subtlety. You need to consume <code>pendingSection</code> in <strong>two</strong> places, because there are two completely different timelines:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;swift&quot;,&quot;nodeId&quot;:&quot;bb4d26a4-9709-4c45-9600-93cfdb1aae4f&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-swift">NavigationSplitView { ... } detail: { ... }
    // Timeline A: the window was CLOSED and just got recreated.
    // Its .task runs on mount &#8212; and a value set *before* mount
    // can&#8217;t be observed by .onChange, because .onChange only fires
    // on *changes that happen while mounted*.
    .task { consumePendingSection() }
&#8203;
    // Timeline B: the window was ALREADY OPEN. The popover sets
    // pendingSection while the shell is mounted &#8212; .onChange catches it.
    .onChange(of: router.pendingSection) { _, _ in
        consumePendingSection()
    }</code></pre></div><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;swift&quot;,&quot;nodeId&quot;:&quot;3475beb7-0373-4435-a17b-9b510b75d14c&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-swift">private func consumePendingSection() {
    guard let section = router.pendingSection else { return }
    selectedSection = section
    router.pendingSection = nil          // &#8592; clear it. this matters.
}</code></pre></div><p>If you only use <code>.onChange</code>, the window-was-closed case silently does nothing: the value was set before the view existed, so there&#8217;s no &#8220;change&#8221; to observe &#8212; <code>.task</code> is your only hook there. If you only use <code>.task</code>, the window-already-open case does nothing: the view&#8217;s already mounted, <code>.task</code> already ran.</p><p>You need both. They cover disjoint timelines.</p><h2><strong>Why the clear is load-bearing</strong></h2><p><code>router.pendingSection = nil</code> after consuming isn&#8217;t tidiness &#8212; it&#8217;s correctness. Without it:</p><ol><li><p>You click &#8220;Security&#8221; in the popover &#8594; window opens on Security. Good.</p></li><li><p>You close the window.</p></li><li><p>A week later you reopen the window normally (from the Dock, not a popover card).</p></li><li><p><code>.task</code> fires, finds the stale <code>pendingSection</code> still pointing at Security, and yanks you to Security instead of your default tab.</p></li></ol><p>Clearing on consume makes the destination strictly one-shot.</p><h2><strong>The takeaway</strong></h2><p>When two SwiftUI scenes need to talk and one of them might not be alive yet, don&#8217;t look for a direct channel &#8212; there isn&#8217;t one. Put the intent in shared observable state, and have the receiver drain it both <strong>on appearance</strong> (for the just-created case) and <strong>on change</strong> (for the already-alive case). Then clear it, so intent doesn&#8217;t outlive its moment.</p><p>It&#8217;s the same idea as a message queue with at-most-once delivery, shrunk down to a single <code>@Observable</code> property. Sometimes the smallest abstraction is the right one.</p>]]></content:encoded></item><item><title><![CDATA[FinderGit 0.8.0 is out 🎉]]></title><description><![CDATA[The headline: in-app file preview.]]></description><link>https://www.undolog.com/p/findergit-080-is-out</link><guid isPermaLink="false">https://www.undolog.com/p/findergit-080-is-out</guid><dc:creator><![CDATA[Giovambattista Fazioli]]></dc:creator><pubDate>Tue, 02 Jun 2026 13:24:24 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!cLsR!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F573ebfd6-49aa-41be-953e-6daa398b7dcf_3072x1886.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!cLsR!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F573ebfd6-49aa-41be-953e-6daa398b7dcf_3072x1886.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!cLsR!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F573ebfd6-49aa-41be-953e-6daa398b7dcf_3072x1886.png 424w, https://substackcdn.com/image/fetch/$s_!cLsR!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F573ebfd6-49aa-41be-953e-6daa398b7dcf_3072x1886.png 848w, https://substackcdn.com/image/fetch/$s_!cLsR!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F573ebfd6-49aa-41be-953e-6daa398b7dcf_3072x1886.png 1272w, https://substackcdn.com/image/fetch/$s_!cLsR!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F573ebfd6-49aa-41be-953e-6daa398b7dcf_3072x1886.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!cLsR!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F573ebfd6-49aa-41be-953e-6daa398b7dcf_3072x1886.png" width="1456" height="894" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/573ebfd6-49aa-41be-953e-6daa398b7dcf_3072x1886.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:894,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:269281,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.undolog.com/i/200295762?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F573ebfd6-49aa-41be-953e-6daa398b7dcf_3072x1886.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!cLsR!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F573ebfd6-49aa-41be-953e-6daa398b7dcf_3072x1886.png 424w, https://substackcdn.com/image/fetch/$s_!cLsR!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F573ebfd6-49aa-41be-953e-6daa398b7dcf_3072x1886.png 848w, https://substackcdn.com/image/fetch/$s_!cLsR!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F573ebfd6-49aa-41be-953e-6daa398b7dcf_3072x1886.png 1272w, https://substackcdn.com/image/fetch/$s_!cLsR!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F573ebfd6-49aa-41be-953e-6daa398b7dcf_3072x1886.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The headline: in-app file preview. Hit Space (or &#8984;Y) on any file and it opens right inside the window &#8212; no bouncing to a separate panel:</p><p>&#8226; Source code with full syntax highlighting, across dozens of languages, themed for light &amp; dark</p><p>&#8226; Markdown rendered formatted on a clean background</p><p>&#8226; Images, PDFs and media in a native in-window preview</p><p>A proper Finder-style toggle &#8212; Space opens and closes, Esc dismisses.</p><p>Also new: a Sponsor link in the Help menu + About tab if you&#8217;d like to back development, and the What&#8217;s New panel now shows on every update.</p><p>Free, notarized, universal (Apple Silicon + Intel), macOS 15+.</p><p>&#128073; https://findergit.app</p>]]></content:encoded></item><item><title><![CDATA[octoscope v0.15.0 — Notified when it matters 🔔]]></title><description><![CDATA[Two additions that close the loop between watching (what v0.14.0 widened) and noticing (what&#8217;s actually waiting for your attention).]]></description><link>https://www.undolog.com/p/octoscope-v0150-notified-when-it</link><guid isPermaLink="false">https://www.undolog.com/p/octoscope-v0150-notified-when-it</guid><dc:creator><![CDATA[Giovambattista Fazioli]]></dc:creator><pubDate>Thu, 28 May 2026 13:21:41 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!hpMX!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F03e63a70-2879-4eec-a93a-39f791632266_1536x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!hpMX!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F03e63a70-2879-4eec-a93a-39f791632266_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!hpMX!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F03e63a70-2879-4eec-a93a-39f791632266_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!hpMX!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F03e63a70-2879-4eec-a93a-39f791632266_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!hpMX!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F03e63a70-2879-4eec-a93a-39f791632266_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!hpMX!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F03e63a70-2879-4eec-a93a-39f791632266_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!hpMX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F03e63a70-2879-4eec-a93a-39f791632266_1536x1024.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/03e63a70-2879-4eec-a93a-39f791632266_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:93782,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.undolog.com/i/199600046?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F03e63a70-2879-4eec-a93a-39f791632266_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!hpMX!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F03e63a70-2879-4eec-a93a-39f791632266_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!hpMX!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F03e63a70-2879-4eec-a93a-39f791632266_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!hpMX!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F03e63a70-2879-4eec-a93a-39f791632266_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!hpMX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F03e63a70-2879-4eec-a93a-39f791632266_1536x1024.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Two additions that close the loop between watching (what v0.14.0 widened) and noticing (what&#8217;s actually waiting for your attention). One feature lands on screen, one in your macOS notification center; both keep octoscope read-only.</p><p><strong>Review-requests inbox</strong></p><p>The PRs tab now shows a sticky &#8220;PRs awaiting your review&#8221; section above your authored list &#8212; whenever someone has requested you as a reviewer. Same partition idiom as Repos pinned/watched (v0.13/v0.14): muted rule separator between segments, cursor walks both, sort cycle only re-orders the authored half (review-requests stay in GitHub&#8217;s <strong>UPDATED_AT DESC</strong> order), filter applies to both.</p><p><strong>Section is absent when count == 0</strong> &#8212; no rule, no header, the tab looks identical to pre-v0.15.0 if no one&#8217;s waiting on you. Discovery is via the README, not visual noise.</p><p>One GraphQL search call (<strong>review-requested:<a href="https://www.producthunt.com/@me">@me</a></strong>), gated on viewer mode (no point asking GitHub for someone else&#8217;s inbox), running in parallel with the rest of the dashboard refresh. Drilling in opens the full PR detail (v0.11.0 surface with v0.12.0 syntax-highlighted diff viewer) with the requester&#8217;s login visible.</p><p><strong>macOS notification subtitle</strong></p><p>Stars/followers change notifications now carry a second line &#8212; <strong>Stars</strong>, <strong>Followers</strong>, or <strong>Stars &amp; Followers</strong> when both moved at once. Two wins:</p><p>- <strong>The Notification Center keeps per-entry timestamps visible</strong> even when terminal-notifier groups notifications. Without the subtitle, grouped notifications collapse and only the most recent shows when it arrived.</p><p>- <strong>The banner gains a visible category line</strong> under the title, useful when you spot the banner after the body has already scrolled past.</p><p><strong>Upgrade</strong></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:&quot;f6a53ab6-e3af-4e07-96c5-65a4ba98fb81&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">brew upgrade gfazioli/tap/octoscope</code></pre></div><p>Or grab a binary from the release page: <a href="https://github.com/gfazioli/octoscope/releases/tag/v0.15.0">https://github.com/gfazioli/octoscope/releases/tag/v0.15.0</a></p><p>As always: read-only, free, MIT, on Linux / macOS / Windows. Source: <a href="https://github.com/gfazioli/octoscope">https://github.com/gfazioli/octoscope</a></p><p>How do you split your day between &#8220;PRs you authored&#8221; and &#8220;PRs you&#8217;re meant to review&#8221;?</p><p>Curious if anyone has a watch_repos config they curated specifically because they review for a team there.</p>]]></content:encoded></item><item><title><![CDATA[Netfox is live — and now on Product Hunt]]></title><description><![CDATA[The first native macOS network monitor is here &#8212; and we need your vote.]]></description><link>https://www.undolog.com/p/netfox-is-live-and-now-on-product</link><guid isPermaLink="false">https://www.undolog.com/p/netfox-is-live-and-now-on-product</guid><dc:creator><![CDATA[Giovambattista Fazioli]]></dc:creator><pubDate>Thu, 28 May 2026 08:41:36 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!ibHH!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc549907c-2ba0-41fe-816e-08b0edd40733_3072x1886.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ibHH!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc549907c-2ba0-41fe-816e-08b0edd40733_3072x1886.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ibHH!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc549907c-2ba0-41fe-816e-08b0edd40733_3072x1886.png 424w, https://substackcdn.com/image/fetch/$s_!ibHH!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc549907c-2ba0-41fe-816e-08b0edd40733_3072x1886.png 848w, https://substackcdn.com/image/fetch/$s_!ibHH!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc549907c-2ba0-41fe-816e-08b0edd40733_3072x1886.png 1272w, https://substackcdn.com/image/fetch/$s_!ibHH!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc549907c-2ba0-41fe-816e-08b0edd40733_3072x1886.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ibHH!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc549907c-2ba0-41fe-816e-08b0edd40733_3072x1886.png" width="1200" height="736.8131868131868" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c549907c-2ba0-41fe-816e-08b0edd40733_3072x1886.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:894,&quot;width&quot;:1456,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:193794,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.undolog.com/i/199571652?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc549907c-2ba0-41fe-816e-08b0edd40733_3072x1886.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-large" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ibHH!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc549907c-2ba0-41fe-816e-08b0edd40733_3072x1886.png 424w, https://substackcdn.com/image/fetch/$s_!ibHH!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc549907c-2ba0-41fe-816e-08b0edd40733_3072x1886.png 848w, https://substackcdn.com/image/fetch/$s_!ibHH!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc549907c-2ba0-41fe-816e-08b0edd40733_3072x1886.png 1272w, https://substackcdn.com/image/fetch/$s_!ibHH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc549907c-2ba0-41fe-816e-08b0edd40733_3072x1886.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Hey there,</p><p>Welcome to the very first Netfox newsletter. If you&#8217;re reading this, you probably care about knowing what&#8217;s happening on your home network &#8212; without handing your data to someone else&#8217;s cloud. That&#8217;s exactly why Netfox exists.</p><h2>What is Netfox?</h2><p>Netfox is a native macOS network monitor. Open it, and you see every device on your local network &#8212; Apple gadgets, smart TVs, IoT sensors, printers, guest phones &#8212; in one live window.</p><p>It combines five discovery protocols (Bonjour, ARP, SSDP, NetBIOS, and active probing) so it catches devices that single-protocol scanners miss. Every device gets a full history: first seen, last seen, every online/offline transition, every IP or hostname change. That timeline persists across launches.</p><p>Five types of alerts keep you informed: new device, returning after long absence, risky arrival, port-state change, new service discovered.</p><p>And a one-click Security Scan checks common ports on every reachable device, with findings explained in plain English &#8212; no sysadmin log to decode.</p><p>It&#8217;s built 100% in SwiftUI for macOS 15+. No Electron, no web views. No cloud, no account, no telemetry. Your data stays on your Mac.</p><p>It&#8217;s free.</p><h2>We&#8217;re on Product Hunt</h2><p>This week I launched Netfox on Product Hunt, and the response has been incredible. We&#8217;ve already collected 86 upvotes and 68 followers &#8212; and the feedback from the community has been genuinely helpful.</p><p>If you haven&#8217;t yet, I&#8217;d really appreciate your support:</p><p>&#128073; Vote for Netfox on <a href="https://www.producthunt.com/products/netfox">Product Hunt</a></p><p>Every upvote helps more people discover Netfox.</p><h2>What&#8217;s in v0.3.2</h2><p>The current release is v0.3.2. You can download it directly from the Netfox website or from the GitHub releases page:</p><p>&#128279; <a href="https://netfox.app">https://netfox.app</a><br>&#128279; <a href="https://github.com/gfazioli/netfox-website/releases/latest">https://github.com/gfazioli/netfox-website/releases/latest</a></p><h2>What&#8217;s next</h2><p>This is just the beginning. I&#8217;m actively working on new features and improvements &#8212; and this newsletter is where you&#8217;ll hear about them first.</p><p>Got ideas? Found a bug? I want to hear from you. Reply to this email or open an issue on GitHub:</p><p>&#128279; <a href="https://github.com/gfazioli/netfox-website/issues">https://github.com/gfazioli/netfox-website/issues</a></p><p>Thanks for being here from day one.</p>]]></content:encoded></item><item><title><![CDATA[octoscope v0.14.0 — Watching more, seeing more ⭐]]></title><description><![CDATA[Track any repo you care about, now with star history and release info]]></description><link>https://www.undolog.com/p/octoscope-v0140-watching-more-seeing</link><guid isPermaLink="false">https://www.undolog.com/p/octoscope-v0140-watching-more-seeing</guid><dc:creator><![CDATA[Giovambattista Fazioli]]></dc:creator><pubDate>Thu, 28 May 2026 08:05:38 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!ek8-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdd2c3123-6a1b-4415-a0e4-247fe6c73048_2130x1474.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Four additions on the Repos surface that turn it from &#8220;your owned repos&#8221; into &#8220;every repo you care about, with the signals that matter&#8221;.</p><p><strong>Star history sparkline </strong>&#11088;</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ek8-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdd2c3123-6a1b-4415-a0e4-247fe6c73048_2130x1474.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ek8-!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdd2c3123-6a1b-4415-a0e4-247fe6c73048_2130x1474.png 424w, https://substackcdn.com/image/fetch/$s_!ek8-!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdd2c3123-6a1b-4415-a0e4-247fe6c73048_2130x1474.png 848w, https://substackcdn.com/image/fetch/$s_!ek8-!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdd2c3123-6a1b-4415-a0e4-247fe6c73048_2130x1474.png 1272w, https://substackcdn.com/image/fetch/$s_!ek8-!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdd2c3123-6a1b-4415-a0e4-247fe6c73048_2130x1474.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ek8-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdd2c3123-6a1b-4415-a0e4-247fe6c73048_2130x1474.png" width="1456" height="1008" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/dd2c3123-6a1b-4415-a0e4-247fe6c73048_2130x1474.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1008,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ek8-!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdd2c3123-6a1b-4415-a0e4-247fe6c73048_2130x1474.png 424w, https://substackcdn.com/image/fetch/$s_!ek8-!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdd2c3123-6a1b-4415-a0e4-247fe6c73048_2130x1474.png 848w, https://substackcdn.com/image/fetch/$s_!ek8-!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdd2c3123-6a1b-4415-a0e4-247fe6c73048_2130x1474.png 1272w, https://substackcdn.com/image/fetch/$s_!ek8-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdd2c3123-6a1b-4415-a0e4-247fe6c73048_2130x1474.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>A 12-month star-history sparkline lands in the Repos drill-in: ASCII blocks &#9601;&#9602;&#9603;&#9604;&#9605;&#9606;&#9607; over 52 weekly buckets, with empty weeks rendered as muted &#183; ticks so the timeline stays continuous even on young repos. Footer reads +N in last 12mo &#183; last star Xd ago. Fetched in parallel with the rest of the drill-in &#8212; no extra wait when you open a repo.</p><p><strong>Latest release column</strong></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!c43B!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4faeb44-4690-43c7-8405-359cd8f9609f_2130x1518.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!c43B!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4faeb44-4690-43c7-8405-359cd8f9609f_2130x1518.png 424w, https://substackcdn.com/image/fetch/$s_!c43B!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4faeb44-4690-43c7-8405-359cd8f9609f_2130x1518.png 848w, https://substackcdn.com/image/fetch/$s_!c43B!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4faeb44-4690-43c7-8405-359cd8f9609f_2130x1518.png 1272w, https://substackcdn.com/image/fetch/$s_!c43B!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4faeb44-4690-43c7-8405-359cd8f9609f_2130x1518.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!c43B!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4faeb44-4690-43c7-8405-359cd8f9609f_2130x1518.png" width="1456" height="1038" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f4faeb44-4690-43c7-8405-359cd8f9609f_2130x1518.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1038,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!c43B!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4faeb44-4690-43c7-8405-359cd8f9609f_2130x1518.png 424w, https://substackcdn.com/image/fetch/$s_!c43B!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4faeb44-4690-43c7-8405-359cd8f9609f_2130x1518.png 848w, https://substackcdn.com/image/fetch/$s_!c43B!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4faeb44-4690-43c7-8405-359cd8f9609f_2130x1518.png 1272w, https://substackcdn.com/image/fetch/$s_!c43B!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4faeb44-4690-43c7-8405-359cd8f9609f_2130x1518.png 1456w" sizes="100vw"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>A new Release column on the Repos tab shows the most recent release tag plus its age (v1.2.3 &#183; 3d). The s sort cycle gains a &#8220;release&#8221; entry. No extra round-trip &#8212; piggybacked onto the existing parallel CI query.</p><p>&#128064; <strong>Watched repos (external)</strong></p><p>Add to your config:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;toml&quot;,&quot;nodeId&quot;:&quot;3e9126b2-e6be-481b-8ed0-aeac81ebe668&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-toml">watch_repos = [
  "charmbracelet/bubbletea",
  "cli/cli",
]</code></pre></div><p>&#8230;and octoscope renders a dedicated Watched section under the Repos tab, separated from your owned repos by a muted rule. Same row idiom &#8212; CI dot, language, stars, latest release column &#8212; but populated from the GraphQL API in parallel with the dashboard refresh. Bounded fan-out (cap 10) so a long config doesn&#8217;t burst-flood GitHub.</p><p><strong>Theme fidelity &#8212; monochrome means monochrome</strong></p><p>The monochrome, phosphor and amber themes used to leak external semantic colour (GitHub language palette, CI rollup green/red, Activity heatmap gradient) even though they explicitly promise zero chroma. This release closes that gap end-to-end: language bars rank-scale through the theme&#8217;s own palette, CI rollup uses distinct glyphs (&#10003; / &#10005; / &#8943; / &#183;) instead of colour, the Activity heatmap routes through the theme&#8217;s gradient. The promise on the theme name finally means what it says.</p><p>Upgrade</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:&quot;555dc8c7-63d1-47b5-9941-7bf31167a898&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">brew upgrade gfazioli/tap/octoscope</code></pre></div><p>Or grab a binary from the release page: <a href="https://github.com/gfazioli/octoscope/releases/tag/v0.14.0">https://github.com/gfazioli/octoscope/releases/tag/v0.14.0</a></p><p>As always: read-only, free, MIT, on Linux / macOS / Windows. Source: <a href="https://github.com/gfazioli/octoscope">https://github.com/gfazioli/octoscope</a></p><p>What would you put in your watch_repos?<br>Curious if anyone uses it for &#8220;my team&#8217;s repos&#8221; vs &#8220;projects I follow personally&#8221; &#8212; the answer shapes what gets a dedicated card next.</p>]]></content:encoded></item><item><title><![CDATA[The Player, the Pieces, and the Hook]]></title><description><![CDATA[A Mantine-native video player for React that ships as three layers in one package: a polished default <Video />, a composable compound API for the control bar, and a fully headless useVideo hook.]]></description><link>https://www.undolog.com/p/the-player-the-pieces-and-the-hook</link><guid isPermaLink="false">https://www.undolog.com/p/the-player-the-pieces-and-the-hook</guid><dc:creator><![CDATA[Giovambattista Fazioli]]></dc:creator><pubDate>Tue, 19 May 2026 11:25:56 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!lKrU!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F125e2b04-62c6-445c-8ea8-62e88e0bd198_2752x1536.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!lKrU!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F125e2b04-62c6-445c-8ea8-62e88e0bd198_2752x1536.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!lKrU!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F125e2b04-62c6-445c-8ea8-62e88e0bd198_2752x1536.jpeg 424w, https://substackcdn.com/image/fetch/$s_!lKrU!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F125e2b04-62c6-445c-8ea8-62e88e0bd198_2752x1536.jpeg 848w, https://substackcdn.com/image/fetch/$s_!lKrU!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F125e2b04-62c6-445c-8ea8-62e88e0bd198_2752x1536.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!lKrU!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F125e2b04-62c6-445c-8ea8-62e88e0bd198_2752x1536.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!lKrU!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F125e2b04-62c6-445c-8ea8-62e88e0bd198_2752x1536.jpeg" width="1200" height="670.054945054945" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/125e2b04-62c6-445c-8ea8-62e88e0bd198_2752x1536.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:813,&quot;width&quot;:1456,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:303205,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.undolog.com/i/198267629?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F125e2b04-62c6-445c-8ea8-62e88e0bd198_2752x1536.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-large" alt="" srcset="https://substackcdn.com/image/fetch/$s_!lKrU!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F125e2b04-62c6-445c-8ea8-62e88e0bd198_2752x1536.jpeg 424w, https://substackcdn.com/image/fetch/$s_!lKrU!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F125e2b04-62c6-445c-8ea8-62e88e0bd198_2752x1536.jpeg 848w, https://substackcdn.com/image/fetch/$s_!lKrU!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F125e2b04-62c6-445c-8ea8-62e88e0bd198_2752x1536.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!lKrU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F125e2b04-62c6-445c-8ea8-62e88e0bd198_2752x1536.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2><strong>Introduction</strong></h2><p>Imagine you&#8217;re building a landing page. The hero needs a looping background video. The product section needs an inline player with a clean control bar. And somewhere on the page, there&#8217;s a feature demo where you want a <em>completely</em> custom UI &#8212; circular play button, vertical timeline, custom seek preview. With the native HTML <code>&lt;video&gt;</code> element, that&#8217;s three different implementations, each with its own quirks and audio glitches. With <code>@gfazioli/mantine-video</code>, it&#8217;s one component, three patterns, zero compromises.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!0FZ-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc212201c-90b5-4e71-af64-7b226edb8414_800x909.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!0FZ-!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc212201c-90b5-4e71-af64-7b226edb8414_800x909.webp 424w, https://substackcdn.com/image/fetch/$s_!0FZ-!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc212201c-90b5-4e71-af64-7b226edb8414_800x909.webp 848w, https://substackcdn.com/image/fetch/$s_!0FZ-!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc212201c-90b5-4e71-af64-7b226edb8414_800x909.webp 1272w, https://substackcdn.com/image/fetch/$s_!0FZ-!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc212201c-90b5-4e71-af64-7b226edb8414_800x909.webp 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!0FZ-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc212201c-90b5-4e71-af64-7b226edb8414_800x909.webp" width="800" height="909" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c212201c-90b5-4e71-af64-7b226edb8414_800x909.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:909,&quot;width&quot;:800,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Mantine Video&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Mantine Video" title="Mantine Video" srcset="https://substackcdn.com/image/fetch/$s_!0FZ-!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc212201c-90b5-4e71-af64-7b226edb8414_800x909.webp 424w, https://substackcdn.com/image/fetch/$s_!0FZ-!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc212201c-90b5-4e71-af64-7b226edb8414_800x909.webp 848w, https://substackcdn.com/image/fetch/$s_!0FZ-!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc212201c-90b5-4e71-af64-7b226edb8414_800x909.webp 1272w, https://substackcdn.com/image/fetch/$s_!0FZ-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc212201c-90b5-4e71-af64-7b226edb8414_800x909.webp 1456w" sizes="100vw"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2><strong>What is mantine-video?</strong></h2><p><code>@gfazioli/mantine-video</code> is a video player for React applications built with Mantine 9 and React 19. It wraps the native HTML <code>&lt;video&gt;</code> element with three layers of abstraction, layered from &#8220;drop-in&#8221; to &#8220;fully custom&#8221;:</p><ol><li><p><code>&lt;Video /&gt;</code> &#8212; a fully themed, batteries-included player that works out of the box.</p></li><li><p><strong>A compound API</strong> (<code>Video.Controls</code>, <code>Video.PlayButton</code>, <code>Video.Timeline</code>, &#8230;) for composable control bars.</p></li><li><p><code>useVideo</code><strong> headless hook</strong> &#8212; full state and actions for building a 100% custom UI on top of a plain <code>&lt;video&gt;</code> element.</p></li></ol><p>Every part is theme-aware (color scheme, Mantine theme colors, radii, sizes), accessible (ARIA labels, keyboard shortcuts, focus management), and customizable through the full Mantine Styles API. Four variants &#8212; <code>overlay</code>, <code>minimal</code>, <code>floating</code>, <code>bordered</code> &#8212; cover the most common layouts out of the box. Picture-in-Picture, fullscreen, captions and live timeline scrubbing are wired in by default.</p><p>Oh, and the same player can also be a background video. More on that below.</p><h2><strong>&#10024; Key Features</strong></h2><h3><strong>A polished default player</strong></h3><p>The fastest path: pass <code>src</code>, optionally <code>poster</code> and <code>aspectRatio</code>, and you have a complete player with play/pause, scrubbable timeline, time display, volume control, captions toggle, Picture-in-Picture and fullscreen.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;tsx&quot;,&quot;nodeId&quot;:&quot;907f66be-9ea9-4fb2-aceb-f7e4feb4d3b5&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-tsx">import { Video } from '@gfazioli/mantine-video';

function Demo() {
  return (
    &lt;Video
      src="https://example.com/video.mp4"
      poster="https://example.com/poster.jpg"
      aspectRatio={16 / 9}
    /&gt;
  );
}</code></pre></div><h3><strong>Compound API for custom control bars</strong></h3><p>The default control bar is just a sensible composition of compound sub-components. Pass <code>controls={false}</code> and bring your own children to reorder, remove, or add controls &#8212; without losing Mantine theming or accessibility.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;tsx&quot;,&quot;nodeId&quot;:&quot;a7708455-a8c8-443b-819d-6098de90f610&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-tsx">&lt;Video src="..." aspectRatio={16 / 9} controls={false}&gt;
  &lt;Video.Controls&gt;
    &lt;Video.PlayButton /&gt;
    &lt;Video.SkipButton seconds={-10} /&gt;
    &lt;Video.SkipButton seconds={10} /&gt;
    &lt;Video.Timeline /&gt;
    &lt;Video.TimeDisplay format="current/-remaining" /&gt;
    &lt;Video.MuteButton /&gt;
    &lt;Video.CaptionsButton /&gt;
    &lt;Video.PiPButton /&gt;
    &lt;Video.FullscreenButton /&gt;
  &lt;/Video.Controls&gt;
&lt;/Video&gt;</code></pre></div><p>Nine compound sub-components available: <code>Video.Controls</code>, <code>Video.PlayButton</code>, <code>Video.SkipButton</code>, <code>Video.Timeline</code>, <code>Video.TimeDisplay</code>, <code>Video.MuteButton</code>, <code>Video.CaptionsButton</code>, <code>Video.PiPButton</code>, <code>Video.FullscreenButton</code>.</p><h3><strong>Headless </strong><code>useVideo</code><strong> hook</strong></h3><p>When the compound API isn&#8217;t flexible enough, skip the <code>&lt;Video /&gt;</code> component entirely and use the <code>useVideo</code> hook with a plain <code>&lt;video&gt;</code> element. You get full state plus a complete set of actions.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;tsx&quot;,&quot;nodeId&quot;:&quot;1ddf5bf0-9760-4703-86bf-e9e392380f68&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-tsx">import { ActionIcon, Slider } from '@mantine/core';
import { IconPlayerPauseFilled, IconPlayerPlayFilled } from '@tabler/icons-react';
import { useVideo } from '@gfazioli/mantine-video';

function CustomPlayer() {
  const video = useVideo();

  return (
    &lt;div&gt;
      &lt;video ref={video.videoRef} src="..." /&gt;
      &lt;ActionIcon onClick={video.toggle}&gt;
        {video.playing ? &lt;IconPlayerPauseFilled /&gt; : &lt;IconPlayerPlayFilled /&gt;}
      &lt;/ActionIcon&gt;
      &lt;Slider
        value={video.currentTime}
        max={video.duration}
        onChange={video.seek}
      /&gt;
    &lt;/div&gt;
  );
}</code></pre></div><p>The hook exposes 16 state values (<code>playing</code>, <code>paused</code>, <code>ended</code>, <code>currentTime</code>, <code>duration</code>, <code>buffered</code>, <code>volume</code>, <code>muted</code>, <code>playbackRate</code>, <code>fullscreen</code>, <code>pip</code>, <code>isLoading</code>, <code>error</code>, <code>canPlay</code>, <code>canFullscreen</code>, <code>canPiP</code>) and 16 actions (<code>play</code>, <code>pause</code>, <code>toggle</code>, <code>seek</code>, <code>seekBy</code>, <code>setVolume</code>, <code>mute</code>, <code>unmute</code>, <code>toggleMute</code>, <code>setPlaybackRate</code>, <code>requestFullscreen</code>, <code>exitFullscreen</code>, <code>toggleFullscreen</code>, <code>requestPiP</code>, <code>exitPiP</code>, <code>togglePiP</code>) plus the two refs (<code>videoRef</code>, <code>containerRef</code>).</p><h3><strong>Live timeline scrubbing</strong></h3><p>Drag the timeline thumb &#8212; the underlying <code>&lt;video&gt;</code> seeks in real time and you see the frame update under your finger, YouTube-style. The player pauses automatically during the drag and resumes on release if it was playing. Throttled with <code>requestAnimationFrame</code> so it stays smooth even on heavier videos. Opt out with <code>&lt;Video.Timeline liveScrub={false} /&gt;</code> to fall back to commit-on-release.</p><h3><strong>Four built-in variants</strong></h3><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;tsx&quot;,&quot;nodeId&quot;:&quot;046f94c8-237d-4901-a225-d26946d1a3ba&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-tsx">&lt;Video src="..." variant="overlay" /&gt;    {/* default &#8212; YouTube-style overlay */}
&lt;Video src="..." variant="minimal" /&gt;    {/* controls below the video, page flow */}
&lt;Video src="..." variant="floating" /&gt;   {/* controls in a glass-morphism card */}
&lt;Video src="..." variant="bordered" /&gt;   {/* bordered frame for cards/lists */}</code></pre></div><p>Each variant changes only the position and styling of the controls &#8212; the API and sub-components stay identical.</p><h3><strong>Background video &#8212; one prop turns the player into a hero</strong></h3><p>This is where the layered API really pays off. Set <code>asBackground</code> and the same player becomes an absolute-positioned, cover-cropped element ready to drop inside any hero section.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;tsx&quot;,&quot;nodeId&quot;:&quot;b8228a3c-f813-40a2-8331-daeee1b9f52a&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-tsx">&lt;Box pos="relative" h="100vh"&gt;
  &lt;Video src="..." asBackground autoPlay muted loop /&gt;
  &lt;Title&gt;Welcome to my product&lt;/Title&gt;
  {/* Your hero content overlaid on top */}
&lt;/Box&gt;</code></pre></div><p>When <code>asBackground</code> is on, the component:</p><ul><li><p>Positions itself absolutely (<code>position: absolute; inset: 0</code>) inside its parent</p></li><li><p>Applies <code>object-fit: cover</code> on the underlying <code>&lt;video&gt;</code></p></li><li><p>Disables <code>controls</code>, <code>clickToToggle</code>, <code>shortcuts</code> and <code>autoHideControls</code> as defaults (you can still re-enable any of them explicitly)</p></li><li><p>Renders a discreet floating mute toggle in the bottom-right corner, controllable via the <code>backgroundMuteButton</code> prop</p></li></ul><p>For finer control, the standalone <code>fit</code> prop accepts <code>'cover' | 'contain' | 'fill' | 'none' | 'scale-down'</code> and maps directly to CSS <code>object-fit</code>.</p><h3><strong>Picture-in-Picture</strong></h3><p>PiP is wired in by default. The browser&#8217;s native always-on-top window can be launched via:</p><ul><li><p>The <code>&lt;Video.PiPButton /&gt;</code> &#8212; auto-hides on browsers without the standard API (notably Firefox)</p></li><li><p>The <strong>P</strong> keyboard shortcut</p></li><li><p>Programmatically via <code>useVideo().togglePiP()</code></p></li></ul><p>Plus two lifecycle callbacks to react to the user popping the video out:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;tsx&quot;,&quot;nodeId&quot;:&quot;e9e13937-25ef-4c49-a114-1b8f412efed0&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-tsx">&lt;Video
  src="..."
  onEnterPictureInPicture={() =&gt; console.log('PiP open')}
  onLeavePictureInPicture={() =&gt; console.log('PiP closed')}
/&gt;</code></pre></div><h3><strong>Keyboard shortcuts</strong></h3><p>When <code>shortcuts</code> is enabled (default) and the player has focus:</p><ul><li><p><strong>Space / K</strong> &#8212; toggle play / pause</p></li><li><p><strong>J / L</strong> &#8212; seek -10s / +10s</p></li><li><p><strong>&#8592; / &#8594;</strong> &#8212; seek -5s / +5s</p></li><li><p><strong>&#8593; / &#8595;</strong> &#8212; volume up / down</p></li><li><p><strong>M</strong> &#8212; mute toggle</p></li><li><p><strong>F</strong> &#8212; fullscreen toggle</p></li><li><p><strong>P</strong> &#8212; Picture-in-Picture toggle</p></li></ul><h2><strong>&#128640; Getting Started</strong></h2><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:&quot;35e20d6d-8d59-4b53-947f-8a495f8873f6&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">npm install @gfazioli/mantine-video
# or
yarn add @gfazioli/mantine-video</code></pre></div><p>Import the styles at the root of your application:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;tsx&quot;,&quot;nodeId&quot;:&quot;e9267d5e-3dc8-45d6-8519-d85ec1c2b87d&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-tsx">import '@gfazioli/mantine-video/styles.css';</code></pre></div><p>A layered version is also available under <code>@layer mantine-video</code> for fine-grained cascade control:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;tsx&quot;,&quot;nodeId&quot;:&quot;99b3038b-087b-41dc-862e-990bce47a0af&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-tsx">import '@gfazioli/mantine-video/styles.layer.css';</code></pre></div><p>Then drop it anywhere:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;tsx&quot;,&quot;nodeId&quot;:&quot;a96c7064-a0f9-495e-9217-930bba6cd6a7&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-tsx">import { Video } from '@gfazioli/mantine-video';

export function Demo() {
  return (
    &lt;Video
      src="https://example.com/video.mp4"
      poster="https://example.com/poster.jpg"
      aspectRatio={16 / 9}
    /&gt;
  );
}</code></pre></div><h2><strong>Props &amp; API</strong></h2><p>The <code>&lt;Video /&gt;</code> component accepts a rich set of props grouped by responsibility:</p><p><strong>Source &amp; playback</strong></p><ul><li><p><code>src: string</code> &#8212; video source URL</p></li><li><p><code>poster: string</code> &#8212; image displayed before playback starts</p></li><li><p><code>autoPlay</code>, <code>muted</code>, <code>loop</code>, <code>playsInline</code>, <code>preload</code> &#8212; forwarded to the underlying <code>&lt;video&gt;</code></p></li><li><p><code>playing</code> / <code>currentTime</code> / <code>volume</code> / <code>playbackRate</code> &#8212; controlled props with matching <code>onPlayChange</code> / <code>onCurrentTimeChange</code> / <code>onVolumeChange</code> / <code>onPlaybackRateChange</code> callbacks</p></li></ul><p><strong>Look &amp; feel</strong></p><ul><li><p><code>variant: 'overlay' | 'minimal' | 'floating' | 'bordered'</code></p></li><li><p><code>color: MantineColor</code></p></li><li><p><code>radius: MantineRadius</code></p></li><li><p><code>size: MantineSize</code></p></li><li><p><code>aspectRatio: number</code> &#8212; e.g. <code>16 / 9</code></p></li></ul><p><strong>Interactivity</strong></p><ul><li><p><code>controls: boolean</code> &#8212; render the default control bar (default <code>true</code>)</p></li><li><p><code>clickToToggle: boolean</code> &#8212; click anywhere on the video to play / pause</p></li><li><p><code>doubleClickToFullscreen: boolean</code></p></li><li><p><code>shortcuts: boolean</code> &#8212; enable keyboard shortcuts when focused</p></li><li><p><code>autoHideControls: number</code> &#8212; ms of inactivity before controls fade out (<code>0</code> disables)</p></li></ul><p><strong>Background mode</strong></p><ul><li><p><code>asBackground: boolean</code> &#8212; preset for hero / section backgrounds</p></li><li><p><code>backgroundMuteButton: boolean</code> &#8212; floating mute toggle when <code>asBackground</code> is on (default <code>true</code>)</p></li><li><p><code>fit: 'cover' | 'contain' | 'fill' | 'none' | 'scale-down'</code> &#8212; CSS <code>object-fit</code> on the <code>&lt;video&gt;</code></p></li></ul><p><strong>Lifecycle callbacks</strong></p><ul><li><p><code>onEnded()</code> &#8212; playback reached the end of the media</p></li><li><p><code>onError(error: MediaError | null)</code></p></li><li><p><code>onEnterPictureInPicture()</code> / <code>onLeavePictureInPicture()</code></p></li><li><p><code>onFullscreenChange(fullscreen: boolean)</code></p></li></ul><h2><strong>&#127912; Styling</strong></h2><p><code>&lt;Video /&gt;</code> supports the full Mantine <a href="https://mantine.dev/styles/styles-api/">Styles API</a>. Every sub-part is targetable through <code>classNames</code>, <code>styles</code>, <code>vars</code> and <code>unstyled</code> props.</p><p><strong>Selectors</strong>: <code>root</code>, <code>video</code>, <code>controls</code>, <code>controlBar</code>, <code>playButton</code>, <code>timeline</code>, <code>timelineBuffered</code>, <code>timeDisplay</code>, <code>muteButton</code>, <code>fullscreenButton</code>, <code>pipButton</code>, <code>captionsButton</code>, <code>skipButton</code>, <code>iconButton</code>, <code>backgroundMuteButton</code>.</p><p><strong>CSS variables</strong>: <code>--video-color</code>, <code>--video-radius</code>, <code>--video-bg</code>, <code>--video-controls-bg</code>, <code>--video-controls-height</code>, <code>--video-controls-text-color</code>, <code>--video-timeline-color</code>, <code>--video-timeline-thumb-color</code>, <code>--video-object-fit</code>.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;tsx&quot;,&quot;nodeId&quot;:&quot;6c2ecbb7-ca49-4dc7-8329-32af25505b0a&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-tsx">&lt;Video
  src="..."
  classNames={{ playButton: 'my-play-button' }}
  styles={{ controls: { background: 'transparent' } }}
  vars={() =&gt; ({
    root: { '--video-color': 'tomato' } as Record&lt;string, string&gt;,
  })}
/&gt;</code></pre></div><h2><strong>Live Demo &amp; Documentation</strong></h2><p>The docs site at <a href="https://gfazioli.github.io/mantine-video">gfazioli.github.io/mantine-video</a> ships with ten interactive demos covering every API surface &#8212; an interactive configurator, the basic player, custom controls, each of the four variants, headless usage via <code>useVideo</code>, Picture-in-Picture lifecycle, and the Styles API playground.</p><p>There are also two <strong>full-bleed standalone pages</strong> that show the <code>asBackground</code> mode in real landing-page scenarios:</p><ul><li><p><a href="https://gfazioli.github.io/mantine-video/fullscreen">fullscreen</a> &#8212; a single immersive hero with title + CTA over a looping video</p></li><li><p><a href="https://gfazioli.github.io/mantine-video/homepage">homepage</a> &#8212; a multi-section landing page that alternates three different background videos with text content and a features grid</p></li></ul><h2><strong>Links</strong></h2><ul><li><p>&#128230; <a href="https://www.npmjs.com/package/@gfazioli/mantine-video">npm: @gfazioli/mantine-video</a></p></li><li><p>&#128214; <a href="https://gfazioli.github.io/mantine-video">Documentation &amp; demos</a></p></li><li><p>&#128279; <a href="https://github.com/gfazioli/mantine-video">GitHub repository</a></p></li><li><p>&#129513; <a href="https://mantine-extensions.vercel.app/">Mantine Extensions Hub</a></p></li></ul>]]></content:encoded></item><item><title><![CDATA[Meet octoscope — your GitHub profile, at a glance, in your terminal]]></title><description><![CDATA[A cross-platform TUI dashboard that keeps the pulse of your GitHub life one keystroke away &#8212; no browser, no context switch.]]></description><link>https://www.undolog.com/p/meet-octoscope-your-github-profile</link><guid isPermaLink="false">https://www.undolog.com/p/meet-octoscope-your-github-profile</guid><dc:creator><![CDATA[Giovambattista Fazioli]]></dc:creator><pubDate>Sat, 25 Apr 2026 11:03:59 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/3c439b3a-8cb0-42d9-b07b-902725b6616b_1280x640.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!SfQm!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8af6855-bd47-4602-8b19-95f3733b1b4d_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!SfQm!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8af6855-bd47-4602-8b19-95f3733b1b4d_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!SfQm!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8af6855-bd47-4602-8b19-95f3733b1b4d_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!SfQm!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8af6855-bd47-4602-8b19-95f3733b1b4d_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!SfQm!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8af6855-bd47-4602-8b19-95f3733b1b4d_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!SfQm!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8af6855-bd47-4602-8b19-95f3733b1b4d_1536x1024.png" width="1200" height="800.2747252747253" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b8af6855-bd47-4602-8b19-95f3733b1b4d_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:599426,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.undolog.com/i/195432330?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8af6855-bd47-4602-8b19-95f3733b1b4d_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-large" alt="" srcset="https://substackcdn.com/image/fetch/$s_!SfQm!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8af6855-bd47-4602-8b19-95f3733b1b4d_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!SfQm!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8af6855-bd47-4602-8b19-95f3733b1b4d_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!SfQm!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8af6855-bd47-4602-8b19-95f3733b1b4d_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!SfQm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8af6855-bd47-4602-8b19-95f3733b1b4d_1536x1024.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2><strong>The problem</strong></h2><p>If you work on GitHub every day, you probably know the dance. You&#8217;re deep in a coding session, you want to glance at &#8220;how many PRs are open on my repos right now&#8221;, or &#8220;did that follower count tick up after the release post&#8221;, or &#8220;which of my repos did I last push to&#8221;. So you tab over to the browser, wait for the page to load, click through to a sub-view, squint at the numbers, and tab back. Thirty seconds later you&#8217;ve lost your place in the code.</p><p>The data itself is trivial. It&#8217;s the context switch that&#8217;s expensive.</p><h2><strong>The solution</strong></h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!vlTO!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b874b90-d95a-40b4-a640-7aab204c182e_800x736.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!vlTO!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b874b90-d95a-40b4-a640-7aab204c182e_800x736.webp 424w, https://substackcdn.com/image/fetch/$s_!vlTO!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b874b90-d95a-40b4-a640-7aab204c182e_800x736.webp 848w, https://substackcdn.com/image/fetch/$s_!vlTO!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b874b90-d95a-40b4-a640-7aab204c182e_800x736.webp 1272w, https://substackcdn.com/image/fetch/$s_!vlTO!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b874b90-d95a-40b4-a640-7aab204c182e_800x736.webp 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!vlTO!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b874b90-d95a-40b4-a640-7aab204c182e_800x736.webp" width="800" height="736" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9b874b90-d95a-40b4-a640-7aab204c182e_800x736.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:736,&quot;width&quot;:800,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Octoscope&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Octoscope" title="Octoscope" srcset="https://substackcdn.com/image/fetch/$s_!vlTO!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b874b90-d95a-40b4-a640-7aab204c182e_800x736.webp 424w, https://substackcdn.com/image/fetch/$s_!vlTO!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b874b90-d95a-40b4-a640-7aab204c182e_800x736.webp 848w, https://substackcdn.com/image/fetch/$s_!vlTO!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b874b90-d95a-40b4-a640-7aab204c182e_800x736.webp 1272w, https://substackcdn.com/image/fetch/$s_!vlTO!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b874b90-d95a-40b4-a640-7aab204c182e_800x736.webp 1456w" sizes="100vw"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><strong>octoscope</strong> is a terminal dashboard for your GitHub account &#8212; profile, repos, pull requests, issues, contribution activity &#8212; pulled from the GitHub GraphQL API in a single round-trip and kept on screen, auto-refreshed every 60 seconds. You run it once in a background terminal tab and it&#8217;s always there when you need it.</p><p>Single binary. Cross-platform (macOS, Linux, Windows). No runtime dependencies. No config file. If you have a shell, you can run octoscope.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:&quot;d5448050-01b7-4949-b931-3016cc68f528&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">octoscope</code></pre></div><p>That&#8217;s it. If you have a GitHub token in your environment, it detects it and renders your own dashboard. If not, you can pass any public username as an argument:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:&quot;65bf345c-8e69-4267-986f-66f02a25bfaa&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">octoscope torvalds</code></pre></div><h2><strong>What it shows</strong></h2><p>The UI is split into <strong>five tabs</strong>, navigable with number keys <code>1</code>&#8211;<code>5</code> or <code>tab</code> / <code>shift+tab</code>. The profile header stays pinned at the top; only the body swaps.</p><h3><strong>Overview &#8212; the landing page</strong></h3><p>The classic dashboard: profile info (name, login, pronouns, bio, company, location, website, years on GitHub), a Social section (followers, following, stars received), an Activity section (lifetime PRs authored and merged, issues opened, commits in the last 12 months, plus a languages bar coloured with the same hex GitHub uses on the site), an Operational section (public repos, forks received, open issues, open PRs), and a Network section (organisations you&#8217;re in, verified social accounts).</p><h3><strong>Repos &#8212; every repository in one list</strong></h3><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!AJcJ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F230411df-0263-4f34-b1dc-7db654d26c76_800x746.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!AJcJ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F230411df-0263-4f34-b1dc-7db654d26c76_800x746.webp 424w, https://substackcdn.com/image/fetch/$s_!AJcJ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F230411df-0263-4f34-b1dc-7db654d26c76_800x746.webp 848w, https://substackcdn.com/image/fetch/$s_!AJcJ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F230411df-0263-4f34-b1dc-7db654d26c76_800x746.webp 1272w, https://substackcdn.com/image/fetch/$s_!AJcJ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F230411df-0263-4f34-b1dc-7db654d26c76_800x746.webp 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!AJcJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F230411df-0263-4f34-b1dc-7db654d26c76_800x746.webp" width="800" height="746" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/230411df-0263-4f34-b1dc-7db654d26c76_800x746.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:746,&quot;width&quot;:800,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Repos&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Repos" title="Repos" srcset="https://substackcdn.com/image/fetch/$s_!AJcJ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F230411df-0263-4f34-b1dc-7db654d26c76_800x746.webp 424w, https://substackcdn.com/image/fetch/$s_!AJcJ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F230411df-0263-4f34-b1dc-7db654d26c76_800x746.webp 848w, https://substackcdn.com/image/fetch/$s_!AJcJ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F230411df-0263-4f34-b1dc-7db654d26c76_800x746.webp 1272w, https://substackcdn.com/image/fetch/$s_!AJcJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F230411df-0263-4f34-b1dc-7db654d26c76_800x746.webp 1456w" sizes="auto, 100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Every owned, non-fork repository as a sortable, searchable table. Columns: name, primary language, stars, forks, open issues, open PRs, last push. Press <code>s</code> to cycle sort by any column, <code>/</code> to filter by substring. The viewport scrolls, so even a 100-repo account stays navigable.</p><h3><strong>PRs &#8212; every pull request you&#8217;ve opened</strong></h3><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!F531!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a7deaf8-c390-41d2-b618-880a80ebb8c6_800x741.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!F531!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a7deaf8-c390-41d2-b618-880a80ebb8c6_800x741.webp 424w, https://substackcdn.com/image/fetch/$s_!F531!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a7deaf8-c390-41d2-b618-880a80ebb8c6_800x741.webp 848w, https://substackcdn.com/image/fetch/$s_!F531!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a7deaf8-c390-41d2-b618-880a80ebb8c6_800x741.webp 1272w, https://substackcdn.com/image/fetch/$s_!F531!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a7deaf8-c390-41d2-b618-880a80ebb8c6_800x741.webp 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!F531!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a7deaf8-c390-41d2-b618-880a80ebb8c6_800x741.webp" width="800" height="741" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7a7deaf8-c390-41d2-b618-880a80ebb8c6_800x741.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:741,&quot;width&quot;:800,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Prs&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Prs" title="Prs" srcset="https://substackcdn.com/image/fetch/$s_!F531!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a7deaf8-c390-41d2-b618-880a80ebb8c6_800x741.webp 424w, https://substackcdn.com/image/fetch/$s_!F531!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a7deaf8-c390-41d2-b618-880a80ebb8c6_800x741.webp 848w, https://substackcdn.com/image/fetch/$s_!F531!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a7deaf8-c390-41d2-b618-880a80ebb8c6_800x741.webp 1272w, https://substackcdn.com/image/fetch/$s_!F531!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a7deaf8-c390-41d2-b618-880a80ebb8c6_800x741.webp 1456w" sizes="auto, 100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Every open pull request you&#8217;ve authored, <strong>across every repo you&#8217;ve ever touched</strong> &#8212; not just your own. Number, title, repo, state (draft / ready-to-merge / conflicting) and last-update time. Same sort &amp; search idioms as Repos.</p><h3><strong>Issues &#8212; every open issue you&#8217;ve opened</strong></h3><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!0KWo!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F801e76c6-c68a-401b-9c0c-0e432292914f_800x739.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!0KWo!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F801e76c6-c68a-401b-9c0c-0e432292914f_800x739.webp 424w, https://substackcdn.com/image/fetch/$s_!0KWo!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F801e76c6-c68a-401b-9c0c-0e432292914f_800x739.webp 848w, https://substackcdn.com/image/fetch/$s_!0KWo!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F801e76c6-c68a-401b-9c0c-0e432292914f_800x739.webp 1272w, https://substackcdn.com/image/fetch/$s_!0KWo!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F801e76c6-c68a-401b-9c0c-0e432292914f_800x739.webp 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!0KWo!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F801e76c6-c68a-401b-9c0c-0e432292914f_800x739.webp" width="800" height="739" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/801e76c6-c68a-401b-9c0c-0e432292914f_800x739.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:739,&quot;width&quot;:800,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Issues&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Issues" title="Issues" srcset="https://substackcdn.com/image/fetch/$s_!0KWo!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F801e76c6-c68a-401b-9c0c-0e432292914f_800x739.webp 424w, https://substackcdn.com/image/fetch/$s_!0KWo!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F801e76c6-c68a-401b-9c0c-0e432292914f_800x739.webp 848w, https://substackcdn.com/image/fetch/$s_!0KWo!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F801e76c6-c68a-401b-9c0c-0e432292914f_800x739.webp 1272w, https://substackcdn.com/image/fetch/$s_!0KWo!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F801e76c6-c68a-401b-9c0c-0e432292914f_800x739.webp 1456w" sizes="auto, 100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Same shape as PRs, minus the state column. Every open issue you&#8217;ve authored, wherever it lives. The whole backlog in one keystroke.</p><h3><strong>Activity &#8212; your year, shaded</strong></h3><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!hduQ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdef2741b-0952-44d9-a044-c4ddb18d3d32_800x743.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!hduQ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdef2741b-0952-44d9-a044-c4ddb18d3d32_800x743.webp 424w, https://substackcdn.com/image/fetch/$s_!hduQ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdef2741b-0952-44d9-a044-c4ddb18d3d32_800x743.webp 848w, https://substackcdn.com/image/fetch/$s_!hduQ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdef2741b-0952-44d9-a044-c4ddb18d3d32_800x743.webp 1272w, https://substackcdn.com/image/fetch/$s_!hduQ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdef2741b-0952-44d9-a044-c4ddb18d3d32_800x743.webp 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!hduQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdef2741b-0952-44d9-a044-c4ddb18d3d32_800x743.webp" width="800" height="743" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/def2741b-0952-44d9-a044-c4ddb18d3d32_800x743.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:743,&quot;width&quot;:800,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Activity&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Activity" title="Activity" srcset="https://substackcdn.com/image/fetch/$s_!hduQ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdef2741b-0952-44d9-a044-c4ddb18d3d32_800x743.webp 424w, https://substackcdn.com/image/fetch/$s_!hduQ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdef2741b-0952-44d9-a044-c4ddb18d3d32_800x743.webp 848w, https://substackcdn.com/image/fetch/$s_!hduQ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdef2741b-0952-44d9-a044-c4ddb18d3d32_800x743.webp 1272w, https://substackcdn.com/image/fetch/$s_!hduQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdef2741b-0952-44d9-a044-c4ddb18d3d32_800x743.webp 1456w" sizes="auto, 100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>A 52-week contribution heatmap rendered on an accent-pink gradient, with month labels above and a summary line below: total contributions, current streak, longest streak, busiest day with its date. The gradient adapts to your own distribution, so the busiest day always hits the full neon pink regardless of your absolute volume.</p><h2><strong>The details that matter</strong></h2><h3><strong>Rate-limit awareness, live in the footer</strong></h3><p>GitHub&#8217;s GraphQL API has a 5,000-points-per-hour ceiling on authenticated calls. octoscope surfaces your remaining budget live in the footer:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:&quot;164a6e06-985a-45fd-b70c-4fe6e5e18a88&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">Updated 12s ago  &#183;  rate 4872/5000  &#183;  reset 23m  &#183;  auto 60s</code></pre></div><p>The chip is muted at normal levels, <strong>warn-yellow under 20%</strong> remaining, and <strong>error-red under 5%</strong>. No surprises when you&#8217;re close to the ceiling. If you do hit it, the auto-refresh backs off until the reset time instead of hammering every 60s and making it worse.</p><h3><strong>Fetch errors that tell you what to do</strong></h3><p>When a refresh fails, the footer says <em>why</em> in plain English:</p><ul><li><p><code>rate-limited &#183; retry at 14:23</code> &#8212; bucket exhausted, here&#8217;s when it refills</p></li><li><p><code>token rejected &#183; check $GITHUB_TOKEN</code> &#8212; auth problem, actionable</p></li><li><p><code>offline &#183; retrying</code> &#8212; network blip</p></li><li><p><code>github errored &#183; retrying</code> &#8212; 5xx from GitHub, wait it out</p></li></ul><p>You know whether to wait, fix auth, or check the network &#8212; instead of staring at an opaque error indicator.</p><h3><strong>Live change feedback</strong></h3><p>When a value changes between two refreshes &#8212; a new star, a new follower, a PR merging &#8212; the affected card&#8217;s border flashes accent-pink for two seconds. No diffing numbers with your eyes.</p><p>And on top of that, changes to <strong>Stars</strong> and <strong>Followers</strong> trigger a native system notification and a short audio beep. You notice passive attention even when octoscope is in a background tab. macOS, Linux and Windows &#8212; no configuration needed.</p><h3><strong>Screenshot-safe by default when you want it</strong></h3><p>If you manage private repositories (work, internal projects, drafts) and you&#8217;re about to share a screenshot of your dashboard, one flag scrubs the output:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:&quot;cc503783-fa4f-4e12-8289-195033fc231f&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">octoscope --public-only</code></pre></div><p>Private repos, PRs and issues are filtered from the Repos / PRs / Issues tabs. Global counters (PRs Authored, Merged, Issues Authored) stay complete since they&#8217;re aggregate numbers, not titles. What gets hidden are repo names and PR/issue titles &#8212; the parts that actually leak context.</p><h3><strong>Authentication that just works</strong></h3><p>octoscope resolves a GitHub token in this order:</p><ol><li><p><code>$GITHUB_TOKEN</code> environment variable</p></li><li><p><code>gh auth token</code> &#8212; if the <a href="https://cli.github.com/">GitHub CLI</a> is installed and logged in</p></li><li><p>No token &#8212; unauthenticated fallback (60 req/h, public profiles only)</p></li></ol><p>Viewing your own account needs a token. Viewing anyone else&#8217;s public profile works either way.</p><h2><strong>Getting started</strong></h2><h3><strong>Homebrew (macOS &amp; Linux)</strong></h3><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:&quot;308fca31-5b48-4633-9ce4-b3167e8d6fe0&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">brew install gfazioli/tap/octoscope</code></pre></div><h3><strong>From source (Go 1.25+)</strong></h3><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:&quot;18e75c9e-b82c-484f-a09d-2407e7c62f34&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">go install github.com/gfazioli/octoscope@latest</code></pre></div><h3><strong>Pre-built binaries</strong></h3><p>Grab the right archive for your platform from the <a href="https://github.com/gfazioli/octoscope/releases/latest">latest release</a> and drop the <code>octoscope</code> binary anywhere on your <code>$PATH</code>.</p><h3><strong>Key bindings</strong></h3><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!lSPX!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6f3bbc1e-e347-4d27-bfcf-15c1c5e47d2d_1314x900.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!lSPX!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6f3bbc1e-e347-4d27-bfcf-15c1c5e47d2d_1314x900.png 424w, https://substackcdn.com/image/fetch/$s_!lSPX!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6f3bbc1e-e347-4d27-bfcf-15c1c5e47d2d_1314x900.png 848w, https://substackcdn.com/image/fetch/$s_!lSPX!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6f3bbc1e-e347-4d27-bfcf-15c1c5e47d2d_1314x900.png 1272w, https://substackcdn.com/image/fetch/$s_!lSPX!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6f3bbc1e-e347-4d27-bfcf-15c1c5e47d2d_1314x900.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!lSPX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6f3bbc1e-e347-4d27-bfcf-15c1c5e47d2d_1314x900.png" width="1314" height="900" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6f3bbc1e-e347-4d27-bfcf-15c1c5e47d2d_1314x900.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:900,&quot;width&quot;:1314,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:112659,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.undolog.com/i/195432330?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6f3bbc1e-e347-4d27-bfcf-15c1c5e47d2d_1314x900.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!lSPX!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6f3bbc1e-e347-4d27-bfcf-15c1c5e47d2d_1314x900.png 424w, https://substackcdn.com/image/fetch/$s_!lSPX!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6f3bbc1e-e347-4d27-bfcf-15c1c5e47d2d_1314x900.png 848w, https://substackcdn.com/image/fetch/$s_!lSPX!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6f3bbc1e-e347-4d27-bfcf-15c1c5e47d2d_1314x900.png 1272w, https://substackcdn.com/image/fetch/$s_!lSPX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6f3bbc1e-e347-4d27-bfcf-15c1c5e47d2d_1314x900.png 1456w" sizes="auto, 100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2><strong>What octoscope won&#8217;t show</strong></h2><p>A note on honesty: some things you see on your GitHub profile page are <strong>not exposed</strong> by GitHub&#8217;s public API, so octoscope doesn&#8217;t show them either. Achievements (Pull Shark, Starstruck, YOLO and friends), highlight badges like PRO, and the local time next to your location field all require scraping the profile HTML. octoscope stays API-only, so those are absent by design.</p><h2><strong>Built with BubbleTea</strong></h2><p>octoscope is a terminal UI built with <a href="https://github.com/charmbracelet/bubbletea">BubbleTea</a> &#8212; the ELM-inspired TUI framework from <a href="https://charm.sh/">Charm</a> &#8212; and styled with <a href="https://github.com/charmbracelet/lipgloss">Lipgloss</a>. The network layer uses <a href="https://github.com/shurcooL/githubv4">shurcooL/githubv4</a> against the GitHub GraphQL API, one round-trip per refresh.</p><p>The whole thing is open source under the MIT license.</p><h2><strong>Links</strong></h2><ul><li><p>&#127760; <strong>Landing</strong>: <a href="https://gfazioli.github.io/octoscope/">https://gfazioli.github.io/octoscope/</a></p></li><li><p>&#128230; <strong>GitHub</strong>: <a href="https://github.com/gfazioli/octoscope">https://github.com/gfazioli/octoscope</a></p></li><li><p>&#128203; <strong>Releases</strong>: <a href="https://github.com/gfazioli/octoscope/releases">https://github.com/gfazioli/octoscope/releases</a></p></li><li><p>&#128027; <strong>Issues &amp; ideas</strong>: <a href="https://github.com/gfazioli/octoscope/issues">https://github.com/gfazioli/octoscope/issues</a></p></li></ul><p>If you live in the terminal and you&#8217;re on GitHub every day, give it a spin. Feedback, bug reports and feature asks are genuinely shaping the roadmap &#8212; every issue ticket has been read.</p>]]></content:encoded></item><item><title><![CDATA[FinderGit v0.1.0 — Stop Tabbing Between Finder and Your Git Client]]></title><description><![CDATA[A public beta of a native macOS app that combines Finder-style file browsing with live Git intelligence &#8212; one window, every repo, branch and diff at a glance.]]></description><link>https://www.undolog.com/p/findergit-v010-stop-tabbing-between</link><guid isPermaLink="false">https://www.undolog.com/p/findergit-v010-stop-tabbing-between</guid><dc:creator><![CDATA[Giovambattista Fazioli]]></dc:creator><pubDate>Fri, 17 Apr 2026 08:31:02 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!YVXB!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba83f93d-f8c4-424d-8a5a-ec2b8e55febf_2752x1536.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!YVXB!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba83f93d-f8c4-424d-8a5a-ec2b8e55febf_2752x1536.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!YVXB!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba83f93d-f8c4-424d-8a5a-ec2b8e55febf_2752x1536.png 424w, https://substackcdn.com/image/fetch/$s_!YVXB!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba83f93d-f8c4-424d-8a5a-ec2b8e55febf_2752x1536.png 848w, https://substackcdn.com/image/fetch/$s_!YVXB!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba83f93d-f8c4-424d-8a5a-ec2b8e55febf_2752x1536.png 1272w, https://substackcdn.com/image/fetch/$s_!YVXB!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba83f93d-f8c4-424d-8a5a-ec2b8e55febf_2752x1536.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!YVXB!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba83f93d-f8c4-424d-8a5a-ec2b8e55febf_2752x1536.png" width="1200" height="670.054945054945" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ba83f93d-f8c4-424d-8a5a-ec2b8e55febf_2752x1536.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:813,&quot;width&quot;:1456,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:6544936,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.undolog.com/i/194492984?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba83f93d-f8c4-424d-8a5a-ec2b8e55febf_2752x1536.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-large" alt="" srcset="https://substackcdn.com/image/fetch/$s_!YVXB!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba83f93d-f8c4-424d-8a5a-ec2b8e55febf_2752x1536.png 424w, https://substackcdn.com/image/fetch/$s_!YVXB!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba83f93d-f8c4-424d-8a5a-ec2b8e55febf_2752x1536.png 848w, https://substackcdn.com/image/fetch/$s_!YVXB!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba83f93d-f8c4-424d-8a5a-ec2b8e55febf_2752x1536.png 1272w, https://substackcdn.com/image/fetch/$s_!YVXB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba83f93d-f8c4-424d-8a5a-ec2b8e55febf_2752x1536.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2><strong>A day with twelve repositories</strong></h2><p><strong>Before.</strong> You have a <code>~/Projects</code> folder with twelve Git repos in it. To know which ones are dirty, you open Terminal, <code>cd</code> into each, run <code>git status</code>, repeat. Or you open Tower and wait for it to scan. Or you open VS Code twelve times. None of this is <em>looking at your files</em> &#8212; it&#8217;s ceremony around looking at your files.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!VGM7!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7def6f4-75b2-4e0c-b6a8-ff1c4ac2cc5b_800x392.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!VGM7!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7def6f4-75b2-4e0c-b6a8-ff1c4ac2cc5b_800x392.webp 424w, https://substackcdn.com/image/fetch/$s_!VGM7!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7def6f4-75b2-4e0c-b6a8-ff1c4ac2cc5b_800x392.webp 848w, https://substackcdn.com/image/fetch/$s_!VGM7!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7def6f4-75b2-4e0c-b6a8-ff1c4ac2cc5b_800x392.webp 1272w, https://substackcdn.com/image/fetch/$s_!VGM7!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7def6f4-75b2-4e0c-b6a8-ff1c4ac2cc5b_800x392.webp 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!VGM7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7def6f4-75b2-4e0c-b6a8-ff1c4ac2cc5b_800x392.webp" width="800" height="392" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f7def6f4-75b2-4e0c-b6a8-ff1c4ac2cc5b_800x392.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:392,&quot;width&quot;:800,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;FinderGit&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="FinderGit" title="FinderGit" srcset="https://substackcdn.com/image/fetch/$s_!VGM7!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7def6f4-75b2-4e0c-b6a8-ff1c4ac2cc5b_800x392.webp 424w, https://substackcdn.com/image/fetch/$s_!VGM7!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7def6f4-75b2-4e0c-b6a8-ff1c4ac2cc5b_800x392.webp 848w, https://substackcdn.com/image/fetch/$s_!VGM7!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7def6f4-75b2-4e0c-b6a8-ff1c4ac2cc5b_800x392.webp 1272w, https://substackcdn.com/image/fetch/$s_!VGM7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7def6f4-75b2-4e0c-b6a8-ff1c4ac2cc5b_800x392.webp 1456w" sizes="100vw"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><strong>After.</strong> You open FinderGit and the folder tree looks like Finder&#8217;s list view, plus four columns: branch name, clean/dirty/unpushed badge, number of changed files, and sizes and dates. Directories inside a dirty repo get a small orange dot with a count &#8212; so you spot modifications three levels deep without expanding anything.</p><p>That&#8217;s the whole pitch. The rest of this post is the details.</p><h2><strong>What is FinderGit?</strong></h2><p>FinderGit is a native macOS app built with SwiftUI. It&#8217;s positioned somewhere between Finder and a heavy Git client like Tower &#8212; it&#8217;s <strong>not a replacement</strong> for either. If your day involves interactive rebases, submodule surgery, or cherry-picking across a long history, you&#8217;ll still want Tower, GitX, or your CLI.</p><p>FinderGit is for the other 90% of the day: the part where you&#8217;re browsing your projects, quickly checking &#8220;what did I leave dirty yesterday?&#8221;, staging a few files, typing a commit message, pushing. Everything happens in the window that already shows your files &#8212; no context switch.</p><p>It runs on macOS 15 Sequoia or later. It&#8217;s free, open-source (the website repo is public, the app repo will follow), and this is the <strong>public beta</strong> &#8212; v0.1.0. Expect a few rough edges; file them, I fix them.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!NdOC!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1616fc91-097d-4f7a-9f25-484dd87a87b7_800x506.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!NdOC!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1616fc91-097d-4f7a-9f25-484dd87a87b7_800x506.webp 424w, https://substackcdn.com/image/fetch/$s_!NdOC!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1616fc91-097d-4f7a-9f25-484dd87a87b7_800x506.webp 848w, https://substackcdn.com/image/fetch/$s_!NdOC!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1616fc91-097d-4f7a-9f25-484dd87a87b7_800x506.webp 1272w, https://substackcdn.com/image/fetch/$s_!NdOC!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1616fc91-097d-4f7a-9f25-484dd87a87b7_800x506.webp 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!NdOC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1616fc91-097d-4f7a-9f25-484dd87a87b7_800x506.webp" width="800" height="506" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1616fc91-097d-4f7a-9f25-484dd87a87b7_800x506.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:506,&quot;width&quot;:800,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;FinderGif&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="FinderGif" title="FinderGif" srcset="https://substackcdn.com/image/fetch/$s_!NdOC!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1616fc91-097d-4f7a-9f25-484dd87a87b7_800x506.webp 424w, https://substackcdn.com/image/fetch/$s_!NdOC!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1616fc91-097d-4f7a-9f25-484dd87a87b7_800x506.webp 848w, https://substackcdn.com/image/fetch/$s_!NdOC!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1616fc91-097d-4f7a-9f25-484dd87a87b7_800x506.webp 1272w, https://substackcdn.com/image/fetch/$s_!NdOC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1616fc91-097d-4f7a-9f25-484dd87a87b7_800x506.webp 1456w" sizes="auto, 100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2><strong>&#10024; Key features</strong></h2><ul><li><p><strong>Tree view with sortable columns</strong> &#8212; Finder-style list view, but the columns are Name, Branch, Status, Changes, Size, Date Modified. Click any header to sort.</p></li><li><p><strong>Live Git status per repository</strong> &#8212; a small badge next to each repo: <code>CLEAN</code> (green), <code>DIRTY</code> (orange), <code>UNPUSHED</code> (blue). Updates in real time via FSEvents.</p></li><li><p><strong>Sub-directory dirty indicators</strong> &#8212; directories inside a repo show an orange dot with the number of modified files they contain. Walk down to the changes without expanding every folder.</p></li><li><p><strong>Inline diff viewer</strong> &#8212; click any modified file and the right pane renders a unified diff with green/red lines, hunk headers, and line numbers. Supports <code>HEAD</code>, staged, unstaged, and even untracked files (rendered as all-added).</p></li><li><p><strong>Git actions from the UI</strong> &#8212; Stage, Unstage, Commit, Push, Pull, Fetch. Per file or per repo. Commit messages get a <code>TextField</code> and two buttons: <em>Commit Staged</em> or <em>Commit All &amp; Push</em>.</p></li><li><p><strong>Native Markdown preview via Quick Look</strong> &#8212; press <strong>Space</strong> on any <code>.md</code> or <code>.markdown</code> file and it opens in a native renderer with a GitHub-style theme. Close with Space or Escape. Every other file type falls back to the system <code>qlmanage</code>, so you keep PDF, image, video previews for free.</p></li><li><p><strong>Smart context menus</strong> &#8212; right-click adapts to what you clicked: repo gets Fetch / Pull / Push / Stage All; tracked file gets Stage / Unstage; non-git directory gets Add as Root Folder; everything gets Open / Reveal in Finder / Copy Path / Open in Terminal.</p></li><li><p><strong>Multiple root folders</strong> &#8212; add as many as you want. Drop folders from Finder directly onto the sidebar; they become tree roots.</p></li><li><p><strong>Search &amp; filter</strong> &#8212; type in the toolbar to filter the tree by name (300ms debounce). Toggle <strong>Git Only</strong> to hide everything that isn&#8217;t a repo &#8212; instant overview of just your projects.</p></li><li><p><strong>Keyboard-driven</strong> &#8212; &#8984;&#8679;F Fetch, &#8984;&#8679;L Pull, &#8984;&#8679;P Push, &#8984;&#8679;A Stage All, &#8984;&#8679;S Stage selected, Space Quick Look, &#8984;+/&#8984;- zoom, &#8984;&#8679;R Reveal in Finder. The usual Mac muscle memory.</p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!4d9D!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe725d089-9275-491a-96a5-e89c5c7c7064_800x494.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!4d9D!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe725d089-9275-491a-96a5-e89c5c7c7064_800x494.webp 424w, https://substackcdn.com/image/fetch/$s_!4d9D!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe725d089-9275-491a-96a5-e89c5c7c7064_800x494.webp 848w, https://substackcdn.com/image/fetch/$s_!4d9D!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe725d089-9275-491a-96a5-e89c5c7c7064_800x494.webp 1272w, https://substackcdn.com/image/fetch/$s_!4d9D!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe725d089-9275-491a-96a5-e89c5c7c7064_800x494.webp 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!4d9D!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe725d089-9275-491a-96a5-e89c5c7c7064_800x494.webp" width="800" height="494" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e725d089-9275-491a-96a5-e89c5c7c7064_800x494.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:494,&quot;width&quot;:800,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Context Menu&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Context Menu" title="Context Menu" srcset="https://substackcdn.com/image/fetch/$s_!4d9D!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe725d089-9275-491a-96a5-e89c5c7c7064_800x494.webp 424w, https://substackcdn.com/image/fetch/$s_!4d9D!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe725d089-9275-491a-96a5-e89c5c7c7064_800x494.webp 848w, https://substackcdn.com/image/fetch/$s_!4d9D!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe725d089-9275-491a-96a5-e89c5c7c7064_800x494.webp 1272w, https://substackcdn.com/image/fetch/$s_!4d9D!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe725d089-9275-491a-96a5-e89c5c7c7064_800x494.webp 1456w" sizes="auto, 100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2><strong>&#128736; What&#8217;s powering it</strong></h2><p>For the developer-curious reader:</p><ul><li><p><strong>Swift + SwiftUI</strong>, deployment target macOS 15. The UI uses <code>NavigationSplitView</code> with a native <code>Table(children:)</code> for the tree, so sorting, keyboard navigation and selection come from AppKit under the hood.</p></li><li><p><strong>Observation framework</strong> (<code>@Observable</code>) for state &#8212; no <code>ObservableObject</code> / <code>@Published</code> boilerplate. Structured concurrency (<code>async</code>/<code>await</code>) everywhere, no <code>DispatchQueue</code>.</p></li><li><p><strong>FSEvents</strong> via a small <code>GitWatcher</code> that observes both the worktree and <code>.git/HEAD</code>, <code>.git/index</code>, <code>.git/refs/</code>, <code>.git/FETCH_HEAD</code> &#8212; debounced at the user-configurable latency. So pushing in Terminal updates the FinderGit badge within milliseconds.</p></li><li><p><code>git status --porcelain=v2</code> under the hood. <code>/usr/bin/git</code> is called via <code>Process</code> with a parser for the machine-readable output. No libgit2, no reimplementing history &#8212; the real git binary is the source of truth.</p></li><li><p><strong><a href="https://github.com/gonzalezreal/swift-markdown-ui">MarkdownUI</a></strong> for the Markdown preview. Pattern is extensible &#8212; same &#8220;interceptor&#8221; will take <code>.css</code> / <code>.js</code> / <code>.swift</code> with syntax highlighting in a future release.</p></li><li><p><strong><a href="https://sparkle-project.org/">Sparkle</a></strong> for auto-updates, EdDSA-signed. See below.</p></li><li><p><strong>Personal Team signing</strong>, no Apple Developer Program ($99/year) required. That has implications for Gatekeeper &#8212; covered in the install section.</p></li></ul><h2><strong>&#128230; Install</strong></h2><h3><strong>Requirements</strong></h3><ul><li><p>macOS 15 (Sequoia) or later</p></li><li><p>Xcode Command Line Tools &#8212; FinderGit uses the system <code>git</code> binary, which on macOS is shipped as a stub. On a fresh Mac, run once:</p></li></ul><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:&quot;3f71e408-fe1c-4115-b7e9-cf4987154bdd&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">  xcode-select --install
  sudo xcodebuild -license</code></pre></div><p>Without these, every Git action inside FinderGit returns <code>nonZeroExit(status: 89, &#8230;)</code>. This will be surfaced as a friendly dialog in a future release &#8212; for now, the Terminal workaround is a one-time setup.</p><h3><strong>Download</strong></h3><ol><li><p>Grab the latest <code>.dmg</code> from the <a href="https://github.com/gfazioli/findergit-website/releases/latest">GitHub Release</a>.</p></li><li><p>Open it and drag <strong>FinderGit</strong> into your Applications folder.</p></li></ol><h3><strong>First launch &#8212; Gatekeeper</strong></h3><p>The app is signed with a free <strong>Personal Team</strong>, not with an Apple Developer ID. That means macOS Gatekeeper doesn&#8217;t know it and will block the first launch with a generic warning. The fix is the standard Mac dance:</p><ol><li><p>Open <strong>Applications</strong> in Finder.</p></li><li><p><strong>Right-click</strong> on FinderGit.app &#8594; <strong>Open</strong>.</p></li><li><p>In the confirmation popup, click <strong>Open</strong> again.</p></li></ol><p>Done once &#8212; from then on the app launches normally, including auto-updates. No paid developer program is funding this; you&#8217;re trading a 5-second first-launch gesture for a free app.</p><h2><strong>&#128260; Auto-updates</strong></h2><p>FinderGit ships with Sparkle. The app checks <code>https://findergit.app/appcast.xml</code> on launch and periodically; when a new version is available, you get the standard Sparkle alert with release notes. Each release DMG is signed with an <strong>EdDSA key</strong> &#8212; the public key ships inside the app, so Sparkle refuses to install an update it cannot verify. Even if my GitHub account were compromised tomorrow, an attacker couldn&#8217;t push a malicious update without my private signing key.</p><p>You can also trigger a check manually via the <strong>FinderGit &#8594; Check for Updates&#8230;</strong> menu item.</p><h2><strong>&#128506; What&#8217;s next</strong></h2><p>The beta is functional, but plenty is already planned. Highlights of the public issue tracker:</p><ul><li><p><strong>Syntax highlighting in Quick Look</strong> for code files (<code>.swift</code>, <code>.js</code>, <code>.ts</code>, <code>.py</code>, <code>.css</code>, <code>.json</code>, &#8230;) &#8212; the Markdown interceptor pattern, extended.</p></li><li><p><strong>Double-click a folder to navigate into it</strong> (drill-down, with a breadcrumb to go back).</p></li><li><p><strong>Branch creation, renaming, deletion, checkout</strong> from the UI.</p></li><li><p><strong>Stash management</strong> (save, pop, apply, drop).</p></li><li><p><strong>GitHub / GitLab / Bitbucket providers</strong> &#8212; list Issues and Pull Requests for the current repo. Preference for <code>gh</code> / <code>glab</code> CLI when installed; REST fallback with a token in Keychain.</p></li><li><p><strong>Kaleidoscope integration</strong> &#8212; jump from the inline diff to the full Kaleidoscope comparison, &#224; la Tower.</p></li><li><p><strong>AI-generated commit messages</strong> &#8212; a built-in &#10024; AI button next to the commit text field that drafts a message from your staged diff, no external tools or API keys required.</p></li><li><p><strong>Auto-fetch on an interval</strong> wired to the setting that already exists.</p></li><li><p><strong>Drag files from the tree to external apps</strong> &#8212; without breaking row selection, which is why it&#8217;s not in v0.1.0.</p></li></ul><p>If any of these are on your wish list, say so &#8212; priorities move.</p><h2><strong>Download, feedback, sponsorship</strong></h2><p>Three things, in order of how much they help:</p><ol><li><p><strong>Download it and try it</strong> &#8594; <a href="https://github.com/gfazioli/findergit-website/releases/latest">latest release</a>. Ten minutes with real repos tells you more than any blog post.</p></li><li><p><strong>Tell me what works and what doesn&#8217;t</strong> &#8594; <a href="https://github.com/gfazioli/findergit-website/issues">open an issue</a> on the website repo. Bug reports, feature requests, workflow pain points &#8212; all welcome. Screenshots help.</p></li><li><p><strong>If it turns into something you rely on, consider sponsoring</strong> &#8594; <a href="https://github.com/sponsors/gfazioli">GitHub Sponsors</a>. FinderGit is free, unsigned by Apple, and maintained in evenings and weekends. Sponsorship keeps the momentum.</p></li></ol><h2><strong>Links</strong></h2><ul><li><p>&#127760; <strong>Website &amp; docs</strong>: <a href="https://findergit.app/">findergit.app</a></p></li><li><p>&#128229; <strong>Download</strong>: <a href="https://github.com/gfazioli/findergit-website/releases/latest">GitHub Release v0.1.0</a></p></li><li><p>&#128027; <strong>Issues &amp; feedback</strong>: <a href="https://github.com/gfazioli/findergit-website/issues">github.com/gfazioli/findergit-website/issues</a></p></li><li><p>&#11088; <strong>Sponsor</strong>: <a href="https://github.com/sponsors/gfazioli">github.com/sponsors/gfazioli</a></p></li></ul>]]></content:encoded></item><item><title><![CDATA[I Built a Shared Library for the Amiga in 68020 Assembly — and a Modern Documentation Site for It]]></title><description><![CDATA[A love letter to the Commodore Amiga &#8212; 50+ utility functions in hand-optimized 68020 assembly, with C bindings, a custom windowing system, and a Next.js documentation site.]]></description><link>https://www.undolog.com/p/i-built-a-shared-library-for-the</link><guid isPermaLink="false">https://www.undolog.com/p/i-built-a-shared-library-for-the</guid><dc:creator><![CDATA[Giovambattista Fazioli]]></dc:creator><pubDate>Wed, 08 Apr 2026 09:58:53 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!q31p!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F200a3ea8-8274-421e-993a-150c9a5d9797_1280x640.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!q31p!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F200a3ea8-8274-421e-993a-150c9a5d9797_1280x640.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!q31p!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F200a3ea8-8274-421e-993a-150c9a5d9797_1280x640.jpeg 424w, https://substackcdn.com/image/fetch/$s_!q31p!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F200a3ea8-8274-421e-993a-150c9a5d9797_1280x640.jpeg 848w, https://substackcdn.com/image/fetch/$s_!q31p!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F200a3ea8-8274-421e-993a-150c9a5d9797_1280x640.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!q31p!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F200a3ea8-8274-421e-993a-150c9a5d9797_1280x640.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!q31p!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F200a3ea8-8274-421e-993a-150c9a5d9797_1280x640.jpeg" width="1280" height="640" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/200a3ea8-8274-421e-993a-150c9a5d9797_1280x640.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:640,&quot;width&quot;:1280,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:133809,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.undolog.com/i/193558127?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F200a3ea8-8274-421e-993a-150c9a5d9797_1280x640.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!q31p!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F200a3ea8-8274-421e-993a-150c9a5d9797_1280x640.jpeg 424w, https://substackcdn.com/image/fetch/$s_!q31p!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F200a3ea8-8274-421e-993a-150c9a5d9797_1280x640.jpeg 848w, https://substackcdn.com/image/fetch/$s_!q31p!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F200a3ea8-8274-421e-993a-150c9a5d9797_1280x640.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!q31p!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F200a3ea8-8274-421e-993a-150c9a5d9797_1280x640.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Back in <strong>1991</strong>, I started writing what would become a 5,800-line shared library for the Commodore Amiga &#8212; entirely in hand-optimized Motorola 68020 assembly. The first routines (string utilities in <code>libraries.asm</code>) were saved on July 29, 1991. Over the next four years, the library grew steadily &#8212; graphics in October 1994, file I/O and memory management in early 1995, the REI windowing system through mid-1995, and the last recorded update in September 1995.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!8xKd!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e41b3bd-d280-4aee-88e3-83162aa0b420_2812x2320.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!8xKd!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e41b3bd-d280-4aee-88e3-83162aa0b420_2812x2320.png 424w, https://substackcdn.com/image/fetch/$s_!8xKd!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e41b3bd-d280-4aee-88e3-83162aa0b420_2812x2320.png 848w, https://substackcdn.com/image/fetch/$s_!8xKd!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e41b3bd-d280-4aee-88e3-83162aa0b420_2812x2320.png 1272w, https://substackcdn.com/image/fetch/$s_!8xKd!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e41b3bd-d280-4aee-88e3-83162aa0b420_2812x2320.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!8xKd!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e41b3bd-d280-4aee-88e3-83162aa0b420_2812x2320.png" width="1456" height="1201" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8e41b3bd-d280-4aee-88e3-83162aa0b420_2812x2320.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1201,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:368323,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.undolog.com/i/193558127?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e41b3bd-d280-4aee-88e3-83162aa0b420_2812x2320.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!8xKd!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e41b3bd-d280-4aee-88e3-83162aa0b420_2812x2320.png 424w, https://substackcdn.com/image/fetch/$s_!8xKd!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e41b3bd-d280-4aee-88e3-83162aa0b420_2812x2320.png 848w, https://substackcdn.com/image/fetch/$s_!8xKd!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e41b3bd-d280-4aee-88e3-83162aa0b420_2812x2320.png 1272w, https://substackcdn.com/image/fetch/$s_!8xKd!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e41b3bd-d280-4aee-88e3-83162aa0b420_2812x2320.png 1456w" sizes="100vw"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>That code sat on floppy disks and hard drives for decades. Now, in 2026, I&#8217;ve dusted it off, open-sourced it, and built a modern documentation site for it with Next.js.</p><p>Let me tell you the story.</p><h2><strong>What Is This?</strong></h2><p>The <strong>Amiga Assembly Library</strong> is a shared library (<code>assembly.library</code>) for AmigaOS, written between <strong>1991 and 1995</strong>, that provides 50+ high-level utility functions in optimized 68020 assembly. Think of it as a &#8220;standard library&#8221; for Amiga developers &#8212; covering everything from memory management and file I/O to bitmap graphics, string manipulation, and even a full windowing abstraction.</p><p>It&#8217;s <strong>Public Domain</strong> &#8212; no copyright, no licensing, no restrictions. Use it however you want.</p><h2><strong>Why Assembly? And Why Now?</strong></h2><p>When I wrote this library in the early &#8216;90s, the Amiga was my daily machine. I was 19, obsessed with squeezing every cycle out of the 68020, and frustrated by rewriting the same utility routines for every project. So I did what any obsessive programmer would do &#8212; I built a shared library.</p><p>The Amiga community, remarkably, is still alive and well in 2026. People are building hardware accelerators, writing new software, and pushing these machines beyond what anyone thought possible. When you&#8217;re working with a 7 MHz 68000 (or a 50 MHz 68060 if you&#8217;re lucky), every cycle counts. Hand-optimized assembly isn&#8217;t a flex &#8212; it&#8217;s a necessity.</p><p>I decided to open-source the library and give it the documentation it always deserved &#8212; because if this code helped me build a dozen projects in the &#8216;90s, maybe it can help someone else today.</p><h2><strong>What&#8217;s Inside?</strong></h2><p>The library is organized into 8 modules:</p><p><strong>Exec</strong> &#8212; Memory and linked list management. <code>AllocNewList</code>, <code>AllocNode</code>, <code>FreeList</code>, <code>ReAllocVec</code>, and <code>RevertMem</code> for reversing memory regions.</p><p><strong>DOS</strong> &#8212; File operations. <code>Load</code> and <code>Save</code> files to/from memory, <code>FileInfo</code> for metadata, <code>CheckSum</code> for data integrity, <code>LineInput</code> for reading files line by line, and <code>UnitInfo</code> for volume information.</p><p><strong>Graphics</strong> &#8212; Bitmap rendering. Allocate and clone RastPorts, manage bitplanes dynamically, draw boxes and frames, and render printf-style formatted text directly to graphics surfaces.</p><p><strong>Libraries</strong> &#8212; String utilities and media parsing. Case conversion, character filtering, AIFF/IFF chunk parsing, and ILBM image decompression.</p><p><strong>Math</strong> &#8212; Number conversions between decimal, hex, binary strings and values &#8212; in both directions.</p><p><strong>Intuition &amp; GadTools</strong> &#8212; UI widgets. Custom dialog requesters with buttons, gadget property control by name (not just ID), and attribute manipulation.</p><p><strong>REI Interface</strong> &#8212; This is the crown jewel. A complete windowing abstraction that separates UI definitions from application logic. You define your window layout in a <code>.rei</code> binary file, load it at runtime, and interact with named gadgets through an event-driven message loop. Think of it as a declarative UI system &#8212; for a 1990s computer.</p><p><strong>C Interface</strong> &#8212; Full C prototypes and SAS/C pragma declarations so you can call every function from C with type safety.</p><h2><strong>The Magic of Auto-Opening</strong></h2><p>One of my favorite design decisions: when you open <code>assembly.library</code>, it automatically opens and caches <strong>12+ system libraries</strong> for you:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;7499db73-a434-4d2b-86c2-47ebac140316&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">#include &lt;assembly/assemblybase.h&gt;
#include &lt;clib/assembly_protos.h&gt;

struct AssemblyBase *AssemblyBase;

void main(int argc, char **argv)
{
    if (AssemblyBase = (struct AssemblyBase *)
        OpenLibrary(ASSEMBLYNAME, ASSEMBLY_MINIMUM))
    {
        /* All sub-libraries are now available:
         * AssemblyBase-&gt;ab_DosBase
         * AssemblyBase-&gt;ab_IntuiBase
         * AssemblyBase-&gt;ab_GfxBase
         * AssemblyBase-&gt;ab_GadToolsBase
         * AssemblyBase-&gt;ab_AslBase
         * ... and 7 more
         */

        CloseLibrary((struct Library *)AssemblyBase);
    }
}</code></pre></div><p>One <code>OpenLibrary</code> call and you get <code>graphics.library</code>, <code>intuition.library</code>, <code>dos.library</code>, <code>gadtools.library</code>, <code>asl.library</code>, <code>icon.library</code>, <code>workbench.library</code>, <code>datatypes.library</code>, <code>locale.library</code>, <code>lowlevel.library</code>, <code>realtime.library</code> &#8212; all ready to use. No boilerplate. No forgetting to close one of them.</p><h2><strong>The REI Windowing System</strong></h2><p>The REI (Runtime External Interface) module deserves its own section. Instead of hardcoding window layouts in your application code, you define them in <code>.rei</code> files &#8212; binary interface definitions that describe windows, gadgets, menus, and their relationships.</p><p>At runtime, you load the <code>.rei</code> file and interact with UI elements by <strong>name</strong>:</p><pre><code><code>OpenREI          &#8594; Create the window from a .rei file
WaitREIMsg       &#8594; Event-driven message loop
FindAsmGadget    &#8594; Find a gadget by its name
SetREIAttrs      &#8594; Change window properties
LockREI/UnlockREI &#8594; Thread-safe access
CloseREI         &#8594; Clean shutdown</code></code></pre><p>The project even includes a visual <strong>REI Editor</strong> (v0.52) &#8212; a GUI tool for designing <code>.rei</code> files without writing code.</p><p>This approach is philosophically similar to what modern frameworks do with JSX or XML layouts. But this was designed for AmigaOS, running on a 68020.</p><h2><strong>The Documentation Site</strong></h2><p>Here&#8217;s where old meets new. The library documentation lives at <strong><a href="https://amiga-assembly-library.vercel.app/">amiga-assembly-library.vercel.app</a></strong> &#8212; a modern site built with:</p><ul><li><p><strong>Next.js 15</strong> with Nextra for MDX-based docs</p></li><li><p><strong>Mantine 9</strong> for the UI components</p></li><li><p><strong>TypeScript 6</strong> for type safety</p></li><li><p>Syntax-highlighted 68020 assembly source code</p></li><li><p>Complete API reference for all 50+ functions</p></li><li><p>Example programs with full source</p></li><li><p>An <strong>Amiga Mode</strong> toggle that applies retro styling</p></li></ul><p>Yes, a documentation site built with React and TypeScript &#8212; for a 68020 assembly library. The irony is not lost on me.</p><h2><strong>Getting Started</strong></h2><h3><strong>Requirements</strong></h3><ul><li><p><strong>CPU</strong>: Motorola 68020 or higher</p></li><li><p><strong>OS</strong>: AmigaOS with KickStart 3.0 (V39) or later</p></li><li><p><strong>Assembler</strong>: HiSoft DevPac or compatible</p></li><li><p><strong>C Compiler</strong> (optional): SAS/C, DICE, or compatible</p></li></ul><h3><strong>Installation</strong></h3><p>Copy <code>assembly.library</code> to your <code>LIBS:</code> directory. That&#8217;s it &#8212; it&#8217;s a standard AmigaOS shared library, available system-wide to all applications.</p><h3><strong>Assembly Usage</strong></h3><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;44468569-cc48-4d3a-ac31-4ec2da48f64b&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">        move.l  _AbsExecBase,a6
        lea     AssemblyName(pc),a1
        moveq   #ASSEMBLY_MINIMUM,d0
        jsr     _LVOOpenLibrary(a6)
        move.l  d0,_AssemblyBase
        beq     .error

        ; Use library functions...
        move.l  _AssemblyBase,a6
        jsr     _LVOAllocNewList(a6)

        ; Clean up
        move.l  _AbsExecBase,a6
        move.l  _AssemblyBase,a1
        jsr     _LVOCloseLibrary(a6)</code></pre></div><h3><strong>C Usage</strong></h3><p>Include the headers, open the library, and call functions directly:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;e568ef46-81ed-42b0-a050-aca70a9e4aef&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">#include &lt;assembly/assemblybase.h&gt;
#include &lt;clib/assembly_protos.h&gt;
#pragma libcall AssemblyBase AllocNewList 1e 00

struct List *myList = AllocNewList();</code></pre></div><h2><strong>What&#8217;s in the Source?</strong></h2><p>The library is about 5,800 lines of hand-optimized 68020 assembly across 8 modules:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!X6mI!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F895a7aa8-89d0-485d-ae4c-19001a47fafc_1118x876.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!X6mI!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F895a7aa8-89d0-485d-ae4c-19001a47fafc_1118x876.png 424w, https://substackcdn.com/image/fetch/$s_!X6mI!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F895a7aa8-89d0-485d-ae4c-19001a47fafc_1118x876.png 848w, https://substackcdn.com/image/fetch/$s_!X6mI!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F895a7aa8-89d0-485d-ae4c-19001a47fafc_1118x876.png 1272w, https://substackcdn.com/image/fetch/$s_!X6mI!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F895a7aa8-89d0-485d-ae4c-19001a47fafc_1118x876.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!X6mI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F895a7aa8-89d0-485d-ae4c-19001a47fafc_1118x876.png" width="1118" height="876" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/895a7aa8-89d0-485d-ae4c-19001a47fafc_1118x876.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:876,&quot;width&quot;:1118,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:127406,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.undolog.com/i/193558127?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F895a7aa8-89d0-485d-ae4c-19001a47fafc_1118x876.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!X6mI!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F895a7aa8-89d0-485d-ae4c-19001a47fafc_1118x876.png 424w, https://substackcdn.com/image/fetch/$s_!X6mI!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F895a7aa8-89d0-485d-ae4c-19001a47fafc_1118x876.png 848w, https://substackcdn.com/image/fetch/$s_!X6mI!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F895a7aa8-89d0-485d-ae4c-19001a47fafc_1118x876.png 1272w, https://substackcdn.com/image/fetch/$s_!X6mI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F895a7aa8-89d0-485d-ae4c-19001a47fafc_1118x876.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>Plus C headers, pragma declarations, example programs (including a screen grabber, a window inspector, and the visual REI editor), and comprehensive documentation.</p><h2><strong>Why Open Source This?</strong></h2><p>Because the Amiga community thrives on sharing. The best Amiga software was always the stuff people passed around on floppy disks, uploaded to Aminet, and shared freely. This library is Public Domain &#8212; not MIT, not GPL, not Apache. <strong>Public Domain</strong>. Take it, use it, modify it, sell it, whatever you want. No attribution required.</p><p>If even one person building an Amiga project finds a useful function in here, it was worth it.</p><h2><strong>Links</strong></h2><ul><li><p><strong>Documentation</strong>: <a href="https://amiga-assembly-library.vercel.app/">amiga-assembly-library.vercel.app</a></p></li><li><p><strong>Source Code</strong>: <a href="https://github.com/gfazioli/Assembly-Library">github.com/gfazioli/Assembly-Library</a></p></li><li><p><strong>Documentation Source</strong>: <a href="https://github.com/gfazioli/amiga-assembly-library">github.com/gfazioli/amiga-assembly-library</a></p></li></ul><div><hr></div><p>This code is over 30 years old. I wrote it when I was a teenager, on a machine with 2 MB of RAM. And somehow it still works, still compiles, and still does exactly what it was designed to do. There&#8217;s something deeply satisfying about that.</p><p>If you&#8217;re into retro computing, 68k assembly, or just appreciate the craft of writing software for constrained platforms &#8212; I&#8217;d love to hear from you. Drop a star on the repo, open an issue, or just say hi.</p><p>The Amiga isn&#8217;t dead. It&#8217;s just warming up.</p>]]></content:encoded></item><item><title><![CDATA[Selection Through the Looking Glass: A 3D Stack Component for Mantine]]></title><description><![CDATA[A Mantine component that turns flat item selection into a spatial, 3D card-stack browsing experience &#8212; with keyboard, wheel, touch, and click navigation built in.]]></description><link>https://www.undolog.com/p/selection-through-the-looking-glass</link><guid isPermaLink="false">https://www.undolog.com/p/selection-through-the-looking-glass</guid><dc:creator><![CDATA[Giovambattista Fazioli]]></dc:creator><pubDate>Sat, 28 Mar 2026 08:56:06 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!G8mH!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78993f24-a4a1-45f9-85a1-06e8f3ee7d34_1280x640.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!G8mH!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78993f24-a4a1-45f9-85a1-06e8f3ee7d34_1280x640.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!G8mH!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78993f24-a4a1-45f9-85a1-06e8f3ee7d34_1280x640.jpeg 424w, https://substackcdn.com/image/fetch/$s_!G8mH!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78993f24-a4a1-45f9-85a1-06e8f3ee7d34_1280x640.jpeg 848w, https://substackcdn.com/image/fetch/$s_!G8mH!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78993f24-a4a1-45f9-85a1-06e8f3ee7d34_1280x640.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!G8mH!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78993f24-a4a1-45f9-85a1-06e8f3ee7d34_1280x640.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!G8mH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78993f24-a4a1-45f9-85a1-06e8f3ee7d34_1280x640.jpeg" width="1280" height="640" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/78993f24-a4a1-45f9-85a1-06e8f3ee7d34_1280x640.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:640,&quot;width&quot;:1280,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:94203,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.undolog.com/i/192390114?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78993f24-a4a1-45f9-85a1-06e8f3ee7d34_1280x640.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!G8mH!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78993f24-a4a1-45f9-85a1-06e8f3ee7d34_1280x640.jpeg 424w, https://substackcdn.com/image/fetch/$s_!G8mH!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78993f24-a4a1-45f9-85a1-06e8f3ee7d34_1280x640.jpeg 848w, https://substackcdn.com/image/fetch/$s_!G8mH!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78993f24-a4a1-45f9-85a1-06e8f3ee7d34_1280x640.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!G8mH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78993f24-a4a1-45f9-85a1-06e8f3ee7d34_1280x640.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2><strong>Introduction</strong></h2><p>Remember the first time you used macOS Time Machine? That moment when your desktop peeled away into an infinite corridor of snapshots, and navigating through time suddenly felt <em>physical</em>. That spatial metaphor stuck with us. <strong>mantine-depth-select</strong> brings that same sense of depth to any selection UI in your React application &#8212; pricing plans, document versions, onboarding steps, or anything else where browsing through stacked content beats scrolling a flat list. It&#8217;s a single component with zero extra dependencies, built entirely on Mantine 8&#8217;s Styles API.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!eBN8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F346244d1-21bf-4c62-8868-7e4955ba577d_800x879.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!eBN8!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F346244d1-21bf-4c62-8868-7e4955ba577d_800x879.webp 424w, https://substackcdn.com/image/fetch/$s_!eBN8!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F346244d1-21bf-4c62-8868-7e4955ba577d_800x879.webp 848w, https://substackcdn.com/image/fetch/$s_!eBN8!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F346244d1-21bf-4c62-8868-7e4955ba577d_800x879.webp 1272w, https://substackcdn.com/image/fetch/$s_!eBN8!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F346244d1-21bf-4c62-8868-7e4955ba577d_800x879.webp 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!eBN8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F346244d1-21bf-4c62-8868-7e4955ba577d_800x879.webp" width="800" height="879" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/346244d1-21bf-4c62-8868-7e4955ba577d_800x879.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:879,&quot;width&quot;:800,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;mantine Depth Select&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="mantine Depth Select" title="mantine Depth Select" srcset="https://substackcdn.com/image/fetch/$s_!eBN8!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F346244d1-21bf-4c62-8868-7e4955ba577d_800x879.webp 424w, https://substackcdn.com/image/fetch/$s_!eBN8!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F346244d1-21bf-4c62-8868-7e4955ba577d_800x879.webp 848w, https://substackcdn.com/image/fetch/$s_!eBN8!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F346244d1-21bf-4c62-8868-7e4955ba577d_800x879.webp 1272w, https://substackcdn.com/image/fetch/$s_!eBN8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F346244d1-21bf-4c62-8868-7e4955ba577d_800x879.webp 1456w" sizes="100vw"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2><strong>What is DepthSelect?</strong></h2><p>DepthSelect renders an array of items as a 3D stack of cards. The frontmost card is the active selection; cards behind it recede with progressive scale, vertical offset, opacity, and blur &#8212; all configurable per-level. When the user navigates (via keyboard, mouse wheel, trackpad swipe, click, or arrow controls), cards animate smoothly in and out of the stack with CSS transitions.</p><p>The component follows Mantine&#8217;s compound component pattern. Built-in arrow controls appear by default, but you can hide them and wire up your own UI through controlled <code>value</code>/<code>onChange</code>. It supports both string and numeric values, loop mode, and respects <code>prefers-reduced-motion</code>.</p><h2><strong>&#10024; Key Features</strong></h2><h3><strong>3D Perspective Stack</strong></h3><p>Each card in the stack receives progressive CSS transforms based on its depth. Four props control the effect:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;tsx&quot;,&quot;nodeId&quot;:&quot;c4fcb0f0-102c-4845-9c44-bd93ca682cd0&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-tsx">&lt;DepthSelect
  data={items}
  scaleStep={0.1}       // scale reduction per level (default)
  translateYStep={30}    // vertical offset in px per level
  opacityStep={0.15}     // opacity reduction per level
  blurStep={1}           // blur in px per level
  w={400}
  h={300}
/&gt;</code></pre></div><p>Cards that exit the stack (when navigating forward) animate toward the viewer with a scale-up and fade-out. Cards entering from behind animate in from the deepest visible position.</p><h3><strong>Multi-Input Navigation</strong></h3><p>Every common input method works out of the box:</p><ul><li><p><strong>Keyboard</strong>: <code>ArrowUp</code>/<code>ArrowDown</code> to step through, <code>Home</code>/<code>End</code> to jump</p></li><li><p><strong>Mouse wheel / Trackpad</strong>: Scroll over the component to navigate (page scroll is blocked automatically). Disable with <code>withScrollNavigation={false}</code></p></li><li><p><strong>Touch</strong>: Vertical swipe gestures for mobile</p></li><li><p><strong>Click</strong>: Click the second card (depth 1) to advance</p></li><li><p><strong>Controls</strong>: Built-in arrow buttons with optional label</p></li></ul><h3><strong>Built-in Controls with </strong><code>controlsProps</code></h3><p>The default controls sit to the right of the stack. You can reposition them, add a label, set a fixed width, change alignment, or swap the icons &#8212; all through a single <code>controlsProps</code> object:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;tsx&quot;,&quot;nodeId&quot;:&quot;6d0b5bc3-fac3-4611-b58f-16eaec6d491a&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-tsx">&lt;DepthSelect
  data={items}
  controlsPosition="left"
  controlsProps={{
    w: 100,
    justify: 'end',
    labelFormatter: (item) =&gt; `Step ${item.value}`,
    upIcon: &lt;IconChevronUp size={16} /&gt;,
    downIcon: &lt;IconChevronDown size={16} /&gt;,
  }}
  w={400}
  h={300}
/&gt;</code></pre></div><h3><strong>Compound Component Pattern</strong></h3><p>Need full layout control? Set <code>withControls={false}</code> and use <code>DepthSelect.Controls</code> as a child:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;tsx&quot;,&quot;nodeId&quot;:&quot;6ab34166-c4d0-4bd9-8e43-da91860dc563&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-tsx">&lt;DepthSelect data={items} withControls={false} w={400} h={300}&gt;
  &lt;DepthSelect.Controls labelFormatter={(item) =&gt; String(item.value)} /&gt;
&lt;/DepthSelect&gt;</code></pre></div><p>Or skip the built-in controls entirely and build your own navigation with <code>value</code> and <code>onChange</code>.</p><h3><strong>Loop Mode</strong></h3><p>Enable <code>loop</code> to let navigation wrap from the last item back to the first (and vice versa). The arrow controls never show a disabled state in loop mode:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;tsx&quot;,&quot;nodeId&quot;:&quot;b3e559d0-0910-4ca8-99ab-027fd25da2d4&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-tsx">&lt;DepthSelect data={items} loop w={400} h={200} /&gt;</code></pre></div><h2><strong>&#128640; Getting Started</strong></h2><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:&quot;65d2c80c-a92e-4d1f-91da-be87071ac7ec&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">npm install @gfazioli/mantine-depth-select</code></pre></div><p>Import the styles at the root of your application:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;e3122884-0688-405a-aec6-3a7760ff9800&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">import '@gfazioli/mantine-depth-select/styles.css';</code></pre></div><p>Minimal example:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;tsx&quot;,&quot;nodeId&quot;:&quot;b728e9d7-4c67-4e40-aac0-f260722d4331&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-tsx">import { Card, Text } from '@mantine/core';
import { DepthSelect, DepthSelectItem } from '@gfazioli/mantine-depth-select';

const data: DepthSelectItem[] = [
  { value: 'first', view: &lt;Card p="lg" withBorder h="100%"&gt;&lt;Text&gt;First item&lt;/Text&gt;&lt;/Card&gt; },
  { value: 'second', view: &lt;Card p="lg" withBorder h="100%"&gt;&lt;Text&gt;Second item&lt;/Text&gt;&lt;/Card&gt; },
  { value: 'third', view: &lt;Card p="lg" withBorder h="100%"&gt;&lt;Text&gt;Third item&lt;/Text&gt;&lt;/Card&gt; },
];

function Demo() {
  return &lt;DepthSelect data={data} w={400} h={200} /&gt;;
}</code></pre></div><p>The <code>w</code> and <code>h</code> props define the area where cards live. Cards should use <code>h="100%"</code> to fill the available height.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!kGf1!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41bfa50e-7554-4160-9a64-653747353588_800x515.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!kGf1!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41bfa50e-7554-4160-9a64-653747353588_800x515.webp 424w, https://substackcdn.com/image/fetch/$s_!kGf1!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41bfa50e-7554-4160-9a64-653747353588_800x515.webp 848w, https://substackcdn.com/image/fetch/$s_!kGf1!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41bfa50e-7554-4160-9a64-653747353588_800x515.webp 1272w, https://substackcdn.com/image/fetch/$s_!kGf1!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41bfa50e-7554-4160-9a64-653747353588_800x515.webp 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!kGf1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41bfa50e-7554-4160-9a64-653747353588_800x515.webp" width="800" height="515" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/41bfa50e-7554-4160-9a64-653747353588_800x515.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:515,&quot;width&quot;:800,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Use Cases&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Use Cases" title="Use Cases" srcset="https://substackcdn.com/image/fetch/$s_!kGf1!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41bfa50e-7554-4160-9a64-653747353588_800x515.webp 424w, https://substackcdn.com/image/fetch/$s_!kGf1!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41bfa50e-7554-4160-9a64-653747353588_800x515.webp 848w, https://substackcdn.com/image/fetch/$s_!kGf1!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41bfa50e-7554-4160-9a64-653747353588_800x515.webp 1272w, https://substackcdn.com/image/fetch/$s_!kGf1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41bfa50e-7554-4160-9a64-653747353588_800x515.webp 1456w" sizes="auto, 100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2><strong>Props &amp; API</strong></h2><h3><strong>DepthSelect</strong></h3><ul><li><p><code>data</code> &#8212; <code>DepthSelectItem[]</code> &#8212; Array of <code>{ value, view }</code> objects. <code>value</code> can be <code>string</code> or <code>number</code>, <code>view</code> is any <code>ReactNode</code></p></li><li><p><code>value</code> / <code>defaultValue</code> / <code>onChange</code> &#8212; Standard controlled/uncontrolled pattern</p></li><li><p><code>w</code> / <code>h</code> &#8212; Dimensions of the card area (passed to the inner stack container)</p></li><li><p><code>visibleCards</code> &#8212; Number of cards visible in the stack (default: <code>4</code>)</p></li><li><p><code>withControls</code> &#8212; Show built-in arrow controls (default: <code>true</code>)</p></li><li><p><code>controlsPosition</code> &#8212; <code>"right"</code> (default) or <code>"left"</code></p></li><li><p><code>controlsProps</code> &#8212; All <code>DepthSelectControlsProps</code> passed to the built-in controls</p></li><li><p><code>loop</code> &#8212; Enable wrap-around navigation (default: <code>false</code>)</p></li><li><p><code>withScrollNavigation</code> &#8212; Enable mouse wheel / trackpad navigation (default: <code>true</code>)</p></li><li><p><code>transitionDuration</code> &#8212; Animation duration in ms (default: <code>400</code>)</p></li><li><p><code>scaleStep</code> / <code>translateYStep</code> / <code>opacityStep</code> / <code>blurStep</code> &#8212; Per-level depth effect parameters</p></li></ul><h3><strong>DepthSelect.Controls</strong></h3><ul><li><p><code>labelFormatter</code> &#8212; <code>(item: DepthSelectItem) =&gt; ReactNode</code> &#8212; Custom label between arrows</p></li><li><p><code>upIcon</code> / <code>downIcon</code> &#8212; Custom icons for the arrow buttons</p></li><li><p><code>justify</code> &#8212; Vertical alignment: <code>"start"</code>, <code>"center"</code> (default), <code>"end"</code></p></li><li><p>Plus all Mantine <code>BoxProps</code> (<code>w</code>, <code>h</code>, <code>style</code>, etc.)</p></li></ul><h2><strong>&#127912; Styles API</strong></h2><p><strong>Selectors</strong>: <code>root</code>, <code>stack</code>, <code>card</code>, <code>controls</code>, <code>controlUp</code>, <code>controlDown</code>, <code>controlLabel</code></p><p><strong>Data attributes</strong>:</p><ul><li><p><code>data-active</code> &#8212; Frontmost (selected) card</p></li><li><p><code>data-depth="N"</code> &#8212; Card depth level (0 = front)</p></li><li><p><code>data-exited</code> &#8212; Card that has animated out</p></li><li><p><code>data-disabled</code> &#8212; Disabled control button</p></li><li><p><code>data-controls-position</code> &#8212; <code>"right"</code> or <code>"left"</code> on root</p></li></ul><p><strong>CSS variables</strong>: <code>--ds-transition-duration</code>, <code>--ds-scale-step</code>, <code>--ds-translate-y-step</code>, <code>--ds-opacity-step</code>, <code>--ds-blur-step</code>, <code>--ds-visible-cards</code></p><h2><strong>&#9889; Performance</strong></h2><ul><li><p><strong>Render window</strong>: Only <code>activeIndex - 1</code> through <code>activeIndex + visibleCards + 1</code> are in the DOM. A dataset of 100 items renders ~6 nodes</p></li><li><p><strong>Memoized styles</strong>: Card CSS is computed once per navigation change via <code>useMemo</code></p></li><li><p><strong>Targeted </strong><code>will-change</code>: Only visible cards get GPU layer promotion; hidden cards use <code>will-change: auto</code></p></li><li><p><strong>Non-passive wheel listener</strong>: Blocks page scroll without layout thrashing</p></li><li><p><code>prefers-reduced-motion</code>: Transitions are disabled automatically for users who prefer reduced motion</p></li></ul><h2><strong>Live Demo &amp; Documentation</strong></h2><p>Explore interactive demos &#8212; including a configurator with live props, rich card examples, pricing plan selector, version history browser, onboarding wizard, and more:</p><p><strong><a href="https://gfazioli.github.io/mantine-depth-select/">gfazioli.github.io/mantine-depth-select</a></strong></p><h2><strong>Links</strong></h2><ul><li><p>&#128230; npm: <a href="https://www.npmjs.com/package/@gfazioli/mantine-depth-select">@gfazioli/mantine-depth-select</a></p></li><li><p>&#128214; Docs: <a href="https://gfazioli.github.io/mantine-depth-select/">gfazioli.github.io/mantine-depth-select</a></p></li><li><p>&#128279; GitHub: <a href="https://github.com/gfazioli/mantine-depth-select">github.com/gfazioli/mantine-depth-select</a></p></li><li><p>&#129513; Mantine Extensions: <a href="https://mantine-extensions.vercel.app/">mantine-extensions.vercel.app</a></p></li></ul>]]></content:encoded></item><item><title><![CDATA[Mantine Border Animate - A Complete Beam Rewrite with Dual Rendering Modes]]></title><description><![CDATA[The beam variant has been completely rebuilt with two rendering engines, multi-color gradient support, and a streamlined API.]]></description><link>https://www.undolog.com/p/mantine-border-animate-a-complete</link><guid isPermaLink="false">https://www.undolog.com/p/mantine-border-animate-a-complete</guid><dc:creator><![CDATA[Giovambattista Fazioli]]></dc:creator><pubDate>Fri, 27 Mar 2026 05:27:36 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!v4a0!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29ed6546-b665-47d9-ac60-a81c8c2a1eb8_1280x640.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!v4a0!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29ed6546-b665-47d9-ac60-a81c8c2a1eb8_1280x640.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!v4a0!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29ed6546-b665-47d9-ac60-a81c8c2a1eb8_1280x640.jpeg 424w, https://substackcdn.com/image/fetch/$s_!v4a0!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29ed6546-b665-47d9-ac60-a81c8c2a1eb8_1280x640.jpeg 848w, https://substackcdn.com/image/fetch/$s_!v4a0!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29ed6546-b665-47d9-ac60-a81c8c2a1eb8_1280x640.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!v4a0!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29ed6546-b665-47d9-ac60-a81c8c2a1eb8_1280x640.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!v4a0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29ed6546-b665-47d9-ac60-a81c8c2a1eb8_1280x640.jpeg" width="1280" height="640" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/29ed6546-b665-47d9-ac60-a81c8c2a1eb8_1280x640.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:640,&quot;width&quot;:1280,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:230648,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.undolog.com/i/192025021?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29ed6546-b665-47d9-ac60-a81c8c2a1eb8_1280x640.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!v4a0!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29ed6546-b665-47d9-ac60-a81c8c2a1eb8_1280x640.jpeg 424w, https://substackcdn.com/image/fetch/$s_!v4a0!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29ed6546-b665-47d9-ac60-a81c8c2a1eb8_1280x640.jpeg 848w, https://substackcdn.com/image/fetch/$s_!v4a0!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29ed6546-b665-47d9-ac60-a81c8c2a1eb8_1280x640.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!v4a0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29ed6546-b665-47d9-ac60-a81c8c2a1eb8_1280x640.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2><strong>Introduction</strong></h2><p><a href="https://gfazioli.github.io/mantine-border-animate/">Version 1.0.0 of </a><code>@gfazioli/mantine-border-animate</code> is a major release that reimagines how animated borders work. The beam effect &#8212; the heart of the component &#8212; now ships with two distinct rendering modes, letting you choose between pixel-perfect border tracing and smooth conic rotation. Combined with new multi-color gradients, hover pause, custom easing, and built-in accessibility, this release makes animated borders more flexible, more beautiful, and easier to use than ever.</p><h2><strong>What&#8217;s New</strong></h2><h3><strong>Two Beam Rendering Modes</strong></h3><p>The biggest addition in v1.0 is the <code>beamMode</code> prop, which lets you choose how the beam effect is rendered:</p><p><strong>Path mode</strong> (<code>beamMode="path"</code>, the new default) uses CSS <code>offset-path</code> to move a radial-gradient circle along the component&#8217;s border. The beam maintains a uniform size at every position and travels at constant speed along the perimeter. This is the best choice for most UI elements.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;tsx&quot;,&quot;nodeId&quot;:&quot;efa98fc6-9506-44a1-b59f-32798d149ebd&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-tsx">&lt;BorderAnimate beamMode="path" size="md" duration={5}&gt;
  &lt;Card&gt;Your content&lt;/Card&gt;
&lt;/BorderAnimate&gt;
</code></pre></div><p><strong>Conic mode</strong> (<code>beamMode="conic"</code>) uses a rotating <code>conic-gradient</code> wedge animated via CSS <code>@property</code>. The rotation is buttery smooth, and the <code>size</code> prop controls the angular spread of the wedge (from a tight 18-degree spotlight at <code>xs</code> to a full 180-degree sweep at <code>xl</code>). It works best on circular or near-square elements.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;tsx&quot;,&quot;nodeId&quot;:&quot;e95b2633-2e18-4a31-b34e-d80e58371a3b&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-tsx">&lt;BorderAnimate beamMode="conic" size="lg" duration={3}&gt;
  &lt;Avatar radius="100%" /&gt;
&lt;/BorderAnimate&gt;
</code></pre></div><h3><strong>Multi-Color Gradients with </strong><code>colorStops</code></h3><p>Move beyond two-color gradients. The new <code>colorStops</code> prop gives you full control over the beam&#8217;s colors:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;tsx&quot;,&quot;nodeId&quot;:&quot;213bd6d6-74a2-45aa-94b3-31701b29bcdb&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-tsx">&lt;BorderAnimate
  colorStops={[
    { color: 'transparent', position: 0 },
    { color: 'green', position: 20 },
    { color: 'cyan', position: 40 },
    { color: 'yellow', position: 60 },
    { color: 'red', position: 80 },
    { color: 'transparent', position: 100 },
  ]}
&gt;
  &lt;Paper&gt;Multi-color beam&lt;/Paper&gt;
&lt;/BorderAnimate&gt;
</code></pre></div><p><code>colorStops</code> works with both beam modes:</p><ul><li><p><strong>Path mode</strong> generates a <code>radial-gradient</code> with concentric color rings</p></li><li><p><strong>Conic mode</strong> generates a custom <code>conic-gradient</code> &#8212; use transparent stops for beam/wedge effects, or fill the entire circle for a rotating rainbow border</p></li></ul><h3><strong>Pause on Hover</strong></h3><p>Interactive elements often need the animation to pause during user interaction. The new <code>pauseOnHover</code> prop does exactly that &#8212; using pure CSS <code>animation-play-state</code> with zero JavaScript overhead:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;tsx&quot;,&quot;nodeId&quot;:&quot;6d8ac68a-f51b-428e-ba67-21e2c934445f&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-tsx">&lt;BorderAnimate pauseOnHover&gt;
  &lt;Button&gt;Hover me to pause&lt;/Button&gt;
&lt;/BorderAnimate&gt;
</code></pre></div><h3><strong>Custom Timing Functions</strong></h3><p>Fine-tune animation feel with <code>timingFunction</code>. By default, beam uses <code>linear</code> and glow/pulse use <code>ease-in-out</code>, but you can override with any CSS timing function:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;tsx&quot;,&quot;nodeId&quot;:&quot;0e30a8aa-7a46-48dc-b832-a70c1659513a&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-tsx">&lt;BorderAnimate timingFunction="ease-in-out" /&gt;
&lt;BorderAnimate timingFunction="cubic-bezier(0.4, 0, 0.2, 1)" /&gt;
&lt;BorderAnimate timingFunction="steps(8)" /&gt;  {/* retro pixel effect */}
</code></pre></div><h3><strong>Accessibility Out of the Box</strong></h3><p>BorderAnimate now respects <code>prefers-reduced-motion</code> automatically. Users who have enabled &#8220;Reduce motion&#8221; in their OS settings will see no animations &#8212; no configuration needed, no extra props. It just works.</p><h2><strong>Breaking Changes</strong></h2><p>This is a major release with several breaking changes. Here&#8217;s what you need to update:</p><h3><code>gradient</code><strong> variant removed</strong></h3><p>The <code>gradient</code> variant is gone. Use <code>beamMode="conic"</code> with full <code>colorStops</code> instead:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;tsx&quot;,&quot;nodeId&quot;:&quot;1fb24ccb-f3dc-4107-81d0-b68d9e996cb4&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-tsx">// v0.x
&lt;BorderAnimate variant="gradient" colorFrom="red" colorTo="blue" /&gt;

// v1.0
&lt;BorderAnimate
  beamMode="conic"
  colorStops={[
    { color: 'red', position: 0 },
    { color: 'blue', position: 50 },
    { color: 'red', position: 100 },
  ]}
/&gt;
</code></pre></div><h3><code>opacity</code><strong> renamed to </strong><code>borderOpacity</code></h3><p>The old <code>opacity</code> prop conflicted with the CSS <code>opacity</code> property inherited from Mantine&#8217;s <code>BoxProps</code>. It&#8217;s now <code>borderOpacity</code>:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;tsx&quot;,&quot;nodeId&quot;:&quot;c92d322e-d941-44ee-897d-6c1d2420933d&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-tsx">// v0.x
&lt;BorderAnimate opacity={0.5} /&gt;

// v1.0
&lt;BorderAnimate borderOpacity={0.5} /&gt;
</code></pre></div><h3><code>anchor</code><strong> and </strong><code>count</code><strong> props removed</strong></h3><p>Both props have been removed with no direct replacement. Simply remove them from your components.</p><h3><code>size</code><strong> semantics changed</strong></h3><p>The <code>size</code> prop now behaves differently depending on <code>beamMode</code>:</p><ul><li><p><strong>Path mode</strong>: still pixel-based (<code>xs</code>=64px to <code>xl</code>=480px) &#8212; same as v0.x</p></li><li><p><strong>Conic mode</strong>: angular spread of the wedge (<code>xs</code>=18 degrees to <code>xl</code>=180 degrees)</p></li></ul><h3><code>BorderAnimateVariant</code><strong> type updated</strong></h3><p>The type changed from <code>'beam' | 'glow' | 'gradient' | 'pulse'</code> to <code>'beam' | 'glow' | 'pulse'</code>. Update any type assertions or switch statements in your code.</p><h2><strong>Improvements</strong></h2><ul><li><p><strong>Glow z-index fix</strong> &#8212; the glow variant now correctly renders behind content, preventing the overlay from obscuring text</p></li><li><p><strong>Accurate demos</strong> &#8212; all documentation demos now show code that exactly matches the rendered output</p></li><li><p><strong>New exported types</strong> &#8212; <code>BorderAnimateBeamMode</code>, <code>BorderAnimateColorStop</code>, and <code>BorderAnimateVariant</code> are now properly exported for consumers</p></li></ul><h2><strong>Getting Started</strong></h2><p>Install or update:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:&quot;8b10b4a9-a5eb-4d0c-a5c8-bef28d844f3a&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">npm install @gfazioli/mantine-border-animate@1
</code></pre></div><p>Import styles at the root of your application:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;c019bbdc-c3fe-473e-8165-b8f7bf979f6a&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">import '@gfazioli/mantine-border-animate/styles.css';
</code></pre></div><p>Basic usage:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;tsx&quot;,&quot;nodeId&quot;:&quot;3ee65ebb-7615-4f56-b0a8-1b40958c49d9&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-tsx">import { BorderAnimate } from '@gfazioli/mantine-border-animate';

function Demo() {
  return (
    &lt;BorderAnimate&gt;
      &lt;Paper withBorder p="md" radius="md"&gt;
        Your content with an animated border
      &lt;/Paper&gt;
    &lt;/BorderAnimate&gt;
  );
}
</code></pre></div><h2><strong>Links</strong></h2><ul><li><p><a href="https://www.npmjs.com/package/@gfazioli/mantine-border-animate">npm package</a></p></li><li><p><a href="https://gfazioli.github.io/mantine-border-animate/">Documentation and Demos</a></p></li><li><p><a href="https://github.com/gfazioli/mantine-border-animate">GitHub Repository</a></p></li><li><p><a href="https://mantine-extensions.vercel.app/">Mantine Extensions Hub</a></p></li></ul>]]></content:encoded></item><item><title><![CDATA[Mantine Rings Progress - A Complete Rewrite Inspired by Apple Watch]]></title><description><![CDATA[The concentric ring progress component for Mantine gets a major upgrade: native RingProgress, glow effects, staggered animations, unified tooltips, and full accessibility &#8212; all in one release.]]></description><link>https://www.undolog.com/p/mantine-rings-progress-a-complete</link><guid isPermaLink="false">https://www.undolog.com/p/mantine-rings-progress-a-complete</guid><dc:creator><![CDATA[Giovambattista Fazioli]]></dc:creator><pubDate>Wed, 25 Mar 2026 11:25:39 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/04f112e3-c3b5-49bc-91c1-e99be91a6286_1280x640.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;4a393aa7-8e00-4872-b5b2-4d9fd0990193&quot;,&quot;duration&quot;:null}"></div><h2><strong>Introduction</strong></h2><p><a href="https://gfazioli.github.io/mantine-rings-progress/">Version 3.0.0</a> of <code>@gfazioli/mantine-rings-progress</code> is a ground-up rewrite. The internal fork of Mantine&#8217;s <code>RingProgress</code> has been completely removed, replaced by the native component from <code>@mantine/core</code>. This eliminates ~600 lines of custom SVG and animation code while adding 10 new features that bring the component closer to the Apple Watch activity rings experience.</p><p>Whether you&#8217;re building fitness dashboards, goal trackers, or compact data visualizations, this release gives you the tools to make your rings shine &#8212; literally.</p><h2><strong>&#10024; New Features</strong></h2><h3><strong>Per-ring Customization</strong></h3><p>Each ring can now override global props individually. Mix thick and thin rings, toggle round caps per ring, or set a custom background track color:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;tsx&quot;,&quot;nodeId&quot;:&quot;c905b75f-ece9-437d-b542-df625deec20d&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-tsx">const rings = [
  { value: 40, color: 'cyan', thickness: 18 },
  { value: 65, color: 'red', thickness: 10, roundCaps: false },
  { value: 90, color: '#f90', thickness: 6, rootColor: 'gray' },
];

&lt;RingsProgress size={180} rings={rings} /&gt;
</code></pre></div><p>Supported per-ring overrides: <code>thickness</code>, <code>roundCaps</code>, <code>rootColor</code>, <code>glowIntensity</code>, <code>glowColor</code>, <code>ariaLabel</code>, <code>tooltip</code>.</p><h3><strong>Staggered Entrance Animation</strong></h3><p>Instead of all rings appearing at once, use <code>staggerDelay</code> to animate them one after another &#8212; outer ring first, inner ring last:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;tsx&quot;,&quot;nodeId&quot;:&quot;50a47541-4c7a-4dc2-9e51-de25e9345da2&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-tsx">&lt;RingsProgress animate staggerDelay={300} transitionDuration={1000} rings={rings} /&gt;
</code></pre></div><h3><strong>Glow / Neon Effect</strong></h3><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!JrEf!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa54cb2e5-cb37-44ab-9b6b-d71a96fb18ea_800x522.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!JrEf!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa54cb2e5-cb37-44ab-9b6b-d71a96fb18ea_800x522.webp 424w, https://substackcdn.com/image/fetch/$s_!JrEf!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa54cb2e5-cb37-44ab-9b6b-d71a96fb18ea_800x522.webp 848w, https://substackcdn.com/image/fetch/$s_!JrEf!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa54cb2e5-cb37-44ab-9b6b-d71a96fb18ea_800x522.webp 1272w, https://substackcdn.com/image/fetch/$s_!JrEf!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa54cb2e5-cb37-44ab-9b6b-d71a96fb18ea_800x522.webp 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!JrEf!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa54cb2e5-cb37-44ab-9b6b-d71a96fb18ea_800x522.webp" width="800" height="522" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a54cb2e5-cb37-44ab-9b6b-d71a96fb18ea_800x522.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:522,&quot;width&quot;:800,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Glow&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Glow" title="Glow" srcset="https://substackcdn.com/image/fetch/$s_!JrEf!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa54cb2e5-cb37-44ab-9b6b-d71a96fb18ea_800x522.webp 424w, https://substackcdn.com/image/fetch/$s_!JrEf!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa54cb2e5-cb37-44ab-9b6b-d71a96fb18ea_800x522.webp 848w, https://substackcdn.com/image/fetch/$s_!JrEf!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa54cb2e5-cb37-44ab-9b6b-d71a96fb18ea_800x522.webp 1272w, https://substackcdn.com/image/fetch/$s_!JrEf!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa54cb2e5-cb37-44ab-9b6b-d71a96fb18ea_800x522.webp 1456w" sizes="auto, 100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Add a neon-like glow that follows the ring shape using CSS <code>drop-shadow</code>. Set <code>glow={true}</code> for a default 6px blur, or pass a custom radius. Each ring can override the effect with <code>glowIntensity</code> and <code>glowColor</code>:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;tsx&quot;,&quot;nodeId&quot;:&quot;3a49807d-cfb2-4990-84a0-cf30e3c3b141&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-tsx">&lt;RingsProgress glow={8} rings={rings} /&gt;
</code></pre></div><h3><strong>Pulse on Completion</strong></h3><p>When a ring reaches 100%, trigger a satisfying scale + brightness pulse animation &#8212; inspired by the Apple Watch ring closure celebration:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;tsx&quot;,&quot;nodeId&quot;:&quot;b8e88765-8b2c-4ab8-8669-f8be197d40aa&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-tsx">&lt;RingsProgress pulseOnComplete rings={rings} /&gt;
</code></pre></div><p>The animation respects <code>prefers-reduced-motion</code> and uses a <code>data-pulsing</code> attribute for CSS customization.</p><h3><code>onRingComplete</code><strong> Callback</strong></h3><p>Run custom logic when a ring crosses the 100% threshold:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;tsx&quot;,&quot;nodeId&quot;:&quot;519a2a26-7d00-41f3-9b8f-d046421e5638&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-tsx">&lt;RingsProgress
  onRingComplete={(index, ring) =&gt; {
    showNotification({ message: `${ring.color} ring completed!` });
  }}
  rings={rings}
/&gt;
</code></pre></div><h3><strong>Start Angle &amp; Direction</strong></h3><p>Control where rings start filling and in which direction:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;tsx&quot;,&quot;nodeId&quot;:&quot;c90e1fe5-c929-45c3-8889-815bbcc9392b&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-tsx">&lt;RingsProgress startAngle={90} direction="counterclockwise" rings={rings} /&gt;
</code></pre></div><p><code>startAngle={0}</code> is the 12 o&#8217;clock position (default). Direction accepts <code>'clockwise'</code> or <code>'counterclockwise'</code>.</p><h3><strong>Unified Tooltip</strong></h3><p>The new <code>withTooltip</code> prop shows a single, chart-style tooltip with color swatches for all rings &#8212; no more overlapping per-ring tooltips:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;tsx&quot;,&quot;nodeId&quot;:&quot;42397d83-6b5f-42c5-8796-e235e3798fc2&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-tsx">&lt;RingsProgress
  withTooltip
  rings={[
    { value: 20, color: 'green', tooltip: 'Fitness &#8211; 40 Gb' },
    { value: 80, color: 'blue', tooltip: 'Running &#8211; 50 min' },
  ]}
/&gt;
</code></pre></div><p>If <code>tooltip</code> is not set on a ring, the percentage value is shown automatically.</p><h3><strong>Accessibility</strong></h3><p>The component is now fully accessible out of the box:</p><ul><li><p>Root element: <code>role="group"</code> with <code>aria-label="Progress rings"</code> (overridable)</p></li><li><p>Each ring: <code>role="progressbar"</code> with <code>aria-valuenow</code>, <code>aria-valuemin</code>, <code>aria-valuemax</code></p></li><li><p>Per-ring <code>ariaLabel</code> for custom screen reader text</p></li><li><p>Automatic <code>prefers-reduced-motion</code> support via <code>useReducedMotion</code></p></li></ul><h2><strong>&#128165; Breaking Changes</strong></h2><p>This is a major release with breaking changes. Here&#8217;s what you need to update:</p><h3><strong>Animation props renamed/removed</strong></h3><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;tsx&quot;,&quot;nodeId&quot;:&quot;148c74f7-ee26-4e78-bfa9-978f4fc298e5&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-tsx">  &lt;RingsProgress
-   animationDuration={1000}
-   animationSteps={60}
-   animationTimingFunction="ease"
+   transitionDuration={1000}
    animate
    rings={rings}
  /&gt;
</code></pre></div><ul><li><p><code>animationDuration</code> &#8594; <code>transitionDuration</code> (default changed from <code>1000</code> to <code>0</code>)</p></li><li><p><code>animationSteps</code> &#8212; removed (CSS transitions replaced JS <code>setInterval</code>)</p></li><li><p><code>animationTimingFunction</code> &#8212; removed (CSS uses <code>ease</code> by default)</p></li></ul><blockquote><p><strong>Tip</strong>: If you only use <code>animate</code> for entrance animation, you can omit <code>transitionDuration</code> entirely &#8212; the component auto-applies 1000ms during the entrance phase.</p></blockquote><h3><strong>Ring type changed</strong></h3><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;660be7e2-cba3-4347-a7ff-304d9b153ab7&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">- import { RingProgressSection } from '@gfazioli/mantine-rings-progress';
- const rings: RingProgressSection[] = [...]
+ import { RingsProgressRing } from '@gfazioli/mantine-rings-progress';
+ const rings: RingsProgressRing[] = [...]
</code></pre></div><p>The base properties (<code>value</code>, <code>color</code>, <code>tooltip</code>) remain the same, so existing code that only uses those will work without changes.</p><h3><strong>Props interface</strong></h3><p><code>RingsProgressProps</code> now extends <code>BoxProps + StylesApiProps + ElementProps&lt;'div'&gt;</code> (standard Mantine pattern) instead of the forked <code>RingProgressProps</code>. All common props (<code>size</code>, <code>thickness</code>, <code>roundCaps</code>, <code>label</code>, <code>animate</code>) are still available.</p><h3><strong>Styles API</strong></h3><p>A new <code>'label'</code> style name was added. Style names are now <code>'root' | 'ring' | 'label'</code>.</p><h2><strong>&#128295; Improvements</strong></h2><h3><strong>Drastically simplified internals</strong></h3><p>The entire forked <code>RingProgress/</code> directory (~600 lines) has been deleted. The component now delegates entirely to Mantine&#8217;s native <code>RingProgress</code>, ensuring better compatibility with future Mantine releases and smaller bundle size.</p><h3><strong>Smart entrance animation</strong></h3><p>The component auto-applies 1000ms transition during the entrance phase only. After all rings mount, <code>transitionDuration</code> reverts to 0 &#8212; preventing geometry changes (like resizing) from triggering unwanted CSS transitions.</p><h3><strong>Interactive demos</strong></h3><p>The documentation now includes replay buttons for animation demos, interactive sliders for the pulse demo, and a configurator with all new props.</p><h2><strong>&#128027; Bug Fixes</strong></h2><ul><li><p><strong>Label centering</strong>: Fixed the label being offset from center &#8212; now rendered as a separate overlay</p></li><li><p><strong>Debug styles</strong>: Removed hardcoded <code>color: 'red'</code> on the label</p></li><li><p><strong>Theme overrides</strong>: Fixed <code>useProps('Rings', ...)</code> &#8594; <code>useProps('RingsProgress', ...)</code></p></li><li><p><strong>Countdown demo</strong>: Added <code>transitionDuration={0}</code> to prevent CSS transitions from interfering with rapid updates</p></li></ul><h2><strong>Getting Started</strong></h2><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:&quot;fb0cfa0b-b975-4400-985d-0ade9e32ee5a&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">npm install @gfazioli/mantine-rings-progress@3
</code></pre></div><p>Import styles at the root of your application:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;419d164a-09c4-48f6-9bc1-7f1f9e23eeba&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">import '@gfazioli/mantine-rings-progress/styles.css';
</code></pre></div><p>Basic usage with the new features:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;tsx&quot;,&quot;nodeId&quot;:&quot;d6b150aa-c2ba-436c-804d-a5fb367d236d&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-tsx">import { RingsProgress } from '@gfazioli/mantine-rings-progress';

function ActivityRings() {
  const rings = [
    { value: 75, color: 'green', tooltip: 'Move' },
    { value: 50, color: 'blue', tooltip: 'Exercise' },
    { value: 90, color: 'orange', tooltip: 'Stand' },
  ];

  return (
    &lt;RingsProgress
      size={180}
      rings={rings}
      animate
      staggerDelay={300}
      glow={6}
      withTooltip
      pulseOnComplete
      onRingComplete={(i) =&gt; console.log(`Ring ${i} done!`)}
    /&gt;
  );
}
</code></pre></div><h2><strong>Links</strong></h2><ul><li><p>&#128230; <a href="https://www.npmjs.com/package/@gfazioli/mantine-rings-progress">npm package</a></p></li><li><p>&#128214; <a href="https://gfazioli.github.io/mantine-rings-progress/">Documentation &amp; Demos</a></p></li><li><p>&#128279; <a href="https://github.com/gfazioli/mantine-rings-progress">GitHub Repository</a></p></li><li><p>&#129513; <a href="https://mantine-extensions.vercel.app/">Mantine Extensions Hub</a></p></li></ul>]]></content:encoded></item><item><title><![CDATA[Mantine QR Code - A Real QR Code Generator for Mantine]]></title><description><![CDATA[Generate fully scannable, beautifully styled QR codes with custom dot shapes, finder patterns, image overlays, and one-click SVG/PNG download &#8212; all integrated with the Mantine design system.]]></description><link>https://www.undolog.com/p/mantine-qr-code-a-real-qr-code-generator</link><guid isPermaLink="false">https://www.undolog.com/p/mantine-qr-code-a-real-qr-code-generator</guid><dc:creator><![CDATA[Giovambattista Fazioli]]></dc:creator><pubDate>Fri, 20 Mar 2026 09:01:19 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!AMjc!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf428c96-e466-4951-83ac-15d9be5e3835_1280x640.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!AMjc!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf428c96-e466-4951-83ac-15d9be5e3835_1280x640.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!AMjc!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf428c96-e466-4951-83ac-15d9be5e3835_1280x640.jpeg 424w, https://substackcdn.com/image/fetch/$s_!AMjc!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf428c96-e466-4951-83ac-15d9be5e3835_1280x640.jpeg 848w, https://substackcdn.com/image/fetch/$s_!AMjc!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf428c96-e466-4951-83ac-15d9be5e3835_1280x640.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!AMjc!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf428c96-e466-4951-83ac-15d9be5e3835_1280x640.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!AMjc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf428c96-e466-4951-83ac-15d9be5e3835_1280x640.jpeg" width="1280" height="640" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/cf428c96-e466-4951-83ac-15d9be5e3835_1280x640.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:640,&quot;width&quot;:1280,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:245090,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.undolog.com/i/191453687?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf428c96-e466-4951-83ac-15d9be5e3835_1280x640.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!AMjc!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf428c96-e466-4951-83ac-15d9be5e3835_1280x640.jpeg 424w, https://substackcdn.com/image/fetch/$s_!AMjc!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf428c96-e466-4951-83ac-15d9be5e3835_1280x640.jpeg 848w, https://substackcdn.com/image/fetch/$s_!AMjc!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf428c96-e466-4951-83ac-15d9be5e3835_1280x640.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!AMjc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf428c96-e466-4951-83ac-15d9be5e3835_1280x640.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2><strong>Introduction</strong></h2><p>The Mantine QR Code component has been completely rebuilt from the ground up. What was previously an LED-style indicator has been transformed into a fully functional QR code generator that produces real, scannable codes from any string input. It renders pure SVG for crisp output at any resolution, follows Mantine&#8217;s Styles API conventions, and ships with a dedicated download hook for exporting to SVG, PNG, JPEG, or WebP.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!oS71!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49bb3f12-b039-4d49-807b-8f99fffd6168_800x875.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!oS71!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49bb3f12-b039-4d49-807b-8f99fffd6168_800x875.webp 424w, https://substackcdn.com/image/fetch/$s_!oS71!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49bb3f12-b039-4d49-807b-8f99fffd6168_800x875.webp 848w, https://substackcdn.com/image/fetch/$s_!oS71!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49bb3f12-b039-4d49-807b-8f99fffd6168_800x875.webp 1272w, https://substackcdn.com/image/fetch/$s_!oS71!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49bb3f12-b039-4d49-807b-8f99fffd6168_800x875.webp 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!oS71!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49bb3f12-b039-4d49-807b-8f99fffd6168_800x875.webp" width="800" height="875" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/49bb3f12-b039-4d49-807b-8f99fffd6168_800x875.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:875,&quot;width&quot;:800,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Mantine QR Code&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Mantine QR Code" title="Mantine QR Code" srcset="https://substackcdn.com/image/fetch/$s_!oS71!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49bb3f12-b039-4d49-807b-8f99fffd6168_800x875.webp 424w, https://substackcdn.com/image/fetch/$s_!oS71!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49bb3f12-b039-4d49-807b-8f99fffd6168_800x875.webp 848w, https://substackcdn.com/image/fetch/$s_!oS71!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49bb3f12-b039-4d49-807b-8f99fffd6168_800x875.webp 1272w, https://substackcdn.com/image/fetch/$s_!oS71!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49bb3f12-b039-4d49-807b-8f99fffd6168_800x875.webp 1456w" sizes="100vw"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2><strong>&#10024; New Features</strong></h2><h3><strong>Real QR Code Generation</strong></h3><p>The component now encodes actual QR code data using the <code>qrcode</code> library. Pass any string &#8212; a URL, WiFi credentials, vCard, plain text &#8212; and get a scannable QR code rendered as optimized SVG.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;tsx&quot;,&quot;nodeId&quot;:&quot;516308d9-9c5f-494a-b16f-678f005b7acf&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-tsx">import { QRCode } from '@gfazioli/mantine-qr-code';

function Demo() {
  return &lt;QRCode value="https://mantine.dev" /&gt;;
}
</code></pre></div><p>The <code>value</code> prop is the only required prop. Everything else has sensible defaults.</p><h3><strong>Dot Styles</strong></h3><p>Control the appearance of data modules with the <code>dotStyle</code> prop. Three built-in styles are available:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;tsx&quot;,&quot;nodeId&quot;:&quot;99701b43-af42-49dd-b5fd-6a4a8768f6c1&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-tsx">&lt;QRCode value="https://mantine.dev" dotStyle="square" /&gt;   {/* Default */}
&lt;QRCode value="https://mantine.dev" dotStyle="rounded" /&gt;
&lt;QRCode value="https://mantine.dev" dotStyle="dots" /&gt;
</code></pre></div><p>Each style generates a single combined <code>&lt;path&gt;</code> element for all data modules, keeping the DOM lightweight even for dense QR codes.</p><h3><strong>Corner Styles</strong></h3><p>The three finder patterns (the large squares in the corners) can be styled independently with <code>cornerStyle</code>:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;tsx&quot;,&quot;nodeId&quot;:&quot;02906df3-d064-4ddd-b4c7-0b2c1f6f3f67&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-tsx">&lt;QRCode value="https://mantine.dev" cornerStyle="square" /&gt;   {/* Default */}
&lt;QRCode value="https://mantine.dev" cornerStyle="rounded" /&gt;
&lt;QRCode value="https://mantine.dev" cornerStyle="dots" /&gt;
</code></pre></div><p>Mix and match dot and corner styles for unique designs:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;tsx&quot;,&quot;nodeId&quot;:&quot;a508f8b4-882e-4b5c-9924-5e36eece525f&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-tsx">&lt;QRCode
  value="https://mantine.dev"
  dotStyle="dots"
  cornerStyle="rounded"
  color="violet"
/&gt;
</code></pre></div><h3><strong>Image Overlay with Excavation</strong></h3><p>Add a logo or image at the center of the QR code. The component automatically removes (&#8221;excavates&#8221;) the data modules behind the image to keep the code scannable:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;tsx&quot;,&quot;nodeId&quot;:&quot;35ab1180-023a-4e4a-8e17-bd44004ca6d6&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-tsx">&lt;QRCode
  value="https://mantine.dev"
  size="xl"
  image="https://example.com/logo.png"
  imageSize={0.2}
  imageRadius="md"
  imagePadding={1}
  imageExcavate={true}
  errorCorrectionLevel="H"
/&gt;
</code></pre></div><blockquote><p><strong>Tip:</strong> Use <code>errorCorrectionLevel="H"</code> when overlaying images &#8212; it provides 30% error correction, ensuring the code remains scannable even with a portion covered.</p></blockquote><h3><strong>Error Correction Levels</strong></h3><p>Four standard error correction levels are supported via the <code>errorCorrectionLevel</code> prop:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!c4o9!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a5c13be-40d7-4442-9b50-7adaa7c0bf8d_1078x494.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!c4o9!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a5c13be-40d7-4442-9b50-7adaa7c0bf8d_1078x494.png 424w, https://substackcdn.com/image/fetch/$s_!c4o9!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a5c13be-40d7-4442-9b50-7adaa7c0bf8d_1078x494.png 848w, https://substackcdn.com/image/fetch/$s_!c4o9!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a5c13be-40d7-4442-9b50-7adaa7c0bf8d_1078x494.png 1272w, https://substackcdn.com/image/fetch/$s_!c4o9!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a5c13be-40d7-4442-9b50-7adaa7c0bf8d_1078x494.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!c4o9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a5c13be-40d7-4442-9b50-7adaa7c0bf8d_1078x494.png" width="1078" height="494" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4a5c13be-40d7-4442-9b50-7adaa7c0bf8d_1078x494.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:494,&quot;width&quot;:1078,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:64835,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.undolog.com/i/191453687?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a5c13be-40d7-4442-9b50-7adaa7c0bf8d_1078x494.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!c4o9!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a5c13be-40d7-4442-9b50-7adaa7c0bf8d_1078x494.png 424w, https://substackcdn.com/image/fetch/$s_!c4o9!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a5c13be-40d7-4442-9b50-7adaa7c0bf8d_1078x494.png 848w, https://substackcdn.com/image/fetch/$s_!c4o9!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a5c13be-40d7-4442-9b50-7adaa7c0bf8d_1078x494.png 1272w, https://substackcdn.com/image/fetch/$s_!c4o9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a5c13be-40d7-4442-9b50-7adaa7c0bf8d_1078x494.png 1456w" sizes="auto, 100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h3><code>useQRCodeDownload</code><strong> Hook</strong></h3><p>A dedicated hook makes it easy to export the QR code in multiple formats:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;tsx&quot;,&quot;nodeId&quot;:&quot;fdc68860-60bd-499b-a7f9-e975895aa355&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-tsx">import { QRCode, useQRCodeDownload } from '@gfazioli/mantine-qr-code';

function Demo() {
  const { ref, download, getDataUrl } = useQRCodeDownload({
    fileName: 'my-qr-code',
    scale: 4,          // 4x resolution for raster formats
  });

  return (
    &lt;&gt;
      &lt;QRCode ref={ref} value="https://mantine.dev" /&gt;
      &lt;button onClick={() =&gt; download({ format: 'svg' })}&gt;Download SVG&lt;/button&gt;
      &lt;button onClick={() =&gt; download({ format: 'png' })}&gt;Download PNG&lt;/button&gt;
    &lt;/&gt;
  );
}
</code></pre></div><p>Supported formats: <code>svg</code>, <code>png</code>, <code>jpeg</code>, <code>webp</code>. The <code>getDataUrl</code> function returns a data URL &#8212; useful for preview thumbnails or embedding.</p><h3><strong>Theme-Aware Colors</strong></h3><p>Both foreground and background colors integrate with the Mantine theme:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;tsx&quot;,&quot;nodeId&quot;:&quot;bd5e6a92-0b00-4b0f-83ef-aa49273a7e9b&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-tsx">&lt;QRCode value="https://mantine.dev" color="blue" background="gray.1" /&gt;
&lt;QRCode value="https://mantine.dev" color="white" background="dark" /&gt;
&lt;QRCode value="https://mantine.dev" color="teal" background="transparent" /&gt;
</code></pre></div><h3><strong>Full Styles API</strong></h3><p>The component exposes 8 style selectors for granular customization:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!kFW1!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F525e3e4f-38fb-4baf-b7fa-44388afcdfe0_1064x874.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!kFW1!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F525e3e4f-38fb-4baf-b7fa-44388afcdfe0_1064x874.png 424w, https://substackcdn.com/image/fetch/$s_!kFW1!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F525e3e4f-38fb-4baf-b7fa-44388afcdfe0_1064x874.png 848w, https://substackcdn.com/image/fetch/$s_!kFW1!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F525e3e4f-38fb-4baf-b7fa-44388afcdfe0_1064x874.png 1272w, https://substackcdn.com/image/fetch/$s_!kFW1!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F525e3e4f-38fb-4baf-b7fa-44388afcdfe0_1064x874.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!kFW1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F525e3e4f-38fb-4baf-b7fa-44388afcdfe0_1064x874.png" width="1064" height="874" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/525e3e4f-38fb-4baf-b7fa-44388afcdfe0_1064x874.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:874,&quot;width&quot;:1064,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:120495,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.undolog.com/i/191453687?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F525e3e4f-38fb-4baf-b7fa-44388afcdfe0_1064x874.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!kFW1!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F525e3e4f-38fb-4baf-b7fa-44388afcdfe0_1064x874.png 424w, https://substackcdn.com/image/fetch/$s_!kFW1!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F525e3e4f-38fb-4baf-b7fa-44388afcdfe0_1064x874.png 848w, https://substackcdn.com/image/fetch/$s_!kFW1!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F525e3e4f-38fb-4baf-b7fa-44388afcdfe0_1064x874.png 1272w, https://substackcdn.com/image/fetch/$s_!kFW1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F525e3e4f-38fb-4baf-b7fa-44388afcdfe0_1064x874.png 1456w" sizes="auto, 100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h3><strong>CSS Variables</strong></h3><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!8mIA!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2b4ee84-b586-452c-9f59-abab65ee8755_958x490.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!8mIA!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2b4ee84-b586-452c-9f59-abab65ee8755_958x490.png 424w, https://substackcdn.com/image/fetch/$s_!8mIA!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2b4ee84-b586-452c-9f59-abab65ee8755_958x490.png 848w, https://substackcdn.com/image/fetch/$s_!8mIA!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2b4ee84-b586-452c-9f59-abab65ee8755_958x490.png 1272w, https://substackcdn.com/image/fetch/$s_!8mIA!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2b4ee84-b586-452c-9f59-abab65ee8755_958x490.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!8mIA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2b4ee84-b586-452c-9f59-abab65ee8755_958x490.png" width="958" height="490" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f2b4ee84-b586-452c-9f59-abab65ee8755_958x490.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:490,&quot;width&quot;:958,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:60050,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.undolog.com/i/191453687?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2b4ee84-b586-452c-9f59-abab65ee8755_958x490.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!8mIA!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2b4ee84-b586-452c-9f59-abab65ee8755_958x490.png 424w, https://substackcdn.com/image/fetch/$s_!8mIA!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2b4ee84-b586-452c-9f59-abab65ee8755_958x490.png 848w, https://substackcdn.com/image/fetch/$s_!8mIA!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2b4ee84-b586-452c-9f59-abab65ee8755_958x490.png 1272w, https://substackcdn.com/image/fetch/$s_!8mIA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2b4ee84-b586-452c-9f59-abab65ee8755_958x490.png 1456w" sizes="auto, 100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Size presets: <code>xs</code> (80px), <code>sm</code> (120px), <code>md</code> (160px), <code>lg</code> (200px), <code>xl</code> (256px).</p><h2><strong>&#128295; Improvements</strong></h2><ul><li><p><strong>SVG Accessibility</strong>: The SVG element includes <code>role="img"</code> and an <code>aria-label</code> derived from the encoded value.</p></li><li><p><strong>Optimized Rendering</strong>: All data modules are combined into a single <code>&lt;path&gt;</code> element, keeping the DOM minimal regardless of QR code density.</p></li><li><p><strong>Memoized Computation</strong>: QR matrix generation and SVG path building are wrapped in <code>useMemo</code> &#8212; re-computation only happens when relevant props change.</p></li><li><p><strong>SSR Compatible</strong>: No browser-only APIs (<code>window</code>, <code>document</code>) are used during rendering. The download hook uses browser APIs only when explicitly called.</p></li></ul><h2><strong>&#128214; Complete Props Reference</strong></h2><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;3c6fa61c-1ea8-46b0-977a-8ecfecc9b1db&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">interface QRCodeBaseProps {
  value: string;                              // Required &#8212; data to encode
  size?: MantineSize | string | number;       // Default: 'md'
  radius?: MantineRadius | string | number;   // Container border radius
  color?: MantineColor;                       // Default: 'dark'
  background?: MantineColor | 'transparent';  // Default: 'white'
  errorCorrectionLevel?: 'L' | 'M' | 'Q' | 'H'; // Default: 'M'
  quietZone?: number;                         // Default: 1
  dotStyle?: 'square' | 'rounded' | 'dots';  // Default: 'square'
  cornerStyle?: 'square' | 'rounded' | 'dots'; // Default: 'square'
  image?: string;                             // Center image URL
  imageSize?: number;                         // 0&#8211;1, default: 0.2
  imageRadius?: MantineRadius | string | number;
  imagePadding?: number;                      // Default: 1
  imageExcavate?: boolean;                    // Default: true
}
</code></pre></div><h2><strong>Getting Started</strong></h2><p>Install the package:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:&quot;c656daeb-1f20-478d-adc8-b3c63394f4b3&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">npm install @gfazioli/mantine-qr-code
</code></pre></div><p>Import styles at the root of your application:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;8ca57580-405c-4928-96fe-f924de128d5a&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">import '@gfazioli/mantine-qr-code/styles.css';
</code></pre></div><p>Use the component:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;tsx&quot;,&quot;nodeId&quot;:&quot;03c6918d-4563-4283-bc29-57d534ea29f4&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-tsx">import { QRCode } from '@gfazioli/mantine-qr-code';

function App() {
  return (
    &lt;QRCode
      value="https://mantine.dev"
      size="lg"
      dotStyle="rounded"
      cornerStyle="rounded"
      color="blue"
    /&gt;
  );
}
</code></pre></div><h2><strong>Use Cases</strong></h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!pQqw!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F23ab72cf-f9ef-46da-bdca-69aa88d6987e_800x1061.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!pQqw!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F23ab72cf-f9ef-46da-bdca-69aa88d6987e_800x1061.webp 424w, https://substackcdn.com/image/fetch/$s_!pQqw!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F23ab72cf-f9ef-46da-bdca-69aa88d6987e_800x1061.webp 848w, https://substackcdn.com/image/fetch/$s_!pQqw!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F23ab72cf-f9ef-46da-bdca-69aa88d6987e_800x1061.webp 1272w, https://substackcdn.com/image/fetch/$s_!pQqw!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F23ab72cf-f9ef-46da-bdca-69aa88d6987e_800x1061.webp 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!pQqw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F23ab72cf-f9ef-46da-bdca-69aa88d6987e_800x1061.webp" width="800" height="1061" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/23ab72cf-f9ef-46da-bdca-69aa88d6987e_800x1061.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1061,&quot;width&quot;:800,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Use Cases&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Use Cases" title="Use Cases" srcset="https://substackcdn.com/image/fetch/$s_!pQqw!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F23ab72cf-f9ef-46da-bdca-69aa88d6987e_800x1061.webp 424w, https://substackcdn.com/image/fetch/$s_!pQqw!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F23ab72cf-f9ef-46da-bdca-69aa88d6987e_800x1061.webp 848w, https://substackcdn.com/image/fetch/$s_!pQqw!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F23ab72cf-f9ef-46da-bdca-69aa88d6987e_800x1061.webp 1272w, https://substackcdn.com/image/fetch/$s_!pQqw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F23ab72cf-f9ef-46da-bdca-69aa88d6987e_800x1061.webp 1456w" sizes="auto, 100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;tsx&quot;,&quot;nodeId&quot;:&quot;9d5affd8-1bcb-4832-875a-d4adcd64a474&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-tsx">{/* URL */}
&lt;QRCode value="https://mantine.dev" /&gt;

{/* WiFi credentials */}
&lt;QRCode value="WIFI:T:WPA;S:MyNetwork;P:MyPassword;;" /&gt;

{/* vCard contact */}
&lt;QRCode value="BEGIN:VCARD\nVERSION:3.0\nFN:John Doe\nTEL:+1234567890\nEND:VCARD" /&gt;
</code></pre></div><h2><strong>Links</strong></h2><ul><li><p>&#128230; <a href="https://www.npmjs.com/package/@gfazioli/mantine-qr-code">npm package</a></p></li><li><p>&#128214; <a href="https://gfazioli.github.io/mantine-qr-code/">Documentation &amp; Demos</a></p></li><li><p>&#128279; <a href="https://github.com/gfazioli/mantine-qr-code">GitHub Repository</a></p></li><li><p>&#129513; <a href="https://mantine-extensions.vercel.app/">More Mantine Extensions</a></p></li></ul>]]></content:encoded></item><item><title><![CDATA[Mantine Scene - Cinematic Backgrounds for Your React Apps]]></title><description><![CDATA[Layer gradients, glowing blobs, star fields, auroras, and more into rich, composable decorative backgrounds &#8212; all powered by Mantine.]]></description><link>https://www.undolog.com/p/mantine-scene-cinematic-backgrounds</link><guid isPermaLink="false">https://www.undolog.com/p/mantine-scene-cinematic-backgrounds</guid><dc:creator><![CDATA[Giovambattista Fazioli]]></dc:creator><pubDate>Thu, 19 Mar 2026 08:08:31 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!sgYr!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ab31680-08c1-4306-9ae1-9f8803ff7c3c_1280x640.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!sgYr!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ab31680-08c1-4306-9ae1-9f8803ff7c3c_1280x640.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!sgYr!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ab31680-08c1-4306-9ae1-9f8803ff7c3c_1280x640.jpeg 424w, https://substackcdn.com/image/fetch/$s_!sgYr!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ab31680-08c1-4306-9ae1-9f8803ff7c3c_1280x640.jpeg 848w, https://substackcdn.com/image/fetch/$s_!sgYr!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ab31680-08c1-4306-9ae1-9f8803ff7c3c_1280x640.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!sgYr!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ab31680-08c1-4306-9ae1-9f8803ff7c3c_1280x640.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!sgYr!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ab31680-08c1-4306-9ae1-9f8803ff7c3c_1280x640.jpeg" width="1280" height="640" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6ab31680-08c1-4306-9ae1-9f8803ff7c3c_1280x640.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:640,&quot;width&quot;:1280,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:151641,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.undolog.com/i/191452945?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ab31680-08c1-4306-9ae1-9f8803ff7c3c_1280x640.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!sgYr!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ab31680-08c1-4306-9ae1-9f8803ff7c3c_1280x640.jpeg 424w, https://substackcdn.com/image/fetch/$s_!sgYr!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ab31680-08c1-4306-9ae1-9f8803ff7c3c_1280x640.jpeg 848w, https://substackcdn.com/image/fetch/$s_!sgYr!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ab31680-08c1-4306-9ae1-9f8803ff7c3c_1280x640.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!sgYr!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ab31680-08c1-4306-9ae1-9f8803ff7c3c_1280x640.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2><strong>Introduction</strong></h2><p><strong>Mantine Scene</strong> is a brand-new composable background component for React that lets you build atmospheric, animated backgrounds by stacking visual layers. Think of it as a stage director for your UI &#8212; you compose the mood by combining gradients, glow effects, dot grids, star fields, snowfall, auroras, and more, all with first-class Mantine theme integration, responsive props, and accessibility baked in from day one.</p><p>This 1.0.0 release ships with <strong>10 composable sub-components</strong>, full interactive mouse-tracking support, GPU-accelerated animations, and a complete documentation site with live configurators for every layer type.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.undolog.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Undolog is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h2><strong>What&#8217;s New in 1.0.0</strong></h2><h3><strong>The Scene Component</strong></h3><p>The root <code>Scene</code> component acts as a container for all visual layers. It supports fullscreen mode, interactive mouse tracking, and respects <code>prefers-reduced-motion</code> out of the box.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;tsx&quot;,&quot;nodeId&quot;:&quot;58fb5ff3-50e9-40fe-bc6b-c41eccf96d8a&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-tsx">import { Scene } from '@gfazioli/mantine-scene';

function HeroBackground() {
  return (
    &lt;Scene fullscreen interactive&gt;
      &lt;Scene.Gradient from="violet" to="transparent" animate /&gt;
      &lt;Scene.Glow color="blue" size={300} /&gt;
      &lt;Scene.StarField count={80} twinkle /&gt;
      &lt;Scene.Noise opacity={0.03} /&gt;
    &lt;/Scene&gt;
  );
}
</code></pre></div><p><strong>Root Props:</strong></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!qH1c!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F88150931-719a-41e8-82f9-070618afc9d9_1516x770.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!qH1c!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F88150931-719a-41e8-82f9-070618afc9d9_1516x770.png 424w, https://substackcdn.com/image/fetch/$s_!qH1c!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F88150931-719a-41e8-82f9-070618afc9d9_1516x770.png 848w, https://substackcdn.com/image/fetch/$s_!qH1c!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F88150931-719a-41e8-82f9-070618afc9d9_1516x770.png 1272w, https://substackcdn.com/image/fetch/$s_!qH1c!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F88150931-719a-41e8-82f9-070618afc9d9_1516x770.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!qH1c!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F88150931-719a-41e8-82f9-070618afc9d9_1516x770.png" width="1456" height="740" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/88150931-719a-41e8-82f9-070618afc9d9_1516x770.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:740,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:126115,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.undolog.com/i/191452945?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F88150931-719a-41e8-82f9-070618afc9d9_1516x770.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!qH1c!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F88150931-719a-41e8-82f9-070618afc9d9_1516x770.png 424w, https://substackcdn.com/image/fetch/$s_!qH1c!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F88150931-719a-41e8-82f9-070618afc9d9_1516x770.png 848w, https://substackcdn.com/image/fetch/$s_!qH1c!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F88150931-719a-41e8-82f9-070618afc9d9_1516x770.png 1272w, https://substackcdn.com/image/fetch/$s_!qH1c!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F88150931-719a-41e8-82f9-070618afc9d9_1516x770.png 1456w" sizes="auto, 100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h3><strong>10 Composable Sub-Components</strong></h3><p>Every sub-component is a visual layer you can freely compose. Layer order follows DOM order &#8212; the last child renders on top.</p><div><hr></div><h4><strong>&#10024; Scene.Gradient</strong></h4><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!CWVd!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5be62a10-7966-4531-b564-476b5dc156c4_800x1323.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!CWVd!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5be62a10-7966-4531-b564-476b5dc156c4_800x1323.webp 424w, https://substackcdn.com/image/fetch/$s_!CWVd!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5be62a10-7966-4531-b564-476b5dc156c4_800x1323.webp 848w, https://substackcdn.com/image/fetch/$s_!CWVd!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5be62a10-7966-4531-b564-476b5dc156c4_800x1323.webp 1272w, https://substackcdn.com/image/fetch/$s_!CWVd!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5be62a10-7966-4531-b564-476b5dc156c4_800x1323.webp 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!CWVd!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5be62a10-7966-4531-b564-476b5dc156c4_800x1323.webp" width="800" height="1323" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5be62a10-7966-4531-b564-476b5dc156c4_800x1323.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1323,&quot;width&quot;:800,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Gradient&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Gradient" title="Gradient" srcset="https://substackcdn.com/image/fetch/$s_!CWVd!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5be62a10-7966-4531-b564-476b5dc156c4_800x1323.webp 424w, https://substackcdn.com/image/fetch/$s_!CWVd!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5be62a10-7966-4531-b564-476b5dc156c4_800x1323.webp 848w, https://substackcdn.com/image/fetch/$s_!CWVd!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5be62a10-7966-4531-b564-476b5dc156c4_800x1323.webp 1272w, https://substackcdn.com/image/fetch/$s_!CWVd!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5be62a10-7966-4531-b564-476b5dc156c4_800x1323.webp 1456w" sizes="auto, 100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Radial, linear, or conic gradients with optional rotate or pulse animation. Supports interactive mouse tracking for radial/conic types.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;tsx&quot;,&quot;nodeId&quot;:&quot;88c45b7f-e877-405b-abb3-30f9e5189eb2&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-tsx">&lt;Scene.Gradient
  type="radial"
  from="violet"
  to="transparent"
  fromOpacity={0.2}
  animate
  animationType="rotate"
  duration={20}
/&gt;
</code></pre></div><div><hr></div><h4><strong>&#10024; Scene.Glow</strong></h4><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!XslC!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14847110-d097-49cf-99cd-e7b1cf87f645_800x1275.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!XslC!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14847110-d097-49cf-99cd-e7b1cf87f645_800x1275.webp 424w, https://substackcdn.com/image/fetch/$s_!XslC!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14847110-d097-49cf-99cd-e7b1cf87f645_800x1275.webp 848w, https://substackcdn.com/image/fetch/$s_!XslC!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14847110-d097-49cf-99cd-e7b1cf87f645_800x1275.webp 1272w, https://substackcdn.com/image/fetch/$s_!XslC!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14847110-d097-49cf-99cd-e7b1cf87f645_800x1275.webp 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!XslC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14847110-d097-49cf-99cd-e7b1cf87f645_800x1275.webp" width="800" height="1275" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/14847110-d097-49cf-99cd-e7b1cf87f645_800x1275.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1275,&quot;width&quot;:800,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Glow&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Glow" title="Glow" srcset="https://substackcdn.com/image/fetch/$s_!XslC!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14847110-d097-49cf-99cd-e7b1cf87f645_800x1275.webp 424w, https://substackcdn.com/image/fetch/$s_!XslC!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14847110-d097-49cf-99cd-e7b1cf87f645_800x1275.webp 848w, https://substackcdn.com/image/fetch/$s_!XslC!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14847110-d097-49cf-99cd-e7b1cf87f645_800x1275.webp 1272w, https://substackcdn.com/image/fetch/$s_!XslC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14847110-d097-49cf-99cd-e7b1cf87f645_800x1275.webp 1456w" sizes="auto, 100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Floating, animated glow blobs. Choose between <code>float</code>, <code>pulse</code>, or <code>breathe</code> animation styles. Supports <strong>responsive </strong><code>size</code><strong> and </strong><code>blur</code> props and interactive mouse tracking.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;tsx&quot;,&quot;nodeId&quot;:&quot;f469c99d-5fd8-42df-b0b2-eeb81dd8562e&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-tsx">&lt;Scene.Glow
  color="cyan"
  size={{ base: 200, md: 400 }}
  blur={{ base: 80, md: 120 }}
  opacity={0.5}
  animate
  animationType="float"
  driftX="30px"
  driftY="20px"
/&gt;
</code></pre></div><div><hr></div><h4><strong>&#10024; Scene.DotGrid</strong></h4><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!4DWR!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd5346686-562d-4efc-b0af-f5d136a0cb06_800x893.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!4DWR!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd5346686-562d-4efc-b0af-f5d136a0cb06_800x893.webp 424w, https://substackcdn.com/image/fetch/$s_!4DWR!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd5346686-562d-4efc-b0af-f5d136a0cb06_800x893.webp 848w, https://substackcdn.com/image/fetch/$s_!4DWR!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd5346686-562d-4efc-b0af-f5d136a0cb06_800x893.webp 1272w, https://substackcdn.com/image/fetch/$s_!4DWR!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd5346686-562d-4efc-b0af-f5d136a0cb06_800x893.webp 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!4DWR!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd5346686-562d-4efc-b0af-f5d136a0cb06_800x893.webp" width="800" height="893" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d5346686-562d-4efc-b0af-f5d136a0cb06_800x893.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:893,&quot;width&quot;:800,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;DotGrid&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="DotGrid" title="DotGrid" srcset="https://substackcdn.com/image/fetch/$s_!4DWR!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd5346686-562d-4efc-b0af-f5d136a0cb06_800x893.webp 424w, https://substackcdn.com/image/fetch/$s_!4DWR!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd5346686-562d-4efc-b0af-f5d136a0cb06_800x893.webp 848w, https://substackcdn.com/image/fetch/$s_!4DWR!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd5346686-562d-4efc-b0af-f5d136a0cb06_800x893.webp 1272w, https://substackcdn.com/image/fetch/$s_!4DWR!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd5346686-562d-4efc-b0af-f5d136a0cb06_800x893.webp 1456w" sizes="auto, 100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Repeating dot patterns with optional stagger (honeycomb) layout, fade masks, and twinkle animation. Supports <strong>responsive </strong><code>spacing</code>.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;tsx&quot;,&quot;nodeId&quot;:&quot;806e4466-a9d8-4b39-9a2e-f3073f2596b1&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-tsx">&lt;Scene.DotGrid
  color="gray"
  spacing={{ base: 16, md: 24 }}
  stagger
  fade="radial"
  animate
/&gt;
</code></pre></div><div><hr></div><h4><strong>&#10024; Scene.Mesh</strong></h4><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ox6a!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F593b6c73-85ad-45c3-a1e6-55e1db967665_800x904.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ox6a!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F593b6c73-85ad-45c3-a1e6-55e1db967665_800x904.webp 424w, https://substackcdn.com/image/fetch/$s_!ox6a!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F593b6c73-85ad-45c3-a1e6-55e1db967665_800x904.webp 848w, https://substackcdn.com/image/fetch/$s_!ox6a!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F593b6c73-85ad-45c3-a1e6-55e1db967665_800x904.webp 1272w, https://substackcdn.com/image/fetch/$s_!ox6a!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F593b6c73-85ad-45c3-a1e6-55e1db967665_800x904.webp 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!ox6a!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F593b6c73-85ad-45c3-a1e6-55e1db967665_800x904.webp" width="800" height="904" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/593b6c73-85ad-45c3-a1e6-55e1db967665_800x904.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:904,&quot;width&quot;:800,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Mesh&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Mesh" title="Mesh" srcset="https://substackcdn.com/image/fetch/$s_!ox6a!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F593b6c73-85ad-45c3-a1e6-55e1db967665_800x904.webp 424w, https://substackcdn.com/image/fetch/$s_!ox6a!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F593b6c73-85ad-45c3-a1e6-55e1db967665_800x904.webp 848w, https://substackcdn.com/image/fetch/$s_!ox6a!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F593b6c73-85ad-45c3-a1e6-55e1db967665_800x904.webp 1272w, https://substackcdn.com/image/fetch/$s_!ox6a!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F593b6c73-85ad-45c3-a1e6-55e1db967665_800x904.webp 1456w" sizes="auto, 100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Multi-stop radial gradient overlay simulating a mesh gradient. Supports hue-rotate animation and blend modes.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;tsx&quot;,&quot;nodeId&quot;:&quot;91d8850e-74bf-4066-bafd-5be7fa16db49&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-tsx">&lt;Scene.Mesh
  stops={[
    { color: 'violet', position: '20% 30%', spread: 50 },
    { color: 'pink', position: '80% 20%', spread: 40 },
    { color: 'blue', position: '50% 80%', spread: 60 },
  ]}
  animate
  blend="screen"
/&gt;
</code></pre></div><div><hr></div><h4><strong>&#10024; Scene.Noise</strong></h4><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!XJ-N!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6620dc45-5ece-43ba-a222-392fd3684dd3_800x875.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!XJ-N!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6620dc45-5ece-43ba-a222-392fd3684dd3_800x875.webp 424w, https://substackcdn.com/image/fetch/$s_!XJ-N!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6620dc45-5ece-43ba-a222-392fd3684dd3_800x875.webp 848w, https://substackcdn.com/image/fetch/$s_!XJ-N!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6620dc45-5ece-43ba-a222-392fd3684dd3_800x875.webp 1272w, https://substackcdn.com/image/fetch/$s_!XJ-N!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6620dc45-5ece-43ba-a222-392fd3684dd3_800x875.webp 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!XJ-N!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6620dc45-5ece-43ba-a222-392fd3684dd3_800x875.webp" width="800" height="875" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6620dc45-5ece-43ba-a222-392fd3684dd3_800x875.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:875,&quot;width&quot;:800,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Noise&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Noise" title="Noise" srcset="https://substackcdn.com/image/fetch/$s_!XJ-N!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6620dc45-5ece-43ba-a222-392fd3684dd3_800x875.webp 424w, https://substackcdn.com/image/fetch/$s_!XJ-N!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6620dc45-5ece-43ba-a222-392fd3684dd3_800x875.webp 848w, https://substackcdn.com/image/fetch/$s_!XJ-N!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6620dc45-5ece-43ba-a222-392fd3684dd3_800x875.webp 1272w, https://substackcdn.com/image/fetch/$s_!XJ-N!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6620dc45-5ece-43ba-a222-392fd3684dd3_800x875.webp 1456w" sizes="auto, 100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>SVG-based film grain texture. Add subtle texture to any background with configurable grain, seed, and color tint.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;tsx&quot;,&quot;nodeId&quot;:&quot;9034bd1e-e2b4-46db-91d9-c1cf4ea3f14e&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-tsx">&lt;Scene.Noise opacity={0.03} grain={0.65} type="fractalNoise" tint="blue" /&gt;
</code></pre></div><div><hr></div><h4><strong>&#10024; Scene.StarField</strong></h4><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!a2Lw!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F90eb4ed0-46e7-4927-b7bf-2d630ea72438_800x927.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!a2Lw!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F90eb4ed0-46e7-4927-b7bf-2d630ea72438_800x927.webp 424w, https://substackcdn.com/image/fetch/$s_!a2Lw!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F90eb4ed0-46e7-4927-b7bf-2d630ea72438_800x927.webp 848w, https://substackcdn.com/image/fetch/$s_!a2Lw!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F90eb4ed0-46e7-4927-b7bf-2d630ea72438_800x927.webp 1272w, https://substackcdn.com/image/fetch/$s_!a2Lw!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F90eb4ed0-46e7-4927-b7bf-2d630ea72438_800x927.webp 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!a2Lw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F90eb4ed0-46e7-4927-b7bf-2d630ea72438_800x927.webp" width="800" height="927" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/90eb4ed0-46e7-4927-b7bf-2d630ea72438_800x927.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:927,&quot;width&quot;:800,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;StarField&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="StarField" title="StarField" srcset="https://substackcdn.com/image/fetch/$s_!a2Lw!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F90eb4ed0-46e7-4927-b7bf-2d630ea72438_800x927.webp 424w, https://substackcdn.com/image/fetch/$s_!a2Lw!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F90eb4ed0-46e7-4927-b7bf-2d630ea72438_800x927.webp 848w, https://substackcdn.com/image/fetch/$s_!a2Lw!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F90eb4ed0-46e7-4927-b7bf-2d630ea72438_800x927.webp 1272w, https://substackcdn.com/image/fetch/$s_!a2Lw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F90eb4ed0-46e7-4927-b7bf-2d630ea72438_800x927.webp 1456w" sizes="auto, 100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>CSS-only star field with deterministic PRNG positioning and twinkle animation across 3 staggered layers. Supports <strong>responsive </strong><code>count</code>.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;tsx&quot;,&quot;nodeId&quot;:&quot;57b6f8b5-f9d8-4e1c-abdb-f1a394a7acd1&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-tsx">&lt;Scene.StarField count={{ base: 50, md: 100 }} twinkle duration={3} seed={42} /&gt;
</code></pre></div><div><hr></div><h4><strong>&#10024; Scene.StarWarp</strong></h4><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!bsmK!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F08b46d75-a1f6-48a5-a140-78e702425f1b_800x1215.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!bsmK!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F08b46d75-a1f6-48a5-a140-78e702425f1b_800x1215.webp 424w, https://substackcdn.com/image/fetch/$s_!bsmK!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F08b46d75-a1f6-48a5-a140-78e702425f1b_800x1215.webp 848w, https://substackcdn.com/image/fetch/$s_!bsmK!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F08b46d75-a1f6-48a5-a140-78e702425f1b_800x1215.webp 1272w, https://substackcdn.com/image/fetch/$s_!bsmK!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F08b46d75-a1f6-48a5-a140-78e702425f1b_800x1215.webp 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!bsmK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F08b46d75-a1f6-48a5-a140-78e702425f1b_800x1215.webp" width="800" height="1215" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/08b46d75-a1f6-48a5-a140-78e702425f1b_800x1215.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1215,&quot;width&quot;:800,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;StarWarp&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="StarWarp" title="StarWarp" srcset="https://substackcdn.com/image/fetch/$s_!bsmK!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F08b46d75-a1f6-48a5-a140-78e702425f1b_800x1215.webp 424w, https://substackcdn.com/image/fetch/$s_!bsmK!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F08b46d75-a1f6-48a5-a140-78e702425f1b_800x1215.webp 848w, https://substackcdn.com/image/fetch/$s_!bsmK!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F08b46d75-a1f6-48a5-a140-78e702425f1b_800x1215.webp 1272w, https://substackcdn.com/image/fetch/$s_!bsmK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F08b46d75-a1f6-48a5-a140-78e702425f1b_800x1215.webp 1456w" sizes="auto, 100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Hyperspace warp-speed effect with configurable focal point, direction, and streak trails. Supports <strong>responsive </strong><code>count</code> and interactive mouse tracking for the focal point.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;tsx&quot;,&quot;nodeId&quot;:&quot;b6acad96-d065-4510-a94e-4fdaaac663dd&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-tsx">&lt;Scene.StarWarp
  count={100}
  speed={1.5}
  direction="out"
  trail
  color="white"
/&gt;
</code></pre></div><div><hr></div><h4><strong>&#10024; Scene.ShootingStar</strong></h4><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!iyAP!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9c1e508-6710-4fef-a7b4-44d3537e1ead_800x1001.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!iyAP!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9c1e508-6710-4fef-a7b4-44d3537e1ead_800x1001.webp 424w, https://substackcdn.com/image/fetch/$s_!iyAP!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9c1e508-6710-4fef-a7b4-44d3537e1ead_800x1001.webp 848w, https://substackcdn.com/image/fetch/$s_!iyAP!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9c1e508-6710-4fef-a7b4-44d3537e1ead_800x1001.webp 1272w, https://substackcdn.com/image/fetch/$s_!iyAP!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9c1e508-6710-4fef-a7b4-44d3537e1ead_800x1001.webp 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!iyAP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9c1e508-6710-4fef-a7b4-44d3537e1ead_800x1001.webp" width="800" height="1001" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f9c1e508-6710-4fef-a7b4-44d3537e1ead_800x1001.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1001,&quot;width&quot;:800,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;ShootingStar&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="ShootingStar" title="ShootingStar" srcset="https://substackcdn.com/image/fetch/$s_!iyAP!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9c1e508-6710-4fef-a7b4-44d3537e1ead_800x1001.webp 424w, https://substackcdn.com/image/fetch/$s_!iyAP!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9c1e508-6710-4fef-a7b4-44d3537e1ead_800x1001.webp 848w, https://substackcdn.com/image/fetch/$s_!iyAP!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9c1e508-6710-4fef-a7b4-44d3537e1ead_800x1001.webp 1272w, https://substackcdn.com/image/fetch/$s_!iyAP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9c1e508-6710-4fef-a7b4-44d3537e1ead_800x1001.webp 1456w" sizes="auto, 100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Animated shooting star trails at configurable angles and intervals. Multiple lanes with randomized timing for a natural feel.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;tsx&quot;,&quot;nodeId&quot;:&quot;1ff775d7-1b6b-4811-aed5-6cbf08d274c1&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-tsx">&lt;Scene.ShootingStar
  count={3}
  angle={215}
  trailLength={80}
  speed={1}
  minInterval={3}
  maxInterval={8}
/&gt;
</code></pre></div><div><hr></div><h4><strong>&#10024; Scene.Snow</strong></h4><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!iS19!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9bba55dd-2ca5-40ef-b837-57911ee115c4_800x945.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!iS19!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9bba55dd-2ca5-40ef-b837-57911ee115c4_800x945.webp 424w, https://substackcdn.com/image/fetch/$s_!iS19!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9bba55dd-2ca5-40ef-b837-57911ee115c4_800x945.webp 848w, https://substackcdn.com/image/fetch/$s_!iS19!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9bba55dd-2ca5-40ef-b837-57911ee115c4_800x945.webp 1272w, https://substackcdn.com/image/fetch/$s_!iS19!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9bba55dd-2ca5-40ef-b837-57911ee115c4_800x945.webp 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!iS19!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9bba55dd-2ca5-40ef-b837-57911ee115c4_800x945.webp" width="800" height="945" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9bba55dd-2ca5-40ef-b837-57911ee115c4_800x945.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:945,&quot;width&quot;:800,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Snow&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Snow" title="Snow" srcset="https://substackcdn.com/image/fetch/$s_!iS19!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9bba55dd-2ca5-40ef-b837-57911ee115c4_800x945.webp 424w, https://substackcdn.com/image/fetch/$s_!iS19!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9bba55dd-2ca5-40ef-b837-57911ee115c4_800x945.webp 848w, https://substackcdn.com/image/fetch/$s_!iS19!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9bba55dd-2ca5-40ef-b837-57911ee115c4_800x945.webp 1272w, https://substackcdn.com/image/fetch/$s_!iS19!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9bba55dd-2ca5-40ef-b837-57911ee115c4_800x945.webp 1456w" sizes="auto, 100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Falling snowflakes with horizontal drift and wind control. Supports <strong>responsive </strong><code>count</code> for performance-conscious responsive designs.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;tsx&quot;,&quot;nodeId&quot;:&quot;3b73851a-5600-410b-80a9-9917093d0bb4&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-tsx">&lt;Scene.Snow
  count={{ base: 30, md: 50 }}
  speed={1}
  drift={30}
  wind={0.2}
/&gt;
</code></pre></div><div><hr></div><h4><strong>&#10024; Scene.Aurora</strong></h4><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!qito!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F20378b12-9721-4df4-bcf9-1e53de93a6b9_800x794.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!qito!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F20378b12-9721-4df4-bcf9-1e53de93a6b9_800x794.webp 424w, https://substackcdn.com/image/fetch/$s_!qito!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F20378b12-9721-4df4-bcf9-1e53de93a6b9_800x794.webp 848w, https://substackcdn.com/image/fetch/$s_!qito!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F20378b12-9721-4df4-bcf9-1e53de93a6b9_800x794.webp 1272w, https://substackcdn.com/image/fetch/$s_!qito!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F20378b12-9721-4df4-bcf9-1e53de93a6b9_800x794.webp 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!qito!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F20378b12-9721-4df4-bcf9-1e53de93a6b9_800x794.webp" width="800" height="794" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/20378b12-9721-4df4-bcf9-1e53de93a6b9_800x794.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:794,&quot;width&quot;:800,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Aurora&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Aurora" title="Aurora" srcset="https://substackcdn.com/image/fetch/$s_!qito!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F20378b12-9721-4df4-bcf9-1e53de93a6b9_800x794.webp 424w, https://substackcdn.com/image/fetch/$s_!qito!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F20378b12-9721-4df4-bcf9-1e53de93a6b9_800x794.webp 848w, https://substackcdn.com/image/fetch/$s_!qito!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F20378b12-9721-4df4-bcf9-1e53de93a6b9_800x794.webp 1272w, https://substackcdn.com/image/fetch/$s_!qito!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F20378b12-9721-4df4-bcf9-1e53de93a6b9_800x794.webp 1456w" sizes="auto, 100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Shimmering aurora borealis bands with wave animation. Configurable band count, position, and colors.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;tsx&quot;,&quot;nodeId&quot;:&quot;2403a8d6-4253-4d54-8032-9cf3f11dc655&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-tsx">&lt;Scene.Aurora
  colors={['green', 'teal', 'cyan']}
  bands={3}
  position="top"
  opacity={0.3}
  blur={60}
/&gt;
</code></pre></div><h2><strong>Key Features</strong></h2><h3><strong>&#127912; Full Mantine Theme Integration</strong></h3><p>Every color prop accepts <code>MantineColor</code> values, so your backgrounds stay consistent with your theme. Shade overrides are available on components like <code>Glow</code> and <code>DotGrid</code>.</p><h3><strong>&#128241; Responsive Props</strong></h3><p>Key numeric props support Mantine&#8217;s responsive object syntax. This means you can adjust visual density based on breakpoint &#8212; fewer stars on mobile, larger glows on desktop:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;tsx&quot;,&quot;nodeId&quot;:&quot;633eb206-02e0-4244-b23f-db1c7c08e5da&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-tsx">&lt;Scene.StarField count={{ base: 40, sm: 60, md: 100 }} /&gt;
&lt;Scene.Glow size={{ base: 150, md: 400 }} blur={{ base: 60, md: 120 }} /&gt;
</code></pre></div><p>Responsive props are available on: <code>Glow</code> (size, blur), <code>DotGrid</code> (spacing), <code>StarField</code> (count), <code>StarWarp</code> (count), <code>Snow</code> (count).</p><h3><strong>&#128433;&#65039; Interactive Mouse Tracking</strong></h3><p>Set <code>interactive</code> on the root <code>Scene</code> to enable cursor-driven effects:</p><ul><li><p><strong>Glow</strong> &#8212; blob position follows the mouse</p></li><li><p><strong>Gradient</strong> &#8212; radial/conic center follows the mouse</p></li><li><p><strong>StarWarp</strong> &#8212; focal point tracks the mouse</p></li></ul><p>The <code>interactiveEasing</code> prop controls how smoothly elements follow the cursor (0 = sluggish, 1 = instant). Components gracefully fall back to their default positions when the mouse leaves.</p><h3><strong>&#9855; Accessibility</strong></h3><ul><li><p>The root component renders with <code>aria-hidden="true"</code> &#8212; it&#8217;s purely decorative</p></li><li><p><code>reducedMotion="auto"</code> respects <code>prefers-reduced-motion</code> by default</p></li><li><p>Set <code>reducedMotion="always"</code> to disable all animations, or <code>"never"</code> to force them on</p></li></ul><h3><strong>&#9889; Performance</strong></h3><ul><li><p>All animations use GPU-accelerated CSS (<code>transform</code>, <code>opacity</code>) &#8212; no JavaScript animation loops</p></li><li><p>Deterministic PRNG (seeded) for StarField, StarWarp, ShootingStar, and Snow ensures reproducible layouts without runtime randomness</p></li><li><p>StarWarp caps at 200 elements; Snow recommends ~80 max for smooth performance</p></li><li><p>Responsive <code>count</code> props let you reduce particle counts on lower-powered devices</p></li></ul><h3><strong>&#127899;&#65039; Styles API</strong></h3><p>Full Mantine Styles API support. Override styles for any part of any sub-component using <code>classNames</code>, <code>styles</code>, or <code>unstyled</code> props. CSS variables are exposed for all dynamic values (e.g., <code>--scene-glow-size</code>, <code>--scene-aurora-duration</code>).</p><h2><strong>Putting It All Together</strong></h2><p>Here&#8217;s a complete example combining multiple layers into a rich hero background:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;tsx&quot;,&quot;nodeId&quot;:&quot;6943d778-b126-49ad-840d-56b164e73350&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-tsx">import { Scene } from '@gfazioli/mantine-scene';

function CosmicHero() {
  return (
    &lt;Scene fullscreen interactive interactiveEasing={0.08}&gt;
      {/* Base gradient */}
      &lt;Scene.Gradient type="radial" from="indigo" fromOpacity={0.2} animate /&gt;

      {/* Mesh overlay */}
      &lt;Scene.Mesh
        stops={[
          { color: 'violet', position: '25% 25%' },
          { color: 'blue', position: '75% 75%' },
        ]}
        blend="screen"
        opacity={0.6}
      /&gt;

      {/* Floating glows */}
      &lt;Scene.Glow color="cyan" size={350} top="20%" left="30%" /&gt;
      &lt;Scene.Glow color="violet" size={250} top="60%" left="70%" delay={2} /&gt;

      {/* Stars */}
      &lt;Scene.StarField count={{ base: 60, md: 120 }} twinkle /&gt;
      &lt;Scene.ShootingStar count={2} /&gt;

      {/* Subtle texture */}
      &lt;Scene.Noise opacity={0.02} /&gt;
    &lt;/Scene&gt;
  );
}
</code></pre></div><h2><strong>Getting Started</strong></h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!WKew!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F77475c17-73d1-4284-b2a6-34f7f437a56f_800x1183.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!WKew!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F77475c17-73d1-4284-b2a6-34f7f437a56f_800x1183.webp 424w, https://substackcdn.com/image/fetch/$s_!WKew!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F77475c17-73d1-4284-b2a6-34f7f437a56f_800x1183.webp 848w, https://substackcdn.com/image/fetch/$s_!WKew!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F77475c17-73d1-4284-b2a6-34f7f437a56f_800x1183.webp 1272w, https://substackcdn.com/image/fetch/$s_!WKew!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F77475c17-73d1-4284-b2a6-34f7f437a56f_800x1183.webp 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!WKew!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F77475c17-73d1-4284-b2a6-34f7f437a56f_800x1183.webp" width="800" height="1183" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/77475c17-73d1-4284-b2a6-34f7f437a56f_800x1183.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1183,&quot;width&quot;:800,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Combined&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Combined" title="Combined" srcset="https://substackcdn.com/image/fetch/$s_!WKew!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F77475c17-73d1-4284-b2a6-34f7f437a56f_800x1183.webp 424w, https://substackcdn.com/image/fetch/$s_!WKew!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F77475c17-73d1-4284-b2a6-34f7f437a56f_800x1183.webp 848w, https://substackcdn.com/image/fetch/$s_!WKew!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F77475c17-73d1-4284-b2a6-34f7f437a56f_800x1183.webp 1272w, https://substackcdn.com/image/fetch/$s_!WKew!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F77475c17-73d1-4284-b2a6-34f7f437a56f_800x1183.webp 1456w" sizes="auto, 100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h3><strong>Installation</strong></h3><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:&quot;a4c8d005-1f31-474a-b220-9aa227a16bae&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">npm install @gfazioli/mantine-scene
# or
yarn add @gfazioli/mantine-scene
</code></pre></div><h3><strong>Import Styles</strong></h3><p>Add the styles import to your app entry point:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;f43d48b8-6602-4e6a-81e1-4c34e015ac3f&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">import '@gfazioli/mantine-scene/styles.css';
</code></pre></div><p>Or use the <code>@layer</code> variant for CSS cascade control:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;a8a97c10-88c2-4703-942c-e669366ea861&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">import '@gfazioli/mantine-scene/styles.layer.css';
</code></pre></div><h3><strong>Requirements</strong></h3><ul><li><p>React 18 or 19</p></li><li><p>@mantine/core &gt;= 7.0.0</p></li><li><p>@mantine/hooks &gt;= 7.0.0</p></li></ul><h2><strong>Links</strong></h2><ul><li><p>&#128230; <a href="https://www.npmjs.com/package/@gfazioli/mantine-scene">npm package</a></p></li><li><p>&#128214; <a href="https://gfazioli.github.io/mantine-scene/">Documentation &amp; Live Demos</a></p></li><li><p>&#128279; <a href="https://github.com/gfazioli/mantine-scene">GitHub Repository</a></p></li><li><p>&#127760; <a href="https://mantine-extensions.vercel.app/">Mantine Extensions Hub</a></p></li></ul><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.undolog.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Undolog is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item></channel></rss>