<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Duckdb on zerokspot.com</title>
    <link>https://zerokspot.com/tags/duckdb/</link>
    <description>Recent content in Duckdb on zerokspot.com</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <lastBuildDate>Sun, 03 May 2026 11:30:00 +0200</lastBuildDate>
    
        <atom:link href="/tags/duckdb/index.xml" rel="self" type="application/rss+xml" />
    
    
    <item>
      
      <title>Simple data analysis setup</title>
      
      <link>https://zerokspot.com/weblog/2026/05/03/simple-data-analysis-setup/</link>
      <pubDate>Sun, 03 May 2026 11:30:00 +0200</pubDate>
      <guid>/weblog/2026/05/03/simple-data-analysis-setup/</guid>
      <description>
          &lt;p&gt;Over the last couple of months I&amp;rsquo;ve had to do a bit more number-crunching than usual in order to write design docs. In a recent example I had to work with a datasource that was slow to query (GitHub&amp;rsquo;s REST API) and eventually needed to work with 100,000+ data points.&lt;/p&gt;
&lt;p&gt;In order to demonstrate my setup there, I&amp;rsquo;ll work on a slightly contrived example: Get data about all PRs in the &lt;a href=&#34;https://github.com/grafana/grafana/&#34;&gt;grafana/grafana&lt;/a&gt; project within the last 6 months. Then try to find out which of these include documentation &lt;em&gt;and&lt;/em&gt; code changes (basically anything but changes to the docs folder).&lt;/p&gt;
&lt;h2 id=&#34;core-setup&#34;&gt;Core setup&lt;/h2&gt;
&lt;p&gt;For me, the most convenient way to think about larger amounts of data is using &lt;a href=&#34;https://jupyter.org/&#34;&gt;Jupyter&lt;/a&gt; notebooks. I have a local Jupyter Lab set up where I collect all my analysis into separate folders.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cd notebooks
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;uv init
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;uv add jupyterlab
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;uv add ipywidgets
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;uv add pygithub
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;uv run jupyter lab
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;setup&#34;&gt;Setup&lt;/h2&gt;
&lt;p&gt;Before being able to access GitHub data, I need to log in. My &lt;a href=&#34;https://cli.github.com/&#34;&gt;gh cli&lt;/a&gt; is already set up, so I&amp;rsquo;m going to use that instead of creating yet another PAT that would be just lying around on my system:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;from github import Github
from github.Auth import Token

token = !gh auth token
token = token[0]
auth = Token(token)

# Create a GitHub client using that token and
# with a page-size of 100 (default: 30):
gh = Github(auth=auth, per_page=100)
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;data-gathering&#34;&gt;Data gathering&lt;/h2&gt;
&lt;p&gt;Each analysis gets its own &lt;code&gt;data&lt;/code&gt; folder. Even if I have to reproduce something with a tiny dataset, I use this structure.&lt;/p&gt;
&lt;p&gt;The data folder is important since it allows me to not only keep my analysis reproducible but also speeds up iterations. Sometimes getting the data is actually the slow part that can take (depending on the data source) minutes, hours, or days. During the gathering phase I try to store as many chunk files as possible. Here I like to use &lt;a href=&#34;https://jsonlines.org/&#34;&gt;JSONLines&lt;/a&gt; files (one JSON document per line) where I just open the output file in append-mode. If my gathering script runs into an issue, I lose at most the last entry. At the same time, I don&amp;rsquo;t generate thousands of files as would be the case if I put one datapoint into its own file.&lt;/p&gt;
&lt;p&gt;Especially for long runs, it helps to have some kind of progress bar available that is more than just continuously printing &lt;code&gt;n/45678 elements processed&lt;/code&gt;. I&amp;rsquo;ve stumbled upon the &lt;a href=&#34;https://github.com/tqdm/tqdm&#34;&gt;tqdm&lt;/a&gt; package a while back and am now using it pretty much everywhere:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# uv add tqdm&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; datetime
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;from&lt;/span&gt; tqdm.notebook &lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; tqdm
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; pathlib
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; math
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; json
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;data_folder &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; pathlib&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;Path(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;data&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;data_folder&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;mkdir(exist_ok&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;True&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;range_start &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; datetime&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;datetime(&lt;span style=&#34;color:#ae81ff&#34;&gt;2025&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;11&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, tzinfo&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;datetime&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;UTC)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;range_end &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; datetime&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;datetime(&lt;span style=&#34;color:#ae81ff&#34;&gt;2026&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, tzinfo&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;datetime&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;UTC)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;repo &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; gh&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;get_repo(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;grafana/grafana&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# Unfortunately, the GH API for pull requests doesn&amp;#39;t allow &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# for time-based filtering, so we actually have to iterate&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# through everything &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;pull_requests &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; repo&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;get_pulls(state&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;closed&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;with&lt;/span&gt; tqdm(total&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;pull_requests&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;totalCount) &lt;span style=&#34;color:#66d9ef&#34;&gt;as&lt;/span&gt; progress:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; pr &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; pull_requests:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        progress&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;update()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;not&lt;/span&gt; (range_start &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;=&lt;/span&gt; pr&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;created_at &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; range_end):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;continue&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# We don&amp;#39;t want a single file per pull request and also not everything in one.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# Let&amp;#39;s split it up by pr.number / 1000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        state_file &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; data_folder &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{&lt;/span&gt;math&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;floor(pr&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;number &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1000&lt;/span&gt;)&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;000.jsonl&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;with&lt;/span&gt; state_file&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;open(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;a+&amp;#39;&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;as&lt;/span&gt; fp:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            files &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; pr&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;get_files()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            json&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;dump({
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;title&amp;#39;&lt;/span&gt;: pr&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;title,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;id&amp;#39;&lt;/span&gt;: pr&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;id,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;number&amp;#39;&lt;/span&gt;: pr&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;number,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;user_login&amp;#39;&lt;/span&gt;: pr&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;user&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;login,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;user_type&amp;#39;&lt;/span&gt;: pr&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;user&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;type,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;files&amp;#39;&lt;/span&gt;: [file&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;filename &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; file &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; files],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;body&amp;#39;&lt;/span&gt;: pr&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;body,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;created_at&amp;#39;&lt;/span&gt;: pr&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;created_at&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;isoformat(),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;merged_at&amp;#39;&lt;/span&gt;: pr&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;merged_at&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;isoformat() &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; pr&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;merged_at &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;None&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            }, fp)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            fp&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;write(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This snippet can take hours and hours (around 20 on my previous run). The problem is that GitHub doesn&amp;rsquo;t let me filter PRs based on a date range. That&amp;rsquo;s only available view the search API which is limited to 1000 items.&lt;/p&gt;
&lt;h2 id=&#34;data-analysis&#34;&gt;Data analysis&lt;/h2&gt;
&lt;p&gt;Now that I have around 10 JSONL files flying on my system, I can feed them into a &lt;a href=&#34;https://duckdb.org/&#34;&gt;DuckDB&lt;/a&gt; and treat everything like one large database table!&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# uv add duckdb&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; duckdb
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;duckdb&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;sql(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;SELECT * FROM &amp;#39;data/*.jsonl&amp;#39;&amp;#34;&lt;/span&gt;)\
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;to_view(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;pull_requests&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;duckdb&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;sql(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;SELECT count(*) FROM pull_requests&amp;#34;&lt;/span&gt;)\
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;show()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This gives me the following output:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;┌──────────────┐
│ count_star() │
│    int64     │
├──────────────┤
│         8292 │
└──────────────┘
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The dataset isn&amp;rsquo;t all that large so I can just put it into a view. The count-query will just do a &lt;code&gt;SELECT * FROM ...&lt;/code&gt; internally without caching. If the dataset were larger, I&amp;rsquo;d use &lt;code&gt;to_table&lt;/code&gt; instead to properly materialize the data.&lt;/p&gt;
&lt;p&gt;Now that all the JSON files are in a database, I can find out how many of the PRs created over those 6 months actually contained documentation. I&amp;rsquo;m going with a naive approach here and just consider anything that touched a file in the &lt;code&gt;docs/&lt;/code&gt; folder to be a documentation update:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# Creating a view again since we will need the filter a couple&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# more times:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;duckdb&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;sql(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    SELECT *
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    FROM pull_requests
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    WHERE len(array_filter(files, lambda file: starts_with(file, &amp;#39;docs/&amp;#39;))) &amp;gt; 0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;to_view(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;docs_pull_requests&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;duckdb&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;sql(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;SELECT 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    count(*) as docs_count,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    100 * docs_count / (SELECT count(*) FROM pull_requests) as docs_percentage
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;FROM docs_pull_requests
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;show()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Turns out, 1033 PRs included docs changes. That&amp;rsquo;s around 12.46%. Who created all these PRs?&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;duckdb&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;sql(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;	SELECT user_login, count(*) as count
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;	FROM docs_pull_requests
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;	GROUP BY user_login
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;	ORDER BY count DESC
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;	LIMIT 10
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;show()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Most came from an the grafana-delivery-bot, which makes sense since it is used to backport fixes into specific release branches:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;┌───────────────────────────┬───────┐
│        user_login         │ count │
│          varchar          │ int64 │
├───────────────────────────┼───────┤
│ grafana-delivery-bot[bot] │   291 │
│ urbiz-grafana             │    86 │
│ imatwawana                │    84 │
│ lwandz13                  │    61 │
│ jtvdez                    │    40 │
│ JohnnyK-Grafana           │    22 │
│ irenerl24                 │    19 │
│ macabu                    │    19 │
│ stephaniehingtgen         │    19 │
│ knylander-grafana         │    15 │
└───────────────────────────┴───────┘
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now back to the original question: &amp;ldquo;How many PRs contain docs and code changes?&amp;rdquo;. Again, I&amp;rsquo;m going with a naive approach here and consider any modifications to &lt;code&gt;.json&lt;/code&gt;, &lt;code&gt;.ts&lt;/code&gt;, or &lt;code&gt;.go&lt;/code&gt; files to be code changes:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ITH code_pull_requests &lt;span style=&#34;color:#66d9ef&#34;&gt;AS&lt;/span&gt; (
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;SELECT&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;FROM&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        pull_requests
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;WHERE&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        len(array_filter(files, lambda file: ends_with(file, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;.json&amp;#39;&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;or&lt;/span&gt; ends_with(file, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;.ts&amp;#39;&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;or&lt;/span&gt; ends_with(file, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;.go&amp;#39;&lt;/span&gt;))) &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;SELECT&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;count&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;) docs,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;count&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;) FILTER (id &lt;span style=&#34;color:#66d9ef&#34;&gt;IN&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;SELECT&lt;/span&gt; id &lt;span style=&#34;color:#66d9ef&#34;&gt;FROM&lt;/span&gt; code_pull_requests)) &lt;span style=&#34;color:#66d9ef&#34;&gt;AS&lt;/span&gt; docs_and_code,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ae81ff&#34;&gt;100&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; docs_and_code &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; docs &lt;span style=&#34;color:#66d9ef&#34;&gt;AS&lt;/span&gt; percentage
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;FROM&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    docs_pull_requests
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;┌───────┬───────────────┬────────────────────┐
│ docs  │ docs_and_code │     percentage     │
│ int64 │     int64     │       double       │
├───────┼───────────────┼────────────────────┤
│  1033 │           224 │ 21.684414327202322 │
└───────┴───────────────┴────────────────────┘
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That&amp;rsquo;s pretty much it! Jupyter + tqdm + DuckDB + JSONLines has become my personal dream team for any kind of quantitive analysis. While I never dreaded writing a design doc, now I consider the analysis part even fun. With every single of them I&amp;rsquo;ve learnt something new and awesome which made my setup better!&lt;/p&gt;
&lt;h2 id=&#34;ps-support-restarts&#34;&gt;PS.: Support restarts&lt;/h2&gt;
&lt;p&gt;I work primarily on a laptop and tend to move around a bit every day. This means that my data gathering steps need to support things like me closing my laptop, going offline, and resuming work somewhere completely different. Normally, you&amp;rsquo;d use some kind of state file for this. In this scenario I can do a little hack, though, since DuckDB is so fast when it comes to ingesting data:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;seen_ids &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; set([])
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;try&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    seen_id &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; set([row[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;] &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; row &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; duckdb&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;sql(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;SELECT number FROM &amp;#39;data/*.jsonl&amp;#39;&amp;#34;&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;fetchall()])
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;except&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;pass&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;With that in place, I can check in the PR loop if I have to also fetch the files again or not. I still have to loop through every pull request page, but without the files query it gets much faster. Thanks to that, I can cancel and restart a gathering run and only lose a few hours &amp;hellip; not tens of hours 😉&lt;/p&gt;
&lt;p&gt;For other API endpoints that allow more detailed querying, that window gets even shorted to the point where I just cancel and restart all the time.&lt;/p&gt;

      </description>
    </item>
    
    <item>
      
      <title>Pluribus season one</title>
      
      <link>https://zerokspot.com/weblog/2026/04/27/pluribus-season-one/</link>
      <pubDate>Mon, 27 Apr 2026 22:48:00 +0200</pubDate>
      <guid>/weblog/2026/04/27/pluribus-season-one/</guid>
      <description>
          &lt;p&gt;When I saw the trailer last year, I thought that might be one of those shows that I could either love or hate. Turns out, I absolutely loved season one of &lt;a href=&#34;https://en.wikipedia.org/wiki/Pluribus_(TV_series)&#34;&gt;Pluribus&lt;/a&gt;! The show starts with astronomers discovering a strong radio signal originating hundreds of light-years away. After lots of guessing and experimenting, it turns out to be an RNA sequence. That got injected into mice, a mouse bites a researcher, and so a mysterious infection starts to spread.&lt;/p&gt;
&lt;p&gt;A bit later, Carol Sturka (&lt;a href=&#34;https://en.wikipedia.org/wiki/Rhea_Seehorn&#34;&gt;Rhea Seehorn&lt;/a&gt;), a fantasy author with a tendency to drown her frustration in alcohol, returns home to Albuquerque (New Mexico) from a book tour. While &amp;ldquo;celebrating&amp;rdquo; the end of the tour at a bar with her wife, all of a sudden, people freeze. Some panic-driving later, everyone awakens again, but they are different&amp;hellip; everyone except Carol. Turns out that the infection has by now spread worldwide; everyone infected is now part of a single hive-mind. World peace has finally been reached, and everyone is happy. Everyone except Carol.&lt;/p&gt;
&lt;p&gt;Well, if the world is finally at peace, the show doesn&amp;rsquo;t have to be all that fast, and it definitely is not. A few reviews I&amp;rsquo;ve seen primarily complain that pretty much nothing happens in some episodes. To me, that&amp;rsquo;s somehow the point: Carol is pretty much alone; everything happens without her somehow in the background. But even in those &amp;ldquo;slow&amp;rdquo; episodes, you learn something new about &amp;ldquo;The others,&amp;rdquo; and over the 10 episodes of season one, a picture starts to emerge.&lt;/p&gt;
&lt;p&gt;Would I&amp;rsquo;ve liked a bit more revelation in those 8 hours? Probably, but the pacing was also just right for me. I wasn&amp;rsquo;t in the mood for another fast and action-centric show. I wanted something slow yet interesting, where the fascination comes from looking at the details and having enough time to do so. There is also a lot of sarcasm and humor in the whole setup despite humanity as we know it having disappeared.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s probably not for everyone (especially those who like fast-paced shows), but for me, it&amp;rsquo;s yet another show that hooks me into AppleTV+. Now I just have to wait until 2027 for the next season. 😩&lt;/p&gt;

      </description>
    </item>
    
    <item>
      
      <title>Paper Republic&#39;s beautiful stationery store</title>
      
      <link>https://zerokspot.com/weblog/2026/04/19/paper-republic-la-maison-store/</link>
      <pubDate>Sun, 19 Apr 2026 22:04:00 +0200</pubDate>
      <guid>/weblog/2026/04/19/paper-republic-la-maison-store/</guid>
      <description>
          &lt;p&gt;While walking through Vienna&amp;rsquo;s inner city on Friday, I stumbled upon Paper Republic&amp;rsquo;s official store in Vienna: &lt;a href=&#34;https://www.paper-republic.com/de-gb/blogs/paper-republic/la-maison-shop-opens-in-vienna&#34;&gt;La Maison&lt;/a&gt;. While it was already closed for the day, I decided to come back the next morning, and it was well worth it!&lt;/p&gt;
&lt;p&gt;The store in &lt;a href=&#34;https://www.openstreetmap.org/search?query=sonnenfelsgasse+3%2C+wien&amp;amp;zoom=16&amp;amp;minlon=-123.13860654830934&amp;amp;minlat=49.287472950866686&amp;amp;maxlon=-123.11560392379761&amp;amp;maxlat=49.29780096870456#map=19/48.209821/16.375731&#34;&gt;Sonnenfelsgasse 3&lt;/a&gt; is just beautiful! You&amp;rsquo;re greeted by classy, wooden furniture that looks like a combination of a cozy living room and a library. Right up front, you can browse through example books ranging from from the Grand Voyageur in all sizes, over the Portfolio to their newest: &lt;a href=&#34;https://www.paper-republic.com/de-at/products/le-trifold-leather-journal&#34;&gt;Le Trifold&lt;/a&gt;. A gorgeous cover that closes around your notebooks on two sides, offering space for up to 6 notebooks.&lt;/p&gt;
&lt;p&gt;Right behind that area is a table where you can see all the parts that make up their products, including tons of customization options. Finally, there is a coffee corner and a huge table inviting you to sit down, relax, and write.&lt;/p&gt;
&lt;p&gt;It was one of those moments where I really regretted that I had bought so many Traveler&amp;rsquo;s Notebook refills. If I didn&amp;rsquo;t have enough paper for a couple of years, I would have probably left the store again only after getting at least one leather cover (although not yet sure which one) and a handful of books. Since all their products are either fully or partially produced in Austria and mostly follow normal DIN standards, it would also be easier for me to get more&amp;hellip; At this point I&amp;rsquo;m pretty sure that my stock in refills will only be able to hold me back for so long until I go back to La Maison and leave with more than just a single notebook and a (magnetic ❤️) pen clip that fits perfectly onto my Plotter Mini 5 😅&lt;/p&gt;

      </description>
    </item>
    
    <item>
      
      <title>E-bikes are not for lightweight commuting</title>
      
      <link>https://zerokspot.com/weblog/2026/04/12/lightweight-bike-commuting/</link>
      <pubDate>Sun, 12 Apr 2026 13:00:00 +0200</pubDate>
      <guid>/weblog/2026/04/12/lightweight-bike-commuting/</guid>
      <description>
          &lt;p&gt;Last summer I saw more and more people with things that looked like racing bikes but were a bit more sturdy and at the same time seemed - I don&amp;rsquo;t know how to put it - casual (?) to me. I looked around a bit and learnt that these were &amp;ldquo;gravel bikes&amp;rdquo; that I had heard about before. For some reason I had always imaged &amp;ldquo;gravel bikes&amp;rdquo; to be those mountain bikes with really fat tires. No idea why. All of a sudden, though, I was interested!&lt;/p&gt;
&lt;p&gt;That could be something that could help me work on my fitness WHILE being a fast and lightweight way to commute! As much as I love my e-bike, there is one thing it is definitely not: lightweight. At the same time I didn&amp;rsquo;t want to dive into yet another rabbit hole. I started looking for a mostly commute-ready option and eventually stumbled upon the &lt;a href=&#34;https://www.cube.eu/de-en/bikes/gravel&#34;&gt;Cube NuRoad&lt;/a&gt;. They also had a fully-equipped (FE) variant which comes with lights and a rear carrier. Luckily there was a Cube store in Graz AND Klagenfurt and so I could talk to the folks there about what I had in mind. Eventually I settled on the &lt;a href=&#34;https://www.cube.eu/de-en/cube-nuroad-race-fe-royalgreen-n-black/129470&#34;&gt;NuRoad Race FE&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Over the last couple of months I&amp;rsquo;ve had to go quite frequently from Graz to Klagenfurt for various appointments. While the public transport at each end is excellent, I missed having a bike with me. A couple of weeks ago I took my e-bike with me on such a trip and it was great &lt;em&gt;there&lt;/em&gt;, but getting it to and from Klagenfurt was extremely annoying! In &lt;a href=&#34;https://www.vagonweb.cz/popisy/mista/DB/ICE4-k-7_Bpmdzf.png&#34;&gt;German ICEs&lt;/a&gt; and &lt;a href=&#34;https://www.vagonweb.cz/popisy/mista/OeBB/RJ_Bmpvz-8490.png&#34;&gt;Austrian Railjets&lt;/a&gt; you&amp;rsquo;re mostly supposed to hang your bike from a hook. Lifting a 23kg+ bike in such a tight space wasn&amp;rsquo;t easy. It worked but it was a struggle.&lt;/p&gt;
&lt;p&gt;Then I did the same trip with my lightweight gravel bike and that story was a completely different one. I didn&amp;rsquo;t have to take the elevators at the train station, I could simply carry the bike between overpass and platform. I didn&amp;rsquo;t struggle with getting the bike on the train and onto the hook but could treat it somehow like a simple bag. Once the door next to the bike compartment was defect and so I had to carry the bike through the whole car from another door. This &lt;em&gt;would not have worked&lt;/em&gt; with my e-bike.&lt;/p&gt;
&lt;p&gt;So, now I have a gravel bike that I use for commuting to places where my e-bike would simply not go. At the same time I try to use it also for intra-city commuting to get more secure on it while improving my fitness. This doesn&amp;rsquo;t yet mean that I will also use it for &amp;ldquo;proper&amp;rdquo; gravel biking in the future. I might, it looks like a nice hobby and it&amp;rsquo;s probably something that could be more fun than going to the gym. I&amp;rsquo;ll have to learn a lot, though! From doing a standing start over re-learning when and how to shift gears properly to just relaxing my upper body enough to better distribute my weight and not feeling every single bump in the road. But even if I stick with commuting, this will we fun!&lt;/p&gt;

      </description>
    </item>
    
    <item>
      
      <title>The TODO Stand</title>
      
      <link>https://zerokspot.com/weblog/2026/04/05/the-todo-stand/</link>
      <pubDate>Sun, 05 Apr 2026 22:38:00 +0200</pubDate>
      <guid>/weblog/2026/04/05/the-todo-stand/</guid>
      <description>
          &lt;p&gt;When I first stumbled upon the &lt;a href=&#34;https://ugmonk.com/en-at/products/analog-starter-kit?variant=37022218748054&#34;&gt;Analog Daily Focus kit&lt;/a&gt; by Ugmonk a couple of years ago, I really liked the basic idea: Having your daily todo list prominently on your desk where you could quickly tick stuff off or add new items sounded awesome! I didn&amp;rsquo;t pick it up because it felt too inflexible and static to me. What would I do while on the go or at a different office location? Where would I get new cards?&lt;/p&gt;
&lt;p&gt;Recently I had received a little business/picture stand for my desk that remembered me off that concept. I combined it with my two Plotters and, all of a sudden, I had a working setup that solved my original issues!&lt;/p&gt;
&lt;p&gt;I had recently started taking notes out of my Plotters while working on them. Instead of letting them just fly around on my desk, I thought about using that stand! Of course it is not at stylish as Ugmonks product (or one of that hundreds of similar products) but it offered me the flexibility to have my tasks visible while at my desk and a mobile solution by just putting the project holders and task lists back into their respective Plotter binders.&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&#34;https://zerokspot.com/api/photos/2026/04/05/IMG_5457.jpeg?profile=1024&#34;&gt;
&lt;figcaption&gt;Card stand with a Mini 5 5mm dot-grid paper on the left and a Bible-size 5mm dot-grid paper on the right&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;I&amp;rsquo;m pretty sure I&amp;rsquo;m going to iterate on the stand itself in the future. It only works for me right now since I use a postcard or a project folder in the back so that I can put both my personal and my work lists onto the stand at the same time. The &lt;a href=&#34;https://shop.minimaldesksetups.com/products/card-pencil-tray&#34;&gt;card &amp;amp; pencil tray by MinimalDesktopSetups&lt;/a&gt; or a similar product might be a better fit. Although, it would probably still require a postcard or something like that to stabilize the Midori paper.&lt;/p&gt;
&lt;p&gt;That being said, I really like my current setup there! It might even make me use Plotter project folders more in the future since I&amp;rsquo;m now more willing to just open the binder and get paper sheets out of it when I&amp;rsquo;m working on them!&lt;/p&gt;

      </description>
    </item>
    
    <item>
      
      <title>Still waiting for a new Apple TV</title>
      
      <link>https://zerokspot.com/weblog/2026/03/29/still-waiting-for-a-new-apple-tv/</link>
      <pubDate>Sun, 29 Mar 2026 17:38:00 +0200</pubDate>
      <guid>/weblog/2026/03/29/still-waiting-for-a-new-apple-tv/</guid>
      <description>
          &lt;p&gt;I&amp;rsquo;ve now been &amp;ldquo;hanging in there&amp;rdquo; for a new &lt;a href=&#34;https://www.apple.com/apple-tv-4k/&#34;&gt;Apple TV&lt;/a&gt; for a long time now. Back in 2017, I got the very &lt;a href=&#34;https://everymac.com/systems/apple/apple-tv/specs/apple-tv-4k-5th-generation-2017-specs.html&#34;&gt;first Apple TV 4K&lt;/a&gt; with the black remote that was and still is really hard to use. Too much touch, too little click 😅 The rumors got really strong throughout 2025 that a new box was imminent, but that never happened. The hold-up seems to be &lt;a href=&#34;https://www.macrumors.com/2026/03/09/apple-tv-4k-new-homepod-launch-timing/&#34;&gt;Siri&lt;/a&gt;. I might be one of the few people out there who really doesn&amp;rsquo;t care about the AI integration there. I use my Apple TV as an Apple Home hub and as the device to watch movies and shows through outside of normal television.&lt;/p&gt;
&lt;p&gt;So why am I actually waiting for a new Apple TV box? Well, I could get the version that was released in 2022. It would be completely serviceable for what I&amp;rsquo;m looking for: a better remote that I can actually also find again when it gets lost on my black sofa and better support for more modern smart-home protocols (i.e. Matter). 4GB of memory instead of 3GB, more disk space, faster CPU; all nice things, but being able to find my remote again and replacing some old smart-home integrations with more modern ones since I use the Apple TV as my Apple Home hub.&lt;/p&gt;
&lt;p&gt;But 4-year-old hardware is still 4 years old, and so it makes little sense for me to buy that if a new iteration is around the corner&amp;hellip; and has been for a year.&lt;/p&gt;

      </description>
    </item>
    
    <item>
      
      <title>Avoid TODO silos when you&#39;re part of a team</title>
      
      <link>https://zerokspot.com/weblog/2026/03/22/avoid-todo-silos/</link>
      <pubDate>Sun, 22 Mar 2026 17:10:00 +0100</pubDate>
      <guid>/weblog/2026/03/22/avoid-todo-silos/</guid>
      <description>
          &lt;p&gt;You know that I love working on my personal productivity system. I have my various paper notebooks and planners and don&amp;rsquo;t even get me started on the various digital tools that help me keep my life in order. But &amp;hellip; when working in a team, I&amp;rsquo;ve learnt to tune them down a bit.&lt;/p&gt;
&lt;p&gt;As I wrote last week, I&amp;rsquo;m now using a mix of &lt;a href=&#34;https://zerokspot.com/weblog/2026/03/15/bullet-journal-plus-kanban/&#34;&gt;Kanban and Bullet Journaling&lt;/a&gt; for my personal projects. This is a silo but it&amp;rsquo;s designed to be flexible and &lt;em&gt;could&lt;/em&gt; be opened up. The Kanban board could also be on GitHub or &lt;a href=&#34;https://www.openproject.org/&#34;&gt;OpenProject&lt;/a&gt; and shared with multiple people. And that&amp;rsquo;s exactly what I&amp;rsquo;m doing at work. Just take my blog posts from last week, replace Obsidian with &lt;a href=&#34;https://docs.github.com/en/issues/planning-and-tracking-with-projects/learning-about-projects/about-projects&#34;&gt;GitHub Projects&lt;/a&gt; where the whole team has access and that&amp;rsquo;s my system at work.&lt;/p&gt;
&lt;p&gt;Previously, I&amp;rsquo;ve used OmniFocus or my Bullet Journal exclusively. There was no clear loop that involved issues that my team saw and operational tasks for a specific day. Of course I&amp;rsquo;ve worked on the shared task pool and was part of the team. The whole setup felt artificial, though, like there were two worlds, each fully functional, but only connected through that simple wooden bridge that is maintained by that one guy in Nebraska.&lt;/p&gt;
&lt;p&gt;My current setup uses the board as shared state where everyone including myself keeps their tasks up-to-date and I pick my tasks from there instead of from some secret silo of ideas and projects. I cannot tell you how liberating that switch felt in the end. It also made it clear to me that I &lt;em&gt;don&amp;rsquo;t&lt;/em&gt; have to work on everything; that there &lt;em&gt;are&lt;/em&gt; other folks that could pick up stuff for which I simply do not have time.&lt;/p&gt;
&lt;p&gt;I still have my Bullet Journal for my daily tasks but anything that is beyond that daily/operational scope goes primarily on the shared GitHub project and is shared with everyone! Can recommend!&lt;/p&gt;

      </description>
    </item>
    
    <item>
      
      <title>Bullet Journal &#43; Kanban</title>
      
      <link>https://zerokspot.com/weblog/2026/03/15/bullet-journal-plus-kanban/</link>
      <pubDate>Sun, 15 Mar 2026 15:15:00 +0100</pubDate>
      <guid>/weblog/2026/03/15/bullet-journal-plus-kanban/</guid>
      <description>
          &lt;p&gt;As I wrote &lt;a href=&#34;https://zerokspot.com/weblog/2025/12/28/techo-kaigi-for-2026/&#34;&gt;back in December&lt;/a&gt; I still required GTD + OmniFocus since I probably couldn&amp;rsquo;t handle my high and diverse workload with a Bullet Journal. Weirdly enough, exactly that high workload caused some problems with my OmniFocus setup. I could no longer see the big picture. Projects were far too fine-grained and folders did not help. I also didn&amp;rsquo;t really know based on just looking at the system, what project was the most important one &lt;em&gt;right now&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;One of GTD&amp;rsquo;s and OmniFocus&amp;rsquo; major advantage is that it shows you primarily those tasks that you &lt;em&gt;can&lt;/em&gt; work on right now based on context information. Outside of very broad location information, contexts stopped working for me a long time ago, though. Perhaps it was the move to home-office, perhaps I didn&amp;rsquo;t try hard enough, but configuring &lt;a href=&#34;https://www.omnigroup.com/omnifocus/features/#Custom-Perspectives&#34;&gt;perspectives&lt;/a&gt; for common context-combination was simply something that I don&amp;rsquo;t do.&lt;/p&gt;
&lt;p&gt;For me, work needs either to be done by a certain time due to external constraints or it can be done following just broad priorities. And GTD isn&amp;rsquo;t great at providing a high-level view over projects that is prioritized.&lt;/p&gt;
&lt;h2 id=&#34;high-level-view&#34;&gt;High-level view&lt;/h2&gt;
&lt;p&gt;That made me think about your &lt;a href=&#34;https://en.wikipedia.org/wiki/Kanban_board&#34;&gt;classic Kanban board&lt;/a&gt;. The idea of such a board is to visualize work, to provide either a whole team or just a single person an overview of what needs to be done in the future (aka &amp;ldquo;the backlog&amp;rdquo;), what is currently being done, and what has been completed. Work is in the simplest case a piece of paper/sticky note that is put into one of these status columns. The backlog in the in-progress column should be sorted based on priority so that it&amp;rsquo;s clear what to focus on (next).&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;/media/2026/kanban-board.svg&#34;&gt;&lt;figcaption&gt;Kanban board with projects and a project with a tasklist&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;That&amp;rsquo;s what I&amp;rsquo;ve now started to implement for my personal projects using Obsidian and the &lt;a href=&#34;https://github.com/obsidian-community/obsidian-kanban&#34;&gt;Kanban plugin&lt;/a&gt;. Each project in there is a Markdown file that in turn contains a list of all the things that need to be done to finish the project.&lt;/p&gt;
&lt;p&gt;I went with a digital system here again, because managing projects in a Bullet Journal has never really worked for me. It just involves too much repetition and it&amp;rsquo;s just to easy to forget something while moving tasks from project pages to weekly pages to daily pages to keeping a high-level view of why I wanted to do the project in the first place.&lt;/p&gt;
&lt;h2 id=&#34;reviewing-and-prioritizing&#34;&gt;Reviewing and prioritizing&lt;/h2&gt;
&lt;p&gt;This approach only works if the tasks stay organized and reviewed. That&amp;rsquo;s what I&amp;rsquo;m doing at least once per week, checking the projects if they still make sense, switching priorities if necessary, and keeping the task lists in them up-to-date.&lt;/p&gt;
&lt;p&gt;Every week I then create a weekly page in my notebook where I write down all the tasks that need to be done during that week based on deadlines written down in the project files.&lt;/p&gt;
&lt;p&gt;Every morning I check with that list and break down what I want to tackle on that particular day.&lt;/p&gt;
&lt;h2 id=&#34;kanban--bullet-journaling&#34;&gt;Kanban + Bullet Journaling&lt;/h2&gt;
&lt;p&gt;So that&amp;rsquo;s it! Kanban for the strategic, a Bullet Journal for the tactical and operational level. While I&amp;rsquo;ve only done that for my personal projects for the last month, I&amp;rsquo;ve had it for work for about half a year now.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;d also like to limit my work-in-progress column but that&amp;rsquo;s currently not realistic. Hopefully by the end of the year, though. For now having a visualization of the things that need to get done that is more useful than OmniFocus&amp;rsquo; project list needs to be enough. That and finally using paper + ink again for task lists 😅&lt;/p&gt;

      </description>
    </item>
    
    <item>
      
      <title>Review: Kaweco Piston AL Sport</title>
      
      <link>https://zerokspot.com/weblog/2026/03/08/review-kaweco-piston-al-sport/</link>
      <pubDate>Sun, 08 Mar 2026 16:30:00 +0100</pubDate>
      <guid>/weblog/2026/03/08/review-kaweco-piston-al-sport/</guid>
      <description>
          &lt;p&gt;When I got my first Kaweco AL Sport in 2020, I noted that I liked then pen very much but wasn&amp;rsquo;t all that happy with the &lt;a href=&#34;https://zerokspot.com/weblog/2020/09/20/kaweco-al-sport-fountain-pen/&#34;&gt;converter&amp;rsquo;s ink capacity&lt;/a&gt;. Since then I&amp;rsquo;ve used the pen on and off, tried various inks, failed at getting Platinum Carbon Black to work with it, and experimented with different nib units. After a while though, I nearly forgot about it. But then Kaweco released a version that promised to fix that one complaint that I had with the pen: The &lt;a href=&#34;https://www.kaweco-pen.com/en/series/al-sport/944/kaweco-piston-sport-al-black/gold?c=248&#34;&gt;Kaweco Piston AL Sport&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;Last year, my partner gave me the black edition with gold trimming for my birthday and it not only became the favorite pen in my collection but also made me rediscover my first Kaweco and get another one in orange!&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&#34;https://zerokspot.com/api/photos/2026/03/08/IMG_5377.jpeg?profile=1024&#34;&gt;
&lt;figcaption&gt;Top: Kaweco AL Sport, bottom: Kaweco Piston AL Sport&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Unlike the AL Sport, the Piston AL Sport by default has a clip in the same color as the rest of the trimming. The knob to operate the piston is hidden below a small cap at the bottom of the pen (after the gold ring you can see in the pictures). My initial fear was that this would be easy to come loose and get lost, but it&amp;rsquo;s quite firm. The knob works very smoothly and has a more solid feel than for instance the one offered by the TWSBI Diamond 580.&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&#34;https://zerokspot.com/api/photos/2026/03/08/IMG_5378.jpeg?profile=1024&#34;&gt;
&lt;figcaption&gt;Kaweco Piston AL Sport with cap unscrewed&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;When you remove the main cap you will also see a quite generous ink window. As with any such window, depending on which ink you&amp;rsquo;re using it might end up being completely useless. Right now I&amp;rsquo;m using &lt;a href=&#34;https://www.kaweco-pen.com/en/accessories/ink/ink-bottles/784/kaweco-ink-bottle-royal-blue-50-ml?c=105&#34;&gt;Kaweco Royal Blue&lt;/a&gt; (because I haven&amp;rsquo;t written with that color ever since my school years) and there the window offers enough for a status indicator.&lt;/p&gt;
&lt;p&gt;The nib is so far the best Kaweco nib I&amp;rsquo;ve had so far. Perhaps I was just lucky here but I simply haven&amp;rsquo;t yet found a thing that I do not like about this pen ❤️&lt;/p&gt;

      </description>
    </item>
    
    <item>
      
      <title>Be nice to your bus driver</title>
      
      <link>https://zerokspot.com/weblog/2026/02/28/be-nice-to-your-bus-driver/</link>
      <pubDate>Sat, 28 Feb 2026 20:51:00 +0100</pubDate>
      <guid>/weblog/2026/02/28/be-nice-to-your-bus-driver/</guid>
      <description>
          &lt;p&gt;During my recent trip to &lt;a href=&#34;https://www.reddit.com/r/vancouver/s/MLpW8agw5O&#34;&gt;Vancouver&lt;/a&gt; I noticed something that I&amp;rsquo;ve come to really love in &lt;a href=&#34;https://www.reddit.com/r/Portland/s/0rcCrzNwAF&#34;&gt;Portland, Oregon&lt;/a&gt;: It is pretty much expected of you that you greet your bus driver when entering and thank them when leaving.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s such a small gesture but it changes the whole feeling inside the vehicle to the better. Ever since my first trip to Portland I&amp;rsquo;ve tried to greet more and just be a more friendly person when using public transportation.&lt;/p&gt;
&lt;p&gt;Here for instance the Austrian Railways has to have campaigns to make people aware that shouting and attacking staff is not acceptable&amp;hellip; In &lt;a href=&#34;https://www.derstandard.at/story/3000000307422/nach-gewaltattacke-bei-ticketkontrolle-wie-oebb-und-wiener-linien-ihr-personal-schuetzen&#34;&gt;Germany&lt;/a&gt; a DB employee has recently been killed!&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m pretty sure that me greeting public transportation staff won&amp;rsquo;t change the world here, but perhaps it will at least bring a smile to their face and calm someone else down&amp;hellip;&lt;/p&gt;

      </description>
    </item>
    
    <item>
      
      <title>Complex app-logic configuration with Rego</title>
      
      <link>https://zerokspot.com/weblog/2026/02/21/complex-applogic-config-with-rego/</link>
      <pubDate>Sat, 21 Feb 2026 21:20:00 +0100</pubDate>
      <guid>/weblog/2026/02/21/complex-applogic-config-with-rego/</guid>
      <description>
          &lt;p&gt;Over the last couple of years I&amp;rsquo;ve written tons of little Go services that required part of their logic to be dynamically configurable. For more complex scenarios it might be time for an embedded scripting language like Lua. Especially when it comes to situations where some kind of document needs to be dynamically generated or a decision to be made based on user-defined input, there is an easier way: &lt;a href=&#34;https://www.openpolicyagent.org/&#34;&gt;OpenPolicyAgent&lt;/a&gt;&amp;rsquo;s &lt;a href=&#34;https://www.openpolicyagent.org/docs/policy-language&#34;&gt;Rego language&lt;/a&gt; and library.&lt;/p&gt;
&lt;p&gt;(Disclaimer: Parts of the code examples were generated with Claude Code.)&lt;/p&gt;
&lt;h2 id=&#34;example-scenarios&#34;&gt;Example scenarios&lt;/h2&gt;
&lt;p&gt;Just to give you some examples for this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A user sends a request to a server and we need to decided based on the data if that request is allowed or not.&lt;/li&gt;
&lt;li&gt;Given a GitHub pull request define what labels it should receive based on the files that are modified.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Rego is a policy language and its tooling is pretty much optimized for the first scenario. What Rego actually generates when you query a policy, though, is a JSON document, so you can also use it to provide more complex answers to your input.&lt;/p&gt;
&lt;h2 id=&#34;example-implementation&#34;&gt;Example implementation&lt;/h2&gt;
&lt;p&gt;So let&amp;rsquo;s go with the the scenario where we want to get some labels that should be associated with a pull request. If a pull request has a title that starts with &amp;ldquo;fix&amp;rdquo;, then it should also get a &lt;code&gt;type:fix&lt;/code&gt;. Additionally, if the pull request modifies a file inside the &lt;code&gt;internal/auth&lt;/code&gt; folder, then the label &lt;code&gt;component:auth&lt;/code&gt; should also be set:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-rego&#34; data-lang=&#34;rego&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;//&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Filepath&lt;/span&gt;: &lt;span style=&#34;color:#a6e22e&#34;&gt;policies&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;/pullrequest_enrichment.rego
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;package pullrequest_enrichment
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;labels contains &amp;#34;type:fix&amp;#34; if {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;	startswith(input.title, &amp;#34;fix&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;labels contains &amp;#34;component:auth&amp;#34; if {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;	all_files := {f | some f in input.added_files} |
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;		{f | some f in input.changed_files} |
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;		{f | some f in input.deleted_files}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;	some file in all_files
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;	startswith(file, &amp;#34;internal/&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;auth&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You can now use that policy with the &lt;code&gt;github.com/open-policy-agent/opa/v1/rego&lt;/code&gt; package:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; (
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;context&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;github.com/open-policy-agent/opa/v1/rego&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;input&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;PullRequestInput&lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;Title&lt;/span&gt;:        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;fix: validate auth token expiry&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;AddedFiles&lt;/span&gt;:   []&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;{},
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;ChangedFiles&lt;/span&gt;: []&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;{&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;internal/auth/token.go&amp;#34;&lt;/span&gt;},
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;DeletedFiles&lt;/span&gt;: []&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;{},
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;ctx&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;context&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Background&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Create a prepared query that could be used in theory&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// for multiple inputs:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;preparedQuery&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;rego&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;New&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;rego&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Query&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;data.pullrequest_enrichment.labels&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// The .rego file we created above is stored inside&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// a folder called &amp;#34;policies&amp;#34; so we can load it from&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// there:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;rego&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Load&lt;/span&gt;([]&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;{&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;./policies/&amp;#34;&lt;/span&gt;}, &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;).&lt;span style=&#34;color:#a6e22e&#34;&gt;PrepareForEval&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;ctx&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;// ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Now evaluate the input against the policy to receive&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// a result set:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;rs&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;pq&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Eval&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;ctx&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;rego&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;EvalInput&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;input&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; len(&lt;span style=&#34;color:#a6e22e&#34;&gt;rs&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;||&lt;/span&gt; len(&lt;span style=&#34;color:#a6e22e&#34;&gt;rs&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;].&lt;span style=&#34;color:#a6e22e&#34;&gt;Expressions&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// No results, so no labels were returned.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Resultsets are quite generic and so we need to do a bit&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// of typecasting to got the results&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;items&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;_&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;rs&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;].&lt;span style=&#34;color:#a6e22e&#34;&gt;Expressions&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;].&lt;span style=&#34;color:#a6e22e&#34;&gt;Value&lt;/span&gt;.([]&lt;span style=&#34;color:#66d9ef&#34;&gt;any&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;labels&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; make([]&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, len(&lt;span style=&#34;color:#a6e22e&#34;&gt;items&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;_&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;item&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;range&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;items&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;s&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;ok&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;item&lt;/span&gt;.(&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;); &lt;span style=&#34;color:#a6e22e&#34;&gt;ok&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#a6e22e&#34;&gt;labels&lt;/span&gt; = append(&lt;span style=&#34;color:#a6e22e&#34;&gt;labels&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;s&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;allowed&#34;&gt;Allowed?&lt;/h2&gt;
&lt;p&gt;For the scenario where we have a user request as input and would like to know if it&amp;rsquo;s allowed or not, there is a helper for results that have the &amp;ldquo;allow&amp;rdquo; property which is just a boolean:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;rs&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;_&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;pq&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Eval&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;ctx&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;rego&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;EvalInput&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;input&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;result&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;rs&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Allowed&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;These policies are basically configuration that you can deploy separately from your core application. These policies can get quite complex. The cool thing about Rego&amp;rsquo;s tooling is that you can also create unit tests:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-rego&#34; data-lang=&#34;rego&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;//&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Filepath&lt;/span&gt;: &lt;span style=&#34;color:#a6e22e&#34;&gt;policies&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;pullrequest_enrichment_test&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;rego&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;package&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;pullrequest_enrichment_test&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;data&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;pullrequest_enrichment&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;test_labels_contains_type_fix_when_title_starts_with_fix &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;type:fix&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;pullrequest_enrichment&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;labels&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;with&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;input&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;as&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;title&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;fix: correct null pointer&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;After installing the OpenPolicyAgent CLI, you can then run these tests:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ cd policies
$ opa test .
PASS: 1/1
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;part-of-my-toolbox&#34;&gt;Part of my toolbox&lt;/h2&gt;
&lt;p&gt;Admittedly, using Rego as a document generator like it&amp;rsquo;s done in the example for getting a list of labels is probably not part of its original mission, but Rego is useful for that and so many other scenarios that it has become part of my standard toolbox!&lt;/p&gt;

      </description>
    </item>
    
    <item>
      
      <title>Exploring Vancouver</title>
      
      <link>https://zerokspot.com/weblog/2026/02/14/exploring-vancouver/</link>
      <pubDate>Sat, 14 Feb 2026 22:03:00 +0100</pubDate>
      <guid>/weblog/2026/02/14/exploring-vancouver/</guid>
      <description>
          &lt;p&gt;If you’re waiting for a post about this year’s FOSDEM, I’ll have to disappoint you. Due to a company event in Vancouver, which had been scheduled in the week before FOSDEM, I opted to stay a bit longer in Canada and not to sleep through Brussels due to being jet-lagged.&lt;/p&gt;
&lt;p&gt;Including a stop in Frankfurt it took my partner and me about 15 hours to get from Graz to Vancouver. The flight itself was good (although the captain for the flight to Vancouver was a bit late 😂) and we then went straight to Waterfront Station, the public transport fanatics that we are, using the Canada Line and then walking the rest of the way to the &lt;a href=&#34;https://www.marriott.com/en-us/hotels/yvrwi-the-westin-bayshore-vancouver/overview/&#34;&gt;Westin Bayshore&lt;/a&gt; hotel.&lt;/p&gt;
&lt;p&gt;Compared to what we had in Graz, the weather on that day and the first four days in general was amazing! We used that well and explored the city on foot and by bus, trying small coffee shops like &lt;a href=&#34;https://timbertraincoffeeroasters.com/&#34;&gt;Timbertrain Coffee Roasters&lt;/a&gt; and drinking hot chocolate at &lt;a href=&#34;https://www.kafkascoffee.ca/&#34;&gt;Kafka&amp;rsquo;s Café&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;capilano-suspension-bridge-park&#34;&gt;Capilano Suspension Bridge park&lt;/h2&gt;
&lt;p&gt;Public transport allowed us to also get to sights outside of the city center like the amazing &lt;a href=&#34;https://www.capbridge.com/&#34;&gt;Capilano Suspension Bridge Park&lt;/a&gt;. While the suspension bridge itself was huge and just pure joy to walk across with a great view down into a river valley, the highlight was the park north of the bridge. Remember the Ewok village in Star Wars 6? It felt exactly like that plus magical. In a height of around 10-15 meters there were walkways built between the trees and LEDs everywhere to give the whole setup a &amp;ldquo;magic forest&amp;rdquo; feeling. It was stunning!&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&#34;https://zerokspot.com/api/photos/2026/02/14/IMG_5106.jpeg?profile=1024&#34;&gt;
&lt;figcaption&gt;&#34;Magic forest&#34; atmosphere at Capilano Suspension Bridge Park&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Also: Best gift shop ever! I could have bought half of what they had. Heck, even the restaurant looked great!&lt;/p&gt;
&lt;h2 id=&#34;for-foodies&#34;&gt;For foodies!&lt;/h2&gt;
&lt;p&gt;That&amp;rsquo;s something we learnt pretty quickly: You can eat very well in Vancouver and there is a lot of pride flying around for that. Rightly so! Awesome seafood everywhere and thanks to Asian immigration throughout the centuries high quality Japanese meals (we didn&amp;rsquo;t try Korean nor Chinese restaurants due to time restraints):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.vancouverdine.com/carderos/&#34;&gt;Cardero&amp;rsquo;s&lt;/a&gt;: Fish restaurant built on stilts in the Coal Harbour next to the hotel with a cozy atmosphere. This was my personal favorite restaurant during the trip.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://thesequel.ca/&#34;&gt;The Sequel&lt;/a&gt;: Fine dining with great cocktails. I felt slightly underdressed with my raincoat and hoodie 😉&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://tomsushi.ca/&#34;&gt;Tom Sushi&lt;/a&gt;: After hearing that Vancouver was known for great sushi, we had to try, and we definitely weren&amp;rsquo;t disappointed at Tom Sushi 🙂&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.hellonori.com/&#34;&gt;Hello Nori&lt;/a&gt;: Never had a Japanese hand roll before and it was great!&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://tapandbarrel.com/locations/bridges/&#34;&gt;Tap &amp;amp; Barrel Bridges&lt;/a&gt;: Sports bar with a nice selection of beers and decent food. Unless you really enjoy sports bars, though, there are places with higher quality for the money.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://stanleyparkbrewing.com/pages/brewpub&#34;&gt;Stanley Park Brewing Restaurant&lt;/a&gt;: Brew pub with good beer, nice atmosphere, and decent pub grub. You come for the beer, though.&lt;/li&gt;
&lt;li&gt;and more but these were the major ones.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Most of these we also tried using the &lt;a href=&#34;https://www.dineoutvancouver.com/&#34;&gt;&amp;ldquo;Dine Out Vancouver Festival&amp;rdquo;&lt;/a&gt; which took place from Jan 21 to Feb 6. Participating restaurants offered a 3 course menu for CAD 45 - 80 depending on the restaurant. Most of the dishes you could also find on the normal menu and so this was great advertising for the restaurants.&lt;/p&gt;
&lt;h2 id=&#34;museums-and-culture&#34;&gt;Museums and culture&lt;/h2&gt;
&lt;p&gt;While we had great weather during the first four days the rest of the trip was rainy all the way through. Perfect for visiting some museums!&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&#34;https://zerokspot.com/api/photos/2026/02/14/IMG_5196.jpeg?profile=1024&#34;&gt;
&lt;figcaption&gt;Museum of Vancouver&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;First we went to the &lt;a href=&#34;https://museumofvancouver.ca/&#34;&gt;Museum of Vancouver&lt;/a&gt;. There we saw three exhibitions: It all started with one about Long-COVID victims, showing their daily routines and how they are coping with the limited support that is available through the health system. The main attraction was the overview of the cities history, though, with a big focus being on immigrants from Asia and how they were treated. Part of that was also a summary of &lt;a href=&#34;https://suitcaseproject.ca&#34;&gt;&amp;ldquo;The Suitcase Project&amp;rdquo;&lt;/a&gt; where people were tasked to pack for the situation that they&amp;rsquo;d have to leave their home on short notice.&lt;/p&gt;
&lt;p&gt;On another day we went all the way down to the campus of the University of British Columbia to visit the &lt;a href=&#34;https://moa.ubc.ca/&#34;&gt;Museum of Anthropology&lt;/a&gt;. Here we learnt a lot about the indigenous people and culture (combined with another exhibit at the Museum of Vancouver). The museum houses tons and tons of sculptures, totem poles, and clothes, all beautiful halls.&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&#34;https://zerokspot.com/api/photos/2026/02/14/IMG_5254.jpeg?profile=1024&#34;&gt;
&lt;figcaption&gt;Totem poles at the Museum of Anthropology&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;We were a couple of hours there and only scratched on the surface. I&amp;rsquo;m pretty sure, that when I eventually return to Vancouver, I&amp;rsquo;ll visit both museums again!&lt;/p&gt;
&lt;h2 id=&#34;stanley-park-and-the-aquarium&#34;&gt;Stanley Park and the Aquarium&lt;/h2&gt;
&lt;p&gt;What made the whole trip even more special was that our hotel was situated pretty much next to the &lt;a href=&#34;https://en.wikipedia.org/wiki/Stanley_Park&#34;&gt;Stanley Park&lt;/a&gt;. The 4km² area offers not only your usual “park” but also lakes, natural forests, the &lt;a href=&#34;https://www.vanaqua.org/&#34;&gt;Vancouver Aquarium&lt;/a&gt;, and “Seawall” which offers a walkway + bike lane around the whole park. We went there right on the second day, when the weather was still good. Imagine my surprise finding a sand beach so close to the heart of a city with benches to enjoy the ocean breeze in the hot sun!&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&#34;https://zerokspot.com/api/photos/2026/02/14/IMG_5085.jpeg?profile=1024&#34;&gt;
&lt;figcaption&gt;Surprise beach!&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;On the last day of our stay, we returned to the park and spent a couple of hours at the Vancouver Aquarium. Lots of volunteers were around to show folks the hidden gems. Like the octopus that decided to hide 😂 We could also touch some jellyfish in a lab-like room which felt strange. The last time I had skin contact with one, I had gotten stung. Luckily, that wasn’t the case here 😉 Next to the jellyfish was a room with three axolotls; two of them I actually found but some of the younger visitors probably had more success on that front.&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&#34;https://zerokspot.com/api/photos/2026/02/14/IMG_5298.jpeg?profile=1024&#34;&gt;
&lt;figcaption&gt;An axolotl I actually found&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Of course, there were also otters and sea lions outside, so we kept them for last.&lt;/p&gt;
&lt;p&gt;This was also our last chance to get a special from one of the participants of the &lt;a href=&#34;https://hotchocolatefest.com/&#34;&gt;Hot Chocolate Festival&lt;/a&gt;. OK, the hot chocolate wasn’t all that special, but the cups definitely were!&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&#34;https://zerokspot.com/api/photos/2026/02/14/IMG_5325.jpeg?profile=1024&#34;&gt;
&lt;figcaption&gt;Finding Cocoa and Hot Choctopus&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&#34;to-summarize&#34;&gt;To summarize&lt;/h2&gt;
&lt;p&gt;I could write a lot more about this trip but this post is already long enough 😂 We spent 13 days in Vancouver and just had a great time! Of course, there was also some things that didn&amp;rsquo;t work too well like TransLink not accepting non-Canadian credit cards online or us having to resort to Burger King once with the expected stomach problems afterwards.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s now nearly two weeks now that we&amp;rsquo;re back in Graz, though, and I miss getting woken up by geese fighting seagulls. This will definitely not have been my last time in Vancouver. Hopefully next time with a proper bike, though!&lt;/p&gt;

      </description>
    </item>
    
    <item>
      
      <title>A maintained YAML library for Go again!</title>
      
      <link>https://zerokspot.com/weblog/2026/02/07/maintained-golang-yaml-library/</link>
      <pubDate>Sat, 07 Feb 2026 18:30:00 +0100</pubDate>
      <guid>/weblog/2026/02/07/maintained-golang-yaml-library/</guid>
      <description>
          &lt;p&gt;For 14 years Gustavo Niemeyer has maintained the &lt;a href=&#34;https://github.com/go-yaml/yaml&#34;&gt;de-facto standard library&lt;/a&gt; for working with YAML in Go. As is so often the case, life just happened and so he eventually &lt;a href=&#34;https://github.com/go-yaml/yaml/blob/944c86a7d2/README.md&#34;&gt;marked the repository of go-yaml as unmaintained&lt;/a&gt;. After a bit of uncertainty OpenSource worked: &lt;a href=&#34;https://github.com/yaml/go-yaml&#34;&gt;The library was forked&lt;/a&gt; and is now managed by the folks who also own the YAML specification and so of the original downstream users.&lt;/p&gt;
&lt;p&gt;The “old” v1, v2, and v3 branches have been frozen, to only receive security fixes, ensuring an easy upgrade from &lt;code&gt;gopkg.in/yaml.vX&lt;/code&gt;. All the new stuff is happening in v4 (in the &lt;code&gt;main&lt;/code&gt; branch)!&lt;/p&gt;
&lt;h2 id=&#34;new-configuration-api&#34;&gt;New configuration API&lt;/h2&gt;
&lt;p&gt;The biggest change of v4 is a &lt;a href=&#34;https://github.com/yaml/go-yaml/pull/212&#34;&gt;new configuration API&lt;/a&gt; that should fit better with the YAML vocabulary. While &lt;code&gt;Marshal&lt;/code&gt; and &lt;code&gt;Unmarshal&lt;/code&gt; are very common in Go libraries, &lt;a href=&#34;https://pyyaml.org/wiki/PyYAMLDocumentation&#34;&gt;PyYAML&lt;/a&gt; and others primarily us &lt;code&gt;load&lt;/code&gt; and &lt;code&gt;dump&lt;/code&gt;. So that’s what is now also in &lt;code&gt;‌go.yaml.in/yaml/v4&lt;/code&gt; (alongside the “old” API):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-golang&#34; data-lang=&#34;golang&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Load&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;in&lt;/span&gt; []&lt;span style=&#34;color:#66d9ef&#34;&gt;byte&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;any&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;opts&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;...&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;Option&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;error&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;data&lt;/span&gt; []&lt;span style=&#34;color:#a6e22e&#34;&gt;Datum&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;yaml&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Load&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;someBytes&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;data&lt;/span&gt;, 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// To load all documents from the input&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;yaml&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;WithAllDocuments&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;//...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Dump&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;any&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;opts&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;...&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;Option&lt;/span&gt;) (&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt; []&lt;span style=&#34;color:#66d9ef&#34;&gt;byte&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;error&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;output&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;yaml&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Dump&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;data&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;yaml&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;WithExplicitStart&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;yaml&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;WithExplicitEnd&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// If the input is a slice, multiple documents&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// will be created in the output&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;yaml&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;WithAllDocuments&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;//...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For streaming from &lt;code&gt;io.Reader&lt;/code&gt; and to &lt;code&gt;io.Writer&lt;/code&gt; objects, there are replacements for &lt;code&gt;Decoder&lt;/code&gt; and &lt;code&gt;Encoder&lt;/code&gt; with &lt;code&gt;Loader&lt;/code&gt; and &lt;code&gt;Dumper&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For more details see the &lt;a href=&#34;https://github.com/yaml/go-yaml/blob/main/docs/v3-to-v4-migration.md#recommended-use-new-api&#34;&gt;v3-to-v4 migration guide&lt;/a&gt;. V4 is currently in the pre-release phase with the 4ths release candidate.&lt;/p&gt;
&lt;h2 id=&#34;adoption&#34;&gt;Adoption&lt;/h2&gt;
&lt;p&gt;While the fork “started” only about a year ago, a lot of libraries have already switched to it. Most prominently probably &lt;a href=&#34;https://github.com/kubernetes/kubernetes/issues/132056&#34;&gt;Kubernetes&lt;/a&gt; and &lt;a href=&#34;https://github.com/prometheus/common/pull/834&#34;&gt;Prometheus&lt;/a&gt;. So&amp;hellip; I should finally get my act together and update my own stuff. While I’m at it, I can also go to the new API 😂&lt;/p&gt;

      </description>
    </item>
    
    <item>
      
      <title>Teaser: Visiting Vancouver</title>
      
      <link>https://zerokspot.com/weblog/2026/02/01/teaser-visiting-vancouver/</link>
      <pubDate>Sun, 01 Feb 2026 20:27:00 -0800</pubDate>
      <guid>/weblog/2026/02/01/teaser-visiting-vancouver/</guid>
      <description>
          &lt;p&gt;While I had other plans for this week’s post, work and exploring Vancouver didn&amp;rsquo;t really allow for any kind of preparation. It’s my first time on the Canadian west coast and while I’m sad that I’ve missed this year’s FOSDEM, I enjoyed Vancouver &lt;em&gt;a lot&lt;/em&gt;! A detailed post will follow in the next couple of weeks but I definitely need to at least share some pictures with you!&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&#34;https://zerokspot.com/api/photos/2026/02/02/IMG_5085.jpeg?profile=1024&#34;&gt;
&lt;figcaption&gt;Beach in the west of Stanley Park&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
&lt;img src=&#34;https://zerokspot.com/api/photos/2026/02/02/IMG_5116.jpeg?profile=1024&#34;&gt;
&lt;figcaption&gt;Exploring trees in the Capilano Suspension Bridge park&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
&lt;img src=&#34;https://zerokspot.com/api/photos/2026/02/02/IMG_5253.jpeg?profile=1024&#34;&gt;
&lt;figcaption&gt;Visiting the Museum of Anthropology&lt;/figcaption&gt;
&lt;/figure&gt;
      </description>
    </item>
    
    <item>
      
      <title>Managing .env files</title>
      
      <link>https://zerokspot.com/weblog/2026/01/24/managing-dotenv-files/</link>
      <pubDate>Sat, 24 Jan 2026 19:55:00 -0800</pubDate>
      <guid>/weblog/2026/01/24/managing-dotenv-files/</guid>
      <description>
          &lt;p&gt;Injecting secrets into applications usually happens through environment variables. For local development it has become common to use &lt;code&gt;.env&lt;/code&gt; files (or &lt;code&gt;.envrc&lt;/code&gt; files if you’re using &lt;a href=&#34;https://direnv.net/&#34;&gt;direnv&lt;/a&gt;) to manage those.&lt;/p&gt;
&lt;p&gt;While having all your environment variables defined inside a text file is simple to set up, it comes with some well documented downsides; first and foremost that secrets in there are not encrypted and can accidentally be committed to a versioning system.&lt;/p&gt;
&lt;h2 id=&#34;1passwords-solution&#34;&gt;1Password’s solution&lt;/h2&gt;
&lt;p&gt;A couple of months ago &lt;a href=&#34;https://1password.com/blog/1password-environments-env-files-public-beta&#34;&gt;1Password&lt;/a&gt; announced that they’d roll out their own solution for this problem. When you enable the “Developer experience” in your settings, you can now define so-called “Environments”. These are associated with your account (or team) and contain a set of key-value pairs. These are just secrets inside your Vault.&lt;/p&gt;
&lt;p&gt;You can then also define a “Destination” for such an environment. This can be either a local &lt;code&gt;.env&lt;/code&gt; file or an AWS Secret Manager instance.&lt;/p&gt;
&lt;p&gt;OK, so 1Password allows you to automatically write a set of key-values pair into a &lt;code&gt;.env&lt;/code&gt; file. What’s does that solve exactly?&lt;/p&gt;
&lt;h2 id=&#34;not-a-normal-file&#34;&gt;Not a normal file&lt;/h2&gt;
&lt;p&gt;What 1Password mounts into your filesystem is actually not a normal file but a Unix-named pipe! When applications read from that pipe you get a popup asking if that’s ok for you (similar to when you use 1Password’s SSH key manager). The file itself never contains those secrets, though. They are just passed through the pipe after your confirmation.&lt;/p&gt;
&lt;p&gt;Additionally, tools like Git don’t commit named pipes. &lt;code&gt;git add .env&lt;/code&gt; is pretty much a no-op.&lt;/p&gt;
&lt;p&gt;You can learn more in the &lt;a href=&#34;https://developer.1password.com/docs/environments/local-env-file/&#34;&gt;1Password docs&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;sharing-with-your-team&#34;&gt;Sharing with your team&lt;/h2&gt;
&lt;p&gt;Since the “Environments” are tied to your 1Password account, they can be shared with others. This is especially handy for small teams where everyone should quickly get a setup with a test system without just checking stuff into Git.&lt;/p&gt;
&lt;h2 id=&#34;snowflake&#34;&gt;Snowflake&lt;/h2&gt;
&lt;p&gt;Unlike SSH keys in 1Password, environments still feel like a little snowflake right now. They are hidden behind their own section in the UI and also in other aspects feel like something completely different from your normal secrets.&lt;/p&gt;
&lt;p&gt;I hope this will eventually change once they move out of beta but I like this feature, otherwise! Previously, I used a local Hashicorp Vault server in combination with direnv to load secrets without putting them into plain text files, but this approach looks even nicer to me!&lt;/p&gt;

      </description>
    </item>
    
    <item>
      
      <title>Sticking to a (blogging) schedule</title>
      
      <link>https://zerokspot.com/weblog/2026/01/18/sticking-to-a-blogging-schedule/</link>
      <pubDate>Sun, 18 Jan 2026 18:30:00 +0100</pubDate>
      <guid>/weblog/2026/01/18/sticking-to-a-blogging-schedule/</guid>
      <description>
          &lt;p&gt;For the longest time I’ve just written on this blog without any particular plan. Especially last year this has led to quite a few longer times of inactivity. For the new year I’ve decided that this shouldn’t happen again. The blog helps me think more deeply about certain topics and I’ve really missed that focus.&lt;/p&gt;
&lt;p&gt;Since December I’ve now been working down a list of blog posts from a schedule/calendar. One post per week and I decide in advance (but on the preceding Sunday at latest) what should appear here by the following Sunday.&lt;/p&gt;
&lt;h2 id=&#34;creating-a-backlog&#34;&gt;Creating a backlog&lt;/h2&gt;
&lt;p&gt;Theoretically, this will allow me to work on multiple posts in parallel so that I can build up a backlog. I could then pick up posts from there and finish in weeks where I don’t have time to start something new. At least that part hasn’t worked yet, but it will 🙂&lt;/p&gt;
&lt;p&gt;This is also the main reason why none of my recent posts have been long or overly detailed. I simply don’t have the backlog yet.&lt;/p&gt;
&lt;h2 id=&#34;low-tech&#34;&gt;Low-tech&lt;/h2&gt;
&lt;p&gt;The organization for all this happens inside two pieces of paper of my &lt;a href=&#34;https://zerokspot.com/weblog/2025/05/05/not-a-plotter-review/&#34;&gt;bible-size Plotter&lt;/a&gt;. One for the schedule where I have a list of all the weeks of the coming months, another where I write down broad ideas for posts.&lt;/p&gt;
&lt;p&gt;Every week I try to fill up the next couple of weeks with those ideas and thereby decide what I’m going to work on next.&lt;/p&gt;
&lt;p&gt;I then go into &lt;a href=&#34;https://ia.net/writer&#34;&gt;iA Writer&lt;/a&gt; where I write all the drafts, sharing them via iCloud so that I can work on these wherever I go. Initially, I thought I could keep on using Obsidian for that, but with everything else going on in there, it’s far too distracting. In iA Writer I just have these drafts and nothing else.&lt;/p&gt;
&lt;h2 id=&#34;a-look-ahead&#34;&gt;A look ahead&lt;/h2&gt;
&lt;p&gt;Luckily, I’ve collected quite a few things I want to write about and even for some started creating drafts. It’s also fun to combine the blog schedule with my journal and calendar to identify in advance topics that might appear in the near future!&lt;/p&gt;
&lt;p&gt;I’m hoping that this will finally get me to write more frequently here again. Times are definitely not boring and so there should be enough things to put to Markdown 😂&lt;/p&gt;

      </description>
    </item>
    
    <item>
      
      <title>Reading challenges for the new year</title>
      
      <link>https://zerokspot.com/weblog/2026/01/11/reading-challenges-for-the-new-year/</link>
      <pubDate>Sun, 11 Jan 2026 19:30:00 +0100</pubDate>
      <guid>/weblog/2026/01/11/reading-challenges-for-the-new-year/</guid>
      <description>
          &lt;p&gt;Another year has come and so it’s once again time for a book reading challenge! Last year I had sat the bar quite low with 15 books. After many times I simply wanted to actually complete the challenge again. Turned out, even 15 books was quite hard and I had to fill in some graphical novels towards the end which had been on my list anyway.&lt;/p&gt;
&lt;p&gt;For this year I’ve decided to be a bit more daring again and am now aiming for 20 books. I already know that some of the books will be Doctor Who graphical novels, so this might still work!&lt;/p&gt;
&lt;p&gt;Besides that main one, I will also look for other reading challenges hosted by &lt;a href=&#34;https://app.thestorygraph.com/&#34;&gt;StoryGraph&lt;/a&gt; over the course of the year. In January, for instance, there is the “January Pages Challenge 2026” where you need to read at least one page on every single day of the month. In the past I had a lot of days or even weeks without reading any books. Somehow I ended up feeling like I could either read or play games. With these challenges I want to force myself to still make some progress in a book while also doing other things.&lt;/p&gt;
&lt;p&gt;Onwards into an exciting year full of books!&lt;/p&gt;

      </description>
    </item>
    
    <item>
      
      <title>Traveling in 2025</title>
      
      <link>https://zerokspot.com/weblog/2026/01/04/traveling-in-2025/</link>
      <pubDate>Sun, 04 Jan 2026 19:15:00 +0100</pubDate>
      <guid>/weblog/2026/01/04/traveling-in-2025/</guid>
      <description>
          &lt;p&gt;Due to some family issues traveling was very limited in 2025. Besides the usual trip to Brussels in February for &lt;a href=&#34;https://zerokspot.com/weblog/2025/02/09/fosdem-2025/&#34;&gt;FOSDEM&lt;/a&gt; and another one to Edinburgh I didn’t go beyond Austria and border regions.&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&#34;https://zerokspot.com/api/photos/2026/01/04/IMG_3747.jpeg?profile=1024&#34; /&gt;
&lt;figcaption&gt;I didn’t expect good weather in Scotland. I was surprised.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Right after Edinburgh I think my highlight last year was a trip to Salzburg, where I accompanied my partner on a business trip. The evenings were free and we also had two days to explore the city and the countryside with our bikes. If you like great food, biking, and beer, Salzburg is an absolute recommendation! Be prepared for wet days, though.&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&#34;https://zerokspot.com/api/photos/2026/01/04/IMG_3893.jpeg?profile=1024&#34; /&gt;
&lt;figcaption&gt;Except for one day the weather in Salzburg wasn’t great&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Since I had to take care of my parents, most of my trips happened around Graz and Klagenfurt. We still also went to Lindau for a couple of days. Due to construction work, though, we couldn’t go directly. Instead, we decided to do a little side tour through &lt;a href=&#34;https://en.wikipedia.org/wiki/South_Tyrol&#34;&gt;South Tyrol&lt;/a&gt; using our bikes and regional trains. Next time, I will have less luggage with me but everything else about that detour was just perfect 😂&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&#34;https://zerokspot.com/api/photos/2026/01/04/img2.jpeg?profile=1024&#34; /&gt;
&lt;figcaption&gt;Perfect weather for a bike tour through South Tyrol&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Between Klagenfurt and there our train also had a quick stop in &lt;a href=&#34;https://en.wikipedia.org/wiki/Lurnfeld&#34;&gt;Möllbrücke&lt;/a&gt;. The mountains just looked amazing and so we thought it might be worth a proper visit. Through a coincident we really made it and noticed a small mountain in the North-West of the town which should offer an amazing view over the whole area. We were not disappointed:&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&#34;https://zerokspot.com/api/photos/2026/01/04/IMG_4612.jpeg?profile=1024&#34; /&gt;
&lt;figcaption&gt;View from &lt;a href=&#34;https://de.wikipedia.org/wiki/Danielsberg&#34;&gt;Danielsberg&lt;/a&gt; to the South-East&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Coming to think of it, I think we made the best of the situation and just had an amazing time wherever we went! Still, I really hope that 2026 will be a little more &amp;hellip; organized 😂&lt;/p&gt;

      </description>
    </item>
    
    <item>
      
      <title>Techo Kaigi for 2026</title>
      
      <link>https://zerokspot.com/weblog/2025/12/28/techo-kaigi-for-2026/</link>
      <pubDate>Sun, 28 Dec 2025 21:23:00 +0100</pubDate>
      <guid>/weblog/2025/12/28/techo-kaigi-for-2026/</guid>
      <description>
          &lt;p&gt;October is now long over, but, unfortunately, I had other things on my mind back then and so I couldn’t really think about my planner setup for 2026 until recently. A couple of things were mostly set, though:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Journal: Here I’ll still use a Hobonichi Original. Somehow the bulletin list format for the most important events and thoughts of each day has worked well for me. One day I will find a good place for longer, private thought pieces, too.&lt;/li&gt;
&lt;li&gt;Personal tasks: These I’ll keep managing with OmniFocus. My load right now is still too high and diverse to work with something like a classic Bullet Journal. As much as I think about also moving personal items into my Plotter with that system, GTD is still the way to go for now.&lt;/li&gt;
&lt;li&gt;Travel journaling: In 2024 I got a full-size Traveler’s Notebook for that. Unfortunately, I didn’t have the capacity for them. To be fair, I also didn’t have any real vacation in 2025 but hopefully 2026 will offer more opportunities for travel journaling again!&lt;/li&gt;
&lt;li&gt;Work tasks: Ever since I got my &lt;a href=&#34;https://zerokspot.com/weblog/2025/05/05/not-a-plotter-review/&#34;&gt;bible-sized Plotter&lt;/a&gt; this February I’ve been using it for managing my operational work tasks in a Bullet Journal-inspired system. This is still working well and I’ll keep using that.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Alongside work tasks I also use my Plotter as a commonplace book for things like movie lists and more. Short notes and inbox items (according to GTD) often end up in my passport size Traveler’s Notebook.&lt;/p&gt;
&lt;p&gt;In January I might have the opportunity to also get my hands on some more Plotter products, so the setup described above might be temporary in a few areas. Let’s see 😂&lt;/p&gt;

      </description>
    </item>
    
    <item>
      
      <title>Koralmbahn: From Graz to Klagenfurt with speed</title>
      
      <link>https://zerokspot.com/weblog/2025/12/21/koralmbahn/</link>
      <pubDate>Sun, 21 Dec 2025 11:50:00 +0100</pubDate>
      <guid>/weblog/2025/12/21/koralmbahn/</guid>
      <description>
          &lt;p&gt;I cannot describe how much I’ve waited for this! Since last Sunday there is finally a direct train connection between &lt;a href=&#34;https://en.wikipedia.org/wiki/Graz&#34;&gt;Graz&lt;/a&gt; and &lt;a href=&#34;https://en.wikipedia.org/wiki/Klagenfurt&#34;&gt;Klagenfurt&lt;/a&gt;, the capitals of Styria and Carinthia in Austria respectively. Ever since I moved to Graz I’ve pretty much had three options to get back to Klagenfurt in order to visit my parents or enjoy a summer vacation at Wörthersee:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A combination of regional trains and &lt;a href=&#34;https://www.oebb.at/en/reiseplanung-services/im-zug/unsere-zuege/railjet&#34;&gt;Railjets&lt;/a&gt; which takes around 3 hours.&lt;/li&gt;
&lt;li&gt;A combination of regional trains only which makes transporting bikes easier but takes more than 4 hours.&lt;/li&gt;
&lt;li&gt;A direct intercity bus which takes around 2 hours.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;With all of those I got to drive through nice countrysides but they just took far to long compared to going by car (about 1h 40m).&lt;/p&gt;
&lt;p&gt;Now everything is different! 41 minutes (or &lt;a href=&#34;https://www.bundespraesident.at/aktuelles/detail/eroeffnung-der-koralmbahn&#34;&gt;38 if you travel with our president&lt;/a&gt;)! That’s quick enough to just use it for a quick visit or a late return trip, something that I did this Wednesday when I needed to get to Graz since I had some tickets for the new Avatar movie but was in Klagenfurt during the day due to logistics.&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;Screenshot of route on oebb.at&lt;/figcaption&gt;&lt;img src=&#34;https://zerokspot.com/api/photos/2025/12/21/IMG_0039.jpeg?profile=1024&#34;/&gt;&lt;/figure&gt;
&lt;p&gt;Since then I went back and forth one more time just to receive a delivery. It&amp;rsquo;s hard to overstate hoe much this connection will make my life easier over the years to come ❤️&lt;/p&gt;
&lt;p&gt;That whole project itself has a long, weird, and highly political history. If I remember correctly, even my grandfather was involved in the early planning phases in the second half of the twentieth century. Since then he and later my dad always told their children: “I might not see it, but hopefully you will!”. Unfortunately my parents died recently, just before they could use the new connection. I’m sure they would have loved it.&lt;/p&gt;

      </description>
    </item>
    
  </channel>
</rss>
