terraform-ts-mode

At work I work with Terraform, so need to edit quite a lot of tf files in GNU Emacs. There's no built-in terraform-ts-mode Emacs package and I don't want to install regexp-based terraform-mode package

Existing terraform-ts-mode is this: kgrotel/terraform-ts-mode, which claims being experimental. So it's a good opportunity to learn how to create major modes using Tree Sitter and on <2026-05-05 Tue> I built my own with Claude Code.

Installation: put terraform-ts-mode.el to load-path.

Usage:

(use-package terraform-ts-mode :ensure nil
  :init
  (add-to-list 'major-mode-remap-alist '(terraform-mode . terraform-ts-mode)))

With this knowledge, the next step is [TK: try mickeynp/combobulate package].

SafariTabs.app

We need RSS for sharing abundant vibe-coded apps post by Matt Webb mentions Tabulator app, part of Wall of Apps by Matt Sephton. All the apps there share the same minimalist design, native Swift UI, tiny and, supposedly, fast. The downside is that they are paid.

I wanted to try Tabulator, as I use seal_safari_tabs.lua with Hammerspoon. So I built SafariTabs.app (using Claude Code, a project), source code is here – velppa/SafariTabs. Version v0.1.0.

260508--safari-tabs-v0-1-0__app_macos.png

Features:

  • Fuzzy-searching for tabs
  • Keyboard navigation
  • Switching to the selected tab
  • Close tab
  • Headless mode, "safaritabs:" URL scheme

Let's see if I'm going to use it, if yes – will think about publishing a release. If you want to build it – clone the repo and ask Claude Code to build it.

Trying org-tufte for public notes

On <2026-04-28 Tue> I tried to add footnotes to notes in Textpod. It works, {C-c C-x f} adds a footnote link. Then publishing works normally, just make sure footnotes heading is level down of the published note (usually h2).

But, footnotes looks ugly, and rendered at the bottom. I want sidenotes, as in Tufte CSS. I remember org-tufte exporter, so decided to try. Apparently, there's newer version exists - Zilong-Li/org-tufte. Let's try it.

(use-package org-tufte :ensure nil)

Export to plain HTML looks fine, side-notes as footnotes work fine this is side note, but in Org file it's actually a footnote! . But when exporting into Textpod – body only, + custom HTML and JS injected – it breaks.

Okay, time to some vibe-coding. New package textpod-org-tufte.el.

HTML export to Textpod

On <2026-04-27 Mon> I'm once again fascinated how bad Markdown is and how broken the export is. So for Textpod I decided to skip Markdown, and export notes directly from Org to HTML.

Problem

Markdown can't handle lists properly.

This Org Mode block:

- github :: [[https://github.com/inanimate-tech/mist][inanimate-tech/mist]]
- posts ::
  - [[https://interconnected.org/home/2026/02/12/mist][mist: Share and edit Markdown together, quickly (Interconnected)]]
  - [[https://interconnected.org/home/2026/04/10/open-mist][mist is now open source and looking for interop (Interconnected)]]

: curl https://mist.inanimate.tech/new -T file.md

is exported as this Markdown block:

-   **github:** [inanimate-tech/mist](https://github.com/inanimate-tech/mist)
-   **posts:** -   [mist: Share and edit Markdown together, quickly (Interconnected)](https://interconnected.org/home/2026/02/12/mist)
    -   [mist is now open source and looking for interop (Interconnected)](https://interconnected.org/home/2026/04/10/open-mist)

    curl https://mist.inanimate.tech/new -T file.md

which is then converted to this HTML block:



The list was rendered completely wrong.

When exporting from Org directly to HTML, it's correct - dl/dt/dd tags are used, comment rendered as verbatim text:


github
inanimate-tech/mist
posts
curl https://mist.inanimate.tech/new -T file.md

Solution

Luckily, the change in texptpod.el is this:

-   (org-export-string-as org-text 'md t)
+   (org-export-string-as org-text 'html t)

I also changed backend to preprocess internal links, they look like this:

../markdown.md // for Markdown export
../markdown.html#5B98C220-A8F4-4202-9708-82B0D000A4E9 // for HTML export

Preprocessing changes both to ?q=markdown., so search starts to /markdown. which matches both md and html extensions.

remoto.el: Browse GitHub repos without cloning

An Emacs package to browse Github without cloning the repository. Uses magit/ghub package for interacting with Github.

Installation:

(use-package remoto
  :vc (:url "https://github.com/agzam/remoto.el"))

Examples:

(remoto-browse "torvalds/linux") ;; hangs for a minute on a slow connection
(remoto-browse "https://github.com/agzam/remoto.el")
(find-file "/github:nex-crm/wuphf@main:/CHANGELOG.md")

Screenshot: img

Hosting static HTMLs

While I'm on vacation and Mac Mini 2024 (my home server) is turned off, I wanted to host HackerNews legible frontend (html file) on the Internet with minimum setup as possible.

Obvious choices like Cloudflare Workers, Github Pages and all their variants were ruled out as they require accounts, backed by big corps while I wanted something indy.

Playing with Surge, made me realise it's a nice way to host static html artifacts created with AI, so here it is: https://hotter.surge.sh.

Operations
(let ((default-directory "~/Developer/src/github.com/velppa/hotter.surge.sh"))
  (async-shell-command
    ;; "surge --help"
    ;; "surge files hotter.surge.sh"
    ;; "surge . hotter.surge.sh" ;; deploy
    "make deploy"
    "*hotter.surge.sh*"))

(setq surge-server (ws-file-server :dir "~/Developer/src/github.com/velppa/hotter.surge.sh" :port 9004))
(ws-stop surge-server)

textpod.el v0.3.0

Another round of improvements of textpod.el, a companion Emacs package to Textpod.

Custom ID format - attribute name and prefix

Added two defcustom options in textpod.el:

  • textpod-id-property (default "TEXTPOD_ID") - name of the Org property used to store the note id.
  • textpod-id-prefix (default "") - prefix prepended to ids when stored, stripped before sending requests to the server.

Introduced textpod--strip-prefix helper and updated all id call sites (textpod-open-current-note, textpod-org-heading-to-note, textpod-org-region-to-note) to respect these configs.

textpod-set-id sets ID without creating a node

I need a function to create texpod id property without sending note to the server. This is needed to submit old notes so they have proper timestamp.

Added textpod-set-id: an interactive command that sets textpod-id-property on the current heading (with textpod-id-prefix) derived from a time as YYYYMMDDhhmmss, without making any HTTP request.

  • No prefix arg → uses (current-time).
  • With prefix arg → prompts via org-read-date, so the note can be back-dated before later uploading with textpod-org-heading-to-note.
Quickly getting to Textpod notes

Using Ack is possible, placing point on a file-name and pressing {H-RET} will call visit-source and go directly to the line.

ack ":ID:\s*textpod_" *.org | grep -v 2026-04-22 | head -n 20

2026-04-12.org:226::ID: textpod_20260412124048
2026-04-13.org:10::ID: textpod_20260414214110
2026-04-13.org:237::ID: textpod_20260414212612
2026-04-13.org:247::ID: textpod_20260414224720
2026-04-13.org:263::ID: textpod_20260414210103
2026-04-13.org:314::ID: textpod_20260414224944
2026-04-16.org:12::ID: textpod_20260416100352
2026-04-16.org:97::ID: textpod_20260417092822
2026-04-21.org:205::ID: textpod_20260421120000

Since I made my Textpod notes to be Org-roam notes, I can query them directly from SQLite DB.

  • (sqlite-mode-open-file "~/.config/emacs/org-roam.db")

    select id, file, title from nodes n where n.id like '"textpod_%"';

textpod_20260329123259 ~/Notes/2026-03.org Standalone HTML Galleries
textpod_20260315124829 ~/Notes/2026-03.org Build HackerNews Legible Frontend
textpod_20260415130636 ~/Notes/2026-04.org Improve public notes
textpod_20260415205822 ~/Notes/2026-04.org “Why Kim Wexler Was The Real Villain of Better Call Saul” by BreakingHub (19 min)
textpod_20260415210948 ~/Notes/2026-04.org “Linus Lays down the Law” – The PrimeTime (ThePrimeagen), 13 min
textpod_20260412124048 ~/Notes/daily/2026-04-12.org Improve HackerNews Legible Frontend
textpod_20260414214110 ~/Notes/daily/2026-04-13.org Interview about k8s setup in Amazon
textpod_20260414212612 ~/Notes/daily/2026-04-13.org kumo – local AWS emulator in Go
textpod_20260414224720 ~/Notes/daily/2026-04-13.org Snowflake MCP server
textpod_20260414210103 ~/Notes/daily/2026-04-13.org Claude Code quota exhausted / degraded quality
textpod_20260414224944 ~/Notes/daily/2026-04-13.org Trying Textpod once again
textpod_20250612070826 ~/Notes/html.org I'm betting on HTML
textpod_20260421120000 ~/Notes/daily/2026-04-21.org Rewrite Textpod to Go
textpod_20260416100352 ~/Notes/daily/2026-04-16.org Advice to org-roam-dailies-goto-today
textpod_20260417092822 ~/Notes/daily/2026-04-16.org Links <2026-04-16 Thu>
textpod_20260422124810 ~/Notes/daily/2026-04-22.org textpod.el v0.3.0

[TK: :colnames yes got broken for some reason].

My textpod.el config

The config automatically makes notes published to Textpod as Org-roam nodes.

(use-package textpod
  :config
  (setq textpod-id-property "ID"
        textpod-id-prefix "textpod_"
        textpod-token (cadr (auth-source-user-and-password "finita.myaddr.dev" "textpod"))
        ;;textpod-url "/notes"
        textpod-url "http://localhost:3000/notes"
        )
  :bind (("C-c b x" . textpod-set-id)
         ("C-c b b" . textpod-org-heading-to-note)
         ("C-c b o" . textpod-open-current-note)))

Updates:

  • <2026-04-23 Thu>: add keybindings

Rewrite Textpod to Go

On <2026-04-21 Tue> I rewritten Textpod from Rust to Go.

While I'm on vacation with the new MacBook Pro M4 Pro and I have installed only bare minimum on it, I don't have Rust toolchain but do have Go. So, asked Claude to do rewrite. The only external dependency is goldmark package to render Markdown as HTMLs.

Updated Elisp helpers:

(use-package textpod
  :config
  (setq textpod-token (cadr (auth-source-user-and-password "finita.myaddr.dev" "textpod"))
        ;;textpod-url "/notes"
        textpod-url "http://localhost:3000/notes"
        ))

(let ((default-directory "~/Developer/src/github.com/velppa/textpod"))
  (async-shell-command "go build -o ~/.local/bin/textpod ." "*textpod-build*"))

(let ((default-directory "~/Developer/src/github.com/velppa/finita-la-comedia/textpod")
      (buffer-name "*textpod-server*"))
  (when (get-buffer buffer-name)
    (kill-buffer buffer-name))
  (async-shell-command (format "textpod --token %s --base-path notes" textpod-token) buffer-name))

(browse-url (format "%s?token=%s" textpod-url textpod-token))

Links <2026-04-16 Thu>

Improve public notes

Next round of improving Textpod on <2026-04-15 Wed>.

Deploy publicly –

Introduced --base-path flag, to serve notes under sub-path.

To start Textpod with sub-path, use:

textpod --token $TEXTPOD_TOKEN --base-path notes
Publish textpod.el to the repository

textpod.el is a companion Emacs package for quick publishing notes from Org Mode documents.

Install it like this:

(use-package textpod
  :config
  (setq textpod-token (cadr (auth-source-user-and-password "finita.myaddr.dev" "textpod")))
  (setq textpod-url "/notes"))

Then these helper functions do the heavy-lifting:

  • textpod-org-region-to-note: Convert the Org region between BEG and END to Markdown and send to Textpod.
  • textpod-org-heading-to-note: Send the current heading’s subtree to Textpod.
  • textpod-open-current-note: Open the current heading’s Textpod note in a browser.

When creating a note, textpod.el sets TEXTPOD_ID property that's equal to node'id.

Change separator in notes.md to ^L

Default separator \n\n---\n\n is fragile as it requires exactly two newlines before / after the note. I often messed up notes when editing notes.md file manually. To fix that, I replaced it with ^L character (insert in Emacs via C-q C-l) which usually represents new pages.

Fix attachments links

Attachement links need to respect --base-path flag.

Handling attachments

My current workflow:

  • Assets are stored next to Notes under assets/ directory

  • I can quickly search for them, then open in Emacs/qView:

    find ../assets -name "*prefix-in-emacs*"
    
    ../assets/m-g-prefix-in-emacs.png
    ../assets/m-s-prefix-in-emacs.png
    
  • I can reference an asset as an image: img

  • An image is a link to an image file that does not have a description part (Images (The Org Manual)).

  • Textpod already support images. What happens when I drag-n-drop an image file to the textarea? Textpod copies them under attachments directory.

What I want:

  • When creating a public note containing an image via API (using textpod.el), copy the asset to assets/ directory, mimicing the directory structure in the notes.
  • I need another API endpoint - POST /notes/assets/<name>, accepting binary as payload.

Okay, I implemented it, let's test.

textpod-org-heading-to-note

Prompt: Implement textpod-org-heading-to-note function, it should select current (top) heading - the one that has TEXTPOD_ID parameter, and call textpod-org-region-to-note on it.

The command {M-x textpod-org-heading-to-note} works!

Should the textpod id be just id?

Currently I use TEXTPOD_ID property. If using ID property, notes will automatically become Org-roam nodes. Currently IDs are generated as timestamps via (setq org-id-method 'ts) with nanoseconds precision. Then it will come with seconds precision. I will need to a prefix, to be able to quickly find them (e.g. with {M-x deadgrep}).

Wrap h3 into details on index page

To declutter the index page, h3 markdown headings (h2 Org headings) are wrapped into <details> tag. The note page renders normally, showing table of contents on the left sidebar.