Files
l0ng-aiandl0ng-ai 425f87e9a4 fix(core): key the machine tree to the config directory (#462)
* fix(core): key the machine tree to the config directory

The tree resolved from $HOME while everything else an instance owns —
views.json, the scrollback, the history, both sockets, the pidfile, and
daemon.lock — resolved from the config directory. So --config-dir moved
every part of an instance except the one that says which workspaces
exist, and two tty7s pointed at different config directories, each
holding its own lock and each certain it was the only server on the
machine, still co-owned one ~/.local/share/tty7/machine.json.

MachineStore::persist writes the document whole. The second one to flush
replaced the first one's workspaces with its own, and the next daemon to
start read the survivor's tree as the machine's. An empty tree is not
distinguishable from a machine that really has nothing on it, so the GUI
does what an empty tree means and forgets those workspaces for good.

A lock and the thing it protects have to be keyed alike. data_dir() now
follows the config directory; TTY7_DATA_DIR stays as the highest-priority
override so the test harnesses keep their sandboxes.

Moving the path without carrying the file would lose every workspace at
the moment of upgrade, which is the failure this change exists to stop,
so the daemon adopts the legacy file on startup before it opens the
store. The destination already existing is the whole guard: it means a
newer run owns the tree and the copy at the old path is stale, from a
build that predates the move and still writes where it believes the tree
lives. Adopting that over the live file would hand the old tree back.

* fix(core): only the machine's own instance inherits the legacy tree

The migration moved `machine.json` into whichever config directory started
first. In the very setup this change exists to fix — a default install beside a
`--config-dir` one — that is the second instance renaming the machine's tree
into its own directory, leaving the primary to come up owning nothing. It also
fired in our own test suite, where `routed_pane` and friends launch a real
`tty7-server --config-dir <TempDir>` under the developer's own `HOME`.

Adoption is now the entitlement of the instance running out of the config
directory this machine resolves to on its own: `$TTY7_CONFIG_DIR` where the box
names one, `$HOME`'s otherwise. Comparing paths rather than asking whether
`--config-dir` was passed is what keeps the ordinary install working — `spawn`
hands every daemon it starts an explicit `--config-dir`, its own included — and
counting `$TTY7_CONFIG_DIR` is what keeps remote hosts upgrading, since a remote
`tty7-server` is launched without the flag and finds its directory that way.

Also tightens the cross-filesystem fallback: a rename that failed because
another process already carried the file over is the one benign race, not an
error to report and not something to copy over. What is left copies through
`create_new`, so "never overwrite what is already there" holds against a racing
writer and not merely against an `exists` check several syscalls old, and a
write that does not finish leaves nothing behind.

Tests: the gate both ways, the appearance hint riding along, the same directory
under two names, the copy path refusing an occupied destination, and two
cross-process cases in `machine_tree` that start a real server under a scratch
`HOME` — one carrying the legacy tree in, one leaving it alone.

---------

Co-authored-by: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com>
2026-08-10 17:12:26 +08:00
..