Freewheeling Apps are a durable substrate
for the future of software.
Easy to download. Easy to run. Easy to modify. Easy to share.
Free and open source. I'm not trying to make money from them, only
meaning. I'm beholden to nobody. No dark patterns, ever. I'll never sell them
to someone else; there's nothing to sell.
All apps contain their source code; you can always look inside the app
you're running. You can always modify it using the same software you use to run
it. Running your modifications takes a press of a button. Freewheeling apps
reward curiosity.
Each app quickly stabilizes, and then seldom updates. Because timeless
software is safer and more trustworthy (it can't change its mind). Definitely
no auto-updates. I think I might be able to get to 50 years without an update.
Join me on that voyage!
Individual apps stay simple; apps spawn forks and remixes rather than grow complex.
Decker. I'd for some reason assumed it was some npm and/or wasm monstrosity, but it turns out it's nice zero-dependency Javascript in a single html file that you can save and run offline! And that's in addition to the native version that uses SDL just like LÖVE. So in some ways Decker has a better cross-platform story than LÖVE, which is non-trivial to run on iOS. The downside: on a web browser on Android it's even less efficient to run than LÖVE's native app.
Rust. I spent some time playing around with a LÖVE-inspired game engine called ggez, and the much more basic wgpu crate. The promise here: Rust seems to be evolving some pretty nice cross-platform tooling, so in time we may end up with a world where it's easy to cross-compile to any platform. Of course, the compile step is not ideal, but it promises to yield much more efficient binaries that might run on lower-end devices like old phones. The eco-system is not quite there, and the npm-like dependency explosions are rough. But I want to try to look outside my comfort zone. It's possible the way forward isn't unique-snowflake minority platforms like LÖVE or Decker, but just to use a majority platform with better taste, taking the time to understand and curate the landscape of dependencies.
Anyways, here's a little program I made to try to stretch Decker to more of the sort of procedural graphics I tend to gravitate towards on LÖVE:
This is dancing letters, a fixed piece of text except we're constantly switching the case of each letter at random.
Here's the code, to give you a flavor for what Decker's quite elegant mix of Lua and APL looks like:
local s: "abcdef" # put in whatever text you want
on view do
if ! 5%sys.frame
me.clear[]
local y:each c in s random["%u","%l"] format c end
local margin:15
me.text[y margin,margin,me.size-margin*2]
end
end
And here's that code along with the surrounding card (you need a canvas widget to be present just so) in a less readable form that you can copy and paste into a deck of your own:
%%CRD0{"c":{"name":"home","script":"on view do\n \nend","widgets":{"canvas":{"type":"canvas","size":[300,200],"pos":[48,51],"animated":1,"volatile":1,"script":"local s: \"Call me Ishmael. Some years ago--never mind how long precisely--having little or no money in my purse, and nothing particular to interest me on shore, I thought I would sail about a little and see the watery part of the world. It is a way I have of driving off the spleen and regulating the circulation. Whenever I find myself growing grim about the mouth; whenever it is a damp, drizzly November in my soul; whenever I find myself involuntarily pausing before coffin warehouses, and bringing up the rear of every funeral I meet; and especially whenever my hypos get such an upper hand of me, that it requires a strong moral principle to prevent me from deliberately stepping into the street, and methodically knocking people's hats off--then, I account it high time to get to sea as soon as I can.\"\n\non view do\n if ! 5%sys.frame\n me.clear[]\n local y:each c in s random[\"%u\",\"%l\"] format c end\n local margin:15\n me.text[y (margin,margin,me.size-margin*2)]\n end\nend","border":1,"scale":1}}},"d":{}}
After a conversation with Jack Rusher and others about Emacs Nature [1, 2] and playing with Seymour by Alessandro Warth, I'm getting interested in building..
An environment for visualizing programs
(Not to be confused with visual programs, or visualization more generally.)
Start with a tiling window manager for managing named graphical canvas "buffers", using Emacs operations like split and resize.
Each buffer exposes a coordinate space of its choosing, listens for messages and positions objects in the space in response to messages.
Buffers can send messages to other buffers.
Examples of coordinate spaces:
Graphical game engines use the obvious 2D/3D cartesian systems. You position stuff using (x, y) or (x, y, z). You could also imagine polar or other coordinate systems that are studied in geometry.
The HTML DOM is a space where positions can be specified using CSS selectors or XSLT.
You can imagine a text editor operating in a coordinate space as well. Emacs seems to use a 1D coordinate, just character count from start of buffer. My stuff so far uses 2D: (line index, UTF-8 codepoint index within line)
Some examples of messages, to show the sorts of use cases this framework might unlock:
In a text editor, the cursor tracks a position, and keyboard and mouse send messages to move the cursor or insert objects (characters or longer text) at the cursor.
You can imagine print statements as a message from the "code" coordinate space to a different, 0-D (append-only so there's no notion of coordinate) space.
Terminal buffers in Emacs take the mostly 0D space of a terminal and augment it with a cursor. When you scroll up to an earlier command and hit a hotkey, the buffer sends a message to itself with the text around the cursor. The message is received at the bottom of the buffer.
Emacs Slime and other IDEs support keyboard shortcuts to send text from the current buffer to a REPL in some other buffer.
Ronin and Sketch-n-Sketch support bidirectional messages between two spaces with very different coordinate systems.
Live programming systems often show the results of a statement to its right. Examples: alv by S-ol Bekic, Bret Victor (of course), Seymour as above. These too can be seen as a reflexive message from a space to itself. In addition, the message contains an implicit coordinate: the current line.
Glamorous Toolkit, Lisp Machines and other Smalltalk systems do a lot of stuff like this. I think all of this can be cast in terms of buffers, coordinate spaces and messages, though you can imagine them as a single, very complex coordinate space like the HTML DOM, or many simple spaces with different possible coordinate systems. For example, in any of them you can create a new "log" space that you can append graphical objects to. Maybe even self-contained interactive graphical widgets.
The major question for me now is: how do you configure a buffer? You need some concise way to specify the space (perhaps just by naming from a small menu of options), handlers that listen for messages (e.g. keypress or mousepress), handlers for sending messages (e.g. widgets on the space that perform tasks when you interact with them), and generic handlers for sending messages to other buffers (e.g. print; here I'm imagining it to send a message from some arbitrary process, through say a socket, back into the environment, with enough information to route it to the appropriate buffer accompanied by a reasonable coordinate)
I have gotten annoyed by timezone calculations for hopefully the final time. Here's a static html page you can download and save locally to roughly compare times in different timezones (just hours; you're on your own for minutes).
If you're not in a whole-number timezone (Hello India), you'll need to do some additional mental arithmetic by comparing nearby rows.
That's it. Since it's almost entirely static, you can always be sure that you're seeing the same thing on this page as anyone else.
Unfortunately you need to know if you're in daylight savings time or not, something that is often beyond me. I'm not sure what to do about that without reintroducing dynamism that takes the current computer's time into account. Then I again end up wondering if others are seeing what I'm seeing.
There are a few abbreviations for America, Europe and Australia in both pages. You can see *ST and *DT on either page, which might help if you're not observing daylight time yet, but someone else is. There's a tension here between trying not to be overwhelming and emphasizing the Western or Northern hemisphere. My thinking is to only add codes for longitudes with lots of cities or with daylight savings time. Hopefully people in Bhutan or Nigeria or the Chatham Islands won't hold it against me.
I've been getting back into teaching kids programming 1:1. Of course, this time using Lua, LÖVE and Carousel. After a couple of months, it occurred to me to collect all my little impromptu puzzles and exercises into a single app anyone can go through on their own schedule.
Carousel Cards (LÖVE app, really just a zip file containing source code, 169KB)
Nowhere near done yet. But it has 50 112 little "levels", each taking between a few seconds and a minute. A full game/curriculum might need 2000 levels or something.
Emacs-style ranges on a text buffer that I can now hang attributes like color, decorations and click handlers on to.
Inserting/deleting text before a range moves it.
Inserting/deleting text after a range leaves it unchanged.
Inserting/deleting text within a range grows/shrinks it.
Deleting text at a boundary shrinks the range, and deletes it when it becomes empty.
Inserting text at boundaries can't disambiguate whether I want the text within or outside the boundaries. But I can grab some handles on the range to adjust.
The final complexity cost was 200 lines but it was a non-linear path getting there. I started out with the notion of pivots from my doodle app. There, pivots live inside the data structure for a single line of text. Since ranges now need two pivots that could be on different lines, I had to move them out. I started tracking pivots in a separate data structure, maintaining bidirectional mappings between pivots and their locations, and then tracking ranges as combinations of pivots. This quickly blew up the implementation to 500 lines, and I was juggling 27 manual tests of which half were failing.
The next day I started from scratch and threw out the notion of pivots entirely. Now I just maintain 2 locations directly inside each range, and linearly scan through them all for any book-keeping. The implementation dropped to 200 lines and the tests passed fairly quickly.
Earlier this year I threw out an implementation after suffering with it for 2+ years. It feels like I'm getting the hang of this programming thing that I threw out an implementation now after just 2 days. I'm setting a higher bar for elegance. At the same time, it's interesting that my instinct remains as poor as ever for the right approach in even a slightly new problem. Here I spent a lot of time trying to squeeze my ranges into lines so that deleting a line would transparently delete ranges within it. But I blindly assumed a fully normalized design with a first-class notion of a pivot must be a good idea.
My notebook app does simple variants of 2 and 3, and replaces 1 with explicit in-document markup.
Now I'm playing with another approach to 1. I already have the idea of pivots from my doodle app. Putting two of those pivots together should yield a range that adjusts in intuitive ways in the presence of edits. An example may be a WYSIWYG UI for adding a hyperlink to some text:
Inserting/deleting text before a range moves it.
Inserting/deleting text after a range leaves it unchanged.
Inserting/deleting text within a range grows/shrinks it.
Deleting text at a boundary shrinks the range, and only deletes the attached attributes if the range becomes empty. This makes ranges more robust to deletion than my doodles which attached to a single pivot.
Inserting text at boundaries can't always do what you want. I imagine it'd be nice to have handles that you can drag to adjust a range.
I hadn't tried this until today, but it turns out to work: I can create equations forwards and back in notebook.love, and trigger either selectively based on what blanks I fill in.
The fine print: to switch directions I have to fill in the right blank, clear the old blank, and then type in something outside the old blank (to indicate I'm not going to type further into the old blank).
Works better if I clear the old query first, but who can remember that?
I've decided to just recompute on every keypress and mouse click. It seemed unnecessary, but now I see that there's some benefit from the inefficiency.
I'm instead using the game engine idea of a pivot. Any time I toggle into doodle mode I have to first pick a pivot from one of the characters on screen. All my drawings are relative to that pivot, and edits to text maintain pivots alongside.
Displacements to the pivot are preserved in font-derived units, so it looks "reasonable" as you resize the font.
Deleting a character deletes all drawings pivoted on it. (But there's undo.)
This took 200 lines, so not too much though it was more than I'd initially expected.
Blanks can be filled in either with the results of computation or with what a
person typed in. Now we indicate such conflicts in two ways.
Blanks filled in from computations get a distinct look (the cyan background), separate from both hand-written (black on white) and computed, non-editable text (cyan on white)
If I manually edit a blank, its background changes, and any code that was overridden doesn't execute. Here are a couple of examples: