No. 1 Homemade software, shown at length October 2026 · Svenska

jglot

10 programs One workshop, one boat, one phone, one terminal No cloud was harmed

Ten programs. One stubborn idea.

That software for your own life can be made at home, like bread: small, exact, and suited to one kitchen.

  • At seaA binnacle on a Raspberry Pi, and a simulator that draws the wind
  • WorkshopA 3D printer watched, fed and second-guessed
  • TerminalPictures, CAD models and a phone, all in a grid of characters
  • DeskA to-do list that lives in your source code
  • Machine roomDashboards, and a log that cannot be quietly edited
jsail: a yacht under way, with the load on her sails drawn as arrows
jstatus: a terminal dashboard of clock, machine, hosts and logs
rakemooner: a printed vase as a 3D toolpath over its bed mesh
Made
at homeno cloud inside
At sea

nakterhus

A chart plotter, a ship’s log and a sail-trim coach, all living on a Raspberry Pi behind the companionway.

Nakterhus is Swedish for binnacle, the housing that keeps the compass where the helmsman can see it. This one keeps rather more than a compass.

Every instrument on the boat speaks SignalK, and nakterhus listens. It draws the boat on OpenStreetMap and OpenSeaMap charts, lays a route over them, watches AIS traffic for anything on a closing course, and records every delta to SQLite so that a passage can be replayed later with one cursor shared between the graph and the track: drag along the depth curve and a marker walks the chart.

There are seven workspaces, each built for a job: Navigate, Helm, Plan, History, Voyage, Dashboards and Sail. A digit key switches between them and nothing is unmounted when you do.

Which feed is live is observed, never set. If the simulator’s port answers, the boat is simulated, and a flashing pill says so.

It is built for a boat on metered 4G. Cameras are ffmpeg processes that exist only while somebody is watching. A viewer off the boat gets a slower feed and a smaller picture, and a phone page that is hidden polls nothing at all.

The web console is the command line: one argparse tree serves both, so every command has exactly one implementation. Dashboards are HTML fragments drawn twice, by the browser and natively for the e-paper panel, with a test that holds the two to the same picture.

And when the wind gets up, it gives advice. The Sail workspace works out the point of sail, says when to reef, and lists the trim steps in order, each with its reason.

Runs on
A Raspberry Pi 4 aboard, Debian 13
Surfaces
Web workspaces, a phone page, a CLI of some 35 command groups, a 7.5-inch e-paper panel
Made of
Python with three runtime dependencies; framework-free ES modules, Leaflet, three.js; SQLite
Talks to
SignalK, AIS, Victron power gear, USB cameras, a 4G router
Status
In use aboard. 1,058 commits since May 2026, some 1,860 tests
Source: not public yet
BrowserNavigate. The route from Lymington to Cowes, AIS targets, the course panel and the bow camera. The boat is the simulator’s, and the pill at the top admits it.
BrowserHelm, in Night Red. Widgets float over a dimmed chart to spare the eyes on a night watch.
BrowserVoyage. A recorded 22.6 nm track with notes flagged along it, the logbook and a depth graph.
BrowserHistory. Depth and speed over ground on one time cursor; unrecorded stretches are drawn dashed.
BrowserPlan. Six waypoints, a passage plan called “Sunday tea run”, and a forecast fetched only when asked.
E-paperThe cockpit repeater: 800 by 480 pixels of one-bit e-paper, readable in full sun, refreshed once a second.
Command lineThe same boat from a shell: a status line, AIS with closest point of approach, and the legs of the plan.
PhoneThe phone page, which installs as an app and keeps the screen awake at the helm.

At sea, indoors

jsail

A sailing simulator that explains its forces. Every newton is computed, and every newton is drawn.

DesktopUnder way at 5.81 knots, heeled 18 degrees, autopilot steering for a mark upwind. Orange arrows are the load on each strip of sail; the panel at lower left shows which strips are luffing and which have stalled.

A survey of what already existed found simulators with physics that explain nothing, and trainers that explain everything with no physics behind it. So here is both.

Sail aerodynamics, hull, keel and rudder, wind, current and waves: jsail solves the lot, then draws what it solved. Pressure runs up the luff as a comb of arrows. Streamlines bend round the rig, blue where the air is slower than the wind and red where it is faster. The righting arm and the heeling arm are labelled in metres, on the boat, while she heels.

The rudder angle is solved together with yaw, so weather helm and a broach are things that happen to you, not settings. Under way she is stepped through time in six degrees of freedom, the crew trims at the pace of a winch, and a tack costs speed exactly as it should.

Ask too much of her and she answers plainly: “no sail set may be carried in 14 m/s.”

Trim is the crew’s or yours, item by item. Eight controls each carry an Auto box, and whatever you leave alone is optimised around what you insist on. Get it badly wrong and the instructor steps in: one correction at a time, each with its reason and what it is worth in knots, and a gold arrow pointing at the slider to move.

Every number in a boat file carries its provenance: declared, calculated or estimated. jsail boat check prints the estimates under the heading “the to-do list”.

The headless build is a 2.1 MB binary that computes a polar on a Raspberry Pi in under a tenth of a second.

Surfaces
A Godot 3D view and a headless jsail CLI
Made of
A Rust workspace of six crates, Godot 4 through gdext, GLSL for water, sails and flow
Checked against
WinVPP polars and the Delft Systematic Yacht Hull Series: 0.42 kn RMS over 224 cells
Status
First iteration stands. She can be sailed
Source: kept ashore for now
DesktopPlan view. Computed streamlines, a pressure comb on both sails and each force labelled in newtons: drive 1,121, heel 3,025.
DesktopThe instructor, step one of five: “Sheet in the headsail”, with an arrow on the slider in question.
DesktopThe verdict. As you had her, 1.74 knots. Taught, by hand, 5.71. The crew, 5.86.
DesktopGauges: heeling against righting moment, drive against resistance, the helm, and the sails strip by strip.
Command lineA polar from the command line. At 19 knots of wind the crew flattens the sails, then reefs.
Command lineOne tack, second by second: speed falls from 5.85 knots to about 2 and climbs back.

Workshop

rakemooner

A flight recorder for a 3D printer. Every print kept, every second of it, and one cursor to walk back through the lot.

BrowserThe Model workspace. A twisted vase stopped at layer 529 of 700 and coloured by height, standing on the bed mesh the printer measured before it began.

Moonraker tells you what a Klipper printer is doing now. Rakemooner, which is Moonraker with its syllables swapped, remembers what it did.

While a print runs, a small daemon writes down everything the printer reports: temperatures and their targets, heater power, fans, speed, the humidity in the filament box, each console line, the bed mesh, the webcam. Afterwards the web UI lets you go back over it, and one time cursor drives the whole page. Scrub a chart and the readouts, the G-code line, the 3D toolpath and the video all move to that moment.

The toolpath is drawn as solid boxes of the true width and height of each extrusion, about a million and a half of them for a ten-hour print, in one draw call. Colour them by feature, by speed, by height, by filament slot, or by any field that was recorded.

Switching workspace unmounts nothing. The webcam keeps streaming and a half-typed note survives.

Prints gather themselves into projects without being filed. A model’s version is a hash of its meshes; the settings that changed between two prints are read from the block the slicer leaves at the end of the G-code. Tick two prints and it tells you what differed.

And then the Slicer workspace asks what if. It takes the slicer project a print was made from, slices it again under other settings with OrcaSlicer’s own command line, and sets the results side by side: 25 minutes and 6.5 grams saved, at two walls and less infill.

From the temperature and humidity in the filament box the daemon also works out the dew point, because somebody wanted to know.

Surfaces
A recorder daemon, a web UI with a phone form, rm-cli, and a desktop shell
Made of
Nine Rust crates on tokio, axum and Tauri; plain JavaScript and three.js, no build step
Records
Sensors at 1 Hz, G-code, console, bed mesh and webcam video
Leans on
Moonraker for the printer, ffmpeg for video, OrcaSlicer’s command line for second opinions
Status
In daily use. 243 commits since May 2026
Source: not public yet
BrowserHistory. Six charts over the first half hour of a PETG bracket, with flags where a setpoint stepped.
BrowserLive. Layer 382 of 700, the last thirty minutes of telemetry, and the toolpath growing as it prints.
BrowserColoured by feature: walls, gyroid infill, top surface and brim, with the G-code following the cursor.
BrowserSlicer. One bracket sliced twice; the second column shows only what differs, and what it saves.
BrowserProjects. A vase across two versions of its model, and two of its prints compared.
Command lineThe same questions from a shell: status, projects, a diff, and a slice with two settings changed.

Workshop

bidibok

Four spools, one filament box, and a command line that knows what colour each of them is.

The Qidi Box feeds a Q2 printer from four spools and dries them while it waits. The touchscreen can drive it. So, now, can a script.

bidibok reads the box and its slots, loads and unloads filament, starts a drying cycle, reads the RFID tag on a spool and assigns filament, colour and vendor from it. Colours are shown as true 24-bit swatches, and vanish politely when the output is piped somewhere.

Your own filaments live in a small local file, layered over the printer’s list by index. A firmware update overwrites the printer’s copy; yours survives, and preset sync puts it back, touching only your entries and leaving the stock ones byte for byte as they were.

It ships with a printer to play with: loads block for seconds, drying heats the box, and humidity falls.

That simulator runs the G-code it receives against a model of the box, slowly, the way the firmware does. Everything on this page was shot against it.

Two generations of firmware are detected at run time. Exit codes mean something: 1 for “may work later”, 2 for “asking again won’t help”.

Surfaces
A CLI, and a library crate anything else can link
Made of
Rust: a sans-IO protocol core, a clap front end, a simulated printer
Talks to
Moonraker over HTTP. No SSH, nothing installed on the printer
Tests
137 golden files; none of them needs a printer
Source: not public yet
Command lineFour slots, four filaments. Slot 1A is feeding the printer, the box is eight hours into drying at 55 °C, and nothing can stand in for anything else.
Command lineStatus and sensors: firmware, endstops, the heater at 5% power, air at 6% relative humidity.
Command lineAn afternoon’s work: read every tag, name a colour, cool a running dry by five degrees, preview a sync.

Workshop

jbuilder

Click a fillet. Land on the line of Python that made it.

build123d lets you write a solid model as a Python script. That is wonderful until the part has forty faces and you need the name of one of them.

jbuilder watches the script. Save it from any editor and the model rebuilds. Click a face, an edge or a vertex in the viewer and the source panel scrolls to the line that created it, because a trace hook was taking notes while your script ran. An untouched face keeps its line through every later cut and fillet.

Clicking also answers the harder question: how do I refer to this thing in code? jbuilder generates candidate selector expressions, evaluates each one, and keeps only those that resolve to exactly the shape you clicked. Up to three come back, simplest first.

The selector suggestions are a plain algorithm. Every one offered has been run and found to be right.

Type a selector of your own and the matches light up as you type, with a count. Click a line number and the viewport shows only what that line built. Call show() anywhere in the script to leave a named, coloured checkpoint; it costs nothing until somebody clicks it.

There is a constraint sketcher that opens on any flat face, a scratch buffer that shares the model’s namespace, and a neovim plugin that turns build errors into ordinary diagnostics and makes the viewer follow your cursor.

Surfaces
A web viewer, a desktop window, jbuilder-cli, and a neovim plugin
Made of
Python with FastAPI and build123d; Three.js and CodeMirror with no build step
Exports
STL, STEP, BREP, glTF and 3MF
Rule of the house
jbuilder never edits your model code
Source: not public yet
BrowserA wall bracket for a desk lamp. The fillet between arm and plate is selected, and the source panel has gone to line 52: part = fillet(root, 5).
BrowserThe selector panel: one expression, 46 matches, every circular edge glowing.
BrowserLine 29, previewed. Only the arm that line extruded is on screen.
BrowserThree checkpoints overlaid in x-ray: the profiles the part was built from.
BrowserSketching on a face, with constraints, a named parameter and the generated code alongside.
TerminalIn neovim: three verified selectors for the face picked in the viewer, ready to insert.

Terminal

jcat

cat, for pictures. Then somebody pointed it at a CAD file.

A terminal that speaks the kitty graphics protocol can show real pixels. jcat is a small viewer built on that: zoom with the wheel, pan with a drag, or use hjkl like a civilised person.

The image is uploaded once. After that each zoom or pan is one placement command of a few dozen bytes, and the graphics card does the scaling. Vector formats are rasterised at twice the size of the screen so they stay sharp when you lean in. Over SSH it notices, and streams the picture instead of handing the terminal a file.

Inside tmux none of that works, so it does something else entirely: Unicode placeholder cells, an image pyramid, and two image IDs taken in turn so there is never a blank frame. The implementation notes include a workaround for one tmux release that drops diacritics in panes away from the left edge.

Other programs borrow it as their image widget: --place draws one picture into a block of cells and leaves.

Then there is modelrender, a second small program that turns a 3D model into a sheet of views with the hidden lines properly removed. It rasterises a depth buffer only to answer what is visible, then writes the answer as vector paths. All nine views share one scale, so sizes compare across them.

Put the two together and jcat bracket.stl works. While FreeCAD tessellates, a cube made of braille dots tumbles to pass the time.

Surfaces
Two commands: jcat and modelrender
Made of
Python. Pillow and ImageMagick for jcat; trimesh, numpy and a headless FreeCAD for modelrender
Reads
Bitmaps, SVG, PDF, HEIC; STL, 3MF, OBJ, glTF; STEP, IGES, BREP and FreeCAD documents
Status
jcat calls itself a prototype, with notes written for whoever rewrites it
Source: not public yet

Without modelrender

TerminalA 4,800-pixel Julia set at 100%, panned into the spirals. The status line is all the chrome there is.
TerminalFitted to the window. n and p step through the files; a PDF and an SVG are waiting behind this one.

With modelrender

TerminalA six-body FreeCAD document, exploded, in nine views, in a terminal. The colours are read from the file.
TerminalThe same sheet at 100%, panned to the view from front right and above.
Sheetmodelrender alone, shading off: a hidden-line drawing fit for a manual.
SheetA 28-tooth spur gear in three chosen views, gold on dark, labels off.
Command lineListing the bodies in a document, rendering two of them, and a progress bar counting as FreeCAD works.

Desk

jpim

Your projects already live on the filesystem. The to-do list may as well move in with them.

Most organisers want to be the place where everything lives. jpim would rather be a thin layer over the directories, notes and source files you have already.

Write @jpim { kind = "todo" } in a comment, in any text file, and a todo exists. The file stays the source of truth: edit the line and the item follows. The scanner honours .gitignore, remembers what it has seen, and goes back over a 37 GB project in about two seconds.

It will not touch your files unless asked. Ask, and it writes an eight-character id back into the line, surgically, so the item survives being moved.

A bare date means the end of that day. Midnight would make a thing overdue for the whole day it is due.

One quick-add grammar works everywhere: varnish the tiller #boat @boatyard due:fri +marigold reads the same at the shell, in the tmux popup, in the terminal UI and on the web. Both interfaces have a : command bar that takes any jpim command, for when the mouse is too far away.

The terminal calendar is not a second implementation of the calendar. It is the CLI’s own renderer, handing over the same lines. Markers mean something without colour: a dot for an appointment, a diamond for a deadline, a triangle for overdue.

Places are named circles on a map, and a todo can wait at one.

Surfaces
Daemon, CLI, terminal UI, web app, desktop shell, a neovim plugin and a tmux popup
Made of
Rust: axum, SQLite, ratatui, Tauri. The web app is Svelte, embedded in the daemon
Endpoints
35, every one wrapped by a CLI command; a test fails the build otherwise
Status
Working on every surface it set out to have
Source: not public yet
BrowserToday, in the browser. Tags, places, overdue deadlines in red, and the files a todo points at listed beneath it.
TerminalAnd in the terminal: October with its agenda beside it. Every line of the grid is drawn by the CLI.
BrowserDirectives: every @jpim found in a project’s source, with its file, line and the TOML as written.
BrowserPlaces. Five circles on a map of Falmouth, each with the todos waiting there.
BrowserThe command bar, mid-sentence, with a place completed by Tab.
TerminalTodos in the terminal: overdue first, sub-todos indented, a dim mark on those that came from a file.
TerminalOne todo, opened: due date, tags, place and two numbered references to open.
Command lineNo interface at all: a week across the terminal, a day, and everything tagged #varnish.

Desk

jphone

The phone stays in the other room. Its texts, camera, contacts and whereabouts come to the keyboard.

Answering a text should not require picking up the device it arrived on. jphone puts the whole phone in one command and one terminal screen.

Five tabs: Messages, Contacts, Camera, Location, Status. Photos appear in the terminal as photos. The composer counts SMS parts while you type, and tells you which character just forced the message into the expensive alphabet, 70 to a part instead of 160.

Neither the terminal screen nor the web app ever reads a data file. Each tab is drawn from what the CLI answers and each action runs the CLI, so there is one implementation of everything and it can be scripted.

Unread is honest. A screen nobody looks at reads nothing.

The phone has no server to connect to. It dials in over SSH to a restricted login shell, and holds one long wait open as its push channel. Asleep on mobile data, it is woken by a single UDP packet, and is up from deep doze in two to four seconds.

Find-my-phone comes built in: a fresh fix, a recording of where it has been, and a ring at full volume even when silenced, with a “Found it” button for whoever digs it out of the sofa.

Comments and tags on a photo are written into the JPEG itself, so they go wherever the file goes.

Surfaces
A CLI, a five-tab terminal screen, a web app, and the Android app at the other end
Made of
Go and Bubble Tea on the desk; Svelte in the browser; Kotlin on the phone
Travels by
SSH over WireGuard, one command per request. No server port anywhere
Borrows
jcat to draw photos, jkarta to draw the map, jpim for what you owe each contact
Source: not public yet
TerminalMessages, in a terminal. Six conversations unread, one open, and its photo drawn in place by jcat.
BrowserThe same conversations in a browser: a group of four, the rule above what is new, and the composer.
PhoneAnd, should you insist, on a phone.
TerminalLocation. The phone’s state, three recordings, and a walk round Central Park drawn as a picture in the terminal.
TerminalCamera. Every photo and video with its tags and comment; p takes a new one from across the house.
TerminalReplying. The counter in the bottom line reads “1 part, 10 left”.
BrowserLocation on the web: a fresh fix, ring, record, and the same map, which the page did not draw either.
BrowserThe camera roll, with the comment and tag editor open on a lighthouse.
TerminalContacts, each with the notes and todos in jpim that mention them.

Machine room

jstatus

Everything worth glancing at, on one screen, laid out the way a tiling window manager would do it.

TerminalOverview. A clock, the machine, six hosts (one of them down for three hours and twenty-two minutes), the newest log lines from another machine and today’s appointments.

A dashboard is a tab. A tab is made of widgets, framed panels in nested boxes. Each computer gets its own, because a laptop and a server do not want to know the same things.

The machine widgets cover the processor and its history, memory, sensors, disks and their traffic, the network, the busiest processes. Others look outward: which hosts answer and how quickly, the newest entries in jlogger, the calendar from jpim, a large clock, and whatever any command you name cares to print.

Layouts are edited in place, with the keys of a tiling window manager. Split with v and s, shove a panel about with HJKL, mark two and swap them, undo. A wireframe shows the result at its real size in cells.

There is a daemon so that “down for 3 h” is true whether or not anybody had a dashboard open.

That daemon has no authentication and no open port. A dashboard reaches it through ssh -W, speaking HTTP down the pipe, so an SSH login is the gate.

Every panel gives up detail before it gives up space: a chart becomes a sparkline, then nothing; columns leave in a documented order. And the sensors widget stops polling an idle graphics card, because asking how warm it is would keep it awake for ever.

A bad config file does not stop anything. It becomes a tab with a warning sign on it.

Surfaces
A terminal UI, a small daemon that keeps watch, and a borderless desktop window
Made of
Go with Bubble Tea and Lip Gloss; the window is 280 lines of C, GTK and VTE
Configured in
KDL, one small file per dashboard, or not at all: there is an editor
Widgets
Sixteen, from cpu to command, which frames whatever a program prints
Source: not public yet
TerminalMachine: processor history in braille, two disks’ traffic mirrored, sensors and processes.
TerminalNetwork: ten hosts with latency sparklines, two world clocks, sixteen log lines.
TerminalThe editor: the layout as a tree, a widget’s properties, and a live wireframe.
TerminalA month drawn by jpim itself, and a tide table from a shell script, each framed like a native widget.
TerminalSplitting a panel: type to filter the sixteen kinds of widget.
TerminalEighty-four columns. Eight panels fold themselves down; open it to see the lot.

Machine room

jlogger

One place for every machine in the house to say what happened, and a hash chain to keep it honest.

Cron jobs, backup scripts, a greenhouse sensor, a printer finishing a job at four in the morning. They all have something to report, and syslog on six machines is six places to look.

Each entry is stored with the hash of the one before it, back to a first entry whose predecessor is the word genesis. Change any row and every hash after it is wrong; jlog verify names the first bad id. A second, append-only copy and a service account stripped of every capability make the chain harder still to rewrite quietly.

Anyone on the network may log. Clients that care can sign what they send, and the server checks the signature before it accepts a word.

Sending a log entry always exits zero. A log server that is down must never be the reason the backup did not run.

Log content is treated as hostile. It is stored byte for byte, so the hashes hold, but the viewers escape control characters and bidirectional overrides, so no message can paint over your terminal or forge the line above it.

The web viewer is rendered on the server and asks every three seconds for anything newer. Sort by something else and it stops inserting rows under you, and offers a count of what is new instead.

Surfaces
A daemon, the jlog CLI, a terminal viewer, a web viewer, and a zsh client for machines with nothing but jq
Made of
About 2,600 lines of Go, SQLite in pure Go, server-rendered HTML
Trust
A SHA-256 chain over every entry; Ed25519 signatures for clients that want them
Runs on
amd64, arm64 and armv7, packaged for Arch and Debian
Source: not public yet
BrowserWarnings and worse, worst first. Somebody lost mains power; a soil probe has been lying about the tomatoes.
Command lineThe chain, verified; then one row edited behind the server’s back, and the chain broken at exactly that row.
BrowserOne host’s night: a backup from start to offsite sync, in Gruvbox, one of seven themes.
TerminalA signed entry in the terminal viewer, with its hash, its predecessor’s, and the verdict on its signature.

How this issue was made

The programs

Ten projects by one person and one LLM, written for one household: a workshop with a 3D printer in it, a sailing boat, a phone, and a great many terminals. They share habits more than code. Every one has a real command line. Daemons are small and say what they are. Data stays on machines you can put a hand on. Where two programs need each other, one runs the other’s CLI and reads what it prints.

The source

Not public yet. If a page here made you want to read the code behind it, that is exactly the sort of thing that might change it.

The paper

Static HTML, one stylesheet, a few dozen lines of script for the lightbox. The type is Noto Serif Display, cut down to the letters this issue uses and served from here. Nothing on this page phones anywhere.