punchagan: An offline-friendly Elfeed web UI

I want to read articles from my RSS subscriptions on my phone without signing up to a hosted service or running a public server.

Elfeed, the Emacs feed reader, already lets me manage my subscriptions and read from within Emacs. It comes with an experimental web UI, but needs the server (running on my laptop) to be accessible whenever I want to read. My phone becomes useless the moment my laptop sleeps.

So, I built an alternate web UI with a service worker that caches content on the client side for a smooth offline reading experience. It’s written in OCaml and compiled to JavaScript using js_of_ocaml.

Why bother with RSS feeds?

It is annoyingly easy to fall into the trap of short form videos or other algorithmically “curated” feeds and get sucked into scrolling mindlessly. I fear this is going to get even worse with all the generative AI stuff.

I want to be more deliberate about what I consume. RSS feels calmer and gives me a greater sense of control. Or it may just be nostalgia from good old Google Reader days.

Why Emacs and Elfeed?

Since Google Reader was killed, I’ve bounced between many readers but none of them has really stuck with me. I’ve had different phases of using Elfeed, over the years, though.

I like the fact that everything is local with Elfeed – no hosted services, no public servers. And since it lives inside Emacs, everything from tagging to how entries are displayed can be tweaked.

Why Elfeed offline?

Elfeed also ships a basic web UI that lets me read my feeds from a different device, but only when my laptop is online and reachable. I’d like to be able to read these posts even when I’m “on the road”, say, while waiting for a train or while taking a cab ride.

I knew it should be possible to do this with some client side caching. And, I considered improving the web UI of Elfeed itself but it doesn’t seem to be actively maintained in the last couple of years. Creating a separate project would also give me more freedom to experiment.

How does it work?

Elfeed offline comes with a tiny Dream based webserver that acts as a proxy in front of the Emacs Elfeed webserver for the API requests. The Dream web server also serves the static assets like the HTML, JavaScript, stylesheets and the Service Worker for the web app. The diagram below shows how data flows between the different components.

+---------------+     +-----------------+
|  Emacs Elfeed |<--->|     Dream       |
|     Server    |     |   Web-server    |
+---------------+     +-----------------+
                               ^
                               |
                               v
 +------------+        +----------------+       +---------+
 |    Web     |        |    Service     |       | Browser |
 |  Client    |<------>|     Worker     |<----->|  Cache  |
 +------------+        +----------------+       +---------+

The Service Worker intercepts all the GET requests from the client and responds using a cache-first-with-cache-refresh strategy. This makes the reading experience feel very responsive. The web client also implements a “good enough” clone of the original search functionality of Elfeed to allow searching and filtering content while offline.

The web app also lets me mark entries as read or star them when I’m offline. The Service Worker caches these operations and updates the server when the server becomes reachable. There’s no smart conflict resolution here, and the API requests just overwrite and update the state on the server.

I have been using this for a couple of weeks now, and am quite happy with how responsive the UI feels and the number of posts I managed to read so far.

But, there are definitely some rough edges to smoothen out.

  • It’s a pain to make sure that both the servers are running when I’m online and want to sync new updates to my phone. I’m considering adding a tiny Emacs helper to manage this.

  • I need to remember to open the app on my phone to trigger a cache update before stepping away from my laptop. I want to explore using the Background Sync API (not available on Firefox) to possibly make this easier.

  • The UI could use some improvements to indicate pending syncs when offline and also indicate the last sync with the server to help ensure everything is synced before stepping away from my laptop.

  • Service workers need the server to be accessible via https. I have a wrapper script around mkcert to make this easy, but some apps on my phone refuse to work correctly when there are user installed certificates.

Why OCaml?

At work, I write OCaml every day and enjoy it. This project seemed like a good one to try out js_of_ocaml since everything is local and I don’t need to worry about bundle sizes, etc.

js_of_ocaml makes it quite convenient to share code between the server and the client side. And given there’s also a “third” Service Worker component in the mix, I thought it would make life easier. For instance, the message types and the serialization code for the messages between the client and the service worker are shared.

I’m also using this project to experiment with Dune’s “soon to be released”™ package management that our team at Tarides is working on building.

How can you use it?

You can try out Elfeed offline’s UI here as a static site (with some posts from Planet Emacslife and The OCaml Planet). You should be able to read posts, search and filter for posts, mark them as read or star them. Any changes you make should be preserved between reloads.

If you are an Emacs user but don’t use Elfeed, you can checkout the Elfeed’s README for instructions to set it up. It also has links to a bunch of blog posts and videos that show off the features of Elfeed.

If you are already using Elfeed, you should be able to set it up quite easily if you’re happy installing some OCaml tooling. The installations instructions are available in the README.

Outro

I have been enjoying using Elfeed offline for the past couple of weeks. And I’m hoping I’ll end up spending much more time reading in it, than fixing and building it. It currently definitely feels like something I built for myself, but I’d love to hear from others who try it out.

-1:-- An offline-friendly Elfeed web UI (Post punchagan)--L0--C0--2026-01-06T21:01:00.000Z

Jack Baty: Dropping back to Doom Emacs

Weaning myself from Emacs is like a minor hobby for me. Or at least you’d think it was, based on how much time I spend on it. I’ve only ever succeeded once or twice, and only for a short time. There is simply nothing like Emacs and definitely nothing like Org mode. Besides, I have a decade of notes in there. Nearly every note-taking, text-editing problem I’ve ever run into has been solved either by me or someone else in Emacs. I’m comfy there.

And yet, last week I tried leaving Emacs again.

I was sucked in by the promise of Markdown being available and useful just about anywhere, with any modern tool. I was tired of C-x C-something for everything. For example, opening my Emacs bookmarks means C-x r b and for some reason I always have to look at the keyboard while typing it. Yes, yes, I can rebind it if it bugs me, but that’s a can of worms I’ve regretted opening before.

It’s possible that the thing I thought about most has been the native Emacs bindings in nearly every app on macOS and the fact that they’re not everywhere on Linux. Assuming I’ll never be able to go all in with only macOS or Linux, I thought I might try covering more bases by switching to Evil mode in Emacs. True Vim bindings aren’t always available, but most apps and CLI tools, etc can fake it pretty well. Vim can fake it pretty well, too, and I spend a lot of time there.

So I dug out my vanilla evil-mode config and went to town. I hated it immediately. Mine was a half-ass configuration I cobbled together from a bunch of blog/Reddit posts. It was inconsistent and broken in places.

You know what does Evil mode really well? Doom Emacs.

So I spent a few hours this morning (re)installing Doom and migrating the important bits of my vanilla config over.

Doom tries to do too much, but it does a pretty good job of it. I’m modal-editing my way around everything and it’s like coming home. I’m sure I’ll start stubbing my toes on things at some point, but for now, the problems I traded for it are worth it.

-1:-- Dropping back to Doom Emacs (Post Jack Baty)--L0--C0--2026-01-06T18:04:52.000Z

Alvaro Ramirez: Bending Emacs - Episode 9: World times

A new year, a new Bending Emacs episode, so here it goes:

Bending Emacs Episode 9: Time around the world

Emacs comes with a built-in world clock: M-x world-clock

To customize displayed timezones, use:

(setq world-clock-list '(("America/New_York" "New York")
                         ("America/Caracas" "Caracas")
                         ("Europe/London" "London")
                         ("Asia/Tokyo" "Tokyo")))

Each entry requires a valid timezone string (as per entries in your system's /usr/share/zoneinfo) and a display label.

I wanted a slightly different experience than the built-in command (more details here), so I built the time-zones package.

time-zones is available on MELPA, so you can install with:

(use-package time-zones :ensure t)

Toggle help with the "?" key add cities with the "+" key. Shifting time is possible via the "f" / "b" keys, in addition to a other features available via the "?" help menu.

Hope you enjoyed the video!

Want more videos?

Liked the video? Please let me know. Got feedback? Leave me some comments.

Please go like my video, share with others, and subscribe to my channel.

If there's enough interest, I'll continue making more videos!

Make it all sustainable

Enjoying this content or my projects? I am an indie dev. Help make it sustainable by ✨sponsoring

Need a blog? I can help with that. Maybe buy my iOS apps too ;)

-1:-- Bending Emacs - Episode 9: World times (Post Alvaro Ramirez)--L0--C0--2026-01-06T00:00:00.000Z

Sacha Chua: 2026-01-05 Emacs news

Looking for something to write about? Christian Tietze is hosting the January Emacs Carnival on the theme "This Year, I'll…". Check out last month's carnival on The People of Emacs for other entries.

Links from reddit.com/r/emacs, r/orgmode, r/spacemacs, Mastodon #emacs, Bluesky #emacs, Hacker News, lobste.rs, programming.dev, lemmy.world, lemmy.ml, planet.emacslife.com, YouTube, the Emacs NEWS file, Emacs Calendar, and emacs-devel. Thanks to Andrés Ramírez for emacs-devel links. Do you have an Emacs-related link or announcement? Please e-mail me at sacha@sachachua.com. Thank you!

View org source for this post

You can comment on Mastodon or e-mail me at sacha@sachachua.com.

-1:-- 2026-01-05 Emacs news (Post Sacha Chua)--L0--C0--2026-01-05T20:21:03.000Z

Marcin Borkowski: Magit and new branch length

Like many Emacsers, I am a heavy Magit user. If you use Magit, I don’t need to tell you how great it is; if you don’t, I suggest you do yourself a favor and try it out. That doesn’t mean that Magit is ideal, though. It has some issues, though usually very minor ones. Today I’d like to write about something which is definitely not a “Magit issue”, but rather something I personally miss in it.
-1:-- Magit and new branch length (Post Marcin Borkowski)--L0--C0--2026-01-05T18:32:14.000Z

Irreal: A Paean To Dumb Jump

I’m a long term user of Dumb Jump. According to this Irreal post, I’ve been a devoted fan since 2017. As I’ve said many times in my several posts about Dumb Jump—search for “dumb jump” on Irreal if you’re interested—I’ve never been able to warm up to TAGS systems because they require so much maintenance and LSP systems have always seemed like too much work for what I want them for.

It turns out that I’m not the only fan. Ruslan Bekenev has a lovely paean to Dumb Jump. He, like me, says it has completely eliminated his need for TAGS or LSP. For all the things that I use it for it’s instantaneous. That can be difficult to believe when you learn how it works but it’s true. You can get the details from Bekenev’s post or the Dumb Jump site but the TL;DR is that it uses grep to search for the desired target. I use ripgrep with it so it’s fast with even big repositories. Bekenev has an animated GIF showing how it works for him.

If you haven’t tried Dumb Jump, I join with Bekenev in urging you to try it out. It’s small, doesn’t require a complicated installation and is easy to configure. If you don’t like it, you can simply delete the package and remove it from your init.el.

-1:-- A Paean To Dumb Jump (Post Irreal)--L0--C0--2026-01-05T15:29:22.000Z

Christian Tietze: Emacs Carnival 2026-01: “This Year, I’ll ...”

Welcome to 2026, and a new year of Emacs Blog Carnival post!

To start the Gregorian calendar year fresh, the blogging/writing/thinking prompt of this month is:

This year, I’ll …

What will you do differently in Emacs this year? Or will you just get started? Why?

What are you excited about to explore and tinker with? What do you want to perfect?

Are there any large projects in fron of you that you’ll emacs1 with confidence?

What’s a Blog Carnival, Again?

A blog carnival is a fun way to tie together a community with shared writing prompts, and marvel at all the creative interpretations of the topic of the month. I’ve provided a couple of interpretations above, but you may think of something else entirely. That’s amazing, roll with it, that’s what makes this fun!

For future Carnivals, check out the “Carnival” page on EmacsWiki . It includes instructions, and is our community space to coordinate participants and topic ideas.

Submissions

Comment below or DM/email me with your submission! I’ll collect submissions up to, and including, February 1st (Central European Time), so that every time zone has had a chance to meet the January 31st deadline.

  1. I’m trying to establish a verb here. 


Hire me for freelance macOS/iOS work and consulting.

Buy my apps.

Receive new posts via email.

-1:-- Emacs Carnival 2026-01: “This Year, I’ll ...” (Post Christian Tietze)--L0--C0--2026-01-05T05:05:41.000Z

Protesilaos Stavrou: Emacs: Substitute version 0.5.0

Substitute provides a set of commands that perform text replacement (i) throughout the buffer, (ii) limited to the current definition (per narrow-to-defun), (iii) from point to the end of the buffer, and (iv) from point to the beginning of the buffer. Variations of these scopes are also available.

These substitutions are meant to be as quick as possible and, as such, differ from the standard query-replace (which I still use when necessary). The provided commands prompt for substitute text and perform the substitution outright, without moving the point. The target is the symbol/word at point or the text corresponding to the currently marked region. All matches in the given scope are highlighted by default.

Below are the release notes.


Version 0.5.0 on 2026-01-05

This is a small release that fixes a bug and adds a relevant user option.

The bug pertained to the scope of the substitution when buffer narrowing was in effect. All commands would ignore narrowing and also fail to properly clear the highlights they apply (highlights are transiently in effect to show what the target of the substitution is while the minibuffer is waiting for user input).

Now all commands do the right thing with respect to buffer narrowing. Though their exact behaviour depends on the value of the new user option substitute-ignore-narrowing:

  • When the value of substitute-ignore-narrowing is non-nil, then substitutions apply to the actual scope of the given command. For example, substitute-target-in-buffer will cover the whole buffer from the absolute minimum position to the absolute maximum position even if narrowing is in effect.

  • When the value of substitute-ignore-narrowing is nil, then substitutions apply to their scope subject to the boundaries of the narrowed buffer. For example, substitute-target-in-buffer will understand as “whole buffer” the region between the minimum and maximum positions of the narrowed buffer.

Users can write small convenience commands that do either of those, depending on preference. For example:

(defun my-substitute-target-in-buffer-with-narrowing ()
  "Call `substitute-target-in-buffer' with `substitute-ignore-narrowing' as `nil'."
  (let ((substitute-ignore-narrowing nil))
    (call-interactively 'substitute-target-in-buffer)))

(defun my-substitute-target-in-buffer-without-narrowing ()
  "Call `substitute-target-in-buffer' with `substitute-ignore-narrowing' as non-`nil'."
  (let ((substitute-ignore-narrowing t))
    (call-interactively 'substitute-target-in-buffer)))

Thanks to zauberen for reporting the bug in issue 11: https://github.com/protesilaos/substitute/issues/11.

-1:-- Emacs: Substitute version 0.5.0 (Post Protesilaos Stavrou)--L0--C0--2026-01-05T00:00:00.000Z

Sacha Chua: Using whisper.el to convert speech to text and save it to the currently clocked task in Org Mode or elsewhere

: Added note about difference from MELPA package, fixed :vc

I want to get my thoughts into the computer quickly, and talking might be a good way to do some of that. OpenAI Whisper is reasonably good at recognizing my speech now and whisper.el gives me a convenient way to call whisper.cpp from Emacs with a single keybinding. (Note: This is not the same whisper package as the one on MELPA.) Here is how I have it set up for reasonable performance on my Lenovo P52 with just the CPU, no GPU.

I've bound <f9> to the command whisper-run. I press <f9> to start recording, talk, and then press <f9> to stop recording. By default, it inserts the text into the buffer at the current point. I've set whisper-return-cursor-to-start to nil so that I can keep going.

(use-package whisper
  :vc (:url "https://github.com/natrys/whisper.el")
  :load-path "~/vendor/whisper.el"
  :config
  (setq whisper-quantize "q4_0")
  (setq whisper-install-directory "~/vendor")
  ;; Get it running with whisper-server-mode set to nil first before you switch to 'local.
  ;; If you change models,
  ;; (whisper-install-whispercpp (whisper--check-install-and-run nil "whisper-start"))
  (setq whisper-server-mode 'local)
  (setq whisper-model "base")
  (setq whisper-return-cursor-to-start nil)
  (setq whisper--ffmpeg-input-device "alsa_input.usb-Blue_Microphones_Yeti_Stereo_Microphone_REV8-00.analog-stereo")
  ;(setq whisper--ffmpeg-input-device "VirtualMicSink.monitor")
  (setq whisper--ffmpeg-input-device "audiorelay-virtual-mic-sink:monitor_FL")
  (setq whisper-language "en")
  (setq whisper-before-transcription-hook nil)
  (setq whisper-use-threads (1- (num-processors)))
  (setq whisper-transcription-buffer-name-function 'whisper--simple-transcription-buffer-name)
  (add-hook 'whisper-after-transcription-hook 'my-subed-fix-common-errors-from-start)
  :bind
  (("<f9>" . whisper-run)
   ("C-<f9>" . my-whisper-org-capture-to-clock)
   ("S-<f9>" . my-whisper-replay)
   ("M-<f9>" . my-whisper-toggle-language)))

The technology isn't quite there yet to do real-time audio transcription so that I can see what it understands while I'm saying things, but that might be distracting anyway. If I do it in short segments, it might still be okay. I can replay the most recently recorded snippet in case it's missed something and I've forgotten what I just said.

(defun my-whisper-replay ()
  "Replay the last temporary recording."
  (interactive)
  (mpv-play whisper--temp-file))

Il peut aussi comprendre le français.

(defun my-whisper-toggle-language ()
  "Set the language explicitly, since sometimes auto doesn't figure out the right one."
  (interactive)
  (setq whisper-language (if (string= whisper-language "en") "fr" "en"))
  ;; If using a server, we need to restart for the language
  (when (process-live-p whisper--server-process) (kill-process whisper--server-process))
  (message "%s" whisper-language))

I could use this with org-capture, but that's a lot of keystrokes. My shortcut for org-capture is C-c r. I need to press at least one key to set the template, <f9> to start recording, <f9> to stop recording, and C-c C-c to save it. I want to be able to capture notes to my currently clocked in task without having an Org capture buffer interrupt my display.

To clock in, I can use C-c C-x i or my ! speed command. Bonus: the modeline displays the current task to keep me on track, and I can use org-clock-goto (which I've bound to C-c j) to jump to it.

Then, when I'm looking at something else and I want to record a note, I can press <f9> to start the recording, and then C-<f9> to save it to my currently clocked task along with a link to whatever I'm looking at.

(defvar my-whisper-org-target nil
  "*Where to save the target.

Nil means jump to the current clocked-in entry and insert it along with
a link, or prompt for a capture template if nothing is clocked in.

If this is set to a string, it should specify a key from
`org-capture-templates'. The text will be in %i, and you can use %a for the link.
For example, you could have a template entry like this:
\(\"c\" \"Contents to current clocked task\" plain (clock) \"%i%?\n%a\" :empty-lines 1)

If this is set to a function, the function will be called from the
original marker with the text as the argument. Note that the window
configuration and message will not be preserved after this function is
run, so if you want to change the window configuration or display a
message, add a timer.")

(defun my-whisper-org-capture-to-clock ()
  (interactive)
  (require 'whisper)
  (add-hook 'whisper-after-transcription-hook #'my-whisper-org-save 50)
  (whisper-run))

(defun my-whisper-org-save ()
  "Save the transcription."
  (let ((text (string-trim (buffer-string))))
    (remove-hook 'whisper-after-transcription-hook #'my-whisper-org-save)
    (erase-buffer)      ; stops further processing
    (save-window-excursion
      (with-current-buffer (marker-buffer whisper--marker)
        (goto-char whisper--marker)
        (cond
         ((functionp my-whisper-org-target)
          (funcall my-whisper-org-target text))
         (my-whisper-org-target
          (setq org-capture-initial text)
          (org-capture nil my-whisper-org-target)
          (org-capture-finalize)
          ;; Delay the display of the message because whisper--cleanup-transcription clears it
          (run-at-time 0.5 nil (lambda (text) (message "Captured: %s" text)) text))
         ((org-clocking-p)
          (let ((link (org-store-link nil)))
            (org-clock-goto)
            (org-end-of-subtree)
            (unless (bolp)
              (insert "\n"))
            (insert "\n" text "\n" link "\n"))
          (run-at-time 0.5 nil (lambda (text) (message "Added clock note: %s" text)) text))
         (t
          (kill-new text)
          (setq org-capture-initial text)
          (call-interactively 'org-capture)
          ;; Delay the window configuration
          (let ((config (current-window-configuration)))
            (run-at-time 0.5 nil
                         (lambda (text config)
                           (set-window-configuration config)
                           (message "Copied: %s" text))
                         text config))))))))

Here's an idea for a my-whisper-org-target function that saves the recognized text with a timestamp.

(defvar my-whisper-notes "~/sync/stream/narration.org")
(defun my-whisper-save-to-file (text)
  (let ((link (org-store-link nil)))
    (with-current-buffer (find-file-noselect my-whisper-notes)
      (goto-char (point-max))
      (insert "\n\n" (format-time-string "%H:%M ") text "\n" link "\n")
      (save-buffer)
      (run-at-time 0.5 nil (lambda (text) (message "Saved to file: %s" text)) text))))

I think I've just figured out my Pipewire setup so that I can record audio in OBS while also being able to do speech to text, without the audio stuttering. qpwgraph was super helpful for visualizing the Pipewire connections and fixing them. Actually making a demonstration video will probably need to wait for another day, though!

This is part of my Emacs configuration.
View org source for this post

You can comment on Mastodon or e-mail me at sacha@sachachua.com.

-1:-- Using whisper.el to convert speech to text and save it to the currently clocked task in Org Mode or elsewhere (Post Sacha Chua)--L0--C0--2026-01-04T01:23:57.000Z

Protesilaos Stavrou: Emacs: ef-orange and ef-fig are part of the ef-themes

I just added two new themes to my ef-themes package. ef-orange is a light theme with a noticeably tinted background that combines orange, yellow, and green hues. ef-fig is a dark theme with a dark purple background that relies on green, yellow, and pink hues.

Sample pictures are available below. Check all the items here: https://protesilaos.com/emacs/ef-themes-pictures. Always click to enlarge the image for best results.

ef-orange

ef-orange theme sample

ef-orange theme git sample

ef-orange theme mail sample

ef-orange theme org sample

ef-fig

ef-fig theme sample

ef-fig theme git sample

ef-fig theme mail sample

ef-fig theme org sample

Coming in version 2.1.0 (next stable release)

Both themes are done. Though there may still be some minor refinements. They will be widely available as part of the next stable version of the ef-themes package (maybe end of January 2026).

Remember that since version 2.0.0, the ef-themes are built on top of my modus-themes. This means that they are highly customisable and support a wide range of packages and face groups.

Enjoy!


The ef-themes are a collection of light and dark themes for GNU Emacs that provide colourful (“pretty”) yet legible options for users who want something with a bit more flair than the modus-themes (also designed by me).

-1:-- Emacs: ef-orange and ef-fig are part of the ef-themes (Post Protesilaos Stavrou)--L0--C0--2026-01-04T00:00:00.000Z

Chris Maiorana: An Org Mode Deep Work Agenda for 2026

It’s a new year, and that means getting yourself organized with Org Mode. Now, as I’ve covered in the past, I have a love/hate relationship with the Org Mode agenda. But it’s mostly my fault. Just because I have not quite found the right approach to making use of the tool does not mean there’s anything inherently wrong with the tool.

So I’ve been experimenting with using the agenda in different ways—different strategies and approaches, you might say. This has been helpful not only to see what I would like but also what might be of use to others breaking into Org Mode for the first time or curious about its capabilities.

Also, I’m happy to announce that my Git For Writers handbook is finally available. This was my 2025 end-of-year project, and I’m excited to share it with you all. It’s a perfect companion to the Emacs For Writers handbook. So check it out. Thanks!

Also, be sure to check out the video that goes along with this post for a visual demonstration and commentary. Below, you will find the code samples referenced in that video.

Deep Work agenda views

  • C-c a d : View all deep work tasks
  • C-c a s : View all shallow work tasks
  • C-c a w : View complete work type overview (both deep and shallow)
  • C-c a m : View morning-optimized deep work tasks
  • C-c a a : View afternoon work tasks

Capture Templates

  • C-c c d : Capture a deep work task (2:00 effort, intense focus)
  • C-c c s : Capture a shallow work task (0:30 effort, admin work)
  • C-c c q : Capture a quick shallow item (0:15 effort)
  • C-c c r : Capture research/reading task (1:30 effort, deep work)

Agenda views

;; Custom agenda commands for deep work strategy
(setq org-agenda-custom-commands
      '(("d" "Deep Work Queue"
         tags-todo "WORK_TYPE=\"deep\""
         ((org-agenda-overriding-header "🧠 Deep Work Sessions Available")
          (org-agenda-prefix-format " %i %-12:c [%e] ")
          (org-agenda-sorting-strategy '(priority-down effort-down))))

        ("s" "Shallow Work Queue"
         tags-todo "WORK_TYPE=\"shallow\""
         ((org-agenda-overriding-header "📋 Shallow Work Tasks")
          (org-agenda-prefix-format " %i %-12:c [%e] ")
          (org-agenda-sorting-strategy '(priority-down effort-up))))

        ("w" "Work Type Overview"
         ((tags-todo "WORK_TYPE=\"deep\""
                     ((org-agenda-overriding-header "🧠 Deep Work (Uninterrupted Focus)")
                      (org-agenda-prefix-format " %i %-12:c [%e] ")))
          (tags-todo "WORK_TYPE=\"shallow\""
                     ((org-agenda-overriding-header "📋 Shallow Work (Administrative)")
                      (org-agenda-prefix-format " %i %-12:c [%e] "))))
         ((org-agenda-sorting-strategy '(priority-down effort-down))))

        ("m" "Morning Deep Work (High Energy)"
         tags-todo "WORK_TYPE=\"deep\"+BEST_TIME=\"morning\""
         ((org-agenda-overriding-header "🌅 Morning Deep Work Sessions")
          (org-agenda-prefix-format " %i %-12:c [%e] ")))

        ("a" "Afternoon Work"
         tags-todo "BEST_TIME=\"afternoon\""
         ((org-agenda-overriding-header "☀️ Afternoon Slump")
          (org-agenda-prefix-format " %i %-12:c [%e] ")
          (org-agenda-sorting-strategy '(priority-down effort-up))))))

Capture templates

;; Capture templates for deep work task management
(setq org-capture-templates
      '(("d" "Deep Work Task"
         entry
         (file+headline "/home/shared/og-deep-work26/deep-work26.org" "Tasks")
         "* TODO %?\n:PROPERTIES:\n:WORK_TYPE: deep\n:EFFORT: 2:00\n:BEST_TIME: morning\n:END:\n%U\n"
         :empty-lines 1)

        ("s" "Shallow Work Task"
         entry
         (file+headline "/home/shared/og-deep-work26/deep-work26.org" "Tasks")
         "* TODO %?\n:PROPERTIES:\n:WORK_TYPE: shallow\n:EFFORT: 0:30\n:BEST_TIME: afternoon\n:END:\n%U\n"
         :empty-lines 1)

        ("q" "Quick Shallow Item"
         entry
         (file+headline "/home/shared/og-deep-work26/deep-work26.org" "Tasks")
         "* TODO %?\n:PROPERTIES:\n:WORK_TYPE: shallow\n:EFFORT: 0:15\n:BEST_TIME: anytime\n:END:\n"
         :empty-lines 1)
        ("r" "Research/Reading (Deep)"
         entry
         (file+headline "/home/shared/og-deep-work26/deep-work26.org" "Tasks")
         "* TODO %?\n:PROPERTIES:\n:WORK_TYPE: deep\n:EFFORT: 1:30\n:BEST_TIME: morning\n:CATEGORY: research\n:END:\n%U\n"
         :empty-lines 1)))

The post An Org Mode Deep Work Agenda for 2026 appeared first on Chris Maiorana.

-1:-- An Org Mode Deep Work Agenda for 2026 (Post Chris Maiorana)--L0--C0--2026-01-03T18:25:57.000Z

Irreal: Fixing My Feed From Sacha

I’ve been following Sacha Chua for a long time. I think I started while she was still at University but it was certainly shortly afterwards at the latest. She’s been at it for about 25 years so it’s hard for me to remember. During that time I’ve had her in my RSS/Atom feed and read her posts in various feed readers.

These days, of course, I’m using Elfeed but she still pops up regularly in my feed. The other day, I was reading her post on Emacs People for the latest Emacs Carnival and I noticed that she wrote in both French and English. That seemed new so I poked around a bit a discovered that it’s another of her ongoing projects, just like my learning Spanish1. The thing is, I had never seen any other posts in or about French from her. Then I realized that all the posts I’ve been seeing from her were actually coming from Planet Emacslife. What was going on? I checked and, sure enough, her blog was in my feed list. After fiddling around for a bit I tried following the link I had for her in my feed and got a 404 but I had no problem finding the feed directly from her site. I checked it against what her site had and it seemed the same. Then I noticed that on the 404 page there was a %20 at the end of the URL. Aha! There was a space at the end of the URL I was using. I wouldn’t think that would matter but when I removed it, things started working again.

So the moral of this long story is that if you’re having trouble getting the RSS/Atom entries from some site, check to be sure you don’t have a space at the end of the URL. It’s easy to see how it could happen even if you know what you’re doing, and once made, it’s hard to see with a casual glance at things.

Anyway, I’m back to getting all Sacha’s posts now. Too bad that I don’t remember a lot of the French I learned as a result of one of those annoying foreign language requirements in graduate school.

Footnotes:

1

Except that I’m not brave enough to try writing a post in Spanish.

-1:-- Fixing My Feed From Sacha (Post Irreal)--L0--C0--2026-01-03T15:26:19.000Z

Ruslan Bekenev: Emacs: Dumb Jump Is So Good

There is a library I rely on constantly in my day to day computing, at work and at home. This library is called dumb-jump.

I don’t see it is prized as often as Magit for example, which made me write this post.

Below I explain what it does but first, let me thank to Jack Angers and all the contributors. I have used this library for 10 years. It is one of the best computer programs I have ever used. There were periods when I was off Emacs during that time and the first thing I looked for in other editors was a plugin similar to dumb-jump.

What does it do

It jumps to definition of a function/method/class.

Here is my config for it:

(use-package dumb-jump
 :ensure t
 :config
 (add-hook 'xref-backend-functions #'dumb-jump-xref-activate)
 (setq dumb-jump-force-searcher 'rg)
 ;; use completion-read instead of a separate buffer with candidates
 (setq xref-show-definitions-function #'xref-show-definitions-completing-read))

Combined with rg it eliminated my need in LSPs or ctags/etags entirely.

Here is me using it on Emacs source code, which is big code base (click to view the video in a separate tab):

In this video I:

  • press M-. to go to definition.
  • In less than a second I get matching definitions that I can filter if necessary.
  • I jump straight to the definition.

How does it work

It just greps the entire project for the symbol at point. The best thing about it is that with LSP for example it’s impossible (or rather I haven’t seen it working) to place my cursor over a function name in a comment somewhere and jump to it.

Like in:

/**
 * This test function is cool but
 * check another_function_name for more info
 */
 function test() {}

With dumb-jump - I can place my cursor at another_function_name, press M-. and I’m looking at the definition of it.

It works with ANY programming language.

Why not

ctags/etags

Every time a file is changed the ctags table must be regenerated. It can be automated but I think it’s unnecessary overhead.

LSP (Eglot)

I love LSP as a technology but I don’t want a background process to run on my machine while I’m working on a project. What if during the day I work on 10 projects, which is real for me at work for example. It will spawn 10 background processes for different languages that I have to mentally track (because I do mentally track what’s running in my computing environment).

The amount of RAM and CPU spent on this just feels too expensive.

Summary

If for some reason you haven’t tried this library - try. It’s so good.

I wish I can find better words on how grateful I am that it exists. Thank you!

-1:-- Emacs: Dumb Jump Is So Good (Post Ruslan Bekenev)--L0--C0--2026-01-03T10:22:17.000Z

Sacha Chua: EmacsConf 2025 notes

Intended audience: This is a long post (~ 8,400 words). It's mostly for me, but I hope it might also be interesting if you like to use Emacs to do complicated things or if you also happen to organize online conferences.

: Added a note about streaming from virtual desktops.

The videos have been uploaded and thank-you notes have been sent, so now I get to write quick notes on EmacsConf 2025 and reuse some of my code from last year's post.

Stats

While organizing EmacsConf 2025, I thought it was going to be a smaller conference compared to last year because of lots of last-minute cancellations. Now that I can finally add things up, I see how it all worked out:

2024 2025 Type
31 25 Presentations
10.7 11.3 Presentation duration (hours)
21 11 Q&A web conferences
7.8 5.2 Q&A duration (hours)
18.5 16.5 Total

EmacsConf 2025 was actually a little longer than 2024 in total presentation time, although that's probably because we had more live talks which included answering questions. It was almost as long overall including live Q&A in BigBlueButton rooms, but we did end an hour or so earlier each day.

Looking at the livestreaming data, I see that we had fewer participants compared to the previous year. Here are the stats from Icecast, the program we use for streaming:

  • Saturday:
    • gen: 107 peak + 7 lowres (compared to 191 in 2024)
    • dev: 97 peak + 7 lowres (compared to 305 in 2024)
  • Sunday: I forgot to copy Sunday stats, whoops! I think there were about 70 people on the general stream. Idea: Automate this next time.

The YouTube livestream also had fewer participants at the time of the stream, but that's okay. Here are the stats from YouTube:

2024 peak 2025 peak YouTube livestream
46 23 Gen Sat AM
24 7 Gen Sat PM
15 8 Dev Sat AM
20 14 Dev Sat PM
28 14 Gen Sun AM
26 11 Gen Sun PM

Fewer people attended compared to last year, but it's still an amazing get-together from the perspective of being able to get a bunch of Emacs geeks in a (virtual) room. People asked a lot of questions over IRC and the Etherpads, and the speakers shared lots of extra details that we captured in the Q&A sessions. I'm so glad people were able to connect with each other.

Based on the e-mails I got from speakers about their presentations and the regrets from people who couldn't make it to EmacsConf, it seemed that people were a lot busier in 2025 compared to 2024. There were also a lot more stressors in people's lives. But it was still a good get-together, and it'll continue to be useful going forward.

At the moment, the EmacsConf 2025 videos have about 20k views total on YouTube. (media.emacsconf.org doesn't do any tracking.) Here are the most popular ones so far:

While I was looking at the viewing stats on YouTube, I noticed that people are still looking at videos all the way back to 2013 (like Emacs Live - Sam Aaron and Emacs Lisp Development - John Wiegley), and many videos have thousands of views. Here are some of the most popular ones from past conferences:

Views aren't everything, of course, but maybe they let us imagine a little about how many people these speakers might have been able to reach. How wonderful it is that people can spend a few days putting together their insights and then have that resonate with other people through time. Speakers are giving us long-term gifts.

Timeline

Of course, the process of preparing all of that started long before the days of the conference. We posted the call for proposals towards the end of June, like we usually do, because we wanted to give people plenty of time to start thinking about their presentations. We did early acceptances again this year, and we basically accepted everything, so people could start working on their videos almost right away. Here's the general timeline:

CFP 2025-06-27 Fri
CFP deadline 2025-09-19 Fri
Speaker notifications 2025-09-26 Fri
Publish schedule 2025-10-24 Fri (oops, forgot to do this elsewhere)
Video submission target date 2025-10-31 Fri
EmacsConf 2025-12-06 Sat - 2025-12-07 Sun
Live talks, Q&A videos posted 2025-12-17
Q&A notes posted 2025-12-28
These notes 2026-01-01
submissions_plot.png
Figure 1: EmacsConf 2025: cumulative proposals and video uploads

This graph shows that we got more proposals earlier this year (solid blue line: 2025) compared to last year (gray: 2024), although there were fewer last-minute ones and more cancellations this year. Some people were very organized. (Video submissions in August!) Some people sent theirs in later because they first had to figure out all the details of what they proposed, which is totally relatable for anyone who's found themselves shaving Emacs yaks before.

I really appreciated the code that I wrote to create SVG previews of the schedule. That made it much easier to see how the schedule changed as I added or removed talks. I started stressing out about the gap between the proposals and the uploaded videos (orange) in November. Compared to last year, the submissions slowly trickled in. The size of the gap between the blue line (cumulative proposals) and the orange line (cumulative videos uploaded) was much like my stress level, because I was wondering how I'd rearrange things if most of the talks had to cancel at the last minute or if we were dealing with a mostly-live schedule. Balancing EmacsConf anxiety with family challenges resulted in all sorts of oopses in my personal life. (I even accidentally shrank my daughter's favourite T-shirt.) It helped to remind myself that we started off as a single-track single-day conference with mostly live sessions, that it's totally all right to go back to that, and that we have a wonderful (and very patient) community. I think speakers were getting pretty stressed too. I reassured speakers that Oct 31 was a soft target date, not a hard deadline. I didn't want to cancel talks just because life got busy for people.

It worked out reasonably well. We had enough capacity to process and caption the videos as they came in, even the last-minute uploads. Many speakers did their own captions. We ended up having five live talks. Live talks are generally a bit more stressful for me because I worry about technical issues or other things getting in the way, but all those talks went well, thank goodness.

For some reason, Linode doesn't seem to show me detailed accrued charges any more. I think it used to show them before, which is why I managed to write last year's notes in December. If I skip the budget section of these notes, maybe I can post them earlier, and then just follow up once the invoices are out.

I think the timeline worked out mostly all right this year. I don't think moving the target date for videos earlier would have made much of a difference, since speakers would probably still be influenced by the actual date of the conference. It's hard to stave off that feeling of pre-conference panic: Do we have enough speakers? Do we have enough videos? But these graphs might help me remember that it's been like that before and it has still worked out, so it might just be part of my job as an organizer to embrace the uncertainty. Most conferences do live talks, anyway.

Emacs Lisp code for making the table

(append '(("slug" "submitted" "uploaded" "cancelled") hline)
        (sort (delq nil
                    (org-map-entries
                     (lambda ()
                       (list
                        (org-entry-get (point) "SLUG")
                        (org-entry-get (point) "DATE_SUBMITTED")
                        (org-entry-get (point) "DATE_UPLOADED")
                        (org-entry-get (point) "DATE_CANCELLED")))
                     "DATE_SUBMITTED={.}"))
              :key (lambda (o) (format "%s - %s" (elt o 3) (elt o 2)))
              :lessp #'string<) nil)

Python code for plotting the graph
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import io

all_dates = pd.date_range(start='2025-06-01', end='2025-12-09', freq='D')
def convert(rows):
    df = pd.DataFrame(rows, columns=['slug', 'proposal_date', 'upload_date', 'cancelled_date'])
    df = df.map(lambda x: pd.NA if (x == [] or x == "nil") else x)
    df['proposal_date'] = pd.to_datetime(df['proposal_date']).apply(lambda d: d.replace(year=2025))
    df['upload_date'] = pd.to_datetime(df['upload_date']).apply(lambda d: d.replace(year=2025))
    df['cancelled_date'] = pd.to_datetime(df['cancelled_date']).apply(lambda d: d.replace(year=2025))
    prop_counts = df.groupby('proposal_date').size().reindex(all_dates, fill_value=0).cumsum()
    additions = df[df['proposal_date'].notna()].groupby('proposal_date').size().reindex(all_dates, fill_value=0)
    subtractions = df[df['cancelled_date'].notna()].groupby('cancelled_date').size().reindex(all_dates, fill_value=0)
    upload_counts = df[df['upload_date'].notna()].groupby('upload_date').size().reindex(all_dates, fill_value=0).cumsum()
    prop_counts = (additions - subtractions).cumsum()
    return prop_counts, upload_counts

prop_counts, upload_counts = convert(current_data)
prop_counts_2024, upload_counts_2024 = convert(previous_data)

plot_df = pd.DataFrame({
    'Date': all_dates,
    '2025 Proposals': prop_counts.values,
    '2025 Videos': upload_counts.values,
})
plot_df_melted = plot_df.melt(id_vars='Date', var_name='Type', value_name='Count')
sns.set_theme(style="whitegrid")
plt.figure(figsize=(12, 6))
plt.plot(all_dates, prop_counts_2024, color='gray', label='2024 cumulative proposals')
plt.plot(all_dates, upload_counts_2024, color='gray', alpha=0.5, label='2024 cumulative uploads')
ax = sns.lineplot(data=plot_df_melted, x='Date', y='Count', hue='Type', linewidth=2.5)
ax.xaxis.set_major_locator(mdates.MonthLocator())
ax.xaxis.set_major_formatter(mdates.DateFormatter('%b'))
plt.title('EmacsConf 2025: cumulative proposals and video uploads', fontsize=16)
plt.xlabel('Date', fontsize=12)
plt.ylabel('Cumulative Count', fontsize=12)
plt.tight_layout()
plt.savefig('submissions_plot.png')

Managing conference information in Org Mode and Emacs

Organizing the conference meant keeping track of lots of e-mails and lots of tasks over a long period of time. To handle all of the moving parts, I relied on Org Mode in Emacs to manage all the conference-related information. This year, I switched to mainly using the general organizers notebook, with the year-specific one mostly just used for the draft schedule. I added some shortcuts to jump to headings in the main notebook or in the annual notebook (emacsconf-main-org-notebook-heading and emacsconf-current-org-notebook-heading emacsconf.el).

As usual, we used lots of structured entry properties to store all the talk information. Most of my functions from last year worked out fine, aside from the occasional odd bug when I forgot what property I was supposed to use. (:ROOM:, not :BBB:…)

The validation functions I thought about last year would have been nice to have, since I ran into a minor case-sensitivity issue with the Q&A value for one of the talks (live versus Live). This happened last year too and it should have been caught by the case-fold-search I thought I added to my configuration, but since that didn't seem to kick in, I should probably also deal with it on the input side. Idea: I want to validate that certain fields like QA_TYPE have only a specified set of values. I also probably need a validation function to make sure that newly-added talks have all the files that the streaming setup expects, like an overlay for OBS, and one that checks if I've set any properties outside the expected list.

Communication

After some revision, the call for participation went out on emacs-tangents, Emacs News, emacsconf-discuss, emacsconf-org, Reddit, lobste.rs, and System Crafters. (Thanks!) People also mentioned it in various meetups, and there were other threads leading up to it (Reddit, HN, lemmy.world, @fsf)

Once the speakers confirmed the tentative schedules worked for them, I published the schedule on emacsconf.org on Oct 24, as sceduled. But I forgot to announce the schedule on emacsconf-discuss and other places, though. I probably felt a little uncertain about announcing the schedule because it was in such flux. There was even a point where we almost cancelled the whole thing. I also got too busy to reach out to podcasts. I remembered to post it to foss.events, though! Idea: I can announce things and trust that it'll all settle down. I can spend some time neatening up our general organizers-notebook so that there's more of a step-by-step-process. Maybe org-clone-subtree-with-time-shift will be handy.

There were also a few posts and threads afterwards:

For communicating with speakers and volunteers, I reused the mail merge from previous years, with a few more tweaks. I added a template for thanking volunteers. I added some more code for mailing all the volunteers with a specific tag. Some speakers were very used to the process and did everything pretty independently. Other speakers needed a bit more facilitation.

I could get better at coordinating with people who want to help. In the early days, there wasn't that much to help with. A few people volunteered to help with captions for specific videos, so I waited for their edits and focused on other tasks first. I didn't want to pressure them with deadlines or preempt them by working on those when I'd gotten through my other tasks, but it was hard to leave those tasks dangling. They ended up sending in the edits shortly before the conference, which still gave me enough time to coordinate with speakers to review the captions. It was hard to wait, but I appreciate the work they contributed. In late November and early December, there were so many tasks to juggle, but I probably could have e-mailed people once a week with a summary of the things that could be done. Idea: I can practice letting people know what I'm working on and what tasks remain, and I can find a way to ask for help that fits us. If I clean up the design of the backstage area, that might make it easier for people to get started. If I improve my code for comparing captions, I can continue editing subtitles as a backup if I want to take a break from my other work, and merge in other people's contributions when they send them.

I set up Mumble for backstage coordination with other organizers, but we didn't use it much because most of the time, we were on air with someone, so we didn't want to interrupt each other.

There was a question about our guidelines for conduct and someone's out-of-conference postings. I forwarded the matter to our private mailing list so that other volunteers could think about it, because at that point in time, my brain was fried. That seemed to have been resolved. Too bad there's no edebug-on-entry for people so we can figure things out with more clarity. I'm glad other people are around to help navigate situations!

Schedule

Like last year, we had two days of talks, with two tracks on the first day, and about 15-20 minutes between each talk. We had a couple of last-minute switches to live Q&A sessions, which was totally fine. That's why I set up all the rooms in advance.

A few talks ended up being a bit longer than their proposed length. With enough of a heads-up, I could adjust the schedule (especially as other talks got cancelled), but sometimes it was difficult to keep track of changes as I shuffled talks around. I wrote some code to calculate the differences, and I appreciate how the speakers patiently dealt with my flurries of e-mails.

Scheduling some more buffer time between talks might be good. In general, the Q&A time felt like a good length, though, and it was nice that people had the option of continuing with the speaker in the web conference room. So it was actually more like we had two or three or four tracks going on at the same time.

Having two tracks allowed us to accept all the talks. I'm glad I kept the dev track to a single day, though. I ended up managing things all by myself on Sunday afternoon, and that was hard enough with one track. Fortunately, speakers were comfortable navigating the Etherpad questions themselves and reading the questions out loud. Sometimes I stepped in during natural pauses to read the next question on the list (assuming I managed to find the right window among all the ones open on my screen).

Idea: If I get better at setting up one window per ongoing Q&A session and using the volume controls to spatialize each so that I can distinguish left/middle/right conversations, it might be easier for me to keep track of all of those. I wasn't quite sure how to arrange all the windows I wanted to pay attention even with an external screen (1920x1200 + my laptop's 1920x1080). I wonder if I'd trust EXWM to handle tiling all the different web browser windows, while keeping the VNC windows floating so that they don't mess with the size of the stream. Maybe I can borrow my husband's ultrawide monitor. Maybe I can see if I can use one of those fancy macropads for quick shortcuts, like switching to a specified set of windows and unmuting myself. Maybe I can figure out how to funnel streaming captions into IRC channels or another text form (also a requested feature) so that I can monitor them that way and quickly skim the history… Or I can focus on making it easier for people to volunteer (and reminding myself that it's okay to ask for their help!), since then I don't have to split my attention all those different ways and I get to learn from other people's awesomeness.

If we decide to go with one track next year, we might have to prioritize talks, which is hard especially if some of the accepted speakers end up cancelling anyway. Alternatively, a better way might be to make things easier for last-minute volunteers to join and read the questions. They don't even need any special setup or anything; they can just join the BigBlueButton web conference session and read from the pad. Idea: A single Etherpad with all the direct BBB URLs and pad links will make this easier for last-minute volunteers.

Also like last year, we added an open mic session to fill in the time from a last-minute cancellation, and that went well. It might be nice to build that into the schedule earlier instead of waiting for a cancellation so that people can plan for it. We moved the closing remarks earlier as well so that people didn't have to stay up so late in other timezones.

I am super, super thankful we had a crontab automatically switching between talks, because that meant one less time-sensitive thing I had to pay attention to. I didn't worry about cutting people off too early because people could continue off-stream, although this generally worked better for Q&A sessions done over BigBlueButton or on Etherpad rather than IRC. I also improved the code for generating a test schedule so that I could test the automatic switching.

It was probably a good thing that I didn't automatically write the next session time to the Etherpad, though, because people had already started adding notes and questions to the pad before the conference, so I couldn't automatically update them as I changed the schedule. Idea: I can write a function to copy the heads-up for the next talk, or I can add the text to a checklist so that I can easily copy and paste it. Since I was managing the check-ins for both streams as well as doing the occasional bit of hosting, a checklist combining all the info for all the streams might be nice. I didn't really take advantage of the editability of Etherpad, so maybe putting it into HTML instead will allow me to make it easier to skim or use. (Copy icons, etc.)

Like before, I offset the start of the dev track to give ourselves a little more time to warm up, and I started Sunday morning with more asynchronous Q&A instead of web conferences. Not much in terms of bandwidth issues this year.

We got everyone's time constraints correctly this year, hooray! Doing timezone conversions in Emacs means I don't have to calculate things myself, and the code for checking the time constraints of scheduled sessions worked with this year's shifting schedules too. Idea: It would be great to mail iCalendar files (.ics) to each speaker so that they can easily add their talk (including check-in time, URLs, and mod codes) to their calendar. Bonus points if we can get it to update previous copies if I've made a change.

This is what the schedule looked like:

(with-temp-file (expand-file-name "schedule.svg" emacsconf-cache-dir)
  (let ((emacsconf-use-absolute-url t))
    (svg-print
     (emacsconf-schedule-svg
      800 300
      (emacsconf-publish-prepare-for-display
       (emacsconf-get-talk-info))))))

I added a vertical view to the 2025 organizers notebook, which was easier to read. I also added some code to handle cancelling a talk and keeping track of rescheduled talks.

Graphical view of the scheduleSchedule for SaturdaySaturday 9:00- 9:10 Saturday opening remarkssat-open 9:10- 9:20 Making Org-Babel reactiveorg-babel 9:30- 9:55 Emacs as a fully-fledged reference managerreference10:15-10:40 org-gmail: A deep integration of Gmail into your Org Modegmail10:50-11:15 Reading and writing emails in GNU Emacs with Gnusgnus11:25-11:45 LaTeX export in org-mode: the overhaullatex 1:00- 1:25 Basic Calc functionality for engineering or electronicscalc 1:35- 2:15 Blee-LCNT: An Emacs-centered content production and self-publication frameworkblee-lcnt 2:35- 2:40 GNU Emacs Greader (Gnamù Reader) mode is the best Emacs mode in existencegreader 2:50- 3:40 Open sessionopen-mic 4:00- 4:10 Saturday closing remarks / open sessionsat-close 9:30- 9:55 One year progress update Schemacs (formerly Gypsum)schemacs10:15-10:35 Juicemacs: exploring speculative JIT compilation for ELisp in Javajuicemacs10:45-11:10 Swanky Python: Interactive development for Pythonswanky11:20-11:40 Interactive Python programming in Emacspython 1:00- 1:25 Emacs, editors, and LLM driven workflowsllm 1:45- 2:05 Emacs and private AI: a great matchprivate-ai 2:25- 2:55 Common Lisp images communicating like-a-human through shared Emacs slime and eevcommonlisp 3:05- 3:30 Modern Emacs/Elisp hardware/software accelerated graphicsgraphics9 AM10 AM11 AM12 PM1 PM2 PM3 PM4 PM5 PMSchedule for SundaySunday 9:00- 9:10 Sunday opening remarkssun-open 9:10- 9:30 Some problems of modernizing Emacsmodern 9:40-10:15 An introduction to the Emacs Readerreader10:35-10:45 Weightlifting tracking with Emacs on Androidweights11:05-11:25 corfu+yasnippet: Easier than I thoughtcompletion 1:00- 1:25 Zettelkasten for regular Emacs hackerszettelkasten 1:45- 2:15 Questions and answers to help you fly with Hyperbolehyperboleqa 2:15- 2:35 Gardening in Emacs: A Windows user's tale of tending, tweaking, and triumphgardening 2:45- 3:20 Bookclub tapasbookclub-tapas 3:40- 3:50 Sunday closing remarkssun-close9 AM10 AM11 AM12 PM1 PM2 PM3 PM4 PM5 PM

Idea: I still haven't gotten around to localizing times on the watch pages. That could be nice.

Recorded introductions

Recording all the introductions beforehand was extremely helpful. I used subed-record.el to record and compile the audio without having to edit out the oopses manually. Recording the intros also gave me something to do other than worry about missing videos.

A few speakers helped correct the pronunciations of their names, which was nice. I probably could have picked up the right pronunciation for one of them if I had remembered to check his video first, as he had not only uploaded a video early but even said his name in it. Next time, I can go check that first.

This year, all the intros played the properly corrected files along with their subtitles. The process is improving!

Recorded videos

As usual, most speakers sent in pre-recorded videos this year, although it took them a little bit longer to do them because life got busy for everyone. Just like last year, speakers uploaded their files via PsiTransfer. I picked up some people's videos late because I hadn't checked. I've modified the upload instructions to ask the speakers to email me when they've uploaded their file, since I'm not sure that PsiTransfer can automatically send email when a new file has been uploaded. We set up a proper domain name, so this year, people didn't get confused by trying to FTP to it.

I had to redo some of the re-encodings and ask one speaker to reupload is video, but we managed to catch those errors before they streamed.

I normalized all the audio myself this year. I used Audacity to normalize it to -16 LUFS. I didn't listen to everything in full, but the results seemed to have been alright. Idea: Audio left/right difference was not an issue this year, but in the future, I might still consider mixing the audio down to mono.

All the files properly loaded from the cache directory instead of getting shadowed by files in other directories, since I reused the process from last year instead of switching mid-way.

People didn't complain about colour smearing, but it looks like the Calc talk had some. Ah! For some reason, MPV was back on 0.35, so now I've modified our Ansible playbook so that it insists on 0.38. I don't think there were any issues about switching between dark mode or light mode.

There was one last-minute upload where I wasn't sure whether there were supposed to be captions in the first part. When I toggled the captions to try to reload them once I copied over the updated VTT, I think I accidentally left them toggled them off, but fortunately the speaker let me know in IRC.

For the opening video, I made the text a bit more generic by removing the year references so that I can theoretically reuse the audio next year. That might save me some time.

I modified our mpv.conf to display the time remaining in the lower right-hand corner. This was a fantastic idea that made it easy to give the speaker a heads-up that their recorded talk was about to finish and that we were about to switch over to the live Q&A session. Because sometimes we weren't able to spare enough attention to host a session, I usually just added the time of the next session to the Etherpad to give speakers a heads-up that the stream would be switching away from their Q&A session. Then I didn't need to make a fancy Javascript timer either.

Captioning

The next step in the process was to caption each uploaded video. While speech recognition tools give us a great headstart, there's really no way around the work of getting technical topics right.

We used WhisperX for speech-to-text again. This time, I used the --initial_prompt argument to try to get it to spell things properly: "Transcribe this talk about Emacs. It may mention Emacs keywords such as Org Mode, Org Roam, Magit, gptel, or chatgpt-shell, or tech keywords such as LLMs. Format function names and keyboard shortcut sequences according to Emacs conventions using Markdown syntax. For example: control h becomes \`C-h\`.". It did not actually do the keyboard shortcut sequences. (Emacs documentation is an infinitesimal fraction of the training data, probably!) It generally did a good job of recognizing Emacs rather than EMAX and capitalizing things. Idea: I can experiment with few-shot prompting techniques or just expand my-subed-fix-common-errors to detect the patterns.

This year, we used Anush V's sub-seg tool to split the subtitles into reasonably short, logically split phrases. I used to manually split these so that the subtitles flowed more smoothly, or if I was pressed for time, I left it at just the length-based splits that WhisperX uses. sub-seg was much nicer as a starting point, so I wrote a shell script to run it more automatically. It still had a few run-on captions and there were a couple of files that confused it, so I manually split those.

Shell script for calling sub-seg

#!/bin/bash ~/vendor/sub-seg/.venv/bin/python3 ~/vendor/sub-seg/src/prediction.py "$1" tmp/subseg-predict.txt ~/vendor/sub-seg.venv/bin/python3 ~/vendor/sub-seg/src/postprocess_to_single_lines.py "\(1" /tmp/subseg-predict.txt /tmp/subseg-cleaned.txt grep -v -e "^\)" /tmp/subseg-cleaned.txt > "${1%.*}–backstage–split.txt"

I felt that it was also a good idea to correct the timing before posting the subtitles for other people to work on because otherwise it would be more difficult for any volunteers to be able to replay parts of the subtitles that needed extra attention. WhisperX often gave me overlapping caption timestamps and it didn't always have word timestamps I could use instead. I used Aeneas to re-align the text with the audio so that we could get better timestamps. As usual, Aeneas got confused by silences or non-speech audio, so I used my subed-word-data.el code to visualize the word timing and my subed-align.el code to realign regions. Idea: I should probably be able to use the word data to realign things semi-automatically, or at least flag things that might need more tweaking. There were a few chunks that weren't recognized at all, but I was able to spot them and fix them when I was fixing the timestamps. Making this more automated will make it much easier to share captioning work with other volunteers. Alternatively, I can also post the talks for captioning before I fix the timestamps, because I can fix them while other people are editing the text.

While I was correcting the timestamps, it was pretty tempting for me to just go ahead and fix whatever errors I encountered along the way. From there on, it seemed like only a little bit more work was needed in order to get it ready for the speaker to review, and besides, doing subtitles is one of the things I enjoy about EmacsConf. So the end result is that for most of the talks, by the time I finished getting it to the point where I felt like someone who was new to captioning could take over, it was often quite close to being done. I did actually manage to successfully accept some people's help. The code that I wrote for showing me the word differences between two files (sometimes the speaker's script or the subtitles that were edited by another volunteer) was very useful for last-minute merges.

Working on captions is one of my favourite parts. Well-edited captions are totally a nice-to-have, but I like it when people can watch the videos (or skim them) and not worry about not being able to hear or understand what someone said. I enjoy sitting down and spending time with people's presentations, turning them into text that we can search. Captioning helps me feel connected with some of the things I love the most about the Emacs community.

I really appreciated how a number of speakers and volunteers helped with quality control by watching other videos in the backstage area. Idea: I can spend some time improving the design of the backstage area to make it more pleasant and to make it easier for people to find something to work on if they have a little time.

Of course, some talks couldn't be captioned beforehand because they were live. For live conferences and for many of the Q&A sessions, we used BigBlueButton.

BigBlueButton web conference

We used BigBlueButton again this year instead of switching over to something like Galene. I moved the node from bandali's Linode account to mine so that I wouldn't feel as guilty bringing it up and down throughout the year for various Emacs meetups. That also meant that the actual setup was fairly stable since it had survived a lot of meetups before EmacsConf, and I didn't have to spend as much time before the event worrying about it. I still worried a little, as after the November OrgMeetup, I noticed a technical issue that I couldn't nail down. Fortunately, that happened after the meetup had already ended. I also used my meetup crontab to schedule test sessions with some speakers so that they could record their presentation or doublecheck their sharing setup.

I resized the BigBlueButton early Friday afternoon this year so that speakers could have a few more hours to test their setups if they wanted to, especially since one speaker had mentioned having a hard time sharing his screen on Wayland.

I thought I had --allow_auto_disk_resize true in the bbb-prod shell-script I used to resize the node, but I don't think linode-cli actually resized the disk (maybe I ran a different shell script), and I forgot to double-check that before the conference. Fortunately, we had just enough space for all the recordings. I noticed only when I was waiting for the recordings to finish processing, since some of them were failing. Idea: Next time, I should manually check that the disk has resized, and I can probably tweak my checklist too.

Like before, we used moderator codes so that speakers could let themselves into the room and get everything set up just in case. Idea: I wonder if there's a way to specify the moderator code in the URL so that I can link to that directly. Maybe I can modify the web interface to fill that in with a little bit of Javascript.

Oddly, the moderator codes didn't let people start or stop recording, but fortunately I was able to log in and take care of all of that. I mostly remembered to start the recording, except for one instance where I started recording a little bit late.

Code for reporting BBB usage
(emacsconf-extract-bbb-report
 (seq-remove (lambda (o)
               (string-match "backups" o))
             (directory-files-recursively emacsconf-extract-bbb-raw-dir "events.xml")))
2023 2024 2025  
62 107 35 Max number of simultaneous users
6 7 5 Max number of simultaneous meetings
27 25 14 Max number of people in one meeting
84 102 55 Total unique users
36 40 27 Total unique talking

I might be able to get away with a Linode 8 GB node (USD 0.072/hour) instead of a Linode 16 GB node (USD 0.144/hour), but keeping it at the current size is probably okay.

I'll go into more detail in the budget section, but the total was USD 14.02 to host BigBlueButton ourselves in December, and between USD 5 to USD 8 / month to run it the rest of the time (including upsizing it for meetups), so that was much less than what it would have cost for BigBlueButton-specific hosting.

Checking speakers in

To keep the live talks and Q&A sessions flowing smoothly, there's a bit of a scramble behind the scenes. We asked speakers to check in at least half an hour before they need to go live, and most of them were able to find their way to the BigBlueButton room using the provided moderator codes. I mostly handled the check-ins, with some help from Corwin when two people needed to be checked in at the same time. We generally didn't have any tech issues, although a couple of people were missing during their Q&A sessions. (We just shrugged and continued; too much to take care of to worry about anything!)

Check-ins are usually good opportunities to chat with speakers, but because I needed to pay attention to the other streams as well as check in other people, it felt a bit more rushed and less enjoyable. I missed having those opportunities to connect, but I'm glad speakers were able to handle so much on their own.

Hosting

Corwin did a lot of the on-stream hosting with a little help from Amin on Saturday. I occasionally jumped in and asked the speakers questions from the pad, but when I was busy checking people in or managing other technical issues cropping up, the speakers also read the questions out themselves so that I could match things up in the transcript afterwards.

No crashes this time, I think. Hooray!

Infrastructure

I used September and October to review all the infrastructure and see what made sense to upgrade. The BigBlueButton instance is on a virtual private server that's dedicated to it, since it doesn't like to share with any other services. Since I set it up from scratch following the recommended configuration, the server uses a recent version of Ubuntu. Some of the other servers use outdated Debian distributions that no longer get security updates. They have other services on them, so I'll leave them to bandali to upgrade when he's ready.

Streaming

I am so, so glad that we had our VNC+OBS setup this year, with two separate user accounts using OBS to stream each track from a virtual desktop on a remote server. We switched to this setup in 2022 so that I could handle multiple tracks without worrying about last-minute coordination or people's laptop specs. If we had had our original setup in 2019, with hosts streaming from their personal computers, I think we'd have been dead in the water. Instead, our scripts took care of most of the on-screen actions, so I just needed to rearrange the layout. (Idea: If I can figure out how to get BigBlueButton to use our preferred layout, that would make it even better.)

I used MPV to monitor the streams in little thumbnails. I set those windows to always be on top, and I set the audio so that the general track was on my left side and the development track was o my right. I used some shortcuts to jump between streams reliably, taking advantage of how I bound the Windows key on my laptop to the Super modifier. I used KDE's custom keyboard shortcuts to set Super-g to raise all of my general-track windows and Super-d to do the same for all of the development-track ones. Those were set to commands like this:

/home/sacha/bin/focus_or_launch "emacsconf-gen - TigerVNC"; /home/sacha/bin/focus_or_launch gen.webm; /home/sacha/bin/focus_or_launch "gen.*Konsole"

Code for focus_or_launch

#!/bin/bash

############# GLOBVAR/PREP ###############

Executable="\(1" ExecutableBase="\)(basename "$Executable")" Launchcommand="$2" Usage="\ Usage: $(basename $0) command [launchcommand] [exclude] E.g.: $(basename $0) google-chrome\ " ExcludePattern="$3"

################ MAIN ####################

if ; then MostRecentWID="$(printf "%d" $(wmctrl -xl | grep "$ExecutableBase" | tail -1 2> /dev/null | awk '{print \(1}'))" else MostRecentWID="\)(printf "%d" $(wmctrl -xl | grep -ve "$ExcludePattern" | grep "$ExecutableBase" | tail -1 2> /dev/null | awk '{print $1}'))" fi

if ; then if ; then "$Executable" > /dev/null 2>&1 & else $LaunchCommand > /dev/null 2>&1 & fi disown else if xdotool search –class "\(ExecutableBase" | grep -q "^\)(xdotool getactivewindow)$"; then xdotool sleep 0.050 key "ctrl+alt+l" else

xdotool windowactivate "$MostRecentWID" 2>&1 | grep failed \ && xdotool search –class "$ExecutableBase" windowactivate %@ fi fi

(Idea: It would be great to easily assign specific windows to shortcuts on my numeric keypad or on a macropad. Maybe I can rename a window or manually update a list that a script can read…)

To get the video streams out to viewers, we used Icecast (a streaming media server) on a Linode 64GB 16 core shared CPU server again this year, and that seemed to work out. I briefly contemplated using a more modern setup like nginx-rtmp, Ant Media Server, or SRS if we wanted HLS (wider support), adaptive bitrate streaming, or lower latency, but I decided against it because I didn't want to add more complexity. Good thing too. This year would not have been a good time to experiment with something new.

Like before, watching the stream directly using mpv, vlc, or ffplay was smoother than watching it through the web-based viewers. One of the participants suggested adding more detailed instructions for VLC so that people can enjoy it even without using the command-line, so we did.

The 480p stream and the YouTube stream were all right, although I forgot to start one of the YouTube streams until a bit later. Idea: Next time, I can set all the streams to autostart.

Corwin had an extra server lying around, so I used that to restream to Toobnix just in case. That seems to have worked, although I didn't have the brainspace to check on it.

I totally forgot about displaying random packages on the waiting screen. Idea: Maybe next year I can add a fortune.txt to the cache directory with various one-line Emacs tips.

I might be able to drop the specifications down to 32GB 8 core if we wanted to.

Publishing

People appreciated being able to get videos and transcripts as soon as each talk aired. Publishing the video files and transcripts generally worked out smoothly, aside from a few times when I needed to manually update the git repository. I modified the code to add more links to the Org files and to walk me through publishing videos to YouTube.

I uploaded all the videos to YouTube and scheduled them so that I didn't have to manage that during the conference. I did not get around to uploading them to Toobnix until after the conference since dealing with lots of duplicated updates is annoying. I've been writing a few more functions to work with the Toobnix API, so it might be a little easier to do things like update descriptions or subtitles next year.

Etherpad

As usual, we used Etherpad to collect questions from conference partcipants, and many speakers answered questions there as well.

I used the systemli.etherpad Ansible role to upgrade to Etherpad 2.5. This included some security fixes, so that was a relief.

I added pronouns and pronunciations to the Etherpad to make it easier for hosts to remember just in case.

I did not get to use emacsconf-erc-copy to copy IRC to Etherpad because I was too busy juggling everything else, so I just updated things afterwards.

Idea: Just in case, it might be good to have a backup plan in case I need to switch Etherpad to authenticated users or read-only use. Maybe I can prepare questions beforehand, just in case we get some serious griefing.

IRC

The plain-text chat channels on IRC continued to be a great place to discuss things, with lots of discussions, comments, and encouraging feedback. My automated IRC system continued to do a good job of posting the talk links, and the announcements made it easier to split up the log by talk.

Planning for social challenges is harder than planning for technical ones, though. libera.chat has been dealing with spam attacks recently. Someone's also been griefing #emacs and other channels via the chat.emacsconf.org web interface, so we decided to keep chat.emacsconf.org dormant until the conference itself. If this is an issue next year, we might need to figure out moderation. I'd prefer to not require everyone to register accounts or be granted voice permissions, so we'll see.

Extracting the Q&A

We recorded all the Q&A sessions so that we could post them afterwards. As mentioned, I only started the recording late once. Progress! I generally started it a few minutes early. As I got more confident about paying attention to the start of a session and rearranging the layout on screen, I also got better at starting the recording shortly before turning it over to the speaker. That made it easier to trim the recordings afterwards.

It took me a little longer to get to the Q&A sessions this year. Like last year, getting the recordings from BigBlueButton was fairly easy because I could get a single processed video instead of combining the audio, screenshare, and webcam video myself. This year the single-file video downloads were .m4v files, so I needed to modify things slightly. I think my workflow last year assumed I started with .webm files. Anyway, after some re-encoding, I got it processed. Now I've modified the /usr/local/bigbluebutton/core/scripts/video.yml to enable webm. I wonder if it got wiped after my panic-reinstall after November's OrgMeetup. Idea: I should add the config to my Ansible so that it stays throughout reinstalls and upgrades.

I wrote a emacsconf-subed-copy-current-chapter-text function so that I can more easily paste answers into the Q&A… which turned out to be a superset of the emacsconf-extract-subed-copy-section-text I mentioned in last year's notes and totally forgot about. This tells me that I need to add it to my EmacsConf - organizers-notebook notes so that I actually know what to do. I did not use my completion functions to add section headings based on comments either. Apparently this was emacsconf-extract-insert-note-with-question-heading, which I do have a note about in my organizer notebook.

Audio mixing was reasonable. I normalized the audio in Audacity, and I manually fixed some sections where some participants' audio volumes were lower.

Budget

Hosting a conference online continues to be pretty manageable. Here are our costs for the year (including taxes where applicable):

Code for calculating BBB node costs
(append
'(("Node" "Jan" "Feb" "Mar" "Apr" "May" "Jun" "Jul" "Aug" "Sep" "Oct" "Nov" "Dec" "Total") hline)
(mapcar
 (lambda (group)
   (let ((amounts (mapcar
          (lambda (file)
            (format "%.2f"
                    (apply '+
                           (seq-keep (lambda (row)
                                       (when (string-match (car group) (car row))
                                         (string-to-number (elt row 8))))
                                     (cdr (pcsv-parse-file file))))))
          (directory-files (format "~/proj/emacsconf/2025/invoices/%s/" (cdr group))
                           t "\\.csv"))))
   (append (list (car group))
           amounts
           (list (format "%.2f" (apply '+ (mapcar 'string-to-number amounts)))))))
 '(("meet" . "sacha")
   ("front0" . "bandali")
   ("live0" . "bandali")))
   nil
   )
Node Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec Total
meet 2.17 7.55 6.78 6.74 7.13 6.95 7.19 7.27 6.75 7.19 7.56 14.02 87.30
front0 5.00 5.00 5.00 5.00 5.00 5.00 5.00 5.00 5.00 5.00 5.00 18.79 73.79
live0 5.00 5.00 5.00 5.00 5.00 5.00 5.00 5.00 5.00 5.00 5.00 32.89 87.89

Grand total for 2025: USD 248.98

Server configuration during the conference:

Node Specs Hours upscaled CPU load
meet 16GB 8core shared 52 peak 172% CPU (100% is 1 CPU)
front 32GB 8core shared 47 peak 47% CPU (100% is 1 CPU)
live 64GB 16core shared 48 peak 440% CPU (100% is 1 CPU)
res 46GB 12core   peak 60% total CPU (100% is 12 CPUs); each OBS ~3.5 CPUs), mem 7GB used
media 3GB 1core    

These hosting costs are a little higher than 2024 because we now pay for hosting the BigBlueButton server (meet) year-round. That's ~ 5 USD/month for a Linode 1 GB instance and an extra USD 2-3 / month that lets us provide a platform for OrgMeetup, Emacs APAC, and Emacs Berlin by alternating between a 1 GB Linode and an 8 GB Linode as needed. The meetups had previously been using the free Jitsi service, but sometimes that has some limitations. In 2023 and most of 2024, the BigBlueButton server had been provided by FossHost, which has since shut down. This time, maintaining the server year-round meant that we didn't have to do any last-minute scrambles to install and configure the machine, which I appreciated. I could potentially get the cost down further if I use Linode's custom images to create a node from a saved image right before a meetup, but I think that trades about USD 2 of savings/month for much more technical risk, so I'm fine with just leaving it running downscaled.

If we need to cut costs, live0 might be more of a candidate because I think we'll be able to use Ansible scripts to recreate the Icecast setup. I think we're fine, though. Also, based on the CPU peak loads, we might be able to get away with lower specs during the conference (maybe meet: 8 GB, front: 8 GB, live: 32 GB), for an estimated savings of USD 27.76, with the risk of having to worry about it if we hit the limit. So it's probably not a big deal for now.

I think people's donations through the Working Together program can cover the costs for this year, just like last year. (Thanks!) I just have to do some paperwork.

In addition to these servers, Ry P provided res.emacsconf.org for OBS streaming over VNC sessions. The Free Software Foundation also provided media.emacsconf.org for serving media files, and yang3 provided eu.media.emacsconf.org.

If other people are considering running an online conference, the hosting part is surprisingly manageable, at least for our tiny audience size of about 100 peak simultaneous viewers and 35 web conference participants. It was nice to make sure that everyone can watch without ads or Javascript.

Behind the scenes: In terms of keeping an eye on performance limits, we're usually more CPU-bound than memory- or disk-bound, so we had plenty of room this year. Now I have some Org Babel source blocks to automatically collect the stats from different servers. Here's what that Org block looks like:

#+begin_src sh :dir /ssh:res:/ :results verbatim :wrap example
top -b -n 1 | head
#+end_src

I should write something similar to grab the Icecast stats from http://live0.emacsconf.org:8001/status.xsl periodically. Curl will do the trick, of course, but maybe I can get Emacs to add a row to an Org Mode table.

Tracking my time

As part of my general interest in time-tracking, I tracked EmacsConf-related time separately from my general Emacs-related time.

Calculations
(append (list '("Year" "Month" "Hours") 'hline)
(seq-mapcat
 (lambda (year)
   (mapcar
    (lambda (group)
      (list
       year
       (car group)
       (format "%.1f"
               (apply '+
                      (mapcar (lambda (o) (/ (alist-get 'duration o) 3600.0))
                              (cdr group))))))
    (seq-group-by (lambda (o)
                    (substring (alist-get 'date o) 5 7))
                  (quantified-records
                   (concat year "-01-01")
                   (concat year "-12-31")
                   "&filter_string=emacsconf&split=keep&order=oldest"))))
 '("2024" "2025")))
Year Month Hours
2024 01 2.5
2024 07 2.8
2024 08 0.6
2024 09 7.0
2024 10 14.4
2024 11 30.7
2024 12 46.2
2025 01 9.7
2025 02 1.4
2025 04 0.9
2025 06 0.8
2025 07 6.0
2025 08 3.6
2025 09 10.9
2025 10 1.2
2025 11 21.0
2025 12 64.3
import pandas as pd
import matplotlib.pyplot as plt
df = pd.DataFrame(data[1:], columns=data[0])
df = pd.pivot_table(df, columns=['Month'], index=['Year'], values='Hours', aggfunc='sum', fill_value=0)
df = df.reindex(columns=range(1, 13), fill_value=0)
df['Total'] = df.sum(axis=1)
df = df.applymap(lambda x: f"{x:.1f}" if x != 0 else "")
return df
Year 1 2 3 4 5 6 7 8 9 10 11 12 Total
2024 2.5           2.8 0.6 7.0 14.4 30.7 46.2 104.2
2025 9.7 1.4   0.9   0.8 6.0 3.6 10.9 1.2 21.0 64.3 119.8

This year, I spent more time doing the reencoding and captions in December, since most of the submissions came in around that time. I didn't even listen to all the videos in real time. I used my shortcuts to skim the captions and jump around. It would have been nice to be able to spread out the work a little bit more instead of squeezing most of it into the first week of December, since that would have made it easier to coordinate with other volunteers without feeling like I might have to do more last-minute scrambles if they were busier than expected.

I was a little bit nervous about making sure that the infrastructure was all going to be okay for the conference as well as double checking the videos for possible encoding issues or audio issues. Stress tends to make me gloss over things or make small mistakes, which meant I had to slow down even more to double-check things and add more things to our process documentation.

I'm glad it all worked out. Even if I switched that time to working on Emacs stuff myself, I don't think I would have been able to put together all the kinds of wonderful tips and experiences that other people shared in the conference, so that was worthwhile.

(Here's a longer-term analysis time going back to 2012.)

Thanks

  • Thank you to all the speakers, volunteers, and participants, and to all those other people in our lives who make it possible through time and support.
  • Thanks to other volunteers: 
    • Corwin and Amin for helping with the organization
    • JC Helary, Triko, and James Endres Howell for help reviewing CFPs
    • Amitav Krishna, Rodion Goritskov, jay_bird, and indra for captions
    • yang3 for the EU mirror we're setting up
    • Bhavin Gandhi, Michael Kokosenski, Iain Young, Jamie Cullen, Ihor Radchenko (yantar92), FlowyCoder for other help
  • Thanks to the Free Software Foundation for the mailing lists, the media server, and of course, GNU Emacs.
  • Thanks to Ry P for the server that we're using for OBS streaming and processing videos.
  • Thanks to the many users and contributers and project teams that create all the awesome free software we use, especially:
  • Thanks to shoshin for the music.
  • Thanks to people who donated via the FSF Working Together program: Scott Ranby, Jonathan Mitchell, and 8 other anonymous donors!

Overall

I've already started hearing from people who enjoyed the conference and picked up lots of good tips from it. Wonderful!

EmacsConf 2025 was a bit more challenging this year. The world has gotten a lot busier. Return-to-work mandates, job market turmoil, health challenges, bigger societal changes… It's harder for people to find time. For example, the maintainers of Emacs and Org are too busy working on useful updates and bugfixes to rehash the news for us, so maybe I'll get back to doing Emacs News Highlights next year. I'm super lucky in that I can stand outside most of all of that and make this space where we can take a break and chat about Emacs. I really appreciated having this nice, cozy little place where people could get together and bump into other people who might be interested in similar things, either at the event itself or in the discussions afterwards. I'm glad we started with a large schedule and let things settle down. I'd rather have that slack instead of making speakers feel stressed or guilty.

I love the way that working on the various parts of the conference gives me an excuse to tinker with Emacs and figure out how to use it for more things.

As you can tell from the other sections in this post, I tend to have so much fun doing this that I often forget to check if I've already written the same functions before. When I do remember, I feel really good about being able to build on this accumulation of little improvements.

I didn't feel like I really got to attend EmacsConf this year, but that's not really surprising because I don't usually get to. I think the only time I've actually been able to take proper notes during EmacsConf itself was back in 2013. It's okay, I get to spend a little time with presentations before anyone else does, and there's always the time afterwards. Plus I can always reach out to the speakers myself. It might be nice to be able to just sit and enjoy it and ask questions. Maybe someday when I've automated enough to make this something that I can run easily even on my own.

So let's quickly sketch out some possible scenarios:

  • I might need to do it on my own next year, in case other organizers get pulled away at the last minute: I think it's possible, especially if I can plan for some emergency moderation or last-minute volunteers. I had a good experience, despite the stress of juggling things live on stream. (One time, one of the speakers had a question for me, and he had to repeat it a few times before I found the right tab and unmuted.) Still, I think it would be great to do it again next year.
  • Some of the other organizers might be more available: It would be nice to have people on screen handling the hosting. If people can help out with verifying the encoding, normalizing the audio, and shepherding videos through the process, that might let me free up some time to coordinate captions with other volunteers even for later submissions.
  • I might get better at asking for help and making it easier for people to get involved: That would be pretty awesome. Sometimes it's hard for me to think about what and how, so if some people can take the initiative, that could be good.

This is good. It means that I can probably say yes to EmacsConf even if it's just me and whoever wants to share what they've been learning about Emacs this year. That's the basic level. We're still here, amazing! Anything else people can add to that is a welcome bonus. We'll make stone soup together.

EmacsConf doesn't have to be snazzy. We don't need to try to out-market VS Code or whatever other editors emerge over the next 10 years. We can just keep having fun and doing awesome things with Emacs. I love the way that this online conference lets people participate from all over the world. We like to focus on facilitating sharing and then capturing the videos, questions, answers so that people can keep learning from them afterwards. I'm looking forward to more of that next year.

I'd love to hear what people thought of it and see how we can make things better together. I'd love to swap notes with organizers of other conferences, too. What's it like behind the scenes?

My next steps are:

  • Extract part of this into a report for the emacsconf.org website, like EmacsConf - 2024 - EmacsConf 2024 Report.
  • Announce the resources and report on emacsconf-discuss.
  • Catch up on the other parts of my life I've been postponing.
  • Start thinking about EmacsConf 2026 =)
View org source for this post

You can comment on Mastodon or e-mail me at sacha@sachachua.com.

-1:-- EmacsConf 2025 notes (Post Sacha Chua)--L0--C0--2026-01-02T17:20:06.000Z

Irreal: Blogging Platforms

Jack Baty has a New Year’s resolution: Don’t change blogging platforms more than once a quarter. Say what? I don’t know about the rest of you but changing blogging platforms regularly seems to me like getting divorced and remarried regularly. What sensible person would want to do that? Baty’s excuse is that he likes to tinker and mostly blogs about tinkering so naturally changing blogging platforms seems to make sense to him.

For me, blogging is all about writing and sharing my discoveries. The last thing I want is to worry about is my blogging platform. I want it to be as transparent as possible so I don’t have to think about it. I just want to write my post in Org mode and push a button to publish it.

I started blogging with Blogger. It was easy and it wasn’t too hard to turn an Org mode file into a post. After a while I got my own domain, Irreal.org, and moved to WordPress. It’s not all that different from Blogger—except that Google isn’t lurking in the background deciding whether my posts are acceptable—and, like blogger, it’s easy to publish Org files as posts.

It’s been 14 years since I moved to WordPress and while it’s sometimes a pain, I’ve never seriously considered moving to something else.

There are, it seems to me, two type of blogging platforms: static and database-centric. Static blogs are simple and don’t require backups but they require more work on the front end. Database systems, like Blogger and WordPress, are more turnkey but are more susceptible to exploits and require you to backup the database periodically.

I don’t know which is the best—it probably depends on your inclinations—but once you’ve decided on a platform you should probably stick with it unless there are compelling reasons to change.

WordPress certainly isn’t perfect but it’s good enough. It allows me to concentrate on my writing and not worry about the details of publishing it. Unless things change drastically, I don’t see Irreal changing.

-1:-- Blogging Platforms (Post Irreal)--L0--C0--2026-01-01T16:10:49.000Z

Jack Baty: How about some blogging stability for 2026?

How about this for a resolution… Don’t change blogging platforms more than once a quarter.

Ha!

I sometimes wish playing with tools wasn’t so much fun. It would be better, I think, to write more, tinker less. Except that I mostly write about tinkering, so that’s sort of self-defeating, no?

Let’s recap.

2025 was comprised of Kirby, Ghost, WordPress, Hugo, Zola, Eleventy, Blot, Tinderbox, Emacs, and TiddlyWiki. That seems like a lot, even for me.

We’re ending the year with baty.net on Hugo, daily.baty.net on Tinderbox, baty.blog with Blot, and the lathe still built with TiddlyWiki.

That’s too much to manage. I dream of having One True Blog, but we all know that’s not happening. Going into 2026, I’m going to try and limit it to 3. This blog, the daily blog, and the wiki. I sometimes wonder why I have both the wiki and daily blogs, but I have never been able to settle on one or the other.

Three blogs seems like plenty to keep me busy. If I can figure out how to not change how they’re made every other day1, we should be good.


  1. People notice. For instance, Scott Fillmer noticed. 😁 ↩︎

-1:-- How about some blogging stability for 2026? (Post Jack Baty)--L0--C0--2025-12-31T20:08:44.000Z

James Dyer: New package dired-video-thumbnail added to MELPA!

I have created another package!, this time something that I thought was missing from the mighty Emacs and that is the ability to show video thumbnails in a grid and to be able to filter, sort e.t.c. Basically like an enhanced image-dired. I have been increasingly using image-dired for my image editing and management needs and am always adding little improvements, to such an extent I decided to create a video thumb grid package, enjoy!

Introduction

dired-video-thumbnail is an Emacs package that provides image-dired style thumbnail viewing for video files. It extracts thumbnails from videos using ffmpeg and displays them in a grid layout, allowing you to visually browse and manage video collections directly from Emacs.

Features

  • Thumbnail grid display - View video thumbnails in a configurable grid layout
  • Persistent caching - Thumbnails are cached and only regenerated when the source file changes
  • Async generation - Emacs remains responsive while thumbnails are generated in the background
  • Dired integration - Marks sync bidirectionally with the associated dired buffer
  • Visual mark indication - Marked thumbnails display a coloured border (like image-dired)
  • Dynamic header line - Shows filename, dimensions, duration, and file size for the current video
  • Click to play - Open videos in your preferred external player
  • Cross-platform - Works on Linux, macOS, and Windows
  • Resizable thumbnails - Adjust thumbnail size on the fly
  • Sorting - Sort videos by name, date, size, or duration
  • Filtering - Filter videos by name pattern, duration range, or file size
  • Recursive search - Browse videos across subdirectories with optional auto-recursive mode
  • Transient menu - Comprehensive command menu accessible via . or C-c .

Whats New

<2025-12-15 Mon> 0.3.0

Added transient menu interface

Introduced a comprehensive transient menu (dired-video-thumbnail-transient) providing quick access to all commands via . or C-c . in the thumbnail buffer. The menu displays current state (sort order, filters, video count, recursive/wrap mode) and organises commands into logical groups: Navigation, Playback, Sorting, Filtering, Marking, Delete, Display, and Other.

<2025-12-15 Mon> 0.2.0

Enhanced package with sorting, filtering, and docs

Added sorting and filtering features to dired-video-thumbnail. Introduced customizable options for sorting and filtering criteria, and implement interactive commands for toggling these settings. Included comprehensive documentation in Texinfo format, covering installation, usage, and customization.

Requirements

  • Emacs 28.1 or later
  • ffmpeg and ffprobe installed and available in your PATH
  • transient 0.4.0 or later (for the transient menu)

Installation

Manual

Download dired-video-thumbnail.el and place it in your load-path:

(add-to-list 'load-path "/path/to/dired-video-thumbnail/")
(require 'dired-video-thumbnail)

use-package

(use-package dired-video-thumbnail
 :load-path "/path/to/dired-video-thumbnail/"
 :bind (:map dired-mode-map
 ("C-t v" . dired-video-thumbnail)))

straight.el

(straight-use-package
 '(dired-video-thumbnail :type git :host github :repo "captainflasmr/dired-video-thumbnail"))

Usage

Basic Usage

  1. Open a directory containing video files in dired
  2. Run M-x dired-video-thumbnail
  3. A new buffer opens displaying thumbnails for all videos in the directory
  4. The cursor automatically moves to the first thumbnail

With Marked Files

  1. In dired, mark specific video files with m
  2. Run M-x dired-video-thumbnail
  3. Only thumbnails for the marked videos are displayed

Recursive Mode

To include videos from subdirectories:

  • Use C-u M-x dired-video-thumbnail (with prefix argument)
  • Or run M-x dired-video-thumbnail-recursive
  • Or press R in the thumbnail buffer to toggle recursive mode

When dired-video-thumbnail-auto-recursive is enabled (the default), the package automatically searches subdirectories if the current directory contains no video files.

Suggested Keybinding

Add a keybinding in dired for quick access:

(with-eval-after-load 'dired
 (define-key dired-mode-map (kbd "C-t v") #'dired-video-thumbnail))

Transient Menu

Press . or C-c . in the thumbnail buffer to open the transient menu. This provides a comprehensive interface to all commands with a live status display.

State: Sort: name ↑ | Videos: 42 | Recursive: OFF | Wrap: ON

Navigation Playback Sorting Filtering
n Next RET Play video s Sort menu... / Filter menu...
p Previous o Play video S Interactive sort \ Interactive filter
C-n Next row r Reverse order c Clear filters
C-p Previous row
d Go to dired

Marking Delete Display Other
m Mark menu... D Delete v Display menu... g Regenerate thumbnail
M Mark all x Delete marked+ Larger thumbnails G Regenerate all
U Unmark all - Smaller thumbnails C Clear cache
t Toggle all marks w Toggle wrap ? Help
 R Toggle recursive q Quit menu
 Q Quit buffer

The status line at the top shows:

  • Current sort criteria and direction (e.g., name ↑)
  • Number of videos displayed (and total if filtered)
  • Recursive mode status
  • Wrap display mode status
  • Active filters (if any)

Submenus

Several keys open submenus with additional options:

  • s - Sort menu: Sort by name, date, size, or duration; reverse order
  • / - Filter menu: Filter by name regexp, duration range, or size range; clear filters
  • m - Mark menu: Mark/unmark current, toggle current, mark/unmark/toggle all
  • v - Display menu: Adjust size, toggle wrap/recursive, refresh, regenerate thumbnails

Header Line

As you navigate between thumbnails, the header line dynamically displays information about the current video:

  • Mark indicator - A red * if the video is marked
  • Filename - The video filename in bold
  • Dimensions - Video resolution (e.g., 1920x1080)
  • Duration - Video length (e.g., 5:32 or 1:23:45)
  • File size - Size in MB (e.g., 45.2 MB)

The header also shows current sort settings (e.g., [name ↑]), active filters, and a [recursive] indicator when browsing subdirectories.

Keybindings

In the *Video Thumbnails* buffer:

Transient Menu

Key Command Description
. dired-video-thumbnail-transient Open transient menu
C-c . dired-video-thumbnail-transient Open transient menu

Navigation

Key Command Description
n dired-video-thumbnail-next Move to next thumbnail
p dired-video-thumbnail-previous Move to previous thumbnail
SPC dired-video-thumbnail-play Play video at point
C-f dired-video-thumbnail-forward Move to next thumbnail
C-b dired-video-thumbnail-backward Move to previous thumbnail
<right> dired-video-thumbnail-forward Move to next thumbnail
<left> dired-video-thumbnail-backward Move to previous thumbnail
<up> dired-video-thumbnail-previous-row Move up one row
<down> dired-video-thumbnail-next-row Move down one row
d dired-video-thumbnail-goto-dired Switch to associated dired buffer
q quit-window Close the thumbnail buffer
Q dired-video-thumbnail-quit-and-kill Quit and kill the buffer

Playback

Key Command Description
RET dired-video-thumbnail-play Play video at point
o dired-video-thumbnail-play Play video at point
mouse-1 dired-video-thumbnail-play Play video (click)

On Linux, videos open with xdg-open. On macOS, they open with open. On Windows, they open with the system default player. You can also specify a custom player.

Marking

Marks are synchronised with the associated dired buffer, so marking a video in the thumbnail view also marks it in dired, and vice versa.

Key Command Description
m dired-video-thumbnail-mark Mark video and move to next
u dired-video-thumbnail-unmark Unmark video and move to next
mouse-3 dired-video-thumbnail-toggle-mark Toggle mark (right-click)
M dired-video-thumbnail-mark-all Mark all videos
U dired-video-thumbnail-unmark-all Unmark all videos
t dired-video-thumbnail-toggle-all-marks Invert all marks

Deletion

Key Command Description
D dired-video-thumbnail-delete Delete video at point (with confirmation)
x dired-video-thumbnail-delete-marked Delete marked videos (with confirmation)

Display

Key Command Description
+ dired-video-thumbnail-increase-size Increase thumbnail size
- dired-video-thumbnail-decrease-size Decrease thumbnail size
r dired-video-thumbnail-refresh Refresh the display
w dired-video-thumbnail-toggle-wrap Toggle wrap mode (flow vs fixed cols)
R dired-video-thumbnail-toggle-recursive Toggle recursive directory search
g dired-video-thumbnail-regenerate Regenerate thumbnail at point
G dired-video-thumbnail-regenerate-all Regenerate all thumbnails

Sorting

Key Command Description
S dired-video-thumbnail-sort Interactive sort menu
sn dired-video-thumbnail-sort-by-name Sort by filename
sd dired-video-thumbnail-sort-by-date Sort by modification date
ss dired-video-thumbnail-sort-by-size Sort by file size
sD dired-video-thumbnail-sort-by-duration Sort by video duration
sr dired-video-thumbnail-sort-reverse Reverse sort order

Filtering

Key Command Description
\ dired-video-thumbnail-filter Interactive filter menu
/n dired-video-thumbnail-filter-by-name Filter by name regexp
/d dired-video-thumbnail-filter-by-duration Filter by duration range
/s dired-video-thumbnail-filter-by-size Filter by size range
/c dired-video-thumbnail-filter-clear Clear all filters
// dired-video-thumbnail-filter-clear Clear all filters

Help

Key Command Description
h dired-video-thumbnail-help Show help
? dired-video-thumbnail-help Show help

Customisation

All customisation options are in the dired-video-thumbnail group. Use M-x customize-group RET dired-video-thumbnail RET to browse them interactively.

Thumbnail Cache Location

Thumbnails are stored in ~/.emacs.d/dired-video-thumbnails/ by default:

(setq dired-video-thumbnail-cache-dir "~/path/to/cache/")

Thumbnail Size

Control the generated thumbnail size and display height:

(setq dired-video-thumbnail-size 200) ;; Generated thumbnail size (pixels)
(setq dired-video-thumbnail-display-height 150) ;; Display height in buffer

Thumbnails are generated as squares to ensure consistent grid alignment regardless of video aspect ratio.

Grid Layout

Set the number of columns in the thumbnail grid:

(setq dired-video-thumbnail-columns 4)

Wrap Display

Control whether thumbnails wrap to fill the window width:

(setq dired-video-thumbnail-wrap-display t) ;; Wrap to window width (default)
(setq dired-video-thumbnail-wrap-display nil) ;; Use fixed columns
(setq dired-video-thumbnail-spacing 4) ;; Spacing between thumbnails (pixels)

Thumbnail Timestamp

By default, thumbnails are extracted at 5 seconds into the video. Change this to get a more representative frame:

(setq dired-video-thumbnail-timestamp "00:00:10") ;; 10 seconds in
(setq dired-video-thumbnail-timestamp "00:01:00") ;; 1 minute in
(setq dired-video-thumbnail-timestamp nil) ;; Let ffmpeg choose

Video Player

Set your preferred video player:

(setq dired-video-thumbnail-video-player "mpv")
(setq dired-video-thumbnail-video-player "vlc")
(setq dired-video-thumbnail-video-player nil) ;; Use system default

When set to nil (the default), videos open with:

  • Linux: xdg-open
  • macOS: open
  • Windows: System default player (e.g., Films & TV)

Video Extensions

Add or modify recognised video file extensions:

(setq dired-video-thumbnail-video-extensions
 '("mp4" "mkv" "avi" "mov" "webm" "m4v" "wmv" "flv" "mpeg" "mpg" "ogv" "3gp"))

Mark Border Appearance

Marked thumbnails are indicated with a coloured border. Customise the border width and colour:

(setq dired-video-thumbnail-mark-border-width 4) ;; Border width in pixels

;; Change border colour via the face
(set-face-foreground 'dired-video-thumbnail-mark "blue")

Default Sorting

Set the default sort criteria and order:

(setq dired-video-thumbnail-sort-by 'name) ;; Options: name, date, size, duration
(setq dired-video-thumbnail-sort-order 'ascending) ;; Options: ascending, descending

Recursive Search

Control recursive directory searching behaviour:

(setq dired-video-thumbnail-recursive nil) ;; Always search recursively
(setq dired-video-thumbnail-auto-recursive t) ;; Auto-recursive when no local videos (default)

When dired-video-thumbnail-auto-recursive is enabled and the current directory has no video files but has subdirectories, the package automatically searches recursively.

ffmpeg Path

If ffmpeg/ffprobe are not in your PATH:

(setq dired-video-thumbnail-ffmpeg-program "/usr/local/bin/ffmpeg")
(setq dired-video-thumbnail-ffprobe-program "/usr/local/bin/ffprobe")

Example Configuration

(use-package dired-video-thumbnail
 :load-path "/path/to/dired-video-thumbnail/"
 :bind (:map dired-mode-map
 ("C-t v" . dired-video-thumbnail))
 :custom
 (dired-video-thumbnail-size 250)
 (dired-video-thumbnail-display-height 180)
 (dired-video-thumbnail-columns 5)
 (dired-video-thumbnail-timestamp "00:00:10")
 (dired-video-thumbnail-video-player nil) ;; Use system default
 (dired-video-thumbnail-mark-border-width 5)
 (dired-video-thumbnail-sort-by 'date)
 (dired-video-thumbnail-sort-order 'descending)
 (dired-video-thumbnail-auto-recursive t)
 :custom-face
 (dired-video-thumbnail-mark ((t (:foreground "orange")))))

Cache Management

Thumbnails are cached based on the file path and modification time. If you modify a video file, the thumbnail will be automatically regenerated on next view.

Video metadata (dimensions, duration) is also cached in memory to avoid repeated calls to ffprobe.

To manually clear the thumbnail cache:

M-x dired-video-thumbnail-clear-cache

Workflow Examples

Reviewing and Deleting Unwanted Videos

  1. Open a directory with videos in dired
  2. C-t v to open thumbnail view
  3. Browse thumbnails with n, p, SPC, or arrow keys
  4. Press D to delete individual videos, or mark with m and delete with x

Selecting Videos for Processing

  1. Open thumbnail view with C-t v
  2. Mark videos you want to process with m
  3. Press d to switch to dired
  4. Your marked videos are already selected in dired
  5. Use any dired command (C, R, !, etc.) on marked files

Quick Video Preview

  1. In dired, position cursor on a video file
  2. C-t v opens thumbnail view
  3. RET to play the video
  4. q to return to dired

Finding Large Videos

  1. Open thumbnail view with C-t v
  2. Press . to open the transient menu
  3. Press s then s to sort by size
  4. Press r to reverse order (largest first)
  5. Or use / then s to filter by size range

Finding Long Videos

  1. Press . to open the transient menu
  2. Press s then D to sort by duration
  3. Or use / then d to filter by duration range (e.g., 5:00 to 30:00)

Searching by Name

  1. Press . to open the transient menu
  2. Press / then n and enter a regexp pattern
  3. Only matching videos are shown
  4. Press c to clear the filter

Troubleshooting

Thumbnails not generating

  1. Ensure ffmpeg is installed: ffmpeg -version
  2. Check that ffmpeg is in your PATH or set dired-video-thumbnail-ffmpeg-program
  3. Try regenerating with g on a specific thumbnail

Placeholder showing instead of thumbnail

Some videos may fail to generate thumbnails if:

  • The video is corrupted
  • The timestamp is beyond the video duration (try setting dired-video-thumbnail-timestamp to nil)
  • ffmpeg doesn’t support the codec

Press g on the thumbnail to retry generation.

Video info not showing in header line

Ensure ffprobe is installed (it comes with ffmpeg). Set dired-video-thumbnail-ffprobe-program if it’s not in your PATH.

Marks not syncing with dired

Run M-x dired-video-thumbnail-debug to check if the dired buffer is properly associated. The output should show a live dired buffer reference.

Performance with many videos

The package processes up to 4 videos concurrently by default. For directories with hundreds of videos, initial thumbnail generation may take some time, but Emacs remains responsive and thumbnails appear as they complete.

Related Packages

  • image-dired - Built-in image thumbnail browser for dired
  • dirvish - A modern file manager for Emacs with preview support
-1:-- New package dired-video-thumbnail added to MELPA! (Post James Dyer)--L0--C0--2025-12-31T18:34:00.000Z

Kana: Extensibility: The "100% Lisp" Fallacy

So, I've seen some articles promoting Emacs-like editors written in Lisp languages, and one of the most common arguments seems to be: "it's written in This Lisp and also scriptable in This Lisp, and that gives it great extensibility." 1

It's not wrong, but I think it does overlook a few things.

By the way: Happy New Year!

Read more… (7 min remaining to read)

-1:-- Extensibility: The "100% Lisp" Fallacy (Post Kana)--L0--C0--2025-12-31T16:00:00.000Z

TAONAW - Emacs and Org Mode: I like the DU command in Dired

I had fun using the du command in Emacs this morning (that’s what I do when I don’t sleep well; judge away).

In Linux (and macOS), the DU command (I believe it stands for Disk Usage) is usually used to figure out what folders take up space on your hard drive.

While different GUI tools exist, they are not useful on encrypted drives on Linux, as they show the encrypted blocks instead of the directories, which don’t really tell you much (you could probably run those with escalated permissions, but I didn’t try).

For a gamer like me, it’s useful to see which games take up the most space. Here’s an example with a few useful flags, in the command line:

du -hs ~/.steam/steam/steamapps/common/* | sort -h

Shows me the following:

    4.0K    /home/jtr/.steam/steam/steamapps/common/Steam.dll
    76K     /home/jtr/.steam/steam/steamapps/common/Steam Controller Configs
    100K    /home/jtr/.steam/steam/steamapps/common/SteamLinuxRuntime
    248M    /home/jtr/.steam/steam/steamapps/common/Steamworks Shared
    657M    /home/jtr/.steam/steam/steamapps/common/SteamLinuxRuntime_soldier
    776M    /home/jtr/.steam/steam/steamapps/common/SteamLinuxRuntime_sniper
    1.4G    /home/jtr/.steam/steam/steamapps/common/Proton - Experimental
    3.4G    /home/jtr/.steam/steam/steamapps/common/Deep Rock Survivor
    3.5G    /home/jtr/.steam/steam/steamapps/common/Deep Rock Galactic
    11G     /home/jtr/.steam/steam/steamapps/common/Hades II
    21G     /home/jtr/.steam/steam/steamapps/common/Yakuza Kiwami
    82G     /home/jtr/.steam/steam/steamapps/common/Space Marine 2
    132G    /home/jtr/.steam/steam/steamapps/common/Helldivers 2

Some of the Steam-related components are not games, but… Helldivers 2 takes how much space?? Anyway.

A quick review of the options I used:

-hs for human readable (shows space in G for gigabyte and M for megabyte instead of writing in kilobytes) and summary (shows the top directory only)

~/.steam/steam/steamapps/common/* for the target path (this is where Steam stores the games in Linux, at least in Debian distros)

| to pipe the du command it into another command to read it better, in this case:

sort -h the sort command, which will sort it nicely again by order in human format. We need this last part if we want to see the directory in order, with the biggest one at the bottom.

Some places recommend using sort -hr, the additional r for reverse, which means in this case we will see the biggest directory at the top of the list. I don’t need it, because I want to see the biggest folder at the bottom, near the command line, which is where I’m going to focus next.

In Emacs, this command is easier use and works better thanks to Dired. Find a folder, mark it (m) in dired, and run dired-do-shell-command (!) and follow up with du -hs.

But since we’re in Emacs, and we might want to work with the results as text, we could use dired-do-async-shell-command (&). This will place the output in a temporary buffer we can work with (so we can save it to a text file, for example, with C-x C-w).

And here’s another thing I didn’t know: you can run these commands in Dired on multiple directories. Just mark several directories in Dired, and the resulting buffer will give you a list of all the directories you’ve marked. If you have this saved as a text buffer, it’s pretty easy to work withthe results (for example, save it as an org file and add headers telling you what you want to do with each directory).

By the way, even though it’s somewhat redundant with Dired’s default listing of files, you can also add the a option for du in this case ( for all) to display the files in the directories you’re viewing. This is useful in cases like the above, where you’re already working with the du command in Emacs and interested in looking at individual files as well, not just directories. Of course, you can just go in and list the files in Dired and open another Dired buffer with another directory listing files by size… this is Emacs, you have many ways to do whatever you want.

-1:-- I like the DU command in Dired (Post TAONAW - Emacs and Org Mode)--L0--C0--2025-12-31T14:46:01.000Z

Amin Bandali: The People of Emacs

GNU Emacs has been my primary computing environment of choice for over a decade. Emacs has enabled me to perform a wide array of tasks involving human and computer languages, such as reading and writing notes, emails, chats, programs, and more, all in a cohesive and consistent environment that I can tailor exactly to my needs and liking.

Coming from a Vim background, I started my Emacs journey trying some configuration frameworks that provided vi-like key bindings, and after a few Emacs bankruptcies, ended up with my current homegrown configuration that I wrote from scratch gradually over the last 7 years, with inspiration from the configurations of some folks who shared theirs publicly. Though my configuration has been mostly stable for a few years now and I consciously keep the number of external packages I use very small, I occasionally add small bits and pieces to my configuration when I’m inspired after learning about a neat feature or package on the blogs aggregated on Planet Emacslife, the messages sent to the Emacs mailing lists, or the videos from the annual EmacsConf conference.

I like getting a glimpse of other people’s worlds through the lens of their creative works such as writings, be it prose or Emacs Lisp. That’s only possible when people share freely, free as in freedom. I’m thankful to Richard Stallman for his foresight to imbue GNU Emacs with that freedom from the very beginning and for his lifelong fight for computer user freedom, and to the many other folks who have joined the free software movement since then and have fought the good fight.

I’ve been inspired and encouraged by many awesome Emacs people through the years. People like Corwin Brust with his joyful creative energy around Emacs and the road to software freedom, Sacha Chua and her philosophy of leading a life of learning, sharing, and scaling, Gopar and his enthusiasm for Emacs and its intersection with the Python world, folks like Protesilaos Stavrou and Greg Farough who discovered Emacs initially as non-programmers yet were enamoured by its embodiment of software freedom in practice and went on to integrate it into their everyday lives, and shoshin of the Cicadas cooperative at the intersection of humanity and technology sharing his passion for the human element and community by developing and contributing input methods for his ancestral language of Lakota to GNU Emacs. I’m deeply inspired by each of these wonderful people, and grateful for having known them and for each of their unique perspectives and life stories with which they have enriched my experience in Emacs and the free software world.

As wonderful and impactful as Emacs has been in the lives of the many who have come to know it throughout the decades that it’s been around, it would not have become what it has been, what it is today, and what it may become in the future without its community of passionate users and contributors. The People of Emacs are all of us. Here’s to many more of us, enjoying many more years of Emacs and software freedom together even if spread far apart.

Take care, and so long for now.

Inspired by the Emacs Carnival theme for this month, The People of Emacs. Thanks to George Jones for hosting.

-1:-- The People of Emacs (Post Amin Bandali)--L0--C0--2025-12-31T14:09:09.000Z

Sven Seebeck: And as of now I have completed the cycle once again

... and I am now, in terms of the tools that I am using to organize my life at the least, exactly where I have been a year ago.

Sure, I could now pretend that I have done a thorough yearly-reflection, got deep into myself and reflected about the year gone by, but simply pressing the "Random Note" button in Obsidian had been enough to stumble into the notes and journal entries that I took then.

A year ago I had been using:

  • Obsidian, for everything To Do, (Bullet-) journaling, note-taking, essentially for everything that involved typing words
  • Bearblog, for the blog (even though I then still had an instance of the blog on Micro.Blog and switched back and forth between them - or course I did)

In the time since then I used:

  • the Bullet Journal system in a Leuchttturm1917 book (both the "official" notebook and the normal ones)
  • the Bullet Journal system in the Traveler's Notebook with a variety of inlays to test which works best
  • the Bullet Journal system in the Hobonichi notebooks to "save time" as I don't have to set up any daily-/weekly-/monthly-layout
  • tried to settle on a journaling system in notebooks (Leuchtturm) or the Hobonichi 5 year one
  • Emacs mostly with Denote, but also tried to figure out to "optimize my workflows" in plain .txt files or even tried the .todotxt and .taskpaper thing
  • tried to do the .todotxt in a "random" text-editor (here Helix)
  • also tried to do the whole OBTF thing
  • moved the blog from Bear/Micro.blog to Ghost
  • copied/migrated/whatever notes from either of the notebooks to either Obsidian, Denote, or between any of the notebooks/journals
  • ...

Needless to say that all of the above involved (and in case my recent move back to Bear still involves) a STUPID AMOUNT OF TIME to migrate, to copy/paste and of course nothing is complete in terms of a continuing timeline.

It’s mostly a mess.

And I don’t want to even think about all the hassle that involved in finding the "right pen/ink/nib-combo" to write with in either of the notebooks.

And now, I am back where I have started, with essentially two applications: Obsidian and Bearblog!

This is a simple setup that does all that I need and that I can use anywhere I want (currently typing this on an Android tablet with an external keyboard, which feels rather comfortable). It goes without saying that I could have saved a stupid amount of time and money (notebooks and pens are really expensive) to have realized this already a year ago (or at any point in the years prior that) but that would have been too easy I guess.

100 days to Offload 27/100

-1:-- And as of now I have completed the cycle once again (Post Sven Seebeck)--L0--C0--2025-12-31T11:16:52.000Z

Christian Tietze: The People Who Got Me into Emacs

This is my entry to the December Emacs blog Carnival hosted by George Jones this month:

Emacs is the most people-centric technology I’ve ever used. I’m willing to bet that’s true for others as well.

Who are your “People of Emacs”?

Apart from a brief University stint where we used Emacs to write some C code on our SUN terminals, my first year of Emacs was shaped by three people:

  • Sascha Fast, my pal, then-roommate and Zettelkasten partner in crime, who found out about Org Mode and just had to get me on that after a brief experimentation to manage tasks and projects in plain text. Sascha had to force me to even give it a try because I didn’t want to spend time offboarding OmniFocus for projects and TextMate for writing, and now look at me. Look. At. Me. I’m almost a radicalized GNU fanboy who considers switching the daily driver from Mac to Arch Linux.

  • Xah Lee for his Emacs Tutorial – I basically learned all my Emacs Lisp baby steps thanks to his hand-curated manual and reference with examples. I gave his modal input, Xah Fly Keys a try and realized what you could do with key bindings in this weird editor: the single key bindings to move by word or line, and to change the case of the word at point was a huge life-saver when proof-reading Sascha’s book draft. That got me hooked. I’m still on a old version of these bindings. Then I also got into mechanical split-ergo keyboards thanks to his website, upgrading from my Kinesis Freestyle. And during the COVID pandemic lockdown of 2020, I had a lot of fun on his Discord.

  • Sacha Chua for everything on her site including cool Emacs hacks, then Planet Emacslife and Emacs News as the community pulse that sucked me into the weirder things you could do with Emacs. I didn’t know about movie playback and email writing before that. Thanks for this, I guess, now I’m the “everything in Emacs” weirdo :)

Since then, there have been many more. Some through packages, some through writing.

  • Protesilaos Stavrou, whose modus-themes v1 won me over and kept me loyal thanks to the deuteranopia variants.
  • Jeremy Friesen of Take on Rules , Mickey Petersen of masteringemacs.org, Jon Snader of irreal.org, for their blogs, among many others!
  • I, uh, ‘enjoy’ more than 200 use-package expressions in my init file … thanks everyone for sharing your work with the world, as free/libre software, and making Emacs better for all of us.

Hire me for freelance macOS/iOS work and consulting.

Buy my apps.

Receive new posts via email.

-1:-- The People Who Got Me into Emacs (Post Christian Tietze)--L0--C0--2025-12-31T07:45:39.000Z

Sacha Chua: Emacs Lisp: Making a multi-part form PUT or POST using url-retrieve-synchronously and mm-url-encode-multipart-form-data

I spent some time figuring out how to submit a multipart/form-data form with url-retrieve-synchronously with Emacs Lisp. It was surprisingly hard to find an example of working with multi-part forms. I had totally forgotten that I had figured something out last year: Using Emacs Lisp to export TXT/EPUB/PDF from Org Mode to the Supernote via Browse and Access. Well, I still had to spend some extra time dealing with the quirks of the PeerTube REST API. For toobnix.org, having = in the boundary didn't seem to work. Also, since I had newlines (\n) in my data, I needed to replace all of them with \r\n, which I could do with encode-coding-string and the utf-8-dos coding system. So here's an example I can use for the future:

(let* ((boundary (format "%s%d" (make-string 20 ?-) (time-to-seconds)))
       (url-request-method "PUT")       ; or POST
       (url-request-extra-headers
        (append
         (list
          (cons "Content-Type"
                (concat "multipart/form-data; boundary=" boundary))
          ;; put any authentication things you need here too, like
          ;; (cons "Authorization" "Bearer ...")
          )
         url-request-extra-headers
         nil))
       (url-request-data
        (mm-url-encode-multipart-form-data
         `(("field1" .
            ,(encode-coding-string "Whatever\nyour value is" 'utf-8-dos)))
         boundary))
       (url "http://127.0.0.1"))        ; or whatever the URL is
    (with-current-buffer (url-retrieve-synchronously url)
      (prog1 (buffer-string)
        (kill-buffer (current-buffer)))))

I've also added it to my local elisp-demos notes file (see the elisp-demos-user-files variable) so that helpful can display it when I use C-h f to describe mm-url-encode-multipart-form-data.

Here I'm using it to update the video description in emacsconf-toobnix.el:

emacsconf-toobnix-update-video-description: Update the description for TALK.
(defun emacsconf-toobnix-update-video-description (talk &optional type)
  "Update the description for TALK.
TYPE is 'talk or 'answers."
  (interactive
   (let ((talk (emacsconf-complete-talk-info)))
     (list
      talk
      (if (plist-get talk :qa-toobnix-url)
          (intern (completing-read "Type: " '("talk" "answers")))
        'talk))))
  (setq type (or type 'talk))
  (let* ((properties
          (pcase type
            ('answers (emacsconf-publish-answers-video-properties talk 'toobnix))
            (_ (emacsconf-publish-talk-video-properties talk 'toobnix))))
         (id
          (emacsconf-toobnix-id-from-url
           (plist-get talk (pcase type
                             ('answers :qa-toobnix-url)
                             (_ :toobnix-url)))))
         (boundary (format "%s%d" (make-string 20 ?-)
                           (time-to-seconds)))
         (url-request-method "PUT")
         (url-request-extra-headers
          (cons (cons "Content-Type"
                      (concat "multipart/form-data; boundary=" boundary))
                (emacsconf-toobnix-api-header)))
         (url-request-data
          (mm-url-encode-multipart-form-data
                     `(("description" .
                        ,(encode-coding-string
                          (plist-get properties :description)
                          'utf-8-dos)))
                     boundary))
         (url (concat "https://toobnix.org/api/v1/videos/" id)))
    (with-current-buffer (url-retrieve-synchronously url)
      (prog1 (buffer-string)
        (kill-buffer (current-buffer))))))

View org source for this post

You can comment on Mastodon or e-mail me at sacha@sachachua.com.

-1:-- Emacs Lisp: Making a multi-part form PUT or POST using url-retrieve-synchronously and mm-url-encode-multipart-form-data (Post Sacha Chua)--L0--C0--2025-12-31T00:42:50.000Z

Protesilaos Stavrou: Emacs: modus-themes version 5.2.0

I just published the latest stable release of the Modus themes. The change log entry is reproduced further below. For any questions, you are welcome to contact me. I will now work to apply these same changes to emacs.git, so please wait a little longer for the updates to trickle down to you.


5.2.0 on 2025-12-31

This version fixes some bugs, adds a new feature for those who want to derive a theme from Modus, and makes other small quality-of-life refinements.

The modus-themes-with-colors should work at all times

In the transition to version 5.0.0, I inadvertently introduced regressions to the behaviour of the modus-themes-with-colors macro. This macro let binds the current theme’s palette around arbitrary Elisp expressions, which allows users to access the named colours therein. In versions 5.0.0 and 5.1.0 the macro could not read variables defined outside its scope. Users needed to write an eval around it, which I did not like. Now the macro should not require such workarounds: it basically is a let that should work as expected everywhere.

This was fixed over a series of Git commits related to issue 170: https://github.com/protesilaos/modus-themes/issues/170. Thanks to Alexandr Semenov and realazy for reporting the problems and testing my revisions.

The modus-themes-generate-palette function to quickly get a palette

Users or package developers who want to create a theme on top of Modus can now get a kickstart by defining their palette with the help of the new modus-themes-generate-palette function. This function is meant to return a complete palette, given a list of basic colours. Users can thus experiment with their new theme while knowing that what they got contains all the definitions; definitions that they may then modify further (e.g. to define different semantic mappings than the defaults such as, for example, to have (fg-heading-1 red-warmer) instead of what originally is (fg-heading-1 fg-main)).

I have written extensive documentation in the manual, which includes a complete example of a Solarized theme that is built on top of Modus. If you have any questions, you are welcome to contact me.

Convenience commands to select only dark or light themes

The commands modus-themes-select-dark and modus-themes-select-light use minibuffer completion to load a theme. The completion candidates are filtered to only dark or light themes, respectively.

This is effectively the same as calling the command modus-themes-select with a prefix argument (C-u by default).

Remember that we also have the commands modus-themes-load-random, modus-themes-load-random-dark, and modus-themes-load-random-light. Otherwise use the command modus-themes-rotate.

Improved prompt for theme selection

The minibuffer prompt used by the various Modus commands to select a theme now has a grouping function in place: it shows the current theme at the top and then all other themes grouped by their dark or light background. This makes it easier to find a relevant theme, especially if lots of them are present, such as when modus-themes-include-derivatives-mode is enabled and relevant packages/themes are available (e.g. my ef-themes and standard-themes).

Semantic colours for transient.el (e.g. in Magit)

The transient.el concept of “semantic colours” is now supported. This is used by default in Magit to denote the different types of keys, such as those that exit the transient, keep it active, move to another transient, and the like. Users who prefer the old style where all key bindings looked the same must customise the user option transient-semantic-coloring.

Note that the deuteranopia- and tritanopia- optimised themes adapt gracefully to such “semantics”, owning to relevant internal refinements I made. Those themes cannot rely on the full colour spectrum to communicate such nuances.

All hl-todo-mode faces use a bold weight if appropriate

When the user option modus-themes-bold-constructs is set to a non-nil value, then all keywords that hl-todo-mode highlights will be rendered in a bold weight (technically, they inherit the bold face). This is how we were doing it before until I undid it by mistake. Thanks to Dominik Schrempf for reporting the bug in issue 177: https://github.com/protesilaos/modus-themes/issues/177.

Theme-sensitive colours for Gnus mail groups

The Gnus mail groups no longer have hardcoded colour values. They will look different depending on the current Modus theme.

Faces that set a :box attribute handle unspecified colours

I updated all faces that use a :box attribute to account for the scenario of a user writing palette overrides that unset the relevant colour. Thanks to JD Smith for reporting a bug along those lines in issue 9 of my standard-themes repository (they are derived from the modus-themes, hence the changes here): https://github.com/protesilaos/standard-themes/issues/9.

The calendar-today and org-date-selected faces are disambiguated

These two faces are no longer using the same styles. This is because they can appear in the same buffer. Thanks to Rudolf Adamkovič for discussing this with me in the context of the same change for my doric-themes (issue 20 in doric-themes.git): https://github.com/protesilaos/doric-themes/issues/20.

The Modus “current theme” respects multiple enabled themes

The Modus concept of “current theme” respects the user’s choice for multiple themes loaded at once. It will return the first Modus theme even if it is not at the front of the list.

[ Emacs will load multiple themes by default, which leads to awkward colour combinations, unless you know what you are doing—as such all the Modus commands that load a theme will disable all others, subject to the user option modus-themes-disable-other-themes. ]

Thanks to Pierre Téchoueyres for reporting the scenario where multiple other themes are loaded on top of a Modus theme. This was done in issue 182: https://github.com/protesilaos/modus-themes/issues/182.

Also thanks to Pierre for covering another snippet that I had missed. This was done in pull request 184: https://github.com/protesilaos/modus-themes/pull/184.

Pierre has assigned copyright to the Free Software Foundation.

Fixed symbol of inherited AUCTeX face

There was a typo which caused an error. Thanks to Rudolf Adamkovič for the patch and also for providing a relevant unit test. This was done in pull request 188: https://github.com/protesilaos/modus-themes/pull/188.

Rudolf has assigned copyright to the Free Software Foundation.

Miscellaneous

  • Thanks to Basil L. Contovounesios for simplifying a couple of expressions. This was done in pull request 190: https://github.com/protesilaos/modus-themes/pull/190. Basil has assigned copyright to the Free Software Foundation.

  • The faces of the built-in completion-preview-mode are now supported. Thanks to Kevin Fleming for asking me about this in issue 178: https://github.com/protesilaos/modus-themes/issues/178.

  • Several faces that had a strike-through effect when they did not really need it are revised to use a wavy underline instead. The idea is to let the text be readable at all times, regardless of the effective font family. With the strike-through effect, some fonts completely obscure the underlying text.Thanks to Morgan Willcock for discussing with me the use of the strike-through style in issue 169: https://github.com/protesilaos/modus-themes/issues/169.

  • All symbol-overlay faces are unique, fixing a mistake I had done before.

  • The org-dispatcher-highlight, which is used to highlight the keys of the Org export interface, now uses the appropriate foreground colour and is always rendered in a bold weight.

  • The org-habit faces no longer call the function readable-foreground-color. This is because that function does not work if the theme is loaded via the early-init.el. Thanks to Gaston Cabotin for reporting the problem in issue 174: https://github.com/protesilaos/modus-themes/issues/174.

  • The gnus-button, which Gnus uses in all sorts of places to mark some text as clickable, is styled with a less intense underline and will no longer follow the style of links, including possible palette overrides. This way, Gnus article buffers will not have visual noise. Thanks to Morgan Willcock for discussing this with me in issue 140: https://github.com/protesilaos/modus-themes/issues/140.

-1:-- Emacs: modus-themes version 5.2.0 (Post Protesilaos Stavrou)--L0--C0--2025-12-31T00:00:00.000Z

Irreal: Never Try Emacs

A couple of weeks ago, I wrote about Tsoding’s video on the annoying usefulness of Emacs. His conclusion was that you should stay away from Emacs because once you try it, you will never be able to break free.

Now Valigo has his own video up that reaches the same conclusion. For him, the main attraction of Emacs is it’s extensibility and customizability. The reason for that, of course, is that the Emacs executable is basically a Lisp image with the source code available from within that image. That means that you can, if needed, reach into the guts of Emacs and change just about any aspect of Emacs on the fly. The only exception is the small C core and even that has the source code available from within Emacs but you’d have to recompile Emacs to change it.

One telling example that Valigo gives is to ask Emacs for the definition of the j key. Because he has evil mode enabled, Emacs reports that j runs the command evil-next-line. Then he disables evil mode and repeats the experiment. This time Emacs reports that j runs the self-insert-command to add a j to the buffer. The point is that the help command adapts itself on the fly to reflect the current state of the system.

Because of all this customizability, Emacs use is addictive. Once you start, you can’t stop. Like Tsoding, Valigo says that Emacs is old and crufty but he can’t escape because nothing else is as useful.

I get that Tsoding an Valigo are probably writing tongue in cheek but really, if Emacs is so useful you can’t live without it, why are you complaining? You can, after all, change anything you don’t like.

-1:-- Never Try Emacs (Post Irreal)--L0--C0--2025-12-30T17:27:05.000Z

Sacha Chua: J'apprécie les gens d'Emacs / I appreciate the people of Emacs

En français

J'aime bien l'éditeur Emacs. C'est si personnalisable. Comme il est tellement personnalisable, quand on lit les fonctions, on peut avoir un aperçu de la vie des autres, de leurs objectifs, et des défis dont ils sont venus à bout avec les fonctions. En lisant le code, on découvre une petite partie de leur univers. Parfois on peut rencontrer des gens sur les blogs (l'agrégateur Planet Emacslife est très utile), les vidéos, les réunions virtuelles ou la conférence annuelle EmacsConf. J'aime particulièrement la série Prot Asks où il converse avec quelques personnes de tout et de rien. Les gens qui sont intéressés par Emacs sont toujours également intéressés par d'autres choses passionnantes. Même s'ils sont dispersés physiquement et sont occupés, ce qui fait que la coopération est très rare, j'apprécie qu'ils existent, ils créent, ils partagent…

Qui m'a le plus influencé ? C'est probablement John Wiegley. Son Planner Mode m'a aidée à organiser mes notes à l'université et m'a inspirée à l'utiliser et à faire du bénévolat. Son Ledger CLI m'a aidée à budgétiser, ce qui m'a permis cette expérience de la vie indépendante. Son idée pour Emacs News continue de me connecter à la communauté Emacs. J'ai pu le rencontrer en 2013 à l'EmacsConf à Londres. Quelle chance !

Beaucoup d'autres personnes me réchauffent le cœur. J'apprécie aussi Jon Snader pour toujours écrire beaucoup de commentaires sur son blog Irreal, et j'apprécie beaucoup de blogueurs, créateurs de vidéos, et ceux qui partagent des liens. J'apprécie kensanata qui entretient EmacsWiki, les modérateurs du salon #emacs et d'autres canaux d'IRC, et les bénévoles qui font de la modération sur les listes de diffusion. Ils font énormément de travail en coulisses pour rendre l'expérience plus plaisante pour nous. J'apprécie Eli Zaretskii et les autres mainteneurs d'Emacs, yantar92 qui entretient Org Mode, et les mainteneurs d'autres packages. Je suis toujours étonnée de voir que les gens partagent leur temps avec nous.

Bien sûr, il y a des difficultés. L'intelligence artificielle peut aider les gens à comprendre et à créer beaucoup de choses, mais ça peut aussi nous inonder de contenu insipide. Étrangement, certaines personnes ont du mal à être polies. Mais je ne dois pas leur laisser gâcher ma reconnaissance pour le reste. J'aime bien lire les billets de blog sur Emacs et les autres sujets, donc je dois ajouter plus de blogs à mon agrégateur.

Ensuite ? Je travaille lentement à copier les discussions d'EmacsConf. Je continue de publier le bulletin Emacs News. Un jour je veux enregistrer des vidéos et écrire des billets. Cette année semble plus difficile pour les gens : plus occupés, plus stressés… Peut-être qu'EmacsConf devrait aussi s'adapter aux temps changeants. Je me demande ce qui pourrait me faciliter la tâche. Cela me stresse surtout à cause de l'administration système, la conversion des vidéos et la gestion de plusieurs choses à la fois pendant la conférence, particulièrement en direct sur scène. Si je limite ça à une piste, elle sera peut-être plus gérable. Nous nous organisons ensemble.

Hourra pour les gens d'Emacs!

In English

I really like the Emacs editor. It's so customizable. Because it's so customizable, when you read functions, you can get a glimpse into other people's lives, their goals, and the challenges they've overcome with those functions. By reading the code, you discover a small part of their world. Sometimes you can meet people on blogs (the Planet Emacslife aggregator is very useful), videos, virtual meetings, or at the annual EmacsConf conference. I particularly like the Prot Asks series where he chats with various people about everything. People who are interested in Emacs are always also interested in other interesting things. Even if they're spread far apart and pretty busy, which makes collaboration very rare, I appreciate that they exist, they create, they share…

Who has influenced me the most? Probably John Wiegley. His Planner Mode helped me organize my notes in university, and it inspired me to use Emacs and volunteer. His Ledger CLI helped me budget, which allowed me to experiment with this independent life. His idea for Emacs News continues to connect me to the Emacs community. I got to meet him in 2013 at EmacsConf in London - how lucky!

Many other people warm the cockles of my heart. I appreciate Jon Snader for writing lots of comments on his Irreal blog. I'm grateful for bloggers, video creators, and people who share links. I appreciate kensanata for maintaining EmacsWiki, the moderators of the #emacs channel and other IRC channels, and the volunteers who moderate the mailing lists. They do a tremendous amount of work behind the scenes to make the experience more enjoyable for us. I appreciate Eli Zaretskii and other Emacs maintainers, yantar92 who maintains Org Mode, and the maintainers of other packages. I'm always amazed that people share their time with us.

Of course, there are challenges. Artificial intelligence can help people understand and create many things, but it can also flood us with slop. Some people struggle with politeness. But I shouldn't let that spoil my appreciation for everything else. I enjoy reading blog posts about Emacs and other topics, so I should probably add more blogs to my aggregator.

What's next? I'm slowly working on copying the EmacsConf discussions. I'll continue putting together Emacs News. Someday, I'd like to record more videos and write more blog posts. This year seems harder for people: busier, more stressed… Perhaps EmacsConf should also adapt to the changing times. I'm wondering what could make things easier for me. I get particularly stressed because of system administration, video conversion, and managing multiple things at once during the conference, particularly on stream. If I limit it to one track, it might be more manageable. We'll work it out together.

Hooray for the people of Emacs!

This was inspired by the Emacs Carnival theme for December, The People of Emacs. Thanks to George Jones for hosting!

(Thanks to bandali for helping me fix some typos!)

View org source for this post

You can comment on Mastodon or e-mail me at sacha@sachachua.com.

-1:-- J'apprécie les gens d'Emacs / I appreciate the people of Emacs (Post Sacha Chua)--L0--C0--2025-12-30T01:04:48.000Z

James Endres Howell: Org mode, LaTeX, and tagged PDFs: producing accessible documents

Many of us in higher education use Emacs to produce course materials, and these must conform to the accessibility requirements of the Americans with Disabilities Act. We often export Org mode documents to LaTeX to produce very nice PDF output. But until very recently, LaTeX could not produce tagged PDFs that conform to the PDF/UA accessibility standard. Until how recently? Only starting with TeX Live 2025.

In short, creating accessible PDFs from Org mode means having to install this new version of TeX Live (the massive, annually-updated software package of all things TeX and LaTeX). I am still on Debian 12, whose stable repositories provide TeX Live 2022. Even the most recent Ubuntu repositories only appear to package TeX Live 2024. The advice from the TeX Live maintainers is to remove the .deb installation and install TeX Live 2025 fresh, as below.

The steps presented here might work also for Ubuntu and later versions of Debian, but that’s only a wildly hopeful speculation that I cannot test.

Installing TeX Live 2025 on Debian 12

Set aside ninety minutes for this installation!

I successfully installed TeX Live 2025 on multiple machines running Debian 12 with the following steps. Note that I closely followed the instructions from the Debian wiki and the TeX Live website on TUG. I have added some clarifying remarks that might be helpful.

Uninstall the old .deb version of TeX Live

  sudo apt autopurge texlive*

Just uninstalling TeXLive takes a couple minutes. If you don’t remember when you installed these packages, wait until you see how many dependencies they have. TeX Live is the biggest package you’re likely to find installed on any Linux system. (No, I probably don’t need every module, but it has always been much easier to install the whole monolith than to deal with missing dependencies.)

Install TeX Live 2025 from CTAN

cd /tmp
wget https://mirror.ctan.org/systems/texlive/tlnet/install-tl-unx.tar.gz
tar xvf install-tl-unx.tar.gz
cd install-tl-2*
sudo perl ./install-tl --no-interaction

The instructions have a cute little note # may take several hours to run which prompted my LOL post to Mastodon. (Again, I choose to install everything here, 4,958 TeX Live packages. In fact the instructions presume a full install.)

It took about an hour on my newish desktop with Ethernet, almost two hours on my ten-year-old laptop over Wi-Fi.

Add the binary directory to your PATH

It will be of the form /usr/local/texlive/2025/bin/PLATFORM, likely /usr/local/texlive/2025/bin/x86_64-linux.

Be certain that root can run the TeX Live binaries too! The way I did so was to edit /etc/profile, find the appropriate lines setting the path, and edit them to add that directory. The result looks like so:

if [ "$(id -u)" -eq 0 ]; then
  PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/texlive/2025/bin/x86_64-linux"
else
  PATH="/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games:/usr/local/texlive/2025/bin/x86_64-linux"
fi
export PATH

Install a few minimal base packages via apt

  sudo apt install tex-common texinfo lmodern 

Configure a “dummy package” and install via apt

This part is opaque to me. It appears we are using something called equivs to wrap our raw-dog TeX Live install into a .deb in order to trick the package manager into playing nice with it, but I am innocent of the mechanism here.

  sudo apt install equivs
  mkdir /tmp/tl-equivs && cd /tmp/tl-equivs
  equivs-control texlive-local

We are now in a temporary directory that contains a configuration file for our dummy package called texlive-local. Replace the contents of that file with the following:

Section: misc
Priority: optional
Standards-Version: 4.1.4

Package: texlive-local
Version: 2025.99999999-1
Maintainer: you <you@yourdomain.example.org>
Provides: asymptote, chktex, cm-super, cm-super-minimal, context,
 dvidvi, dvipng, dvisvgm, feynmf, fragmaster, jadetex, lacheck, 
 latex-cjk-all, latex-cjk-chinese, latex-cjk-chinese-arphic-bkai00mp,
 latex-cjk-chinese-arphic-bsmi00lp, latex-cjk-chinese-arphic-gbsn00lp,
 latex-cjk-chinese-arphic-gkai00mp, latex-cjk-common, latex-cjk-japanese,
 latex-cjk-japanese-wadalab, latex-cjk-korean, latex-cjk-thai, latexdiff,
 latexmk, latex-sanskrit, lcdf-typetools, lmodern, luatex,
 musixtex, preview-latex-style, ps2eps, psutils, purifyeps, t1utils,
 tex4ht, tex4ht-common, tex-gyre, texinfo, texlive, texlive-base,
 texlive-bibtex-extra, texlive-binaries, texlive-common, texlive-extra-utils,
 texlive-fonts-extra, texlive-fonts-extra-doc, texlive-fonts-recommended,
 texlive-fonts-recommended-doc, texlive-font-utils, texlive-formats-extra,
 texlive-games, texlive-humanities, texlive-humanities-doc, 
 texlive-lang-all, texlive-lang-arabic, texlive-lang-cjk, texlive-lang-cyrillic,
 texlive-lang-czechslovak, texlive-lang-english, texlive-lang-european,
 texlive-lang-japanese, texlive-lang-chinese, texlive-lang-korean,
 texlive-lang-french, texlive-lang-german, texlive-lang-greek,
 texlive-lang-italian, texlive-lang-other,
 texlive-lang-polish, texlive-lang-portuguese, texlive-lang-spanish,
 texlive-latex-base, texlive-latex-base-doc, texlive-latex-extra,
 texlive-latex-extra-doc, texlive-latex-recommended,
 texlive-latex-recommended-doc, texlive-luatex, texlive-math-extra,
 texlive-metapost, texlive-metapost-doc, texlive-music,
 texlive-pictures, texlive-pictures-doc, texlive-plain-generic,
 texlive-pstricks, texlive-pstricks-doc, texlive-publishers,
 texlive-publishers-doc, texlive-science, texlive-science-doc, texlive-xetex,
 thailatex, tipa, tipa-doc, xindy, xindy-rules
Depends:
Architecture: all
Description: My local installation of TeX Live 2025.
 A full "vanilla" TeX Live 2025
 http://tug.org/texlive/debian#vanilla

And finally:

  equivs-build texlive-local
  sudo dpkg -i texlive-local_2025.99999999-1_all.deb

Producing a compliant PDF

Reality check: let’s make sure we can in fact use our new TeX Live install to produce a minimal accessible PDF. Note that the polyglossia package is a replacement for the babel package that adds accessibility features. But the \DocumentMetadata{} command does the heavy lifting for producing tagged PDFs. It must come before \documentclass{}.

  \DocumentMetadata{
    lang        = en,
    pdfstandard = ua-2,
    pdfstandard = a-4f,
    tagging     = on
  }

  \documentclass{article}

  \usepackage{polyglossia}
  \setdefaultlanguage[variant=US]{english}

  \begin{document}

  \section{Lorem ipsum}

  I know I promised US English but here's some nonsense
  not-quite-Latin. Est eveniet accusamus dolor et. Possimus fugit
  consectetur alias iure suscipit facere est exercitationem. Sed enim
  sapiente atque.

  Voluptas et tempora est. Recusandae velit qui nesciunt. Molestiae excepturi
  occaecati doloribus. Eum sunt optio aut consequatur doloremque. Quo eveniet
  rerum aut dicta impedit quia ut autem. Dolor nisi qui architecto sunt.

  Corporis quidem aut natus est quidem pariatur. Error aut repellat nobis
  velit corporis voluptatem. Libero hic nesciunt omnis ut quam minus soluta.
  Ad quo culpa facere pariatur voluptas et quis nostrum. Pariatur tempore
  ipsum voluptatibus iusto repudiandae. Earum ea quo saepe autem et. Eum
  ratione eaque non dolorum ut fugit vitae dolorum.

  \end{document}

Save this file as minimal.tex and run

lualatex minimal.tex

(On its first run, it will take a minute to build a font names database.)

If it doesn’t work, well, I guess you have some debugging to do? Note that the LuaLaTeX engine is required for all the accessibilty new hotness. (TIL it has been the recommended engine for a year.)

Configuring Org LaTeX export to produce compliant PDFs

Note that I am stealing everything here from Kenny Ballou’s comprehensive and excellent post to which I have little to add.

Add these expressions to your Emacs configuration.

Set the default engine to lualatex but allow individual files to override it (in case you have some weird old files, I suppose).

  (setq org-latex-compiler "lualatex")

  ;;; %latex gets replaced with org-latex-compiler
  ;;; OR overridden by the #+LATEX_COMPILER header
  (setq org-latex-pdf-process
        '("latexmk -f -pdf -%latex -interaction=nonstopmode -shell-escape -output-directory=%o %f"))

Insert the \DocumentMetadata{} command before \documentclass{} for the article document type. Note that as written, only the article documentclass is defined for export. You might already have a org-latex-classes declaration in your config, in which case you should modify it to replace the article class definition with this one.

  (defvar org-latex-metadata "\\DocumentMetadata{lang = en, pdfversion = 2.0, pdfstandard = ua-2, pdfstandard = a-4}"
    "LaTeX preamble command to specify PDF accessibility metadata.\nIt must appear before the \\documentclass{} declaration.")

  (setq org-latex-classes
        `(
          ("article" ,(concat org-latex-metadata "\n" "\\documentclass[11pt]{article}")
           ("\\section{%s}" . "\\section*{%s}")
           ("\\subsection{%s}" . "\\subsection*{%s}")
           ("\\subsubsection{%s}" . "\\subsubsection*{%s}")
           ("\\paragraph{%s}" . "\\paragraph*{%s}")
           ("\\subparagraph{%s}" . "\\subparagraph*{%s}"))
        ))

I have not yet attempted to make the corresponding definition for Beamer. When I get it to work I will update this post.

(Note finally that the testphase key in the \DocumentMetadata{} command has been deprecated, so I’ve removed it.)

Here is a minimal Org document to test export:

#+title: A nice title for a PDF
#+subtitle: a test of tagged PDF accessibility
#+latex_header: \usepackage{polyglossia}
#+latex_header: \setdefaultlanguage[variant=US]{english}

Est eveniet accusamus dolor et. Possimus fugit consectetur alias
iure suscipit facere est exercitationem. Sed enim sapiente atque.

* Heading 1

** Subhead 1.1
Voluptas et tempora est. Recusandae velit qui nesciunt. Molestiae
excepturi occaecati doloribus.

** Subhead 1.2
Eum sunt optio aut consequatur doloremque. Quo eveniet rerum aut
dicta impedit quia ut autem. Dolor nisi qui architecto sunt.

* Heading 2

** Subhead 2.1
Corporis quidem aut natus est quidem pariatur. Error aut repellat
nobis velit corporis voluptatem. Libero hic nesciunt omnis ut quam
minus soluta. Ad quo culpa facere pariatur voluptas et quis nostrum.

** Subhead 2.2
Pariatur tempore ipsum voluptatibus iusto repudiandae. Earum ea quo
saepe autem et. Eum ratione eaque non dolorum ut fugit vitae
dolorum.

Dealing with remaining issues

It’s important to remember that the accessibility functionality in LaTeX is still new and incomplete. But it’s a big step from zero to very good.

For me and everyone who posts our course materials to Canvas, the touchstone is the “Ally Accessibility Checker,” which is also not perfect. For instance, it insists that PDFs produced by LaTeX are missing a title when they are not. If I can get to the bottom of that bug, I’ll update this post.

You can respond on the original Mastodon thread.

-1:-- Org mode, LaTeX, and tagged PDFs: producing accessible documents (Post James Endres Howell)--L0--C0--2025-12-30T00:33:00.000Z

Alvaro Ramirez: My 2025 review as an indie dev

In 2024, I took the leap to go indie full-time. By 2025, that shift enabled me to focus exclusively on building tools I care about, from a blogging platform, iOS apps, and macOS utilities, to Emacs packages. It also gave me the space to write regularly, covering topics like Emacs tips, development tutorials for macOS and iOS, a few cooking detours, and even launching a new YouTube channel.

The rest of this post walks through some of the highlights from 2025. If you’ve found my work useful, please consider sponsoring.

Off we go…

Launched a new blogging service

For well over a decade, my blogging setup consisted of a handful of Elisp functions cobbled together over the years. While they did the job just fine, I couldn't shake the feeling that I could do better, and maybe even offer a blogging platform without the yucky bits of the modern web. At the beginning of the year, I launched LMNO.lol. Today, my xenodium.com blog proudly runs on LMNO.lol.

LMNO.lol blogs render pretty much anywhere (Emacs and terminals included, of course).

2026 is a great year to start a blog! Custom domains totally welcome.

A journaling/note-taking app that feels like tweeting

Sure, there are plenty of journaling and note-taking apps out there. For one reason or another, none of them stuck for me (including my own apps). That is, until I learned a thing or two from social media.

With that in mind, Journelly was born: like tweeting, but for your eyes only. With the right user experience, I felt compelled to write things down all the time. Saving to Markdown and Org markup was the mighty sweet cherry on the cake.

Journelly app icon
Download on App Store button link

Let's learn Japanese

As a Japanese language learning noob, what better way to procrastinate than by building yet another Kana-practicing iOS app? Turns out, it kinda did the job.

Here's mochi invaders, a fun way to practice your Kana

Mochi Invaders app icon
Download on App Store button link

A new Emacs-native AI/LLM agent (powered by ACP)

2025 brought us the likes of Claude Code, Gemini CLI, Goose, Codex, and many more AI/LLM CLI agents. While CLI utilities have their appeal, I wanted a native Emacs integration, so I simply ignored agents for quite some time.

I was initially tempted to write my own Emacs agent, but ultimately decided against it. My hope was that agent providers would somehow converge to offer editor integration, so I could focus on building an Emacs integration while leveraging the solid work from many teams producing agents. With LLM APIs historically fragmented, my hope for agent convergence seemed fairly far-fetched.

To my surprise, ACP (Agent Client Protocol) was announced by Zed and Google folks. This was the cue I had been waiting for, so I set out to build acp.el, a UX agnostic elisp library, followed by an actual client: agent-shell.

I'm fairly happy with how agent-shell's been shaping up. This is my most popular package from 2025, receiving lots of user feedback. If you're curious about the feature-set, I've written about agent-shell's progress from early on:

chatgpt-shell improvements

While agent-shell is the new kid on the block, chatgpt-shell received DeepSeek, Open Router, Kagi, and Perplexity support, in addition to a handful of other improvements and bugfixes.

A new YouTube channel

While most of what I share usually ends up as a blog post, this year I decided to try something new. I started the Bending Emacs YouTube channel and posted 8 episodes:

Enjoying the content? Leave me a comment or subscribe to my channel.

My decade with org (Emacs Carnival)

While I enthusiastically joined the Emacs Carnival, I didn't quite manage monthly posts. Having said that, when I did participate, I went all in, documenting my org experience over the last decade. Ok well… I also joined in with my elevator pitch ;)

Awesome Emacs on macOS

While migrating workflows to Emacs makes them extra portable across platforms, I've also accumulated a bunch of tweaks enhancing your Emacs experience on macOS.

EverTime for macOS

While we're talking macOS, I typically like my desktop free from distractions, which includes hiding the status bar.

Having said that, I don't want to lose track of time, and for that, I built EverTime, an ever-present floating clock (available via Homebrew).

A new time zone Emacs package

Emacs ships with a perfectly functional world clock, available via M-x world-clock, but I wanted a little more, so I built time-zones.

Also covered in:

A new WhatsApp Emacs client

For better or worse, I rely on WhatsApp Messenger. Migrating to a different client or protocol just isn't viable for me, so I did the next best thing and built wasabi, an Emacs client ;)

While not a trivial task, wuzapi and whatsmeow offered a huge leg up. I wanted tighter Emacs integration, so I upstreamed a handful of patches to add JSON-RPC support, plus easier macOS installation via Homebrew.

Details covered in a couple of posts:

Spiff that shell up

While both macOS and iOS offer APIs for generating URL previews, they also let you fetch rich page metadata. I built rinku, a tiny command-line utility, and showed how to wire it all up via eshell for a nifty shell experience.

With similar eshell magic, you can also get a neat cat experience.

At one with your code

I always liked the idea of generating some sort of art or graphics from a code base, so I built one, a utility to transform images into character art using text from your codebase. Also covered in a short blog post.

Screencast converting image to source code art

Emacs can trim your videos too

Emacs is just about the perfect porcelain for command-line utilities. With little ceremony, you can integrate almost any CLI tool. Magit remains the gold standard for CLI integration.

While trimming videos doesn't typically spring to mind as an Emacs use case, I was pleasantly surprised by the possibilities.

Landing Emacs patches upstream

While I've built my fair share of Emacs packages, I'm still fairly new at submitting Emacs features upstream. This year, I landed my send-to (aka sharing on macOS) patch. While the proposal did spark quite the discussion, I'm glad I stuck with it. Both Eli and Stefan were amazingly helpful.

This year, I also wanted to experiment with dictating into my Emacs text buffers, but unfortunately dictation had regressed in Emacs 30.

Bummer. But hey, it gave me a new opportunity to submit another patch upstream.

Ready Player improvements

Ready Player, my Emacs media-playing package received further improvements like starring media (via Emacs bookmarks), enabling further customizations, and other bug fixes. Also showcased a tour of its features.

GitHub activity

  • Commits: 1,095
  • Issues created: 37
  • PRs reviewed: 106
  • Average commits per day: ~3

New GitHub projects

  • EverTime - An ever present clock for macOS
  • acp.el - An ACP implementation in Emacs lisp
  • agent-shell - A native Emacs buffer to interact with LLM agents powered by ACP
  • diverted - Identify temporary Emacs diversions and return to original location
  • emacs-materialized-theme - An Emacs theme derived from Material
  • homebrew-evertime - EverTime formula for the Homebrew package manager
  • homebrew-one - Homebrew recipe for one
  • homebrew-rinku - Homebrew recipe for rinku
  • one - Transform images into character art using text from your codebase
  • rinku - Generate link previews from the command line (macOS)
  • time-zones - View time at any city across the world in Emacs
  • video-trimmer - A video-trimming utility for Emacs
  • wasabi - A WhatsApp Emacs client powered by wuzapi and whatsmeow

Blog posts

Hope you enjoyed my 2025 contributions. Sponsor the work.

-1:-- My 2025 review as an indie dev (Post Alvaro Ramirez)--L0--C0--2025-12-30T00:00:00.000Z

Sacha Chua: 2025-12-29 Emacs news

Links from reddit.com/r/emacs, r/orgmode, r/spacemacs, Mastodon #emacs, Bluesky #emacs, Hacker News, lobste.rs, programming.dev, lemmy.world, lemmy.ml, planet.emacslife.com, YouTube, the Emacs NEWS file, Emacs Calendar, and emacs-devel. Thanks to Andrés Ramírez for emacs-devel links. Do you have an Emacs-related link or announcement? Please e-mail me at sacha@sachachua.com. Thank you!

View org source for this post

You can comment on Mastodon or e-mail me at sacha@sachachua.com.

-1:-- 2025-12-29 Emacs news (Post Sacha Chua)--L0--C0--2025-12-29T20:17:03.000Z

Irreal: Emacs Lisp Elements

Some time ago, Protesilaos Stavrou published a nice book on Emacs Lisp. The idea is to bring a “big picture approach” to Elisp so that every Emacs user can experience the joy of fine tuning Emacs to meet their exact needs.

Just recently, Stavrou has added EPUB and PDF versions. In a way, it doesn’t matter since he provides the Org mode source and you can export that to almost any format you want. Now, though, he has nice PDF and EPUB versions that you can simply download and read in your preferred format.

It’s nice having the book available as, for example, an Info file but apparently I’m old fashioned and prefer to read it as a PDF. Others may like EPUB or Info. Whatever your preferred format, Stavrou has you covered.

There aren’t that many books addressing Elisp and how to use it. Marcin Borkowski’s Hacking your way around in Emacs is one good example and there are some short tutorials but Stavrou’s and Borkowski’s books are the only ones I can think of off hand that address Elisp exclusively.

I’ve skimmed through the book and it seems like a good introduction. If you’re an Emacs user and want to advance, you really should learn a bit of Elisp. It’s not as daunting as it might seem since even adjusting the Emacs configuration is an exercise in using Elisp.

-1:-- Emacs Lisp Elements (Post Irreal)--L0--C0--2025-12-29T15:45:48.000Z

Donovan R.: 💭 The Seed Beneath The Tree

“I cannot understand every leaf of a tree. There is just too much. But if I understand the essence of the tree, I will intuitively know what will come out and when.”

By understanding the root, the understanding of the branches arises naturally. And understanding the rest follows.

What is the root but the planted seed? The one source of truth where everything else came from.

Studying all the patterns within all leaves of a tree is a tremendous task. But one might understand more about the tree just from its seed.

Everything is connected

Often, what appears to be unrelated is but the prolongation of the same thing.

Everything seems separated—not because they objectively are, but because we are unable to see the interconnection yet.

Every aha moment is the finding of that one dot connecting two seemingly isolated points.

I keep noticing this. A real-life situation that can be resolved using a paradigm from programming. A problem in one area that mirrors a pattern from somewhere else entirely. It happens so often that I have started to expect it.

Most innovation in the world is not novelty. It is borrowed concepts from other domains. A combination of what appeared to be unrelated.

I was watching a video recently—Michael Levin on Lex Fridman’s channel—talking about how different domains seem to point to the same direction about the manifestation of “emergence.” It struck me. Isn’t that what mystic figures were talking about thousands of years ago? Scriptures from different religions already pointing to the same thing?

Different waters, same fountain.

Finding the fountain is what understanding means to me.

What is timeless must be true

What is true in one place must be true in another. What is timeless must be true. And what is fundamentally true must remain true across different domains.

This is why I am drawn to philosophy, psychology, spirituality. This interest in the meta—in understanding itself.

And somehow it led me to Clojure, LISP, Emacs, Smalltalk. To the philosophy behind FOSS. Not just because of how they work, but because of what they represent—meta-programming, self-modification, the freedom to understand all the way down.

What is even greater is that these tools attract like-minded people.

When I read articles from the LISP or Emacs community, I see it. They are not just tinkering with tools. They are building deep understanding—a meta-understanding beyond their craft.

Consciously or not, we are pursuing the same Dao, if I might say it differently. Seeking essential patterns of life.


I wonder how many fountains there are. Or if it has always been just one.

-1:-- 💭 The Seed Beneath The Tree (Post Donovan R.)--L0--C0--2025-12-28T13:49:55.000Z

Irreal: The Power of C-x 8

I’m a long time user of Ctrl+x 8 Return for inserting arbitrary Unicode characters. I use it, for instance to insert the red meat character (🥩) for my Red Meat Friday posts.

For mundane accents like é, I use ivy-insert-org-entity that I stole from John Kitchin’s Scimax. It’s lighter weight and easier to use for spelling peoples’ names or using the occasional foreign accented word.

Even though I’ve been using Ctrl+x 8 Return for many years, I had no idea of how powerful the command is until I read this post from Rahul M. Juliato. It turns out that in addition to just using an existing Unicode character you can compose accented characters manually, combine multiple code points into a single glyph, and much more.

Juliato’s post is long and complicated so I won’t try summarize it here. You really need to take a look at the post itself. I had difficulty making some of his examples work but that was probably me doing something wrong or a shortcoming of my fonts. If you’re like me, you’ll probably never need all the power he describes but it’s nice to know it’s there if you do need it.

-1:-- The Power of C-x 8 (Post Irreal)--L0--C0--2025-12-27T15:31:07.000Z

Emacs APAC: CANCELED Announcing Emacs Asia-Pacific (APAC) virtual meetup, Saturday, December 27, 2025

Emacs Asia-Pacific (APAC) virtual meetup will not be happening this month. Regular meetups will resume next month, in January 2026.

In the meantime, you can enjoy the talks from EmacsConf 2025 here: https://emacsconf.org/2025/watch/

-1:-- CANCELED Announcing Emacs Asia-Pacific (APAC) virtual meetup, Saturday, December 27, 2025 (Post Emacs APAC)--L0--C0--2025-12-27T06:32:59.000Z

Protesilaos Stavrou: Emacs Lisp Elements: EPUB and PDF versions now available

I just added .epub and .pdf versions of my free book Emacs Lisp Elements. All files are available in the Git repository: https://github.com/protesilaos/emacs-lisp-elements.

I produced both of those from the source Org file. Here are the steps for posterity:

  • From the elispelem.org use the Org export mechanism to generate an Info manual (by default: C-c C-e i i).
  • This will generate a .texi file, which is then used as the source for the .info file.
  • From the command-line do texi2any --epub elispelem.texi to get the EPUB file.
  • For the PDF, install the dependencies with something like sudo apt install texlive.
  • Finally, run texi2any --pdf elispelem.texi.
-1:-- Emacs Lisp Elements: EPUB and PDF versions now available (Post Protesilaos Stavrou)--L0--C0--2025-12-27T00:00:00.000Z

Irreal: A Replacement For Diminish

Any serious Emacs user is apt to have a lot of minor modes active at any given time. That’s certainly true in my case. What we don’t need, though, is to see lighters for each and every one of them. It wouldn’t matter so much if they didn’t fill up the mode line and obscure other indicators that you might actually want to see.

The current way of dealing with this is Diminish that prevents any of the minor modes listed from putting their lighters on the mode line. I’ve found them difficult to get configured correctly but they do the job and I haven’t thought about the problem in years.

In a new post, Bozhidar Batsov reports that a new feature in Emacs 31 is the mode-line-collapse-minor-modes command. It turns out that I’ve written about this before but Batsov has more details. The difference from Diminish is that you list all the minor modes that you don’t want to see on the mode line (or even all of them) and Emacs compresses them into a single symbol, “…” by default.

If you’re living on the edge and building Emacs from Master, you can try this out now. Otherwise you’ll have to wait for the release of Emacs 31.

-1:-- A Replacement For Diminish (Post Irreal)--L0--C0--2025-12-26T16:47:33.000Z

Tony Zorman: Fixing Eglot's Hover Signatures

25th Dec 2025   ·   5 min read   ·   #emacs

Fixing Eglot's Hover Signatures

In an earlier post I talked about making the hover signatures for lsp-mode a little bit more useful. Out of the completely irrational desire to use more built-in packages, I’ve recently switched to eglot as my LSP client of choice, which however has the same inclination for showing hover information I don’t care about by default.

I won’t assume that you have read the lsp-mode post, so let’s quickly remind ourselves of the general problem:

Lsp clients have the option of showing useful things on hover. In most languages, there is an obvious candidate for this: the type signature of the thing at point. Sadly—for some languages—the implementation of the feature is… not great.

This still holds true two years later,1 but this is as good of an excuse as any to become a bit more familiar with the eglot codebase. Plus, customising Emacs is just so much fun.


Different language servers behave a bit differently here, but the default for all of them is pretty “meh”.2

  • rust-analyzer shows where the thing comes from

    Hovering over a method shows crate::module::Type

    I will draw your attention to the second line in the minibuffer; the first is just additional context,3 and the third are available code actions. What eglot shows me is that apply_ctx is a method of the Type type, sitting in ./type/context.rs, with ./ being the root of the crate.

  • clangd shows the vague type

    Hovering over fgetc just shows 'function fgetc'

  • (based)pyright shows something completely useless

    Hovering over the function 'go' shows '(function) def go(' (because the type signature gets split into multiple lines, see below)

  • ocaml-lsp sort of shows what I want to see

    Hovering over a function with a short type shows the full type signature on one line

    …but only for short type signatures

    Hovering over a function with a long type signature just shows the first line again

  • Same issue with haskell-language-server

    Hovering over a function with a short type shows the full type signature on one line

    Hovering over a function with a long type signature just shows the first line


What I’d like to achieve instead is a type signature—if I want to know where the thing comes from I can just M-. it.

Hovering over a method shows the full type signature of the method


Hovering over 'fgetc' shows its actual signature 'extern int fgetc(FILE *__stream)'


Hovering over a function shows the complete type signature with proper formatting


Hovering over a function shows the full type signature, even for long signatures


Hovering over a function shows the full type signature, even for long signatures


In case anyone finds this useful, I’ve packaged the source code of this article as eglot-hover. It may need some adjustments depending on the language server implementation, but at least for the ones shown above it should work as-is.

Onto the fun part.

The good—or bad, depending on your inclination—thing about lsp-mode is that it’s almost unnecessarily configurable just by user options. This includes hover signatures: lsp-clients-extract-signature-on-hover is just a cl-defmethod, which one can trivially override depending on the name of the currently active language server (one of its arguments).

Eglot doesn’t actually do any displaying itself, but instead delegates to the built-in ElDoc. Scrolling through eglot’s code, we can find integration functions like this:

(defun eglot-hover-eldoc-function (cb &rest _ignored)
  "A member of `eldoc-documentation-functions', for hover."
  (when (eglot-server-capable :hoverProvider)
    (let ((buf (current-buffer)))
      (eglot--async-request
       (eglot--current-server-or-lose)
       :textDocument/hover (eglot--TextDocumentPositionParams)
       :success-fn
       (eglot--lambda ((Hover) contents range)
        (eglot--when-buffer-window buf
          (let* ((info (unless (seq-empty-p contents)
                         (eglot--hover-info contents range)))
                 (pos (and info (string-match "\n" info))))
            (while (and pos (get-text-property pos 'invisible info))
              (setq pos (string-match "\n" info (1+ pos))))
            (funcall cb info :echo pos))))
       :hint :textDocument/hover))
    t))

How exactly to handle the callback cb is documented in eldoc-documentation-functions, but that’s actually not super important for this application. We don’t want to add anything to eglot’s already generated hover signature, but completely replace it in certain contexts, which essentially boils down to redefining pos. A priori, this is a buffer position up until which the docstring will be shown; as you can see above, the default implementation is to just show the first “real” line. While pos is named as if it should always be a number, it gets passed into the callback as a value for the :echo key. Quoting the eldoc-documentation-functions documentation:

:echo, controlling how eldoc-display-in-echo-area should present this documentation item in the echo area, to save space. If VALUE is a string, echo it instead of DOCSTRING. If a number, only echo DOCSTRING up to that character position. If skip, don’t echo DOCSTRING at all.

The value can be a string, in which case that string is displayed verbatim. Hence, the only thing we need to do is to monkey patch eglot-hover-eldoc-function, match on the name of the current major mode, and extract the “correct” signature ourselves if needed.

Suppose we are given the magic functions eglot-hover--get to extract the signature out of the response that the LSP server sends, as well as eglot-hover--hl-string to highlight the resulting string. Then, matching just on rustic-mode, the final change is rather small:

(defun eglot-hover-eldoc-function (cb &rest _ignored)
  (when (eglot-server-capable :hoverProvider)
    (let ((buf (current-buffer)))
      (eglot--async-request
       (eglot--current-server-or-lose)
       :textDocument/hover (eglot--TextDocumentPositionParams)
       :success-fn
       (eglot--lambda ((Hover) contents range)
         (eglot--when-buffer-window buf
           (let* ((info (unless (seq-empty-p contents)
                          (eglot--hover-info contents range)))
                  (echo
                   ; ∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨
                   (if (and (eq major-mode 'rustic-mode) (stringp info))
                       (eglot-hover--hl-string
                        (eglot-hover--get "rust" (substring-no-properties info))
                        major-mode)
                   ; ∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧
                     (let ((pos (and info (string-match "\n" info))))
                       (while (and pos (get-text-property pos 'invisible info))
                         (setq pos (string-match "\n" info (1+ pos))))
                       pos))))
             (funcall cb info :echo echo))))
       :hint :textDocument/hover))
    t))

Since we’re not returning a buffer position, we have a lot more freedom to highlight the string as we want. For example, in haskell-mode I prettify some symbols (forall becomes , \ becomes λ, and so on), and these would otherwise get lost when just the buffer position is sent to ElDoc.

The actual implementation of eglot-hover--hl-string and eglot-hover--get is relatively straightforward, and just involves some markdown mangling. It originally started with some Rust-specific code given in emacs-lsp/lsp-mode#1740, and was then generalised to fit other LSP servers. You can try throwing servers not listed above at it as well, but since the implementation is relatively brittle it’ll probably need some adjustments.4

(defun eglot-hover--hl-string (str mode)
  "Syntax highlight STR according to MODE."
  (with-temp-buffer
    (insert str)
    (delay-mode-hooks (funcall mode))
    (-each #'funcall
      (--remove (-contains? '(nil
                              rustic-setup-lsp
                              eglot--managed-mode
                              eldoc-mode
                              flymake-mode-off)
                            it)
                (--mapcat (ignore-errors (symbol-value it))
                          delayed-mode-hooks)))
    (font-lock-ensure)
    (buffer-string)))
(defun eglot-hover--get (lang str)
  "Get LANGs hover information in STR."
  (cl-flet ((join (sep strings)
              ;; This shields against Python shenanigans like
              ;;
              ;; def f(
              ;;   a,
              ;;   b
              ;; )
              (--reduce (concat acc
                                (if (or (s-suffix? "(" acc)
                                        (s-prefix? ")" it))
                                    it
                                  (concat sep it)))
                        strings)))
    (let* ((start (concat "```" lang))
           (groups (--filter (or (s-equals? start (car it))
                                 (s-equals? start (cadr it)))
                             (-partition-by #'s-blank?
                                            (s-lines (s-trim str)))))
           (name-at-point (symbol-name (symbol-at-point)))
           (type-sig-group (car
                            (--filter (--any? (s-contains? name-at-point it)
                                              it)
                                      groups))))
      (->> (or type-sig-group (car groups))
           (--drop-while (not (s-prefix? start it)))
           (-drop 1)                    ; ``` LANG
           (-drop-last 1)               ; ```
           (-map #'s-trim)
           (--filter (not (s-matches? comment-start-skip it)))
           (join " ")
           (s-chop-suffixes '("," "```" "``` ---"))))))

That’s pretty much it—you can now enjoy having useful hover information!


In case you want to manually add type signatures then this is similarly straightforward, provided one knows the correct incantations:

(defun +haskell-type-sig-at-point ()
  (interactive)
  (let ((s (-> (eglot--request
                (eglot--current-server-or-lose)
                :textDocument/hover (eglot--TextDocumentPositionParams))
               (plist-get :contents)
               (plist-get :value)
               ((lambda (s) (eglot-hover--get "haskell" s)))
               (eglot-hover--hl-string 'haskell-mode))))
    (back-to-indentation)
    (insert s)
    (haskell-indentation-newline-and-indent)))

Hovering over a function and executing '+haskell-type-sig-at-point' makes a type signature appear


  1. See, among others probably, emacs-lsp/lsp-haskell#151, emacs-lsp/lsp-haskell#187, emacs-lsp/lsp-mode#4362, haskell/haskell-language-server#4724, and emacs-lsp/lsp-mode#1740.↩︎

  2. To be clear, I don’t think these are shortcomings of the individual language servers, it’s just that there’s a mismatch with the integration into Emacs’s LSP client landscape. Breaking long things into several lines is quite normal, after all.↩︎

  3. I do like eglot’s default behaviour of showing that we’re inside of a function call and which argument is currently being filled in!↩︎

  4. Needs at least cl-lib.el, dash.el, and s.el to work.↩︎

-1:-- Fixing Eglot's Hover Signatures (Post Tony Zorman)--L0--C0--2025-12-25T00:00:00.000Z

Emacs Redux: Hide Minor Modes in the Modeline in Emacs 31

Most Emacs users run a tone of minor modes and many of them contribute something (usually useless) to the modeline. The problem is that the modeline is not infinite and can quickly get quite cluttered. That’s why for the longest time I’ve been using the third-party diminish package and I have something like this in my config:

(use-package diminish
  :config
  (diminish 'abbrev-mode)
  (diminish 'flyspell-mode)
  (diminish 'flyspell-prog-mode)
  (diminish 'eldoc-mode))

diminish gets the job done, but it’s a bit annoying that you need a third-party package for something so basic. Fortunately that’s about to change…

I just learned that in Emacs 31 it’s finally possible to hide minor modes in the modeline using built-in functionality! Here’s how you can do the above:

(setq mode-line-collapse-minor-modes '(abbrev-mode flyspell-mode flyspell-prog-mode eldoc-mode))

And here’s how you can hide all minor modes (probably a bad idea, though, as some add useful info to the modeline):

(setq mode-line-collapse-minor-modes '(not))

For more info on what you can do with this new functionality see C-h v mode-line-collapse-minor-modes. After all, they don’t call Emacs the “self-documenting editor” for no reason.

From the docs you’ll learn that hidden mode “lighters” (Emacs lingo for a mode’s modeline indicator) get compressed into one. It’s ... by default, but it can be customized via the variable mode-line-collapse-minor-modes-to.

Apart from diminish, there are also the newer delight and minions packages that tackle more or less the same problem. As explained here for minions, they might still be useful, depending on your use-cases. One of the great aspects of Emacs is having options and when it comes to dealing with the minor mode lighters we have plenty of options!

That’s all I have for you today. Happy Christmas holidays! Keep hacking!

-1:-- Hide Minor Modes in the Modeline in Emacs 31 (Post Emacs Redux)--L0--C0--2025-12-24T08:14:00.000Z

Please note that planet.emacslife.com aggregates blogs, and blog authors might mention or link to nonfree things. To add a feed to this page, please e-mail the RSS or ATOM feed URL to sacha@sachachua.com . Thank you!