Much like last year, after the summer break I've had time to work on features for Lagrange. Version 1.19 focuses on adding new sidebar functionality, but there is a generous helping of bug fixes and minor enhancements as well. The release is available for all platforms (except Android ¹).
I effectively took a sabbatical from programming during the first half of the year, hardly touching an IDE at all. This wan't due to burnout: as a dad with small children, there really isn't enough time to be burnt out. It seemed more like a swing of the pendulum from purely cerebral hobbies to more emotional ones. I invested a lot of time into reading and music, and it has been delightful and refreshing. I guess it's good to occasionally exercise all parts of the brain! In any case, after the summer vacations ended and normal life resumed, dipping back into coding has felt increasingly appealing.
One crucial factor in this recommencement has been my homegrown text editor "jked".
Virtually all the work for the last two months has been done in jked, apart from debugging and the mobile builds. It is gratifying that the editor is working as intended and able to act as my primary development tool for Lagrange as well. Working on a cross-platform app like this, it is useful to have essentially the same editor everywhere, from the Raspberry Pi to remote servers to Linux, Windows, and macOS. I am glad I took the time last fall to build this little IDE for myself.
After the lengthy break, however, I didn't feel comfortable tackling huge changes in version 1.19. The focus is squarely on the sidebars in this release. I've added three new sidebar modes, and two of these can be quite impactful to the overall browsing experience. In addition to feature work, I went through the issues on GitHub and bbs.geminispace.org and tried to pick all the long-hanging fruit. Hopefully some of your annoyances are now dealt with.
It is common practice to have a little menu or sidebar on one's website that allows quickly navigating to sections of the site. This is generally good for usability. Unfortunately, having something like this on Gemini either requires clunky link lists or back-and-forth links between parent, child, and sibling sections. It is hard for the reader to easily visualize the structure of the capsule at a glance and one can lose their place browsing through the pages.
While each Gemtext document is its own little island that links to other documents, we can still look to the Gemini URI for additional information, namely the path component. More often than not, the URI path is able to communicate a logical structure that can aid the reader while browsing. Many Gemini capsules are manually constructed and human authors tend to set up proper (often elegant) directory structures, so the resulting hierarchy can be very useful.
The new Structure sidebar looks at all known URIs related to the current capsule and builds a navigable tree out of the paths. As you browse around, discovered links are added to the tree, and the tree keeps growing while you stay on the same capsule. I've found this quite helpful in many cases: it is easy to switch to sibling items or find a particular place elsewhere in the tree. For example, this works nicely as an alternative compact UI for Astrobotany. Another great use case is multi-user hosts where you can see the different users in the tree. It's a nice surprise to discover new people this way, looking around the directory tree.
URI paths are typically somewhat hidden from view, especially if a client primarily displays link labels. Visualizing this directory structure is a nice way to surface information that was already there but challenging for the user to comprehend.
The Structure sidebar is not specific to the Gemini protocol: any URI with paths will do. It does work best with natural path hierarchies, though. For example, one challenging case is dynamically generated paths that use hashes or sequence numbers to identify resources. There isn't much human-readable information to glean from these. Another example is Gopher selectors that may or may not refer to a directory hierarchy. Furthermore, Gopher URI paths start with an item type character. This effectively breaks the tree into multiple roots (a forest?), making the overall organization more difficult to understand. I went on a twisty detour trying to apply special transformations to Gopher URI paths to get them to form a more well-behaved tree, but in the end nothing worked that well. Selectors aren't even guaranteed to describe a hierarchy, and there isn't a reserved separator character. It would be a bigger project to analyze common selector patterns and break them down appropriately. Perhaps something for a later day. (The forward slash must be the predominant separator and, historically, Gopher was certainly used for serving directory trees and files so this seems like a solvable problem.)
If you are a chronic tab hoarder, Lagrange's UI has been letting you down pretty badly. The horizontal tab bar does not scroll or fold its contents, so you end up with a ton of tiny tab buttons. Your prayers have been answered, though, because there is now a vertical tabs list in the sidebar! You can dismiss that horizontal bar entirely and manage tens of tabs easily using the list.
This has been a feature request since 2020, so better late than never, I suppose.
Having extra space for the list items affords additional status information:
And of course, the list also supports reordering via drag-and-drop, much like the horizontal tab bar.
The old horizontal tab bar has been particularly awkward in the mobile UI, where there is very little horizontal space available. I usually have around four tabs open on my phone, and even with those it gets a little fiddly. Thanks to the tabs sidebar, the horizontal tar bar can be hidden entirely and you can manage tabs using the popup toolbar on the phone. While this introduces an extra tap or two for switching tabs, the benefits outweigh that easily. With the tab and nav bars hidden, one can have full-screen content on the phone without any UI in the way.
(Alas, I still haven't quite tracked down the iOS bug where app state gets sometimes wiped at launch and you lose all the open tabs.)
The third new sidebar tab is a more modest addition but useful nonetheless.
Lagrange has supported subscribing to Gemfeeds for a long time, but it has provided little help in managing those subscriptions. The only way has been to go through the Bookmarks list and look for ones with the ★ icon. To make things more cumbersome, the Feeds tab only shows up to a hundred entries so it has been easy to lose track of infrequently updated feeds. The new Subscriptions sidebar aims to help with this by listing all the active subscriptions and giving high-level information about them.
After implementing this new sidebar I immediately discovered a couple of broken subscriptions, so effort well spent I guess.
Having added three new sidebar tabs, fitting all eight into a single sidebar seemed a little much. To alleviate this, there is a new page in Preferences for choosing which tabs appear on which side(s), so you can assign different functions for the left and right sidebars.
There is also a new dropdown menu in the sidebar header. This lets you switch to any mode regardless of which buttons are visible. This way you can hide the ones that are needed only infrequently. The keyboard shortcuts for accessing sidebar tabs are now bindable, too, for further customizability.
The Bookmarks sidebar has a new filter field. The Ctrl+F or ⌘F shortcut takes you there if Bookmarks is visible, otherwise it opens the page content search as before.
While it has been possible to look up bookmarks quickly using the URL field, there are advantages to filtering the bookmarks list itself. For one, the filtered bookmarks can be manipulated via the context menu, so you can open them in tabs/windows, edit them, assign behaviors, or delete them. Secondly, you can filter by built-in behavior tags like ".subscribed" to see all bookmarks with that tag only. Previously, there hasn't been a way to access these without dipping into the bookmarks.ini file. (The "User Data > List Bookmarks by Tag" only displays regular tags.) Other useful behavior tags for filtering include ".homepage" and ".remote".
The mobile UI does not yet have the filter field because I couldn't quite decide how I want to implement it. Wrangling the sliding sheet with the keyboard taking a portion of the screen requires some extra work.
Lagrange has rendered quoted text in italics since the very beginning. Unfortunately, this results in poor legibility with some fonts, particularly on low-resolution displays. I've added an option to pick between regular style and italics for quotes so you can pick the one that works best for you.
Speaking of quotes, I've been using the "line indicator" quote style recently more often and noticed a few bugs with it: the indicator was displayed incorrectly on short and empty quote lines. These have been fixed.
While the mobile builds use exactly the same application logic as the desktop ones, the "dialog UI" (like settings, popups, bookmark editors, etc.) has a specialized mobile variant with its own appearance and layout rules. It operates in a lot more procedural way than the desktop GUI. The latter is mostly laid out as traditional manually constructed widget trees. I originally designed the mobile UI with an iOS 7 visual aesthetic, with controls inside full-width "cells". iOS has since moved away from this style by adding a lot of padding so a little refresh was in order. You can most prominently see the differences in Settings, where the pages have breathing room on the sides and item dividers have been toned down. I think it looks a lot cleaner now, although I still haven't implemented any kind of corner rounding, to really attempt fitting into the modern UI style.
As a result of this refresh, a few minor color tweaks can be noticed on the desktop as well.
The terminal has not been any sort of focus area in this release but I did discover a few crashing bugs in text rendering. Particularly non-printable control characters were causing trouble. I should probably spend more time with `clagrange` to get a feel of how stable it is in daily use and whether any awful usability pitfalls remain. I'm sure the keyboard input can streamlined further.
The new sidebars are naturally available in the TUI, too, because it's the exact same widgets being used there. I had to make very little TUI adaptation for these apart from adjusting the paddings.
Good news for Windows users: Lagrange is now built using the MinGW toolchain, which makes it a truly native Win32 application. Previously, the MSYS runtime has been used for providing a POSIX-ish environment. Binaries compiled with MinGW depend only on Microsoft's C runtime and the various operating system libraries, plus of course the third-party libraries like OpenSSL and mpg123. The end result should be a leaner and more efficient executable.
However, this does mean there is a bunch of new platform-specific code that may still contain bugs. Most crucially, network sockets are implemented using the Winsock2 API instead of BSD sockets. Subtle or rare glitches may have been overlooked in my testing.
Another Windows-specific improvement is that if you launch from the command line with `-E`, the app enables output via stdout/stderr to the console, so you can actually see the message log like on UNIX platforms. Amazing! 🤯
¹ For some reason, the Android build has decided to stop working even though the v1.18.8 release went swimmingly. I'll have to poke around, maybe rebuild the dependencies, upgrade/downgrade something. Let's see how long this'll take...
📅 2025-09-01
CC-BY-SA 4.0
The original Gemtext version of this page can be accessed with a Gemini client: gemini://skyjake.fi/gemlog/2025-09_lagrange-1.19.gmi