Ovvi

Labels and instance ids

How a plugin pane is stored in herdr: label, placeholder and instance id

Every browser and plugin pane is stored in herdr this way (Browser, File Explorer and Git). The plugin id is the manifest's id (The manifest).

Label format

label     = "ovvi:" plugin-id "#" iid [ " " arg ]
plugin-id = [a-z0-9][a-z0-9-]{0,63}
iid       = [a-z0-9]{6}
arg       = the rest: no control characters, at most 1024 characters

Examples, as a herdr TUI user sees them in the pane border:

ovvi:git-history#k3f9q2 main
ovvi:explorer#q2m0zd /Users/me/code/ovvi/README.md
ovvi:git#a81xw0 /Users/me/code/ovvi/ uncommitted
ovvi:browser#m4p8sa http://localhost:3000/dashboard
  • The ovvi: prefix keeps a user's own label (todo: fix login) from turning into a plugin.
  • The argument goes last, so the useful part is what a TUI user reads. herdr trims labels.
  • Labels travel in every snapshot to every device, so the argument stays small (under ~200 characters advised). Bigger state goes to a file (State).

Instance id

The instance id (iid) names the pane's state file (State). terminal_id changes on a herdr cold restart, and the label survives it, so the iid lives in the label.

  • The device that creates the pane generates 6 characters of [a-z0-9], checked against the snapshot. It never changes.
  • The card itself is keyed like every card, by (machine, session, terminal_id) (Reconcile). Two panes with a copied label share one state file, last writer wins.

Create

Browser and plugin panes are created the same way.

  1. A pick (New pane, the palette) → the argument from the pane's folder → generate the iid.
  2. layout.apply without a tab_id makes a new tab with one pane. herdr starts the pane with its label and its placeholder command in one call:
    layout.apply {workspace_id, focus:false,
                  root:{type:"pane", cwd, label:"ovvi:<plugin>#<iid> <arg>",
                        command:["sh","-c",<script>,"sh",<title>,<arg>]}}
    • cwd is the folder of the label's argument when it is a path (a file's folder, a root): herdr saves it and brings the pane back after a restart only when that folder is still there, so such a card is not tied to the folder it was opened from. A URL card, or one with no argument (a plugin with no opens, which uses it), starts in the source pane's folder.
    • The script prints the title, the argument without its scheme and "Shown in Ovvi" in the middle of the pane. On SIGWINCH it reads the label's argument from herdr (herdr --session "$HERDR_SESSION" pane get "$HERDR_PANE_ID", with herdr's own HERDR_BIN_PATH) and draws again: on a resize, and when the app signals its processes by pid after the label changed (a browser's new address). herdr still finds a pane by its old id after a move to another space. A placeholder from before the app started shows its old argument until its label changes. It runs stty raw -echo and a background cat <&3 >/dev/null (standard input copied to descriptor 3 first: dash gives a background job /dev/null), and waits on it. stty raw makes Ctrl-C and Ctrl-D plain bytes, so a herdr TUI user cannot end the pane by accident (checked on 0.9.5).
    • herdr runs the argv itself. No login shell, quoting or envelope is involved: the title and argument are plain argv ("$1", "$2").
  3. For a new tab, that is all. For a split, pane.move {pane_id, destination:{type:"tab", tab_id, split, target_pane_id}, focus:false} puts the pane in place, and the temporary tab closes itself. For left or top, then pane.swap, as for drops.
  • Other devices can see the temporary tab for one snapshot. The device that creates the pane hides it by its label until it is in place: until a snapshot after the move has it in the split. A ⌘T's new tab never takes the temporary tab's place. A pane with no label (Resume beside, ovvi agent beside) needs none of this: pane.split makes it in place, in its folder (cwd).
  • pane.created already carries the label, so other devices draw the card at once.

Never pass tab_id

layout.apply with a tab_id replaces the whole tab and kills its panes. The app never passes it.

The label is the truth

The placeholder is cosmetic, and the app relies on nothing in it. It shows herdr TUI users and devices without the plugin what the pane is. If the placeholder process is killed, herdr closes the pane. Closing a browser or plugin pane never asks, because it runs only its placeholder (the app knows it by its label).

The placeholder

Every browser and plugin pane runs the placeholder. Three kinds of pane come without it: after a cold restart herdr brings each pane back as a plain shell with its label (it saves the command, but runs it again only after a live update), or with no program at all when its saved folder is gone ("Saved directory is unavailable"); and a pane that an agent or a person labels by hand has the shell it was made with.

  • Each time the app learns what a pane runs (pane.process_info), a browser or plugin pane with only its shell in front gets the placeholder: the app sends ⌃U as a key (pane.send_keys; it clears a line half typed in herdr's TUI), then types exec and the placeholder's argv (pane.send_input), in the base64 envelope that every login shell passes on (Running commands), and Return. The placeholder takes the shell's place, so the pane runs only the placeholder.
  • It looks again when the pane's label changes, and once more 5 s after it found another program in front (a slow shell startup after a restart).
  • Once per shell process (its pid). A pane that runs any other program gets nothing typed. The line starts with a space, which keeps it out of a history that ignores such lines. A shell's own question that waits for a line (an update prompt in a .zshrc) looks like the shell, and gets the line.
  • ⌃U goes as a key, not in the text: herdr wraps text in bracketed paste when the shell turned it on (zsh, bash 5.1 and later, and fish at their prompt), and a pasted ⌃U is a plain character, so the line fails with command not found: ^U (checked on herdr 0.9.5).
  • A pane that came back with no program gets a new pane with the same label and the placeholder in its place, as Replace Pane makes one, once per pane.

Choices

  • The label marks the pane. It is durable and readable, and survives moves and cold restarts. Rejected: metadata tokens (capped at 80 characters, lost on restart); a pane list kept by the app.
  • A placeholder pane made in one call. Zero app state, and one layout for every device and the herdr TUI, with no race with shell startup. The app types the placeholder only into a pane that came back without it. Rejected: typing the placeholder into a new shell that the app makes; plugin UI drawn by the app outside herdr's layout; a placeholder that reports itself as an agent.

On this page