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 charactersExamples, 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.
- A pick (New pane, the palette) → the argument from the pane's folder → generate the iid.
layout.applywithout atab_idmakes 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>]}}cwdis 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 noopens, 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
SIGWINCHit reads the label's argument from herdr (herdr --session "$HERDR_SESSION" pane get "$HERDR_PANE_ID", with herdr's ownHERDR_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 runsstty raw -echoand a backgroundcat <&3 >/dev/null(standard input copied to descriptor 3 first: dash gives a background job/dev/null), and waits on it.stty rawmakes 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").
- 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, thenpane.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 agentbeside) needs none of this:pane.splitmakes it in place, in its folder (cwd). pane.createdalready 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 typesexecand 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.