Skip to content

feat(session): keep the active session across daemon hide/show - #408

Merged
devmobasa merged 14 commits into
mainfrom
feat/daemon-session-continuity
Oct 5, 2026
Merged

devmobasa merged 14 commits into
mainfrom
feat/daemon-session-continuity

Conversation

@devmobasa

Copy link
Copy Markdown
Owner

Return to the active session when the daemon's overlay is hidden and shown again, with an explicit way back to the daemon's home session.

Changes

  • The daemon remembers the session its overlay last committed through Open or Save As and starts the next overlay there. The remembered session is home or a named file, lives only in daemon memory, and is resolved at spawn time. A restarted daemon starts at home.
  • The Session popover gains a return-home action: Back to <session> for a daemon started with --session-file, or Default session without one. It saves dirty data first, switches only once home has loaded, is disabled while home is active, and can be hidden with side.session.home.
  • --daemon-toggle --session-file still outranks the remembered session for its activation. A request for home itself returns home and clears the remembered session.
  • The overlay reports its session in daemon-commands/overlay-targets/<generation>.target, a private sibling of the strict v2/ tree. Reports are written only when the daemon advertises support. The daemon reads the final report inside child retirement, before releasing the child's identity, and accepts it only from a private regular file with the exact generation, PID and start identity. Startup clears leftover reports.
  • Launch inputs are optional environment variables, and the command line is unchanged, so older overlays and daemons keep working.
  • A remembered session is loaded only while it is still a usable session file, checked before and after the load and on every retry. If it was moved, deleted or replaced, home opens instead with a notice, and the daemon stops remembering it. A backup or recovery copy left beside the old path is never used for it.
  • Return home refuses a home whose file a save could not replace, leaving the current session untouched.
  • The visible-target guard uses the session the running overlay reports, including a continued remembered session.

Also on this branch:

  • Tests play external helper programs through the test binary instead of generated shell scripts.
  • The GTK proxy closes its connections before joining its listeners, fixing a teardown deadlock.
  • The no-Python guard's documentation now states what it reads and skips.

Validation

  • Local tools/lint-and-test.sh passed: 26 steps, 11,644 Cargo tests across both feature modes, and the C# tests.
  • The GTK gate passed: 88 tests and all 4 EXECUTED markers.
  • Real-child daemon tests cover hide/show continuity, a report followed by an immediate exit, a forced stop, an explicit file versus the remembered session, a request for home, and the guard.
  • Reader tests reject foreign, malformed, oversized, symlinked, non-private, relative and directory reports, and refuse a symlinked report directory.
  • Overlay tests run against a real persistence worker. They cover a deleted remembered file, a moved one with only its backup left, a symlink or directory in its place, deletion before a retry together with that retry's save, and a home without persistence.
  • Headless session-command tests cover return home: save first, commit, refusal of unusable homes, refusal on an edit while pending, and a home without persistence.
  • Mutation checks confirmed that each new test fails when the code it covers is removed or reordered.

Not validated on a live compositor. The thin Wayland-state glue that applies the load result, and the forced-stop path after a broker failure, have no automated coverage.

Four tests wrote and ran /bin/sh scripts as stand-ins for the configurator,
an initial-detach probe, and wl-copy. A test-only constructor now lets the
test binary play those helpers instead: a test links the helper's name to
the test binary and names that link in the environment, and only a start
under exactly that path plays the role, so other children run the tests.

- Configurator override and tray settings launch: exit, or record the
  arguments the tray passed.
- Initial detach: report whether the child leads its process group.
- wl-copy publication: count the bytes it was given.
- OCR path probes only need an executable file, so write an empty one.
A forwarder blocked on a hung peer holds its connection's state lock, so a
control command waiting for that lock kept its listener from finishing and
the proxy's teardown from returning. Close the connections first, which
wakes the forwarder, then join the listeners and close any connection
accepted meanwhile.
The guard's header claimed it read MSBuild and packaging files in general,
but it reads only listed extensions and extensionless files; other types,
such as .csproj or .json, are checked by name. It also honors a `!` line in
.gitignore only when it names one file. Say both, and list them among the
known limits.
A daemon overlay can now be told, through optional launch variables, the
daemon's home session and a remembered session to continue in place of
home. An explicit --session-file other than home outranks the remembered
one. Before the first load, the overlay checks that the remembered session
still exists; when it was moved or deleted it starts at home instead and
says so, rather than creating an empty session at the missing path.

Whenever an explicit command commits a new target, and when the first load
falls back home, the overlay reports its session to the daemon in a
per-generation record under daemon-commands/overlay-targets/, outside the
strict v2 tree. Home is reported as home even when it is a named file, an
unchanged target is not reported again, and only a daemon that advertises
support receives reports. A failed report leaves the switch in place and
says that the session may not reopen after the overlay hides.

No daemon sets these variables yet, so behavior is unchanged until the
daemon starts reading the reports.
The daemon now remembers the session its overlay last reported and starts
the next overlay there. The remembered session is two-valued, home or a
named file, lives only in daemon memory, and is resolved when the child is
spawned, so a report read after a request was queued still applies.

The child owner records the start identity proved at readiness and reads
the child's final report inside retirement, before it releases that
identity, on natural exit, on stop and on forced reap. A report must be a
private regular file carrying exactly that generation, PID and start time;
anything else is logged and ignored, and the report is removed either way.
Startup removes reports an earlier daemon left without restoring anything
from them.

Launches carry optional variables: a capability marker, the daemon's home
session file, and the remembered session as the one to continue. The
command line is unchanged, so an older overlay still starts at home. A
--daemon-toggle --session-file request outranks the remembered session for
its activation; a request for home itself starts at home and forgets the
remembered session. The visible-target guard uses the session the running
overlay last reported. The broker allows the new variables and strips them
from helpers that are not wayscriber relaunches.
The Session popover gains a way back to the home session: "Back to
<session>" when the overlay or its daemon started with a session file, or
"Default session" without one. It is disabled while home is already
active, and the side.session.home toolbar item hides it. The label and
destination come from the launch's home input, not from the session the
overlay is in.

Returning home is an explicit session command. Like Open, it saves dirty
current data first, refuses if the canvas or interaction changes while it
is pending, and replaces nothing unless home loads. Home loads the way a
launch would, from its own options for the current output, so recovery,
clear-boundary and tool-restore rules apply, and its load errors stay
those of a startup session file rather than becoming an empty session. A
home with persistence disabled continues unsaved on an empty canvas.

Once home is committed the overlay reports it, so the daemon forgets the
session it remembered. The overlay keeps whether it is home with its
reported target, so the menu needs no file system check on redraw.
The first load checked once, ahead of loading, whether any artifact of the
remembered session existed, and then loaded it with startup rules. A file
moved away came back from the backup left beside it; one replaced by a
symlink or directory opened as an empty canvas whose saves then failed; and
a file deleted after the check, or before a retry of a failed load, became
a new empty session at the missing path.

A remembered session now loads through its own worker operation, which
holds it to the runtime Open checks before and after the load: the file
itself must be a usable regular session file, not merely a backup or
recovery copy. Every attempt until a session has loaded runs that
operation, including retries. When the file cannot be used, home loads in
its place in the same attempt; the notice and the report to the daemon
follow once home has committed.

A continued remembered session is reported as the overlay starts, before
the daemon sees it ready. The daemon launched it at home, so until now its
visible-target guard took the overlay for being at home. A report that
could not be written is tried again on the next commit instead of being
treated as delivered.

Home's options in a daemon launch follow the resume policy the daemon
passed. The run's own override is the one its --session-file forces on and
says nothing about the default session, so a daemon started with
--no-resume-session no longer gets a persistent default home.
Return home handed what the load found to the startup handler, which
starts on an empty canvas for a session that is not a regular file or one
too large to restore. A home that had become a directory, or had grown
past the load cap, cleared the current canvas and history, switched
target and was reported to the daemon as home.

Home now commits inside the session transaction, through the same
launch-style outcome handling the startup load uses, now shared, so the
transaction's guards cover the whole switch. Those two outcomes fail the
command and leave the current session as it was, and a named home is
checked like a startup session file before it loads. A home without
persistence is applied in the transaction too.

After home commits, the overlay rechecks its output: a per-output home
follows an output change that happened while it loaded. The Session menu
offers the way home only while a persisted session is active, since
without one the overlay is in a home it cannot leave, and the row goes
through the same item table as the other session controls.
Reports are read and removed only through a report directory that is a
real directory private to this user, so a symlinked overlay-targets/ is
neither read through nor cleaned through. A reported session file must
also pass the shape rules every named session file follows, not only be
absolute.

When a stop forces the child down because its broker failed, the child
was still retired and its final report read; the report now travels with
the error to the daemon instead of being dropped.

The private-file reader and the private-directory helper are now shared:
the report reader uses the bounded protocol reader with a privacy check,
and the command layout, the action journal and the reports create their
directories through one helper. The daemon reads a trusted report through
one function that logs and ignores what it cannot trust.
The hide-and-show paragraph in CONFIG.md had landed inside the list of
session CLI helpers, cutting the last helper off from the list, and its
unquoted <session> was eaten as an HTML tag. It now follows the list, quotes
the button names as code, mentions a remembered file replaced by something
other than a regular file, and states the --no-resume-session decision: a
daemon started with it still continues a named session the overlay switched
to, since a named session file always persists, while its default home
stays unsaved.

The v2 protocol doc now describes the optional launch variables, the
overlay-targets/ reports, when the daemon reads them, and why leftovers do
not reach an older daemon. The codebase overview credits the report writer
and reader to the files that hold them.

The no-Python guard reads the first line of every file for a Python
shebang, including file types it otherwise checks only by name; its header
and the tools README now say so.
The fake overlay kept its own reader of /proc/self/cmdline beside the one
the fake helper constructor uses. It now reuses that reader. The helper's
header no longer claims that no test relies on a system program: it is the
helpers it plays that need neither a script nor one.
Return home checked only a named home before loading it. A configured home
whose session file had become a symlink was followed, and one that had
become a directory loaded from a recovery copy beside it; either replaced
the current canvas and committed home, and every later save failed. Home
now loads only while its file is one a save can replace, a regular file or
none yet, checked before and after the load.

When the first load of a remembered session failed and was retried, the
output transition saved the current session before loading, and that
session was still the never-loaded remembered one. If its file had been
deleted with nothing left beside it, the save recreated it, the load then
accepted it, and the overlay continued an empty session at the deleted
path; with its folder gone too, every retry failed on the save and never
reached home. The transition now checks the remembered session before that
save and skips saving one that can no longer be used, so the load gives it
up for home.
- An output's session load is now an enum, Loaded or WentHome, so the
  impossible state of neither a loaded session nor a reason is gone.
- The daemon checks only the shape of a reported session path. A file that
  changes before the next show is the overlay's to judge when it
  continues it; checking it at retirement kept a stale remembered session.
- The report file name is built in one place.
- The shared private-directory helper again refuses a directory another
  user owns.
- launched_resume_policy names the environment's own policy apart from
  resume_override_from_env; both now say how they differ.
- Return home asserts that a home too large to load never reaches the
  canvas, instead of dropping the value.
- The fake helper keeps launch arguments as bytes, so an argument that is
  not UTF-8 cannot stop a helper start from recognising itself.
- Imports sit at the top of the action journal and command layout, and
  docs no longer say a remembered session is continued "if it still
  exists".
…loses

The protocol doc said reports are read only at retirement and removed
either way. It now says the visible-target guard also reads the current
report, and that nothing in a report directory that is not private is read
or removed. CONFIG.md says that a moved or deleted remembered file opens
home even when a backup or recovery copy is left beside it, unlike a
startup --session-file, and its session command list renders as one tight
list again.
@devmobasa
devmobasa merged commit fc4c9c7 into main Oct 5, 2026
3 checks passed
@devmobasa
devmobasa deleted the feat/daemon-session-continuity branch October 5, 2026 12:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant