Terminal: remember tmux layout and state between sessions #70
Labels
No labels
prio_critical
prio_low
type_bug
type_contact
type_issue
type_lead
type_question
type_story
type_task
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
lhumina_code/hero_router#70
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
When a user reconnects to the hero_proc terminal, their tmux layout
and state is not remembered. Users have to reorganize their windows
every time they reconnect.
Requirements
instead of creating a new one
Relevant code
crates/hero_proc_ui/— terminal UIAcceptance Criteria
Implementation Spec for Issue #70
Objective
Make tmux terminal sessions in hero_router persist their layout and window state across browser disconnects and PTY-job restarts, by switching the launch command from a bare
tmux(which always creates a fresh session) totmux new-session -A -s <name>(attach if exists, else create), keyed by the user-facing session name.Requirements
N, it MUST attach to a tmux daemon-side session namedtmux_session_name(N)if one already exists, instead of creating a brand-new one.tmux_session_name(N)= the hero_router session name with every/replaced by_. Hero_router session names are validated to[A-Za-z0-9_-]per segment (max 3 segments separated by/); after/→_substitution the result contains only chars legal in a tmux session name (no:, no., no/).NuandBashshells is unchanged.attach(name)path already reuses the running PTY job, so the only path that needs fixing is the case where a tmux PTY job has exited (or never existed for this name) and the user re-creates a session under the same name.Files to Modify/Create
crates/hero_router/src/server/terminal.rs— change the tmuxActionBuilderscript string, add a smalltmux_session_namehelper, and add unit tests.No changes to
templates/terminal.html,static/js/terminal.js, or any RPC method shapes.Implementation Plan
Step 1: Add a
tmux_session_namehelperAdd a private function next to
encode_job_namenear line 99:It is intentionally separate from
encode_job_namebecause the encodings serve different downstreams (hero_proc job-name space vs tmux session-name space).Step 2: Switch the tmux launch command to
new-session -A -s <name>Inside
create_session, replace theShellType::Tmuxarm of thematch shell:Confirmed-safe properties:
validate_namerestricts each segment to[A-Za-z0-9_-], sotmux_namecontains no whitespace and cannot smuggle extra args through whitespace-split argv that theexecinterpreter performs.tmux new-session -A -s NAMEpertmux(1): "If-Ais given, attach to an existing session with the given name if it exists, otherwise create a new one." Exactly the required semantics.Step 3: Update the inline comment
The existing comment that says
Tmux: exec tmux directly; it manages its own session naming.becomes:Step 4: Add unit tests
Add a
#[cfg(test)] mod tmux_naming_testsblock at the end of the file:Pure, no external dependency on tmux being installed; pins the naming contract.
Step 5: Manual smoke test
cargo build -p hero_routerthen run the router locally with hero_proc available./terminal, create a tmux session calledwork. Inside, runCtrl-b "to split horizontally and runhtopin the lower pane.workentry in the sidebar. Verify the split andhtopare still present.terminal.delete(this kills the hero_proc PTY job; tmux daemon survives in the background).workfrom the New-session modal. Verify the previous tmux layout is reattached (this is the path the issue specifically targets).team/workto exercise the/->_mapping.Acceptance Criteria
tmux new-session -Asemantics; verifiable on the host withtmux lsshowing exactly one entry pertmux_session_name(N).cargo test -p hero_router tmux_naming_testspasses.cargo clippy -p hero_routerproduces no new warnings.Notes
Process-group kill risk for the tmux server. hero_proc's
kill_process_treewalks parent-child relationships viasysinfoand signals each PID. When tmux is started, the FIRSTtmuxinvocation forks a daemon that callssetsid(), becoming session leader of a NEW session and re-parenting to PID 1. From that moment, the daemon is no longer a descendant of the hero_proc PTY child, sokill_process_tree(<PTY child PID>)will NOT reach the daemon and the daemon survives — exactly what we need. This is the standard tmux contract; it should hold under hero_proc's current implementation. Tracked as a follow-up only if a future hero_proc release switches to a different kill strategy that catches the detached server.Sanitization rationale. Hero_router's
validate_namealready rejects everything dangerous; the only character that can appear in a valid hero_router name but is illegal/reserved in a tmux session name is/. We map it to_. Underscores already appear freely in hero_router names, so collisions likea/bvsa_bare theoretically possible at the tmux level — buta_banda/balready collide in hero_proc job space too (both encode torouter_term_a_b__tmuxafter/->__and_is already legal). Existingvalidate_namedoes not deduplicate these, so this is not a regression introduced by this fix; if it ever matters, a separate ticket can tighten the validator.Backward compatibility. Sessions created BEFORE this change ran bare
tmux(= a fresh anonymous session each time). Their per-launch tmux servers were daemonized but had no further reattach path; once the PTY job exited they remain idle on the host until rebooted (thetmux lsentry is named0by default) but are unreachable via this fix. After the change ships, every new session creates or attaches to a deterministically named tmux server-side session, and the persistence guarantee starts there. No migration is needed.Diff size estimate. ~6–8 lines of substantive Rust changes in
create_session, plus a 3-line helper, plus ~25 lines of unit tests. Well under 50 lines.Test Results
cargo build -p hero_router)Breakdown:
herolib_routerlib: 97 passed (94 pre-existing + 3 new intmux_naming_tests)hero_routerbin: 5 passed (log_bridgetests)New tests added by this change:
server::terminal::tmux_naming_tests::flat_name_passes_through_unchangedserver::terminal::tmux_naming_tests::slashes_are_replaced_with_underscoresserver::terminal::tmux_naming_tests::name_contains_no_tmux_reserved_charsClippy:
cargo clippy -p hero_router --no-depsproduces no NEW warnings on the changed file (crates/hero_router/src/server/terminal.rs); the only clippy note in that file is the pre-existingvery complex typeon theattached()registry — unrelated to this change.The added unit tests pin the
tmux_session_namenaming contract (no:, no., no/, no whitespace). End-to-end persistence verification (delete PTY job, recreate same name, layout returns) requires a live tmux + hero_proc environment and is left to manual smoke testing per the spec.Implementation Summary
Switched the tmux launch command in hero_router from a bare
tmux(which always creates a new session) totmux new-session -A -s <session_name>(attach if exists, else create). Layout / windows / scrollback are now preserved across PTY job restarts because the tmux serversetsid()s itself off the PTY child, survives the client exiting, and the next launch attaches to the existing named session instead of starting fresh.How it works
work, orteam/dev) is mapped to a tmux session name viatmux_session_name(N)—/is replaced with_, all other allowed chars ([A-Za-z0-9_-]) are already valid tmux session-name chars.tmux new-session -A -s <tmux_name>. tmux's-Asemantics (attach if exists, otherwise create) match the issue requirement exactly.create_session(clean up any exited job with the same name before recreating) continues to work as before, so re-creating the same hero_router session name remains idempotent.Files modified
crates/hero_router/src/server/terminal.rstmux_session_name(name: &str) -> Stringhelper nearencode_job_name.ShellType::Tmuxarm of thematch shellincreate_sessionto buildtmux new-session -A -s <tmux_name>.matchto describe the new tmux behavior.#[cfg(test)] mod tmux_naming_testswith three unit tests pinning the naming contract.No changes to templates, JS, or any RPC method shapes.
Test results
cargo build -p hero_router)Acceptance criteria
tmux new-session -Asemantics.-A -sdoes.Notes
tmux, so their server-side sessions were anonymous (named0) and have no reattach path. After this change ships, each new hero_router session creates or attaches to a deterministically named tmux session, and the persistence guarantee starts there. No migration required.setsid()on the very first invocation, so it is no longer a descendant of the hero_proc PTY child and is not reaped bykill_process_tree. This is the standard tmux contract; persistence holds under the current hero_proc implementation./needed mapping. Existingvalidate_namealready rejects all other characters that would be illegal or reserved in a tmux session name.Pull request opened: #83
This PR implements the changes discussed in this issue.