<rss
    version="2.0"
    xmlns:atom="http://www.w3.org/2005/Atom"
    xmlns:content="http://purl.org/rss/1.0/modules/content/"
>
<channel>

<title>Posts on Mark's Posts</title>
<link>https://markc.su/posts/</link>
<description>Recent content in Posts on Mark's Blog</description>
<generator>Mark's Cool Site</generator>
<language>en-us</language>
<lastBuildDate>Wed, 13 Sep 2023 22:58:05 +0200</lastBuildDate>
<atom:link
            href="https://markc.su/posts/index.xml"
            rel="self"
            type="application/rss+xml"
        />

 

<item>
<title>Creating VVZAPI</title>
<link>https://markc.su/posts/vvzapi</link>
<pubDate>Tue, 28 Apr 2026 15:06:13 &#43;0200</pubDate>
<guid>https://markc.su/posts/vvzapi</guid>
<description>Some motivation behind why and how I created an updated course catalog API for ETH Zurich.</description>
<content:encoded><![CDATA[<p><em>This is a article I wrote for <a href="https://members.vis.ethz.ch/committees/visionen">Visionen</a> and thought I might as well also share on my blog.</em></p>
<hr>
<p>Every semester is the same story. You want to search for some courses to plan out your semester, you open the Course Catalog (VVZ), and then sluggishly jump through page after page until you finally find a few potential candidates for your next semester. Every single click makes you tediously wait. And personally, it also pains me to see a website being so slow in this day and age. Though, it's still somewhat impressive for VVZ to still be running with minimal (frontend) changes since 25 years.</p>
<p>Students are not satisfied with VVZ. Want proof? In the past years, almost every VIScon hackathon has had a task related to creating an improved VVZ or course planning tool. Seemingly, I keep coming back to VVZ, even having worked on a course planning tool in two separate VIScon hackathons. Creating such a tool presents two major hurdles. On one hand, it is a large undertaking to create a tool that can correctly keep track of every requirement for all the different study programmes. But even if you choose to forgo that aspect, there is still the looming question of how to even get the data for all the courses. You might only be interested in the course name, number, and credits, but maybe you also want access to the course description and exam information.</p>
<p>But how can you get the actual course data? VVZ has an internal API, but it requires authentication, so it is a no-go if you just want to quickly play around with data to see if your tool even makes sense. So there is only one other option: You will have to resort to scraping the raw HTML pages of VVZ and then tediously extracting all the relevant data. While not the most difficult task imaginable, it does impose a challenge, especially in the likely case that you don't know the full structure of VVZ and what all the values can be or how everything gets displayed. So even with an AI agent speeding up the work of writing all the scraping logic, you will inevitably stumble upon something new that you didn't expect or handle. Luckily, you can assume that the actual structure of the HTML will probably not change anymore, allowing for any scraping tool to be quite heavily hard-coded.</p>
<p>And don't even try to make any assumptions about the structure of VVZ. Anything that you believe suuuurely doesn't exist, actually <em>does</em> exist. Did you know there can be two courses with the same number (000-0000-00L type number) in the same semester, or that credits don't have to be integers? Credits can theoretically have up to four decimal places. Luckily, the most I've stumbled upon only uses one decimal place. For example, &quot;151-0055-10L  Ingenieur-Tool: Planung menschlicher Arbeit&quot; in the 2024 Winter semester gives you 0.4 credits. You can take some 2.6 credit course to even it out.</p>
<p>There's no reason why it should be so hard to build tools on top of VVZ. I fall in with the open-source and unix ethos of creating modular tools that can easily work together. For most applications, it becomes exponentially hard to account for all features anybody would ever want. It therefore often makes sense to instead allow for easy interoperability. If people want some specific niche features, they can build their own third-party tools. People who don't want those features don't have to wade through and explicitly disable everything. In the simplest form, that usually means creating a REST API that can allow for third-parties to, on one hand, access the data, but on the other, also perform operations if required.</p>
<p>Before, when you wanted to use course catalog data, you might have been able to find some random script on GitHub somebody cooked up to quickly scrape a few things from VVZ. The problem is that basically all of these tools all only require a subset of the catalog data, and hence if you need something they don't scrape, you had to figure out and rewrite a large part of the code. You were usually better off just quickly writing your own scraper to fit your needs and tech-stack instead.</p>
<p>To aid in that, in the past months, I worked on creating an API that makes the full VVZ data available over a simple REST API. My two main goals were to first replicate <strong>all</strong> the data available, including the past semesters and all the information I doubt a lot even notice (have you ever looked at the &quot;Competencies&quot; section?). Most of the data will probably never be used, but it's all there if ever needed. And second, to make the API easy-to-use while allowing for the same filtering capabilities (and more) as VVZ.</p>
<h3 id="the-api">The API</h3>
<p>The website can be found under <a href="https://vvzapi.ch">vvzapi.ch</a>. It initially started out as a bare-bones API-only website, but I've recently added a frontend that allows you to unleash the power-user in you and search using all sorts of operators (heavily inspired by scryfall). You can add AND/OR clauses or filter by certain keywords, credits, and even by the coursereview rating if the course has one. Interestingly, but also understandably, the search itself is used by a magnitude more than the actual API endpoints.</p>
<p><img src="/content/posts/vvzapi/vvzapi.jpg" alt=""></p>
<p>My goal with VVZAPI wasn't to create the next course planning tool everyone should be using. I wanted to make the course data more easily accessible and generally also just be faster in response times. I want the API to enable new projects and tools to sprout. Often the best ideas never come to light, simply because too there are too many obstructions that have to be worked around with. With VVZAPI, I intend to at least make the data gathering step a lot easier.</p>
<p>Do you have an idea for a course tool, but were hesitant because the data-gathering step deemed itself too big of a hurdle? No need to fret, use the API endpoints on vvzapi or even just download the sqlite database dump (around 250MB), so you have all the data at your fingertips without even having to send out a single request.</p>
<p>And of course, the fine print, VVZAPI isn't an official ETH tool, and I also don't guarantee that the data is always correct and up to date. Don't blame me for misplanning your semester :)</p>
<p>Additionally, in as soon as 2 years, VVZ will get a completely new overhaul when PAKETH takes effect. At that point VVZAPI will probably be taken offline and some new solutions have to be sought after.</p>
]]></content:encoded>
</item>


 

<item>
<title>Expenses Tracking using iOS Shortcuts</title>
<link>https://markc.su/posts/ios-shortcuts</link>
<pubDate>Mon, 27 Apr 2026 10:20:35 &#43;0200</pubDate>
<guid>https://markc.su/posts/ios-shortcuts</guid>
<description>A quick guide on how I have my expenses tracking set up with Google Sheet and an iOS shortcut for adding entries</description>
<content:encoded><![CDATA[<p>A few months back I noticed I was spending a lot of money, but I just had no good way to track where that money actually went other than manually going through my bank entries one by one. It was time to add some way of tracking finances so I can better look back and see what crap I wasted my money on.</p>
<p>For common tasks like tracking expenses or managing <a href="/posts/todos">TODOs</a>, a large part is how to make it easy and friction-less to use to incorporate it into your daily life. That is why I want to share not only how I store all my finances, but also how I made it very easy to add expenses from my phone using iOS shortcuts.</p>
<h2 id="sheets">Sheets</h2>
<p>There are a lot of finance tools. Some self-hosted, some overly expensive, and some with barely any features. But all have a common flaw. They are often extremely inflexible. I want to foremost track my finances in an easy and minimal manner. If at a later point I decide I want to now add a cronjob that, for example, automatically adds my monthly subscriptions, I have to hope that whatever finance tool I'm using already supports subscriptions. And if not, hopefully it's open source so I can maybe contribute the subscription feature or at least hack it together for my personal use. But the subscriptions feature was just one example. There are a bunch of finance tracking apps that already support subscriptions. Replace it with some other feature X that you feel is very important or some graph representation Y that would make your finances so much easier to view. Very quickly you'll reach a point where all the apps that support your desired feature set either simply don't exist, or are way too complicated/expensive for a little personal finance tracking. Such a flexible finance tracking app sounds almost impossible. Luckily there's already a 40 year old solution for simple finance tracking:</p>
<p>It's Excel. Or at least, the whole spreadsheet system introduced with Excel 40 years ago. Of course, I don't use Excel myself though. I want to be able to edit my spreadsheets anywhere and on any device. Google Sheets fills that gap just perfectly for me.</p>
<p>Google sheets allows me to keep track of my finances with minimal effort. If I want a specific type of graph to visualize my spendings, I can simply insert and design the chart however I want. If I want to add a way for my subscriptions to automatically be added, I can simply vibecode a little script and have it trigger on a daily basis (called Apps Script in GSheets). If I ever have the urge to move away from Google Sheets, I can simply download the whole sheets as an excel, ods, or csv file. So I'm not vendor-locked in any way.</p>
<h2 id="adding-entries">Adding entries</h2>
<p>Then there's the question of how to insert entries. In theory, I could always open up my sheet and add the entries. Only downside is that Google Sheets takes a while to load. It might be negligible at first, but I know myself enough that this sort of friction will have me stop tracking my finances down the line as I can't be bothered anymore.</p>
<p>Google also provides Google Forms, which allows you to directly add form submissions into a google sheet. And a little undocumented trick is that you can submit form submissions using a GET request, which can be done by basically any sort of tool. Below is a quick guide on how to set it up.</p>
<p>To determine that link:</p>
<ol>
<li>Create your Google Form. Add any inputs you desire. The actual answer type does not matter. You could also just keep them all as &quot;Short answer text&quot;.</li>
<li>Head to the &quot;Pre-fill form&quot; page.</li>
<li>Enter some arbitrary values.</li>
<li>Click &quot;Get link&quot;</li>
<li>Click the &quot;COPY LINK&quot; which gets you a forms link along the lines of (split up into lines to make the components clearer):
<pre style="padding:10px;margin:20px 0 20px 0;border-radius:10px;box-shadow:5px 5px 10px rgba(0, 0, 0, 0.3);overflow:auto;color:#e6edf3;background-color:#0d1117;"><code><span style="display:flex;"><span>https:<span style="color:#ff7b72;font-weight:bold">//</span>docs<span style="color:#ff7b72;font-weight:bold">.</span>google<span style="color:#ff7b72;font-weight:bold">.</span>com<span style="color:#ff7b72;font-weight:bold">/</span>forms<span style="color:#ff7b72;font-weight:bold">/</span>d<span style="color:#ff7b72;font-weight:bold">/</span>e<span style="color:#ff7b72;font-weight:bold">/</span>aSBs<span style="color:#ff7b72;font-weight:bold">...</span>OlA
</span></span><span style="display:flex;"><span><span style="color:#ff7b72;font-weight:bold">/</span>viewform<span style="color:#f85149">?</span>usp<span style="color:#ff7b72;font-weight:bold">=</span>pp_url
</span></span><span style="display:flex;"><span><span style="color:#ff7b72;font-weight:bold">&amp;</span>entry<span style="color:#a5d6ff">.1975411869</span><span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">420</span><span style="color:#ff7b72;font-weight:bold">&amp;</span>entry<span style="color:#a5d6ff">.278177584</span><span style="color:#ff7b72;font-weight:bold">=</span>prostate<span style="color:#ff7b72;font-weight:bold">+</span>exam<span style="color:#ff7b72;font-weight:bold">&amp;</span>entry<span style="color:#a5d6ff">.453701871</span><span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">2200</span><span style="color:#ff7b72;font-weight:bold">-</span><span style="color:#a5d6ff">04</span><span style="color:#ff7b72;font-weight:bold">-</span><span style="color:#a5d6ff">01</span><span style="color:#ff7b72;font-weight:bold">&amp;</span>entry<span style="color:#a5d6ff">.816962894</span><span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">13</span>:<span style="color:#a5d6ff">37</span>
</span></span></code></pre></li>
<li>The <code>entry.&lt;id&gt;</code> are what fill in each of the inputs. Now replace the middle <code>/viewform?usp=pp_url</code> part with <code>/formResponse?submit=Submit</code>. You should now have:
<pre style="padding:10px;margin:20px 0 20px 0;border-radius:10px;box-shadow:5px 5px 10px rgba(0, 0, 0, 0.3);overflow:auto;color:#e6edf3;background-color:#0d1117;"><code><span style="display:flex;"><span>https:<span style="color:#ff7b72;font-weight:bold">//</span>docs<span style="color:#ff7b72;font-weight:bold">.</span>google<span style="color:#ff7b72;font-weight:bold">.</span>com<span style="color:#ff7b72;font-weight:bold">/</span>forms<span style="color:#ff7b72;font-weight:bold">/</span>d<span style="color:#ff7b72;font-weight:bold">/</span>e<span style="color:#ff7b72;font-weight:bold">/</span>aSBs<span style="color:#ff7b72;font-weight:bold">...</span>OlA
</span></span><span style="display:flex;"><span><span style="color:#ff7b72;font-weight:bold">/</span>formResponse<span style="color:#f85149">?</span>submit<span style="color:#ff7b72;font-weight:bold">=</span>Submit
</span></span><span style="display:flex;"><span><span style="color:#ff7b72;font-weight:bold">&amp;</span>entry<span style="color:#a5d6ff">.1975411869</span><span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">420</span><span style="color:#ff7b72;font-weight:bold">&amp;</span>entry<span style="color:#a5d6ff">.278177584</span><span style="color:#ff7b72;font-weight:bold">=</span>prostate<span style="color:#ff7b72;font-weight:bold">+</span>exam<span style="color:#ff7b72;font-weight:bold">&amp;</span>entry<span style="color:#a5d6ff">.453701871</span><span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">2200</span><span style="color:#ff7b72;font-weight:bold">-</span><span style="color:#a5d6ff">04</span><span style="color:#ff7b72;font-weight:bold">-</span><span style="color:#a5d6ff">01</span><span style="color:#ff7b72;font-weight:bold">&amp;</span>entry<span style="color:#a5d6ff">.816962894</span><span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">13</span>:<span style="color:#a5d6ff">37</span>
</span></span></code></pre></li>
<li>Done! Visiting this in your browser (or sending a GET request to this) will now immediately submit a form entry.</li>
</ol>
<p><img src="/content/posts/ios-shortcuts/forms-prefill-guide.jpg" alt="">
<img src="/content/posts/ios-shortcuts/forms-copy-link.jpg" alt=""></p>
<h2 id="ios-shortcuts">iOS Shortcuts</h2>
<p>I use an iPhone, on which you can make use of the iOS shortcuts. I've never really used these before, but you can generally do quite a lot of useful things with them. Whether you actually want to spend a lot of time making these (on mobile) is another question though. The Shortcuts app is a valid contender for the &quot;top 10 most dogwater mobile UX apps&quot;. The drag and drop that sometimes breaks in addition with some variables not showing up unless you restart the app make it extremely infuriating to develop. But at least once it works, it works. Not that that is an entirely high bar to reach.</p>
<p>You can download the <a href="/content/posts/ios-shortcuts/submit.shortcut">shortcut here</a> if you want to use it. It requires the google forms URL and the above four entry.ids from above to set up fully. There are multiple ways to launch it. You could create a homescreen icon or a widget on your lockscreen. Or alternatively, you can assign a gesture (like clicking the lock button three times) to launch the shortcut.</p>
<p>As for how I actually use it, I don't have an all too strict system for naming my payments. Luckily I'm not an accountant and it's all just for my own two eyes. If I go to IKEA and buy a green chair for my room, I'd probably add it under &quot;green chair ikea&quot;. A small hint so that a few months or maybe a year down the line I can look at my expenses and be reminded of how I spent 200.- for that green clothes hanger in the corner of my room.</p>
<p>And that is how I have my finance/expenses tracking set up using just a Google Sheet for storing all the data, while having a shortcut on my phone to quickly add entries. I've since also created a few more shortcuts, albeit simpler. You can have them trigger when you open an app, so I have one that turns my screen grey when I open instagram, and then makes it colorful again when I close it. I leave the implementation as an exercise for the reader though (btw, the releavnt action is called &quot;Set Color Filters&quot;). Curious if you have any other shortcuts you use.</p>
<p><img src="/content/posts/ios-shortcuts/shortcut.jpg" alt=""></p>
]]></content:encoded>
</item>


 

<item>
<title>Mark&#39;s System Setups</title>
<link>https://markc.su/posts/my-tools</link>
<pubDate>Sun, 22 Feb 2026 11:20:00 &#43;0100</pubDate>
<guid>https://markc.su/posts/my-tools</guid>
<description>How I have my systems optimized for programming with little configuration</description>
<content:encoded><![CDATA[<p>Over the past years, during my studies, I've tried out a lot of tools and programs to find &quot;the best setup&quot; for myself. I've recently been asked about my setup a bunch and thought why not just write another blogpost about it I can direct people to.</p>
<p>Over time my mentality on what is important in a system has changed a lot. I used to enjoy procrastinating studies by spending absurd amounts of time configuring Neovim or my previous NixOS setup. But I now don't enjoy that anymore as it also distracts from the actual work I want to do (on my projects). I now only want things that <em>&quot;just work&quot;</em> with mostly the default settings. I want to be able to get to work and not have to configure random things all the time. But I enjoy having the option to still customize my system however I want without having the OS restrict me.</p>
<h1 id="table-of-contents">Table of Contents</h1>
<ul>
<li><a href="#table-of-contents">Table of Contents</a></li>
<li><a href="#system-setup">System Setup</a>
<ul>
<li><a href="#systems">Systems</a></li>
<li><a href="#os--distro">OS / Distro</a></li>
<li><a href="#package-management">Package Management</a></li>
<li><a href="#development-tools">Development Tools</a></li>
<li><a href="#dotfiles">Dotfiles</a></li>
<li><a href="#editor">Editor</a></li>
<li><a href="#version-control">Version Control</a></li>
</ul>
</li>
<li><a href="#conclusion">Conclusion</a></li>
<li><a href="#footnotes">Footnotes</a></li>
</ul>
<h1 id="system-setup">System Setup</h1>
<h2 id="systems">Systems</h2>
<p>I use two systems: A <a href="https://www.tuxedocomputers.com/">Tuxedo</a> laptop and a full AMD CPU+GPU self-built desktop gaming PC.</p>
<p>On both systems I have 64GB ram. It's personally the minimum amount of RAM that I now get for my systems, not because I <em>need</em> the RAM, but because it was a relatively cheap (before the RAM price spike) upgrade you could do to your PC and then it's a resource that you will never struggle with or be bottlenecked by. Granted I also don't do any ML.</p>
<p>I am fully satisfied with my desktop. I haven't done a single bit of ML since I have it so I can't tell you how good cuda(-replacements) are nowadays. But as for daily use and gaming the only issues I've had which were due to it being AMD were that the GPU sometimes crashed for three specific games. Underclocking the GPU to 95% fixed any crashes.</p>
<p>The Tuxedo laptop is a bit of a mixed bag. Performance is great, but the touchpad is actually ass and sometimes starts becoming a bit unresponsive on fine movements (reloading the touchpad kernel module fixes it but I haven't been able to pinpoint where this issue comes from exactly and how to fix it. If you have an idea please hit me up.). The battery is not the best, but to be honest I can just charge it wherever I am so it's not really something I mind a lot. I don't do any very long trips without a power bank on hand or a plug available in the train.</p>
<h2 id="os--distro">OS / Distro</h2>
<p>Both on my laptop and desktop I daily-drive Linux. On both I've installed <a href="https://endeavouros.com/">EndeavourOS</a>. Here, I'll say it, EndeavourOS is the best Linux distro out there if you just want a working system without straight up bloating your system. On my desktop I used to run Windows. I've now replaced it with EndeavourOS running KDE Plasma and it all works without having to endlessly configure anything. Everything you need is preinstalled and the feel is extremely familiar if you come from Windows. Everything you expect to <em>just work</em> from an OS just works; printers, drivers, usable settings UI, etc. The install processes is a straightforward UI where you easily click yourself through.</p>
<p>As a note, I mainly use my systems for programming and gaming, while most general work I do is in browsers (photopea, google docs, etc.) and therefore OS-agnostic. I don't really work with excel/docx files locally (other than maybe having to read one once in a blue moon) and mainly use google sheets and docs if required. Installing Steam and playing some games all just worked with no hiccups on my desktop.</p>
<p>EndeavourOS is Arch-based, so you have the powerful AUR (package repository) at your fingertips, which is superior to homebrew or Nix (Nix theoretically has more packages if you only look at the numbers, but if you've used Nix you'll know that it can often happen that some random library or tool simply doesn't exist on Nix yet, while on Arch you practically never face the problem of a package not existing.).</p>
<p>As for my laptop, I'm a big advocate for tiling window managers on laptops. On my desktop I would never install a tiling window manager. I have two monitors and a gaming mouse that I use to flick-shot all my windows to the correct positions. On a laptop, the screen real estate is a lot more limiting though and I also only ever use my laptop with the touchpad as I'm not going to carry around a mouse everywhere. There, I want a system that saves me the work of having to tediously move around windows with the touchpad. The solution? A tiling window manager!</p>
<p>Out of the box, EndeavourOS can install <em>standard</em> window managers like Sway/i3. By &quot;standard&quot; I mean the type of tiling window managers that simply split your screen and allows you to move around and resize them on the screen using keybinds (i3/sway/hyprland). My main gripe with such window managers though is the annoyance that comes when all the windows are constantly resizing when a browser or something like Anki opens a new temporary window and it is then closed again. If I have a browser open and am on some website that cares too much about the size of my browser, the moment I try to add a new card to Anki (which opens a new window), all my windows, including the browser, are then squished and messed up until I fix up the layout of my screen.</p>
<p>The solution to that is a different type of tiling window manager. The one I use is called <a href="https://github.com/YaLTeR/niri">Niri</a>. It is a tiling window manager that provides infinite horizontally-scrolling workspaces.
Opening a new window will never resize any of the other windows on the workspace. Instead, it simply shifts over the workspace as if you are on an infinitely long strip of windows.</p>
<p>So I instead also installed EndeavourOS with KDE on my laptop just to have a working system for the start. But I then used <a href="https://github.com/AvengeMedia/DankMaterialShell">DankMaterialShell</a> (DMS) to install both Niri and pre-configured <a href="https://quickshell.org/">Quickshell</a> to have all the fancy status bars and widgets out of the box.
It's quite a whack name, but it's an insane project and flies quite under the radar. People be glazing Omarchy while being completely unaware of the EndeavourOS+DMS combo. DMS provides the desktop feel with an easy-to-use settings-UI while supporting Niri out of the box.</p>
<h2 id="package-management">Package Management</h2>
<p>Using NixOS for a few years got me used to keeping my system clear of random packages everywhere. I don't install development tools globally. I hate cluttering my global paths and system with random packages and libraries. I instead only install development tools specifically into the projects I need them in (more on that in the next section).</p>
<p>For globally installed packages, like editors or other general apps, I use paru (pacman-helper) to install them. I additionally often use paruz [1], a fuzzy package finder, to find the correct package if I'm unsure about the exact name of it.</p>
<p>Sometimes I install something just to quickly test it out, but I don't actually need it lying around. Or sometimes I install certain tools I only require for a course and would like to delete after the course is finished. That is where <code>aconfmgr</code> comes in. I already have a blogpost on it <a href="/posts/tidy-system-with-aconfmgr">here</a>. But tl;dr: It keeps track of what packages I install and forces me to organize my installed packages so I know <em>why</em> I installed some package and I can also quickly glance over certain categories to see what packages I could clean up and remove. The process of organizing can be done at a later point though, so if I just want to quickly install some package without having to fiddle around with my system config like with NixOS, I can just do that.</p>
<p>For example, this semester I had a <code>35-course-modules.sh</code> that included the specific programs two of my courses required:</p>
<pre style="padding:10px;margin:20px 0 20px 0;border-radius:10px;box-shadow:5px 5px 10px rgba(0, 0, 0, 0.3);overflow:auto;color:#e6edf3;background-color:#0d1117;"><code><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># Applied Security Lab</span>
</span></span><span style="display:flex;"><span>AddPackage --foreign kathara <span style="color:#8b949e;font-style:italic"># A lightweight container-based network emulation tool.</span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># Security of Wireless Networks</span>
</span></span><span style="display:flex;"><span>AddPackage gnuradio-companion <span style="color:#8b949e;font-style:italic"># Signal processing runtime and signal processing software development toolkit (GUI)</span>
</span></span></code></pre><p>At the end of the semester I could then simply delete these knowing I don't need them for anything else. Having my list of packages organized and commented like this is a lot easier to manage than simply looking at the list of all explicitly installed packages with <code>pacman -Qe</code> or similar.</p>
<h2 id="development-tools">Development Tools</h2>
<p>The workflow of not installing development packages globally is something I picked up and been doing religiously ever since I started using NixOS. I have a <a href="/posts/nix-direnv-setup">blogpost</a> on how I used to use <code>shell.nix</code> for my development tools. I've noticed that Nix is way too overkill for me and has the negatives of being too overcomplicated and pedantic for even the most minor tasks. I want to be developing my projects, not my devtool setup. The fact that I couldn't even execute some <em>quick</em> <a href="https://docs.astral.sh/uv/guides/tools/">uvx</a> command on NixOS because the binary it tried to execute used dynamic linking, which doesn't work on NixOS, is what eventually got me to ragequit and change to EndeavourOS after almost 3 years of using Nix.</p>
<p>The main requirement is that I want to be able to work on multiple projects with maybe overlapping tools (albeit with different versions) and I don't want a change in one project to affect the tooling in another project. For example, I might have an older project that requires nodejs 18 to run but want to use nodejs 24 on a newer project simultaneously. For node, I could use <a href="https://github.com/nvm-sh/nvm">nvm</a> or <a href="https://github.com/tj/n">n</a> to select the correct version manually every time I work on a specific project. But I need the same for any type of development tool or language. I don't want to have to install tens of tools for managing the versions of all my devtools. And I also don't want to constantly have to remember to switch around the tool versions when I switch over to another project. So I need a way to set up project-specific environments and I want a single tool to manage all my tools.</p>
<p>A third requirement is that I want to be able to easily clean up tools that I don't need anymore.</p>
<p>For quite a few months I've been trying to get devcontainers to work. And while devcontainers are quite a nice way to set up a clean dev environment, it brings with it a lot of negatives like permission problems, making it unnecessarily hard to use my shell and tool configurations, slow startup when opening the project, as well as actual feature development being quite stale. I eventually switched over to using <a href="https://podman.io/">podman</a> for devcontainers, because it didn't change ownership of all my files in a directory to root. This way I could at least continue to use git outside of the devcontainer without having to constantly <code>chown -R</code> my project directory.</p>
<p>After enough annoyances with devcontainers, I finally decided to try out <a href="https://mise.jdx.dev/">mise</a>. I had a few doubts at first, but they were quickly swept away when I started using it and noticed how it can somehow just do everything I need it to. To start using mise, after entering a project directory, you can simply execute e.g. <code>mise use node@24</code>, which adds <code>node@24</code> to the local <code>mise.toml</code> configuration as well as also installing node itself with the right version into the current shell environment. Extremely pain-free and fast. You can also install packages directly from cargo (with <code>mise use cargo:...</code>), npm, go, git, or even just install binaries directly. All while not being overly pedantic about EVERY. SINGLE. THING.</p>
<p>PS: If you don't want to push the <code>mise.toml</code> file and don't want to add it to the <code>.gitignore</code>, you can add it to your <a href="https://docs.github.com/en/get-started/git-basics/ignoring-files#excluding-local-files-without-creating-a-gitignore-file"><code>.git/info/exclude</code></a>. But you can also for example push the <code>mise.toml</code> with the devtools everybody needs, and then create a <code>mise.local.toml</code> which is in the gitignore, with solely your own local devtools.</p>
<p>If you have mise set up in your shell config with <code>mise activate</code>, it will automatically download and add tools your shell environment when you enter a project directory, while removing them again when you leave the directory. My workflow when setting up a new project with mise is to simply enter the directory, type <code>mise use zig@</code>, tap tab twice for autocomplete to kick in, up arrow to jump to the most up to date version, and then I simply select whatever new version I want. For languages, I'll fixate the version (to at least the major version), while for less important tools like <a href="https://just.systems/">just</a> I'll use <code>@latest</code>.</p>
<p><img src="/content/posts/my-tools/mise-use-zig.png" alt=""></p>
<p>PS: Mise also allows for setting environment variables either one-by-one or by directly giving the path to an environment variable file. Extremely nice when you're working on a project that has a local docker-compose setup with a lot of docker-network specific environment variables, but you also want to be able to develop locally outside of the docker network, meaning you would need to change some environment variables like <code>POSTGRES_HOST</code> from <code>postgres</code> to <code>localhost</code>. Mise allows you to read a docker environment file (let's call it <code>.env.docker</code>), but then simply override a few of those variables without any hassle:</p>
<pre style="padding:10px;margin:20px 0 20px 0;border-radius:10px;box-shadow:5px 5px 10px rgba(0, 0, 0, 0.3);overflow:auto;color:#e6edf3;background-color:#0d1117;"><code><span style="display:flex;"><span><span style="color:#ff7b72;font-weight:bold">[</span>env<span style="color:#ff7b72;font-weight:bold">]</span>
</span></span><span style="display:flex;"><span>_.file <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">&#34;.env.docker&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#79c0ff">POSTGRES_HOST</span> <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">&#34;localhost&#34;</span>
</span></span></code></pre><p>If you're interested, you can view this exact example more concretely on the <a href="https://gitlab.ethz.ch/vseth/sip-com-apps/community-solutions/-/blob/master/mise.toml">VIS Community Solutions repo</a>.</p>
<p>To summarize, I personally find mise one of the best devtools and a must-have if you tend to work on multiple projects with different tech-stacks.</p>
<h2 id="dotfiles">Dotfiles</h2>
<p>Managing my config files (dotfiles) is something I still can see a lot of improvement in. At first I used a <code>--bare</code> git repository. It is very simple to set up and use. But it quickly becomes messy, since I have some dotfiles (like aconfmgr config files) that are vastly different on my KDE desktop and my DMS/Niri laptop. That forced me to maintain two separate branches of my dotfiles, which then makes it extremely annoying to share dotfiles between the two systems.</p>
<p>Because of that, I instead use <a href="https://github.com/SuperCuber/dotter">dotter</a> to manage my dotfiles. It has two main features I need:</p>
<ol>
<li>By default any config files you manage are symlinked to whatever path you define. This then allows you to store your dotfiles repository wherever you want, while allowing you directly (or something like a settings app) to edit the original (<code>~/.config/...</code>) path as normal.</li>
<li>It allows for adding dotfiles conditionally as well as template any files you define. I use this to split what dotfiles are installed on my desktop and laptop. For example, I don't need any Niri or DankMaterialShell config on my desktop running KDE. Or since I use aconfmgr on both devices, I template the aconfmgr files to allow me to conditionally install certain programs, like Steam, solely on my desktop while not installing it on my laptop. Templating uses <a href="https://github.com/SuperCuber/dotter/wiki/2.-Symbolic-links-and-Templates#templating">handlebars</a> allowing for operations like so:</li>
</ol>
<pre style="padding:10px;margin:20px 0 20px 0;border-radius:10px;box-shadow:5px 5px 10px rgba(0, 0, 0, 0.3);overflow:auto;color:#e6edf3;background-color:#0d1117;"><code><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># {{#if laptop}}</span>
</span></span><span style="display:flex;"><span>AddPackage thunar
</span></span><span style="display:flex;"><span>AddPackage niri
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># {{#else if desktop}}</span>
</span></span><span style="display:flex;"><span>AddPackage dolphin
</span></span><span style="display:flex;"><span>AddPackage steam
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># {{/if}}</span>
</span></span></code></pre><p>I don't have my dotfiles repository public as of now though, since I have some secrets and slightly confidential files (ssh, kubectl, etc.) that I don't want to publish without first encrypting. I'm still on the lookout for a clean solution for that and open for any suggestions. Or another alternative is to simply keep the repository private as it is now.</p>
<h2 id="editor">Editor</h2>
<p>I use Zed, LazyVim, VSCode (in order of usage) as my editors. Zed is currently my main editor I use for any project. It has some flaws. Like being quite uncustomizable, having bad UX surrounding extensions/LSPs, as well as sometimes having some visual bugs on my laptop. But it is extremely fast, has a minimalistic UI fully controllable with the keyboard whenever desired, git diff viewer, and a debugger (which I frankly haven't used <em>yet</em>). I also use it mostly with all default settings, other than a few keybinds like switching tabs or opening/closing the terminal [3].</p>
<p>For smaller things and anything in the terminal, I use Neovim with <a href="https://www.lazyvim.org/">LazyVim</a>. Again going along with the &quot;no-setup&quot; route since I really don't want to be spending my time configuring Neovim anymore. I once forced myself to only use Neovim for half a year for all my coding. I set everything up from scratch and had it be basically everything I wanted, but at the end of the day mouse support is still bad and it being in the terminal drastically restricts it with any UI/UX choices. I now solely use vim for all my quick terminal file edits and lazyvim is a nice way to quickly have a bunch of small must-haves and nice visuals.</p>
<p>The sole settings I've added to lazyvim is the <code>&lt;leader&gt;y</code> keymap to copy something into my system clipboard instead of the vim register. Same goes for paste and delete.</p>
<pre style="padding:10px;margin:20px 0 20px 0;border-radius:10px;box-shadow:5px 5px 10px rgba(0, 0, 0, 0.3);overflow:auto;color:#e6edf3;background-color:#0d1117;"><code><span style="display:flex;"><span>vim.opt.clipboard <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">&#34;&#34;</span>
</span></span><span style="display:flex;"><span>vim.keymap.set(<span style="color:#a5d6ff">&#34;n&#34;</span>, <span style="color:#a5d6ff">&#34;&lt;leader&gt;y&#34;</span>, <span style="color:#a5d6ff">&#39;&#34;+y&#39;</span>)
</span></span><span style="display:flex;"><span>vim.keymap.set(<span style="color:#a5d6ff">&#34;v&#34;</span>, <span style="color:#a5d6ff">&#34;&lt;leader&gt;y&#34;</span>, <span style="color:#a5d6ff">&#39;&#34;+y&#39;</span>)
</span></span><span style="display:flex;"><span>vim.keymap.set(<span style="color:#a5d6ff">&#34;n&#34;</span>, <span style="color:#a5d6ff">&#34;&lt;leader&gt;d&#34;</span>, <span style="color:#a5d6ff">&#39;&#34;+d&#39;</span>)
</span></span><span style="display:flex;"><span>vim.keymap.set(<span style="color:#a5d6ff">&#34;v&#34;</span>, <span style="color:#a5d6ff">&#34;&lt;leader&gt;d&#34;</span>, <span style="color:#a5d6ff">&#39;&#34;+d&#39;</span>)
</span></span><span style="display:flex;"><span>vim.keymap.set(<span style="color:#a5d6ff">&#34;n&#34;</span>, <span style="color:#a5d6ff">&#34;&lt;leader&gt;p&#34;</span>, <span style="color:#a5d6ff">&#39;&#34;+p&#39;</span>)
</span></span><span style="display:flex;"><span>vim.keymap.set(<span style="color:#a5d6ff">&#34;v&#34;</span>, <span style="color:#a5d6ff">&#34;&lt;leader&gt;p&#34;</span>, <span style="color:#a5d6ff">&#39;&#34;+p&#39;</span>)
</span></span></code></pre><p>VSCode is currently my backup editor for when I need some specific feature I can't seem to find on Zed or generally when I need an editor I know just works. I'm a fan of how powerful VSCode extensions are, but when working with a lot of different tools and languages, each requiring a different set of extensions, it quickly slows down VSCode itself. You can choose to only install extensions on a per-project basis, but that feature is still quite clunky and quite an afterthought. I wish I could have an almost empty VSCode with only a handful of global extensions, and then define in a file in the project directory what extensions should be used/installed when working in this directory. Devcontainers allow for that, but as mentioned previously that brings with itself all other sorts of problems. That is why I'm fond of Zed right now, because while the extensions themselves are somewhat limiting, they're written in rust and don't slow down the startup and editor so much.</p>
<p>One thing with the mise workflow is that for the environment to be properly accepted by the three editors, you need to first enter the specific project directory and activate the mise environment, before then launching the editor of your choice. I don't open my editor and afterwards proceed to open the directory from there, since that does not set up the environment correctly. But I also don't want to have to tediously always have to open a terminal, navigate to the project and run <code>nvim .</code>/<code>code .</code>/<code>zeditor .</code>. For that I have a popup that launches with Super+P. I can then select the project directory I want, it <code>cd</code>s into the directory, executes <code>mise x -- zeditor .</code> and lets me correctly use the the environment in my editor [2]. More details on the popup thing can be found in my <a href="/posts/popup-tools">popup blogpost</a>. I've configured the keybind and popup to work on both my KDE and Niri systems.</p>
<h2 id="terminal">Terminal</h2>
<p>For my terminal I use <a href="https://github.com/kovidgoyal/kitty">kitty</a>. A simple and fast terminal. I don't use the features like tabbing or window splitting. I have my tiling window manager for that.</p>
<p>As for my shell, I use <a href="https://fishshell.com/">fish</a> which looks and behaves very nicely right out of the box. I have my terminal set up with three key features to aid in my productivity:</p>
<ul>
<li>
<p>For quickly jumping to any directory I use <a href="https://github.com/junegunn/fzf">fzf</a>. With <code>Alt+C</code> I get the fzf popup allowing me to search and instantly jump to any directory. Alternatively I can also use <code>Ctrl+T</code> in the middle of writing a command to fill in a filename using fuzzy matching.</p>
<pre style="padding:10px;margin:20px 0 20px 0;border-radius:10px;box-shadow:5px 5px 10px rgba(0, 0, 0, 0.3);overflow:auto;color:#e6edf3;background-color:#0d1117;"><code><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># .config/config.fish</span>
</span></span><span style="display:flex;"><span>fzf --fish | source
</span></span><span style="display:flex;"><span>set -gx FZF_ALT_C_COMMAND <span style="color:#a5d6ff">&#39;fd --type d&#39;</span>
</span></span></code></pre></li>
<li>
<p>One of the greatest tools I've added to my tool box is <a href="https://github.com/atuinsh/atuin">atuin</a>. It allows me to synchronize my terminal history between my laptop/desktop while also providing a fancier way to fuzzy search my commands history with <code>Ctrl+R</code>. I have it disabled so it doesn't trigger on arrow-up, as that messes with the &quot;command context&quot; I keep of my terminal windows where I expect arrow-up to only use the local history.</p>
<pre style="padding:10px;margin:20px 0 20px 0;border-radius:10px;box-shadow:5px 5px 10px rgba(0, 0, 0, 0.3);overflow:auto;color:#e6edf3;background-color:#0d1117;"><code><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># .config/config.fish</span>
</span></span><span style="display:flex;"><span>atuin init fish --disable-up-arrow | source
</span></span></code></pre></li>
<li>
<p>On kitty I also have a slight change. With <code>Super+Shift+Enter</code> I can instantly open up another shell in the same directory or ssh connection.</p>
<pre style="padding:10px;margin:20px 0 20px 0;border-radius:10px;box-shadow:5px 5px 10px rgba(0, 0, 0, 0.3);overflow:auto;color:#e6edf3;background-color:#0d1117;"><code><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># .config/kitty/kitty.conf</span>
</span></span><span style="display:flex;"><span>map super+shift+enter new_os_window_with_cwd
</span></span></code></pre></li>
</ul>
<h2 id="version-control">Version Control</h2>
<p>I don't use the standard git CLI anymore and have been exclusively using <a href="https://docs.jj-vcs.dev/">jj</a> (jujutsu) the past weeks. <a href="https://steveklabnik.github.io/jujutsu-tutorial/introduction/introduction.html">Steve's Jujutsu Tutorial</a> is a great place to learn how to use it.</p>
<p>The main thing about jj is that it's a different workflow and way of thinking about git, while being fully compatible with the git backend. This allows you to easily use jj in place of git without everyone else in a project also having to immediately swap. jj makes jumping between commits and branches extremely easy and never requires you to ever explicitly git stash. It also makes rebasing a breeze and if you ever mess something up, you can look at a long log of all operations you executed and revert any change you want.</p>
<p>I've always thought that git isn't the perfect version control system and there's room for improvement. jj also isn't perfect, since it also uses the git backend after all, but it is a nice upgrade. jj can handle the same operations (if not more) as git, while being simpler to use. And in the case you need a git-specific feature, like adding tags (doesn't make sense in the jj sense), you can still always just use specific git commands interchangeably with jj.</p>
<h1 id="conclusion">Conclusion</h1>
<p>The post turned out a bit wordier than I'd hoped. But I hope I could maybe motivate you to try out some new tool or set up your system to allow for a more seamless workflow.</p>
<h1 id="footnotes">Footnotes</h1>
<p>[1] Paruz used to be available on the <a href="https://aur.archlinux.org/packages/paruz-git">AUR</a>. But the repository has been removed on Github and the version still available on AUR uses a broken paru version. I instead use the following fish function which allows for the same fuzzy matching of packages, but simply keep the name because it's fitting:</p>
<pre style="padding:10px;margin:20px 0 20px 0;border-radius:10px;box-shadow:5px 5px 10px rgba(0, 0, 0, 0.3);overflow:auto;color:#e6edf3;background-color:#0d1117;"><code><span style="display:flex;"><span><span style="color:#ff7b72">function</span> paruz
</span></span><span style="display:flex;"><span>    set -l pkgs <span style="color:#ff7b72;font-weight:bold">(</span>
</span></span><span style="display:flex;"><span>      paru -Sl |<span style="color:#79c0ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff"></span>      sed -e <span style="color:#a5d6ff">&#34;s: :/:; s/ unknown-version//; /installed/d&#34;</span> |<span style="color:#79c0ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff"></span>      fzf --multi --ansi --preview <span style="color:#a5d6ff">&#34;paru -Si {1}&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72;font-weight:bold">)</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">if</span> test -z <span style="color:#a5d6ff">&#34;</span><span style="color:#79c0ff">$pkgs</span><span style="color:#a5d6ff">&#34;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">return</span>
</span></span><span style="display:flex;"><span>    end
</span></span><span style="display:flex;"><span>    echo <span style="color:#79c0ff">$pkgs</span> | awk <span style="color:#a5d6ff">&#39;{print $1}&#39;</span> | xargs -ro paru -S
</span></span><span style="display:flex;"><span>end
</span></span><span style="display:flex;"><span>
</span></span></code></pre><p>[2] The following fish function is what allows me to fuzzy match the directory I want to enter (using fzf) and then instantly open the editor in it with the mise environment tools loaded (if existing, the editor still opens even if no <code>mise.toml</code> is located in the given directory).</p>
<pre style="padding:10px;margin:20px 0 20px 0;border-radius:10px;box-shadow:5px 5px 10px rgba(0, 0, 0, 0.3);overflow:auto;color:#e6edf3;background-color:#0d1117;"><code><span style="display:flex;"><span><span style="color:#ff7b72">function</span> zed_open
</span></span><span style="display:flex;"><span>    set -l directory <span style="color:#ff7b72;font-weight:bold">(</span>
</span></span><span style="display:flex;"><span>      fd . --type d ~/Documents |<span style="color:#79c0ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff"></span>        xargs ls -td |<span style="color:#79c0ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff"></span>        sed <span style="color:#a5d6ff">&#39;s|/$||&#39;</span> |<span style="color:#79c0ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff"></span>        sed <span style="color:#a5d6ff">&#39;s|^/home/mark/Documents/|~/D/|&#39;</span> |<span style="color:#79c0ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff"></span>        awk -F/ <span style="color:#a5d6ff">&#39;{OFS=&#34;/&#34;; last=$NF; $NF=&#34;\033[1;34m&#34;last&#34;\033[0m&#34;; print}&#39;</span> |<span style="color:#79c0ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff"></span>        fzf --ansi --prompt<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">&#34;Zed Recent&gt; &#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72;font-weight:bold">)</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">if</span> test -n <span style="color:#a5d6ff">&#34;</span><span style="color:#79c0ff">$directory</span><span style="color:#a5d6ff">&#34;</span>
</span></span><span style="display:flex;"><span>        set -l clean_dir <span style="color:#ff7b72;font-weight:bold">(</span>string replace -ra <span style="color:#a5d6ff">&#39;\e\[[0-9;]*m&#39;</span> <span style="color:#a5d6ff">&#39;&#39;</span> -- <span style="color:#a5d6ff">&#34;</span><span style="color:#79c0ff">$directory</span><span style="color:#a5d6ff">&#34;</span><span style="color:#ff7b72;font-weight:bold">)</span>
</span></span><span style="display:flex;"><span>        set -l formatted <span style="color:#ff7b72;font-weight:bold">(</span>echo <span style="color:#a5d6ff">&#34;</span><span style="color:#79c0ff">$clean_dir</span><span style="color:#a5d6ff">&#34;</span> | sed <span style="color:#a5d6ff">&#39;s|^~/D/|/home/mark/Documents/|&#39;</span><span style="color:#ff7b72;font-weight:bold">)</span>
</span></span><span style="display:flex;"><span>        sh -c <span style="color:#a5d6ff">&#34;cd &#39;</span><span style="color:#79c0ff">$formatted</span><span style="color:#a5d6ff">&#39; &amp;&amp; mise x -- zeditor .&#34;</span>
</span></span><span style="display:flex;"><span>    end
</span></span><span style="display:flex;"><span>end
</span></span><span style="display:flex;"><span>
</span></span></code></pre><p>[3] Because of browsers I'm used to <code>Ctrl+Tab</code> moving across tabs in the order they're open. Editors like VSC/Zed default to <code>Ctrl+Tab</code> switching tabs by recency which I find quite annoying/unintuitive. In Zed I now use these keybinds:</p>
<pre style="padding:10px;margin:20px 0 20px 0;border-radius:10px;box-shadow:5px 5px 10px rgba(0, 0, 0, 0.3);overflow:auto;color:#e6edf3;background-color:#0d1117;"><code><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#7ee787">&#34;context&#34;</span>: <span style="color:#a5d6ff">&#34;Pane&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#7ee787">&#34;bindings&#34;</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#7ee787">&#34;ctrl-tab&#34;</span>: <span style="color:#a5d6ff">&#34;pane::ActivateNextItem&#34;</span>,
</span></span><span style="display:flex;"><span>  },
</span></span><span style="display:flex;"><span>}<span style="color:#f85149">,</span>
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#7ee787">&#34;context&#34;</span>: <span style="color:#a5d6ff">&#34;Pane&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#7ee787">&#34;bindings&#34;</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#7ee787">&#34;ctrl-shift-tab&#34;</span>: <span style="color:#a5d6ff">&#34;pane::ActivatePreviousItem&#34;</span>,
</span></span><span style="display:flex;"><span>  },
</span></span><span style="display:flex;"><span>}<span style="color:#f85149">,</span>
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#7ee787">&#34;context&#34;</span>: <span style="color:#a5d6ff">&#34;Workspace&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#7ee787">&#34;bindings&#34;</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#7ee787">&#34;ctrl-tab&#34;</span>: <span style="color:#79c0ff">null</span>,
</span></span><span style="display:flex;"><span>  },
</span></span><span style="display:flex;"><span>}<span style="color:#f85149">,</span>
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#7ee787">&#34;context&#34;</span>: <span style="color:#a5d6ff">&#34;Workspace&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#7ee787">&#34;bindings&#34;</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#7ee787">&#34;ctrl-shift-tab&#34;</span>: <span style="color:#79c0ff">null</span>,
</span></span><span style="display:flex;"><span>  },
</span></span><span style="display:flex;"><span>}
</span></span></code></pre><pre><code>
</code></pre>
]]></content:encoded>
</item>


 

<item>
<title>TODO: Create blogpost about todo and idea management</title>
<link>https://markc.su/posts/todos</link>
<pubDate>Sun, 08 Feb 2026 12:37:25 &#43;0100</pubDate>
<guid>https://markc.su/posts/todos</guid>
<description>Managing a lot of todos and ideas can quickly get unruly. Super Productivity allows me to more easily manage them and also has me being more productive.</description>
<content:encoded><![CDATA[<p>Ever since stumbling on the fact that you can get a year of Google's Gemini for free as a student, I've been using it extensively for a lot of things. Most notably, I've been abusing its &quot;Deep Research&quot; mode like crazy.</p>
<p>For those in the unknown, it is an extremely powerful way to find something on The Great Internets. I use it extensively for finding apps, libraries, or even just book recommendations for further reading. The report it generates I couldn't give a rats ass about. It's just some AI slop report after all and there's little I hate more than overconfidently wrong and overly-wordy LLM slop. But the real and slightly hidden power is when you look at the sources and the &quot;Thinking&quot; steps (&quot;Seven Thinking Steps&quot; type vibe).</p>
<p>Instead of having to endlessly google keyword after keyword and looking through reddit posts trying to see what makes sense, I hand that part completely over to Gemini and have it nicely gather all links that are even just slightly relevant. Gemini finds me the needles in the haystack, but I'll take it from there and determine what is actually good and what is not.</p>
<p>To end this somewhat long preamble, this has now often been my approach for finding tools, libraries, or even interesting books to read.</p>
<h2 id="super-productivty">Super Productivty</h2>
<p>That is how I then stumbled upon <a href="https://super-productivity.com/">Super Productivity</a>, an insanely well-made open source and free todo-management app.</p>
<p><em>TL;DR: I use it for all my todos. End of blogpost.</em></p>
<h2 id="but-why">But why?</h2>
<p><img src="/content/posts/todos/but-why.gif" alt=""></p>
<p>In the past years, I've always used Google Tasks. I had the Tasks app on my phone, which allowed me to have a homescreen widget (iOS) to quickly see and add any todos I had. Or alternatively, I also added any (project) ideas I had for future me to hopefully implement someday.</p>
<p>You can create different tabs to slightly separate your todos, but at the end of the day, it's quite a basic way of managing todos. I am always trying to slightly optimize different things in my life, so there was always the looming question if this is really the best way to do it.</p>
<p>On my phone, I have the RSS reader app (Reeder) set up to show me posts from hacker news, lobste.rs, and my Github feed to maybe stumble upon cool articles or interesting projects. One morning while sipping my horribly failed latte art, I stumbled upon a really interesting blogpost.</p>
<p><a href="https://jeffhuang.com/productivity_text_file/">My productivity app is a never-ending .txt file</a> talks about how the author has been using a text file the past 14 years for his daily todos. A lil' too hardcore for me. But for me, the real gold of the article was one key point:</p>
<ul>
<li>Using a calendar for all todos and ideas.</li>
</ul>
<h2 id="assign-a-date">Assign a date</h2>
<p>Have a new todo or idea? Don't just throw it on an endless list of todos you can't be assed to complete. Instead, assign a date to the todo for when you want to work on it or even just think of it again.</p>
<p>To cut to the chase, I knew I needed to reform my todo management. After a little digging with big brother Gemini, I then stumbled upon Super Productivity. It supports a bunch of features to make it extremely easy to oversee a lot of todos. And it also supports keybinds and shortcuts for the poweruser addicts.</p>
<p>I've added it as my home website when I open my browser to always see my todos there and I also added it as a homescreen PWA on my phone to add ideas while I'm out and about.</p>
<h2 id="workflow-for-new-todos">Workflow for new todos</h2>
<p>Whenever I remember I need to do something or I generally just have an idea, I follow these steps:</p>
<ol>
<li>Create a task and put a short description of what it is I wanna do. More details are better than too short and cryptic messages. If I see the todo a few weeks down the line and dunno what it's about, the effort of adding it was for nothing.</li>
<li>Add a date for when I want to work on the task or generally just think about it again. I usually throw my todos a few days into the future when I think I have time again. I'll often look at my todos for the upcoming days in the &quot;Planner&quot; tab
and reorganize/spread them out a bit so my todos for a day are actually feasible. A shortcut for adding a date when creating a task is typing something like <code>@tmr</code> or <code>@26feb</code>.</li>
<li>Assign a project to the task. I currently have four projects (not including the default &quot;Inbox&quot;). The projects consist of &quot;general ETH stuff&quot;, &quot;general programming stuff&quot;, &quot;VIS related stuff&quot;, and for bigger tasks with a lot of todos I might create a separate project (have one for <a href="https://github.com/markbeep/AudiobookRequest">ABR</a>). Things not in those categories, like reminders for me to do Steuererklärung, I leave uncategorized, which implicitly places them into the separate &quot;Inbox&quot; project. A shortcut for adding a project is to type <code>+prog</code> or <code>+eth</code>.</li>
<li>Add an estimate of how long the task will take me. This helps a lot in gauging when and where I can fit this task into my day. It also helps me sometimes get over the bump of not wanting to work on a task I dislike, knowing that it <em>only</em> takes 20 minutes, so I might as well do it now. Adding <code>2.5h</code> or <code>5m</code> anywhere in the task creation text adds a time estimate. As a small tip, you will probably massively underestimate how long you take at tasks at first. No harm in over-estimating or refining the estimate later on.</li>
<li>Sometimes, if it's related to some article or requires a bit more information, I'll add the link as an attachment to the task and/or add some more details to the task in the notes section.</li>
<li>Sync/save the changes so I don't lose them.</li>
</ol>
<h2 id="workflow-for-getting-into-a-working-flow">Workflow for getting into a working flow</h2>
<p>When I open SUP, I'll be greeted with all the tasks I assigned myself for today. Usually, I'll start with the task I vibe most with, while maybe reassigning some tasks to other days if I know I won't be working on those today anyway.</p>
<p>I've also been trying to use the in-app time tracking feature so I can better measure how long I actually take to complete tasks. This has made me realize that I massively misjudge how long I take for tasks, but slowly my estimates are starting to be more realistic.</p>
<p>At the end of the day, I'll try to use the &quot;Finish Day&quot; feature, which cleans up old tasks, as well as allows me to see what's coming up the next day.</p>
<h2 id="programming-todos-example">Programming TODOs Example</h2>
<p>For example, these are some todos I have for my programming project. I also have five todos in the backlog (at the very botom) which are just generally ideas that I'll <em>eventually</em> and <em>maybe</em> do, but don't have a date fixed for, nor do I actually expect to do them anytime soon.</p>
<p>I don't really make use of tags. I find it easier to just maybe add a small prefix like (<code>vvz:</code> for <a href="https://vvzapi.ch/">VVZ API</a>), which is just enough for me to easily identify what programming project the idea is for.</p>
<p><img src="/content/posts/todos/programming.png" alt=""></p>
<h2 id="finito">Finito</h2>
<p>Thanks for reading another one of my rambles. Mainly just wanted to share my discovery of the goated SUP with you. Hope I may have inspired you to check out Super Productivity (or maybe the Deep Research mode on Gemini).</p>
]]></content:encoded>
</item>


 

<item>
<title>Popup Tools</title>
<link>https://markc.su/posts/popup-tools</link>
<pubDate>Thu, 16 Oct 2025 13:13:43 &#43;0200</pubDate>
<guid>https://markc.su/posts/popup-tools</guid>
<description></description>
<content:encoded><![CDATA[<p>I'm a huge fan of using fuzzy matching for search. It's such a great solution for so many types of searches that is extremely underutilized. And for the terminal, <a href="https://github.com/junegunn/fzf">fzf</a> is always my goto. I've been using fzf to allow me to quickly jump to any directory in the terminal for years already. But only recently have I started creating small popup tools that I can activate using some keybind. Here I wanna quickly explain how I have it set up.</p>
<p>I use <a href="https://github.com/YaLTeR/niri">Niri</a> as my window manager. It's an amazing window manager I've really taken a liking to the past months. For some reason I have not released my blog post about it yet. So stay tuned. For this blog post, all you need to know is that Niri is a tiling window manager with some additional benefits I'll not go into here. In basics, that means that the window manager automatically splits up the screen so that each open window has a space somewhere. But you can also add a filter for certain types of programs to float around and not automatically get tiled. We'll use this to create our popup.</p>
<p>What I found was that it's extremely easy to create a quick CLI tool that pops up and allows you to select something. This saves me having to open the terminal and using a command there for things I just want to quickly execute and then have gone again. Let me give you an example.</p>
<h3 id="install-packages-quickly">Install packages quickly</h3>
<p>If I want to quickly install a package, what I can do, is simply press <code>Super+P</code>, which then opens my fuzzy finding package manager terminal window as a popup in the middle of my screen. I can now search for any packages I want with fuzzy matching. Additionally I also get all the relevant information like URL, author, last updated at, etc. to make sure I'm downloading the right package. Once the download is complete the popup nicely disappears and I can go on with my work.</p>
<p><img src="/content/posts/popup-tools/paruz-popup.png" alt=""></p>
<p>You can create most of this without any third-party dependencies, using just pacman/yay/paru. I'll also mention how to below. In the screenshot above, and because of straight-up laziness, I myself use paruz (the github repo has since been removed so I use my own function as shown below).</p>
<h3 id="launch-vsc-workspaces">Launch VSC Workspaces</h3>
<p>But why stay at only using it for installing packages? I'm a VSCode user, and on this system I use devcontainers for basically every project. I have more than enough RAM to handle Docker and it allows me to neatly manage all the dependencies without cluttering my whole system or having to write nix files.</p>
<p>Now one of the problems is, that when I want to open a specific devcontainer workspace, I first have to open VSCode and wait for it to load into the project. Probably I had some other devcontainer open, so I have to first wait until VSCode finishes initializing the dev docker container, before I can properly switch over to the workspace (using <code>Ctrl+R</code>) I was intending to go to.</p>
<p>So my task was to create some terminal script that allowed me to launch VSCode in a workspace. But now that always requires me to first open the terminal just to then open VSCode. So why not expand that into a popup just like I did with the package manager above.</p>
<p><img src="/content/posts/popup-tools/vsc-popup.png" alt=""></p>
<p>Now I can simply run <code>Super+P</code> and open a nice popup that allows me to pick the VSCode workspace I want to resume.</p>
<h3 id="quick-how-to">Quick How-to</h3>
<p>The config files I'll show are for my setup, so might have to be setup and translated (with gpt) into whatever you use:</p>
<ul>
<li>WM: Niri</li>
<li>Terminal: Kitty</li>
<li>Shell: Fish</li>
</ul>
<h4 id="wm">WM</h4>
<p>Add a window rule so that any apps opened with the ID <code>floating-popup</code> are not automatically tiled, but instead initially opened as a floating window:</p>
<pre><code class="language-kdl">// .config/niri/config.kdl
window-rule {
    // floating terminal popup (like paruz and vscode workspace finder)
    match app-id=&quot;floating-popup&quot;
    open-floating true
    default-window-height { fixed 400; }
    default-column-width { fixed 700; }
}
</code></pre>
<p>Then we add the keybinds to launch our scripts:</p>
<pre><code class="language-kdl">// .config/niri/config.kdl
Mod+P hotkey-overlay-title=&quot;Pacman Install&quot; { spawn &quot;bash&quot; &quot;/home/user/.../paruz-popup.sh&quot;; }
Mod+T hotkey-overlay-title=&quot;VSCode Workspace Launcher&quot; { spawn &quot;bash&quot; &quot;/home/user/.../vscode-popup.sh&quot;; }
</code></pre>
<h4 id="fish">Fish</h4>
<p>Now we come to the scripting part. Make sure to swap out the <code>kitty</code> terminal for whatever you use.</p>
<p>In the <code>paruz-popup.sh</code> I simply have the following:</p>
<pre style="padding:10px;margin:20px 0 20px 0;border-radius:10px;box-shadow:5px 5px 10px rgba(0, 0, 0, 0.3);overflow:auto;color:#e6edf3;background-color:#0d1117;"><code><span style="display:flex;"><span>kitty --class floating-popup -e bash -c <span style="color:#a5d6ff">&#39;paruz&#39;</span> &amp;
</span></span></code></pre><p>Alternatively, you can create your own &quot;paruz&quot; using pacman/yay/paru. Here's my fish function for it:</p>
<pre style="padding:10px;margin:20px 0 20px 0;border-radius:10px;box-shadow:5px 5px 10px rgba(0, 0, 0, 0.3);overflow:auto;color:#e6edf3;background-color:#0d1117;"><code><span style="display:flex;"><span><span style="color:#ff7b72">function</span> <span style="color:#d2a8ff;font-weight:bold">paruz</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">set</span> -l <span style="color:#79c0ff">pkgs</span> <span style="color:#ff7b72;font-weight:bold">(</span>
</span></span><span style="display:flex;"><span>      <span style="color:#d2a8ff;font-weight:bold">paru</span> -Sl <span style="color:#ff7b72;font-weight:bold">|</span><span style="color:#79c0ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff"></span>      <span style="color:#d2a8ff;font-weight:bold">sed</span> -e <span style="color:#a5d6ff">&#34;s: :/:; s/ unknown-version//; /installed/d&#34;</span> <span style="color:#ff7b72;font-weight:bold">|</span><span style="color:#79c0ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff"></span>      <span style="color:#d2a8ff;font-weight:bold">fzf</span> --multi --ansi --preview <span style="color:#a5d6ff">&#34;paru -Si {1}&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72;font-weight:bold">)</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">if</span> <span style="color:#ff7b72">test</span> -z <span style="color:#a5d6ff">&#34;</span><span style="color:#79c0ff">$pkgs</span><span style="color:#a5d6ff">&#34;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">return</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">end</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">echo</span> <span style="color:#79c0ff">$pkgs</span> <span style="color:#ff7b72;font-weight:bold">|</span> <span style="color:#d2a8ff;font-weight:bold">awk</span> <span style="color:#a5d6ff">&#39;{print $1}&#39;</span> <span style="color:#ff7b72;font-weight:bold">|</span> <span style="color:#d2a8ff;font-weight:bold">xargs</span> -ro paru -S
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">end</span>
</span></span></code></pre><p>And now we're already finished. With <code>Super+P</code> we can easily install what we want.</p>
<p>For the VSCode workspaces, you can find all the recent workspace paths in your <code>.config</code> directory:</p>
<pre style="padding:10px;margin:20px 0 20px 0;border-radius:10px;box-shadow:5px 5px 10px rgba(0, 0, 0, 0.3);overflow:auto;color:#e6edf3;background-color:#0d1117;"><code><span style="display:flex;"><span>find ~/.config/Code/User/workspaceStorage -type f <span style="color:#79c0ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff"></span>	-name <span style="color:#a5d6ff">&#34;workspace.json&#34;</span> <span style="color:#79c0ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff"></span>	-exec jq -r .folder <span style="color:#ff7b72;font-weight:bold">{}</span> +
</span></span></code></pre><p>Then using a fish function, the base directory is extracted and shown in the fzf menu:</p>
<pre style="padding:10px;margin:20px 0 20px 0;border-radius:10px;box-shadow:5px 5px 10px rgba(0, 0, 0, 0.3);overflow:auto;color:#e6edf3;background-color:#0d1117;"><code><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># .config/fish/functions/vscode_recent.fish
</span></span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"></span><span style="color:#ff7b72">function</span> <span style="color:#d2a8ff;font-weight:bold">vscode_recent</span> --description <span style="color:#a5d6ff">&#34;Open recent VSCode workspace via fzf&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># 1. Finds all workspace files
</span></span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"></span>    <span style="color:#8b949e;font-style:italic"># 2. Turn slashes into tabs (for better formatting in fzf)
</span></span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"></span>    <span style="color:#8b949e;font-style:italic"># 3. Add spaces to &#39;file:&#39; to align results with &#39;vscode-remote:&#39;
</span></span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"></span>    <span style="color:#8b949e;font-style:italic"># 4. Add color to remote/file type
</span></span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"></span>    <span style="color:#8b949e;font-style:italic"># 5. Show with fzf (only show uri type + basedir)
</span></span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"></span>    <span style="color:#8b949e;font-style:italic"># 6. Remove spaces
</span></span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"></span>    <span style="color:#8b949e;font-style:italic"># 7. Turn tabs back into slashes
</span></span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"></span>    <span style="color:#d2a8ff;font-weight:bold">find</span> .config/Code/User/workspaceStorage -type f -name <span style="color:#a5d6ff">&#34;workspace.json&#34;</span> <span style="color:#79c0ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff"></span>        -exec jq -r .folder <span style="color:#ff7b72;font-weight:bold">{}</span> + <span style="color:#79c0ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff"></span>        <span style="color:#ff7b72;font-weight:bold">|</span> <span style="color:#d2a8ff;font-weight:bold">sed</span> <span style="color:#a5d6ff">&#39;s/\//\t/g&#39;</span> <span style="color:#79c0ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff"></span>        <span style="color:#ff7b72;font-weight:bold">|</span> <span style="color:#d2a8ff;font-weight:bold">sed</span> <span style="color:#a5d6ff">&#39;s/file:/file:         /g&#39;</span> <span style="color:#79c0ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff"></span>        <span style="color:#ff7b72;font-weight:bold">|</span> <span style="color:#d2a8ff;font-weight:bold">sed</span> -E <span style="color:#a5d6ff">&#39;s/(.+:)/\x1b[1;34m\1\x1b[0m/g&#39;</span> <span style="color:#79c0ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff"></span>        <span style="color:#ff7b72;font-weight:bold">|</span> <span style="color:#d2a8ff;font-weight:bold">fzf</span> --ansi --delimiter<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#79c0ff">\t</span> --with-nth<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">1</span>,-<span style="color:#a5d6ff">1</span> --prompt<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">&#34;VSCode Workspaces &gt; &#34;</span> <span style="color:#79c0ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff"></span>        <span style="color:#ff7b72;font-weight:bold">|</span> <span style="color:#d2a8ff;font-weight:bold">sed</span> <span style="color:#a5d6ff">&#39;s/ //g&#39;</span> <span style="color:#79c0ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff"></span>        <span style="color:#ff7b72;font-weight:bold">|</span> <span style="color:#d2a8ff;font-weight:bold">sed</span> <span style="color:#a5d6ff">&#39;s/\t/\//g&#39;</span> <span style="color:#79c0ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff"></span>        <span style="color:#ff7b72;font-weight:bold">|</span> read -l uri
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">if</span> <span style="color:#ff7b72">test</span> -n <span style="color:#a5d6ff">&#34;</span><span style="color:#79c0ff">$uri</span><span style="color:#a5d6ff">&#34;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#d2a8ff;font-weight:bold">code</span> --folder-uri <span style="color:#79c0ff">$uri</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">end</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">end</span>
</span></span></code></pre><p>Placing this function into <code>.config/fish/functions/vscode_recent.fish</code> will allow you to execute it in the terminal and test it. Lastly, we just add the sh file that will launch it as a popup.</p>
<pre style="padding:10px;margin:20px 0 20px 0;border-radius:10px;box-shadow:5px 5px 10px rgba(0, 0, 0, 0.3);overflow:auto;color:#e6edf3;background-color:#0d1117;"><code><span style="display:flex;"><span>kitty --class floating-popup -e fish -c <span style="color:#a5d6ff">&#39;vscode_recent&#39;</span> &amp;
</span></span></code></pre><h3 id="looking-back">Looking back</h3>
<p>Now having written this blogpost, I just realized that both of the shell scripts are so short, I could just put them directly into the niri config without additional files. But oh well, that's for you to then do.</p>
<p>But also remember that fzf basically just takes in a list and allows you to fuzzy match over it. You can use this for anything you can think of that uses text.</p>
]]></content:encoded>
</item>


 

<item>
<title>Keeping the system tidy with aconfmgr</title>
<link>https://markc.su/posts/tidy-system-with-aconfmgr</link>
<pubDate>Mon, 29 Sep 2025 10:23:29 &#43;0200</pubDate>
<guid>https://markc.su/posts/tidy-system-with-aconfmgr</guid>
<description></description>
<content:encoded><![CDATA[<p>This is just a quick blogpost to share an amazing tool I just stumbled upon, called aconfmgr.</p>
<h2 id="wait-what-dont-i-use-nix">Wait what, don't I use nix?</h2>
<p>As some might know, the past years I was an avid NixOS user. I was attracted by the whole idea of configuring your system using some config files. The Nix language allows you to split up and organize your files however you wanted, which meant I had a separate <code>modules.nix</code> file just listing all my installed packages. Anytime I wanted to install something system-wide*, I had to add it to my <code>modules.nix</code>. This forced me to think about anything I wanted to install on my system with maybe a comment on why I installed it (i.e. required for X to work), so when I do my occasional pruning I knew if I could remove it.</p>
<p>This made me quite conservative on what I installed and hence mostly relied on installing packages only for a shell session instead of system-wide. Read my more than two year old blogpost about this <a href="/posts/nix-direnv-setup">here</a>. But this whole ordeal with a config file OS also forced me to be tidy about what resides on my system. My system was basically in a permanent clean state with no garbage packages anywhere.</p>
<p>I have recently started using a new laptop and with that I felt it was time to change things up again. I was also starting to get annoyed at simple things like binaries with shared libraries not working out of the box on NixOS (only took me almost 3 years). I initially went for an immutable fedora, but that wasn't able to fulfill the itch I was looking for. Upon stumbling upon the aforementioned tool, I knew exactly what distro I had to run.</p>
<h2 id="the-itch-fulfilled">The itch fulfilled</h2>
<p>I was looking for nix alternatives in terms of package management, curious to see what was out there. I knew about ansible and was even close to writing my own specific package management that automatically installed and exported packages in distroboxes using ansible, but luckily didn't pull through with it.</p>
<p>I then stumbled upon <a href="https://github.com/CyberShadow/aconfmgr">aconfmgr</a>. The basics are, it creates a diff of expected and installed packages and files which is all outputted into a new &quot;unsorted&quot; config file.</p>
<h2 id="the-workflow">The Workflow</h2>
<p>The idea with aconfmgr is &quot;install first, worry and sort later&quot;, which is a nice change to NixOS's approach of &quot;worry and sort first, install later&quot;.</p>
<p>So I install what I want as normal. Oh, I need X for this course. I can quickly install it and start using it. But let's say it didn't quite work and i also have to install the dependency D for X to work.</p>
<p>Afterwards I can then run <code>aconfmgr save</code> in the terminal and it will generate a &quot;unsorted&quot; config file which will then list X and D. The config file looks like this:</p>
<pre style="padding:10px;margin:20px 0 20px 0;border-radius:10px;box-shadow:5px 5px 10px rgba(0, 0, 0, 0.3);overflow:auto;color:#e6edf3;background-color:#0d1117;"><code><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># 99-unsorted.sh</span>
</span></span><span style="display:flex;"><span>AddPackage X
</span></span><span style="display:flex;"><span>AddPackage D
</span></span><span style="display:flex;"><span>AddPackage SomeGarbagePackage
</span></span></code></pre><p>I can then take X and D and sort them into one of my other config files together with a quick description of why I need it:</p>
<pre style="padding:10px;margin:20px 0 20px 0;border-radius:10px;box-shadow:5px 5px 10px rgba(0, 0, 0, 0.3);overflow:auto;color:#e6edf3;background-color:#0d1117;"><code><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># 40-course-modules.sh</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># Required for course Y HS2025</span>
</span></span><span style="display:flex;"><span>AddPackage X
</span></span><span style="display:flex;"><span>AddPackage D
</span></span></code></pre><p>I can also see that I seem to have installed some <code>SomeGarbagePackage</code>. I don't need it so I can either just delete it manually using my package manager or simply not sort it into any of my module files.</p>
<p>After sorting my packages I would then delete the unsorted config file (<code>99-unsorted.sh</code>) and then upon running <code>aconfmgr apply</code>, aconfmgr will automatically delete any packages I don't have in my config.</p>
<p>I then also add my aconfmgr config files to my gitified dotfiles so I can also see when I committed which line.</p>
<h2 id="and-theres-more">And there's more</h2>
<p><code>aconfmgr apply</code> doesn't only remove unlisted packages, it also adds uninstalled but listed packages. It also doesn't only track packages, but it also keeps track of files (like the config files in /etc).</p>
<p>So in theory you can use it to backup your system state and if you ever decide to reset your system, you can hop back with the correct packages and files.</p>
<p>But I don't really use it for that and mainly use it to nicely keep track of my packages.</p>
<p>Another con is that it only works for arch-based distros. I was open to distro-hopping anyway, so this fit quite well. So yes, I'm on Endeavour OS now.</p>
<p>Have a go, try out aconfmgr and see if it also clicks for you like it did for me. A must-have if you want to keep your system clean of random packages.</p>
]]></content:encoded>
</item>


 

<item>
<title>Anki is cheating</title>
<link>https://markc.su/posts/anki-is-cheating</link>
<pubDate>Wed, 09 Oct 2024 21:16:46 &#43;0200</pubDate>
<guid>https://markc.su/posts/anki-is-cheating</guid>
<description></description>
<content:encoded><![CDATA[<p>Quizlet is that tool you use to efficiently cram in flashcards just before your french test, no? From very early on in school I had teachers recommend Quizlet for french and recommended us to study 15 minutes every day. Me, knowing I can compensate my french grade with math obviously didn't bother. To nobody's surprise I was and still am bad at french. But what I took from all that was that flashcard apps are pointless. Who even studies with flashcards?</p>
<p>Fast forward three years and I'm 150 days deep, tapping through my digital flashcards, 10 minutes on average per day. Must have lost my mind somewhere in those three years it seems. Maybe ETH has something to do with that.</p>
<p>My first years in bachelor I just continued my old habits of cramming in all the information before the exam. Only difference to matura being, here it was a few weeks before instead of hours or days. Third year comes around and while talking about some inner workings of the good ol' computer during a lunch in the <em>better mensa</em>, I realize I've forgotten so many of the cool terms I learned in my first year systems course. Detrimental. I study for half my 20's and in the end I forget all the cool things I crammed in for the exams? I needed a solution.</p>
<p>Over the years I've often overheard people using Anki and simply thought of it another flashcard app for nerds. Especially with the amount my Anki addicted friends hustled through it, it looked like so much pain and was confused why anyone would ever hurt themselves like that. After settling with the fact that I'm also a nerd and having an upcoming exam that required a lot of learning by heart, I gave Anki a chance. Oh boy did that spark a new world.</p>
<p>First thing you see when using Anki, apart from the trashy looking Qt UI, is the system with how cards are evaluated. Coming from only knowing Quizlet, which has a basic &quot;Right&quot; or &quot;Wrong&quot; for flashcards [1] , I was quickly immersed in Anki's system.</p>
<p>Anki in its basic form is just a simple flashcard app. Your cards have two sides. You are shown the first side and upon tapping you see the other side of the card. You are then given four buttons: &quot;Again&quot;, &quot;Hard&quot;, &quot;Good&quot;, &quot;Easy&quot;. It's up to you to determine how well you knew the card. Everyone has their own special system they follow, but the &quot;standard&quot; way is that you tap &quot;Again&quot; if you didn't know the answer, &quot;Hard&quot; if you knew the answer but it took some time, &quot;Good&quot; if you knew the answer well, and &quot;Easy&quot; if the answer was so obvious you're wondering why you even made this card.</p>
<p>It all bases on the concept of spaced repetition. Basically, you learn the most effectively for the long term if you see a card just before you forget it. And the duration between seeing the same card again almost doubles every time. First time you see the card in maybe 2 days, then in 4, and before you know it the card is spaced months away. Can really recommend reading more into how spaced repetition works. It's basically magic.</p>
<p>My first deck was one I got from somebody else, but I quickly learned that making your own decks is where the main power of Anki comes to light. Usually all I do for lectures is read through the slides or script and anything I deem important to know I clap into Anki. Simply shortcuts like Ctrl+N for choosing the card type and Ctrl+Enter for adding the card really help here. A key ingredient I always go with is to make my cards very small and bite-sized. I rather go through 20 small cards each taking 5 seconds than 20 each making me think and write down something for a minute straight. I got places to be. My toilet breaks already take long enough because of Anki, no need to make them so long I won't be able to walk for days.</p>
<p>Very early on I noticed that Anki is like cheating. You want to remember something with as little effort as possible? Just make a small Anki card out of it and eventually you'll know it by heart. I knew that anything I put into Anki, no matter how small, Anki will eventually get me to know the card by heart.</p>
<p>It was also in these early days, when I started adding random things to Anki that had nothing to do with ETH. I felt like learning countries and flags, so I just downloaded an Anki deck and went through those. I had terminal commands I wanted to remember and clapped those in. When I saw a word I didn't know I threw it in. Random language I wanted to learn? -&gt; Anki. Everything I wanted to know long-term landed in Anki. It was so low effort, but so effective which was so golden for my lazy ass. I could quickly throw in cards and in my morning train ride I quickly studied everything I wanted for 10 minutes.</p>
<p>The magic of Anki is that anything you put in, you will <em>eventually</em> learn. It is very ineffective for learning things short-term, but for learning things across months it is the most effective system I have stumbled upon in all my wise years of traversing the web.</p>
<p>There are quite a few things I've added and used that I feel make the Anki experience just so much better. The number in the parentheses is the add-on ID if you want to try it.</p>
<ul>
<li>FSRS (Free Spaced Repetition Scheduler) (759844606): For new Anki users it won't make much sense, but for Anki veterans this is a must-have. It uses your history to make a scheduler which is suited just for you. It will reduce the amount of cards you review and instead show you the cards you need to look at. New cards on which you instantly did &quot;Good&quot; twice will show up a lot further down the line than cards you struggled to remember and had to press &quot;Hard&quot; a few times.</li>
<li>Cloze overlapper plugin (1784155610): Clozes on a line basis and extremely customizable. Lists are the enemy of Anki, but sometimes you just have to learn them. This is perfect for that.</li>
<li>Image Occlusion (1374772155): This is an add-on I often use to cheat and be lazy. I sometimes take pictures of some slides and just draw boxes around everything I feel like I should learn.</li>
<li>Clozes: Allows you to make cloze (Lückentext) cards. Helps break down big cards where the context might be helpful.</li>
</ul>
<p>You are a nerd, embrace it, download Anki.</p>
<p>[1] I haven't looked at Quizlet in years. So no shade at them, maybe they have a better system now.</p>
]]></content:encoded>
</item>


 

<item>
<title>QWERTY sucks</title>
<link>https://markc.su/posts/qwerty-sucks</link>
<pubDate>Tue, 16 Jul 2024 09:19:02 &#43;0200</pubDate>
<guid>https://markc.su/posts/qwerty-sucks</guid>
<description></description>
<content:encoded><![CDATA[<p>If you'd asked me a few months ago what I thought about ortholinear, split keyboards, or any keyboard layouts under 60%, I would have said that those are just niche and for the geeky people. My opinions have drastically changed since then. I now believe every CS student should nerd out and look into ergonomic keyboards or layouts if they see themselves using a keyboard in the future.</p>
<h3 id="backstory">Backstory</h3>
<p>To give some backstory, a few months ago I started upping the amount of time I spend at a keyboard on a daily basis by starting an internship. I started getting pain in my arms by all the typing I was doing. I had already gotten this during my studies, but it was at this point that it finally clicked for me; if I'm going to be typing so much in the future, I need to find a more ergonomic setup. Otherwise it might not just be a little pain that passes after a few days anymore. Speed was never a goal and I already knew my speed would take a big hit anyway.</p>
<h3 id="layout">Layout</h3>
<p>The first thing I looked at was a different keyboard layout. A layout determines how the keys are laid out on a keyboard. The most common layout is QWERTY (or QWERTZ).</p>
<p>Below are heat maps showing the most frequent letters in English. Take note of how empty the home row is. The home row is the middle row where your fingers should be resting. With your index fingers being on F and J. So basically most of the time you're typing on QWERTY your fingers are not on the home row and instead reaching to some other key. Also notice how your left hand is doing most of the work. While there are a lot of theories about why QWERTY came to be, it's a known fact that it sucks and is easy to improve on.</p>
<p>Over the years, I've gotten really fast at typing on QWERTY, being in the 98th percentile on typeracer. I have to admit though, my typing form is some 6 finger Frankenstein-technique which also doesn't help with reducing finger strain. I thought I might as well switch to a better layout if I plan to relearn typing.</p>
<p><img src="/content/posts/qwerty-sucks/layout_heatmap.jpeg" alt=""></p>
<p>A layout I've started my journey on is colemak-dh. It is very similar to the colemak layout shown above with some slight modifications to make it even better. Most notably D and H are moved underneath T and N, with the idea that moving fingers down is easier and more comfortable than moving the fingers laterally. The idea behind colemak is to keep a lot of special keys in the same or similar locations (Q, Z, X, C, etc.) to allow for an easier transition from QWERTY while making the whole layout ergonomic to use.</p>
<p>During my first month in May I aimed to spend at least 30 minutes to practice the layout on keybr.com, a website which improves typing by slowly introducing letters and having you focus on your weakest letter.</p>
<p>keybr introduces letters in the order of how frequent the letter is. On QWERTY the letters will be all over the place. On more ergonomic layouts like colemak you'll see that the letters are at first all on the homerow and any new letters introduced are in comfortable to reach positions. The amount you can write without even moving your fingers away from home row is quite surprising coming from having used QWERTY all my life.</p>
<p>This first month I only ever practiced at home after work. I didn't want to go cold turkey and write a whole two lines of code all day, because of how slow I was. I didn't yet learn where all the letters were, let alone where the symbols were. I tried to limit myself to use the keyboard wherever else I could though; for example to chat on Discord. It was a completely different experience from getting my thoughts out as a message in an instant to having around 15 WPM and slowly tapping away my letters and almost losing my train of thought while I search for the letters and fix my typos.</p>
<h3 id="but-i-need-qwerty">But I need QWERTY</h3>
<p>QWERTY is so deeply ingrained everywhere it's almost impossible to get rid of it completely. One of the biggest fears of people learning a new layout is that they fear they lose their ability to type on QWERTY. What happens if you're on another system or you don't have your &quot;colemak keyboard&quot; with you? Then you suddenly can't type anymore.</p>
<p>That should not be the cause of any worry though. You can learn multiple layouts without forgetting how to use QWERTY. One very effective strategy is to only use an alternative layout on a different keyboard. That is also how I'm doing it. I only use colemak on my split column-staggered keyboard. I probably couldn't even use colemak on a regular row-staggered keyboard. This helps with the separation of layouts.</p>
<p>This does mean I have to carry around my keyboard wherever I wanna go, but realistically I only type a lot in two places anyway: At home and at work. Another bonus is that my keyboard is basically pocket-sized so not difficult to bring anywhere.</p>
<p>For gaming, every game has a lot of keybindings. Changing the keybindings of every game I open would be a nightmare, so instead I just my usual keyboard and QWERTY for that. My split keyboard wouldn't work for gaming anyway because of the layers and the homerow modifiers.</p>
<h3 id="keyboard">Keyboard</h3>
<p>Ergonomic keyboards can quickly lead to a deep research rabbit hole. Something all ergonomic keyboards share is that they aim to avoid the angling of hands. They achieve this by either having both halves of the keyboard be angled to allow for straight arms or by simply splitting the keyboard in two. Having a split keyboard allows for your arms to be straight respective to your shoulders. This avoids your arms from aiming inwards and then the arms having to angle back out (ulnar deviation), which puts a lot of strain on the arms.</p>
<p><img src="/content/posts/qwerty-sucks/ulnar_deviation.png" alt=""></p>
<p>In my split keyboard research I first stumbled upon ergonomic keyboards like the Kinesis Advantage 360 or Glove 80. These are off the shelf products, but I didn't feel like shelling out half a grand for a keyboard.</p>
<p>To my surprise, there's a huge community behind ergonomic split keyboards. Lots of different variations making it easy to choose exactly what suits you. In the end I decided on a choc <em>crkbd</em> (corne keyboard) which leaves me with 42 keys.</p>
<p><img src="/content/posts/qwerty-sucks/crkbd.jpeg" alt=""></p>
<p>The above is my crkbd. It is intended for your hand to nicely rest on the home row. Your thumbs, the strongest fingers, handle the three special keys on the bottom instead of both being wasted on a huge space bar. This frees up your pinky from having to do finger gymnastics to reach uncomfortable far away keys.</p>
<p>A major problem on standard keyboards is that the layout makes no sense. One of the most common keys, the backspace, is out of reach for most people, since it was an afterthought. Typewriters had no backspace. Have a look at your hands while typing. How much do you have to readjust your hands just to reach certain keys? I don't have the smallest hands yet I can't say I can comfortably reach all the keys without having to move my hand back and forth. Then come the modifier keys like CTRL, ALT, CMD, and FN. Your pinky, the weakest finger, is responsible for reaching all around. I'm honestly still unsure which finger is supposed to click CMD on a mac keyboard. Too far for the pinky, so I usually use my thumb or move my whole hand around. Then comes the row staggered keyboard layout. The only reason keyboards are row staggered is because of how a typewriter used to have levers and it made it easier to assemble. There are no ergonomic benefits and it's completely inferior to column staggered keyboards.</p>
<p>With the crkbd your fingers at most have to move one key up or down. With the exception of the pinky and index which additionally have to move one left or right respectively. Add this with the combination of having a column staggered layout, where the columns are shifted slightly up and down depending on what finger is on that column. For example, the middle finger column is the highest up allowing all your fingers to be at a comfortable position without having the middle finger cramp up too much or the pinky stretch too much. In my setup, I don't even use the corner pinky keys on the far top left/right and bottom left/right, because even those keys make me have to stretch my pinky over. Also lets me say I use a sub 40 key keyboard.</p>
<h3 id="layers">Layers</h3>
<p>42 keys??? Where are the numbers? How do I write symbols? It for sure doesn't have F-keys.</p>
<p>Less keys means you have to use layers for achieving certain keys. For example I have three layers that I currently use. My first layer is the letter layer. I don't use a system level colemak, but instead just have the keys in a colemak layout and then use EurKey or US on whatever system I'm on. Less of a hassle, since I want to be able to write on Colemak no matter what system I plug my corne into.</p>
<p>The second text underneath is the key that is pressed if I hold the key for long enough or hold it together with another key. That means if I hold A and then press T I can open a new tab in the browser since that turns into Ctrl+T. These are called homerow modifiers, and are a really cool way to use modifiers without having to dedicate a special key for them. These keys are also one of the main reasons I don't use this keyboard for gaming. In the rare cases I need to spam a character which has a second key, I can tap and then hold the key to simulate a long key press.</p>
<p>The notable thumb keys are the <code>&amp;cmo 2</code> and <code>&amp;cmo 4</code> keys which are a modified version of the zmk &quot;momentary layer&quot; which bring me into the second and fourth layer while holding them. I technically only have three layers, but I like to swap GUI (Win key/CMD) and CTRL when working on a mac which I solved by simply duplicating my relevant layers.</p>
<p>Notably I also have my backspace on the left where caps lock usually is. It actually amazed me how natural it feels to be there. It is currently the only key I mispress on standard keyboards nowadays.</p>
<p><img src="/content/posts/qwerty-sucks/layer1.png" alt=""></p>
<p>My second layer consists of my symbols on the left side and the navigation keys on my right. This is reached when I hold my left thumb layer-switch key. My QWERTY typing form was so illegal, that when using VIM I actually rest my fingers on <code>hjkl</code> instead of <code>jkl;</code>, so I just got used to having the &quot;arrow&quot; keys be on my home row. And below the arrow keys I have the corresponding faster movements Home, PgDown, PgUp, End.</p>
<p><img src="/content/posts/qwerty-sucks/layer2.png" alt=""></p>
<p>My third layer the one I reach when pressing the right thumb layer-switch key. In this layer I have a whole numpad on my right hand. I find a numpad so much superior to writing numbers. Also helps me get high scores in the Monkeytype accountant mode.
And on the left side I just have all my F-keys jumbled in as well as my shift is a caps lock. Not that I've actually ever really needed the caps lock since I started using this keyboard.</p>
<p><img src="/content/posts/qwerty-sucks/layer3.png" alt=""></p>
<h3 id="conclusion">Conclusion</h3>
<p>Learning a new keyboard (or layout) is one of the nerdy things you should be doing as computer scientist. You are in a job direction where you'll spend thousands of hours typing on a keyboard in the future. Look into ways to make it healthy.</p>
<p>Since I started writing this post almost three months ago I have improved a lot on my keyboard. I now use it everyday for work, know where my symbols and special keys are. Have reached around 70 wpm on monkeytype and see no end in having it improve yet.</p>
<p>I severely underestimated how long this whole journey would take. If I spent a bit more time typing and was less scared to use it at work my progress might've been a bit faster.</p>
<p>Hope I was able to inspire you to also explore this part of keyboards. When are you switching to a corne?</p>
]]></content:encoded>
</item>


 

<item>
<title>You should learn a new language</title>
<link>https://markc.su/posts/learning_languages</link>
<pubDate>Fri, 02 Feb 2024 10:34:00 &#43;0100</pubDate>
<guid>https://markc.su/posts/learning_languages</guid>
<description></description>
<content:encoded><![CDATA[<p>To some people's surprise, when I started at ETH I only knew a bare level of Python. Am always surprised to learn about the fifty programming languages first-years nowadays bring to the table when starting at ETH. I've since learned to appreciate and learn a lot of different languages and dare to say learning new lesser-used languages is even my hobby.</p>
<p>Every language I know now I learned during my past 3.5 years at ETH. 95% of it, if not more, I learned outside the usual study schedule.
I want to introduce you to why you should learn new languages and how you can do it effectively to not forget it all a week later.</p>
<h1 id="why-you-should-learn-a-new-language">Why you should learn a new language</h1>
<p>You'll often hear that instead of learning ten programming languages very roughly, you should stick to one language to learn it more in-depth. I fully agree with this point. You shouldn't try to quickly force in ten different programming languages just so you can make your CV look good at a rough glance. Take every language you learn one at a time so you can dive into it and fully learn it.
That doesn't mean you should only stick to the one language you love. Be open to learning and checking out a new language! Just don't rush the learning process too much.</p>
<p>The main strength I see behind learning a new language is that every language has its unique way of thinking and approaching problems. This helps extremely with widening your horizon. Different languages have different ways of how projects are structured. Learning new languages makes you think differently about projects and languages you work with.
New ways of thinking from one language can most often be transferred to other projects and languages. It helps understand different code bases better and with every language you properly learn, any additional language becomes even easier to learn because there are always some similarities (just like with spoken languages).</p>
<p>Some examples from my experience include learning Haskell, which opened up a new world of functional programming. I've started to think more about functional programming when writing in any other language and always making the conscious decision if for example a global variable is actually required or if I can approach the problem in a purely functional way.</p>
<p>Alternatively, learning Go and <a href="https://ziglang.org">Zig</a> opened me up to the concept of returning errors as values. Previously with Python and Java I always felt like errors had to be raised and caught in a bulky try/catch syntax. Go and Zig both introduced returning errors as values from functions (Sidenote: C also theoretically returns errors as values, but there it is not an integral part of the language). That is a new way of thinking which I've lately been enjoying a lot. With Go and Zig, it is also interesting to see how much they differ and how much is similar.</p>
<h1 id="how-to-learn-a-new-language">How to learn a new language</h1>
<p>There is no one true way to learn a language and every language is different, which is why I'm not going to go fully in detail on how you should learn a language step by step.</p>
<p>There are two key steps I recommend for anyone learning a new language to follow:</p>
<ol>
<li>
<p>Learn the basics of a language so you get the hang of the syntax. I've tried multiple methods for learning a language. I'd recommend you try the different methods to see what you enjoy the most.</p>
<ul>
<li>For Zig I used <a href="https://codeberg.org/ziglings/exercises/">ziglings</a> (also exists for Rust: <a href="https://github.com/rust-lang/rustlings/">rustlings</a>).
This is a new way of learning a language for me which I tried out the first time for Zig. In this method, you get a lot of exercises with some code already prewritten for you. The code is buggy or broken and you have to fix it. Then there's always an explanation of the concept that is introduced or what is going on in the program. This helps a lot with becoming good at reading code you didn't write as well as learning to understand the compiler errors. This was one of the most effective ways to learn a language for me. I also didn't have to set up a project to create some practice files and could dive straight into using the language while learning about it.</li>
<li>For Go I followed the book &quot;Get Programming with Go&quot; by Nathan Youngman &amp; Roger Peppé. This was my first time learning a language using a book and this book was perfect. It introduces the language slowly with humor and slowly turns you into a proper Gopher by teaching you the right etiquette.</li>
<li>For my earlier languages like Python, I learned using an app. Sadly the app I used to use has gone completely haywire with in-app purchases and I wouldn't recommend it anymore.</li>
</ul>
<p>An important point is that no matter how you learn a language, it is important that you not only learn the language theoretically but also apply the knowledge by playing around with the language and coding in it.</p>
</li>
<li>
<p>The next step to learning a language is to use the language in a project. <a href="https://austinhenley.com/blog/challengingprojects.html">Austin's Blog Post</a> goes over some cool in-depth project ideas if you're looking for some. But a project can be of any scale and simply anything that interests you. Here also comes a point I find extremely important. <strong>Don't try to use a single language for everything.</strong> See a language as a tool and use the right language for the right job. If your goal is to create websites, you'll need to learn HTML and JS. By creating webassembly websites using Rust you're just shooting yourself in the foot. Or if you want to learn a systems language, find a project which allows you to focus on system-level aspects. Creating a discord bot with C is a nice project, but almost a bit too high level for C and results in a lot of pain. By using a language for something it isn't intended for, you learn more about the language, but it isn't necessarily the most effective way since you're trying to force the language to your use case.</p>
<p>For example, a good project for higher-level languages and if you're a Discord addict, is to create a Discord bot. Gives you a lot of insights into handling user input while allowing you to dive more in detail for any feature you want your bot to have (image processing, web scraping, high-performance computing, etc.).</p>
</li>
</ol>
<h1 id="conclusion">Conclusion</h1>
<p>I learn languages not to fill my CV, but to get more insights into how different languages approach problems. I find learning about the different approaches quite enjoyable and can dearly recommend you to also have a look at new languages.
You should also not rush learning a new language. Take your time with new languages and create projects to properly learn the language (projects also show you have proper experience with a language on your CV).</p>
<p>I'm curious how you approach learning a new language. Or do you avoid learning new languages? Why?</p>
<hr>
<p><em>Sidenote: I tried to make a Zig propaganda post, but somehow resulted in writing a more general language post. Don't fret though, the Zig propaganda will come in a future post.</em></p>
]]></content:encoded>
</item>


 

<item>
<title>Creating a TODO App using templ&#43;htmx</title>
<link>https://markc.su/posts/htmx-todo-app</link>
<pubDate>Tue, 26 Dec 2023 08:04:44 &#43;0200</pubDate>
<guid>https://markc.su/posts/htmx-todo-app</guid>
<description></description>
<content:encoded><![CDATA[<p>A few weeks ago at a coding weekend I held a workshop to introduce the base concepts of
the templ+htmx tech stack. The final product of that coding weekend was a quick todo app.
Here is the workshop so you can also follow along to code your first templ+htmx website.</p>
<p>What we'll be building:
<img src="/content/posts/htmx-todo-app/image-1.png" alt="Finished template todo list"></p>
<hr>
<h2 id="what-is-htmx">What is htmx?</h2>
<p><a href="https://htmx.org/">htmx</a>' motto is to stay simple. Instead of throwing huge chunks of JavaScript at a user, we only work with the actual required HTML. Using htmx we can make buttons responsive and replace elements on the website without reloading the website. The basics are, that when we click a button that should modify the site, we send a request to the backend server. The backend then doesn't return JSON, but the direct HTML which will be used to place wherever defined.</p>
<h2 id="base-project-layout">Base project layout</h2>
<p>The project source code is available on <a href="https://github.com/markbeep/htmx-todo-workshop">GitHub</a>. It is intended to be followed along starting with the <code>template</code> branch. The <code>final</code> branch has the full code of how it will look in the end. If you're ever lost when following along, you can check out the <code>final</code> branch to see how it should look when complete.</p>
<blockquote>
<p>Sidenote: Styling seemed to be broken on Safari when I last tried it. It works on other browsers though.</p>
</blockquote>
<h3 id="go">Go</h3>
<p>This project has a small amount of Go code. <code>main.go</code> is the whole web server. For handling requests we use <a href="https://github.com/go-chi/chi">chi</a> which is a lightweight HTTP library. It allows for some nice features like easily adding middleware and getting values from routes.</p>
<p>The other file is <code>internal/todo_type.go</code> and this file only defines a single type definition. Go doesn't allow for circular dependencies and we need it in both main.go and the templ templates.</p>
<h3 id="templ">Templ</h3>
<p>We're building a template-driven web server (similar to Django). Go has a template library out of the box (<code>template/html</code>), but it is not as clean as it could be. Instead for this project, we'll be using <a href="https://templ.guide/">templ</a>. Templ goes in the direction of components, similar to React. Like in the code snippet below, we can easily create a component (like <code>Hello</code>) and then easily use that wherever we want (using <code>@Hello()</code>). This gives a lot of freedom for how to use our components and it allows for a very clear structuring of the website. The syntax is also basically Go (with the same types), but we sprinkle in some HTML. Templ will generate Go code which then can be imported in <code>main.go</code>. It is important to make sure the generation runs and works.</p>
<pre style="padding:10px;margin:20px 0 20px 0;border-radius:10px;box-shadow:5px 5px 10px rgba(0, 0, 0, 0.3);overflow:auto;color:#e6edf3;background-color:#0d1117;"><code><span style="display:flex;"><span><span style="color:#ff7b72">package</span> main
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>templ <span style="color:#d2a8ff;font-weight:bold">Hello</span>(name <span style="color:#ff7b72">string</span>) {
</span></span><span style="display:flex;"><span>  &lt;div&gt;Hello, { name }&lt;<span style="color:#ff7b72;font-weight:bold">/</span>div&gt;
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>templ <span style="color:#d2a8ff;font-weight:bold">Greeting</span>(person Person) {
</span></span><span style="display:flex;"><span>  &lt;div class=<span style="color:#a5d6ff">&#34;greeting&#34;</span>&gt;
</span></span><span style="display:flex;"><span>    <span style="color:#f85149">@</span><span style="color:#d2a8ff;font-weight:bold">Hello</span>(person.Name)
</span></span><span style="display:flex;"><span>  &lt;<span style="color:#ff7b72;font-weight:bold">/</span>div&gt;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre><h3 id="tailwindcss">Tailwindcss</h3>
<p>For styling everything, we use <a href="https://tailwindcss.com/">Tailwindcss</a>. It's a must-have for designing a website. It allows you to more easily write and use CSS. For this project, most of the tailwindcss should already be added to components though, so you don't have to touch it. To make the main part of the code easier to understand and not bloated as much, all Tailwindcss has been thrown into <code>static/tw.css</code>.</p>
<h1 id="code-along-workshop">Code along workshop</h1>
<h2 id="part-1-getting-familiar-with-templates">Part 1: Getting familiar with templates</h2>
<p>Currently the <code>main.go</code> file is very barebones. It includes a single GET endpoint which returns our <code>main.css</code> file so that we have some styling on the website. The <code>r.Get</code> syntax is the afore-mentioned chi library and it'll also be how we can add endpoints for POST requests later on (<code>r.Post</code>).</p>
<p>I already prepared a base template which is located in <code>components/base.templ</code>. This file includes the htmx installation (the simple script tag) and the styling for the upcoming parts of the todo app. Using tailwind we set the background to a dark blue-grey, text to white and create a div that fully centers all its items. The <code>{ children... }</code> tag allows the Base template to surround any other components we want.</p>
<p><code>components/todo.templ</code> will be the file you'll also be editing. This file will contain all the todo-specific component structures.</p>
<p>The first thing we'll do is update our <code>main.go</code> with an endpoint that shows an index page:</p>
<pre style="padding:10px;margin:20px 0 20px 0;border-radius:10px;box-shadow:5px 5px 10px rgba(0, 0, 0, 0.3);overflow:auto;color:#e6edf3;background-color:#0d1117;"><code><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">// Handles all GET requests to /
</span></span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"></span>r.<span style="color:#d2a8ff;font-weight:bold">Get</span>(<span style="color:#a5d6ff">&#34;/&#34;</span>, templ.<span style="color:#d2a8ff;font-weight:bold">Handler</span>(components.<span style="color:#d2a8ff;font-weight:bold">Index</span>()).ServeHTTP)
</span></span></code></pre><p>Now visit <code>http://localhost:3000</code> and you'll see &quot;Hello World&quot;.</p>
<p><img src="/content/posts/htmx-todo-app/image.png" alt="Hello world template"></p>
<p>We're missing the dark background though. Our Base is missing! Let's add that:</p>
<pre style="padding:10px;margin:20px 0 20px 0;border-radius:10px;box-shadow:5px 5px 10px rgba(0, 0, 0, 0.3);overflow:auto;color:#e6edf3;background-color:#0d1117;"><code><span style="display:flex;"><span>templ <span style="color:#d2a8ff;font-weight:bold">Index</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#f85149">@</span><span style="color:#d2a8ff;font-weight:bold">Base</span>() {
</span></span><span style="display:flex;"><span>        &lt;p&gt;
</span></span><span style="display:flex;"><span>            Hello World
</span></span><span style="display:flex;"><span>        &lt;<span style="color:#ff7b72;font-weight:bold">/</span>p&gt;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre><p>This wraps our Index component inside of our Base component at the spot where we had <code>{ children... }</code>.</p>
<p>Let's update our <code>todo.templ</code> to be more like an actual todo list though. We create a new component
called <code>TodoList</code> which will be a component containing a list of all our todos.
The <code>id=&quot;todo-list&quot;</code> is what gives it some predefined styling. Don't forget it!</p>
<pre style="padding:10px;margin:20px 0 20px 0;border-radius:10px;box-shadow:5px 5px 10px rgba(0, 0, 0, 0.3);overflow:auto;color:#e6edf3;background-color:#0d1117;"><code><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">// The list of all todos. Basically the whole page functionality
</span></span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"></span>templ <span style="color:#d2a8ff;font-weight:bold">TodoList</span>() {
</span></span><span style="display:flex;"><span>    &lt;div id=<span style="color:#a5d6ff">&#34;todo-list&#34;</span>&gt;
</span></span><span style="display:flex;"><span>        &lt;h1&gt;Todo List&lt;<span style="color:#ff7b72;font-weight:bold">/</span>h1&gt;
</span></span><span style="display:flex;"><span>        &lt;form&gt;
</span></span><span style="display:flex;"><span>            &lt;p&gt;
</span></span><span style="display:flex;"><span>                First Todo
</span></span><span style="display:flex;"><span>            &lt;<span style="color:#ff7b72;font-weight:bold">/</span>p&gt;
</span></span><span style="display:flex;"><span>        &lt;<span style="color:#ff7b72;font-weight:bold">/</span>form&gt;
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#ff7b72;font-weight:bold">/</span>div&gt;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre><p>Then update the <code>Index()</code> component to use the <code>TodoList</code> template:</p>
<pre style="padding:10px;margin:20px 0 20px 0;border-radius:10px;box-shadow:5px 5px 10px rgba(0, 0, 0, 0.3);overflow:auto;color:#e6edf3;background-color:#0d1117;"><code><span style="display:flex;"><span>templ <span style="color:#d2a8ff;font-weight:bold">Index</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#f85149">@</span><span style="color:#d2a8ff;font-weight:bold">Base</span>() {
</span></span><span style="display:flex;"><span>        <span style="color:#f85149">@</span><span style="color:#d2a8ff;font-weight:bold">TodoList</span>()
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre><p>Now we want to add a component for a single todo item called <code>todoItem</code>.
This will be our component for a single todo item. Because our todo list is a two
wide grid, each todo item will have two elements. The text on the left and an <code>X</code> button
on the right to delete the todo.</p>
<pre style="padding:10px;margin:20px 0 20px 0;border-radius:10px;box-shadow:5px 5px 10px rgba(0, 0, 0, 0.3);overflow:auto;color:#e6edf3;background-color:#0d1117;"><code><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">// A single todo item
</span></span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"></span>templ <span style="color:#d2a8ff;font-weight:bold">todoItem</span>() {
</span></span><span style="display:flex;"><span>    &lt;p&gt;
</span></span><span style="display:flex;"><span>        Todo text placeholder
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#ff7b72;font-weight:bold">/</span>p&gt;
</span></span><span style="display:flex;"><span>    &lt;div class=<span style="color:#a5d6ff">&#34;flex justify-end&#34;</span>&gt;
</span></span><span style="display:flex;"><span>        &lt;button <span style="color:#ff7b72">type</span>=<span style="color:#a5d6ff">&#34;button&#34;</span>&gt;
</span></span><span style="display:flex;"><span>            X
</span></span><span style="display:flex;"><span>        &lt;<span style="color:#ff7b72;font-weight:bold">/</span>button&gt;
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#ff7b72;font-weight:bold">/</span>div&gt;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre><p>Then update the TodoList to use a few of these components:</p>
<pre style="padding:10px;margin:20px 0 20px 0;border-radius:10px;box-shadow:5px 5px 10px rgba(0, 0, 0, 0.3);overflow:auto;color:#e6edf3;background-color:#0d1117;"><code><span style="display:flex;"><span>templ <span style="color:#d2a8ff;font-weight:bold">TodoList</span>() {
</span></span><span style="display:flex;"><span>    &lt;div id=<span style="color:#a5d6ff">&#34;todo-list&#34;</span>&gt;
</span></span><span style="display:flex;"><span>        &lt;h1&gt;Todo List&lt;<span style="color:#ff7b72;font-weight:bold">/</span>h1&gt;
</span></span><span style="display:flex;"><span>        &lt;form&gt;
</span></span><span style="display:flex;"><span>            <span style="color:#f85149">@</span><span style="color:#d2a8ff;font-weight:bold">todoItem</span>()
</span></span><span style="display:flex;"><span>            <span style="color:#f85149">@</span><span style="color:#d2a8ff;font-weight:bold">todoItem</span>()
</span></span><span style="display:flex;"><span>        &lt;<span style="color:#ff7b72;font-weight:bold">/</span>form&gt;
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#ff7b72;font-weight:bold">/</span>div&gt;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre><hr>
<h2 id="part-2-adding-variables-to-templates">Part 2: Adding variables to templates</h2>
<p>Okay, at this point it looks quite good already. Now comes a big step. Let's make the components use some variables we can pass in. For this, we'll use the <code>internal.Todo</code> struct defined in <code>internal/todo_type.go</code>.</p>
<ol>
<li>Create a todo array in <code>main.go</code>:
<pre style="padding:10px;margin:20px 0 20px 0;border-radius:10px;box-shadow:5px 5px 10px rgba(0, 0, 0, 0.3);overflow:auto;color:#e6edf3;background-color:#0d1117;"><code><span style="display:flex;"><span><span style="color:#ff7b72">import</span> <span style="color:#a5d6ff">&#34;coding-weekend/internal&#34;</span> <span style="color:#8b949e;font-style:italic">// add import
</span></span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"></span><span style="color:#ff7b72;font-weight:bold">...</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">var</span> todos = []internal.Todo{} <span style="color:#8b949e;font-style:italic">// all todos are stored in a global array
</span></span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"></span><span style="color:#ff7b72;font-weight:bold">...</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">func</span> <span style="color:#d2a8ff;font-weight:bold">main</span>() {<span style="color:#ff7b72;font-weight:bold">...</span>
</span></span></code></pre></li>
<li>Import the same in <code>todo.templ</code> at the top:
<pre style="padding:10px;margin:20px 0 20px 0;border-radius:10px;box-shadow:5px 5px 10px rgba(0, 0, 0, 0.3);overflow:auto;color:#e6edf3;background-color:#0d1117;"><code><span style="display:flex;"><span><span style="color:#ff7b72">import</span> <span style="color:#a5d6ff">&#34;coding-weekend/internal&#34;</span>
</span></span></code></pre></li>
<li>Adjust <code>todoItem</code> to take a todo as an argument:
<pre style="padding:10px;margin:20px 0 20px 0;border-radius:10px;box-shadow:5px 5px 10px rgba(0, 0, 0, 0.3);overflow:auto;color:#e6edf3;background-color:#0d1117;"><code><span style="display:flex;"><span>templ <span style="color:#d2a8ff;font-weight:bold">todoItem</span>(todo internal.Todo) {<span style="color:#ff7b72;font-weight:bold">...</span>
</span></span></code></pre></li>
<li>Adjust <code>TodoList</code> to take an array of todos as argument:
<pre style="padding:10px;margin:20px 0 20px 0;border-radius:10px;box-shadow:5px 5px 10px rgba(0, 0, 0, 0.3);overflow:auto;color:#e6edf3;background-color:#0d1117;"><code><span style="display:flex;"><span>templ <span style="color:#d2a8ff;font-weight:bold">TodoList</span>(todos []internal.Todo) {<span style="color:#ff7b72;font-weight:bold">...</span>
</span></span></code></pre></li>
<li>Adjust <code>Index</code> to also take an array of todos as argument and pass it to <code>TodoList</code>:
<pre style="padding:10px;margin:20px 0 20px 0;border-radius:10px;box-shadow:5px 5px 10px rgba(0, 0, 0, 0.3);overflow:auto;color:#e6edf3;background-color:#0d1117;"><code><span style="display:flex;"><span>templ <span style="color:#d2a8ff;font-weight:bold">Index</span>(todos []internal.Todo) {
</span></span><span style="display:flex;"><span>    <span style="color:#f85149">@</span><span style="color:#d2a8ff;font-weight:bold">Base</span>() {
</span></span><span style="display:flex;"><span>        <span style="color:#f85149">@</span><span style="color:#d2a8ff;font-weight:bold">TodoList</span>(todos)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre>We won't be touching this <code>Index</code> component anymore.</li>
<li>Update our route handler in <code>main.go</code> to pass in the created todo array:
<pre style="padding:10px;margin:20px 0 20px 0;border-radius:10px;box-shadow:5px 5px 10px rgba(0, 0, 0, 0.3);overflow:auto;color:#e6edf3;background-color:#0d1117;"><code><span style="display:flex;"><span>r.<span style="color:#d2a8ff;font-weight:bold">Get</span>(<span style="color:#a5d6ff">&#34;/&#34;</span>, templ.<span style="color:#d2a8ff;font-weight:bold">Handler</span>(components.<span style="color:#d2a8ff;font-weight:bold">Index</span>(todos)).ServeHTTP)
</span></span></code></pre></li>
<li>Last step is to modify our <code>TodoList</code> to create a <code>todoItem</code> for every todo. For this, we add a Go for-loop:
<pre style="padding:10px;margin:20px 0 20px 0;border-radius:10px;box-shadow:5px 5px 10px rgba(0, 0, 0, 0.3);overflow:auto;color:#e6edf3;background-color:#0d1117;"><code><span style="display:flex;"><span>templ <span style="color:#d2a8ff;font-weight:bold">TodoList</span>(todos []internal.Todo) {
</span></span><span style="display:flex;"><span>    &lt;div id=<span style="color:#a5d6ff">&#34;todo-list&#34;</span>&gt;
</span></span><span style="display:flex;"><span>        &lt;h1&gt;Todo List&lt;<span style="color:#ff7b72;font-weight:bold">/</span>h1&gt;
</span></span><span style="display:flex;"><span>        &lt;form&gt;
</span></span><span style="display:flex;"><span>            <span style="color:#ff7b72">for</span> _, t <span style="color:#ff7b72;font-weight:bold">:=</span> <span style="color:#ff7b72">range</span> todos { <span style="color:#8b949e;font-style:italic">// IMPORTANT: the first value (underscore) is the index in Go. We can ignore it
</span></span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"></span>                <span style="color:#f85149">@</span><span style="color:#d2a8ff;font-weight:bold">todoItem</span>(t)
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>        &lt;<span style="color:#ff7b72;font-weight:bold">/</span>form&gt;
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#ff7b72;font-weight:bold">/</span>div&gt;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></li>
<li>All errors should be gone now. Let's finish <code>todoItem</code> by making it use the passed-in todo struct:
<pre style="padding:10px;margin:20px 0 20px 0;border-radius:10px;box-shadow:5px 5px 10px rgba(0, 0, 0, 0.3);overflow:auto;color:#e6edf3;background-color:#0d1117;"><code><span style="display:flex;"><span>templ <span style="color:#d2a8ff;font-weight:bold">todoItem</span>(todo internal.Todo) {
</span></span><span style="display:flex;"><span>    &lt;p&gt;
</span></span><span style="display:flex;"><span>        { todo.Text } <span style="color:#8b949e;font-style:italic">// add this
</span></span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"></span>    &lt;<span style="color:#ff7b72;font-weight:bold">/</span>p&gt;
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72;font-weight:bold">...</span>
</span></span></code></pre></li>
<li>Now when we visit the site, we have an empty todo list. Let's add some mock data in <code>main.go</code> to our todo list:
<pre style="padding:10px;margin:20px 0 20px 0;border-radius:10px;box-shadow:5px 5px 10px rgba(0, 0, 0, 0.3);overflow:auto;color:#e6edf3;background-color:#0d1117;"><code><span style="display:flex;"><span><span style="color:#ff7b72">var</span> todos = []internal.Todo{
</span></span><span style="display:flex;"><span>    {Text: <span style="color:#a5d6ff">&#34;drink mate&#34;</span>},
</span></span><span style="display:flex;"><span>}
</span></span></code></pre>Yay, if we visit our site we now have our first dynamic todo there.</li>
</ol>
<p>The last thing we want to add to this part is to add a way to add new todos. For that, we create a new component in <code>todo.templ</code>.
We call the component <code>todoInput</code> as it will be a text input field for adding new todos.</p>
<pre style="padding:10px;margin:20px 0 20px 0;border-radius:10px;box-shadow:5px 5px 10px rgba(0, 0, 0, 0.3);overflow:auto;color:#e6edf3;background-color:#0d1117;"><code><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">// The input field for adding new todos
</span></span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"></span>templ <span style="color:#d2a8ff;font-weight:bold">todoInput</span>() {
</span></span><span style="display:flex;"><span>    &lt;input
</span></span><span style="display:flex;"><span>        placeholder=<span style="color:#a5d6ff">&#34;Enter todo here...&#34;</span>
</span></span><span style="display:flex;"><span>        name=<span style="color:#a5d6ff">&#34;text&#34;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">type</span>=<span style="color:#a5d6ff">&#34;text&#34;</span>
</span></span><span style="display:flex;"><span>        required
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72;font-weight:bold">/</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;button <span style="color:#ff7b72">type</span>=<span style="color:#a5d6ff">&#34;submit&#34;</span>&gt;
</span></span><span style="display:flex;"><span>        Add
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#ff7b72;font-weight:bold">/</span>button&gt;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre><p>Then below our for loop in <code>TodoList</code> add the input component:</p>
<pre style="padding:10px;margin:20px 0 20px 0;border-radius:10px;box-shadow:5px 5px 10px rgba(0, 0, 0, 0.3);overflow:auto;color:#e6edf3;background-color:#0d1117;"><code><span style="display:flex;"><span><span style="color:#ff7b72;font-weight:bold">...</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">for</span> _, t <span style="color:#ff7b72;font-weight:bold">:=</span> <span style="color:#ff7b72">range</span> todos {
</span></span><span style="display:flex;"><span>            <span style="color:#f85149">@</span><span style="color:#d2a8ff;font-weight:bold">todoItem</span>(t)
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    <span style="color:#f85149">@</span><span style="color:#d2a8ff;font-weight:bold">todoInput</span>() <span style="color:#8b949e;font-style:italic">// add this
</span></span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"></span>&lt;<span style="color:#ff7b72;font-weight:bold">/</span>form&gt;
</span></span><span style="display:flex;"><span><span style="color:#ff7b72;font-weight:bold">...</span>
</span></span></code></pre><p>We won't be touching this <code>todoInput</code> anymore.</p>
<p>We can now visit our site and we'll see the following. This concludes all the base templating we'll be doing. When you click a button nothing happens right now except that the site refreshes. In the third part, we'll look into how to make it dynamic and function correctly using htmx.</p>
<p><img src="/content/posts/htmx-todo-app/image-1.png" alt="Finished template todo list"></p>
<hr>
<h2 id="part-3-making-the-site-dynamic-using-htmx">Part 3: Making the site dynamic using htmx</h2>
<ol>
<li>
<p>The first thing we'll look at is adding new todos when we click &quot;Add&quot;. You might have noticed we're using a form. By creating a form we can define the different input fields we want which will then be returned as values in a POST request using htmx.</p>
<blockquote>
<p>For the next steps to work, make sure you have <code>name=&quot;text&quot;</code> in the <code>todoInput()</code> text input. This will be they key for getting the value out of the POST body in the backend.</p>
</blockquote>
<p>Head to <code>TodoList</code> where we have the &lt;form&gt; and add the following <code>hx-post</code> tag:</p>
<pre style="padding:10px;margin:20px 0 20px 0;border-radius:10px;box-shadow:5px 5px 10px rgba(0, 0, 0, 0.3);overflow:auto;color:#e6edf3;background-color:#0d1117;"><code><span style="display:flex;"><span>templ <span style="color:#d2a8ff;font-weight:bold">TodoList</span>(todos []internal.Todo) {
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72;font-weight:bold">...</span>
</span></span><span style="display:flex;"><span>        &lt;form hx<span style="color:#ff7b72;font-weight:bold">-</span>post=<span style="color:#a5d6ff">&#34;/todo&#34;</span>&gt; <span style="color:#8b949e;font-style:italic">// add this
</span></span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"></span>    <span style="color:#ff7b72;font-weight:bold">...</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre><p>This basically says, &quot;Send post request to /todo with the form values on submit&quot;. If you try and add an item now, you'll notice the website doesn't reload anymore, but in the networks tab we get a 404:</p>
<p><img src="/content/posts/htmx-todo-app/image-2.png" alt="404 on todo post"></p>
</li>
<li>
<p>Head back to <code>main.go</code> and we'll implement the handling of the POST method now. Basically, we want to parse the form values and get the &quot;text&quot; value out.</p>
<pre style="padding:10px;margin:20px 0 20px 0;border-radius:10px;box-shadow:5px 5px 10px rgba(0, 0, 0, 0.3);overflow:auto;color:#e6edf3;background-color:#0d1117;"><code><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">// Handles all POST requests to /todo made by htmx
</span></span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"></span>r.<span style="color:#d2a8ff;font-weight:bold">Post</span>(<span style="color:#a5d6ff">&#34;/todo&#34;</span>, <span style="color:#ff7b72">func</span>(w http.ResponseWriter, r <span style="color:#ff7b72;font-weight:bold">*</span>http.Request) {
</span></span><span style="display:flex;"><span>	r.<span style="color:#d2a8ff;font-weight:bold">ParseForm</span>() <span style="color:#8b949e;font-style:italic">// REQUIRED for the r.PostFormValue to get filled with values
</span></span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"></span>	text <span style="color:#ff7b72;font-weight:bold">:=</span> r.<span style="color:#d2a8ff;font-weight:bold">PostFormValue</span>(<span style="color:#a5d6ff">&#34;text&#34;</span>)
</span></span><span style="display:flex;"><span>	log.<span style="color:#d2a8ff;font-weight:bold">Printf</span>(<span style="color:#a5d6ff">&#34;text = %s&#34;</span>, text)
</span></span><span style="display:flex;"><span>})
</span></span></code></pre><p>Let's add a new todo on the website. If the website seems to just disappear, it's all working as intended until now. We should also see our text being printed in the webserver console.</p>
</li>
<li>
<p>The site seems to disappear because htmx is by default expecting us to return the new HTML. As of now, we're not returning anything though resulting in htmx just removing the element.
In that case, let's update our todos-list by adding the new todo to our array and returning the required HTML for the updated todo-list:</p>
<pre style="padding:10px;margin:20px 0 20px 0;border-radius:10px;box-shadow:5px 5px 10px rgba(0, 0, 0, 0.3);overflow:auto;color:#e6edf3;background-color:#0d1117;"><code><span style="display:flex;"><span>r.<span style="color:#d2a8ff;font-weight:bold">Post</span>(<span style="color:#a5d6ff">&#34;/todo&#34;</span>, <span style="color:#ff7b72">func</span>(w http.ResponseWriter, r <span style="color:#ff7b72;font-weight:bold">*</span>http.Request) {
</span></span><span style="display:flex;"><span>	r.<span style="color:#d2a8ff;font-weight:bold">ParseForm</span>() <span style="color:#8b949e;font-style:italic">// REQUIRED for the r.PostFormValue to get filled with values
</span></span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"></span>	text <span style="color:#ff7b72;font-weight:bold">:=</span> r.<span style="color:#d2a8ff;font-weight:bold">PostFormValue</span>(<span style="color:#a5d6ff">&#34;text&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	todos = append(todos, internal.Todo{Text: text}) <span style="color:#8b949e;font-style:italic">// update todos list with new todo
</span></span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"></span>
</span></span><span style="display:flex;"><span>	templ.<span style="color:#d2a8ff;font-weight:bold">Handler</span>(components.<span style="color:#d2a8ff;font-weight:bold">TodoList</span>(todos)).<span style="color:#d2a8ff;font-weight:bold">ServeHTTP</span>(w, r) <span style="color:#8b949e;font-style:italic">// write html result
</span></span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"></span>})
</span></span></code></pre></li>
<li>
<p>Now let's try it out and see if it works... Okay, this is confusing, what is happening when I click the submit button?</p>
<p><img src="/content/posts/htmx-todo-app/image-3.png" alt="Html in html"></p>
<p>That is because by default htmx replaces the inner HTML of the component that sent the request. Meaning when we submit, the outer form that sent the request will stay, but it'll replace all its contents inside with the result of our <code>r.Post</code> function above. Let's fix that by simply swapping out the whole Todolist with the new content.</p>
<blockquote>
<p>Note the <code>id=&quot;todo-list&quot;</code> on the outer most div inside <code>templ TodoList</code>.</p>
</blockquote>
<p>We do that by adding the <code>hx-target=&quot;#todo-list&quot;</code> to have it target our whole todo list and <code>hx-swap=&quot;outerHTML&quot;</code> to make it replace the full target, instead of simply the contents inside. Here's how our <code>TodoList</code> looks now:</p>
<pre style="padding:10px;margin:20px 0 20px 0;border-radius:10px;box-shadow:5px 5px 10px rgba(0, 0, 0, 0.3);overflow:auto;color:#e6edf3;background-color:#0d1117;"><code><span style="display:flex;"><span>templ <span style="color:#d2a8ff;font-weight:bold">TodoList</span>(todos []internal.Todo) {
</span></span><span style="display:flex;"><span>    &lt;div id=<span style="color:#a5d6ff">&#34;todo-list&#34;</span>&gt;
</span></span><span style="display:flex;"><span>        &lt;h1&gt;Todo List&lt;<span style="color:#ff7b72;font-weight:bold">/</span>h1&gt;
</span></span><span style="display:flex;"><span>        &lt;form
</span></span><span style="display:flex;"><span>            hx<span style="color:#ff7b72;font-weight:bold">-</span>post=<span style="color:#a5d6ff">&#34;/todo&#34;</span>
</span></span><span style="display:flex;"><span>            hx<span style="color:#ff7b72;font-weight:bold">-</span>target=<span style="color:#a5d6ff">&#34;#todo-list&#34;</span>
</span></span><span style="display:flex;"><span>            hx<span style="color:#ff7b72;font-weight:bold">-</span>swap=<span style="color:#a5d6ff">&#34;outerHTML&#34;</span>
</span></span><span style="display:flex;"><span>        &gt;
</span></span><span style="display:flex;"><span>            <span style="color:#ff7b72">for</span> _, t <span style="color:#ff7b72;font-weight:bold">:=</span> <span style="color:#ff7b72">range</span> todos {
</span></span><span style="display:flex;"><span>                <span style="color:#f85149">@</span><span style="color:#d2a8ff;font-weight:bold">todoItem</span>(t)
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>            <span style="color:#f85149">@</span><span style="color:#d2a8ff;font-weight:bold">todoInput</span>()
</span></span><span style="display:flex;"><span>        &lt;<span style="color:#ff7b72;font-weight:bold">/</span>form&gt;
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#ff7b72;font-weight:bold">/</span>div&gt;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre><p>With this, we can now add new todos and it works without refreshing the whole page.</p>
<p><code>TodoList</code> is also fully done now. We won't be touching it anymore.</p>
</li>
<li>
<p>We now have the problem, that when we refresh the page, we suddenly only have our base example of <code>drink mate</code>. That is because our current <code>r.Get(&quot;/&quot;)</code> route only creates the template once when we start up the Go application. Let's fix that by making it recreate the template on each request:</p>
<pre style="padding:10px;margin:20px 0 20px 0;border-radius:10px;box-shadow:5px 5px 10px rgba(0, 0, 0, 0.3);overflow:auto;color:#e6edf3;background-color:#0d1117;"><code><span style="display:flex;"><span>r.<span style="color:#d2a8ff;font-weight:bold">Get</span>(<span style="color:#a5d6ff">&#34;/&#34;</span>, <span style="color:#ff7b72">func</span>(w http.ResponseWriter, r <span style="color:#ff7b72;font-weight:bold">*</span>http.Request) {
</span></span><span style="display:flex;"><span>	templ.<span style="color:#d2a8ff;font-weight:bold">Handler</span>(components.<span style="color:#d2a8ff;font-weight:bold">Index</span>(todos)).<span style="color:#d2a8ff;font-weight:bold">ServeHTTP</span>(w, r)
</span></span><span style="display:flex;"><span>})
</span></span></code></pre><p>Refreshing the page now gives us the correct list of todos.</p>
</li>
<li>
<p>We have almost everything done already. We just need a way to clean up todos now. For that, we want to have it so clicking on the <code>X</code> button sends a delete request to <code>/todo/{id}</code> with the correct todo ID. The backend then deletes the corresponding ID and sends back the correct updated HTML for the todo list. Theoretically, htmx allows you to delete HTML elements, but to keep it simple we'll be replacing the whole todo list.</p>
<p>Just as before, set <code>hx-target=&quot;#todo-list&quot;</code> to have it target the whole todo list and <code>hx-swap=&quot;outerHTML&quot;</code> to have it replace the whole HTML tag. To have htmx create a DELETE request, we'll use <code>hx-delete</code> and we use Go's <code>fmt</code> library to build an URL using the todo ID:</p>
<pre style="padding:10px;margin:20px 0 20px 0;border-radius:10px;box-shadow:5px 5px 10px rgba(0, 0, 0, 0.3);overflow:auto;color:#e6edf3;background-color:#0d1117;"><code><span style="display:flex;"><span><span style="color:#ff7b72">import</span> <span style="color:#a5d6ff">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72;font-weight:bold">...</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>templ <span style="color:#d2a8ff;font-weight:bold">todoItem</span>(todo internal.Todo) {
</span></span><span style="display:flex;"><span>    &lt;p&gt;
</span></span><span style="display:flex;"><span>        { todo.Text }
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#ff7b72;font-weight:bold">/</span>p&gt;
</span></span><span style="display:flex;"><span>    &lt;div class=<span style="color:#a5d6ff">&#34;flex justify-end&#34;</span>&gt;
</span></span><span style="display:flex;"><span>        &lt;button
</span></span><span style="display:flex;"><span>            <span style="color:#ff7b72">type</span>=<span style="color:#a5d6ff">&#34;button&#34;</span>
</span></span><span style="display:flex;"><span>            hx<span style="color:#ff7b72;font-weight:bold">-</span>delete={ fmt.<span style="color:#d2a8ff;font-weight:bold">Sprintf</span>(<span style="color:#a5d6ff">&#34;/todo/%d&#34;</span>, todo.ID) }
</span></span><span style="display:flex;"><span>            hx<span style="color:#ff7b72;font-weight:bold">-</span>target=<span style="color:#a5d6ff">&#34;#todo-list&#34;</span>
</span></span><span style="display:flex;"><span>            hx<span style="color:#ff7b72;font-weight:bold">-</span>swap=<span style="color:#a5d6ff">&#34;outerHTML&#34;</span>
</span></span><span style="display:flex;"><span>        &gt;
</span></span><span style="display:flex;"><span>            X
</span></span><span style="display:flex;"><span>        &lt;<span style="color:#ff7b72;font-weight:bold">/</span>button&gt;
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#ff7b72;font-weight:bold">/</span>div&gt;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre><p>All our components are now fully done. We won't have to modify them anymore.</p>
</li>
<li>
<p>Time to add the delete handler in <code>main.go</code>. Chi allows us to have arbitrary routes using regex. Here we only listen to routes of the form <code>/todo/{id}</code> where id is some number. We then convert the string parameter from the URL into an int and using <code>slices.DeleteFunc</code> we iterate over our todos array and delete all the elements that match the given ID.</p>
<pre style="padding:10px;margin:20px 0 20px 0;border-radius:10px;box-shadow:5px 5px 10px rgba(0, 0, 0, 0.3);overflow:auto;color:#e6edf3;background-color:#0d1117;"><code><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">// Handles all DELETE requests to /todo/{id} where id is some number
</span></span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"></span>r.<span style="color:#d2a8ff;font-weight:bold">Delete</span>(<span style="color:#a5d6ff">&#34;/todo/{id:\\d+}&#34;</span>, <span style="color:#ff7b72">func</span>(w http.ResponseWriter, r <span style="color:#ff7b72;font-weight:bold">*</span>http.Request) {
</span></span><span style="display:flex;"><span>	<span style="color:#8b949e;font-style:italic">// make sure the argument is a valid int (not too large for example)
</span></span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"></span>	id, err <span style="color:#ff7b72;font-weight:bold">:=</span> strconv.<span style="color:#d2a8ff;font-weight:bold">Atoi</span>(chi.<span style="color:#d2a8ff;font-weight:bold">URLParam</span>(r, <span style="color:#a5d6ff">&#34;id&#34;</span>))
</span></span><span style="display:flex;"><span>	<span style="color:#ff7b72">if</span> err <span style="color:#ff7b72;font-weight:bold">!=</span> <span style="color:#79c0ff">nil</span> {
</span></span><span style="display:flex;"><span>		http.<span style="color:#d2a8ff;font-weight:bold">Error</span>(w, <span style="color:#a5d6ff">&#34;invalid id&#34;</span>, http.StatusBadRequest)
</span></span><span style="display:flex;"><span>		<span style="color:#ff7b72">return</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	todos = slices.DeleteFunc[[]internal.Todo](todos, <span style="color:#ff7b72">func</span>(t internal.Todo) <span style="color:#ff7b72">bool</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#ff7b72">return</span> t.ID <span style="color:#ff7b72;font-weight:bold">==</span> id
</span></span><span style="display:flex;"><span>	})
</span></span><span style="display:flex;"><span>	templ.<span style="color:#d2a8ff;font-weight:bold">Handler</span>(components.<span style="color:#d2a8ff;font-weight:bold">TodoList</span>(todos)).<span style="color:#d2a8ff;font-weight:bold">ServeHTTP</span>(w, r)
</span></span><span style="display:flex;"><span>})
</span></span></code></pre></li>
<li>
<p>Last course of action. We have to add a unique ID to each todo so we can then also delete the correct one properly.
We'll be doing this very simply and not thread-safe for simplicity sake.</p>
<ol>
<li>Add a <code>todoCounter</code> which we'll increase every time a todo item is added. This is what we'll use for assigning IDs. Additionally, remove the hardcoded example from the array.
<pre style="padding:10px;margin:20px 0 20px 0;border-radius:10px;box-shadow:5px 5px 10px rgba(0, 0, 0, 0.3);overflow:auto;color:#e6edf3;background-color:#0d1117;"><code><span style="display:flex;"><span><span style="color:#ff7b72">var</span> (
</span></span><span style="display:flex;"><span>    todos       = []internal.Todo{} <span style="color:#8b949e;font-style:italic">// all todos are stored in a global array
</span></span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"></span>    todoCounter = <span style="color:#a5d6ff">0</span>                 <span style="color:#8b949e;font-style:italic">// used for assigning todo ids
</span></span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"></span>)
</span></span></code></pre></li>
<li>Add the ID to created todos in our <code>r.Post</code> function and increment the counter:
<pre style="padding:10px;margin:20px 0 20px 0;border-radius:10px;box-shadow:5px 5px 10px rgba(0, 0, 0, 0.3);overflow:auto;color:#e6edf3;background-color:#0d1117;"><code><span style="display:flex;"><span>todos = append(todos, internal.Todo{Text: text, ID: todoCounter})
</span></span><span style="display:flex;"><span>todoCounter<span style="color:#ff7b72;font-weight:bold">++</span>
</span></span></code></pre></li>
</ol>
</li>
</ol>
<p><strong>With that, the workshop is concluded! Your site should now be in a working state where you can add and remove todo items.</strong></p>
<h2 id="user-specific-todo-list">User-specific todo list</h2>
<p>The next step to how you would make this site more production-ready.</p>
<p>The site works as of now, but as soon as you start having more users, you'll quickly notice a problem: The todo list is shared by all users since it's completely server-side in a single slice. You'll then want to start looking into storing data on a per-user level. In the simplest terms, this can be done by creating a map with the keys being a user's hostname and the value being their list of todos. <a href="https://github.com/gorilla/sessions">Gorilla sessions</a> allow this to be implemented in a more secure way.</p>
]]></content:encoded>
</item>




</channel>
</rss>
