Skip to main content

Activity for beathagenlocher.com

Active on:

Loading activity...

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
Bluesky network3mo ago

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!
Bluesky network3mo ago

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.

Bluesky network3mo ago

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!
Bluesky network3mo ago

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>
Bluesky network3mo ago

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 :)
Bluesky network3mo ago

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.

:)
Bluesky network4mo ago

Wrote a piece about functional programming in TypeScript on the frontend for the company blog: funktionale-programmierung.de/en/2026/03/0... :)

Bluesky network4mo ago

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)
Bluesky network4mo ago

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.)

:)
Bluesky network4mo ago

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 :)
Bluesky network4mo ago

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! :)
Bluesky network4mo ago

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!
Bluesky network4mo ago

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 :)
Bluesky network4mo ago

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>
Bluesky network4mo ago

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
Bluesky network4mo ago

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.
Bluesky network4mo ago

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 :)
Bluesky network4mo ago

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.
Bluesky network5mo ago

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.
Bluesky network5mo ago