Sign in

beat

@beathagenlocher.com
93 followers 352 following 329 posts

Mostly random stuff I find interesting, or other sharable thoughts that cross my mind. beathagenlocher.com

PostsRepliesMedia
Reposted by beat
Martin Kleppmann @martin.kleppmann.com · 14/04/2026
It's very exciting that AI can now take substantial real-world software like zlib and prove it correct (github.com/kim-em/lean-...). @kirancodes.me found a bug by fuzzing, but it was outside of the boundary of what was verified (kirancodes.me/posts/log-wh...). That doesn't diminish the excitement!
kirancodes.me
Lean proved this program was correct; then I found a bug.
AI agents are getting very good at finding vulnerabilities in large-scale software systems.
5453
beat @beathagenlocher.com · 10/04/2026
I've had some GNOME issues again, and did a very small writeup:GNOME Wayland Desktop Portal/Nautilus/Screen Sharing Issues #GNOME #Linux #Wayland #Desktop
Over the last week, I learned that it's possible to have multiple desktop portals (_implementations_, I guess?) for Wayland active, and even specify which one to choose for which to choose which one.

This is what this looks like:

```nix title="home.nix"
# ...
    xdg.portal = {
      enable = true;
      extraPortals = [
        pkgs.xdg-desktop-portal-gtk
        pkgs.xdg-desktop-portal-gnome
      ];
      config.common = {
        # for files, mainly, since my nautilus takes ages to load
        default = "gtk";
         # screen sharing doesn't work otherwise on GNOME
        "org.freedesktop.impl.portal.ScreenCast" = "gnome";
        "org.freedesktop.impl.portal.RemoteDesktop" = "gnome";
      };
    };
# ...
```

Link to the config
110
beat @beathagenlocher.com · 05/04/2026
Details that make interfaces feel better: Really good article, and well-crafted site :) #Design #CSS #TheWeb
A pretty good article on small but meaningful design elements: Details that make interfaces feel better

Sweet!
120
Reposted by beat
matia 🇭🇷 @joyofcode.xyz · 28/03/2026
Don't let them farm you 🧑‍🌾
1388
beat @beathagenlocher.com · 26/03/2026
I just spent 1 1/2 hours reading this absolutely amazing blog post by @iankduncan.com: www.iankduncan.com/engineering/... And if you're likely to get nerdsniped by me mentioning #distributedsystems, #functionalprogramming, you definitely should read it too.
iankduncan.com
What Functional Programmers Get Wrong About Systems - Ian Duncan
Type systems verify properties of programs. Production correctness is a property of systems. The gap between these is where the interesting failures live.
110
beat @beathagenlocher.com · 26/03/2026
Just discovered an actually-useful Nutrient Ranking Tool, and thought I'd share :) #Nutrition #LessWrong #AminoAcids
I just found something pretty cool: A nutrient ranking tool which one can use to filter for basically any ingredient across lots of sources with lots of filters.

Here it is:

My Food Data | Nutrient Ranking Tool

(I discovered this through reading this nice article on glycine on LessWrong)

Hope it helps!
110
beat @beathagenlocher.com · 18/03/2026
PSA: If you're writing CSS, value simplicity, and don't know about Fluid (Type) Scales yet, you should check them out :) #CSS #Design #Simplicity
Something I originally learned about reading Maggie Appletons Colophon, and now use on this website, too.

Original source: Fluid Responsive Design | Utopia

Implementations:

- Fluid for Tailwind CSS - Build better responsive designs in less code. (not updated to Tailwind 4)
- Unocss preset fluid | Unocss preset fluid
- Tailwind Clamp - Documentation

<References>
  <ReferenceLink href="https://haglobah.github.io/talks/2025-05-07-fluid-type-scales/">
    My slides from a lightning talk
  </ReferenceLink>
</References>
210
beat @beathagenlocher.com · 18/03/2026
Today I researched Fluid Scales in Tailwind :) #Tailwind #CSS #TIL
Some time ago, I held a lightning talk at our functional programming meetup on Fluid Type Scales (Where you try and get rid of `class="sm:... md:... lg:..."` whenever it makes sense).

This was, for a long time, a good reason for me to use UnoCSS.

But today, I learned that someone built this for Tailwind, too:

- fluid.tw

But since this doesn't support Tailwind 4, I'll be using the slightly uglier, but simpler (and maintained!) tailwind-clamp for now.

May your utility classes always be simple :)
100
Reposted by beat
David Gasquez @davidgasquez.com · 18/03/2026
Anyone knows if there is a chart with all the AT Protocol apps users over time? Would love to see popular and trending apps using AT Protocol!
261
Reposted by beat
Stephen G @jovianmoon.io · 17/03/2026
Reason number 627383 having a blog is awesome... Leo from Beekeeb read my 36 key / RSI post and included a pre-release Toucan 36, keeb in my chocofi order. totally unexpected. totally awesome. thanks Jenny & Leo! shop.beekeeb.com for some awesome hand made keebs.
292
beat @beathagenlocher.com · 14/03/2026
Today I learned that field-sizing: content exists :) #CSS #TIL
Today I learned that you can use `field-sizing: content` to make the `<textarea>` shrink and grow with the text in it.

Try it out: (If you can, haha ^^)

<textarea
  class="w-full p-2 rounded-xl bg-zinc-700 focus:outline-none"
  style="field-sizing: content"
></textarea>

It's not available everywhere as of today (field-sizing: content | Can I use), but that's up to you to decide.

:)
100
beat @beathagenlocher.com · 12/03/2026
Wrote a piece about functional programming in TypeScript on the frontend for the company blog: funktionale-programmierung.de/en/2026/03/0... :)
funktionale-programmierung.de
Funktionale Programmierung - Functional Programming by Example: Refactoring a TypeScript/SolidJS Application | Part 1
Learn to make illegal states unrepresentable in TypeScript with tagged unions and explicit modeling.
010
beat @beathagenlocher.com · 09/03/2026
Today, I had a short research spree on the state of Lean->Wasm compilation. Pretty interesting! #Lean #Wasm
Today I was thinking about how easy it is to include Lean into a website.

Since it compiles via an intermediate C layer, it shouldn't be _that_ hard to do this via Emscripten, but apparently it's harder than that: Since there's `#eval`, every function of the language needs to be exported per default (only then it's really a full Lean compiler), and that apparently hit a limit for the number of imports and exports a WASM program(?, module?) allows.

That has been increased since then, but now there's other issues.

Wild.

# Notes

# Links

- Lean 4 | wasm build | Zulip team chat
- Lean 4 | lean.js | Zulip team chat
- GitHub - T-Brick/lean2wasm: Tool for compiling Lean to WASM · GitHub (shouldn't work right now, if the lean.js thread is right)
110
Reposted by beat
BOB Konferenz @bobkonf.de · 09/03/2026
For the first tutorial after lunch at #BOBkonf2026, @beathagenlocher.com presents "Functional Programming in TypeScript" on track A. Read more here: bobkonf.de/2026/hagenlo...
054
beat @beathagenlocher.com · 06/03/2026
Hey :) I've greatly expanded upon an older note of mine. Check it out! #Simplicity #Writing
Some time ago, I visited a friend who's fairly well versed in tech (e.g. NeoVim, can code) and watches him struggle with editing a file in Word fast on someone else's Laptop.

I asked him whether he knew about <KB>Ctrl-arrow</KB> for wordwise movement (which he didn't), and was fairly surprised. But in hindsight, I didn't know that for the longest time either.

I took this as an example to start writing on Better Computer Use (then only a collection of basically "Keyboard Shortcuts You Might Want To Know").

Today I revisited this, and something that started out as "You might want to know these shortcuts" ended in a discussion on how Software Developers and Product Designers should structure their UIs.

Feel free to check it out, tell me what you think, and tell me where I'm wrong :)

(Or just wait until it's more polished.)

:)
100
beat @beathagenlocher.com · 04/03/2026
Async Link Fetching in Emacs #TIL #Emacs #DoomEmacs
Today I finally tackled something that has annoyed me for a while since switching most of my editing/digital gardening/second brain-writing over to Emacs.

Whenever I took 30 minutes and tried to get title fetching of links to work, I couldn't get it to work in this self-allotted time. So I asked Opus to do the dumbest thing possible: Just fetch it via curl, and then apply stuff asynchronously.

(Obviously the other, non-dumb way should work too, but I've spent way to much time on this now either way.)  
(And apparently there's also `url-insert-file-contents`, maybe I'gonna try that some other time.)

```elisp
(defun flt--url-at-point ()
  "Get URL at point, or nil."
  (thing-at-point 'url))

(defun flt--url-from-kill-ring ()
  "Get URL from kill ring if it looks like one, or nil."
  (let ((text (current-kill 0 t)))
    (when (and text (string-match-p "\\`https?://" text))
      (string-trim text))))

(defun flt--get-url ()
  "Get URL from point first, then kill ring. Nil if neither has one."
  (or (flt--url-at-point)
      (flt--url-from-kill-ring)))

(defun flt--extract-title (html)
  "Extract <title> content from HTML string."
  (when (string-match "<title[^>]*>\\([^<]*\\)</title>" html)
    (string-trim (match-string 1 html))))

(defun flt--insert-markdown-link (url title marker)
  "Replace URL at MARKER with a markdown link, or insert at MARKER."
  (with-current-buffer (marker-buffer marker)
    (save-excursion
      (goto-char marker)
      ;; If there's a raw URL at point, replace it
      (let ((url-bounds (thing-at-point-bounds-of-url-at-point)))
        (if url-bounds
            (progn
              (delete-region (car url-bounds) (cdr url-bounds))
              (insert (format "%s" title url)))
          (insert (format "%s" title url)))))))

(defun flt-fetch-link-title ()
  "Fetch the title of a URL (at point or kill ring) and insert a markdown link.
If point is on a URL, replaces it. Otherwise inserts at point."
  (interactive)
  (let ((url (flt--get-url)))
    (if (not url)
        (message "No URL found at point or in kill ring.")
      (let ((marker (point-marker))
            (buf (generate-new-buffer " *flt-curl*")))
        (message "Fetching title for %s..." url)
        (make-process
         :name "flt-curl"
         :buffer buf
         :command (list "curl" "-sL" "-m" "10"
                        "-H" "User-Agent: Emacs"
                        "--" url)
         :sentinel
         (lambda (proc _event)
           (when (eq (process-status proc) 'exit)
             (let ((title
                    (with-current-buffer (process-buffer proc)
                      (flt--extract-title (buffer-string)))))
               (if title
                   (progn
                     (flt--insert-markdown-link url title marker)
                     (message "Inserted: %s" title url))
                 (message "Could not extract title from %s" url)))
             (kill-buffer (process-buffer proc))
             (set-marker marker nil))))))))

(map! :leader
      :desc "->title" :nv "d f" #'flt-fetch-link-title)
```

And in the meantime, I also learned that _markers_ exist!

So now, I can fetch link titles again, like I'm used to :)
200
beat @beathagenlocher.com · 03/03/2026
Just uploaded a bunch of my already-written/accumulated notes :) #Writing
I've been dragging quite a few notes around with me for the last one, two weeks.

Here they are! :)
110
beat @beathagenlocher.com · 02/03/2026
Finally fixed a minor slide annoyance of mine: Non-working QR codes #Slidev #QRCode #slides #Vue
For presentations, I'm using slidev, a pretty cool and versatile tool (Markdown slides with inline HTML/Vue, so you can do basically anything on your slides), which has lots of cool features.

One minor annoyance: If you're using either of the QRCode addons and want to add more than one QR Code per slide deck, it breaks.

Luckily, with the power of the web, this is pretty easy to fix:

We can just define a component:

```vue
<template>
  <QrcodeCanvas
    :value="value"
    :size="width"
    :foreground="foreground"
    :background="background"
    :margin="margin"
    level="H"
  />
</template>

<script lang="ts" setup>
import { QrcodeCanvas } from 'qrcode.vue'

const props = withDefaults(
  defineProps<{
    value: string
    width?: number
    height?: number
    color?: string
    margin?: number
  }>(),
  {
    width: 200,
    color: '#000000',
    margin: 2,
  },
)

const foreground = props.color.startsWith('#') ? props.color : `#${props.color}`
const background = '#FFFFFF'
</script>
```

And use it:

```vue
<QRCode class="rounded" value="https://beathagenlocher.com/me" :width="140" :height="140" />
```

Sweet!
100
beat @beathagenlocher.com · 27/02/2026
Learned something about package.json postinstall scripts :) #npm #JavaScript #TypeScript #bun #pnpm #TIL
Today I learned that there's magic `pre<*>` and `post<*>` hooks inside `npm` (and everything npm-compatible):

-> https://docs.npmjs.com/cli/v8/using-npm/scripts

I think I would've preferred less magic-side-effect-ness, but fair, I guess.

So, to run something post `install`

```json
{
  "scripts": {
    // ...,
    "postinstall": "echo 'hello postinstall'",
  }
}
```

Caveat:

For `npm`, this only works when literally running `npm install` (or `npm i`), but now when running `npm i <some-package>`.  
Well, that's what you get, I guess?  
I'm even less convinced by its usefulness now.

For `bun` at least, both seem to work :)
120
beat @beathagenlocher.com · 20/02/2026
Especially this here for Nix Flakes :) #Nix #Simplicity #FunctionalProgramming
An entry point for Nix stuff.

# Writing your first flake

Your flake is the entry point to your application built and run with nix.

Your flake is a function from `inputs` (which consist of urls for getting package sets) to `outputs`, with outputs being one big _attribute set_.

This `outputs` _attribute set_ looks something like this (very much like a JSON object):

```nix
# outputs
{
  apps = { ... };
  checks = { ... };
  devShells = { ... };
  formatter = { ... };
  legacyPackages = { ... };
  nixosConfigurations = { ... };
  nixosModules = { ... };
  overlays = { ... };
  packages = { ... };
}
```

Every key in here is a special, magic key that Nix interprets in a certain way.

For the minimal (non-functional) flake:

```nix
{
  inputs = {};

  outputs = {...}: {};
}
```

all of them are empty.

Through populating them, we give our Nix Flake additional capabilities.

The first capability to give our Nix Flakes is normally a _development shell_.

In Nix Language, we create this like this:

```nix title="flake.nix"
{
  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
  };

  outputs = { nixpkgs, ... }: {
    devShells.x86_64-linux.default = nixpkgs.legacyPackages.x86_64-linux.mkShell {};
  };
}
```

And run it via `nix develop` (getting it? The `devShells` output gets run by `nix develop`):

```shell
> nix develop
(nix-shell-env)>
```

But that doesn't do anything yet.

For it to do something we need to add something into the devshell:

```nix title="flake.nix" del={7} ins={8-10}
{
  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
  };

  outputs = { nixpkgs, ... }: {
    devShells.x86_64-linux.default = nixpkgs.legacyPackages.x86_64-linux.mkShell {};
    devShells.x86_64-linux.default = nixpkgs.legacyPackages.x86_64-linux.mkShell {
        packages = [ nixpkgs.legacyPackages.x86_64-linux.hello ];
    };
  };
}
```

(Run `nix develop`, and then we have the `hello` executable available.):

```shell
> nix develop
(nix-shell-env)> hello
Hello, world!
```

Let's clean this up a bit, and then we're done for now.  
There's lots of repetition in the current file, and the solution to this is not as easy as inserting a `let` block.

The accepted and easily extensible solution for this is flake-parts, but their homepage is pretty hard to understand at first.

We want to translate our example to this:

```nix title="flake.nix"
{
  inputs = {
    nixpkgs.url = "https://github.com/nixos/nixpkgs?ref=nixos-unstable";
  };

  outputs = inputs@{ flake-parts, ... }:
    flake-parts.lib.mkFlake { inherit inputs; } {
      systems = [ "x86_64-linux" "aarch64-linux" "aarch64-darwin" "x86_64-darwin" ];
      perSystem = { pkgs, ... }: {
        devShells.default = pkgs.mkShell {
          packages = [ pkgs.hello ];
        };
      };
    };
}
```

This even has the benefit that it will work on all the systems specified.

<Draft>

# Evaluated Flake top level

You can inspect your flake like this:

```shell
nix repl .
> :load-flake . # shorthand ':lf .'
```

```nix
{
  _type = "flake";
  inputs = { ... };
  outputs = { ... };
  sourceInfo = { ... };
}
```

</Draft>

<References>
<ReferenceLink href="https://www.youtube.com/watch?v=JCeYq72Sko0">vimjoyer flakes guide</ReferenceLink>
</References>
100
beat @beathagenlocher.com · 20/02/2026
I've added a bit more barebones Nix stuff that can be confusing at first :) #Nix #OS #ProgrammingLanguage #nixpkgs #NixOS #FunctionalProgramming #DeclarativeProgramming
# Install Nix

The installer on the website sucks (doesn't work with SELinux, for example), but there is a community-maintained one:

```shell
curl -fsSL https://artifacts.nixos.org/nix-installer | sh -s -- install
```

With that completed, go write your first Nix Flake
100
beat @beathagenlocher.com · 16/02/2026
Apparently this is a thing: Desktop Environments in the browser #OS #TheWeb
I did not know it's a thing that people are building desktop environments in the browser for fun:

- https://webos.js.org/
- https://dustinbrett.com/

Wild.
100
beat @beathagenlocher.com · 15/02/2026
Just found a better sqlite cli: [litecli](https://litecli.com/) #TIL #sqlite
After getting annoyed by the default sqlite Command Line Interface (it doesn't even support `arrow up` ≙ last typed command), I found `litecli`

```nix title=flake.nix
# ...
packages = [
# ...
   pkgs.litecli
# ...
];
# ...
```

in every sqlite project from now on :)
100
beat @beathagenlocher.com · 12/02/2026
Auth #Auth #BetterAuth #ZITADEL #StackAuth #Logto #kanidm
Over the last few weeks, I've spent some time on and off researching a good way to do Authn and Authz, and struggled quite a bit with the number of options available.

Not completely understanding what I'm doing/how to search for my requirements, I've first started trying Logto (but that an UI clicking contest to set up), then going for kanidm without realizing this doesn't have support for a public sign in form, which is something I need.

And just now when I'm relatively sure that Better Auth fits my needs quite well, I've discovered Stack Auth via this nice website: https://www.auth0alternatives.com/

Man, if I only would've found this 4 weeks earlier.
100
beat @beathagenlocher.com · 12/02/2026
I wrote a very simple introductory note I'd really enjoyed reading some years ago: Setting Up A Server (with NixOS and Clan) #ssh #TheInternet #IP
# 1. Getting the server

As with most things, setting up a server is relatively straighforward once you know what you're doing.

So, what you want is:

- a machine that runs on the internet for you, awaiting your requests and which
- you can reach from anywhere.

Since both the 'is reachable' and the 'runs constantly' parts are relatively hard to provide when you're just starting out (and probably only have a laptop/phone), you want to go to someone who's _job_ is to provide these two things for this (That's called a _Hosting Provider_).

For starting out, the thing you're looking for is the cheapest option of a _Virtual Private Server_ (called VPS).

So for finding what you're looking for, google something like `hosting provider vps cheap reddit`, and see what's currently in store.

(This should be about 5€/month)

Then go onto the site of your choice, create an account, and rent the VPS you want.

For Hetzner in 02/2026 (the provider I'm currently using), this is something like:

- [x] Cost-optimized
- [x] CX23
- [x] Somewhere in the EU, preferably close to where you are
- [x] Ubuntu (but we're gonna change that later)
- [x] both a IPv4 and IPv6
- [ ] your _ssh keys_ (You might be wondering: What are _those_?)
- [x] and a name

That's it.

> Except for the ssh keys.

## Generating ssh keys

An _ssh key_ is a thing you can use to verify you're you to someone else.

For now, the only thing that's important about them is that our server can let us login without a password if we have the correct ssh key available on our system.

So what we need to do is generating a key pair (a private key with a matching public 'lock', confusingly also called a key), and then depositing the _public_ key  of the pair (so the _lock_) on the server.  
With this, the server will be able to tell that we're allowed to login whenever we log in from a system that has the private key of this specific key pair.

With this knowledge:

<Il href="/setting-up-git#1-generating-the-ssh-key-pair">Generate the ssh key pair</Il>

And then upload the public key to the hetzner.

You can check that everything worked by `ssh`ing onto your server:

If you're _not_ prompted for a password, everything worked.

# 2. Put NixOS on the server with Clan

Now for the important part. Just having a server means manual, stateful configuration, and we're not going to do that.

Instead, we're going to use NixOS with Clan.

Follow Creating your first Clan.

And that's it.
100
beat @beathagenlocher.com · 12/02/2026
I've recently set up Immich on my home server (with Clan), and half-finished a note on this :) #NixOS #Clan #Immich #HetznerStorageBox #Samba/CIFS #FileSystemMount
Today, I spent some time getting my photo setup together, (and away from manually uploading them to my storage box from time to time 😬).

So I've decided I'd give Immich (as a self-hostable photo solution on my server) a try.

<Callout type="info">
In case you don't know yet how to get and set up a server, take a look here: Setting Up A Server (with NixOS and Clan)
</Callout>

# Setting up Immich

Setting Immich up with NixOS is relatively straightforward, at least at first glance:

```nix
services.immich = {
  enable = true;
  host = "photos.example.ts.net";
};
```

But since I'm not running a Tailscale network (yet, I guess?), I need to make my immich service reachable from the outside.

This looks like this:

```nix
services.immich = {
  enable = true;
};

services.caddy = {
  enable = true;
  virtualHosts."photos.your.domain" = {
  extraConfig = ''
    reverse_proxy 127.0.0.1:2283
  '';
  };
};
```

<Callout type="info">
For this to work, you need to:

- own a domain
- set a DNS A record to `photos` -> "IPv4 IP of your server", e.g. `1.12.234.45`

If you don't know how to do that, take a look here: Setting A DNS Record On A Domain
</Callout>

Now we've got a machine running Immich that's reachable from the outside via a domain.

Meaning: You should be able to go to `https://photos.your.domain`, and see the Immich login screen.

So that's it—almost.

# Mounting a storage box

We've still got one problem:

The machine this is running on is the smallest Hetzner VPS: powerful enough to run Immich, but not large enough to host all the photos.  
It's got only 40GB of disk space, and upgrading that gets expensive fast.  
So you probably don't want to store photos on there.

Thankfully, Hetzner also has a service called a _Storage Box_.

A Storage Box has the opposite characteristics to a VPS: It's pretty cheap per TB, but you can't really run anything on it.  
So, let's match the two.

What we're going to do is the following:

We will mount the storage box as a file system into the folder in which Immich is going to store its data in.

For this, you need to:

1. Have a Storage Box (so rent one)
2. Create a new sub-account (with corresponding subfolder) on it. _Save the password for this into your password manager_
3. Mount the subfolder into our server

Mounting the subfolder looks like this:

<Draft>

```nix
services.immich = {
  enable = true;
};

services.caddy = {
  enable = true;
  virtualHosts."photos.your.domain" = {
  extraConfig = ''
    reverse_proxy 127.0.0.1:2283
  '';
  };
};
environment.systemPackages = [ pkgs.cifs-utils ];
fileSystems."/var/lib/immich" = {
  device = "//uXXXXXX-subX.your-storagebox.de/uXXXXXX-subX";
  fsType = "cifs";
  options = [
    "x-systemd.automount"
    "noauto"
    "x-systemd.device-timeout=5s"
    "x-systemd.mount-timeout=5s"
    "credentials=${config.clan.core.vars.generators.storagebox-immich-secret.files."credentials".path}"
    "uid=immich"
    "gid=immich"
  ];
};
```

- vars setup
- add the secret to vars

- issues
  - immich can't write or read the files
  - with disabled machine learning the server dies, and I don't know why

</Draft>

# Notes

<References>
<ReferenceLink href="https://forums.linuxmint.com/viewtopic.php?t=333084">Samba Storage Share </ReferenceLink>
</References>
110
beat @beathagenlocher.com · 12/02/2026
Trader/Non-trader in Chrome Web Store #EU #ChromeWebStore
Today, I thought about writing and publishing an extension to the Chrome Web Store.

I researched, and found this funny bit:

The EU apparently requires you to indicate whether you're a _Trader_ or _Non-Trader_, when you want to publish extensions on the store.

Which is fine, (I guess), but apparently literally no one has a definitive answer to what that means:

- Am I a trader or non-trader | Stackoverflow
- What constitutes a trader
- trader/non-trader disclosure | Google Groups
100
beat @beathagenlocher.com · 08/02/2026
Diffing two directories #TIL #shell
Today I learned that you can diff two directories just like you can diff two files:

```shell
diff dir1/ dir2/

# or recursively
diff --recursive dir1/ dir2/
```

Sweet!

Comparing the contents of two directories | Stackoverflow
100
beat @beathagenlocher.com · 08/02/2026
New stuff #TIL #Emacs
Just now I learned

- about Shannon, an pentesting agent
- and that there's still an ongoint "Emacs in Rust" experiment people are actively working on!
  -> rune

Looking forward to try Shannon out ^^

(rune isn't anything more than an Elisp interpreter right now)
100
beat @beathagenlocher.com · 06/02/2026
mas.to/@zekjur/1160... This is wild.
mas.to
Michael Stapelberg 🐧🐹😺 (@zekjur@mas.to)
Attached: 4 images PSA: Did you know that it’s **unsafe** to put code diffs into your commit messages? Like https://github.com/i3/i3/pull/6564 for example Such diffs will be applied by patch(1) (al...
000
beat @beathagenlocher.com · 05/02/2026
OAuth Playground #OAuth #PKCE #Learning #Auth
Now that it's working again (I couldn't access it some days ago), OAuth playground is just a pretty nice way to understand code flows in Auth.
100
beat @beathagenlocher.com · 05/02/2026
<a name=\ #HTML #TheWeb #TIL
Today I learned there was a `name` attribute in HTML once.
100
beat @beathagenlocher.com · 05/02/2026
Okay, I get the hype: npmx.dev is pretty cool.
npmx.dev
npmx - Package Browser for the npm Registry
A better browser for the npm registry. Search, browse, and explore packages with a modern interface.
000
beat @beathagenlocher.com · 05/02/2026
Which brought me to write about Dynamic Scoping, too :) #Racket #Lisp #ProgrammingLanguages #LexicalScoping
> Lexical Scoping refers to when the location of a function's definition determines which variables you have access to. On the other hand, Dynamic Scoping uses the location of the function's invocation to determine which variables are available.

```racket title=dyn-scope.rkt
#lang smol/dyn-scope-is-bad

(defvar y 2)
(deffun (times-2 x)
    (* y x))

(let ([y 3])
  (times-2 2))
(times-2 2)
```

Evaluation:

```racket
6
4
```

Well, this is bad.

<References>
<ReferenceLink href="https://youtu.be/b1MvKYHCdno?si=O6qEgfY2QtH_HtqG&t=2330">Programming Languages Lectures</ReferenceLink>
<ReferenceLink href="https://www.plai.org/3/2/PLAI%20Version%203.2.2%20printing.pdf"><KB>Ctrl+f</KB> for 'dynamic scope'</ReferenceLink>
</References>
100
beat @beathagenlocher.com · 05/02/2026
Inspired by my TypeScript macro finding yesterday, I started a note about Macros :) #Lisp #Racket #Emacs #Clojure #DSLs #CommonLisp #Rust
The idea is pretty simple:

We write code, and sometimes we repeat ourselves there. So why not write code that abstracts over this repetition?

That is what _Macros_ are.

# C Macros

The lowest sophistication level (and the worst to work with) is something like C preprocessor macros. There, you do the easiest thing possible: A string replacement over the full following program text, irrespective of anything else.

So a Macro in C has this type signature:

```haskell
CMacro :: String -> String
```

---

```c title=simple.c
#define LALA "hohohoho"

char laugh[] = LALA
```

->

```c title=simple.c
char laugh[] = "hohohoho"
```

A standard C Macro usage looks like this:

```c title=min.c
#define min(X, Y)  ((X) < (Y) ? (X) : (Y))
  x = min(a, b);          →  x = ((a) < (b) ? (a) : (b));
  y = min(1, 2);          →  y = ((1) < (2) ? (1) : (2));
  z = min(a + 28, *p);    →  z = ((a + 28) < (*p) ? (a + 28) : (*p));
```

But since this is just doing straight string replacement, anything can happen (There's just no rules whatsoever for the expansion, so you can have unbalanced parentheses and other weird stuff).

They're still powerful, and you probably want to use them in C, but we can do better.

# Lisp Macros

Lisp Macros do better: They're not doing string replacement, but operate on the _syntax tree_.

```haskell
LispMacro :: Syntax -> Syntax
```

The also have a second pretty helpful property: They use _the same syntax as the base language_.
So their type is actually this:

```haskell
LispMacro :: LispSyntax -> LispSyntax
```

This is what they look like:

```lisp
> (defmacro the-macro (a b)
  `(+ ,a (* ,b 3)))

> (the-macro 4 5) ;; => 19
```

This makes them way more generally useful (for example)

But they, too, have a problem: They're not _hygienic_.

That is, they're introducing variables into their target program that could be referenced both from the inside and outside.

This possibly couples the macro source code to the location it's being used in, which, if you squint, is the one unambigiously bad (default) feature in programming languages (again): Dynamic Scoping

There's an easy fix for this, however: gensym

But this makes for a relatively bad default, and we shouldn't rely on programmers knowing all the ways in which they could fuck up, and then having the mental workload left to fix them.

<Draft>

# Scheme Macros

Scheme was the first language that didn't have the problem of accidental scope leaking. They introduced _hygienic macros_.



# Racket Macros

Racket escalates this completely, and has facilities to define whole new languages.

</Draft>

# Notes

- I've skipped procedural macros#Procedural_macros) (never heard of them before)
- Damn, are those AI Memos badly scanned sometimes: https://dspace.mit.edu/bitstream/handle/1721.1/6111/AIM-057.pdf?sequence=2&isAllowed=y
120
beat @beathagenlocher.com · 04/02/2026
TypeScript Macros #TypeScript #Macros #FunctionalProgramming #Immutable.js
Today I've been playing around with Immutable.js as a library that supplies immutable collection for JavaScript and TypeScript.

Something that's relatively annoying at first sight (cause _verbose_) is how they handle typed Objects (_Records_).

I'm not completely sure yet, but I _think_ in TypeScript you want to declare sums of products using those records something like this:

```js
import { Record } from 'immutable'

type IdleT = { kind: 'Idle' }
type Idle = Record<IdleT>
const Idle = Record<IdleT>({ kind: 'Idle' })

type TrackT = { id: string; url: string; title: string }
type Track = Record<TrackT>
const Track = Record<TrackT>({ id: '', url: '', title: '' })

type LoadingT = { kind: 'Loading'; track: Track }
type Loading = Record<LoadingT>
const Loading = Record<LoadingT>({ kind: 'Loading', track: Track() })
// ..

export type State = Idle | Loading // | ..
```

This is, compared to

```js
export type Track = { id: string; url: string; title: string }

type Idle = { kind: 'Idle' }
type Loading = { kind: 'Loading'; track: Track }
// ..


export type State = Idle | Loading // | ..
```

quite the feat syntax-wise.

It can be alleviated a bit by doing something like this:

```js

export const makeRecord = <T extends object>(defaults: T) => Record<T>(defaults)

const Idle = makeRecord({ kind: 'Idle' as const })
type Idle = RetureType<typeof Idle>
```

But even that is still double the code and more mental workload payed as a price for immutability.

So I wondered:

> Are there people writing Macros in TypeScript?

And as it turns out, there are:

- ts-macros
- macro-ts

But nope, I'm not gonna use them.
110
beat @beathagenlocher.com · 04/02/2026
selfhostblocks #Clan #NixOS #Selfhosting
Today, I lurked a bit around the on the clan gitea, which made me find this nice PR trying to nail down a better clan positioning in the future, which made me find selfhostblocks.

Cool!

(Though I'm pretty convinced by clan so far)
100
Reposted by beat
Fight Chat Control @fightchatcontrol.bsky.social · 02/02/2026
The European Commission continues to push to extend Chat Control 1.0, threatening your fundamental right to privacy. MEPs on the Civil Liberties Committee (LIBE) need to hear from you directly: fightchatcontrol.eu now allows you to contact them all at once. Speak up today!
https://fightchatcontrol.eu/ now allows you to contact all members of the Civil Liberties Committee (LIBE) with a single click.
0202221
beat @beathagenlocher.com · 03/02/2026
Just started a Bun note with me forgetting @bun/types in my tsconfig.json #bun #JavaScript #TypeScript
# Correct Types in TypeScript

Don't forget to install your types:

```shell
bun install @bun/types
```

And then, add them to your `tsconfig.json`:

```json title=tsconfig.json
{
  "compilerOptions": {
    "types": ["node", "types/bun"],
    "skipLibCheck": true,
    }
}
```
110
beat @beathagenlocher.com · 03/02/2026
I've added a note about (me learning) Auth :) #Auth #Authn #Authz #OAuth #OIDC #PKCE
Over the last few days, I've plunged into finally trying to understand how all of this Auth stuff works.

(The landscape of Acronyms is almost as bad as with the CORS one)

These are the videos/sites I would've liked to find from the beginning on:

- The Auth Wiki from Logto, but only as a reference whenever some word is unclear (though that has duplicate pages for some reason)
- Illustrated Guide to OAuth and OIDC (Youtube)
- Everything you ever wanted to know about OAuth and OICD (though the mentions OAuth playground is currently broken, or so it seems)
- OAuth 2 Simplified (Blog Post), which has been expanded into OAuth 2 Simplified (Book)

# Not super-intuitive stuff

- A normal web client shouldn't have a client secret (makes sense if you think about it), and needs to use PKCE
- OAuth is only about _Authorization_ (read: Authorizing the service you're currently logging in to to access some resources on another service), OpenId Connect (OIDC) adds _Authentication_ (read: telling the service you're currently logging into who you are) to this.
- In my head, every service supporting OAuth (or OIDC, at least) also supported something called "Public Sign Up". But that's not the case, most of them actually don't (which makes sense, because _Authorization_ and _Registration_ don't even belong to the same area)
100
beat @beathagenlocher.com · 03/02/2026
Hey, I've just started a new note about Clan (clan.lol) :) #NixOS #Clan #InfrastructureasCode #Selfhosting
_The_ tool to manage homelabs.

Provides a really nice CLI over standard NixOS stuff, like setting up a new server, `ssh`ing onto the server, handling secrets management for all of your machines, setting up users everywhere, backing up state from your machines, and so on.

`Clan services` (services that span multiple machines are also pretty nice.)

Secrets are handled via the `var` abstraction. This lets us write _generators_ for secrets so that they mostly already come prepackaged with the service one is using.

# Useful commands

```yaml
machines: [formenos]
```

## ssh onto a machine

```shell
clan ssh formenos
```

## Show machines

```shell
clan machines list
```

## Update a machine

```shell
clan machines update formenos
```

## Show the value of a secret

```shell
clan vars list formenos
# (pick one)
clan vars list root-password/password
```
110
Reposted by beat
Local-First Conf @localfirstconf.com · 30/01/2026
We’re back. Save the date: www.localfirstconf.com
localfirstconf.com
Local-First Conf 2026
Join us for the third edition of Local-First Conf. Connect with a rapidly-growing community in an intimate setting. Berlin 12-14th July 2026.
0209
beat @beathagenlocher.com · 28/01/2026
I'm now switching windows with run-or-raise, cause that's better ^^ #Simplicity #Flow #GNOME
This is a piece for my self-from-2-years-ago.

I am automation obsessed. Over the last few years, I've accumulated nice level of tooling to speed things up: Almost all of the most-used commands in my shell history are aliases, I'm creating most of my projects via Nix Flake Templates, and my keyboard layout is full of Macros. But arguably, one of the very basic things has been still missing.

For the longest time in my life, I've been wasting countless keystrokes for switching between all the windows on my computer.

Enter gnome-magic-window—since 8 months, I've rarely been switching windows 'the normal way':

- Want to open your <span class="font-bold">t</span>erminal? <KB>⌘+t</KB> (Hold super, press t, release both)
- Want to start to <span class="font-bold">c</span>ode? <KB>⌘+c</KB>
- Switch back to your <span class="font-bold">t</span>erminal? <KB>⌘+t</KB>
- Look something up on the internet? <KB>⌘+f</KB> (<span class="font-bold">f</span>irefox)
- Resume <span class="font-bold">c</span>oding? <KB>⌘+c</KB>

Yes, it's better, and yes, it's way faster.

But for me, the key difference is that it's _simpler_—there isn't the shortest moment when I'm tabbing through my open applications trying to select the one I want to switch to.

That's what I want to use automation for: Simplicity.

---
And in case anyone of you got something that's even more versatile/faster/better, _please_ do let me know! (And yes, I do know about tiling window managers.)

---

Note (01/2026): Nowadays, I use run-or-raise (Github) with this shortcuts.conf

That's pretty nice! :)
100
beat @beathagenlocher.com · 28/01/2026
I added a note about "How People Learn" (a course named after the ed report) #Learning #Education #Coding #Psychology
A course I taught on learning at Schülerakademie Roßleben 2022:

Started with Neuroanatomy basics, then moved on to the psychology of learning, and concluded with applying the learned things to learning a programming language (Racket, of course ^^)

Interleaved with generally useful inputs on

- how to read a paper
- finding good papers on a topic
- how to do good presentations

<References>
  <ReferenceLink href="https://www.scotthyoung.com/blog/the-best-articles-on-learning/">Scott Young: Best articles on learning</ReferenceLink>
  <ReferenceLink href="https://www.scotthyoung.com/blog/2022/01/04/cognitive-load-theory/">Scott Young: Cognitive Load Theory</ReferenceLink>
  <ReferenceLink href="https://www.youtube.com/watch?v=vd2dtkMINIw">Barbara Oakley: Learning How to Learn</ReferenceLink>
  <ReferenceLink href="https://streaming.media.ccc.de/rc3_2021/relive/496">Mike Sperber: Teaching Programming to your loved ones</ReferenceLink>
  The title-giving report: <ReferenceLink href="https://www.nationalacademies.org/publications/9853">How People Learn</ReferenceLink>
  <ReferenceLink href="https://www.amazon.de/-/en/Peak-Secrets-New-Science-Expertise/dp/0544456238">Peak (Anders Ericsson)</ReferenceLink>
  <ReferenceLink href="https://www.amazon.de/Dont-Teach-Coding-Until-Read/dp/1119602629">Don't Teach Coding: Until You Read This Book</ReferenceLink>
</References>
110
beat @beathagenlocher.com · 27/01/2026
Hey, I wrote a new piece about another bug that has plagued me for a while :) #GNOME #Linux #AMD #Framework #Framework16 #NixOS #KernelParameters
Over the last time, I've pretty regularly had Screen Freezes that appeared out of nowhere (maybe together with faster screen movement/resource usage, but not in a reproducible way).

It had the following characteristics:

1. The screen froze out of nowhere
2. Although the screen didn't show anything anymore, all input still worked (so typing went on, the mouse could still click on stuff, and so on)
3. Among them, locking my screen via GNOME's <KB>Super-l</KB> still worked
4. As soon as the error appeared once, it tended to appear again and again until a restart
5. After a restart, it was good for a while
6. It only appeared on my internal laptop screen, not on an external display

This is what it looked like (yep, bad video, I know 😬):

<video controls="controls" preload="auto">
  <source src="amd-screen-errors.mp4" type="video/mp4"/>
</video>

All of the above mostly .. confused me.

- <ListMarker>2.</ListMarker> and <ListMarker>3.</ListMarker> strongly pointed into the direction of GNOME being the problem, and maybe my session crashing in a weird way
- <ListMarker>4.</ListMarker> and <ListMarker>5.</ListMarker> pointed into the direction of a resource exhaustion problem (but there wasn't higher resource usage at all), while
- <ListMarker>6.</ListMarker> pointed in the direction of it being a hardware/firmware problem, or at least towards it being hardware/firmware induced.

These findings left me relatively clueless, since they didn't really point towards anything specific (without also pointing away from it).

So I googled.

At first, I didn't really find anything, but then I came upon some other Framework users experiencing similar issues.

Among them was this post: https://www.reddit.com/r/framework/comments/1goh7hc/anyone_else_get_this_screen_flickering_issue/

Which pointed to an AMD power-saving technology that has pretty regular regression bugs.

So I tried that fix out (and first needed to learn how to enable Kernel Parameters on NixOS), and .. I didn't have any further screen freezes since then. (Well, only whole GNOME session crashes on Bluetooth Headphones / Unmute, but that's for another post)

This is the fix:

```nix title=hardware.nix
config.boot.kernelParams = [
  # https://www.reddit.com/r/framework/comments/1goh7hc/anyone_else_get_this_screen_flickering_issue/
  "amdgpu.dcdebugmask=0x410"
];
```


<References>
## Probably different
  <ReferenceLink href="https://community.frame.work/t/screen-keeps-flickering-on-lower-brightness/70138/5">Screen keeps flickering on lower brightness - Framework Laptop 13 / Linux - Framework Community</ReferenceLink>
  <ReferenceLink href="https://community.frame.work/t/flickering-when-using-firefox-under-kde-wayland-on-ryzen-ai-300/69599/3">Flickering when using Firefox under KDE Wayland on Ryzen AI 300 - #3 by trumee - Linux - Framework Community</ReferenceLink>
  <ReferenceLink href="https://community.frame.work/t/framework-13-amd-7040-some-screen-flicker-still/75766">Framework 13 AMD 7040: some screen flicker still - Linux - Framework Community</ReferenceLink>
  <ReferenceLink href="https://community.frame.work/t/responded-screen-issues-on-fedora/40772/7">[RESPONDED] Screen issues on Fedora - #7 by dimitris - Linux - Framework Community</ReferenceLink>

## Maybe related
  <ReferenceLink href="https://community.frame.work/t/screen-flickers-after-logging-in-and-at-random-times/39192">Screen flickers after logging in and at random times - Framework Laptop 13 - Framework Community</ReferenceLink>
  <ReferenceLink href="https://discourse.nixos.org/t/nixos-hard-locks-driving-my-crazy/72210/7">NixOS hard locks driving my crazy! - Help - NixOS Discourse</ReferenceLink>

## Resolved the issue
  <ReferenceLink href="https://www.reddit.com/r/NixOS/comments/1hjruq1/graphics_corruption_on_kernel_6125_and_up/">Graphics corruption on kernel 6.12.5 and up : r/NixOS</ReferenceLink>
  <ReferenceLink href="https://www.reddit.com/r/framework/comments/1goh7hc/anyone_else_get_this_screen_flickering_issue/">Anyone else get this screen flickering issue? : r/framework</ReferenceLink>
  <ReferenceLink href="https://jonathanmfung.com/setting-kernel-parameters-in-nixos">Setting Kernel Parameters in NixOS</ReferenceLink>
  <ReferenceLink href="https://gitlab.freedesktop.org/drm/amd/-/issues/3827">Making sure you're not a bot!</ReferenceLink>
  <ReferenceLink href="https://community.frame.work/t/solved-my-screen-flickers-sometimes/26270/2">[SOLVED] My screen flickers sometimes - #2 by Anachron - Linux - Framework Community</ReferenceLink>
</References>
110
beat @beathagenlocher.com · 26/01/2026
Using dig #DNS #dig #JuliaEvans
Just had a small DNS dispute again, and remembered the post by Julia Evans on using `dig`.

Pretty helpful!

(She's also wrote a website for simple DNS lookup: a simple DNS lookup tool)
100
beat @beathagenlocher.com · 23/01/2026
I added some pitfalls/learnings to my Emacs config bits page :) #Emacs #Doom Emacs #completion
# Speeding up lsp-mode

- emacs-lsp-booster
- lsp-mode | performance

# Setting the battery indicator

```elisp
;; config.el
(custom-set-variables
 '(display-battery-mode +1)
 '(battery-load-low 30)
 '(battery-load-critical 15))
```

# Easier font size management

```elisp
(map! :leader
      :desc "Decrease UI font size" :g "-" #'doom/decrease-font-size
      :desc "Increase UI font size" :g "+" #'doom/increase-font-size
      :desc "Change to framework font size" :g "!" (cmd! (doom/increase-font-size 4))
      :desc "Reset UI font size" :g "=" #'doom/reset-font-size)
```

# Don't pollute the kill ring

```elisp
(map! :i "C-<backspace>" #'doom/delete-backward-word
      :i "C-<delete>" #'delete-word)
```

# Never, ever need accidentially hit enter and complete a suggestion

```elisp
(after! company
  (keymap-unset company-active-map "<return>" t)
  (keymap-unset company-active-map "RET" t)
  (define-key company-active-map (kbd "<tab>") #'company-complete-selection)
  (define-key company-active-map (kbd "TAB") #'company-complete-selection))
```

# Make the suggestions overlay fast enough

```elisp
(after! company
  (setq company-idle-delay 0.1)
  (setq company-tooltip-idle-delay 0.1)
  (setq company-minimum-prefix-length 1))
```

# Add Emoji :shortcodes: to the suggestions overlay

```elisp
(require 'company-emoji)

(after! markdown-mode
  (set-company-backend! 'company-emoji #'bah/wikilinks-backend))
```

<Callout type="warning">
If you're adding more than one custom suggestion per mode, you need to add both in one call:

```elisp
(after! markdown-mode
  (set-company-backend! 'some-other 'company-emoji #'bah/wikilinks-backend))
```

</Callout>
130
beat @beathagenlocher.com · 20/01/2026
Threading Macros And Pipes #Macros #Racket #Functional Programming #Simplicity #Declarative Programming
≺≻ Modeling Programs as Functions

In functional programming, we try to model our programs around the idea of functions that take in inputs and return outputs.

A webserver, for example, is just this:

```
HTTP request -webserver-> HTTP response
```

A compiler is this:

```
Source code -compiler-> Target Language Code
```

And a file browser window just this:
```
folder path -file browser-> folder view
```

When we go through with this, a big chunk (except for the part at ther boundary where effects should happen) of our program is just Function Composition.

When we apply this idea naively, our programs look like this:

```js title="main.ts"
export const main = (request) => {
    return formatResponse(validateRequest(parseHeaders(request)))
}
```

```elixir title="main.ex"
def main(request) do
  format_response(validate_request(parse_headers(request)))
end
```

```haskell title="main.hs"
main :: Request -> Response
main req = formatResponse(validateRequest(parseHeaders req))
```

```racket title="main.rkt"
(define (main request)
 (format-response (validate-request (parse-headers request))))
```

This is okay (and still waay better that a ton of statements, I'd say), but not it loses the nice property of statements that it's not nested.  
Also, when we try to read/understand the code, we're probably going to read it from right to left (while mentally 'piping' through the arguments in our heads).

If we mentally parse the code like this either way, we can also just write it down like that:

```js title="main.ts"
import { pipe } from 'effect' // for example, but it's pretty easy to write yourself, too

export const main = (request) => {
    return pipe(request,
                parseHeaders,
                validateRequest,
                formatResponse)
}
```

```elixir title="main.ex"
def main(request) do
  request
  |> parse_headers
  |> validate_request
  |> format_response
end
```

```haskell title="main.hs"
main :: Request -> Response
main req = req & parseHeaders & validateRequest & formatResponse
```

```racket title="main.rkt"
(require threading)

(define (main request)
 (~> request
     parse-headers
     validate-request
     format-response))
```

Two more sidenotes:

- I think pipelines are especially useful when paired with something like Elixir's `IO.inspect()`, like this:

```elixir title="main.ex"
def main(request) do
  request
  |> parse_headers
  |> IO.inspect()
  |> validate_request
  |> format_response
end
```

- Pipelines need their functions to be functions of only one arg (in 'effect', for example), or they need to make a choice on where to splice the argument into the function call. Elixir always splices it into the first, while Lisps aren't decided that (decide per-case), and normally have both a `->` (inserts as the first argument), `->>` (inserts as the last argument), as well as `-as->`-variants, which you can call like this:

```elisp title="example.el"
(-as-> topic-string _
  (string-split _ "," t " ")
  (mapcar (lambda (topic) (concat "#" topic)) _)
  (string-join _ " "))
```
110
beat @beathagenlocher.com · 19/01/2026
Backing up my stuff from Github #git #Github #Codeberg #Gitlab #Gitea #Forgejo
Today, I thought about making myself less dependent on Github (once again).

There are a few ways to do that (I still want to have an account on Github and be there, just be less dependent on them) – the easiest would be thing for this would be setting up a git mirror.

I could do that by setting up my private git forge (forgejo or gitea), or by just going to one of the git hosters (Gitlab or Codeberg)

Two promising tools for that seem to be:

- https://github.com/ChappIO/git-backup
- and https://github.com/cooperspencer/gickup

But _actually_ setting this up is for another day. ^^

(Small caveat: Codeberg doesn't want you to mirror your stuff there for resource reasons.)
100
beat @beathagenlocher.com · 19/01/2026
dash.el for nice list operations #Emacs #Macros #Syntax #Functional Programming
When programming in Elisp I've been using the `->` and `->>` threading Macros for quite some time.

I learned to value them first in Racket (`(require threading)`), then in Elixir: (`|>`), and since then I use them wherever it makes sense (`&` in Haskell, or the `->`/`->>` Threading Macros in Clojure, for example.

And yesterday, I opened the in-Emacs docs of `->` for the first time (since I actually needed `-as->` in that situation, and wanted to see whether it exists, which it does) only to see it's part of a library called dash.el, which has lots of list functions with an intuitive api.

Nice!

In case you're _more_ confused about Threading Macros now than you were before, take a look here: Threading Macros And Pipes.
110