https://christiantietze.de/Worklog of Christian Tietze2024-03-18T10:30:34ZChristian Tietzehttps://christiantietze.detag:christiantietze.de,2024-03-18:/posts/2024/03/easing-into-language-environment-week-3-php/Easing Into a Language and Environment: Week 3 with PHP2024-03-18T10:30:34Z2024-03-18T10:30:34Z<p>Based on the calendar, I’m about two months into a server script project for the <a href="https://zettelkasten.de">Zettelkasten website</a>, but measured in actual work weeks, it’s now the start of week three. I notice that I’m now in a comfortable rhythm to develop features, and that’s always an exciting milestone.</p>
<p><strong>Week 1</strong> was about getting a demo done and check out how things work at all. PHP 8 is a much better language today than the PHP 4 I left in the early 2000s. The tools have matured even more, the language has (optional) type annotations, and static code analyzers can help with adhering to the type rules you set up for your project. Paired with LSP, the feedback was good.</p>
<p>I confess that I also used GPT-4 liberally to look at (possibly hallucinated) conventions to set up a project, and how to partition objects and namespaces, and how to work with a database nowadays. This speed up time to get anything done at all and not worry about syntax and library usage at the same time.</p>
<p><strong>Week 2</strong> was about introducing tests and refactoring the code with the help of an improving test harness. This was also when I tweaked the tools more, introducing <code>phpunit-watcher</code> to automatically run the test suite.</p>
<p>With Emacs as my text editor, and a language server (LSP) as the completion engine, I still wanted to reduce some typing of curly braces and configured text expansion snippets. Quite clever ones that autopopulate attribute declarations and setting initial values from a constructor, generating a docstring in the process. I’ll share these in a while after working with them a bit more.</p>
<p>LSP-wise, I’m using <a href="https://github.com/phpactor/phpactor">PHPActor</a> for most of the time. I also have a license for <a href="https://intelephense.com/">intelephense</a>, and intelephense does a much better job at providing feedback on syntax errors like missing semicolons as I type. It’s also very good at formatting code for me. But it sometimes fails and reports false-positives, and PHPActor does a better job at auto-importing and offering suggestions. A combination of both would be ideal – but while PHPActor lacks a couple of features, it works with <code>flycheck-mode</code> to report issues and suggest changes properly, which I can <a href="https://github.com/emacs-sideline/sideline-flycheck/">hook into <code>sideline</code></a>. Seeing incomplete feedback is preferable to not seeing the feedback of intelephense at all. This may change in the future and I’ve switched back and forth a bit.</p>
<p>The journey up to that point spawned a lot of Zettel notes about atomic PHP language details I wanted to remember, and a couple of higher-order notes about more complex processes and object structures. That part was the most exciting, and I’m in the process of writing things up to share language learning with a Zettelkasten in the future.</p>
<p><strong>Week 3</strong> now is about building upon what I learned about structuring the project, and using the flow of TDD to write new services to perform new tasks. That’s a comfortable flow for me, so I can add a couple of features in a tested and somewhat safe manner now and probably finish everything by the end of the week.</p>
<p>One thing I did <em>not</em> get comfortable with in that time is isolating the project with a Docker container. It kinda sorta works, and I can run and reset it to test APIs I am working on. But it’s not in a state where I can confidently deploy the app (via rsync) to a virtual server while using Docker locally. (I don’t want to run this thing containerized on the server, though, that’s a waste of resources.)</p>
<hr><p><small><a href="https://christiantietze.de/hire-me/">Hire me</a> for freelance macOS/iOS work and consulting.</small></p><p><small><a href="https://christiantietze.de/apps/">Buy</a> my apps.</small></p><p><small><a href="https://christiantietze.de/newsletter/">Receive</a> new posts via email.</small></p>Based on the calendar, I’m about two months into a server script project for the <a href="https://zettelkasten.de">Zettelkasten website</a>, but measured in actual work weeks, it’s now the start of week three. I notice that I’m now in a comfortable rhythm to develop features, and that’s always an exciting milestone. tag:christiantietze.de,2024-03-11:/posts/2024/03/confuse-app-dev-with-platform/Oh, You're Making Apps, So You Are Working for Apple/Google/...?2024-03-11T17:05:47Z2024-03-13T07:21:58Z<p><a href="https://mastodon.social/@octavinavarro@mastodon.gamedev.place/112076690604298446">Octavi Navarro</a> reports that (some) people think (some of) his games should be free instead of paid:</p>
<blockquote>
<p>I’ve got hundreds of “this game should be free” angry reviews over the years but I find it shockinge every time. These are not evil corporation executives, but consumers (most probably underpaid workers themselves) who advocate very vocally against remunerated work.</p>
</blockquote>
<p>This is terrible feedback, of course. Love and strength goes out to indies who get this.</p>
<p>Then <a href="https://mastodon.social/@nicklockwood/112076740966975969">Nick Lockwood</a> chimed in:</p>
<blockquote>
<p>I’ve seen several reviews for mobile apps over the years that claimed that since Apple/Google pay the developers to make apps anyway (?) they should be free for end users.</p>
</blockquote>
<p>That got me thinking – my family doesn’t quite understand that I’m creating apps for e.g. Apple platforms without being an Apple employee, either.</p>
<p><a href="https://hachyderm.io/@WLBORg/112077118074145945">Wolfgang Lutz</a>:</p>
<blockquote>
<p>We regularly have folks that think me and my girlfriend are working for Apple and Google because we are developing for iOS and Android and how funny this is.</p>
</blockquote>
<p>Any more anecdotes like that?</p>
<p><strong>Update 2024-03-12:</strong> <a href="https://mastodon.social/@jsq/112084014395796823">Jesse Squires</a>:</p>
<blockquote>
<p>this is a very common misconception / misunderstanding in my experience.</p>
<p>Lots of folks think <em>Apple</em> makes every single app in the store.</p>
</blockquote>
<p>Imagine that!</p>
<blockquote>
<p>It reminds me of older folks who think Facebook <em>is</em> the entire internet, which is also a thing.</p>
</blockquote>
<p>If you believe that, and the App Store is full of all kinds of stuff, this would make Apple appear generous and benevolent, like: “All the nice things they bring to us, look!” – It could also undermine critique in the public opinion. As in: “Glad we have all this Apple stuff on our iPhones. Imagine if we didn’t. How shitty would that be.” From that vantage point, critique could be reframed as complaining about raisins in a free lunch.</p>
<p><strong>Update 2024-03-13:</strong> <a href="https://tech.lgbt/@thebittergreen/112084567526925086">Randy Saldinger</a> points out that Apple isn’t helping in that issue:</p>
<blockquote>
<p>Even as someone who gives software away for free, I still get comments from users who take umbrage when I don’t want to add a feature they ask for, “after I paid all that money to Apple.” It’s not often stated that clearly, but the subtext is often there.</p>
<p>This misconception certainly isn’t helped by Apple’s framing of App Store updates at WWDC. “Apple has paid developers 70 billion dollars!” Not “developers have earned 70 billion dollars selling on the App Store.” Not “developers have earned 100 billion dollars and Apple scraped 30 billion off the top.” But “Apple has PAID developers.”</p>
</blockquote>
<hr><p><small><a href="https://christiantietze.de/hire-me/">Hire me</a> for freelance macOS/iOS work and consulting.</small></p><p><small><a href="https://christiantietze.de/apps/">Buy</a> my apps.</small></p><p><small><a href="https://christiantietze.de/newsletter/">Receive</a> new posts via email.</small></p><a href="https://mastodon.social/@octavinavarro@mastodon.gamedev.place/112076690604298446">Octavi Navarro</a> reports that (some) people think (some of) his games should be free instead of paid: I’ve got hundreds of “this game should be free” angry reviews over the years but I find it shockinge every time. These are not evil corporation executives, but consumers (most probably underpaid workers themselves) who advocate very vocally against remunerated work. tag:christiantietze.de,2024-03-07:/posts/2024/03/swiftui-challenge-port-from-auto-layout-to-swiftui/SwiftUI Challenge: Can You Port this from Auto Layout to SwiftUI?2024-03-07T20:59:54Z2024-03-08T07:14:28Z<p>On Mastodon, we had a discussion about whether you are more or less productive with SwiftUI or UIKit/AppKit. <a href="https://layer8.space/@teilweise/112053743588688616">Der Teilweise (@teilweise@layer8.space) chimed in</a> with an actual, measurable benchmark: a flexible-width window, with reflowing text, and equal-size buttons. Doable in 10 minutes. Can SwiftUI beat this?</p>
<h2 id="the-baseline">The Baseline</h2>
<p>The Auto Layout version I cobbled together to replicate his setup looks like this:</p>
<figure><a href="https://christiantietze.de/posts/2024/03/swiftui-challenge-port-from-auto-layout-to-swiftui/./screenshot.png"><img alt="" src="https://christiantietze.de/posts/2024/03/swiftui-challenge-port-from-auto-layout-to-swiftui/./screenshot.png" /></a><figcaption>The Auto Layout version in Interface Builder/Xcode</figcaption></figure>
<p>Indeed, it took me 10 minutes to replicate this. The only small puzzle was to get the buttons to behave, but it’s all standard stuff.</p>
<p>So – could I do it in less than 10 minutes in SwiftUI as an intermediate SwiftUI dabbler?</p>
<h2 id="participate-in-the-challenge">Participate in the Challenge!</h2>
<p>But first: <em>can you do this?</em></p>
<p>I want to invite you to try!</p>
<p>Here’s the reference implementation on GitHub: <a href="https://github.com/DivineDominion/10min-Window-AutoLayout">https://github.com/DivineDominion/10min-Window-AutoLayout</a></p>
<ul>
<li>Only valid window sizes. You can’t resize vertically and end up with tons of space. 20pt padding from window border to content, aka the default passing.</li>
<li>If the window is wide enough, all buttons have the same width and space between the leftmost one and the Cancel/OK pair to the right.</li>
<li>As the width of the window decreases, the text reflows and the buttons shrink.</li>
<li>As the text reflows, it takes up more vertical space and increases the window height.</li>
<li>As the buttons shrink, the “OK” and “Cancel” buttons reduce in width uniformly until their intrinsic content size (the text content) is reached as the minimum size.</li>
<li><strong>Stretch goal:</strong> “Translate” the text to affect the layout. Change the button labels so the leftmost one is the shortest.</li>
</ul>
<p>Check it out, play with the app, and then try to recreate everything in SwiftUI.</p>
<p>Stop a timer after 10 minutes.</p>
<p>Record your progress, if you like, and share with the community!</p>
<h2 id="live-coding-video-of-my-attempt">Live Coding Video of My Attempt</h2>
<p>Now to my attempt. Watch the video to find out whether I finished and how it looked after 10 minutes.</p>
<figure class="post-figure youtube"><div class="video-container"><iframe src="https://www.youtube-nocookie.com/embed/p8gRlrYWxwc" frameborder="0" allowfullscreen=""></iframe></div><figcaption class="post-figure__caption">Live Coding Video: 10min Auto Layout, 10min SwiftUI</figcaption></figure>
<p>It contains both the Auto Layout code and the SwiftUI recreation plus a bit of discussion.</p>
<h2 id="attemptssolutions-by-folks-in-the-community">Attempts/Solutions by Folks in the Community</h2>
<ul>
<li>Richard Kolasa <a href="https://iosdev.space/@notkoalas/112056933483899940">got pretty far</a> in 6mins, but the default SwiftUI window has too much free movement :)</li>
<li>Mike Apurin <a href="https://hachyderm.io/@auramagi/112057426851975548">points out</a> that window resizing is hell (<a href="https://gist.github.com/auramagi/c505b2c4493090e1dc7a4132c08ae0b1">Code</a>).</li>
<li>Ryan Lintott <a href="https://mastodon.social/@ryanlintott/112058221729989102">shared</a> a solution that reflows the text and increases window height properly (<a href="https://gist.github.com/ryanlintott/06e25c3945224b4dd5e3d821ef5027ec">Code</a>). Uses <a href="https://github.com/ryanlintott/FrameUp">his FrameUp library</a> to help with the layout.</li>
</ul>
<hr><p><small><a href="https://christiantietze.de/hire-me/">Hire me</a> for freelance macOS/iOS work and consulting.</small></p><p><small><a href="https://christiantietze.de/apps/">Buy</a> my apps.</small></p><p><small><a href="https://christiantietze.de/newsletter/">Receive</a> new posts via email.</small></p>On Mastodon, we had a discussion about whether you are more or less productive with SwiftUI or UIKit/AppKit. <a href="https://layer8.space/@teilweise/112053743588688616">Der Teilweise (@teilweise@layer8.space) chimed in</a> with an actual, measurable benchmark: a flexible-width window, with reflowing text, and equal-size buttons. Doable in 10 minutes. Can SwiftUI beat this? tag:christiantietze.de,2024-03-04:/posts/2024/03/fastspring-storefronts/3 Types of FastSpring Storefronts2024-03-04T19:28:12Z2024-03-04T19:28:12Z<p>Modern FastSpring storefronts come in three standard flavors. On top, you can create your own JavaScript client page that uses the <a href="https://developer.fastspring.com/reference/store-builder-library-overview">Store Builder Library</a> under the hood. Here they are:</p>
<h2 id="web-storefront">Web Storefront</h2>
<figure><a href="https://christiantietze.de/posts/2024/03/fastspring-storefronts/./storefront.png"><img alt="" src="https://christiantietze.de/posts/2024/03/fastspring-storefronts/./storefront.png" /></a><figcaption>Web Storefront landing page with multiple products</figcaption></figure>
<p>The most basic storefront is the Web Storefront (<a href="https://developer.fastspring.com/docs/get-started-web-storefronts">docs</a>). It’s a hosted web shop where you can offer multiple products. <a href="https://christiantietze.onfastspring.com/">Here’s mine for apps.</a></p>
<p>Visitors need to put products into a basket and then perform a checkout. It’s rather cumbersome for single-product sales, but can make sense for studios with multiple apps.</p>
<h2 id="popup-storefront">Popup Storefront</h2>
<figure><a href="https://christiantietze.de/posts/2024/03/fastspring-storefronts/./popup.png"><img alt="" src="https://christiantietze.de/posts/2024/03/fastspring-storefronts/./popup.png" /></a><figcaption>Popup Storefront for TableFlip</figcaption></figure>
<p>The better choice to sell an app directly is the Popup Storefront (<a href="https://developer.fastspring.com/docs/customize-your-popup-storefront">docs</a>).</p>
<p>Backend-wise, it’s a shopping basket pre-filled with the 1 product you want to sell, displaying the checkout form in a popover. I think that’s a fantastic choice for an app landing page.</p>
<p>I use this on each of my apps’ landing pages to make the way from visit to sale as frictionless as possible. Examples are the “Buy” buttons on <a href="https://tableflipapp.com">https://tableflipapp.com</a> and <a href="https://wordcounterapp.com">https://wordcounterapp.com</a>, but these buttons can also be found on <a href="https://christiantietze.de/software/">my apps overview page</a>.</p>
<h2 id="embedded-storefront">Embedded Storefront</h2>
<figure><a href="https://christiantietze.de/posts/2024/03/fastspring-storefronts/./embedded.png"><img alt="" src="https://christiantietze.de/posts/2024/03/fastspring-storefronts/./embedded.png" /></a><figcaption>Embedded Storefronts look like the Popup ones but out of context</figcaption></figure>
<p>While the Popup Storefront is very minimal and direct, to make it even <em>more</em> direct, you can embed the popover’s contents in your website without the need to display a popover on click. Just show the store directly, in place. That’s the Embedded Storefront (<a href="https://developer.fastspring.com/docs/building-and-adding-an-embedded-storefront-to-your-website">docs</a>).</p>
<p>There’s no harm to show this trimmed-down version of the Store, so here’s the one for TableFlip: <a href="https://tableflipapp.com/embedded-store/">https://tableflipapp.com/embedded-store/</a></p>
<p>There’s not much to see, really. You control the rest of the website. The layout, footer, everything.</p>
<p>You could put this on your landing page and skip the “Buy” button completely.</p>
<!-- That's actually what is embedded when you purchase within the app.-->
<h2 id="store-builder-library">Store Builder Library</h2>
<p>All self-hosted storefronts (the Popup and Embedded ones) use the Store Builder Library (SBL) under the hood to perform the checkout.</p>
<p>Online sales are hard to get right and secure, so this is how FastSpring does it under the hood: by funneling all interactions through their SBL.</p>
<p>The Embedded Storefront is just a single call to the SBL that translates to “show the checkout form here, and put product <em>X</em> into the basket for immediate purchase.” The Popup Storefront does almost the same but shows a modal dialog instead.</p>
<p>With the raw SBL, you can go nuts and create a whole shopping catalog. I’ve linked to FastSpring Examples <a href="https://christiantietze.de/posts/2019/09/fastspring-store-examples/">before in 2019</a>, but there’s been additions to the roster, and the relevant ones for my overview today are: <a href="https://fastspringexamples.com/sbl-example/simple-popup/">Simple Popup</a> for the Popup Webstore I use for apps, and <a href="https://fastspringexamples.com/full-cart/full-cart/">Full Cart</a> to show a shopping page.</p>
<h2 id="no-native-api">No native API?</h2>
<p>As an iOS or macOS app developer, you don’t get an API to make sales from within your app <em>programmatically,</em> though. Instead, you need to go through FastSpring’s web storefronts one way or another.</p>
<p>The only API that you get is intended for <em>you</em>, and you can use it from your own server as a trusted source. While it would be technicalluy possible to use some API commands from your app, that’s like using your admin account. Very risky, not worth trying.</p>
<p>This means you can’t create native UI for a checkout form in your apps, but you can show a web view that includes the store.</p>
<hr><p><small><a href="https://christiantietze.de/hire-me/">Hire me</a> for freelance macOS/iOS work and consulting.</small></p><p><small><a href="https://christiantietze.de/apps/">Buy</a> my apps.</small></p><p><small><a href="https://christiantietze.de/newsletter/">Receive</a> new posts via email.</small></p>Modern FastSpring storefronts come in three standard flavors. On top, you can create your own JavaScript client page that uses the <a href="https://developer.fastspring.com/reference/store-builder-library-overview">Store Builder Library</a> under the hood. Here they are: The most basic storefront is the Web Storefront (<a href="https://developer.fastspring.com/docs/get-started-web-storefronts">docs</a>). It’s a hosted web shop where you can offer multiple products. <a href="https://christiantietze.onfastspring.com/">Here’s mine for apps.</a> tag:christiantietze.de,2024-03-04:/posts/2024/03/disallow-gptbot-in-robots-txt/How to Disallow GPTBot Crawling in robots.txt2024-03-04T15:52:47Z2024-03-04T15:52:47Z<p>To tell OpenAI’s web crawler to skip your site, add these lines to your <a href="https://www.robotstxt.org/"><code>robots.txt</code></a> (<a href="https://platform.openai.com/docs/gptbot">see docs</a>):</p>
<pre><code>User-agent: GPTBot
Disallow: /
</code></pre>
<p>(<a href="https://hidde.blog/llm-theft-opt-out/">via Hidde de Vries</a>)</p>
<p>The outlook is bleak: There’s no way to win this if you don’t want to be scraped at all, ever, except by not putting things online. If they don’t scrape your content, they scrape the copycat sites as <a href="https://mastodon.social/@rikschennink/110890016507876007">Rik Schennink pointed out</a>. Or ignore the <code>robots.txt</code> rule. (How could you tell, anyway?)</p>
<p>My stuff is CC-BY-SA licensed. I don’t <em>want</em> to feel overly protective about my content, and I generally feel best when I stay out of a scarcity mindset. If any of my wisdom makes its way into the LLM content regurgitation machine, so be it. I don’t hold my breath for OpenAI to figure out the “BY” part of my license any time soon, but I do hope they are going to offer source attribution eventually for all of us.</p>
<hr><p><small><a href="https://christiantietze.de/hire-me/">Hire me</a> for freelance macOS/iOS work and consulting.</small></p><p><small><a href="https://christiantietze.de/apps/">Buy</a> my apps.</small></p><p><small><a href="https://christiantietze.de/newsletter/">Receive</a> new posts via email.</small></p>To tell OpenAI’s web crawler to skip your site, add these lines to your <a href="https://www.robotstxt.org/"><code>robots.txt</code></a> (<a href="https://platform.openai.com/docs/gptbot">see docs</a>): (<a href="https://hidde.blog/llm-theft-opt-out/">via Hidde de Vries</a>) The outlook is bleak: There’s no way to win this if you don’t want to be scraped at all, ever, except by not putting things online. If they don’t scrape your content, they scrape the copycat sites as <a href="https://mastodon.social/@rikschennink/110890016507876007">Rik Schennink pointed out</a>. Or ignore the <code>robots.txt</code> rule. (How could you tell, anyway?) tag:christiantietze.de,2024-03-04:/posts/2024/03/developer-voices-on-property-based-testing/Developer Voices on Property-Based Testing Made Me Realize PBT’s Value2024-03-04T10:39:15Z2024-03-04T10:39:15Z<p>I listened to <a href="https://www.youtube.com/watch?v=wHJZ0icwSkc">“Automate Your Way to Better Code: Advanced Property Testing (with Oskar Wickström)”</a> and I believe that Property-Based Testing (PBT) clicked for me a bit now. Maybe. It never made sense to me before, but <a href="https://wickstrom.tech/">Oskar Wickström</a> has a lot of actually interesting examples.</p>
<p>My highlights are instances where you have to think outside the box, like a good puzzle:</p>
<ul>
<li>You cannot describe complex actions on random data directly (like cutting gaps from audio tracks); but you can offer <em>inverse actions</em>, like undo operations, and then essentially test that applying an action, undoing it, redoing it, then undoing it again doesn’t change the result.</li>
<li>Testing a search function, you can’t actually test the search (without implementing the search functionality inside the tests again) because you don’t know the test data. But you can test that <em>filtering</em> the search results works, because filtering should product subsets of the unfiltered search.</li>
<li>You can describe web apps as state machines with valid transitions and then go ballistics with trying all kinds of interactions. That’s the promise of <a href="https://quickstrom.io/">Quickstrom</a>: You don’t need to specify each state of the web page as an example, but you specify the properties of each valid state and then ensure that no combination of interactions and button presses produces an invalid state. (Relying on app introspection, this probably works much better on the web with headless browsers than in Xcode/on mobile.)</li>
</ul>
<p>None of this sounds like a replacement for what unit tests would do. But they do sound like a much better high-level approach to regression testing!</p>
<p>If nothing else, I’m intrigued to learn more about this now.</p>
<p>Can absolutely recommend this episode.</p>
<p>Actually, <a href="https://pod.link/developer-voices">I recommend the whole podacst.</a></p>
<hr><p><small><a href="https://christiantietze.de/hire-me/">Hire me</a> for freelance macOS/iOS work and consulting.</small></p><p><small><a href="https://christiantietze.de/apps/">Buy</a> my apps.</small></p><p><small><a href="https://christiantietze.de/newsletter/">Receive</a> new posts via email.</small></p>I listened to <a href="https://www.youtube.com/watch?v=wHJZ0icwSkc">“Automate Your Way to Better Code: Advanced Property Testing (with Oskar Wickström)”</a> and I believe that Property-Based Testing (PBT) clicked for me a bit now. Maybe. It never made sense to me before, but <a href="https://wickstrom.tech/">Oskar Wickström</a> has a lot of actually interesting examples. tag:christiantietze.de,2024-03-02:/posts/2024/03/tableflip-v1-6-0-json-support-font-resizing/TableFlip v1.6.0: JSON Support and Font Resizing2024-03-02T09:45:28Z2024-03-02T09:45:28Z<p>TableFlip v1.6.0 got approved to the Mac App Store (direct customers got the update a bit earlier, as usual).</p>
<p class="actions">
<a class="action action--buy" href="https://tableflipapp.com">Check out TableFlip</a>
</p>
<p>The least exciting feature first, so that it doesn’t go unnoticed: you can now scale the font in TableFlip (aka “zoom in and out”).</p>
<p>Now the true highlight of this version: <strong>JSON file support.</strong></p>
<figure><a href="https://christiantietze.de/posts/2024/03/tableflip-v1-6-0-json-support-font-resizing/2024-03-02_tableflip-json.png"><img alt="" src="https://christiantietze.de/posts/2024/03/tableflip-v1-6-0-json-support-font-resizing/2024-03-02_tableflip-json.png" /></a><figcaption>Short JSON table featuring interactive checkboxes and right-aligned number fields</figcaption></figure>
<p>Please do applaud <a href="https://underplot.com/">Marin Todorov</a> again for helping with JSON import and export! ❤️ Someone reached out to Marin via email, and I’d love to have more of that!</p>
<p>TableFlip can deal with JSON documents, so you can open and convert like you would with Markdown and CSV/TSV.</p>
<p>Possible workflows include:</p>
<ul>
<li>Export to JSON documents (from Markdown or CSV/TSV documents),</li>
<li>Copy tables to your clipboard as JSON,</li>
<li>Import tabular data from JSON, and convert to any other exportable format (Markdown, CSV/TSV, LaTeX, HTML).</li>
<li>Detect typos in object keys in the columnar view,</li>
<li>and reorganize object arrays spatially.</li>
<li>Manage your to-do list with interactive checkboxes. (Yes, really, see below!)</li>
</ul>
<p>Working with JSON documents at the moment is not in its final form and to gel with the rest of the app, it’s currently limited to support <strong>files with arrays at the root</strong>, like this:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="w">
</span><span class="p">{</span><span class="w"> </span><span class="nl">"process"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Updating TableFlip"</span><span class="p">,</span><span class="w">
</span><span class="nl">"duration"</span><span class="p">:</span><span class="w"> </span><span class="mf">1.234</span><span class="p">,</span><span class="w">
</span><span class="nl">"requirements"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Internet connection"</span><span class="w"> </span><span class="p">},</span><span class="w">
</span><span class="p">{</span><span class="w"> </span><span class="nl">"process"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Editing"</span><span class="p">,</span><span class="w">
</span><span class="nl">"duration"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"requirements"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
</span><span class="nl">"easy"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w"> </span><span class="p">},</span><span class="w">
</span><span class="p">]</span><span class="w">
</span></code></pre></div></div>
<p>It does what you will expect: convert object keys into table columns, and each object in an array becomes its own row.</p>
<p><strong>Data types</strong> are inferred (mixed data is handled as gracefully as possible) and TableFlip displays boolean-type cells as interactive checkboxes.</p>
<p>Dealing with arbitrary (and arbitrarily <em>nested</em>) JSON object requires more work and redoing parts of TableFlip’s core UI as well, so that’ll come in a later update.</p>
<p class="actions">
<a class="action action--buy" href="https://tableflipapp.com"><b>Download TableFlip!</b></a>
</p>
<h2 id="release-notes">Release Notes</h2>
<ul>
<li><strong>New:</strong> JSON file import. Open JSON files with arrays of non-nested objects at the root.
<ul>
<li>JSON keys become column headers and the JSON is represented in tabular format for editing.</li>
<li>Value types are preserved, like: integers, floats, booleans and null values.</li>
<li>Input is validated (illegal input discarded) and converted to the given type during editing.</li>
<li>Boolean data becomes toggle-able checkboxes. (No matter if you double click, <kbd>⌥</kbd>+<kbd>Enter</kbd>, <kbd>F2</kbd>.)</li>
</ul>
</li>
<li><strong>New:</strong> Copy as JSON feature added allowing the user to copy the current table in JSON format to the pasteboard.</li>
<li><strong>New:</strong> JSON file export. Produce well formatted JSON from tabular data and export to disk. Support for either minified or pretty printed JSON.</li>
<li><strong>New:</strong> Zoomable contents aka custom font sizes. Increase font size with <kbd>⌘+</kbd> and decrease with <kbd>⌘-</kbd>.</li>
<li><strong>Fixed:</strong> Some toolbar items did not do their job when they were clicked in the overflow menu (that automatically appears when the window becomes too narrow). Thanks to <a href="https://cykele.ro/">Nathan Manceaux-Panot</a> for discussing this problem and <a href="https://christiantietze.de/posts/2024/02/fix-nssegmentedcontrol-toolbar-action/">sharing a fix</a>!</li>
</ul>
<hr><p><small><a href="https://christiantietze.de/hire-me/">Hire me</a> for freelance macOS/iOS work and consulting.</small></p><p><small><a href="https://christiantietze.de/apps/">Buy</a> my apps.</small></p><p><small><a href="https://christiantietze.de/newsletter/">Receive</a> new posts via email.</small></p>TableFlip v1.6.0 got approved to the Mac App Store (direct customers got the update a bit earlier, as usual).
<a class="action action--buy" href="https://tableflipapp.com">Check out TableFlip</a>
The least exciting feature first, so that it doesn’t go unnoticed: you can now scale the font in TableFlip (aka “zoom in and out”). tag:christiantietze.de,2024-02-26:/posts/2024/02/portable-every-day-carry-paper-kanban/Portable Every-Day-Carry Paper Kanban2024-02-26T15:44:52Z2024-02-26T15:44:52Z<p>I found this among my notes from 2013, and think it’s a fun little tool for analog productivity – the portable Kanban board!</p>
<p>It’s a foldable personal Kanban board, suitable as an Every Day Carry in either A3 or A4 size (or US Letter or whatever).</p>
<ol>
<li>Fold the sheet of paper along the long axis,</li>
<li>fold it again at the short axis,</li>
<li>done!</li>
</ol>
<p>This produces four quadrants and the folded size is ideal to stuff it into a backpack, book, or maybe even your pants. Thus, it’s convenient to transport to university, school, or work.</p>
<p>Use very small Post-Its for your tasks. These are your cards that move around between the Kanban ‘columns’ (which are actually not columns, but quadrants here).</p>
<p>Label the quadrants like this:</p>
<pre><code class="language-asciiart">┌────────┬────────┐
│Backlog │ Done │
│ │ │
├────────┼────────┤
│Ready │ W.I.P. │
│ │ │
└────────┴────────┘
</code></pre>
<p>Cards move from <em>Backlog</em> to <em>Ready</em> to <em>Work in Progress</em> (W.I.P.) to <em>Done</em>.</p>
<p>My board’s design is shaped like a “U”. Why not a “Z”?</p>
<p>This layout makes the most important day-to-day action flow from “Ready” to “Work in Progress” visibly easy to track. They are right next to each other.</p>
<p>It offers a clear view of what needs doing and what is currently underway.</p>
<p>Users can focus on the lower half of the sheet, potentially even folding away the top half, during a day at work or studying at the library.</p>
<p>The “Backlog,” serving as a midterm storage of things that will come up, and the “Done” quadrant, which displays completed tasks, fade into the background in daily use. You don’t need to see “Done” all day every day, but it’s important during review; and you don’t need to see the “Backlog” all day every day because you take tasks from “Ready”.</p>
<p>So the two important quadrants are in focus, that’s cool I think.</p>
<h2 id="application-to-university-students">Application to University Students</h2>
<p>We’ve used this Kanban back in 2013 during work and with a couple of mentees (‘clients’) at University as a tool to get organized. The playful and physical nature helped a few of them. Others prefered digital solutions or something else entirely, of course.</p>
<p>If you’re a University student and need to write 2 papers and prepare for 3 tests – first, remember there’s only so much you can do in a day. Your throughput is limited, and your “Work in Progress” quadrant should reflect this.</p>
<p>Mark each of your 5 projects total somehow to focus on something. Examples to differentiate between projects:</p>
<ul>
<li>Use different colors of Post-It,</li>
<li>put short marks into the card’s corners to show which is which.</li>
<li>Or draw a squiggly, serrated, dotted, dashed, … border around cards.</li>
<li>Or use different colored pencils.</li>
</ul>
<p>Either way, you need a way to know which card is about what so you can decide what to do <em>now</em>, what ought to move from “Ready” to “W.I.P.” next as you complete a task.</p>
<p>You could also carry 5 different foldable boards with you!</p>
<p>A problem I see with separating things into 5 boards is that your personal throughput doesn’t scale like that. You don’t really have a “W.I.P.” in 5 places, you just have 1 thing you can really do at a time, and maybe about 3 tasks that you can consider doing next.</p>
<p>Why more than 1? – Because when you go to the library to research for a paper, you can continue research for the next paper while you’re there, or borrow a book for an upcoming test. You can time-box “library research” in your calendar and fill the available time with relevant tasks from different projects that apply to this particular context.</p>
<hr />
<p>I’m personally a supporter for analog productivity systems. I love their haptic nature. Moving real cards in real space!</p>
<p>But to be frank, I always, <em>always</em> return to digital, and did so 10 years ago when we tried this foldable board, and also 15 years ago when I got my first Mac and OmniFocus. I like the infinite virtual space of digital, too.</p>
<p>Be as it may, a personal overview system to not get lost in the complexity of one’s life can take many forms, and this is a particularly playful form in my opinion.</p>
<hr><p><small><a href="https://christiantietze.de/hire-me/">Hire me</a> for freelance macOS/iOS work and consulting.</small></p><p><small><a href="https://christiantietze.de/apps/">Buy</a> my apps.</small></p><p><small><a href="https://christiantietze.de/newsletter/">Receive</a> new posts via email.</small></p>I found this among my notes from 2013, and think it’s a fun little tool for analog productivity – the portable Kanban board! It’s a foldable personal Kanban board, suitable as an Every Day Carry in either A3 or A4 size (or US Letter or whatever). This produces four quadrants and the folded size is ideal to stuff it into a backpack, book, or maybe even your pants. Thus, it’s convenient to transport to university, school, or work. tag:christiantietze.de,2024-02-19:/posts/2024/02/splitting-large-tasks-not-mathematical-process/Splitting Large Tasks is not a Mathematical Process2024-02-19T11:41:48Z2024-02-19T11:41:48Z<p>Here’s an anecdote for you: Imagine a dev team that performs task estimates expressed in “story points”, Agile style, and encounters a large estimate. Large, in this team, means “13 or more.”</p>
<p>Then in one of these sessions, a specific task initially received an estimation of 13 story points. This marks the team’s threshold for considering the division of tasks into more manageable pieces by convention.</p>
<p>After performing the division, the first segment of this task was then estimated to be worth 8 story points. This led to a deliberation on the value of the remaining portion.</p>
<p>Is it 5? (13 minus 8)</p>
<figure><a href="https://christiantietze.de/posts/2024/02/splitting-large-tasks-not-mathematical-process/./cake-slices.webp"><img alt="" src="https://christiantietze.de/posts/2024/02/splitting-large-tasks-not-mathematical-process/./cake-slices.webp" /></a><figcaption>Found this decorative separator to put in this blog post on Midjourney, based the prompt “watercolour kawaii slice cakes with plenty of negative space”</figcaption></figure>
<p>When splitting large tasks–those that are estimated to be worth a high number of “story points”–it’s an opportunity to abandon the initial estimate and start fresh for a more accurate outcome.</p>
<p>This approach is crucial, particularly for complex tasks with inherent unknowns. They should not be treated as simplistically as slicing a cake, because that doesn’t do the creative process of our work justice.</p>
<p>That’s why, contrary to a straightforward subtraction, the remaining task should not simply be labeled being worth 5 story points (13 minus 8).<br />
I want to argue for the principle that splitting tasks is more than a numerical exercise; it serves as a safeguard against inaccurate large-scale estimates.</p>
<ol>
<li>
<p><strong>Splitting a task fundamentally changes our understanding of what needs to be accomplished</strong>. There’s a difference between thinking about the forest (and its macro scale phenomena) and about individual trees (and their individual properties). Reducing the scope or resolution of a problem helps in pinpointing specifics that a broader perspective might miss.</p>
</li>
<li>
<p>Daniel Kahneman popularized this notion that “what you see is all there is”, which means <strong>our evaluations are influenced by our current focus.</strong> When examining a task as a whole, we might activate different ideas than when breaking it down. To properly combat the potential overconfidence in our initial estimates, breaking down a task that is identified as “too large to work on” needs to go hand in hand with discounting the original estimate. Acknowledge that it’s likely that it was not just large, but probably also inaccurate.</p>
</li>
<li>
<p>On the flip side, <strong>reducing task size can significantly enhance problem detection.</strong> The decision to split a task becomes an act of breaking the “known knowns” into smaller, more understandable pieces. This not only helps in managing the work better but also in identifying any previously untackled unknowns. For instance, issues specific to certain aspects of implementation, like SQL injections in the context of database interactions, might only become apparent when tasks are dissected into finer details. Changing the granularity facilitates a more thorough examination, making it easier to identify and address potential problems early in the development process. Again, “what you see is all there is”, but as a human, you can’t see a thousand things at full resolution, so changing what you look at changes what you can think of.</p>
</li>
<li>
<p>Viewing estimation as a simple mathematical equation like slicing a cake fails to <strong>account for the inherent uncertainties of creative and complex tasks</strong>. Writing two essays of approximately half the size of a large one doesn’t necessarily divide the effort. The creative process is non-linear, and smaller tasks might require more time due to the need for cohesion and clarity in all of them.</p>
</li>
<li>
<p>Related, there’s probably some very <strong>practical and technical overhead</strong>. On the technical side, in programming, splitting one function into two to reduce its size increases the required lines of code by the overhead of declaring a new function and calling it. On a less obvious, creative side, the task of splitting a function is also about understanding that dividing work introduces new problems and changes how the existing code will be reas. Then there’s overhead such as designing proper interfaces and refactoring code, or writing documentation and adding tests for new API.</p>
</li>
</ol>
<p>A reevaluation of the smaller tasks acknowledges our shortcomings in estimation, especially as the scale of the task increases. The team’s original rule of thumb to not continue with tasks worth 13 story points or more is an attempt to acknowledge exactly this. The problem is not that the number is high (e.g. too large to be entered into a form field), but that high numbers indicate that the represented task could hide many traps.</p>
<p>Therefore, when considering the second portion of the initially large task, automatically assigning it 5 story points misses the point of splitting a large task in the first place. Instead, each segment of the original task deserves a new evaluation (ideally, free from the bias of the original).</p>
<p>To split a large task into smaller ones should uncover parts of our incomplete understanding of the original one. Otherwise, the process of splitting becomes mere ceremony, and performed in a team meeting wastes collective time more than it helps understanding. It’s still maybe not at all pointless – after all, you end up with smaller tasks that you can focus on –, but it’s more of a formality, more ritual than understanding.</p>
<hr><p><small><a href="https://christiantietze.de/hire-me/">Hire me</a> for freelance macOS/iOS work and consulting.</small></p><p><small><a href="https://christiantietze.de/apps/">Buy</a> my apps.</small></p><p><small><a href="https://christiantietze.de/newsletter/">Receive</a> new posts via email.</small></p>Here’s an anecdote for you: Imagine a dev team that performs task estimates expressed in “story points”, Agile style, and encounters a large estimate. Large, in this team, means “13 or more.” Then in one of these sessions, a specific task initially received an estimation of 13 story points. This marks the team’s threshold for considering the division of tasks into more manageable pieces by convention. tag:christiantietze.de,2024-02-15:/posts/2024/02/swiftui-field-guide/SwiftUI Field Guide2024-02-15T13:02:08Z2024-02-15T13:02:08Z<p>Chris Eidhof and team launched the <a href="https://www.swiftuifieldguide.com/">SwiftUI Field Guide</a> website today.</p>
<p>I noticed that Chris <a href="https://m.objc.io/@chris/111918552328942811">fiddled with JavaScript animations and layout representations</a> to mimick SwiftUI as close as possible and wondered what kind of e-book could be upcoming, but it turns out it’s a website!</p>
<figure><a href="https://christiantietze.de/posts/2024/02/swiftui-field-guide/./fieldguide.gif"><img alt="" src="https://christiantietze.de/posts/2024/02/swiftui-field-guide/./fieldguide.gif" /></a><figcaption>Change the alignment or the alignment guide offset interactively and check out the result. There is so much detail!</figcaption></figure>
<p>As a resource to learn, the approximations are more than good enough. They are excellent and by virtue of being interactive, they are also much better to get a feeling for everything than the SwiftUI documentation’s images can ever be. There’s only so much an API documentation can teach you before you need to observe how it really behaves.</p>
<p>Since it’s in a browser, the preview is of course even faster than Xcode Previews would be, and without the crashes. (Oh, the crashes …)</p>
<p>I wish the SwiftUI Field Guide had been available a year ago when I had to figure out so many things through trial and error!</p>
<p>Some sections apparently aren’t finished yet (they’re greyed-out), but you can learn a lot about the reverse-engineered layout system’s inner workings.</p>
<hr><p><small><a href="https://christiantietze.de/hire-me/">Hire me</a> for freelance macOS/iOS work and consulting.</small></p><p><small><a href="https://christiantietze.de/apps/">Buy</a> my apps.</small></p><p><small><a href="https://christiantietze.de/newsletter/">Receive</a> new posts via email.</small></p>Chris Eidhof and team launched the <a href="https://www.swiftuifieldguide.com/">SwiftUI Field Guide</a> website today. I noticed that Chris <a href="https://m.objc.io/@chris/111918552328942811">fiddled with JavaScript animations and layout representations</a> to mimick SwiftUI as close as possible and wondered what kind of e-book could be upcoming, but it turns out it’s a website! tag:christiantietze.de,2024-02-08:/posts/2024/02/looking-for-testers--japanese-chinese-korean/Looking for Testers: Typers of Japanese, Chinese, and Korean Characters2024-02-08T07:50:06Z2024-02-08T07:50:06Z<p>I’m looking for testers for my macOS apps <a href="https://tableflipapp.com">TableFlip</a> and <a href="https://wordcounterapp.com">WordCounter</a> to get an opinion on</p>
<ul>
<li>how accurately characters and “words” are counted,</li>
<li>how text alignment in documents look.</li>
</ul>
<p>Reach out if that’s you!</p>
<p><a href="mailto:hi@christiantietze.de">hi@christiantietze.de</a></p>
<hr><p><small><a href="https://christiantietze.de/hire-me/">Hire me</a> for freelance macOS/iOS work and consulting.</small></p><p><small><a href="https://christiantietze.de/apps/">Buy</a> my apps.</small></p><p><small><a href="https://christiantietze.de/newsletter/">Receive</a> new posts via email.</small></p>I’m looking for testers for my macOS apps <a href="https://tableflipapp.com">TableFlip</a> and <a href="https://wordcounterapp.com">WordCounter</a> to get an opinion on Reach out if that’s you! <a href="mailto:hi@christiantietze.de">hi@christiantietze.de</a> tag:christiantietze.de,2024-02-04:/posts/2024/02/example-object-tag-vs-topic-tag-programming-zettelkasten/Example of Object Tag vs Topic Tag in Programming Zettelkasten2024-02-04T10:45:38Z2024-02-04T10:45:38Z<p>Went through some old notes this week (I’m doing this AppKit/UIKit stuff for surprisingly many years!) and found a problem with the tags I used in one of my notes.</p>
<p>Let’s dive right in with an example:</p>
<div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gh"># 201801251531 Copy NSView contents into NSImage</span>
<span class="gh">#appkit #image #screenshot</span>
...
</code></pre></div></div>
<p>It’s a how-to note with a code snippet. Its tags are: <code>#appkit</code>, <code>#image</code>, <code>#screenshot</code>.</p>
<p>Adhering to <a href="https://zettelkasten.de/media/2020-10-31-boy-scout-rule/">the boy scout rule</a>, I changed it to leave it in a better state:</p>
<div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gh"># 201801251531 Copy NSView contents into NSImage</span>
<span class="gh">#nsimage #screenshot</span>
...
</code></pre></div></div>
<ul>
<li>I specified <code>#image</code> to become <code>#nsimage</code>. I’m never casually searching for “oh what could I do with images today, I wonder”, but I am looking for <code>NSImage</code>-related tips. The specificity helps.</li>
<li>I removed <code>#appkit</code>. Yes, this is a problem related to the <em>topic</em> of AppKit (aka Mac app programming), but the note is not about AppKit itself.</li>
</ul>
<p>This should be an instructive example of <a href="https://zettelkasten.de/posts/object-tags-vs-topic-tags/">The Difference Between Good and Bad Tags</a>:</p>
<ul>
<li>The <code>NSImage</code> <em>object</em> tag specifies what the note is actually about. It’s precise, and all notes tagged <code>#nsimage</code> are actually about this type.</li>
<li>The AppKit <em>topic</em> would pollute if used consequently in the way I originally did (I have thousands of notes about Mac development).</li>
<li>Similarly, the <code>#image</code> <em>topic</em> was too broad to be useful: it’s not about images in general, but about <code>NSImage</code> in particular!</li>
</ul>
<hr><p><small><a href="https://christiantietze.de/hire-me/">Hire me</a> for freelance macOS/iOS work and consulting.</small></p><p><small><a href="https://christiantietze.de/apps/">Buy</a> my apps.</small></p><p><small><a href="https://christiantietze.de/newsletter/">Receive</a> new posts via email.</small></p>Went through some old notes this week (I’m doing this AppKit/UIKit stuff for surprisingly many years!) and found a problem with the tags I used in one of my notes. Let’s dive right in with an example: It’s a how-to note with a code snippet. Its tags are: <code>#appkit</code>, <code>#image</code>, <code>#screenshot</code>. tag:christiantietze.de,2024-02-02:/posts/2024/02/fix-nssegmentedcontrol-toolbar-action/When Actions of NSSegmentedControl in Toolbars Do Not Fire In Their Overflow Menu Item State, Do This2024-02-02T11:42:57Z2024-02-02T11:42:57Z<p>In this fourth and probably still not final part of my series on <code>NSToolbarItem</code>s with segmented controls, I just want to share a problem and a quick fix that <a href="https://cykele.ro/">Nathan Manceaux-Panot</a> brought up today.</p>
<p>The series spans 8 years and is this:</p>
<ol>
<li><a href="https://christiantietze.de/posts/2016/06/segmented-nstoolbaritem/">Original Approach</a></li>
<li><a href="https://christiantietze.de/posts/2016/06/disabling-toolbar-item-segments/">Enabling/Disabling Segments</a></li>
<li><a href="https://christiantietze.de/posts/2018/11/reliable-nssegmentedcontrol-in-toolbar/">Fixing the action dispatching bug</a></li>
<li>Fixing action dispatching within overflow menus (this post)</li>
</ol>
<p>Nathan recently went through the series to implement segmented controls in toolbars but discovered that the overflow menu items would not enable (a validation problem) and when they enable, they don’t fire the action. When he brought this up today, I investigated.</p>
<p>I’m using this approach in <a href="https://tableflipapp.com">TableFlip</a> and bypassed the validation problem with a <a href="https://christiantietze.de/posts/2016/06/disabling-toolbar-item-segments/">custom validation logic</a>. So I was safe there.</p>
<p>But some of the menu items were indeed clickable without producing any effect. Uh oh.</p>
<p>The fix is quite simple: make sure you assign the <code>NSToolbarItem</code>’s <code>action</code>. That’s being used when the overflow menu item representations are assembled.</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">extension</span> <span class="kt">ToolbarSegmentedControlSegment</span> <span class="p">{</span>
<span class="kd">func</span> <span class="nf">toolbarItem</span><span class="p">()</span> <span class="o">-></span> <span class="kt">NSToolbarItem</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">item</span> <span class="o">=</span> <span class="kt">NSToolbarItem</span><span class="p">(</span><span class="nv">itemIdentifier</span><span class="p">:</span> <span class="n">toolbarItemIdentifier</span><span class="p">)</span>
<span class="n">item</span><span class="o">.</span><span class="n">label</span> <span class="o">=</span> <span class="n">label</span>
<span class="n">item</span><span class="o">.</span><span class="n">action</span> <span class="o">=</span> <span class="n">action</span> <span class="c1">// Make sure to set this!</span>
<span class="n">item</span><span class="o">.</span><span class="n">menuTitle</span> <span class="o">=</span> <span class="n">menuTitle</span>
<span class="n">item</span><span class="o">.</span><span class="n">menuImage</span> <span class="o">=</span> <span class="n">menuImage</span>
<span class="n">item</span><span class="o">.</span><span class="nf">updateMenuFormRepresentation</span><span class="p">()</span>
<span class="k">return</span> <span class="n">item</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Looking at this code out of <a href="https://christiantietze.de/posts/2018/11/reliable-nssegmentedcontrol-in-toolbar/">context</a>, you might wonder how on earth the toolbar item was supposed to work at all without an action.</p>
<p>It worked because outside its overflow menu item representation, the toolbar item didn’t have to handle clicks anyway. The <code>NSSegmentedControl</code> handled interaction with its segments.</p>
<hr><p><small><a href="https://christiantietze.de/hire-me/">Hire me</a> for freelance macOS/iOS work and consulting.</small></p><p><small><a href="https://christiantietze.de/apps/">Buy</a> my apps.</small></p><p><small><a href="https://christiantietze.de/newsletter/">Receive</a> new posts via email.</small></p>In this fourth and probably still not final part of my series on <code>NSToolbarItem</code>s with segmented controls, I just want to share a problem and a quick fix that <a href="https://cykele.ro/">Nathan Manceaux-Panot</a> brought up today. The series spans 8 years and is this: Nathan recently went through the series to implement segmented controls in toolbars but discovered that the overflow menu items would not enable (a validation problem) and when they enable, they don’t fire the action. When he brought this up today, I investigated. tag:christiantietze.de,2024-01-31:/posts/2024/01/life-hack-label-your-trash-bins/Life Hack: Label Your Trash Bins2024-01-31T09:24:52Z2024-01-31T09:24:52Z<p>I can’t for the life of me remember which trash bin bag size to buy.</p>
<p>Once I find a fit, it’ll be months before I buy the next batch. By then, I’ve long forgotten which one I bought.</p>
<p>Some more expensive ones have the bag’s size printed all over them. That helps exactly one (1) time: until you buy a cheaper make of the same size. Next time, it’s guessing time again.</p>
<p>I’m not alone, <a href="https://mas.to/@cjwirth/111764549227442745">I learned,</a> so I want to share this crazy solution with the world, with <em>you!</em></p>
<p>Best thing:</p>
<p>It’s for free!</p>
<p>Ready?</p>
<p>Here you go:</p>
<p><strong>Label your bins and trash cans</strong> as soon as a bag fits.</p>
<figure><a href="https://christiantietze.de/posts/2024/01/life-hack-label-your-trash-bins/2024-01-31_bag-label.jpg"><img alt="" src="https://christiantietze.de/posts/2024/01/life-hack-label-your-trash-bins/2024-01-31_bag-label.jpg" /></a><figcaption>A label on a trash can lid.</figcaption></figure>
<ul>
<li><strong>Tool:</strong> Buy a Sharpie or a label printer or some transparent tape.</li>
<li><strong>Process:</strong> Continue to buy bin bags. Once a size fits, write its size on the bin with the tool you bought.</li>
</ul>
<p>(We have an embossing label printer, so we use that.)</p>
<p>That’s it! No more bag guessing!</p>
<p>Expensive 30 liter bag manufacturers hate that I can get cheaper 30 liter bags reliably. I also don’t buy rolls of bags that are too large anymore, wasting potential volume, and getting fewer bags per roll. It’s amazing.</p>
<h2 id="testimonials">Testimonials</h2>
<p>Happy users and impressed people have this to say:</p>
<blockquote>
<p>omg<br />
—<a href="https://m.objc.io/@chris/111766388900001475">Chris Eidhof</a></p>
</blockquote>
<!-- -->
<blockquote>
<p>this is the best idea that i never thought of<br />
—<a href="https://mas.to/@cjwirth/111770804184010347">Caesar Wirth</a></p>
</blockquote>
<!-- -->
<blockquote>
<p>What. That’s it?<br />
—Anonymous family member</p>
</blockquote>
<hr><p><small><a href="https://christiantietze.de/hire-me/">Hire me</a> for freelance macOS/iOS work and consulting.</small></p><p><small><a href="https://christiantietze.de/apps/">Buy</a> my apps.</small></p><p><small><a href="https://christiantietze.de/newsletter/">Receive</a> new posts via email.</small></p>I can’t for the life of me remember which trash bin bag size to buy. Once I find a fit, it’ll be months before I buy the next batch. By then, I’ve long forgotten which one I bought. Some more expensive ones have the bag’s size printed all over them. That helps exactly one (1) time: until you buy a cheaper make of the same size. Next time, it’s guessing time again. tag:christiantietze.de,2024-01-31:/posts/2024/01/personal-website-ideas/32-Bit Cafe: Personal Website Ideas2024-01-31T07:45:35Z2024-01-31T07:45:35Z<p><a href="https://32bit.cafe/websiteideas/">Here’s a truly inspirational list of things to do</a> from the <em>32-Bit Cafe</em>, “a community of like-minded website hobbyists and professionals helping to make the personal web fruitful and bountiful again”. It covers these topics:</p>
<ul>
<li>Page Ideas</li>
<li>Potential Website Topics</li>
<li>CSS & Page Design</li>
<li>Art & Graphic Design</li>
<li>Technical Tasks</li>
<li>Accessibility</li>
<li>Interactivity</li>
<li>Social</li>
</ul>
<p>It’s a refreshing read (and brings up a lot of nostalgia)!</p>
<p>—<a href="https://baty.net/journal/2024/01/23/ideas-for-your-personal-website">via Jack Baty</a></p>
<hr><p><small><a href="https://christiantietze.de/hire-me/">Hire me</a> for freelance macOS/iOS work and consulting.</small></p><p><small><a href="https://christiantietze.de/apps/">Buy</a> my apps.</small></p><p><small><a href="https://christiantietze.de/newsletter/">Receive</a> new posts via email.</small></p><a href="https://32bit.cafe/websiteideas/">Here’s a truly inspirational list of things to do</a> from the <em>32-Bit Cafe</em>, “a community of like-minded website hobbyists and professionals helping to make the personal web fruitful and bountiful again”. It covers these topics: It’s a refreshing read (and brings up a lot of nostalgia)! tag:christiantietze.de,2024-01-30:/posts/2024/01/low-app-prices-are-pre-emptive-excuses/Low App Prices are Preemptive Excuses2024-01-30T15:06:23Z2024-01-30T15:06:23Z<p>Developers see the bugs and problems of their products, and thus they are prone to not charge a high price instinctively.</p>
<p>The price of an app signals its value or <em>worth</em> to the prospective customer looking at the price tag. <a href="https://swiftjectivec.com/Pricing-Indie-iOS-Apps-According-To-Perks-of-a-Wallflower/" title="Jordan Morgan: Pricing Indie Apps. The Perks of a Wallflower Rule">Jordan Morgen shares this</a> from the Spend Stack days:</p>
<blockquote>
<p><strong>It’s a $2.99 app.</strong> Though, when we price something, we are also inherently telling a story before anybody ever downloads it: <strong>“It’s worth this much.”</strong> (My emphasis)</p>
</blockquote>
<p>Jordan relates this to the line <a href="https://www.youtube.com/watch?v=ZrVIhNkOA64">“we accept the love we think we deserve” from the movie “The Perks of Being a Wallflower”</a>. A direct transfer would be:</p>
<p>You accept the price you think you deserve for this app.</p>
<p>Or: You accept the price you find the app is worth.</p>
<p>For a recent project, Jordan picked a much higher price than he instinctively would, <a href="https://swiftjectivec.com/Pricing-Indie-iOS-Apps-According-To-Perks-of-a-Wallflower/" title="Jordan Morgan: Pricing Indie Apps. The Perks of a Wallflower Rule">noting</a> that most of the time, he just sees the “holes” and “missing features”.</p>
<blockquote>
<p>I boldly said to users that they should pay me $40 a year for it, or $10 a month or $149.99 for a one-off payment.</p>
<p>Now, three months later, I think I underpriced myself. <strong>But, when I look at my own app - all I see are the holes. The missing features. The rough edges. The animation that, after hours of tweaking, still just doesn’t feel quite right.</strong> (My emphasis)</p>
</blockquote>
<p>If you focus on what’s not right with the app, how short it falls from perfection and your vision, then you will pick a lower price naturally to offset this.</p>
<p>A pre-emptive excuse, so to speak: “Sorry this doesn’t do everything I believe you might want, but I’m taking only a little money in return.”</p>
<p>I’d love to end this on a positive note and a quick fix to take away from all this. But at the moment, all I can point out that this is probably a very natural reaction for many. Developers’ takes on a fair price for the thing they made probably isn’t the best. (Ideally, ask friends who work in sales.)</p>
<hr><p><small><a href="https://christiantietze.de/hire-me/">Hire me</a> for freelance macOS/iOS work and consulting.</small></p><p><small><a href="https://christiantietze.de/apps/">Buy</a> my apps.</small></p><p><small><a href="https://christiantietze.de/newsletter/">Receive</a> new posts via email.</small></p>Developers see the bugs and problems of their products, and thus they are prone to not charge a high price instinctively. The price of an app signals its value or <em>worth</em> to the prospective customer looking at the price tag. <a href="https://swiftjectivec.com/Pricing-Indie-iOS-Apps-According-To-Perks-of-a-Wallflower/" title="Jordan Morgan: Pricing Indie Apps. The Perks of a Wallflower Rule">Jordan Morgen shares this</a> from the Spend Stack days: tag:christiantietze.de,2024-01-24:/posts/2024/01/sent-email-with-file-located-on-server/Have You Ever Sent an Email with a File Attachment that is Located on a Server?2024-01-24T15:28:33Z2024-01-24T15:28:33Z<p>Today was a day of convergence.</p>
<p>Our home server/NAS had a lot of SATA-related kernel errors and drive failures in the past weeks that I couldn’t track down. I replaced the drive <em>and</em> the cables and things have quieted down. This means I was SSH’ing into the server quite a bit this month. Mild data loss ans corrupted file systems included.</p>
<p>So when I was asked to send a potentially corrupted backup record file that prevents <a href="https://www.arqbackup.com/">Arq for Mac</a> from backing up anything, I did the straight-forward and sensible thing.</p>
<p>The UUIDs in the path of Arq backups are quite cryptic. The file’s absolute path actually is this: <code>/mnt/user/ct_mirror/mbp2020/EC5C50F5-96D9-4965-8849-C2E8296E12FD/backupfolders/F6BA5DBC-31E4-4DE3-9AE9-7A41873881EC/backuprecords/00170/4209506.backuprecord</code>.</p>
<p>Now this is the recipe to get the file from the NAS to the nice support people:</p>
<ol>
<li>Hit “Reply” on the email. You do your email in Emacs, of course, so a mail composition buffer opens.</li>
<li>In another window, SSH onto the server. That’s quite simple using TRAMP: start with this fake path-protocol-thingie notation, like <code>/ssh:USER@SERVER:/</code>, and from that absolute “path”, interactively drill down into the directory tree to get to the final directory of the path, “<code>00170</code>”.</li>
<li>In that directoy listing (via <code>dired</code>, you’re still in Emacs, after all!), locate the file they want. You can use full text search on the buffer like I did, it’s all text after all!</li>
<li>Use <kbd>C-,</kbd> to launch Embark on the file. For files, which I have an “attach the selected file to mail composition buffer” action.</li>
<li>Compose the rest of the reply, hit send.</li>
</ol>
<p>It’s a bit slower to attach a file that’s being transferred from the server than a local file, but since email is just weird plain text with binary blobs anyway, it’s no big deal, either.</p>
<p>That workflow was quite magical. How would normal people send stuff from servers via email? <code>rsync</code> or <code>scp</code> to their local machine first, I guess? It’s sort of fun that the actual location of a file was completely irrelevant for this process. That sparked a lot of joy, I must say.</p>
<p>Oh, I mentioned the <a href="https://github.com/oantolin/embark">Embark</a> action to attach files <a href="https://christiantietze.de/posts/2022/04/transient-menu-galore/">before</a> but never shared the code, so here it is:</p>
<div class="language-elisp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="nb">defun</span> <span class="nv">ct/attach-file</span> <span class="p">(</span><span class="nv">file</span><span class="p">)</span>
<span class="s">"Attach FILE to an email message.
The message to which FILE is attached is chosen as for `gnus-dired-attach`."</span>
<span class="p">(</span><span class="nv">interactive</span> <span class="s">"fAttach: "</span><span class="p">)</span>
<span class="p">(</span><span class="nb">require</span> <span class="ss">'gnus-dired</span><span class="p">)</span>
<span class="p">(</span><span class="nv">ct/with-notmuch-as-compose-mail</span>
<span class="p">(</span><span class="nv">gnus-dired-attach</span> <span class="p">(</span><span class="nb">list</span> <span class="nv">file</span><span class="p">))))</span>
<span class="p">(</span><span class="nb">defmacro</span> <span class="nv">ct/with-notmuch-as-compose-mail</span> <span class="p">(</span><span class="k">&rest</span> <span class="nv">body</span><span class="p">)</span>
<span class="s">"Overrides `compose-mail' with `notmuch-mua-mail' for the duration of BODY."</span>
<span class="o">`</span><span class="p">(</span><span class="k">progn</span>
<span class="p">(</span><span class="nb">require</span> <span class="ss">'notmuch-mua</span><span class="p">)</span>
<span class="p">(</span><span class="nv">advice-add</span> <span class="ss">'compose-mail</span> <span class="ss">:override</span> <span class="nf">#'</span><span class="nv">notmuch-mua-mail</span><span class="p">)</span>
<span class="p">(</span><span class="k">unwind-protect</span> <span class="p">(</span><span class="k">progn</span> <span class="o">,@</span><span class="nv">body</span><span class="p">)</span>
<span class="p">(</span><span class="nv">advice-remove</span> <span class="ss">'compose-mail</span> <span class="nf">#'</span><span class="nv">notmuch-mua-mail</span><span class="p">))))</span>
</code></pre></div></div>
<p>Then bind this to the <kbd>a</kbd> key (for “<u>a</u>ttach”) in the Embark file action keymap:</p>
<div class="language-elisp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="nv">define-key</span> <span class="nv">embark-file-map</span> <span class="p">(</span><span class="nv">kbd</span> <span class="s">"a"</span><span class="p">)</span> <span class="nf">#'</span><span class="nv">ct/attach-file</span><span class="p">)</span>
</code></pre></div></div>
<p>Please don’t ask how this came into being. It is a horrible mix of tips on Reddit (the original implementation was by @oantolin, creator of <code>embark.el</code>, if I recall correctly) and other hacks because attaching files from <code>dired</code> apparently is not part of the <code>mml-*</code> or <code>message-*</code> packages, but <code>gnus</code>. It works, so I’m not touching it.</p>
<hr><p><small><a href="https://christiantietze.de/hire-me/">Hire me</a> for freelance macOS/iOS work and consulting.</small></p><p><small><a href="https://christiantietze.de/apps/">Buy</a> my apps.</small></p><p><small><a href="https://christiantietze.de/newsletter/">Receive</a> new posts via email.</small></p>Today was a day of convergence. Our home server/NAS had a lot of SATA-related kernel errors and drive failures in the past weeks that I couldn’t track down. I replaced the drive <em>and</em> the cables and things have quieted down. This means I was SSH’ing into the server quite a bit this month. Mild data loss ans corrupted file systems included. tag:christiantietze.de,2024-01-18:/posts/2024/01/transformative-reading-2017/Transformative Reading ... 2017 Edition?!2024-01-18T09:10:08Z2024-01-18T09:10:08Z<p>So I found this list of books I read and which I wanted to put on this blog in my inbox.</p>
<p>It’s from a migration from OmniFocus to Emacs/org-mode from 2019, and the title is “Transformative Reading 2017”.</p>
<p>What were the picks back then?</p>
<p>And being 7 (!) years wiser, what do I think about the picks now?</p>
<p>Here’s the list. I don’t know why I originally ordered them this way, but I left it as-is.</p>
<p>The book links are affiliate links. I’m only linking books I do actually think are worth buying. I’m also handing out 1 to 5 stars in total, but each ranking only once. The rest goes unranked.</p>
<h2 id="transformative-reading-2017">Transformative Reading 2017</h2>
<ul>
<li><a href="https://www.amazon.com/Dreaming-Code-Programmers-Transcendent-Software-ebook/dp/B000PDZFOI?crid=28DA6F93LR3DP&keywords=dreaming+in+code&qid=1705569226&sprefix=dreaming+in+co%2Caps%2C194&sr=8-1&linkCode=ll1&tag=chritietwork-20&linkId=60853f3d4090dc824b4d35ad52be26cb&language=en_US&ref_=as_li_ss_tl">Scott Rosenberg: (2007) <em>Dreaming in Code</em></a>
<ul>
<li>★☆☆☆☆ Was a fun story about, as the subtitle says: “Two Dozen Programmers, Three Years, 4,732 Bugs, and One Quest for Transcendent Software”. You won’t learn how to program, but you’ll learn something about programmer culture and open source.</li>
</ul>
</li>
<li><a href="https://www.amazon.com/Psychology-Computer-Programming-Silver-Anniversary-ebook/dp/B004R9QACC?crid=2YXTGYVV4A74H&keywords=psychology+of+computer+programming&qid=1705569267&sprefix=psychology+of+compu%2Caps%2C185&sr=8-1&linkCode=ll1&tag=chritietwork-20&linkId=a05943cd4c75a5eab24454a4f844fa9e&language=en_US&ref_=as_li_ss_tl">Gerald M Weinberg (1971): <em>Psychology of Computer Programming</em></a>
<ul>
<li>I don’t remember much, but I have 2 notes in my Zettelkasten from 2017 about the difference between observation and experiment, which I planned (and still plan) to use in WordCounter. Not sure if you should pick it up.</li>
</ul>
</li>
<li>Nassim Taleb (2012): <em>Antifragile</em>
<ul>
<li>★★☆☆☆ 10 notes in my Zettelkasten, the highest count of all on this list. I remember I found the book a bit confusing, going here and there, back and forth. But at the same time not hard to read at all. It’s well written, after all, but idiosyncratically structured. If you’re interested in cognitive biases and fallacies, you’ve probably already read this book. It was a huge thing a decade ago. I like to read stuff like this because it <em>feels</em> like I’m becoming more aware of how brittle or arbitrary everything I have in my mind actually is. Since nothing’s really as certain as my brain wants me to believe, this helps a bit with holding beliefs less strongly.</li>
</ul>
</li>
<li><a href="https://www.amazon.com/Shop-Class-Soulcraft-Inquiry-Value/dp/0143117467?crid=B5635YCUUH53&keywords=Matthew+B.+Crawford+%282010%29%3A+_Shop+Class+as+Soulcraft_&qid=1705569319&sprefix=matthew+b.+crawford+2010+_shop+class+as+soulcraft_%2Caps%2C171&sr=8-1&linkCode=ll1&tag=chritietwork-20&linkId=2c132233cf56752f8d0ee66662d4c45a&language=en_US&ref_=as_li_ss_tl">Matthew B. Crawford (2010): <em>Shop Class as Soulcraft</em></a>
<ul>
<li>★★★★★ Still resonates with me a lot. This is what I believe work should be. I also found immediate moments of happiness in manual work after reading the book which made me think back to this. Recommended to everyone working on computers all day to think about what work is.</li>
</ul>
</li>
<li>Edward Yourdon: <em>Object-Oriented Systems Design</em>
<ul>
<li>No notes in my Zettelkasten, but paging through the book, I see lots of marginal notes. Maybe this was the book that mentioned all these high software project failure rates? I do remember that this book sounded more like talking to a consultant than talking to a programmer. On the inside cover, I wrote: “lots of rave about <a href="https://en.wikipedia.org/wiki/Computer-aided_software_engineering">CASE tools</a>, all the time…” Never heard of that? I guess it’s because whatever came out of that movement culminated in UML. You can skip this book, except if you’re interested in its value as a historic document.</li>
</ul>
</li>
<li>Neil Fiore (2007): <em>The Now Habit</em>
<ul>
<li>I know it’s a popular book, but I don’t remember anything now. My Zettelkasten says the book is the origin of “Work hard, play hard”. So that’s something. Searching for the book, I get notes on procrastination. I can also tell I never revisited these notes since 2012. Wait. 2012? Apparently I read the book 5 years prior, too?</li>
</ul>
</li>
<li><a href="https://www.amazon.com/Love-Shrinks-Sensible-Finding-Relationship-ebook/dp/B01HMXRXF2?&linkCode=ll1&tag=chritietwork-20&linkId=8f0b446ffd9191dd72012bc78a56ad2d&language=en_US&ref_=as_li_ss_tl">Michael and Sarah Bennett: <em>F*ck Love</em></a>
<ul>
<li>Ugh. The humor is not lost on me, but it was a bit much. No Zettel have been written. I do recall that professionaling the screening process resonated with me, as in: taking this topic serious, and not leaving finding a mate for life and marry her to feelings, which are fickle at best. The substance is good, there’s solid advice. For me, this ties into an idea complex that was opened by Robert Solomon (1976): <em>The Passions: Emotions and the Meaning of Life</em>, a philosophical book.</li>
</ul>
</li>
<li>Debora Phillips and Robert Judd (1982): <em>How to Fall out of Love</em>
<ul>
<li>I wasn’t in a relationship at the time and not in love with anyone, but wanted to know more about the topic, I recall. I did create a couple of notes in my Zettelkasten, to my surprise, with gems like “To forget someone means to overwrite everyday expectations”, pointing out that the felling of hurt is tied to mundane situations that remind us of a past lover. Notes like this make the book worth to me, but I don’t know if it’s worth your while? I would re-read this with a teenage child to be prepared.</li>
</ul>
</li>
<li>Jocko Willink: <em>Extreme Ownership</em>
<ul>
<li>I remember brouhaha and war stories, but regarding the substance, the term “Extreme Ownership” really says it all. Which kind of indicates that the book did its job! The concept of taking extreme ownership is great, but the book you could replace with YouTube videos and podcasts on the topic. One or two inspiring anecdotes should suffice to anchor the idea.</li>
</ul>
</li>
<li><a href="https://www.amazon.com/One-Straw-Revolution-Introduction-Natural-Classics/dp/1590173139?crid=3GVOZRD91X2JX&keywords=Fukuoka%3A+_The+One-Straw+Revolution_&qid=1705569531&sprefix=fukuoka+_the+one-straw+revolution_%2Caps%2C163&sr=8-1&linkCode=ll1&tag=chritietwork-20&linkId=cb9b28ddf54d0987d5917049cfa24590&language=en_US&ref_=as_li_ss_tl">Fukuoka: <em>The One-Straw Revolution</em></a>
<ul>
<li>★★★☆☆ Farming and gardening! I really like the topic. This is about rice farming, and mulching, and how you can produce a lot without doing all too much of anything. Permaculture fans will know this, the gardeners may enjoy this.</li>
</ul>
</li>
<li>Guy Claxton (1999): <em>Hare Brain, Tortoise Mind</em>
<ul>
<li>Mixing this up with “Thinking, Fast and Slow” in my head. Same category for me. Interesting, but I don’t recall details. Falls into the category of “transformative by making me question my beliefs a bit more”. Only note in my Zettelkasten related to this book is actually St. Augustine: “I cannot totally grasp all that I am. The mind is not large enough to contain itself.” That’s a very generalizable principle!</li>
</ul>
</li>
<li><a href="https://www.amazon.com/Eat-Pray-Love-Everything-Indonesia/dp/0143038419?crid=1VT21DL2CUGJM&keywords=gilbert+eat+pray+love&qid=1705570046&sprefix=gilbert+eat+pray+lov%2Caps%2C190&sr=8-1&linkCode=ll1&tag=chritietwork-20&linkId=8ef40ce6c5517fcb4a0fb2a8b2b45b1d&language=en_US&ref_=as_li_ss_tl">Elizabeth Gilbert (2006): <em>Eat, Pray, Love</em></a>
<ul>
<li>★★★★☆ I still recommend this book. That one does have transformative potential. Well, it had for me. Priorities in life; going on adventures; discover yourself a bit more in silence and soliture. And do not focus on the mundane too much. Leave room for exploration. “Spirituality” is such an awkward term, but I don’t know anything better. Above I mentioned <em>Shop Class as Soulcraft</em>, and that is about working with your hands more to discover something about your human nature. That’s also kinda sorta spiritual. <em>Eat, Pray, Love</em> is even more so. I don’t have a hard time thinking about the ‘learnings’ from books like these without being offended by their ‘spiritual’ focus. Given that you, dear reader, are likely a tech-savvy person – maybe that’s not your cup of tea :)</li>
</ul>
</li>
<li>Yuval Harari: <em>Sapiens: A Brief History of Humankind</em> (Audiobook)
<ul>
<li>I remember I enjoyed listening to this, but I don’t remember anything. Other people also liked it. Harari became even more of a household name since then. Must be for a reason. No recommendation, but you could spend your time worse.</li>
</ul>
</li>
</ul>
<hr />
<p>To summarize the experience of going through this list again: I am doing an awful job at processing notes from books!</p>
<p>I never take the time. Really, <em>never.</em> Not exaggerating. I’m reading, I’m thinking, but I’m coding the rest of the time, so a lot is lost. That’s a bit depressing.</p>
<p>It’s also funny to me that the books that really left an impression have little to do with work or self-help and everything to do with what could be called “spirituality” – a term I’m not fond of, because it sounds like hocus-pocus to me. Whatever category you put “the meaning of life” into, that’s what I mean. Closely followed by books that are making me question my beliefs more. The rest went into the underdark of my subconsciousness where all the good stuff probably is buried.</p>
<hr><p><small><a href="https://christiantietze.de/hire-me/">Hire me</a> for freelance macOS/iOS work and consulting.</small></p><p><small><a href="https://christiantietze.de/apps/">Buy</a> my apps.</small></p><p><small><a href="https://christiantietze.de/newsletter/">Receive</a> new posts via email.</small></p>So I found this list of books I read and which I wanted to put on this blog in my inbox. It’s from a migration from OmniFocus to Emacs/org-mode from 2019, and the title is “Transformative Reading 2017”. What were the picks back then? And being 7 (!) years wiser, what do I think about the picks now? Here’s the list. I don’t know why I originally ordered them this way, but I left it as-is. tag:christiantietze.de,2024-01-10:/posts/2024/01/chatgpt-shell-confirm-close-compose-buffer/ChatGPT Shell: Confirm Before Closing and Split Compose Buffer2024-01-10T18:59:12Z2024-01-16T08:08:36Z<p>I admit: I’ve been relying heavily on ChatGPT to get to grips with some PHP things. Asking for interpretation, alternatives, and PHP 8-specific stuff was a lot of help.</p>
<p>I’ve been using this in a separate floating window (aka ‘frame’) in Emacs next to my editing context, and that was great.</p>
<p>Until I accidentally closed the buffer and lost the history.</p>
<p>Twice.</p>
<p>Álvaro Ramírez, author of <a href="https://github.com/xenodium/chatgpt-shell"><code>chatgpt-shell</code></a>, <a href="https://indieweb.social/@xenodium/111733069067113062">kindly suggested</a> I could ask for confirmation before closing ChatGPT buffers. Haven’t thought of that, obviously, so I was very grateful.</p>
<p><strong>Update 2024-01-16:</strong> All of this is now part of <a href="https://melpa.org/#/shell-maker"><code>shell-maker.el</code></a> which is used by <code>chatgpt-shell</code> (and other interactive shells, like kagi), so if you install the bleeding edge version from MELPA or source now, you get a confirmation dialog out of the box. I’m leaving this post for history.</p>
<p>A web search later, I found out that <code>kill-buffer-query-functions</code> exists and is triggered as a sort of filter that intercepts buffer closing actions. The documentation says:</p>
<blockquote>
<p>List of functions called with no args to query before killing a buffer.<br />
The buffer being killed will be current while the functions are running.<br />
See ‘kill-buffer’.</p>
<p>If any of them returns nil, the buffer is not killed. Functions run by<br />
this hook are supposed to not change the current buffer.</p>
</blockquote>
<p>Confusingly, though, you can use <code>yes-or-no-p</code> to ask the user for confirmation, and confirming returns <code>t</code> for “yes” and <code>nil</code> for “no”, but the returned valued <em>just works</em> in a <code>kill-buffer-query-functions</code> function …?! Also, returning <code>t</code> actually skips the function and is the neutral element here.</p>
<p>That doesn’t sound right <a href="https://stackoverflow.com/questions/86963/how-do-i-get-a-warning-before-killing-a-temporary-buffer-in-emacs">but StackOverflow agrees</a>, so I’m keeping it.</p>
<p>I don’t know.</p>
<figure><a href="https://christiantietze.de/posts/2024/01/chatgpt-shell-confirm-close-compose-buffer/2024-01-10_chatgpt-shell-compose.png"><img alt="" src="https://christiantietze.de/posts/2024/01/chatgpt-shell-confirm-close-compose-buffer/2024-01-10_chatgpt-shell-compose.png" /></a><figcaption>Screenshot of Emacs with a bottom split showing the compose window</figcaption></figure>
<p>Anyway, here’s the config:</p>
<div class="language-elisp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="nb">use-package</span> <span class="nv">chatgpt-shell</span>
<span class="ss">:commands</span> <span class="p">(</span><span class="nv">chatgpt-shell</span>
<span class="nv">chatgpt-shell-prompt-compose</span><span class="p">)</span>
<span class="ss">:bind</span> <span class="p">((</span><span class="s">"C-c C-e"</span> <span class="o">.</span> <span class="nv">chatgpt-shell-prompt-compose</span><span class="p">))</span>
<span class="ss">:config</span>
<span class="p">(</span><span class="nb">defun</span> <span class="nv">ct/confirm-before-killing-chatgpt</span> <span class="p">()</span>
<span class="p">(</span><span class="k">let</span> <span class="p">((</span><span class="nv">buf</span> <span class="p">(</span><span class="nv">current-buffer</span><span class="p">)))</span>
<span class="p">(</span><span class="k">if</span> <span class="p">(</span><span class="nb">and</span> <span class="p">(</span><span class="nv">buffer-match-p</span> <span class="s">"^\\*chatgpt\\*"</span> <span class="nv">buf</span><span class="p">)</span>
<span class="p">(</span><span class="nb">not</span> <span class="p">(</span><span class="nv">buffer-match-p</span> <span class="s">" compose$"</span> <span class="nv">buf</span><span class="p">)))</span>
<span class="p">(</span><span class="nb">yes-or-no-p</span> <span class="s">"ChatGPT Buffer! Kill anyway? "</span><span class="p">)</span>
<span class="no">t</span><span class="p">)))</span>
<span class="p">(</span><span class="nv">add-to-list</span> <span class="ss">'kill-buffer-query-functions</span> <span class="nf">#'</span><span class="nv">ct/confirm-before-killing-chatgpt</span><span class="p">)</span>
<span class="p">(</span><span class="nv">add-to-list</span> <span class="ss">'display-buffer-alist</span>
<span class="o">'</span><span class="p">(</span><span class="s">"\\*chatgpt\\*.*compose"</span>
<span class="p">(</span><span class="nv">display-buffer-reuse-window</span>
<span class="nv">display-buffer-in-side-window</span><span class="p">)</span>
<span class="p">(</span><span class="nv">reusable-frames</span> <span class="o">.</span> <span class="nv">visible</span><span class="p">)</span>
<span class="p">(</span><span class="nv">side</span> <span class="o">.</span> <span class="nv">bottom</span><span class="p">)</span>
<span class="p">(</span><span class="nv">window-height</span> <span class="o">.</span> <span class="mf">0.3</span><span class="p">))))</span>
<span class="c1">;; Later:</span>
<span class="p">(</span><span class="nv">with-eval-after-load</span> <span class="ss">'chatgpt-shell</span>
<span class="p">(</span><span class="nv">with-eval-after-load</span> <span class="ss">'xah-fly-keys</span>
<span class="p">(</span><span class="nv">define-key</span> <span class="nv">xah-fly-leader-key-map</span> <span class="p">(</span><span class="nv">kbd</span> <span class="s">"i k"</span><span class="p">)</span> <span class="nf">#'</span><span class="nv">chatgpt-shell-prompt-compose</span><span class="p">)))</span>
</code></pre></div></div>
<p>I bound the composition window to <kbd>SPC i k</kbd>, the space key being the leader key in command-mode. That was free and is pretty convenient to pop up a composition buffer. I like the dedicated side window split for this.</p>
<p>Since I’m using the composition function, the buffer closing interception checks for “starts with <code>*chatgpt*</code>” and “does not end with ` compose`” so that the pop-up composition buffers can come and go, only a real history buffer is protected.</p>
<p>Maybe I should add autosaving of the transcript next, deleting transcripts that are 5 days old or older automatically.</p>
<hr><p><small><a href="https://christiantietze.de/hire-me/">Hire me</a> for freelance macOS/iOS work and consulting.</small></p><p><small><a href="https://christiantietze.de/apps/">Buy</a> my apps.</small></p><p><small><a href="https://christiantietze.de/newsletter/">Receive</a> new posts via email.</small></p>I admit: I’ve been relying heavily on ChatGPT to get to grips with some PHP things. Asking for interpretation, alternatives, and PHP 8-specific stuff was a lot of help. I’ve been using this in a separate floating window (aka ‘frame’) in Emacs next to my editing context, and that was great. Until I accidentally closed the buffer and lost the history. tag:christiantietze.de,2024-01-09:/posts/2024/01/emacs-sqlite-mode-open-sqlite-files-automatically/Emacs sqlite-mode to Open .sqlite Files Automatically2024-01-09T13:49:27Z2024-01-09T20:22:59Z<p>Normally, you’d associate file path extensions with major modes in Emacs via <code>auto-mode-alist</code>. The associative list contains entries like <code>("\\.html" . web-mode)</code> so that when you open (aka “visit”) an HTML file, Emacs automatically switches to <code>web-mode</code>, which in turns supplies shortcuts and syntax highlighting and so on.</p>
<p>That doesn’t work with <code>.sqlite</code> files and <code>sqlite-mode</code>, though. The built-in <code>sqlite-mode</code> (since Emacs 29) is peculiar: it expects you to manually run <code>sqlite-mode-open-file</code> and pick a file to open. There are probably very good technical reasons (like not having SQLite support bundled into your Emacs binary), but that’s so weird. Especially since you can open PDFs, images, and SVGs in a graphical preview just like that without special incantations.</p>
<figure><a href="https://christiantietze.de/posts/2024/01/emacs-sqlite-mode-open-sqlite-files-automatically/./2024-01-09_sqlite-table.png"><img alt="" src="https://christiantietze.de/posts/2024/01/emacs-sqlite-mode-open-sqlite-files-automatically/./2024-01-09_sqlite-table.png" /></a><figcaption>Why, yes, I do consider this to be a graphical representation of a SQLite file!</figcaption></figure>
<p><a href="https://mastodon.online/@zardoz03/111720653550362827">On Mastodon, @zardoz.el@mastodon.online suggested <code>magic-mode-alist</code></a> and using a lambda or function. Never heard of that list, but it turns out that this is checked <em>before</em> <code>auto-mode-alist</code>, and instead of operating on the file path, it operates on the file contents (or rather: buffer contents of the file that is just being opened). That means I can copy and paste whatever Emacs displays as text in its buffer for the <em>binary file format</em> <code>.sqlite</code> and go from there. The start of the file is:</p>
<pre><code>SQLite format 3������
</code></pre>
<p>Oh, no, that doesn’t render properly as plain text. Of course. Let me try again with a picture:</p>
<figure><a href="https://christiantietze.de/posts/2024/01/emacs-sqlite-mode-open-sqlite-files-automatically/./2024-01-09_binary-file.png"><img alt="" src="https://christiantietze.de/posts/2024/01/emacs-sqlite-mode-open-sqlite-files-automatically/./2024-01-09_binary-file.png" /></a><figcaption>The <code>^@</code> is code point 0 (null), <code>^P</code> is code point 16 (#x10, data link escape), and <code>^A</code> is code point 1 (start of heading)</figcaption></figure>
<p>And then there’s many more of these character sequences, but that should do to disambiguate a binary <code>.sqlite</code> file from a text file that starts with the text “SQLite format 3”.</p>
<p>Now the function to auto-magic-ally use <code>sqlite-mode-open-file</code> (which creates a new buffer!) and close the binary file buffer can work with the buffer-local variable <code>buffer-file-name</code>. The procedure is</p>
<ol>
<li>save <code>buffer-file-name</code>,</li>
<li>kill the current binary file buffer,</li>
<li>invoke <code>sqlite-mode-open-file</code> with the file name from step (1).</li>
</ol>
<p>The piece form my <code>init.el</code> thus is:</p>
<div class="language-elisp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="nb">use-package</span> <span class="nv">sqlite-mode</span>
<span class="ss">:config</span>
<span class="p">(</span><span class="nb">defun</span> <span class="nv">ct/sqlite-view-file-magically</span> <span class="p">()</span>
<span class="s">"Runs `sqlite-mode-open-file' on the file name visited by the
current buffer, killing it."</span>
<span class="p">(</span><span class="nb">require</span> <span class="ss">'sqlite-mode</span><span class="p">)</span>
<span class="p">(</span><span class="k">let</span> <span class="p">((</span><span class="nv">file-name</span> <span class="nv">buffer-file-name</span><span class="p">))</span>
<span class="p">(</span><span class="nv">kill-current-buffer</span><span class="p">)</span>
<span class="p">(</span><span class="nv">sqlite-mode-open-file</span> <span class="nv">file-name</span><span class="p">)))</span>
<span class="p">(</span><span class="nv">add-to-list</span> <span class="ss">'magic-mode-alist</span> <span class="o">'</span><span class="p">(</span><span class="s">"SQLite format 3\x00"</span> <span class="o">.</span> <span class="nv">ct/sqlite-view-file-magically</span><span class="p">)))</span>
</code></pre></div></div>
<p><strong>Update 2024-01-09:</strong> In the comments, <a href="https://christiantietze.de/posts/2024/01/emacs-sqlite-mode-open-sqlite-files-automatically/#fast-comments-jt=p8a3RIdX7X8h">matteo</a> suggested the string <code>"SQLite format 3\x00"</code> instead of my hastily pasted one. I adapted the code accordingly.</p>
<hr><p><small><a href="https://christiantietze.de/hire-me/">Hire me</a> for freelance macOS/iOS work and consulting.</small></p><p><small><a href="https://christiantietze.de/apps/">Buy</a> my apps.</small></p><p><small><a href="https://christiantietze.de/newsletter/">Receive</a> new posts via email.</small></p>Normally, you’d associate file path extensions with major modes in Emacs via <code>auto-mode-alist</code>. The associative list contains entries like <code>("\\.html" . web-mode)</code> so that when you open (aka “visit”) an HTML file, Emacs automatically switches to <code>web-mode</code>, which in turns supplies shortcuts and syntax highlighting and so on.