Per-place tab-set persistence, shared by EVERY TUI running there.
The set of open tabs is the thing you want back when you return to a place: launch vis in a directory, restore the tabs you had there. The sessions themselves already persist in the DB (keyed by session id); this only records, per launch directory, WHICH sessions are open as tabs, their order, and which one was active.
Stored as a small EDN sidecar under ~/.vis/tabs/<place>.edn — no schema,
no migration, entirely owned by this channel. Shape:
{:active "<session-id>" ; the focused tab (or nil) :sessions [{:id "<session-id>" :root "/abs/project-root"} …] ; MERGED tab set across every live TUI in ; this place, left to right; :root is the ; tab's PROJECT root (absent when unknown) :clients {"<pid>" {:active … :sessions […] :at <ms>} …}} ; one entry per TUI process that saved here
MULTI-TUI SYNC: several TUIs can run in the same place at once, and each
used to clobber the sidecar with only its own tabs (last writer wins).
save! now MERGES instead of overwriting (see merge-clients): this
process replaces only ITS :clients entry, keeps every sibling whose pid
is still alive, and prunes dead pids — so a crashed/exited TUI stops
pinning tabs while a live sibling's tabs survive our save. The top-level
:active/:sessions pair is the merged, legacy-compatible view the
restore path reads. Merging is read-modify-write without a file lock; a
racing save can momentarily drop a sibling's entry, which that sibling's
next save restores — best effort by design, like the IO handling below.
Legacy snapshots stored plain id strings in :sessions (and carried no
:clients); readers go through snapshot-session-ids, which tolerates
both shapes.
Per-place tab-set persistence, shared by EVERY TUI running there.
The set of open tabs is the thing you want back when you return to a
place: launch vis in a directory, restore the tabs you had there. The
sessions themselves already persist in the DB (keyed by session id); this
only records, per launch directory, WHICH sessions are open as tabs, their
order, and which one was active.
Stored as a small EDN sidecar under `~/.vis/tabs/<place>.edn` — no schema,
no migration, entirely owned by this channel. Shape:
{:active "<session-id>" ; the focused tab (or nil)
:sessions [{:id "<session-id>" :root "/abs/project-root"} …]
; MERGED tab set across every live TUI in
; this place, left to right; :root is the
; tab's PROJECT root (absent when unknown)
:clients {"<pid>" {:active … :sessions […] :at <ms>} …}}
; one entry per TUI process that saved here
MULTI-TUI SYNC: several TUIs can run in the same place at once, and each
used to clobber the sidecar with only its own tabs (last writer wins).
`save!` now MERGES instead of overwriting (see `merge-clients`): this
process replaces only ITS `:clients` entry, keeps every sibling whose pid
is still alive, and prunes dead pids — so a crashed/exited TUI stops
pinning tabs while a live sibling's tabs survive our save. The top-level
`:active`/`:sessions` pair is the merged, legacy-compatible view the
restore path reads. Merging is read-modify-write without a file lock; a
racing save can momentarily drop a sibling's entry, which that sibling's
next save restores — best effort by design, like the IO handling below.
Legacy snapshots stored plain id strings in `:sessions` (and carried no
`:clients`); readers go through `snapshot-session-ids`, which tolerates
both shapes.(merge-clients disk client snapshot now-ms alive?)Merge THIS process's tab snapshot ({:active sid :sessions […]}) into the
sidecar disk may already hold, as client client. Pure — liveness is
injected via alive? (client-id → boolean) so multi-TUI scenarios are
unit-testable without real processes.
client entry is replaced wholesale (tabs closed HERE disappear),alive? holds (a crashed or exited
TUI stops pinning tabs),:sessions lists OUR tabs first (order and
:active win), then each surviving sibling's tabs (oldest save first),
deduped by session id,:clients contributes nothing — exactly the
pre-sync overwrite semantics (a dead process's tabs self-heal away).Merge THIS process's tab `snapshot` ({:active sid :sessions […]}) into the
sidecar `disk` may already hold, as client `client`. Pure — liveness is
injected via `alive?` (client-id → boolean) so multi-TUI scenarios are
unit-testable without real processes.
- our `client` entry is replaced wholesale (tabs closed HERE disappear),
- sibling clients are kept only while `alive?` holds (a crashed or exited
TUI stops pinning tabs),
- the merged top-level `:sessions` lists OUR tabs first (order and
`:active` win), then each surviving sibling's tabs (oldest save first),
deduped by session id,
- a legacy sidecar without `:clients` contributes nothing — exactly the
pre-sync overwrite semantics (a dead process's tabs self-heal away).(read-snapshot)(read-snapshot f)Read this place's persisted tab snapshot, or nil when absent/unreadable.
Returns only the well-formed shape: a map with a vector :sessions (the
merged multi-TUI view; per-client entries ride under :clients).
Read this place's persisted tab snapshot, or nil when absent/unreadable. Returns only the well-formed shape: a map with a vector `:sessions` (the merged multi-TUI view; per-client entries ride under `:clients`).
(save! snapshot)(save! snapshot f)Persist snapshot ({:active sid :sessions [{:id sid :root root} …]}) for
this place, MERGED with every live sibling TUI's entry instead of
overwriting it (see merge-clients). Best effort — IO failures are
swallowed so a read-only home never breaks the TUI.
Persist `snapshot` ({:active sid :sessions [{:id sid :root root} …]}) for
this place, MERGED with every live sibling TUI's entry instead of
overwriting it (see `merge-clients`). Best effort — IO failures are
swallowed so a read-only home never breaks the TUI.(snapshot-session-ids snapshot)Ordered session ids from a snapshot's :sessions, tolerating BOTH entry
shapes: the current {:id … :root …} maps and legacy plain id strings.
Ordered session ids from a snapshot's `:sessions`, tolerating BOTH entry
shapes: the current `{:id … :root …}` maps and legacy plain id strings.cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |