<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" 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">
    <channel>
        <title>ezhik.jp</title>
        <link>https://ezhik.jp</link>
        <description>A website for an Internet Hedgehog</description>
        <lastBuildDate>Fri, 13 Feb 2026 13:05:54 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>ezhik.jp</generator>
        <copyright>© 2026 Ezhik</copyright>
        <atom:link href="https:/ezhik.jp/feed.xml" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[TIL]]></title>
            <link>/til</link>
            <guid>f1bb8b9dbef1c5349d6f9519a499d39022fc1dbf35a2e5ab0f99b459e65acdb1</guid>
            <pubDate>Thu, 09 Sep 9999 00:09:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>
									<figure>
										<a href="/assets/the-more-you-know.gif" target="_blank">
											<img src="/assets/the-more-you-know.gif" alt="The More You Know">
										</a>
										<figcaption>The More You Know</figcaption>
									</figure>
								</p>]]></content:encoded>
            <enclosure url="https://ezhik.jp/assets/the-more-you-know.gif" length="0" type="image/gif"/>
        </item>
        <item>
            <title><![CDATA[(AI) Slop Terrifies Me]]></title>
            <link>/ai-slop-terrifies-me</link>
            <guid>142f75f6f5c140321a22033841d45a76a73632451f60399a79f3dcce3df07f2e</guid>
            <pubDate>Sun, 08 Feb 2026 10:00:00 GMT</pubDate>
            <description><![CDATA[What if this is as good as software is ever going to be? What if AI stops getting better and what if people stop caring?]]></description>
            <content:encoded><![CDATA[<p>
									<figure>
										<a href="/assets/bro-you-had-chatgpt-write-the-get-well-card-for-your-grandma.png" target="_blank">
											<img src="/assets/thumbnails/bro-you-had-chatgpt-write-the-get-well-card-for-your-grandma.jpg">
										</a>
										
									</figure>
								</p>
<p>What if this is as good as software is ever going to be? What if AI stops getting better and what if people stop caring?</p>
<aside class="left">All em dashes in this post are organically-made.</aside>
<p>Imagine if this is as good as AI gets. If this is where it stops, you'd still have models that can almost code a web browser, almost code a compiler—and can even present a pretty cool demo if allowed to take a few shortcuts. You'd still get models that can kinda-sorta simulate worlds and write kinda-sorta engaging stories. You'd still get self-driving cars that almost work, except when they don't. You get AI that can make you like 90% of a thing!</p>
<p>90% is a lot. Will you care about the last 10%?</p>
<p>I'm terrified that you won't.</p>
<p>I'm terrified of the <em>good enough to ship</em>—and I'm terrified of nobody else caring. I'm less afraid of AI agents writing apps that they will never experience than I am of the AI herders who won't care enough to actually learn what they ship. And I sure as hell am afraid of the people who will experience the slop and will be fine with it.</p>
<p>As a <a href="/losing-the-plot-that-was-never-there">woodworking enthusiast</a> I am slowly making my peace with standing in the middle of an IKEA. But at the rate things are going in this dropshipping hell, IKEA would be the dream. Software <em>temufication</em> stings much more than software commoditization.</p>
<aside class="right">Are these the same needles in my heart that older programmers had when IDEs became mainstream?</aside>
<p>I think Claude and friends can help with crafting good software and with learning new technologies and programming languages—though I sure as hell move slower when I stop to learn and understand than the guy playing Dwarf Fortress with 17 agents. But at the same time AI models seem to constantly nudge towards that same median Next-React-Tailwind, <em>good enough</em> app. These things just don't handle going off the beaten path well.</p>
<p>
									<figure>
										<a href="/assets/claudes-mid-paper-clone.png" target="_blank">
											<img src="/assets/thumbnails/claudes-mid-paper-clone.jpg" alt="Spend all the tokens you want, trying to make something unique like Paper by FiftyThree with AI tools will just end up looking normal and uninspired.">
										</a>
										<figcaption>Spend all the tokens you want, trying to make something unique like Paper by FiftyThree with AI tools will just end up looking normal and uninspired.</figcaption>
									</figure>
								</p>
<p>Mind you, it's not like slop is anything new. A lot of human decisions had to happen before your backside ended up in an extremely uncomfortable chair, your search results got polluted by poorly-written SEO-optimized articles, and your brain had to deal with a ticket booking website with a user interface so poorly designed that it made you cry. So it's a people problem. Incentives just don't seem to align to make good software. Move fast and break things, etc, etc. You'll make a little artisan app, and if it's any good, Google will come along with a free clone, kill you, then kill its clone—and the world will be left with net zero new good software. And now, with AI agents, it gets even worse as agent herders can do the same thing much faster.</p>
<p>Developers aside, there's also the users. AI models can't be imaginative, and the developers can't afford to, but surely with AI tools, the gap between <em>users</em> and <em>developers</em> will be bridged, ChatGPT will become the new HyperCard and people will turn their ideas into reality with just a few sentences? There's so many people out there who are coding without knowing it, from Carol in Accounting making insane Excel spreadsheets to all the kids on TikTok automating their phones with Apple Shortcuts and hacking up cool Notion notebooks.</p>
<p>But what if those people are an aberration? What if this state of <em>tech learned helplessness</em> cannot be fixed? What if people really do just want a glorified little TV in their pocket? What if most people truly just don't care about tech problems, about privacy, about Liquid Glass, about Microsoft's upsells, about constantly dealing with apps and features which just <em>don't work</em>? What if there will be nobody left to carry the torch? What if the future of computing belongs not to artisan developers or Carol from Accounting, but to whoever can churn out the most software out the fastest? What if <em>good enough</em> really is good enough for most people?</p>
<p>I'm terrified that our craft will die, and nobody will even care to mourn it.</p>]]></content:encoded>
            <enclosure url="https://ezhik.jp/assets/thumbnails/bro-you-had-chatgpt-write-the-get-well-card-for-your-grandma.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Dead Trees and EPUBs]]></title>
            <link>/dead-trees-and-epubs</link>
            <guid>5a3efe586f9ee432ab65ebd554bad07dde874acf7db862656911960f6d71fd3b</guid>
            <pubDate>Tue, 23 Sep 2025 16:27:00 GMT</pubDate>
            <description><![CDATA[I wanted to read more. And so I started buying books. Now I have shelves and shelves of unread books—a classic case of tsundoku.]]></description>
            <content:encoded><![CDATA[<p>I wanted to read more. And so I started buying books. Now I have shelves and shelves of unread books—a classic case of <a href="https://en.wikipedia.org/wiki/Tsundoku" class="external">tsundoku</a>.</p>
<p>I wanted to read more. And so I bought a Kindle. It sat in a drawer for a decade.</p>
<aside class="right">My choice of Kindle specifically is motivated primarily by the decade-old Kindle Paperwhite sitting in my drawer.</aside>
<p>I wanted to read more. And so I installed the Kindle app on my tablet. The app sat mostly untouched.</p>
<p>I wanted to read more. And so I installed the Kindle app on my phone. And then I started reading a lot.</p>
<p>A bigger screen is nicer than a smaller screen. E-ink is nicer than a typical screen. And real ink is nicer than e-ink. And yet I do most of my reading on my phone. Funny how that works.</p>
<aside class="left">To feel less bad about the tsundoku problem, I've taken to primarily choosing my next read from my shelves, then getting the digital version of it.</aside>
<p>I guess the most important reason is that I always have my phone in me. I do make an effort to put my Kindle or a paperback in my bag, and yet I always end up reaching for my phone instead. Perhaps it's a bit too rainy so I don't want to damage the paperback. Or I'm on the train and can't easily fish the Kindle out of my bag. But my phone's right there and so is my personal library. Plus, seeing that reading streak count up does feel pretty nice.</p>
<p>It's not as ✨bookcore✨ and ✨aesthetic✨. I don't feel as comfy reading a book at a coffee shop when I do it on my phone. And it can't be that great for my eyesight either. Yet for some reason it just worked for me.</p>
<p>Maybe that's my problem with a lot of things. I try to get it all nice and perfect, but then getting the details just right is too hard and so I give up entirely. Ultimately, an EPUB read on an LCD screen is better than a paperback left untouched. Maybe I'll grow into reading paperbacks one day. Until then, e-books it is.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Obsidian Note Codes]]></title>
            <link>/obsidian/note-codes</link>
            <guid>05b30036778ee6ad76ef3f19059c044936df3b59ea59d42b031a77fc7d4d5f86</guid>
            <pubDate>Fri, 12 Sep 2025 17:50:00 GMT</pubDate>
            <description><![CDATA[I made a little plugin for Obsidian: Note Codes.]]></description>
            <content:encoded><![CDATA[<p>
									<figure>
										<a href="/assets/obsidian/note-codes.png" target="_blank">
											<img src="/assets/thumbnails/obsidian/note-codes.png" alt="Obsidian with the Note Code plugin on the bottom right showing the code for a note about hedgehogs.">
										</a>
										<figcaption>Obsidian with the Note Code plugin on the bottom right showing the code for a note about hedgehogs.</figcaption>
									</figure>
								</p>
<p>I made a little plugin for Obsidian: <a href="https://github.com/SilverEzhik/obsidian-note-codes" class="external">Note Codes</a>.</p>
<p>It will assign a 4-character code to every note in your <a href="/obsidian">Obsidian</a> vault. Those codes let you quickly reference notes in your vault from other places such as hand-written notes.</p>
<p><a href="obsidian://show-plugin?id=note-codes" class="external">Click here to try it out.</a></p>
<p>To make things nicer, there's also a protocol handler, so something like <code>obsidian://note-codes/open?code=XX-XX</code> will open the note with the code <code>XX-XX</code>.</p>
<p>I tried to make these codes look nice even if they are handwritten. They're just four characters and some of the similar-looking ones are removed (though the plugin is smart enough to know that <code>AA-0A</code> is the same as <code>AA-OA</code>.</p>
<p>As usual, it's all open source: <a href="https://github.com/SilverEzhik/obsidian-note-codes" class="external">https://github.com/SilverEzhik/obsidian-note-codes</a></p>
<h1>What's in a note code?</h1>
<p>Note codes are generated based on a note's name and path, meaning that the note code will change if you rename your note.</p>
<p>Each note code consists of 4 alphanumeric characters. For clarity, <code>O</code>, <code>I</code>, <code>L</code>, and <code>U</code> are excluded from the codes, but this plugin will automatically handle these correctly - so <code>OI-LU</code> will automatically be treated as <code>01-1V</code> when searching. Same with the missing dash or lowercase letters.</p>
<p>Note codes are generated by SHA-256-hashing the note's path in the vault, then taking the first 20 bits of the hash and encoding them using <a href="https://www.crockford.com/base32.html" class="external">Douglas Crockford's Base32 encoding scheme</a>.</p>
<p>32^4 = 1,048,576, which is hopefully enough.</p>
<hr>
<h1>Hi Hacker News</h1>
<p>Looks like this extension made it on the orange website: <a href="https://news.ycombinator.com/item?id=45288739" class="external">https://news.ycombinator.com/item?id=45288739</a></p>
<p>I got some interesting feedback—the first is that probability of note code collisions can be a pretty high due to the <a href="https://en.wikipedia.org/wiki/Birthday_problem" class="external">birthday paradox</a> and the second is that if you rename a note, its code will change.</p>
<p>Both of these are consequences of how I chose to generate note codes. As said above, note codes are generated based off the note's path relative to the vault root. This means that note codes will be as stable as the note's name and path and won't be tied to Obsidian in any way—generating the note code is as simple as:</p>
<pre><code class="hljs language-python"><span class="keyword">import</span> hashlib

<span class="keyword">def</span> <span class="title function_">hash_string</span>(<span class="params">s</span>):
    h = hashlib.sha256(s.encode()).digest()
    n = ((h[<span class="number">0</span>] &#x3C;&#x3C; <span class="number">16</span>) | (h[<span class="number">1</span>] &#x3C;&#x3C; <span class="number">8</span>) | h[<span class="number">2</span>]) % (<span class="number">32</span>**<span class="number">4</span>)
    a = <span class="string">'0123456789ABCDEFGHJKMNPQRSTVWXYZ'</span>
    r = <span class="string">''</span>.join(a[n // <span class="number">32</span>**(<span class="number">3</span>-i) % <span class="number">32</span>] <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">4</span>))
    <span class="keyword">return</span> <span class="string">f"<span class="subst">{r[:<span class="number">2</span>]}</span>-<span class="subst">{r[<span class="number">2</span>:]}</span>"</span>
</code></pre>
<p>But it does come at the cost of the note code changing if the note is renamed.</p>
<p>I don't have a good answer to this. I don't think it's a good idea to tie note codes to Obsidian like that, but at the same time, the renaming problem is quite annoying—after all, Obsidian doesn't (as of me writing this) provide an API to go into the real world and rename every instance of a note name the way it can in the vault. So these note codes are only as stable as a deep link into Obsidian.</p>
<p>So far, I'm contemplating a small improvement on this. I still want to keep note codes as they are, however, I'd like to keep some sort of a vault-specific cache of note codes previously assigned to a note. This way, even if you rename a note, at least within the same vault, you'd still be able to access it via its previous note codes.</p>
<p>As for the birthday paradox, oops. I think ultimately it's not a critical issue. The worst case scenario here is that you'll look up a note code and then see multiple matches in the search panel. I honestly don't think this one can be solved any other way besides tying note codes to the Obsidian vault. Is it worth it? You tell me.</p>]]></content:encoded>
            <enclosure url="https://ezhik.jp/assets/thumbnails/obsidian/note-codes.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Losing the Plot That Was Never There]]></title>
            <link>/losing-the-plot-that-was-never-there</link>
            <guid>f939a3aa363063a9343fc409d9ee54f330403f0446508b2f0dc61054327b8156</guid>
            <pubDate>Tue, 12 Aug 2025 16:25:00 GMT</pubDate>
            <description><![CDATA[Somewhere along the way, we've lost the "fun" part of "for fun and profit".]]></description>
            <content:encoded><![CDATA[<p>
									<figure>
										<a href="/assets/making-the-world-a-better-place.png" target="_blank">
											<img src="/assets/thumbnails/making-the-world-a-better-place.jpg" alt="I don&#39;t know about you people, but I don&#39;t wanna live in a world where someone else makes the world a better place better than we do.">
										</a>
										<figcaption>I don&#39;t know about you people, but I don&#39;t wanna live in a world where someone else makes the world a better place better than we do.</figcaption>
									</figure>
								</p>
<aside class="left"><p>This post isn't going to go into all the man-made horrors created by Silicon Valley—places like <a href="https://www.404media.co/" class="external">404 Media</a> do a much better job covering that than I ever could.</p><p>By the way, all em dashes in this post are organically-made.</p></aside>
<p>Somewhere along the way, we've lost the "fun" part of "for fun and profit".</p>
<p>I have this temptation to act like a hipster and complain about people just being in it for the money now, but then I'd be making the exact same mistake that gradually drove Silicon Valley's culture insane: treating technology-as-a-craft and technology-as-a-job as one and the same.</p>
<p>Silicon Valley (and the tech industry as a whole) used to have a silly catchphrase about "making the world a better place" with tech. It was all lies, of course, but it was an interesting sign of lingering naive optimism held by a sizable enough portion of technologists still drunk on dreams of <a href="https://www.eff.org/cyberspace-independence" class="external">cyberspace independence</a>.</p>
<p>Being passionate—being a nerd—used to be an indicator of skill. Skilled programmers used to be in demand. To succeed as a tech company, you needed to sell an idea to the nerds. A "don't be evil" for the altruistic, a Tolkien allusion for those who above all else desired power—"passion" is not equal to "ethics", after all.</p>
<p>And so, passionate, well-fed programmers practiced tech as a craft at the same time as they practiced tech as a means to get paid. From all the open source projects to <a href="https://www.pacifict.com/story/" class="external">breaking into Apple offices to work on a graphing calculator</a>, it was an interesting time.</p>
<p>For the majority of programmers out there, though, the noble pursuit of putting food on the table is much more important than tech-as-a-craft.</p>
<p>But passion for the craft never stopped being a signal for skill. And signals get gamed.</p>
<h1>Perversion</h1>
<aside class="right">I know it's bait, but I just couldn't resist with the Hundred Rabbits mention.</aside>
<p>Imagine getting inspired by <a href="https://100r.co" class="external">a low-tech artist collective</a> to ruin your sleep schedule to... <a href="https://mattwie.se/no-sleep-till-agi" class="external">work on a B2B SaaS project</a>? What the fuck?</p>
<p>This is the cursed version of that graphing calculator story, with love for the craft sucked out and replaced with love for getting that MRR.</p>
<p>I seek love for the craft, and find perversion instead.</p>
<p>And that's my real problem—not somebody losing sleep to get paid—but them dressing that up as passion for <em>technology</em>. My problem is going to tech meetups and getting told to just make a web app when I try to talk about learning AppKit on an old PowerBook. My problem is that I have the misfortune of loving tech as a craft and as a hobby.</p>
<p>The <a href="https://fly.io/blog/youre-all-nuts/" class="external">hit rage-bait blog post of June 2025</a> had an entire section about the craft that pissed me off.</p>
<blockquote>
<p>Professional software developers are in the business of solving practical problems for people with code. We are not, in our day jobs, artisans. Steve Jobs was wrong: we do not need to carve the unseen feet in the sculpture. Nobody cares if the logic board traces are pleasingly routed. If anything we build endures, it won’t be because the codebase was beautiful.</p>
</blockquote>
<p>In our day jobs, we are not artisans. And that's fine! But when work and play bleed into each other so much, when "hacker" is synonymous with "entrepreneur", when the hell do we find the time for art?</p>
<blockquote>
<p>Do you like fine Japanese woodworking? All hand tools and sashimono joinery? Me too. Do it on your own time.</p>
</blockquote>
<p>When the hell do I get to talk about my Japanese woodworking, if the local crafts circle just fucking talks about leasing out IKEA furniture?</p>
<h1>For <del>Fun</del> and <del>Profit</del></h1>
<aside class="right">The profit seekers and the artisans are often the same person. Sometimes you wear one hat, sometimes you wear another.</aside>
<p>Now that we no longer pretend that technology is a force for good, now that we've struck out the fun from "for fun and profit", can we drop the charade? Can we let people who see technology as a job just do their job in peace? Can we let people who see technology as a craft just do their artisan projects in peace?</p>
<p>I don't want <a href="https://www.youtube.com/watch?v=73mEhS0gxgI" class="external">a yet another better computing manifesto</a>. I don't want to claim that real artists starve or that making money is or isn't evil. But can't we have a way to signal that we are doing something <a href="https://justforfunnoreally.dev/" class="external">for fun</a> or out of love for the craft? Can't we have a space to discuss creating <a href="https://maggieappleton.com/home-cooked-software" class="external">home-cooked software</a> without having to deflect monetization questions? To talk about generative art without getting NFTs involved? To talk about homelabs without getting told to migrate to the cloud?</p>
<aside class="right">Hobbyist programming?</aside>
<p>I mean, it's not like tech communities that prioritize the craft don't exist. The aforementioned low-tech arts collective, <a href="https://100r.co" class="external">Hundred Rabbits</a>, all the incredibly skilled retrocomputing enthusiasts, research laboratories like <a href="https://inkandswitch.com/" class="external">Ink &#x26; Switch</a>, the open source community, all the decentralized protocol hackers. There's tons and tons of interesting people out there.</p>
<p>It's just too bad that their voices get drowned out by an entirely different community with an entirely different set of priorities.</p>]]></content:encoded>
            <enclosure url="https://ezhik.jp/assets/thumbnails/making-the-world-a-better-place.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Obsidian's 10,000 note surprise]]></title>
            <link>/obsidian-10000-note-surprise</link>
            <guid>f437dbda9c6ba510614c02fe5764fd50c47bd0949fc134d45eb9ac621a01d3e0</guid>
            <pubDate>Tue, 20 May 2025 17:45:00 GMT</pubDate>
            <description><![CDATA[Once you hit 10,000 notes—and any alias or link to a non-existent note counts towards this limit—Obsidian's fuzzy search becomes Very Bad.]]></description>
            <content:encoded><![CDATA[<p>Once you hit 10,000 notes—and any alias or link to a non-existent note counts towards this limit—<a href="/obsidian">Obsidian</a>'s fuzzy search becomes Very Bad.</p>
<aside class="right">All em dashes in this post are organically-made.
<code>Opt</code>+<code>Shift</code>+<code>-</code> is all it takes. On my machine, at least.</aside>
<p>There's <a href="https://forum.obsidian.md/t/options-to-control-the-quick-switcher-links-suggestion-algorithm-above-10000-items-ignore-spaces/56861" class="external">a feature request on the Obsidian forum with more details</a>, but the really nice fuzzy search mechanism where you can type something like <code>[[objc</code> and have it match "<strong>Obj</strong>ective-<strong>C</strong>" just no longer works.</p>
<p>Obsidian developers say the limit is there for performance reasons, but I've paid lots of yen for my computer and so I intend to play my note-taking apps with maximum settings.</p>
<p>Here's a cursed little plugin that works around the problem as of Obsidian 1.8.10 using the ✨Monte Carlo method✨:</p>
<p><strong><a href="/assets/obsidian/patch-fuzzy-search.zip">Download (1.1.0)</a></strong></p>
<p>Unzip that to your vault's plugin folder (there should be a button to open it in the <em>Community plugins</em> section of the settings), then reload your plugins and enable it.</p>
<p><del>I've only encountered this problem when inserting links, so not sure if this helps with the quick switcher—please let me know if it doesn't.</del>
<strong>UPDATE 2025-06-07:</strong> There's now a fix for the quick switcher too, using the same mechanism.</p>
<h1>Tell me more</h1>
<p>I had to do a bit of digging and reverse engineering to figure this one out. Obsidian's all webpacked and stuff so sadly I couldn't easily patch the check itself from a plugin, so instead I patched the function that collects files to only return up to 9,999 items, sampled randomly, with the sample reshuffled once a second or so (check the code out, it's very silly). If you are just a little over the limit, this works pretty well in practice as the list gets reshuffled while you type.</p>
<h1>Tell me less</h1>
<p>Now your fuzzy searches can be even fuzzier.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[f-string.lua]]></title>
            <link>/f-string.lua</link>
            <guid>88cb174cc20b938ada233d58e1ccfcc274a6e4006da88a8fd11ef73afc4e288c</guid>
            <pubDate>Sun, 13 Apr 2025 12:40:00 GMT</pubDate>
            <description><![CDATA[A Python-like f-string implementation for Lua.]]></description>
            <content:encoded><![CDATA[<p>A <a href="/python">Python</a>-like f-string implementation for <a href="/lua">Lua</a>.</p>
<p><a href="https://github.com/SilverEzhik/f-string.lua" class="external">Source code (MPL-2.0)</a></p>
<p>You can play with it above by using <code>f"fancy strings with variable interpolations: {1 + 2 = }"</code> in it and they should work exactly as you'd expect.</p>
<pre><code class="hljs language-lua">f = <span class="built_in">require</span>(<span class="string">"f-string"</span>)

<span class="keyword">local</span> who = <span class="string">"hedgehogs"</span>
<span class="built_in">print</span>(f<span class="string">"hello {who}!"</span>)
</code></pre>
<h2>Features</h2>
<p>You can put any Lua expression into the f-string and it'll hopefully work.</p>
<ul>
<li><code>=</code> endings are supported:</li>
</ul>
<pre><code class="hljs language-lua">> f<span class="string">"{1 + 1 = }"</span>
<span class="number">1</span> + <span class="number">1</span> = <span class="number">2</span>
</code></pre>
<ul>
<li>Formatting specs support everything in <code>string.format</code> and can be used with <code>:%</code>:</li>
</ul>
<pre><code class="hljs language-lua">> f<span class="string">"{100:%.2f}"</span>
<span class="number">100.00</span>
</code></pre>
<h2>Caveats</h2>
<ul>
<li>Upvalue access is kinda broken: Variables from outer scopes of functions (upvalues) are only reliably accessible if they are referenced outside the f-string. For example:</li>
</ul>
<pre><code class="hljs language-lua"><span class="keyword">local</span> x = <span class="string">"outer scope"</span>
<span class="keyword">local</span> <span class="function"><span class="keyword">function</span> <span class="title">inner</span><span class="params">()</span></span>
	<span class="comment">-- This reference is needed for the upvalue to be accessible in the f-string</span>
	<span class="keyword">if</span> x <span class="keyword">then</span> <span class="keyword">end</span>
	<span class="keyword">local</span> result = f<span class="string">"x = {x}"</span>  <span class="comment">-- Now works correctly</span>
<span class="keyword">end</span>
</code></pre>
<ul>
<li>Uses <code>debug</code> so is probably slow as heck</li>
</ul>
<h1>Making-of</h1>
<p>This was fun to make. Writing parsers is always good fun and the little one I hand-rolled here seems to do the job well enough, giving this handling for strings and comments that's a lot better than a regex-based approach. Writing up little parsers comes up a lot for me and previously I'd try to take care of the stack myself and only go character by character, but here I tried to use the function stack as the stack (hence all the parse local functions) for some recursive descent parser action, along with making it easier to take and check multiple characters ahead and it made for a pretty nice experience for me.</p>
<p>I did ask <em>the computer</em> to write <code>test.lua</code> for me, which saved some time but was not as fun. It works though, I guess.</p>
<p><a href="https://github.com/SilverEzhik/f-string.lua" class="external">Source code (MPL-2.0)</a></p>]]></content:encoded>
            <enclosure url="https://ezhik.jp/assets/f-string.lua.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Promise.lua]]></title>
            <link>/promise.lua</link>
            <guid>2b4fee4546e858633e0d88d0b9667c0e39c4c71900c1e4f123a3a9978411edae</guid>
            <pubDate>Sat, 29 Mar 2025 09:15:00 GMT</pubDate>
            <description><![CDATA[A JavaScript-style Promise library for Lua.]]></description>
            <content:encoded><![CDATA[<p>A JavaScript-style Promise library for <a href="/lua">Lua</a>.</p>
<p><a href="https://github.com/SilverEzhik/Promise.lua" class="external">Source code (MPL-2.0)</a>
<a href="https://luarocks.org/modules/ezhik/promise-dot-lua" class="external">LuaRocks</a></p>
<p>You can play with it above by messing with the <code>Promise</code> object and also <code>setTimeout</code> and <code>setInterval</code> which work exactly as you'd expect them to work in JavaScript.</p>
<h1>The API</h1>
<p>I tried to cover most of the API I saw on <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise" class="external">MDN</a>:</p>
<ul>
<li>Constructor
<ul>
<li><strong><code>Promise.new(fn)</code></strong> – returns a new Promise. <code>fn</code> is a function which takes in two parameters, <code>resolve</code> and <code>reject</code>, both of which are functions that will either resolve or reject the promise with the given value. If the value given to <code>resolve</code> is a promise or a table that has a <code>next</code> method, it will use the result of that method instead.</li>
</ul>
</li>
<li>Instance methods
<ul>
<li><strong><code>:next(onFulfilled, onRejected)</code></strong> – registers callbacks to run when the promise is settled (either fulfilled or rejected). This is called <code>next</code> instead of <code>then</code> as <code>then</code> is a reserved keyword in Lua.</li>
<li><strong><code>:catch(onRejected)</code></strong> – registers a callback to run when the promise is rejected.</li>
<li><strong><code>:finally(onFinally)</code></strong> – registers a callback that will run when the promise is settled. This promise will settle with whatever the original promise's state is.</li>
</ul>
</li>
<li>Static methods
<ul>
<li><strong><code>Promise.all(promises)</code></strong> – takes a list of promises and returns a promise that is fulfilled when all input promises are fulfilled or is rejected when any of the promises are rejected.</li>
<li><strong><code>Promise.allSettled(promises)</code></strong> – takes a list of promises and returns a promise that is fulfilled when all input promises are settled with a list of tables in the shape of <code>{ status = "fulfilled", value = &#x3C;...> }</code> or <code>{ status = "rejected", reason = &#x3C;...> }</code>.</li>
<li><strong><code>Promise.any(promises)</code></strong> – takes a list of promises and returns a promise that is fulfilled when any of the input promises are fulfilled or is rejected when all of the input promises are rejected.</li>
<li><strong><code>Promise.race(promises)</code></strong> – takes a list of promises and returns a promise that is settled with the state of the first promise in the list to settle.</li>
<li><strong><code>Promise.reject(reason)</code></strong> – returns a promise that's rejected with the given reason.</li>
<li><strong><code>Promise.resolve(value)</code></strong> – returns a promise that's resolved with the given value.</li>
<li><strong><code>Promise.try(fn)</code></strong> – takes a callback of any kind and wraps its result in a promise.</li>
<li><strong><code>Promise.withResolvers()</code></strong> – returns a promise, its <code>resolve</code> function, and its <code>reject</code> function.
I also added a few extra helpers for my own sake:</li>
</ul>
</li>
<li>Async-await
<ul>
<li><strong><code>Promise.async(fn)</code></strong> – returns a function that returns a promise that will be resolved or rejected based on the result of executing <code>fn</code> in a coroutine.</li>
<li><strong><code>:await()</code></strong> – if executed from a coroutine, will yield until the promise is settled and return the resolved value or <code>error</code> with the rejection reason.</li>
</ul>
</li>
<li>Various
<ul>
<li><strong><code>:ok()</code></strong> – converts rejections of the promise into <code>nil</code>, think Rust's <a href="https://doc.rust-lang.org/std/result/enum.Result.html#method.ok" class="external">Result.ok()</a>.</li>
<li><strong><code>:print()</code></strong> – prints the settled result of the promise without modifying it.</li>
</ul>
</li>
</ul>
<p>I've kept this async runtime-agnostic. You can override the <strong><code>Promise.schedule(fn)</code></strong> function with your own implementation that will schedule functions for later asynchronous execution. An example implementation for Hammerspoon is provided in the repository.</p>
<h1>Making of</h1>
<p>I've been suffering from callback hell when automating things with <a href="/hammerspoon">Hammerspoon</a>, so I ended up using <a href="https://github.com/zserge/lua-promises" class="external">zserge/lua-promises</a> (<code>deferred</code>) for a while, but then decided to write my own because reinventing the wheel is a good way to learn how the wheel works.</p>
<p>It was pretty fun to make. The <a href="https://tc39.es/ecma262/multipage/control-abstraction-objects.html#sec-promise-objects" class="external">TC39</a> specification for JavaScript is insanely detailed, so I always could count on it to help me resolve any ambiguities, though I didn't implement it word for word.</p>
<p>I actually get quite a lot of use out of those async-await functions in Hammerspoon, as they let me completely flatten functions that deal with UI automation, which can require waiting for animations to finish and so on.</p>
<p><a href="https://github.com/SilverEzhik/Promise.lua" class="external">Source code (MPL-2.0)</a></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Disabling Dash 7 paid upgrade prompts]]></title>
            <link>/disabling-dash-7-paid-upgrade-prompts</link>
            <guid>c1b104915824827fd3300b976a4d07972784c6ec9139a7b24b57866c3f704b5b</guid>
            <pubDate>Sat, 29 Mar 2025 07:30:00 GMT</pubDate>
            <description><![CDATA[So I use a rather nice documentation browser, Kapeli's Dash. With version 7, this app switched to a subscription model, which came with an unfortunate side effect of daily paid upgrade prompts for those staying on their one-time purchase licenses.]]></description>
            <content:encoded><![CDATA[<p>
									<figure>
										<a href="/assets/dash-7-paid-upgrade-prompt.png" target="_blank">
											<img src="/assets/thumbnails/dash-7-paid-upgrade-prompt.png" alt="This prompt appears every single day.">
										</a>
										<figcaption>This prompt appears every single day.</figcaption>
									</figure>
								</p>
<p>So I use a rather nice documentation browser, Kapeli's <a href="https://kapeli.com/dash" class="external">Dash</a>. With version 7, this app switched to a subscription model, which came with an unfortunate side effect of daily paid upgrade prompts for those staying on their one-time purchase licenses.</p>
<p>Run this in the terminal to disable those update prompts:</p>
<pre><code class="hljs language-sh">defaults write com.kapeli.dashdoc SUFeedURL <span class="string">""</span>
</code></pre>
<p>To restore things back to normal, run:</p>
<pre><code class="hljs language-sh">defaults delete com.kapeli.dashdoc SUFeedURL
</code></pre>
<aside class="right">As this disables all update checks for Dash, you probably should keep an eye on their website to make sure you don't miss out on any important security notices for the app.</aside>
<p>This doesn't really seem like a malicious action on the part of the developer – the prompt is just a standard <a href="https://sparkle-project.org/" class="external">Sparkle</a> update window, used by most artisan Mac software. However, I couldn't find a toggle to disable update checks in the app's settings, so I had to figure out a slightly more manual way.</p>
<p>I tried the <a href="/nsuserdefaults">NSUserDefaults</a> route next, as Sparkle offers ways to customize itself through <code>Info.plist</code> configuration: <a href="https://sparkle-project.org/documentation/customization/" class="external">https://sparkle-project.org/documentation/customization/</a> – however, both <code>SUEnableAutomaticChecks</code> and <code>SULastCheckTime</code> keys would get reset whenever I relaunched Dash. So I tried setting the URL that the app polls for new updates to an empty string and that seemed to do the trick.</p>
<p>By the way, it's pretty neat that Sparkle update feeds are just RSS. <a href="/til">The more you know!</a></p>]]></content:encoded>
            <enclosure url="https://ezhik.jp/assets/thumbnails/dash-7-paid-upgrade-prompt.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Spreadsheet #3]]></title>
            <link>/spreadsheet-3</link>
            <guid>5153cfbb9327c74f74fc0875791693882f3779ac139ceb85179b45fd66a0c0ff</guid>
            <pubDate>Sun, 23 Mar 2025 08:00:00 GMT</pubDate>
            <description><![CDATA[What if Spreadsheet #2 was Lua? You get Spreadsheet #3.]]></description>
            <content:encoded><![CDATA[<p>What if <a href="/spreadsheet-2">Spreadsheet #2</a> was <a href="/lua">Lua</a>? You get Spreadsheet #3.</p>
<p>Here we get to use one of the coolest parts of Lua: metatables. You can set all sorts of properties on objects using this way – what happens when you call or index them, what happens when you assign, what happens when you use operators, and so on.</p>
<p>The special <code>__index</code> metatable method will be called whenever you try to access a non-existent table property – so if I try to do <code>foo.bar</code> when <code>bar</code> doesn't exist, <code>__index</code> will be called, giving me a chance to resolve its value.</p>
<p>Now, Lua has a global table, <code>_G</code>. This table holds all the global variables – think JavaScript's <code>window</code>.</p>
<p>And you can set a metatable on that as well. This means that we can use metatable tricks like <code>__index</code> with global variables.</p>
<p>To make things even cooler, Lua lets you evaluate some code using <a href="https://www.lua.org/manual/5.4/manual.html#pdf-load" class="external"><code>load()</code></a>, letting you set that code's environment. This means we can craft our own environment that'll automatically resolve dependencies for us whenever the cell formula tries to access any other cell, all while keeping each cell isolated from one another since they all have their own unique environments.</p>
<p>Pretty neat!</p>
<p>I originally wrote this as a more JavaScript-intensive thing, where each cell got its own Lua interpreter, efficiency be damned. But the more I looked at metatables, the more I started to realize how powerful they are.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Lua]]></title>
            <link>/lua</link>
            <guid>8c90710df0cd555d9bd25cb96254f4821470da8fc657c76423185b43be20adb6</guid>
            <pubDate>Sat, 22 Mar 2025 17:45:00 GMT</pubDate>
            <description><![CDATA[Lua is a programming language that's often used in embedded contexts. It's pretty small and pretty cute.]]></description>
            <content:encoded><![CDATA[<p>Lua is a programming language that's often used in embedded contexts. It's pretty small and pretty cute.</p>
<p>Please enjoy a little REPL up above.</p>
<aside class="right"><p>This REPL powered by <a href="https://github.com/ceifa/wasmoon" class="external">wasmoon</a>. Wasmoon is nice to work with, but I sure wish it had documentation.</p><p>Pretty cool that we can just run Lua in the web browser.</p></aside>
<p><a href="https://lua.org" class="external">https://lua.org</a></p>
<p>I do like working with Lua, though some things about it mildly annoy me:</p>
<ul>
<li>No assignment operators like <code>+=</code></li>
<li>No destructuring assignments like <code>[a, b] = tbl</code></li>
<li><code>~=</code> instead of <code>!=</code></li>
<li>No <code>continue</code> in loops</li>
<li>Inability to have statements after <code>return</code> so I have to do <code>do return end</code> whenever I want to quickly return while testing</li>
<li>No shorthand for function definitions like JavaScript's arrow functions</li>
<li>No syntax for coroutines</li>
<li>No string interpolation</li>
</ul>
<p>Most of these are syntactic, which is interesting. The common one-based array indexing complaint doesn't really bother me that much in practice.</p>
<p>It'd be nice to have some more batteries, but it's fine, I guess.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Fixing window frame manipulation in Hammerspoon]]></title>
            <link>/fixing-window-frame-manipulation-in-hammerspoon</link>
            <guid>153d56d9e762d8a8a11ff3edde9fffecea892bd2963966e1625292e9ca3dbf13</guid>
            <pubDate>Tue, 18 Mar 2025 17:30:00 GMT</pubDate>
            <description><![CDATA[I use Hammerspoon to automate all sorts of things on my Mac. I've been running into some annoying issues where trying to use :setFrame(...) and the like on hs.window objects would behave erratically.]]></description>
            <content:encoded><![CDATA[<p>I use <a href="/hammerspoon">Hammerspoon</a> to automate all sorts of things on my Mac. I've been running into some annoying issues where trying to use <code>:setFrame(...)</code> and the like on <code>hs.window</code> objects would behave erratically.</p>
<p>
									<figure>
										<a href="/assets/hammerspoon/setframemachinebroke.gif" target="_blank">
											<img src="/assets/hammerspoon/setframemachinebroke.gif" alt="Trying to resize a window with the animation duration set to zero, with the window not moving properly and still animating the resize animation.">
										</a>
										<figcaption>Trying to resize a window with the animation duration set to zero, with the window not moving properly and still animating the resize animation.</figcaption>
									</figure>
								</p>
<p>Here's what's actually supposed to happen:</p>
<p>
									<figure>
										<a href="/assets/hammerspoon/setframemachinefixed.gif" target="_blank">
											<img src="/assets/hammerspoon/setframemachinefixed.gif" alt="Same exact resize operations as above, but with the fix applied. The window now moves across an invisible grid instantly.">
										</a>
										<figcaption>Same exact resize operations as above, but with the fix applied. The window now moves across an invisible grid instantly.</figcaption>
									</figure>
								</p>
<h1>The fix</h1>
<pre><code class="hljs language-lua"><span class="comment">---</span>
<span class="comment">--- Monkeypatch for hs.window operations to temporarily</span>
<span class="comment">--- disable accessibility while moving/resizing windows</span>
<span class="comment">---</span>

<span class="keyword">do</span>
    <span class="keyword">local</span> axOnTimers = {}
    <span class="keyword">local</span> axOriginalState = {}
    <span class="keyword">local</span> windowMT = hs.getObjectMetatable(<span class="string">"hs.window"</span>)

    <span class="comment">-- clean up when apps are closed</span>
    hs.window.axTimerWatcher = hs.application.watcher.new(<span class="function"><span class="keyword">function</span><span class="params">(name, event, app)</span></span>
        <span class="keyword">if</span> event == hs.application.watcher.terminated <span class="keyword">then</span>
            <span class="keyword">local</span> pid = app:pid()
            <span class="keyword">if</span> axOnTimers[pid] <span class="keyword">then</span>
                axOnTimers[pid]:stop()
            <span class="keyword">end</span>
            axOnTimers[pid] = <span class="literal">nil</span>
            axOriginalState[pid] = <span class="literal">nil</span>
        <span class="keyword">end</span>
    <span class="keyword">end</span>):start()


    <span class="keyword">local</span> <span class="function"><span class="keyword">function</span> <span class="title">patch</span><span class="params">(fn)</span></span>
        <span class="keyword">return</span> <span class="function"><span class="keyword">function</span><span class="params">(window, ...)</span></span>
            <span class="keyword">local</span> app = window:application()
            <span class="keyword">local</span> pid = app:pid()

            <span class="keyword">local</span> ax = hs.axuielement.applicationElement(app)
            <span class="comment">-- disable accessibility, remembering what the original state was</span>
            <span class="built_in">pcall</span>(<span class="function"><span class="keyword">function</span><span class="params">()</span></span>
                <span class="keyword">if</span> <span class="keyword">not</span> axOriginalState[pid] <span class="keyword">then</span>
                    axOriginalState[pid] = {
                        AXEnhancedUserInterface = ax.AXEnhancedUserInterface,
                        AXManualAccessibility = ax.AXManualAccessibility
                    }
                <span class="keyword">end</span>

                ax.AXEnhancedUserInterface = <span class="literal">false</span>
                ax.AXManualAccessibility = <span class="literal">false</span>
            <span class="keyword">end</span>)

            <span class="keyword">local</span> ok, result = <span class="built_in">pcall</span>(fn, window, ...)

            <span class="comment">-- restore accessibility after a short delay</span>
            axOnTimers[pid] = (
                axOnTimers[pid]
                <span class="keyword">or</span> hs.timer.delayed.new(
                    <span class="built_in">math</span>.<span class="built_in">max</span>(<span class="number">0.2</span>, hs.window.animationDuration <span class="keyword">or</span> <span class="number">0</span>),
                    <span class="function"><span class="keyword">function</span><span class="params">()</span></span>
                        <span class="keyword">local</span> orig = axOriginalState[pid] <span class="keyword">or</span> {}

                        <span class="built_in">pcall</span>(<span class="function"><span class="keyword">function</span><span class="params">()</span></span>
                            ax.AXEnhancedUserInterface = orig.AXEnhancedUserInterface
                            ax.AXManualAccessibility = orig.AXManualAccessibility
                        <span class="keyword">end</span>)

                        axOriginalState[pid] = <span class="literal">nil</span>
                    <span class="keyword">end</span>)):start()

            <span class="keyword">if</span> ok <span class="keyword">then</span>
                <span class="keyword">return</span> result
            <span class="keyword">else</span>
                <span class="built_in">error</span>(result)
            <span class="keyword">end</span>
        <span class="keyword">end</span>
    <span class="keyword">end</span>

    <span class="keyword">for</span> _, key <span class="keyword">in</span> <span class="built_in">ipairs</span>({
        <span class="string">"setFrame"</span>, <span class="string">"setFrameInScreenBounds"</span>,
        <span class="string">"setFrameWithWorkarounds"</span>, <span class="string">"setTopLeft"</span>,
        <span class="string">"setSize"</span>, <span class="string">"maximize"</span>, <span class="string">"move"</span>,
        <span class="string">"moveToUnit"</span>, <span class="string">"moveToScreen"</span>,
        <span class="string">"moveOneScreenEast"</span>, <span class="string">"moveOneScreenNorth"</span>,
        <span class="string">"moveOneScreenSouth"</span>, <span class="string">"moveOneScreenWest"</span>,
    }) <span class="keyword">do</span>
        windowMT[key] = patch(windowMT[key])
    <span class="keyword">end</span>
<span class="keyword">end</span>

<span class="comment">-- 🦔🦔🦔</span>

</code></pre>
<p>This issue seems to be related to accessibility services – when they are enabled for a specific app, it may misbehave. The Hammerspoon team is tracking the problem as <a href="https://github.com/Hammerspoon/hammerspoon/issues/3224" class="external">Bug #3224</a>, but this appears to be an application-specific issue.</p>
<p>Unfortunately the application in question is Chrome and in the future, everything is Chrome.</p>
<p><a href="/til">The more you know</a>, the more Chrome you start to notice.</p>]]></content:encoded>
            <enclosure url="https://ezhik.jp/assets/hammerspoon/setframemachinebroke.gif" length="0" type="image/gif"/>
        </item>
        <item>
            <title><![CDATA[Hypertext Maximalism]]></title>
            <link>/hypertext-maximalism</link>
            <guid>7f7f9dc8053afdd5782967fc51ba842bd91c1df72156ec01b6694f1c13ec9a79</guid>
            <pubDate>Sun, 16 Mar 2025 16:10:00 GMT</pubDate>
            <description><![CDATA[I've always been enamored with the idea of note-taking but always struggled with actually practicing it. But I managed to develop a nice system for it.]]></description>
            <content:encoded><![CDATA[<p>I've always been enamored with the <em>idea</em> of note-taking but always struggled with actually practicing it. But I managed to develop a nice system for it.</p>
<p>And here it is:</p>
<h1>The System</h1>
<ol>
<li>Get a note-taking app with links and backlinks, such as <a href="/obsidian">Obsidian</a>.</li>
<li>Make sure every note is connected via links or backlinks to at least two other notes</li>
<li>Go write.</li>
</ol>
<h1>What?</h1>
<p>Okay, so. I've sucked at note-taking for most of my life.</p>
<p>Andy Mauschak's working notes. Zettelkasten. Johnny Decimal. Digital gardens. Bullet Journals. All these cool people and cool systems. I've tried to emulate them and I failed.</p>
<p>From piles of Moleskine notebooks to Surfaces and iPad Pros, from Evernote and OneNote to plain old text files and Apple Notes, there is a graveyard of note-taking tech in my drawers, including some attempts of my own like <a href="/notegram">Notegram</a> and <a href="/iroiro">IroIro</a> <s>and all the hours wasted trying to comprehend <code>contenteditable</code></s>. I was always looking for a better bicycle for the mind having never learned to ride a bike to begin with.</p>
<aside class="right">People who use some of the smarter systems I've mentioned above might instantly point out many flaws with hypertext maximalism, especially as the amount of notes grows. But consider this as a metaphorical <strong>ladder to be thrown away</strong>. This system got me to actually start note-taking consistently, and that I think is the most important aspect of it.</aside>
<p>I couldn't do analog notes because I'd misplace notebooks.<br>
I couldn't do folders because I'd always want to put one note into multiple folders.<br>
I couldn't do tags because I'd want to write a note about the tag itself.<br>
And so on, and so on, forever distracted by the meta-problem of <em>how</em> to write rather than <em>what</em> to write.</p>
<p>But one day a friend suggested Obsidian, and it just clicked for me – just the right kind of mix between Markdown and What-You-See-Is-What-You-Get, and, of course, the way it displays backlinks very, very prominently.</p>
<p>So I decided to just roll with it. Sick to death of worrying about these systems, I decided to just roll with the three rules above. No tags, no folders, no categories, no inboxes. Only hypertext.</p>
<p>At first, you navigate through weaponized wiki-wandering – dropping into the closest relevant note and clicking through links and backlinks until you find what you need. But eventually, patterns start to emerge. Perhaps you start to use some notes as hubs. And so the organization system emerges organically. Things start to fall into places where your own brain expects them to be, rather than artificial categories made up on the spot. And all that happens without having to think much about it.</p>
<p>That's Hypertext Maximalism.</p>
<p>From zero notes three years ago, to 6,961 as of me writing this, I think this system worked out pretty well for me.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Posting a little more code]]></title>
            <link>/posting-a-little-more-code</link>
            <guid>4cad40ea6e7a218d5a18fe0e19243e514364e0c973c91a4d9d48be7e30910476</guid>
            <pubDate>Wed, 12 Mar 2025 12:00:00 GMT</pubDate>
            <description><![CDATA[Here's a little enhancement to the website. All the little apps will now have a little link to the source code on the bottom that'll take you to the website repository.]]></description>
            <content:encoded><![CDATA[<p>
									<figure>
										<a href="/assets/hedgehog-footer.png" target="_blank">
											<img src="/assets/thumbnails/hedgehog-footer.jpg" alt="Posts with apps will now have a little &quot;source code&quot; link at the bottom.">
										</a>
										<figcaption>Posts with apps will now have a little &quot;source code&quot; link at the bottom.</figcaption>
									</figure>
								</p>
<p>Here's a little enhancement to the website. All the little apps will now have a little link to the source code on the bottom that'll take you to the website repository.</p>
<aside class="right">Once again I was inspired by a TodePond talk - "<a href="https://www.youtube.com/watch?v=MJzV0CX0q8o" class="external">What it means to be open</a>" this time.</aside>
<p>I'm now also including the original TypeScript code for these along with source maps, so if anybody here likes to play with browser developer tools, you'll hopefully have a better time doing it.</p>]]></content:encoded>
            <enclosure url="https://ezhik.jp/assets/thumbnails/hedgehog-footer.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Spreadsheet #2]]></title>
            <link>/spreadsheet-2</link>
            <guid>b4d3cac3340999bfc86dcfc98007460086d375650ca359b1985a6e99e831ab0f</guid>
            <pubDate>Tue, 11 Mar 2025 16:00:00 GMT</pubDate>
            <description><![CDATA[What if we could set other properties besides just the cell value?]]></description>
            <content:encoded><![CDATA[<p>What if we could set other properties besides just the cell value?</p>
<p>Here's <a href="/spreadsheet-1">Spreadsheet No. 1</a> enhanced to allow setting properties based on other cells. It's still all nice and reactive.</p>
<p>I might be on to something interesting here...</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Spreadsheet #1]]></title>
            <link>/spreadsheet-1</link>
            <guid>815167e9bda3680814b443e037967b535ea545651f965ede7a94f157996a671b</guid>
            <pubDate>Sun, 09 Mar 2025 16:00:00 GMT</pubDate>
            <description><![CDATA[Let's make a spreadsheet. No looking at hints or cool algorithms. All bugs are intentional.]]></description>
            <content:encoded><![CDATA[<p>Let's make a spreadsheet. No looking at hints or cool algorithms. All bugs are intentional.</p>
<aside class="right"><p>Maybe we can call that <em>naive coding</em> – I just start writing and see where that takes me. Not sure how often people write spreadsheet software for fun, though.</p><p>And yes, I know that <code>eval</code> and friends are not production-grade.</p></aside>
<p>So, there's a very basic spreadsheet up above. In each cell, you can type a JavaScript expression and it will execute. You can refer to cells by the name that shows up when you hover your mouse cursor over a cell (<code>A1</code>, <code>B1</code>, <code>A2</code>, <code>B2</code>...).</p>
<h1>So how do you make a spreadsheet, anyway?</h1>
<p>I made a weird React hook that will give you:</p>
<ul>
<li>An object mapping cell IDs to formulas (just good old JS)</li>
<li>An object mapping cell IDs to computed values</li>
<li>A function to set a cell's value</li>
</ul>
<p>That hook also has an effect that actually handles cell recalculation. When a formula changes:</p>
<ul>
<li>Make a cache that'll store computed outputs</li>
<li>Convert all formulas to <code>AsyncFunction</code>s
<ul>
<li>Replace each cell reference with a retrieval of its cached result or queuing that cell reference's formula for execution and awaiting that execution's completion</li>
</ul>
</li>
<li>Execute all the <code>AsyncFunction</code>s and await for them to finish</li>
</ul>
<p>Spreadsheets are directed graphs and this is just recursive depth-first graph traversal in disguise, I suppose. I honestly thought this part would be harder, but it actually was alright. I think there are better ways to do it than using JavaScript promises as a weird cache, though.</p>
<h1>Some food for thought</h1>
<ul>
<li>Can the computation/caching be done using React somehow, rather than JS?</li>
<li>The whole spreadsheet gets redrawn on every change which is lame
<ul>
<li>That makes sense given that there is one big blob of all the data that's created at the spreadsheet level rather than at the cell level</li>
</ul>
</li>
<li>Cycles produce <code>NaN</code> – would love to step through that with a debugger to figure out why
<ul>
<li>Cool that they don't just freeze up or whatever, though</li>
</ul>
</li>
<li>Do cells really have to just be cells?</li>
</ul>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The new domain]]></title>
            <link>/the-new-domain</link>
            <guid>b487ca3bab074dc08819f59c82991a9af9eb5826b0de288896ce16aeb3f073b4</guid>
            <pubDate>Sat, 08 Mar 2025 07:30:00 GMT</pubDate>
            <description><![CDATA[On one of those good old days, I wanted to create an identity for myself online. I called myself Ezhik – a hedgehog – and I was building a corner on the internet that was for me. Hence, ezhik.me.]]></description>
            <content:encoded><![CDATA[<p>
									<figure>
										<a href="/assets/2013-12-27-hog.jpg" target="_blank">
											<img src="/assets/thumbnails/2013-12-27-hog.jpg" alt="Hedgehogs and URLs during a trip a long time ago">
										</a>
										<figcaption>Hedgehogs and URLs during a trip a long time ago</figcaption>
									</figure>
								</p>
<p>On one of those <em>good old days</em>, I wanted to create an identity for myself online. I called myself <em>Ezhik</em> – a hedgehog – and I was building a corner on the internet that was for <em>me</em>. Hence, <em>ezhik.me</em>.</p>
<p>It was a different time. The internet was still young and there was still hope for a brighter future unburdened by all the baggage of history, all the war, all the nations, all the ideologies. The twentieth century was over but the twenty-first did not yet truly begin.</p>
<aside class="right">Young Ezhik was a naive idealist, I know.</aside>
<p>But times have changed. History never ended. And so we are back to the same old mess. <em>Cyberspace</em> never became independent. Worse than that, it was colonized. Little communities, all swept away by waves of corporate interests, propaganda, and psyops.</p>
<p>And so, rather than a cute domain hack, I have to look at <em>.me</em> for what it truly is: the top-level domain (TLD) for the country of Montenegro. And if I'm going to use a country-based TLD, rather than one that just so happened to have a nice ISO 3166 code, I might as well use the TLD for the country that accepted me.</p>
<p>So, <em>ezhik.jp</em> it is.</p>]]></content:encoded>
            <enclosure url="https://ezhik.jp/assets/thumbnails/2013-12-27-hog.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Picture in Picture for your photo gallery]]></title>
            <link>/pip-pwa</link>
            <guid>064ec51176409c7000bf4445c9e7f0d3b2c1890cb24c102eae75b783bd1e9039</guid>
            <pubDate>Fri, 07 Mar 2025 17:00:00 GMT</pubDate>
            <description><![CDATA[My phone doesn't let me do picture in picture from its photo gallery and that's just stupid.]]></description>
            <content:encoded><![CDATA[<p>My phone doesn't let me do picture in picture from its photo gallery and that's just stupid.</p>
<p>So this little app will have to do instead.</p>
<p>Select a video by clicking that button above, it'll appear right below it. You can then PiP that.</p>
<p>I don't receive your videos, I don't need them. This is all like 50 lines of JavaScript magic.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Fixing 縦書き text in EPUBs on Kindle for iOS]]></title>
            <link>/fixing-縦書き-text-in-epubs-on-kindle-for-ios</link>
            <guid>82eb31e51ff8f261901779352ca62efbd9f360e64e4b8eea4893a944e16c786c</guid>
            <pubDate>Fri, 07 Mar 2025 14:15:00 GMT</pubDate>
            <description><![CDATA[I was uploading a Japanese book in .epub format via Send to Kindle, but ran into a weird issue: the book would not display as right-to-left vertical text (縦書き), instead displaying as horizontal, left-to-right text, on the Kindle app for iOS and Mac, while still looking fine on my Kindle e-reader.]]></description>
            <content:encoded><![CDATA[<p>I was uploading a Japanese book in <code>.epub</code> format via <em>Send to Kindle</em>, but ran into a weird issue: the book would not display as right-to-left vertical text (縦書き), instead displaying as horizontal, left-to-right text, on the Kindle app for iOS and Mac, while still looking fine on my Kindle e-reader.</p>
<p><a href="https://note.com/atsu1166/n/nc2d0ad0f3aab" class="external">This note.com post by Atsu1166</a> has a fix that worked for me - edit the <code>.epub</code> file's <code>.opf</code> <em>rootfile</em> (This was <code>content.opf</code> in my case) to add the following tag in its <code>&#x3C;metadata></code>:</p>
<pre><code class="hljs language-xml"><span class="tag">&#x3C;<span class="name">meta</span> <span class="attr">name</span>=<span class="string">"primary-writing-mode"</span> <span class="attr">content</span>=<span class="string">"horizontal-rl"</span>/></span>
</code></pre>
<aside class="right">You read that right, <strong>horizontal</strong> and not <strong>vertical</strong>.</aside>
<p>For a bit more context, <code>.epub</code> files are really <code>.zip</code> archives containing all the files the book is made out of. You should be able to either unzip the file and edit that <code>.opf</code> <em>rootfile</em> manually, or, if you use Calibre, edit the epub to update it.</p>
<p>So far I only encountered <code>.opf</code> <em>rootfiles</em> located at the top of the archive, but apparently the EPUB standard specifies that these will be defined in the <code>META-INF/container.xml</code> file in a <code>&#x3C;rootfile></code> tag.</p>
<p><a href="/til">The more you know...</a></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Adding a little interactivity]]></title>
            <link>/adding-a-little-interactivity</link>
            <guid>eb107c9093d88b0a70ad1b71d2e93ee6ed12f14ebb0ac655124fdb9f2e3a961b</guid>
            <pubDate>Thu, 06 Mar 2025 14:00:00 GMT</pubDate>
            <description><![CDATA[There's this architecture called islands of interactivity – where you add little interactive bits here and there on an otherwise static website. And that sure reminds me of Flash – you'd have a static website, but it'd have an an interactive SWF file embedded, which would include fun stuff.]]></description>
            <content:encoded><![CDATA[<p>There's this architecture called <em>islands of interactivity</em> – where you add little interactive bits here and there on an otherwise static website. And that sure reminds me of Flash – you'd have a static website, but it'd have an an interactive SWF file embedded, which would include fun stuff.</p>
<p>I like that approach. The website still works just fine if you don't have JavaScript, but at the same time you can bring in fun bits of interactivity. Make little apps and stuff. So I tried building this for my website too, and the little <a href="/react">React</a> hello world is a demonstration of that.</p>
<p>These days, <a href="https://react.dev" class="external">react.dev</a> tries really hard to force you to use stuff like Next.js and whatnot. But if you try, you can figure out how to build some little toy web apps that don't require you to get weird with web servers. Just write some code and just make yourself a little app that'll sit in its own rectangle. It's not a big deal. It's not the next big startup. It's just there for you and whoever else you share it with.</p>
<p>I really do miss Flash.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Fxtec Pro1 X: The silliest battery fix]]></title>
            <link>/fxtec-pro1-x-the-silliest-battery-fix</link>
            <guid>dbdcfc6bd19a9c9aa2a0954185af418fdf4d0b0f912955e9fb01d13972c388b5</guid>
            <pubDate>Tue, 04 Mar 2025 14:02:00 GMT</pubDate>
            <description><![CDATA[My Fxtec Pro1 X died a very silly death. It lost so much battery it wouldn't charge anymore. The phone sat dead in my drawer until I came across a very silly fix.]]></description>
            <content:encoded><![CDATA[<p>
									<figure>
										<a href="/assets/fxtec-pro1-x-battery-trick.jpg" target="_blank">
											<img src="/assets/thumbnails/fxtec-pro1-x-battery-trick.jpg" alt="A Fxtec Pro1 X, reviving very slowly">
										</a>
										<figcaption>A Fxtec Pro1 X, reviving very slowly</figcaption>
									</figure>
								</p>
<p>My <a href="/fxtec-pro1-x">Fxtec Pro1 X</a> died a very silly death. It lost so much battery it wouldn't charge anymore. The phone sat dead in my drawer until I came across <a href="https://community.fxtec.com/topic/4106-a-user-found-a-way-to-charge-a-dead-pro1x-confirmed-by-others/" class="external">a very silly fix</a>.</p>
<p>When you hold the power button for like 10 seconds, it'll draw <em>juuust</em> a little bit of power. So if you leave it in that state overnight, it will charge like 6%, which is enough for it to revive and start changing normally.</p>
<p><a href="/til">The more you know!</a></p>]]></content:encoded>
            <enclosure url="https://ezhik.jp/assets/thumbnails/fxtec-pro1-x-battery-trick.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[The new website: Day 2]]></title>
            <link>/the-new-website-day-2</link>
            <guid>42a1806a85928c4364af9544e16d7c1626f715d8e1cb18eb12f72ce1302a7589</guid>
            <pubDate>Tue, 04 Mar 2025 14:00:00 GMT</pubDate>
            <description><![CDATA[Yesterday I launched the new website on a whim. All things considered, it's pretty cool. Of course, I caught some bugs that are now fixed (but there are plenty still left to feed a hedgehog, I'm sure.]]></description>
            <content:encoded><![CDATA[<p>Yesterday I launched <a href="/the-new-website">the new website</a> on a whim. All things considered, it's pretty cool. Of course, I caught some bugs that are now fixed (but there are plenty still left to feed a hedgehog, I'm sure.</p>
<p>Here are some little things I fixed though:</p>
<ul>
<li>Reduced margins on smaller screens</li>
<li>Added image previews in <code>&#x3C;meta></code> tags</li>
<li>Made RSS descriptions nicer</li>
<li>Fixed the external link indicator on Safari</li>
<li>Made overflows look prettier</li>
<li>Made the hog resize less</li>
<li>Removed some weird stuff like old font imports</li>
<li>Kinda sorta integrated <a href="/radical-lookup">Radical Lookup</a> into this now (it's an <code>&#x3C;iframe></code> going to the old page, glitchhog aesthetics and all. Flash embed type vibes.)</li>
</ul>
<p>I also made a <a href="/til">TIL</a> page. Swap the backlinks and the articles and bam, it's now a tag. Neat. To make it even neater, I made a weird <code>~[[TIL]]</code> syntax that'll exclude that TIL link from its backlinks.</p>
<p>And since this is a blog about my hedgehog stuff, I'll try to post some stuff that's not just about this blog. Blogging about blogging and taking notes about taking notes. I hate getting stuck in metawork. So I made a small post about a gadget I was messing with: <a href="/fxtec-pro1-x-the-silliest-battery-fix">Fxtec Pro1 X - The silliest battery fix</a>.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The new website]]></title>
            <link>/the-new-website</link>
            <guid>6c08f5dd70a1b546c9c9e49a0931b12546288829683b320bb62c05905af6dc0d</guid>
            <pubDate>Mon, 03 Mar 2025 14:59:00 GMT</pubDate>
            <description><![CDATA[I wanted to make a new website for a while. I thought I'd be cool and do it right and stuff, but then I watched Lu Wilson's Death of the Tadiweb and decided to wing it because life is too short to bikeshed a homebrew blogging engine especially since this one is a horror show that uses SvelteKit for some reason even though the whole thing is just a static website with some islands of interactivity Flash applet-style. Don't ask. Or do. Do ask. Email me. i@ezhik.me. Let's chat. Send me stories. Send me your favorite hedgehog picture or story or video or song or app or novel. Send me small computers.]]></description>
            <content:encoded><![CDATA[<p>I wanted to make a new website for a while. I thought I'd be cool and do it right and stuff, but then I watched Lu Wilson's <a href="https://www.youtube.com/watch?v=73mEhS0gxgI" class="external"><em>Death of the Tadiweb</em></a> and decided to wing it because life is too short to bikeshed a homebrew blogging engine especially since this one is a horror show that uses SvelteKit for some reason even though the whole thing is just a static website with some islands of interactivity Flash applet-style. Don't ask. Or do. Do ask. Email me. <a href="mailto:i@ezhik.me" class="external">i@ezhik.me</a>. Let's chat. Send me stories. Send me your favorite hedgehog picture or story or video or song or app or novel. Send me small computers.</p>
<p>So here we are. I'm still scouring the internet for my various blog posts (I recovered one so far and it's a silly one!). I've also moved most of my existing stuff on the old site over - ping me if anything's missing as I'm sure it's not gone forever and I want to put it all back.</p>
<p>Also... There's now an <a href="/feed.xml">✨RSS feed✨</a>. Please follow it.</p>
<p>I've also got ✨backlinks✨. It's all because of my <a href="https://obsidian.md" class="external">Obsidian</a> addiction. Who needs folders or tags when you have hypertext? Hypertext is all you need. Expect lots of hypertext.</p>
<p>Since it's been a while since I did web stuff and the internet became a very different place during that time, I also now have a <a href="/privacy-policy">Privacy Policy</a>.</p>
<p>Please enjoy.</p>
<p>P.S.<br>
What do you think of that hog? I was kinda winging it with this one and don't know if I like this hog or not.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Privacy Policy]]></title>
            <link>/privacy-policy</link>
            <guid>8f75388c2d6c62bb7f249cedf492e5f4413c8c020fb1b85a85ddc481244aa0f7</guid>
            <pubDate>Mon, 03 Mar 2025 14:58:00 GMT</pubDate>
            <description><![CDATA[I was too lazy to put spyware on this site so you'll have to share your personal information with me manually. You must email i@ezhik.me with your engagement metrics or wild hedgehogs will bite your ankles.]]></description>
            <content:encoded><![CDATA[<p>I was too lazy to put spyware on this site so you'll have to share your personal information with me manually. You must email <a href="mailto:i@ezhik.me" class="external">i@ezhik.me</a> with your engagement metrics or wild hedgehogs will bite your ankles.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The objectively correct guide to making Vim use the system clipboard]]></title>
            <link>/vim-clipboard</link>
            <guid>c467f8943446eab0441b6a56d200189714643d771b847f69c4637824bf560d75</guid>
            <pubDate>Mon, 16 Sep 2019 15:00:00 GMT</pubDate>
            <description><![CDATA[Make sure your Vim has clipboard support (run vim --version and check for +clipboard), stick this in your .vimrc or Neovim's init.vim, enjoy life:]]></description>
            <content:encoded><![CDATA[<p>Make sure your Vim has clipboard support (run <code>vim --version</code> and check for <code>+clipboard</code>), stick this in your <code>.vimrc</code> or Neovim's <code>init.vim</code>, enjoy life:</p>
<pre><code class="hljs language-vim"><span class="keyword">set</span> clipboard=
<span class="keyword">noremap</span> <span class="string">""</span><span class="keyword">y</span> <span class="string">""</span><span class="keyword">y</span>
<span class="keyword">noremap</span> <span class="string">""</span>yy <span class="string">""</span>yy
<span class="keyword">noremap</span> <span class="string">""</span><span class="keyword">p</span> <span class="string">""</span><span class="keyword">p</span>
<span class="keyword">noremap</span> <span class="string">""</span><span class="keyword">P</span> <span class="string">""</span><span class="keyword">P</span>
<span class="keyword">noremap</span> <span class="symbol">&#x3C;expr></span> <span class="keyword">y</span>  (<span class="variable">v:register</span> ==# <span class="string">'"'</span> ? <span class="string">'"+'</span> : <span class="string">''</span>) . <span class="string">'y'</span>
<span class="keyword">noremap</span> <span class="symbol">&#x3C;expr></span> yy (<span class="variable">v:register</span> ==# <span class="string">'"'</span> ? <span class="string">'"+'</span> : <span class="string">''</span>) . <span class="string">'yy'</span>
<span class="keyword">noremap</span> <span class="symbol">&#x3C;expr></span> <span class="keyword">p</span>  (<span class="variable">v:register</span> ==# <span class="string">'"'</span> ? <span class="string">'"+'</span> : <span class="string">''</span>) . <span class="string">'p'</span>
<span class="keyword">noremap</span> <span class="symbol">&#x3C;expr></span> <span class="keyword">P</span>  (<span class="variable">v:register</span> ==# <span class="string">'"'</span> ? <span class="string">'"+'</span> : <span class="string">''</span>) . <span class="string">'P'</span>
</code></pre>
<p>That's it, post over, go home.</p>
<h2>Wait, what?</h2>
<p>Okay, so. Vi was originally invented in 1300 BC, before we had GUIs and even the idea of a shared system clipboard. What it had (and what Vim still has today) instead were registers. Registers are basically a bunch of internal Vim clipboards that you can use through the <code>"</code> key — for example, <code>"fy</code> to copy something into the <code>f</code> register, then <code>"fp</code> to paste from it.</p>
<p>By default, anything you yank (Vim's word for copy) goes into the unnamed register (which is actually assigned to the <code>"</code> key, so you can yank something into it explicitly by doing <code>""y</code>. Then, what Vim's standard way of handling the system clipboard does is simply link up this unnamed register to your global clipboard. Nice and cool, until you overwrite everything you copied with a single character that you just deleted.</p>
<p>See, anything you delete using keys such as <code>x</code>, <code>d</code>, <code>c</code>, and the like, also goes into this unnamed register, overwriting it (<code>"fd</code> would delete something into the <code>f</code> register, by the way). This may have been okay if you were back in Heian Era Japan and all you had was a simple VT100 terminal to write love poems on, as you would have been living inside Vi anyway, using its registers extensively. However, in this day and age, you want to be consistent with the modern OS you're running Vim on (and if you aren't on a modern OS, the Vim on the ancient computer you're configuring is compiled without clipboard support anyway).</p>
<p>Anyone first starting to learn Vim will absolutely not be expecting their clipboard to be wiped every time they delete something, and it will drive them <em>insane</em>. They'll look around the internet for a solution, and they will only find two things.</p>
<p>The first is a bunch of ideology from Unix purists about how you're supposed to just live with that, get used to the Vim way, that poetry declined under the Kamakura shogunate, et cetera. Those people are important and help prevent this world from going completely off the rails drunk on overhyped technology, but listening to them for too long will quickly make the newcomer to Vim start to hate life. To any Vim newcomers reading this, now is also a good time to mention that if you want to have tabs in Vim, search for how to use Vim's buffers. Vim has another feature called tabs, but those are not actually tabs at all. It's complicated and everyone online will be telling you about the Vim way even though you just want to have a little bar on top with two text files that you can quickly switch between.</p>
<p>But anyway. Any newcomer not scared off at this point will keep looking and will find a bunch of solutions that remap the various delete commands to use the "void" (<code>_</code>) register. Those are <em>okay</em>, until you actually do want to use registers the Vim way, plus you'll have to play whack-a-mole with every single command that puts text in the clipboard register.</p>
<p>So what you really want is to just remap the yank (copy) and paste keys to use the system clipboard register. That's exactly what that snippet of code does.</p>
<p>The first line disables any sort of clipboard integration that may have been enabled by default (along with selections automatically getting yanked — change that to <code>set clipboard=autoselect</code> if you're really into that sort of thing and if your Vim supports it).</p>
<p>The last four lines change the main yank and paste keys: if you invoke a yank or paste command with the default unnamed register, it swaps that register out for the system clipboard one instead.</p>
<p>The four lines above those add an exception to the rule: if you explicitly try to use the unnamed register, it will be used instead. Make sure you get the order of the lines right in your Vim configuration file, or who knows what might happen.</p>
<p>So, with the clipboard set up this way, everybody wins! You get to have sane clipboard settings that work the same way as the rest of your OS, while still having the flexibility of proper registers if you need it.</p>
<p>The end!</p>
<p>P.S. If you find out that I made a mistake in my hubris, please email me at <a href="mailto:i@ezhik.jp" class="external">i@ezhik.jp</a>, or maybe make an issue on this site's <a href="https://github.com/SilverEzhik/ezhik.jp/issues" class="external">GitHub</a> if it's urgent enough to require public shaming. Thanks!</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Radical Lookup]]></title>
            <link>/radical-lookup</link>
            <guid>ee27ecbd383367d36623d80a3efde2a38058545e7f2912c2a97b002f6900b075</guid>
            <pubDate>Mon, 17 Sep 2018 15:00:00 GMT</pubDate>
            <description><![CDATA[This is a kanji radical lookup tool with a little twist - it allows you to also decompose a kanji with its radicals. This is useful if you remember a kanji that uses a certain radical, but are not sure how many strokes it actually has (which you'd need to know to use the Jisho radical tool, for example).]]></description>
            <content:encoded><![CDATA[<aside class="right">I haven't gotten around to rebuilding this one for the new site yet so enjoy it in all of its glitchhog aesthetic glory. That's an <code>&#x3C;iframe></code> right there.</aside>
<p>This is a kanji radical lookup tool with a little twist - it allows you to also decompose a kanji with its radicals. This is useful if you remember a kanji that uses a certain radical, but are not sure how many strokes it actually has (which you'd need to know to use the Jisho radical tool, for example).</p>
<p>This uses <a href="http://www.edrdg.org/krad/kradinf.html" class="external">KRADFILE</a> for looking things up.</p>
<p><a href="https://github.com/SilverEzhik/radical-lookup" class="external">Source (GPL3)</a></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Beyond 184]]></title>
            <link>/beyond184</link>
            <guid>e350b3cb6779c7538d47c82cc2d85495f6a6f9f616c208f75d63cfe0611b3f63</guid>
            <pubDate>Wed, 27 Jun 2018 15:00:00 GMT</pubDate>
            <description><![CDATA[Beyond 184 is a cellular automata for simulating traffic in an urban setting.]]></description>
            <content:encoded><![CDATA[<p><strong>Beyond 184</strong> is a cellular automata for simulating traffic in an urban setting.</p>
<p>Every single one of those little cars is not just going in a straight line, but is instead behaving similarly to an actual car – accelerating as they move through an empty road, and slowing down before obstacles.</p>
<p>It is based on the Rule 184 CA (hence the name), with enhancements made to add varying speed levels and to make the whole thing work in two dimensions.</p>
<p><a href="https://github.com/SilverEzhik/beyond184" class="external">Source (GPL2)</a>  <a href="/assets/old/beyond184/conference.pdf">Paper</a></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Things I learned when I decided to put every single sticker I could find in my room on my laptop]]></title>
            <link>/laptop-stickers</link>
            <guid>7a7ee980c06a3aa9627d869098020cbce1f415d8ea8599af12dc6d7056b79832</guid>
            <pubDate>Thu, 13 Aug 2015 18:34:39 GMT</pubDate>
            <description><![CDATA[After a hackathon, you are always left with a lot of swag.]]></description>
            <content:encoded><![CDATA[<p>
									<figure>
										<a href="/assets/laptop-stickers/1.jpg" target="_blank">
											<img src="/assets/thumbnails/laptop-stickers/1.jpg" alt="please help">
										</a>
										<figcaption>please help</figcaption>
									</figure>
								</p>
<p>After a hackathon, you are always left with a lot of swag.</p>
<p>Shirts, bags, and, of course <em>stickers</em>.</p>
<p><em>So many stickers.</em></p>
<p>So of course, the most logical thing to do, is to put them all to use.</p>
<p>All of them.</p>
<p>Here are the lessons I have learned when torturing my poor laptop with all these stickers.</p>
<h2>Google stickers have strong adhesive.</h2>
<p>Like, good luck getting those guys off. There’s an upside down SQL sticker somewhere in that mess and it’s never going to get fixed.</p>
<p>
									<figure>
										<a href="/assets/laptop-stickers/2.jpg" target="_blank">
											<img src="/assets/thumbnails/laptop-stickers/2.jpg" alt="why are you doing this">
										</a>
										<figcaption>why are you doing this</figcaption>
									</figure>
								</p>
<h4>I’m pretty sure some of these aren’t even hackathon stickers.</h4>
<p>Where did I even get these?</p>
<p>
									<figure>
										<a href="/assets/laptop-stickers/3.jpg" target="_blank">
											<img src="/assets/thumbnails/laptop-stickers/3.jpg" alt="these don’t even look good">
										</a>
										<figcaption>these don’t even look good</figcaption>
									</figure>
								</p>
<h4>Big red stickers are boring.</h4>
<p>Come on man, don’t monopolize my laptop lid.</p>
<p>
									<figure>
										<a href="/assets/laptop-stickers/4.jpg" target="_blank">
											<img src="/assets/thumbnails/laptop-stickers/4.jpg" alt="didn’t you plan to put mac os on me">
										</a>
										<figcaption>didn’t you plan to put mac os on me</figcaption>
									</figure>
								</p>
<h4>“Transparent” stickers are the coolest.</h4>
<p>Check them out, they’re really neat.</p>
<p>
									<figure>
										<a href="/assets/laptop-stickers/5.jpg" target="_blank">
											<img src="/assets/thumbnails/laptop-stickers/5.jpg" alt="you can’t even read what the sticker says">
										</a>
										<figcaption>you can’t even read what the sticker says</figcaption>
									</figure>
								</p>
<h4>I have a lot of HackingEDU stickers.</h4>
<p>Too many, in fact.</p>
<p>
									<figure>
										<a href="/assets/laptop-stickers/6.jpg" target="_blank">
											<img src="/assets/thumbnails/laptop-stickers/6.jpg" alt="stop">
										</a>
										<figcaption>stop</figcaption>
									</figure>
								</p>
<p>Seriously.</p>
<p>
									<figure>
										<a href="/assets/laptop-stickers/7.jpg" target="_blank">
											<img src="/assets/thumbnails/laptop-stickers/7.jpg" alt="at least don’t wrinkle them dude">
										</a>
										<figcaption>at least don’t wrinkle them dude</figcaption>
									</figure>
								</p>
<p>Where did I even get so many?</p>
<p>
									<figure>
										<a href="/assets/laptop-stickers/8.jpg" target="_blank">
											<img src="/assets/thumbnails/laptop-stickers/8.jpg" alt="bad">
										</a>
										<figcaption>bad</figcaption>
									</figure>
								</p>
<p>I hope I didn’t take all the stickers or something. Stickers are meant for sharing.</p>
<p>
									<figure>
										<a href="/assets/laptop-stickers/9.jpg" target="_blank">
											<img src="/assets/thumbnails/laptop-stickers/9.jpg" alt="i am a thinkpad i don’t need these stickers">
										</a>
										<figcaption>i am a thinkpad i don’t need these stickers</figcaption>
									</figure>
								</p>
<p>Still, so many stickers.</p>
<p>
									<figure>
										<a href="/assets/laptop-stickers/10.jpg" target="_blank">
											<img src="/assets/thumbnails/laptop-stickers/10.jpg" alt="at least put another “ship it” one">
										</a>
										<figcaption>at least put another “ship it” one</figcaption>
									</figure>
								</p>
<p>Like, damn.</p>
<p>
									<figure>
										<a href="/assets/laptop-stickers/11.jpg" target="_blank">
											<img src="/assets/thumbnails/laptop-stickers/11.jpg" alt=":(">
										</a>
										<figcaption>:(</figcaption>
									</figure>
								</p>
<h4>Conclusion</h4>
<p>I had a big pile of stickers, and now I don’t. Please look forward to the next installment when I try to peel all of these off.</p>
<p>
									<figure>
										<a href="/assets/laptop-stickers/12.jpg" target="_blank">
											<img src="/assets/thumbnails/laptop-stickers/12.jpg" alt="The dark side of Sticker Culture">
										</a>
										<figcaption>The dark side of Sticker Culture</figcaption>
									</figure>
								</p>
<p>Also, shout-outs to HHCdT and <a href="https://web.archive.org/web/20150801122117/http://hackingedu.co/" class="external">HackingEDU</a>.</p>]]></content:encoded>
            <enclosure url="https://ezhik.jp/assets/thumbnails/laptop-stickers/1.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[ZeroPlan]]></title>
            <link>/zeroplan</link>
            <guid>d2f3375f470771a07de3da15f0bfe830bd6c86610bd2708267c3093223508e41</guid>
            <pubDate>Sun, 26 Apr 2015 15:00:00 GMT</pubDate>
            <description><![CDATA[ZeroPlan is a simple app that switches the power plan from “High Performance” to “Power Saver” when you unplug your PC, and vice versa.]]></description>
            <content:encoded><![CDATA[<p><strong>ZeroPlan</strong> is a simple app that switches the power plan from “High Performance” to “Power Saver” when you unplug your PC, and vice versa.</p>
<p>It has no installer, no GUI, no options, and uses only a little more than 1 MB of your RAM. Just stick it in your Autorun folder, and you’re ready to go.</p>
<p>It uses Net Framework 4.5, so in theory it should be able to run on jailbroken Windows RT devices.</p>
<p><a href="http://www.mediafire.com/download/r4749ke5wkh7kpx/ZeroPlan.zip" class="external">Download</a>  <a href="https://github.com/SilverEzhik/ZeroPlan" class="external">Source (GPL2)</a></p>]]></content:encoded>
        </item>
    </channel>
</rss>