Files
hqt/docs/superpowers/specs/2026-06-10-session-attach-rename-design.md
T
felixm 90944948f5 Initial commit: hqt — HQ Terminal TUI
TUI for orchestrating AI coding harness sessions (Claude Code, Codex,
Kiro, etc.) via tmux. Click CLI bootstraps a Textual TUI over
ProjectService/SessionService backed by SQLite, spawning harness
sessions as tmux windows through TmuxManager.

Includes recent fixes:
- Visible Tab focus highlight on dialog OK/Cancel buttons
- Auto-select first project on launch
- Auto-select first session + per-project session-selection memory
- tmux new-window targets an explicit free index, fixing
  "index N in use" failures (broken spawn/attach in attached sessions)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 17:06:57 -04:00

3.2 KiB

Simplify session attach + add rename

Date: 2026-06-10 Status: Approved

Goal

Streamline session handling with two changes:

  1. Remove the redundant Resume (Shift+R) binding — attach already auto-resumes.
  2. Add a Rename action (r) to relabel the selected session.

Background

Each session row carries two names:

  • tmux_session_name (hqt-{id}) — the stable internal identifier used to target the tmux window and as the unique DB key. Never user-facing.
  • nickname — an optional display label shown in the session list and in the tmux window label (falls back to tmux_session_name when unset).

attach_session() (src/hqt/sessions/service.py) already handles every window state: alive → switch to it; dead pane or missing window → respawn via the fallback ladder (_respawn_with_fallback) then attach. The resume_session() method and its R,shift+r binding only duplicate that respawn-then-attach path, so Resume is dead weight.

Change 1 — Remove the Resume binding (deletion only)

  • src/hqt/tui/app.py: delete the Binding("R,shift+r", "resume_session", "Resume") entry and the action_resume_session() method.
  • src/hqt/sessions/service.py: delete the resume_session() method.

Keep _respawn_with_fallback()attach_session() depends on it. Attach behavior is unchanged; Enter becomes the single way into a session, resuming transparently when the window is dead or gone.

Change 2 — Add Rename (r)

Service

New synchronous method on SessionService (pure DB write — no tmux call):

def rename_session(self, session_id: int, nickname: str | None) -> None:
    sess = self.db.get(Session, session_id)
    sess.nickname = nickname or None  # empty -> None -> label falls back to tmux name
    self.db.commit()

The next 3-second poll (sync_window_labels()) reads the updated nickname and refreshes the tmux window label automatically — no immediate label push needed.

A small get_session(session_id) -> Session | None accessor is added so the app layer can read the current nickname to prefill the dialog without reaching into db directly.

Modal

New RenameSessionScreen(ModalScreen[str | None]) in src/hqt/tui/screens/rename_session.py, mirroring ProjectFormScreen:

  • Titled dialog with a single Input prefilled with the current nickname.
  • OK / Cancel buttons in a .dialog-actions row.
  • OK dismisses with the stripped input value; Cancel dismisses with None.

App wiring (src/hqt/tui/app.py)

  • Add Binding("r", "rename_session", "Rename").
  • action_rename_session():
    • Get the selected session id; warn ("Select a session first") if none.
    • Load the session via get_session() to read its current nickname.
    • Push RenameSessionScreen prefilled with that nickname.
    • On a non-None dismiss, call rename_session() then _refresh_sessions().

Testing

  • rename_session sets nickname; an empty string clears it to None.
  • Remove or update any test referencing resume_session or the Resume binding.
  • Pilot test: press r, type a name, confirm, assert the session's nickname / displayed label updates.

Out of scope

  • No change to tmux_session_name.
  • No new harness logic.
  • No DB migration (reuses the existing nickname column).