Lagrange v1.21: Inline Prompts, Gopher-over-TLS, UI Improvements

This release is largely about user interface improvements. The new default behavior for input prompts is to appear inline on the page, exactly like images have always done. I've also addressed a few rough edges here and there to make the UI sleeker.

Inline input prompts

Ever since the first version of Lagrange, when the server has requested input from the client, a modal dialog has appeared where the user can enter text. Why a modal prompt? While this is a distinct back-and-forth between the client and server, it does not involve changes to page contents, so it exists kind of outside the normal model of browsing: the user has effectively left the page since they opened a link and are now at a URL that resulted in a query prompt, however they haven't received a new document so there is nothing else to switch to yet (except an empty page). The modality is a cheap and straightforward way to prevent the user from accidentally performing any actions on the (now obsolete) document being shown.

The idea of displaying prompts inline with page contents has been around for several years now.

By turning the prompt into an inline element, a sub-component of the current page, its place in the model is resolved neatly. Exactly like an inline image, it can now be treated like a part of the page when it comes to navigation and history.

The challenge with this is that it is quite an invasive change. Of course, the internal machinery for inline content exists (for both images and audio), but input prompts have some unique properties to consider. First of all, they are UI widgets, so they live on an entirely separate layer compared to page contents. Also, they require capturing keyboard input focus, backing up any text entered by the user, and if you navigate back and forward in history, the prompts on the page must be part of the cached state. After managing to iron out all these details, I realized there was also a unique problem when it comes to the navigation swipe animations used on mobile devices and macOS. Each prompt is a widget that needs to sync its position with the page contents. Easy enough: we have both the vertical scroll position and the horizontal swipe offset. But during a swipe, the tab is actually showing two overlapping document views, and prompt widgets must appear properly layered between them. This was not exactly trivial to get right. (This area of the code was not straightforward to begin with.) In the end, I had to resort to excluding prompt widgets from normal drawing and introducing a new manual child drawing step between the view layers.

A few releases ago I added the option to mark certain URLs as "assumed to require input", which means the app will not fetch those URLs without first asking for a query string from the user. With prompts now appearing inline, this affords them to be displayed immediately when the page loads. The end result is that it is finally possible to play prompt-loop games like SpellBinding in the ideal way: type a word, press Enter, type the next word, press Enter... Your hands don't need to leave the keyboard, no shortcuts need triggering, the prompt is always visible, and input focus stays inside the prompt.

Social capsules like Station and BBS also benefit from inline prompts: the post/reply/comment fields appears inline, and the rest of the page contents (comment thread, etc.) can be conveniently browsed and quoted. You can even navigate back in history to reference some other material and then return back to the still-open prompt.

All in all, I think inline prompts are the biggest step forward for the app's user experience in years.

Unfocused input prompts

The mobile UI β€” the phone variant in particular β€” does not have quite enough real estate to show fully-functional prompts together with the page contents. Therefore, I've also made the previously strictly-modal top/bottom-attached input prompts less strict. Clicking or tapping outside the modal prompt relinquished focus to the document, enabling the rest of the app UI as well, so you can access the page contents, sidebar, and other parts of the app normally, while keeping the prompt open but unfocused. However, changing the current page will dismiss the prompt and returning to it may not be possible.

This new behavior applies to top/bottom prompts on all platforms.

Gopher-over-TLS

One notable new feature of v1.21 is the sometimes-requested "gophers://" support. There is not much to it, really: it is Gopher over a TLS connection. Gopher TLS certificates get the Gemini-style TOFU treatment, using exactly the same code that the Gemini side is using.

Tab bar layout

The most prominent UI change is perhaps that the tab bar now appears at the same level as sidebar tab buttons. Previously, the document tab bar extended edge-to-edge across the entire window, and if the sidebars were open, the tab bar looked out of place compared to the document contents. Moving the tab bar into the document area also means the UI chrome at the top of the window can be slimmer. In other words, a more compact and better-aligned layout.

Persistent window arrangement

While Lagrange has always saved the places of open windows when quitting, that placement has not been respected when opening new windows later on. The app has only remembered one default position and size that all new windows have used.

In v1.21, I've made window arrangement persistent: if you open three windows and arrange them in a particular way (e.g., side by side), you can close one or more of them, and when you open new windows, they will reuse the vacant slots in the previous arrangement. This is a nice little usability improvement if you frequently use multiple windows.

Better middle-button scrolling

Middle-button scrolling was added in the previous release. I'm a wheel-scroller myself, so I confess I didn't get this quite right. In v1.20, you needed to drag with the middle button to scroll, but if you actually do this for any extended period of time, it quickly starts tiring your finger. In v1.21, middle-button scrolling has been improved:

Windows MSVC build

Work has been progressing toward making Lagrange compile using MSVC on Windows, using Microsoft's build tools. Thus far, building Lagrange has required a run-time or compile-time POSIX compatibility layer like MSYS2 and MinGW. Version 1.21 is still built with MinGW, but it is now also possible to use MSVC with Microsoft's vcpkg tool (it takes care of the dependencies like the_Foundation and Harfbuzz) to compile a fully-functional executable.

I haven't yet decided if/when the prebuilt Windows binaries will switch to this. One downside of vcpkg is that it is not a light-weight tool: all the dependencies are built from source, which makes the process take a looong while and use oodles of disk space. My Windows build machine is a VM with meager resources so vcpkg may not be the right fit.

Many thanks to @gim913 for their contribution to this new option.

Arm64 build

I have a Raspberry Pi 400 that will be serving as a build machine so now there are AppImages for 64-bit ARM systems as well.

Other small things

πŸ“… 2026-08-26

🏷 Lagrange

CC-BY-SA 4.0

The original Gemtext version of this page can be accessed with a Gemini client: gemini://skyjake.fi/gemlog/2026/08/lagrange-1.21.gmi