Keep Notes on a Remote Server

Ledge can keep your notes on a server and run the app as the window onto it. The server holds the notes, spawns the shells, and keeps them running; the app draws them. The transport is ssh, so there is no account to make and no service to sign up for.

One server at a time per window. The connection bar above the workspace strip always names the one you are typing into, and a server can serve several of your devices at once.

Ledge has two ways to involve a remote machine, and they answer different questions.

The noteIts blocks
A host: line in the note (Run Code on Remote Hosts)Stays where it isRun over ssh on the host it names
A server connection (Keep Notes on a Remote Server)Lives on the serverRun on the server
BothLives on the serverRun on the host, which the server dials

This page is the second row. Run Code on Remote Hosts is the first, and the two compose: a note kept on your VPS can carry host: prod, and the VPS makes that outbound ssh connection, so the app never holds credentials for prod.

Add a server

Click the connection bar, or run "Notes On…" from the palette or the File menu. Choose Add, then fill in the fields.

FieldWhat it takes
NameAnything you want to see in the bar.
SSH destinationuser@host, a bare hostname, or a name from your ~/.ssh/config.
PortBlank unless sshd listens somewhere other than 22.
Sign in withA key or a password.
KeyA private key to offer, or blank to let your ssh config decide.

The port goes in its own field, not in the address: write ledge@vps, not ledge@vps:2222.

Leave it blank whenever you can. Blank means your ssh config decides, so an alias from ~/.ssh/config keeps whatever Port it already sets.

Ledge uses your ssh config both to fetch the host key and to connect. A server you reach through a jump host (ProxyJump or ProxyCommand) works the same way it does in a terminal. The jump host has to let you in without a prompt, with a key or your ssh agent, because Ledge has no terminal for it to ask in.

Ledge then fetches that machine's host key and shows you its fingerprint. Compare it against what the machine reports for itself, in a shell on that machine:

sh
ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub

Choose "It Matches, Add" only when the two agree. Ledge pins the key and refuses any future connection that presents a different one. There is no "connect anyway".

The pinned keys live in ~/.ledge/.client/known_hosts, separate from your own ~/.ssh/known_hosts so you can read and revoke them on their own. Your existing entries still work: a host you already trust needs no second pin.

A pinned key belongs to one address and one port. Change either and Ledge asks for the fingerprint again, because two sshd instances on one machine can offer different keys.

Add as many as you like. The list is the app's own and lives on this machine, so nothing about it is stored on any server.

Add a server from a pairing code

A pairing code saves the typing and the fingerprint check. On the server, run ledge pair (Ledge on Your Phone describes what it prints). Copy the link under the QR code, open Add Server here, and paste it into "Pairing code (optional)".

The account, host and port fill in from the code, and the host keys it names appear under the field. The button reads "Add" instead of "Continue": Ledge asks the server for its key and pins it only if it is one the code names, so there is nothing to compare by eye. A server that answers with a different key is refused, with the key it offered, and nothing is added.

A code never replaces a key you already pinned. If a server in your list at that address is pinned to a key the code does not name, the code is refused, and the message names that server. The code is for a different machine, or the server's key has changed: use "Check Key Again" on that server instead.

Paste the link only if it came from your own server. A code holds no password or key, but one made for a lookalike server would point Ledge at that server (Ledge on Your Phone says the same of a tapped link).

Show a pairing code for a server

A server in your list can hand its code to your phone without a terminal. Open Notes On…, and click the QR code icon on the server's row.

Ledge shows the code, then the account, host, port and host key it names, then the same code as a link. Scan the QR code with Ledge on your phone ("Pair with a code" on Ledge on Your Phone), or use Copy Link and paste it into another computer's Add Server form.

The code is made from what this computer already has: the address it dials and the host key you pinned. Nothing is sent to the server to make it, and it holds no password or key. The phone still signs in with its own key, which has to be in the server's authorized_keys, or with a password.

The phone has to reach the host by the same address this computer does. A server on your home network or tailnet works from a phone on that network or tailnet, and not from elsewhere. A destination that is an alias from your ~/.ssh/config means nothing to a phone: give the server its real address here first.

A server with no pinned key has no code, since the code names the key. Edit the server and use "Check Key Again" to pin one. A phone checks only Ed25519 and ECDSA host keys, so a server pinned to an RSA key has no code either.

Edit or remove a server

Every row in the picker carries three controls: a QR code icon to show its pairing code, a pencil to change it and a bin to remove it. Press ⌫ on a focused row to remove it without reaching for either.

Removing asks first, and the dialog says what goes with the record: the address, the host key you pinned, and the password if that connection uses one. The notes on that machine stay where they are. Adding the server back means comparing its fingerprint again.

Editing opens the same form. A rename, or a change of account on the same machine, saves in one step, because neither changes which machine the pinned key belongs to.

Changing the address to a different machine does not. The button reads "Continue" instead of "Save", Ledge asks that machine for its host key, and you compare the fingerprint again before anything is stored. A key pinned for one machine says nothing about another, and carrying it across would refuse every later connection with a warning about a changed host key.

Use "Check Key Again" when a server you already have has legitimately rotated its host key. It is the same fingerprint step, on a connection you keep.

The row for this computer cannot be removed or edited, and neither can the connection you are currently using: switch somewhere else first.

Sign in with a password

Choose "A password" in the form and type the password for that account on that machine. Ledge stores it and offers it on every connection, including the reconnects it makes by itself.

Use it when the machine has no key on it yet. A fresh VPS with a password is a machine you can reach today, and setting up a key afterwards is a change you make once. Keys are the better long-term answer, and switching a connection over to one later is one edit.

Ledge keeps the password in the system keychain and never in ~/.ledge: the macOS keychain on a Mac, Credential Manager on Windows, and on Linux the desktop's keyring (GNOME Keyring or KWallet) through secret-tool, which the libsecret-tools package provides. When ssh asks for it, ssh reads it from the keychain itself, so the password does not pass through Ledge on its way out.

Anything running as you on this computer can read that keychain item. That is the same reach a private key file in ~/.ssh gives, so a password here is neither safer nor less safe than the key it stands in for.

Removing the connection removes the password with it. So does switching that connection back to a key.

The password field is blank when you edit an existing connection, and blank means keep the one that is stored. Type a new one only when you want to replace it. If the new one does not work, Ledge puts the old one back and tells you the connection could not be reached.

Some servers do not allow passwords at all. If the machine reports "Permission denied", check PasswordAuthentication in its /etc/ssh/sshd_config before checking what you typed.

Restricting a key to Ledge does not apply to a password. That restriction is a line in authorized_keys, which is a file about keys.

Switch servers

The picker opens on the connection in use, so Enter means stay and moving somewhere else takes an arrow key first.

Switching closes every tab and opens that machine's instead. Nothing is lost: the tabs are on the other machine and come back when you switch back.

A connection that will not open costs you nothing. Ledge reaches the new machine before it lets go of the old one, so a typo or a sleeping laptop leaves you exactly where you were with the reason on screen. If the failure happens at launch, Ledge opens on this computer and the bar reads "not reachable".

Two machines at once

New Window in the File menu opens a second window, and each window is on its own machine. Switching moves one window; a second window is how you have a build box and a VPS open side by side.

A new window opens on this computer. Switch it wherever you like from inside it.

Each window is titled after the machine it is on, so the title bar reads "This Mac" ("This Computer", on Linux and Windows) or the name you gave the connection. On a Mac that is the name in the Window menu too, and on a window's tab when macOS merges your windows into tabs.

The manual's window is the exception, titled "Documentation". It reads the copy of the manual that ships with the app, so it stays on this computer whichever machine the window you opened it from is on.

Each window keeps its own tabs and panes, and the server remembers them: switch a window back to a machine you used before and its arrangement comes back. Ledge reopens every window you left open at the next launch, each on the machine it was pointed at.

Two windows on the same machine are the exception. Only one of them can be that machine's arrangement, so the second opens empty and does not overwrite the first.

Closing the last window quits Ledge.

Install the server

The other machine needs the server, which is the ledge-server package. One command installs it, on Linux or a Mac. Run it in a terminal on that machine, signed in as the account Ledge will sign in to:

sh
curl -fsSL https://ledge.sh/server.sh | sh

It needs no sudo and nothing installed first. It downloads the package and a Bun of its own, checks both against the checksums written into the script, and puts them in ~/.ledge/.server in that account's home. Ledge's ssh command looks in ~/.ledge/.server/bin before anything else, so an incoming ssh finds the server with no change to the PATH. The script also adds a PATH line to the shell's startup file, so your own new terminals find ledge too.

It refuses to run as root, because the server belongs to the account Ledge signs in to. To install for another account, such as one named ledge, run it through sudo:

sh
curl -fsSL https://ledge.sh/server.sh | sudo -iu ledge sh

Running the same command again updates the server. A server that is already running goes on serving until it exits on its own, a minute or more after the last device disconnects, and the next connection starts the new version. Tutorial: Set Up a Ledge Server walks through the install on a fresh VPS, with an account for Ledge and the sshd hardening this page describes further down.

A computer that runs the Ledge app needs none of this. "Install Shell Command (ledge)" in the app's command palette puts ledge in ~/.ledge/.server/bin, where an incoming ssh looks first, pointing at the app's own copy. Signing in as that account then reaches the notes the app shows, with the app's server answering both. The machine also needs an ssh server: Remote Login on a Mac ("Expose ssh carefully"), or the openssh-server package on a Linux desktop.

macOS and Linux are supported, on arm64 or x64. On Linux the floor is glibc 2.29, which means Debian 11, Ubuntu 20.04, RHEL 9, or anything newer. Alpine and other musl systems are not supported. Windows itself is not either: the Windows app runs its server in WSL, which is Linux.

Nothing else has to be installed and no port is opened. Ledge speaks its protocol over ssh's stdin and stdout.

Blocks need zsh or bash on that machine. Ledge spawns the account's login shell when it is one of those, and otherwise the first of the two it finds, so an ordinary Linux install needs nothing extra. Where neither exists, a run refuses and names the shell it looked for instead of appearing to do nothing.

Install the server with Bun

If the machine already has Bun and you would rather use it, the same package installs with bun add -g. Where Bun goes decides where the server goes, because Bun puts global commands beside itself, and both have to be on the short PATH an incoming ssh gets. On Linux, that means Bun in /usr/local:

sh
curl -fsSL https://bun.sh/install | sudo BUN_INSTALL=/usr/local bash
sudo BUN_INSTALL=/usr/local bun add -g ledge-server

The variable on the second command is not optional. Without it the package installs into the home directory of whoever ran it, which is not a directory an incoming ssh searches.

A Mac is different: an ssh command there searches only /usr/bin, /bin, /usr/sbin, and /sbin, and none of those can take new files. Install Bun for the account instead, and add its directory to ~/.zshenv, which zsh reads for commands run over ssh:

sh
curl -fsSL https://bun.sh/install | bash
echo 'export PATH="$HOME/.bun/bin:$PATH"' >> ~/.zshenv
source ~/.zshenv
bun add -g ledge-server

Run them as the account Ledge signs in to. None of them needs sudo. The account's shell has to be zsh, which it is unless the account predates macOS Catalina.

Updating is bun add -g ledge-server@latest, with the same sudo BUN_INSTALL=/usr/local in front on Linux.

Check that ssh can find the server

Worth doing once, because Ledge reports the failure it catches as a server that is not installed. A remote shell that cannot find a command says only that, so that is all the app has to go on.

Ledge starts the server by running PATH=$HOME/.ledge/.server/bin:$PATH ledge serve over ssh. A command run that way skips the startup files a terminal reads, so ledge has to be in ~/.ledge/.server/bin or on the short PATH an incoming ssh gets. From your Mac's own terminal, ask the machine the same question:

sh
ssh you@machine 'PATH=$HOME/.ledge/.server/bin:$PATH command -v ledge'

A path printed means the machine is ready to add.

Nothing printed after server.sh means it ran as a different account from the one you signed in as. Run it again as that account.

After an install with Bun, check bun as well, since the package starts with it: ssh you@machine 'command -v bun'. On Linux, nothing printed means Bun is installed for one user rather than in /usr/local, which is what a machine that already had Bun before you started usually has. Its global commands are then in ~/.bun/bin, which an incoming ssh does not search, and bun pm bin -g on that machine confirms where they went. Linking both names into a system directory, on that machine, fixes it without reinstalling anything:

sh
sudo ln -s "$(bun pm bin -g)/ledge" /usr/local/bin/ledge
sudo ln -s "$(command -v bun)" /usr/local/bin/bun

On a Mac, nothing printed means the ~/.zshenv line is missing, or the account's shell is not zsh. The symlinks do not help there, since ssh on a Mac does not search /usr/local/bin.

Build the server from a checkout

Only if you want a build of your own. The package is the ordinary way. From a checkout of the repository:

sh
bun run build:native
bun build src/bun/serve.ts --compile --outfile ledge

Copy ledge and dist-native/libledge_pty.so to the server, side by side, somewhere on the PATH. Build it on a machine of the same architecture as the one that will run it, since a compiled binary is one architecture and the package is the thing that carries all of them.

The .so holds two C functions the terminal needs. Without it beside the binary, resizing a terminal does nothing and a shell that stops reading can stall the server.

Restrict the key to Ledge

Optional, and worth doing on a server you care about. Ledge connects with an ordinary key either way and never edits this file for you.

Restricting gives the server a key that can speak Ledge's protocol and nothing else. In that machine's ~/.ssh/authorized_keys:

restrict,command="PATH=$HOME/.ledge/.server/bin:$PATH ledge serve" ssh-ed25519 AAAA... ledge@laptop

The command is the one Ledge itself runs, so it finds the server wherever the check above found it. sshd runs this line instead of whatever the client asked for.

That key cannot forward a port, run scp, or open a shell over ssh. What it limits is what the key is good for if it is ever stolen: no route into the network behind that server, and no file copying.

It does not limit Ledge. Blocks in your notes still run, because running them is what the protocol does, so anyone holding that key can run code on that machine. Restricting narrows what else they could do with it.

If you also want to use ssh directly to that machine from a terminal, keep your usual key there as well. The restricted line is for Ledge alone.

A phone's key arrives already restricted. The line its pairing screen hands you carries this prefix (Ledge on Your Phone).

Expose ssh carefully

A Ledge server executes the code in your notes. Anyone who can authenticate to it can run anything you could.

On a VPS, bind sshd to a private interface rather than to the public internet. In /etc/ssh/sshd_config:

ListenAddress 100.x.y.z
PasswordAuthentication no

Use the address your VPN or tailnet gives the machine. A Ledge server on 0.0.0.0 is a box on the public internet whose purpose is running code.

PasswordAuthentication no and Ledge's password sign-in are the same setting seen from two ends, and the order to do them in is: use a password to reach the machine, put your key on it, then turn passwords off. A box reachable from the internet should not be answering password attempts from it.

On a Mac, the server needs Remote Login turned on in System Settings, under General then Sharing. Restrict it to specific users while you are there.

What lives on the server

ServerThis app
Notes, images, and the trashWindow size and position
Workspaces, and where each attached folder isThe clipboard
Shells, running blocks, and scrollbackWhich servers you have added and their pinned host keys
The vault and locked notesAny stored passwords, in this computer's keychain
Profiles and their secretsThe ledge command, which reads this computer's notes whichever server the window is on (The ledge CLI)
The settings for the shell, interpreters, the trash, and daily notesThe settings for the theme, font sizes, live preview, and update checks

Settings (⌘,) has a tab for each column, Server and This app, and each tab is its own file. The This app half follows you between machines. The Server half describes the machine it is on, because a VPS's shell is not your laptop's.

A computer that runs the app is a server too. The app starts one of its own, so with no server added both columns are on this computer, and the Server tab edits this computer's file. Add a server and the left column moves there with your notes: the Server tab edits that machine's file, and so does "Edit Note Profile…".

Server settings apply when the server starts. A server the app or a phone started restarts with the new file once nothing is connected to it, so quitting Ledge and opening it again applies a change. While another device is still connected, the server keeps the settings it started with, and Ledge says so when it opens. A server you started yourself, or one a service manager runs, applies them when you restart it.

Profile values never cross the connection. A note names a profile and the server reads the file at spawn, so the secrets exist only where the commands run (Profiles and Secrets).

Unlocking a locked note sends the passphrase to the server, which is the only machine that can use it (Note Locking). The vault and its idle relock timer stay there.

Each device unlocks for itself. Typing the passphrase on your computer does not open the locked notes on your phone, and ⌘L on one leaves the other reading. Every window on the same computer shares one unlock.

Back up the server

ledge backup puts an encrypted copy of everything the server holds into an S3-compatible bucket, on a schedule the server keeps itself. Tutorial: Back Up Your Notes to S3 sets it up with one command. This section is what the verbs do.

Run them as the account the server runs as, on the machine the server runs on.

VerbWhat it does
ledge backup setupAsks for the bucket and its key, fetches restic if none is installed, writes the credentials to the backup profile, creates the repository, and takes the first backup. --existing joins a repository that already has backups in it, and takes no first backup, since a machine that has just joined has nothing on it to back up.
ledge backup nowTakes a backup and thins old snapshots.
ledge backup statusWhen the last backup ran and how it went, when the next is due, and any attached folder the last run could not find.
ledge backup snapshotsThe snapshots in the repository, newest first.
ledge backup restorePuts files back from a snapshot, into a fresh folder under your home or, with --in-place, where they were.
ledge backup pathsThe paths a backup of this machine covers, for a backup tool of your own.
ledge backup resticRuns restic itself with the backup's repository and credentials.

What a backup covers is decided at every run, because only the server knows it: the app home, every workspace folder you attached from elsewhere on the machine, and the profiles directory. Inside the app home it skips the daemon's socket and pidfile, the logs, the copy of the manual, and the installed server in .server. An attached folder that is not on disk at the time, on an unmounted volume say, is skipped, said on stderr, and shown by status until it is back.

Backups run every hour while the server is up, and once more before it exits: after the app closes on a desktop, or after the last device disconnects from a VPS. The repository keeps 24 hourly, 30 daily, 12 weekly, and 24 monthly snapshots, and the rest are dropped.

Three things to know before you rely on it:

  • Keep the restic password somewhere other than this machine, such as a password manager. setup prints it once. It is the only key to the backup, and nothing can be restored without it.
  • The backup holds secrets. Profile values are plain text on disk and so are unlocked notes, which is why restic encrypts before uploading. The bucket sees ciphertext only.
  • Locked notes and the vault travel together. .vault.json is inside the app home, so it is always in the backup, and a restore opens locked notes with the passphrase they had (Note Locking).

The repository and its credentials live in the backup profile, ~/.config/ledge/profiles/backup.env, outside the app home (Profiles and Secrets). A note with profile: backup runs restic by hand with the same variables.

For a backup tool of your own, ledge backup paths prints one absolute path per line:

FlagWhat it prints
noneThe paths to back up.
--excludeWhat to skip inside them.
--no-secretsThe same list without the profiles directory.
--jsonBoth lists, plus any registered folder that is not on disk.

What a provider snapshot does not do

Most hosts offer whole-server backups. Run both if you like: a snapshot restores a machine, and the backup above restores your notes.

Provider snapshotThe backup above
Smallest thing you can restoreThe whole serverOne note, one version
Where the copy livesThe account that holds the serverAny bucket you choose
Moves to another hostNoYes

A snapshot also lives in the account that pays for the server, so a lost login or a lapsed card takes the notes and the copy together.

Several devices on one server

A server serves every device that connects to it. Your computer and your phone can both be on the same server at once, reading the same notes and running commands (Ledge on Your Phone).

Each device keeps its own tabs and panes. The server files them under the device that arranged them, so a phone does not open into a desktop's three-pane layout.

A second Ledge window counts as another device here. Point two windows at one server and each is listed in the other's connection bar, and a note's terminal has one owner between them, exactly as a desktop and a phone would.

The connection bar shows who else is connected: one other device by name, more than one as a count. Hover it for the full list. Names come from the devices themselves, so a desktop uses its computer name, and a device that gives no name reads as "another device".

Nothing appears there when you are the only one connected. A computer that runs the app is a server of its own, so a phone signed in to it, or a second window on it, appears there the same way.

A note saved on one device appears on the other without a refresh. Everything else a server owns is shared the same way: the same workspaces, the same trash, the same tags and backlinks, the same vault.

Every device shows the same workspaces under the same names and icons. A workspace created, renamed, given an icon, or deleted on one device changes on the others straight away. What each device keeps for itself is the arrangement: the order of the workspaces, the split panes, and the open tabs, so a phone does not inherit a desktop's three panes.

The one thing two devices cannot share is a note's terminal.

Sharing a server with others

A server is one account, and everyone who connects to it shares that account. Anyone else on your server reads every note in every workspace, writes to any of them, and runs commands as the account the server runs as.

Locked notes stay shut on each device until somebody types the passphrase there. There is one vault and one passphrase, so whoever has it opens every locked note on the server (Note Locking).

Profiles are on the server, and a note names one by name. A note somebody else writes can run a block with your credentials in its environment, without ever opening the file (Profiles and Secrets).

Several people can work this way. They get the same notes, the same trash, and the same tags, and every save appears on every device. What they do not get is separation: no per-person access, no per-workspace permission, and no record of who changed a note.

For notes that belong to different people, give each person a server, or keep a clone each over git (Tutorial: Share Notes with a Git Clone).

Take a shell from another device

Opening a note's terminal on a second device moves the shell there, output and typing together. A shell has one keyboard: two devices typing into the same one would interleave their keystrokes on a single line.

The device that had it keeps the last of the output on screen behind a notice, which names the device that took the shell and offers a Take This Shell button. Press it and the shell comes back, along with everything it printed while it was away.

The shell is unaffected either way. It runs on the server throughout, so a build keeps building while the two devices take turns watching it.

Blocks are different: a block runs for the device that started it, and only that device sees its output panel or can stop it (Running Code).

Edit the same note on two devices

An open note you are not editing follows its file. Save that note on one device and the other device's copy updates on screen, with no refresh and no prompt.

A note being edited on both devices is settled when they save. The second save wins the file and the version it displaced goes to the workspace trash, so nothing is overwritten. The device that displaced it shows a notice in the sidebar naming the note, and the other version is in the trash until you empty it.

This is the same arbitration a note gets from any other writer, including an agent in the terminal, a git checkout, or a sync service (Tutorial: Keep Notes Synced).

When the connection drops

The bar reads "reconnecting…" and Ledge re-dials for about thirty seconds. Requests made in the meantime wait rather than fail.

A machine that has been asleep is asked about at once rather than after the usual twenty seconds, because a lid that opens usually opens onto a connection that ended hours ago.

A drop the network does not announce takes twenty to twenty-five seconds to notice. A connection that closes tells Ledge straight away. A network that simply goes away, such as wifi dropping or a laptop moving between networks, sends nothing at all, so Ledge asks the server every 5 seconds and treats three unanswered asks as a lost wire. The same goes for a server that stops answering while the network is fine. The bar still reads as connected until then, and anything you send in that window is held and sent again once the connection is back.

Anything running keeps running. Shells belong to the server and survive a wire dropping, so a build carries on while you are on a train and its output is waiting when you come back.

An open terminal stops taking what you type once the bar reads "disconnected", and covers itself with a notice saying so. Nothing typed at a machine Ledge cannot reach would get there, and a terminal that does not echo looks exactly like one waiting on a slow command, so the notice is what tells the two apart. The last output stays readable underneath it and can still be selected and copied. While the bar reads "reconnecting…" the terminal takes your typing as usual, and it lands when the connection does.

A terminal that was open the whole time fills in its own gap. Ledge asks that shell for its history the moment the connection is back and redraws it, so what printed while you were away is on screen with everything that came before it. The last 256 KB is what a shell keeps, on a reconnect exactly as on any other reattach.

Two other things can have become of that shell while you were away, and the terminal tells you which. Another device may have taken it, which reads the same as it always does: the notice, and the Take This Shell button. Or it may have ended, and the terminal closes rather than reopening on a fresh prompt with none of your work in it.

A block's output panel is the exception, and only across a restart. The panel lives in the page rather than on the server, so a wire that drops and comes back finds it still there with the run still going.

Once the bar reads "disconnected" that panel reads "Disconnected" too, rather than "Running". Ledge cannot see the machine, so it neither claims the block finished nor claims it is still going. The output that already arrived stays on screen and the word says why no more is appearing. When the connection returns the panel goes back to "Running" if the server still has that run, and finishes if it does not.

The block underneath it will not run again meanwhile. It may still be executing over there, and a second copy of a deploy is worse than a wait.

That panel stops taking what you type at it too. A block that was waiting on a password is the case worth getting right: the answer would go nowhere, and nothing would say it had. The line beside the header that reads "typing here" reads "not connected" instead. The caret stays in the panel rather than jumping back to the note, because the connection may be back in seconds, and the panel takes your typing again the moment it is.

What the block printed while you were away arrives when the connection does. The server holds it for you, in the order the shell said it, and the panel adds it to what was already there. A block that finished while you were away comes back finished, with its exit status, rather than closed out blank.

Two limits on that. The last 256 KB is what is held, so a very long outage over a very chatty build loses the middle and keeps the end. And the twenty seconds or so before Ledge notices the connection has gone are lost too: nothing can be held back until something knows there is a reason to.

You cannot start a run at all once the bar reads "disconnected". Every block's Run and terminal buttons go gray, hovering one names the machine that cannot be reached, and ⌘↩ answers with the same sentence. While the bar still reads "reconnecting…" they stay live, and a block run there really does run: the request waits for the connection like any other and goes as soon as it is back. This is the only thing Ledge refuses outright instead of trying and telling you what came back. A run is the one request with no answer to report: Ledge sends it and then listens for output, so a block sent to a machine that is not there would open a panel reading "Running" that nothing would ever correct.

A Ledge that has relaunched has no panel, and no way to show that run or stop it. So blocks left running on a server are stopped the next time Ledge connects to it, which includes switching to another connection and back. A terminal is not affected, because reattaching finds its shell where you left it.

This reaches only the blocks that device started. A server can be carrying runs for more than one of your devices, and a phone connecting does not stop what your computer left running.

A save that was in flight when the wire dropped is retried once the connection is back, and applied once, even if the first attempt had already landed.

Notes that changed on the server while you were away are re-read as soon as the connection is back. Another device saving, a checkout in a terminal, an agent writing to a note: none of that reaches you while the wire is down, so Ledge asks every open workspace for its list again and re-reads the notes you have open. A note somebody added appears in the sidebar, and an open note you had not touched pours in the newer text.

A note you were editing across a brief drop keeps exactly what you typed. If that note also changed on the server, the two are settled on your next save the same way any two devices editing at once are (see above): your version is saved, and the one it displaced goes to the workspace trash with a notice naming the note.

After a real outage it goes the other way, and it is worth knowing which way before it happens. Once the bar has read "disconnected", Ledge stops trying to save and holds what you type in the tab, with a red dot on it saying so. When the connection comes back, a note nobody else touched is simply saved. A note that did change on the server takes the server's version, and what you had typed goes to that workspace trash with a notice naming the note.

The reason it flips is that the argument for keeping your version is that you are the one at the keyboard, which is exactly what an outage undoes: a laptop shut in a bag for a day, while the same note is edited from a phone, has the older text and the emptier claim to it. Neither version is thrown away either way, and restoring from the trash puts the copy next to the live note rather than over it, so you can merge the two yourself.

Locked notes relock while you are away, and Ledge catches up the moment the connection is back. The vault belongs to the server and shuts itself after 15 minutes with nothing to do (Note Locking), and a connection that is down is 15 quiet minutes. So a locked note you had open goes back to its placeholder on the reconnect, exactly as if you had pressed ⌘L, and unlocking again pours it back. Unlocking on another machine reaches you the same way: a note sitting behind its placeholder opens as soon as the connection is back.

Anything you typed into a locked note during the outage goes with it. That text could not have reached the disk either way, because writing a locked note needs the vault open, so copy it somewhere else before you reconnect if you want to keep it.

If the re-dialling runs out, the bar reads "disconnected" and Ledge stops accepting work for a machine it cannot reach. It keeps trying underneath, every thirty seconds, for as long as the window is open, so a laptop that wakes up on a working network reconnects itself with nothing for you to do. Clicking the bar tries immediately instead of waiting for the next attempt.

Ledge also tries the moment your machine wakes and the moment your operating system says the network is back, which are the two times a connection that has been failing for hours suddenly works.

Reconnecting to a server that has restarted meanwhile works too, and it is what usually happens after a long sleep: a server with nobody connected shuts itself down after a minute, and connecting starts a fresh one. Your notes are on its disk and are all there. What does not survive is anything that was only in the old server's memory, so shells and running blocks are gone, and the terminal and any output panels say so rather than showing you a prompt that is not there.

A short absence keeps them. Ledge asks every server it connects to hold its shells for five minutes after the connection ends, which covers a lift, a lid closed for a meeting, or a walk between buildings.

Ledge also stops when the server hangs up on purpose rather than the wire failing, and hovering the bar says why. The reason is a second copy of Ledge on this same device connecting to it: the server keeps the newer connection and tells the older one, which stops instead of the two taking the server off each other in a loop. Another device connecting is not a reason (see above).

Limits

  • No accounts. A server is one account's notes, and anyone who can connect to it has all of them (see above).
  • One connection per window. Search, tags, backlinks, and wikilinks all stay within the machine that window is on. Open a second window for a second machine (see above).
  • One device at a time in a note's terminal. Everything else on a server is shared by every device connected to it (see above).
  • No moving a note between servers from inside the app. Use rsync or git; the notes are ordinary files (Tutorial: Keep Notes Synced).
  • No offline editing. The server has to be reachable to open a note.