Today I Learned

Short TIL posts

169 posts latest post 2026-06-04 simple view
Publishing rhythm
May 2026 | 4 posts

I’ve been deploying my site old school for most of this year, rsync to a volume mounted to nginx. I ran into an issue today where I updated my site and all of the pages updated first, followed by upload. The issue this created was that the new cache busted css files were not up yet and the site had no styles for a brief period during upload.

I found that delaying updates and delaying deletes until the new content exists first solves this problem pretty well. Theres still possiblility of jank while uploading to a live directory and not doing some sort of hot swap, but I’m good with this low budget option for now.

sync:
	rsync -rlt --delete --omit-dir-times \
	--info=progress2 \
	--delay-updates \
	--delete-delay \
	./output/ \
	server:/mnt/mysite

To ignore commands that start with a space character, use the HIST_IGNORE_SPACE option in bash or zsh.

setopt HIST_IGNORE_SPACE

I just learned that forgejo has a push to create repo feature and it is a gamechanger. Upon first try it didn’t work, with just a couple of environment variables I was up and running with push to create.

notify.wayl.one on  main is 📦 v0.1.62  v3.14.4  NO PYTHON VENV SET  USING SYSTEM NVIM
❯ git remote add origin https://git.waylonwalker.com/waylon/notify.wayl.one
notify.wayl.one on  main is 📦 v0.1.62  v3.14.4  NO PYTHON VENV SET  USING SYSTEM NVIM
❯ git push
remote: Push to create is not enabled for users.
fatal: unable to access 'https://git.waylonwalker.com/waylon/notify.wayl.one/': The requested URL returned error: 403

So I added the following environment variables.

Author: Waylon S. Walker <[email protected]>
Date:   Wed May 6 21:56:53 2026 -0500

    enable push to create

diff --git a/k8s/forgejo/deployment.yaml b/k8s/forgejo/deployment.yaml
index d77daab..9346763 100644
--- a/k8s/forgejo/deployment.yaml
+++ b/k8s/forgejo/deployment.yaml
@@ -91,6 +91,10 @@ spec:
               value: "0.0.0.0"
             - name: FORGEJO__server__HTTP_PORT
               value: "3000"
+            - name: FORGEJO__repository__ENABLE_PUSH_CREATE_USER
+              value: "true"
+            - name: FORGEJO__repository__ENABLE_PUSH_CREATE_ORG
+              value: "true"
             - name: FORGEJO__database__DB_TYPE
               value: postgres
             - name: FORGEJO__database__HOST

https://github.com/WaylonWalker/homelab-argo/commit/b2e953bc12

Tried again, and it just worked!

notify.wayl.one on  main is 📦 v0.1.62  v3.14.4  NO PYTHON VENV SET  USING SYSTEM NVIM
❯ git push
Enumerating objects: 171, done.
Counting objects: 100% (171/171), done.
Delta compression using up to 12 threads
Compressing objects: 100% (169/169), done.
Writing objects: 100% (171/171), 176.22 KiB | 16.02 MiB/s, done.
Total 171 (delta 99), reused 0 (delta 0), pack-reused 0 (from 0)
remote: Resolving deltas: 100% (99/99), done.
To https://git.waylonwalker.com/waylon/notify.wayl.one
 * [new branch]      main -> main

nless is a seriously sick tui for exploring streaming data. It makes it seriously simple to pivot (U), drill in (Enter), sort (s). It leave breadcrumbs as you go and you can press q to back out.

Play with your kubernetes events. Ya, my homelab is far from perfect, dont judge.

kubectl get events -A -w | uvx --from nothing-less nless

markata-go now has web awesome integration for image compare. It renders a nice web component with a slider to compare two images.

d628ffba-de18-4fff-91a8-700f037df119.webp

It’s done with a class wrapper around the image components.

::: wa-comparison
![d628ffba-de18-4fff-91a8-700f037df119.webp](https://dropper.wayl.one/file/d628ffba-de18-4fff-91a8-700f037df119.webp)
![](https://dropper.waylonwalker.com/file/ca30665f-1a15-453e-aab8-221901c7df99.webp)
:::

Without markata-go’s web awesome integration, the above would look like:

<script type="module">
  import 'https://ka-f.webawesome.com/[email protected]/components/comparison/comparison.js';
</script>

<wa-comparison>
  <img
    slot="before"
    src="https://dropper.wayl.one/file/d628ffba-de18-4fff-91a8-700f037df119.webp"
    alt="Grayscale version of kittens in a basket looking around."
  />
  <img
    slot="after"
    src="https://dropper.waylonwalker.com/file/ca30665f-1a15-453e-aab8-221901c7df99.webp"
    alt="Color version of kittens in a basket looking around."
  />
</wa-comparison>

Today I found a way to test model syntax, cause the clankers always get the exact model name that copilot wants wrong.

copilot --model claude-sonnet-4.5 -p "Reply with OK" --allow-all --no-ask-user -s
copilot --model gpt-5.4 -p "Reply with OK" --allow-all --no-ask-user -s

Stow comes with a local and global ignore list that you can use to ignore certain files or directories.

If you put Perl regular expressions, one per line, in a .stow-local-ignore file within any top level package directory, in which case any file or directory within that package matching any of these regular expressions will be ignored. In the absence of this package-specific ignore list, Stow will instead use the contents of ~/.stow-global-ignore, if it exists. If neither the package-local or global ignore list exist, Stow will use its own built-in default ignore list, which serves as a useful example of the format of these ignore list files:

Example given from the docs

RCS
.+,v

CVS
\.\#.+       # CVS conflict files / emacs lock files
\.cvsignore

\.svn
_darcs
\.hg

\.git
\.gitignore
\.gitmodules

.+~          # emacs backup files
\#.*\#       # emacs autosave files

^/README.*
^/LICENSE.*
^/COPYING

Reference #

https://www.gnu.org/software/stow/manual/html_node/Types-And-Syntax-Of-Ignore-Lists.html

I’ve been having issue with my keyboard disconnecting to my main desktop for awhile. Today I got a cheap bluetooh dongle in and am giving it a run this week to see how things go. The first step was to move it to the new adapter. I’ve never had multiple adapters installed so this was a new to me process.

I was able to do it all with the same keyboard, It did require some juggling between usb and bluetooth modes pluging and unplugging, two keyboards would be simpler to reason about.

I can’t be bothered to change my brain to think about this machine on a different zmk profile it is of absolute importance for it to remain on the same profile, otherwise this would be a simple bind to another empty profile.

Why not use a cable on desktop?

I dont mind cable, and have used one on this setup for years, but I have actually been picking up and moving this keyboard and using it with different devices.

I’ve got a big battery and performace cranked up, unless my machine is under load I do not notice any key lag.

I did it with bluetoothctl, I’m sure it could have been done with a gui like blueberry or blueman.

bluetoothctl
# list adapters
list
select <old-adapter>
devices
# fin the MAC address of the device 42BLOCK
remove <42BLOCK_MAC>

Now I plugged into usb. And importantly cleared out the zmk profile. If you do not clear the profile your board does not go into pairing mode.

bluetoothctl
# switch adapters
select <new-adapter>
power on
agent on
default-agent
scan on
Put 42Block in pairing mode, then:
pair <42BLOCK_MAC>
trust <42BLOCK_MAC>

At this pint I saw this show up in the logs, I think there was some masking issues or something in zmk, output kept going out usb no matter what so I disconnected the keyboard and typed the passkey in, and it worked.

[agent] Passkey: 540044

Boom, it just started working right away.

bluetoothctl
connect <42BLOCK_MAC>
info <42BLOCK_MAC>
scan off
exit

Today I learned that docker creates an empty /.dockerenv file to indicate that you are running in a docker container. Other runtimes like podman commonly use /run/.containerenv. kubernetes uses neither of these, the most common way to detect if you are running in kubernetes is to check for the presence of the KUBERNETES_SERVICE_HOST environment variable. There will also be a directory at /var/run/secrets/kubernetes.io/serviceaccount that contains the service account credentials if you are running in kubernetes.

In the age of agents sometimes work gets done on so many different worktrees and branches its hard to tell if there is already a PR or any of them or not, the great gh cli has us covered.

gh pr list --head fix/markata-go-connections-graph

I keep forgetting about the double gutter problem with nested containers. When you put padding on a parent and the child also has padding, you get twice the spacing you wanted.

The Problem #

.container {
  padding: 2rem;
}

.child {
  padding: 2rem;
}

Now your content is 4rem from the edge. Not what I meant at all.

The Fix #

Either remove padding from the parent or use box-sizing: border-box and plan for it. I usually just drop the parent padding when I realize what I have done.

Like a dufus this morning I did a hard reset on a git repo for getting I was working on a manifest for. You see I generally use argo, but occasionally I have no idea what I am doing or want yet and I start raw doggin it, fully aware that I’m going to just nuke this namespace before getting it into a proper argocd.

I was overjoyed when I found out that you can diff your manifests with live production using the kubectl diff command. It uses standard diff so you can bring all your fancy diff viewers you like.

# regular manifest
kubectl diff -f k8s/shots -n shot
# kustomize
kubectl diff -k k8s -n go-waylonwalker-com
# using a fancy diff viewer
kubectl diff -f k8s/shots -n shot | delta
# using an even fancier diff viewer
# pinkies out for this one
kubectl diff -f k8s/shots -n shot | delta --diff-so-fancy

Now I can get those changes back that I thought I lost, and apply updates with confidence knowing what is about to change.

I really wish I would have got this right a few years ago. Theres a couple of flags I had to use to get mdformat to do hard wraps at 80 characters and not wreck tables. This mix of flags and plugins is workign really well for me so far.

mdfmt() {
    uvx \
        --with "mdformat-ruff" \
        --with "mdformat-beautysh" \
        --with "mdformat-web" \
        --with "mdformat-config" \
        --with "mdformat-gfm" \
        --with "mdformat-front-matters" \
        --with "mdformat-wikilink" \
        --with "mdformat-simple-breaks" \
        mdformat \
        --wrap 80 \
        --end-of-line lf \
        --codeformatters python \
        --codeformatters bash \
        "$@"
}

And as pre-commmit.

repos
  - repo: https://github.com/hukkin/mdformat
    rev: 1.0.0  # pin to the version you want
    hooks:
      - id: mdformat
        args:
          - --wrap
          - "80"
          - --end-of-line
          - lf
          - --codeformatters
          - python
          - --codeformatters
          - bash
        additional_dependencies:
          - mdformat-ruff
          - mdformat-beautysh
          - mdformat-web
          - mdformat-config
          - mdformat-gfm
          - mdformat-front-matters
          - mdformat-wikilink
          - mdformat-simple-breaks

Opencode is changing on the daily right now, today I noticed the word low pop up in Orange text in my opencode window. Looking into this they are exposing variants to the user. This allows you to change between fast or slow and thinking, the later taking more time to prepare before doing an action.

It looks like this toggle may have been here for awhile and I’m just now discovering it. Potentially because its a new feature of the free Zen provider.

Variants Many models support multiple variants with different configurations. OpenCode ships with built-in default variants for popular providers.

Built-in variants OpenCode ships with default variants for many providers:

Anthropic:

high - High thinking budget (default) max - Maximum thinking budget OpenAI:

Varies by model but roughly:

none - No reasoning minimal - Minimal reasoning effort low - Low reasoning effort medium - Medium reasoning effort high - High reasoning effort xhigh - Extra high reasoning effort Google:

low - Lower effort/token budget high - Higher effort/token budget

This is opencode’s init prompt.

Please analyze this codebase and create an AGENTS.md file containing:
1. Build/lint/test commands - especially for running a single test
2. Code style guidelines including imports, formatting, types, naming conventions, error handling, etc.
The file you create will be given to agentic coding agents (such as yourself) that operate in this repository. Make it about 150 lines long.
If there are Cursor rules (in .cursor/rules/ or .cursorrules) or Copilot rules (in .github/copilot-instructions.md), make sure to include them.
If there's already an AGENTS.md, improve it if it's located in <dir>

Today I discovered vim-speeddating by tpope. I’m sure I’ve seen years ago but it did not click for my workflow until today. I often go through pictures from my phone for the past few days and make Posts tagged: shots posts, but I want to date them to about when the image was taken most of the time. This allows me to quickly bump days up and down using c-a and c-x even around the new year.

Sound on, listen to those new switches.

Yesterday I wrote about a way to do light mode screen recording to convert to light mode from dark mode with ffmpeg. I was wondering if it could be done entirely on the front end for web applications. Turns out you can. I’m sure there are limited wikis and site builders that don’t allow adding style like this, but it works if you can.

<video
    src="https://dropper.waylonwalker.com/file/1c53dbcb-4b84-4e94-9f04-a42986ab3fa1.mp4?width=800"
    controls
    style="filter: invert(1) hue-rotate(180deg) contrast(1.2) saturate(1.1);"
    >

<!--markata-attribution-->
</video>

0 deg hue rotate

90 deg hue rotate

180 deg hue rotate

270 deg hue rotate

I saw this tip from Cassidoo and had to try it out for myself. I kicked on a screen recording right from where my terminal was, converted it, and it actually looks pretty good.

ffmpeg \
   -i screenrecording-2026-01-01_10-10-49.mp4 \
   -vf "negate,hue=h=180,eq=contrast=1.2:saturation=1.1" \
   screenrecording-2026-01-01_10-10-49-light.mp4

Dark Mode

Light Mode

There are a few unsettling things about it, but overall I feel like it was a success.

I’ve found Gemini to be very useful lately, especially for finding information within long form content.

When writing thought-896, I wanted to use a direct quote from Jeff Dickey, Gemini popped it out very quickly.

give me a quote from jeff just before the timestamp I'm at the interviewer
asked what makes a good cli and he started talking about stdout/stderr

In another case, my wife and I are huge Good Eats fans. Alton Brown taught us how to cook during college and on. We watched every single good eats episode nearly 10 years after they aired. He is back with some updates to those those shows on his Youtube. Gemini gives very good detailed responses with timestamps.

Alton Brown had a recent YouTube video for cooking turkey. Can you get the
instructions from the video?

I’ve been using this one for awhile now, I have a post type that I only edit from my phone, but I have all the post numbered. I set up a template in obsidian for using templater, the template goes right in the static site repo, I point templater to the templates directory and this has been working pretty seamlessly for awhile.

---
date: <% tp.date.now("YYYY-MM-DD HH:mm:ss") %>
templateKey: myposttype
published: true
tags:
- myposttype
<%*
const folder = "pages/myposttype";

// get all files in the vault, keep only those inside the folder
const files = app.vault.getFiles().filter(f => f.path.startsWith(folder + "/"));

// extract numeric suffixes from filenames like myposttype-123.md
const nums = files.map(f => {
  const m = f.basename.match(/^myposttype-(\d+)$/);
  return m ? parseInt(m[1], 10) : null;
}).filter(n => n !== null);

// next number (start at 1 if none exist)
const next = (nums.length ? Math.max(...nums) : 0) + 1;

// include the .md extension when moving
const newPath = `${folder}/myposttype-${next}`;
await tp.file.move(newPath);
%>
---