#!/usr/bin/env bash
#clear
#export PS4='\e[90m+${LINENO} in ${#BASH_SOURCE[@]}>${FUNCNAME[0]}:${BASH_SOURCE[@]##*/} \e[0m'
#set -x

#echo "starting: $0 <LOG_LEVEL=$1>"

### new.method

# ============================================================================
# otmux - tmux wrapper for oosh
# Makes tmux commands easy to use with intuitive method names
# ============================================================================

# Always use UTF-8 mode - essential for Claude Code and modern terminal apps
# The -u flag forces tmux to assume UTF-8 is supported, regardless of locale
TMUX_CMD="tmux -u"

# Configuration file location (symlinked to ~/.tmux.conf)
# Features: mouse support, macOS clipboard (pbcopy/pbpaste), vi keys
OTMUX_CONFIG="${OOSH_DIR:-$(dirname "$0")}/tmux.conf"

# Ensure color support for Claude Code and other modern CLI apps
# FORCE_COLOR=1 is needed because NO_COLOR might be set elsewhere
export FORCE_COLOR=2 
unset  COLORTERM 
#export COLORTERM=truecolor   # truecolor is not supported on mac just 256 colors

# ─────────────────────────────────────────────────────────────────────────────
# TARGET COMPLETION HELPERS
# ─────────────────────────────────────────────────────────────────────────────
# Targets in tmux follow these patterns:
#   Session:  session_name or $session_id (e.g., "dev" or "$1")
#   Window:   [session:]window (e.g., "dev:0", "dev:main", or just "2")
#   Pane:     [session:][window.]pane (e.g., "dev:0.1", "0.2", or just "1")

private.resolve.target() {
  # Resolve direction targets (U/D/L/R) to pane IDs
  local target="$1"
  case "$target" in
    U|u|up)    $TMUX_CMD display-message -p -t "{up-of}" "#{pane_id}" 2>/dev/null || echo "$target" ;;
    D|d|down)  $TMUX_CMD display-message -p -t "{down-of}" "#{pane_id}" 2>/dev/null || echo "$target" ;;
    L|l|left)  $TMUX_CMD display-message -p -t "{left-of}" "#{pane_id}" 2>/dev/null || echo "$target" ;;
    R|r|right) $TMUX_CMD display-message -p -t "{right-of}" "#{pane_id}" 2>/dev/null || echo "$target" ;;
    *)         echo "$target" ;;
  esac
}

private.otmux.target.isPane() { # <target> # rc=0 if target is a valid pane reference (session:win.pane or %paneId)
  # Bug #4 defense: send.* methods must validate target format BEFORE delegating
  # to tmux send-keys. Otherwise garbage from a failed upstream resolve (e.g.
  # multi-line error.log captured into a target var) becomes raw input to the
  # currently-focused pane via tmux's silent fallback.
  #
  # Valid pane references:
  #   - "%N"               tmux pane id (e.g. %42)
  #   - "sess:win.pane"    fully qualified (e.g. ooshTeam:0.2)
  # Direction targets (U/D/L/R) are resolved by private.resolve.target before
  # arriving here, so we only see post-resolve values.
  local target="$1"
  [ -z "$target" ] && return 1
  # Reject anything with whitespace or newlines (typical of captured error.log)
  case "$target" in
    *[[:space:]]*) return 1 ;;
  esac
  # Match tmux pane id or session:win.pane
  [[ "$target" =~ ^%[0-9]+$ ]] && return 0
  [[ "$target" =~ ^[A-Za-z_][A-Za-z0-9_.-]*:[0-9]+\.[0-9]+$ ]] && return 0
  return 1
}

private.complete.sessions() {
  $TMUX_CMD list-sessions -F "#{session_name}" 2>/dev/null
}

private.complete.windows() {
  $TMUX_CMD list-windows -a -F "#{session_name}:#{window_index}:#{window_name}" 2>/dev/null | \
    while IFS=: read -r sess idx name; do
      echo "$sess:$idx"
      echo "$sess:$name"
    done
}

private.complete.panes() {
  $TMUX_CMD list-panes -a -F "#{session_name}:#{window_index}.#{pane_index}" 2>/dev/null
}

private.complete.buffers() {
  $TMUX_CMD list-buffers -F "#{buffer_name}" 2>/dev/null
}

private.complete.clients() {
  $TMUX_CMD list-clients -F "#{client_name}" 2>/dev/null
}

# ─────────────────────────────────────────────────────────────────────────────
# SESSION COMMANDS
# ─────────────────────────────────────────────────────────────────────────────

otmux.new() # <?name> <?command> # create a new session
{
  local isFirstSession=false
  $TMUX_CMD list-sessions &>/dev/null || isFirstSession=true

  if [ -n "$1" ]; then
    local name="$1"
    shift
    $TMUX_CMD new-session -d -s "$name" "$@"
  else
    $TMUX_CMD new-session -d "$@"
  fi

  # Apply defaults on first session (server just started)
  if [ "$isFirstSession" = true ]; then
    otmux.setup.default
  fi

  # Only attach if not already inside tmux (avoids nesting error)
  if [ -z "$TMUX" ]; then
    if [ -n "$name" ]; then
      $TMUX_CMD attach-session -t "$name"
    else
      $TMUX_CMD attach-session
    fi
  fi
}

otmux.attach() # <?session> <?readonly> # attach to session; pass 'readonly' to attach with -r (no input, no resize)
{
  local target="" ro=""
  for arg in "$@"; do
    case "$arg" in
      readonly|ro|-r) ro="-r" ;;
      *) [ -z "$target" ] && target="$arg" ;;
    esac
  done
  if [ -n "$target" ]; then
    $TMUX_CMD attach-session $ro -t "$target"
  else
    $TMUX_CMD attach-session $ro
  fi
}
otmux.attach.completion.session() { private.complete.sessions; }
otmux.attach.completion.readonly() { echo "readonly"; }

otmux.attach.readonly() # <?session> # convenience: attach in read-only mode (alias for attach <session> readonly)
{
  otmux.attach "$1" readonly
}
otmux.attach.readonly.completion.session() { private.complete.sessions; }

otmux.detach() # # detach from current session
{
  $TMUX_CMD detach-client "$@"
}

otmux.sessions() # # list all sessions
{
  $TMUX_CMD list-sessions "$@"
}

otmux.has() # <target> # check if session exists; target=session name
{
  $TMUX_CMD has-session -t "$1" 2>/dev/null
}
otmux.has.completion.target() { private.complete.sessions; }

otmux.kill() # <target> # kill session; target=session name to destroy
{
  if [ -n "$1" ]; then
    # SC-E.2 P3: destructive — refuse to kill garbage that might match wildcard
    # patterns in tmux or destroy the wrong session.
    if ! this.isSessionName "$1"; then
      error.log "otmux.kill: invalid session name '$1' (alphanumeric/dot/dash/underscore)"
      return 1
    fi
    $TMUX_CMD kill-session -t "$1"
  else
    error.log "usage: otmux kill <session-name>"
    return 1
  fi
}
otmux.kill.completion.target() { private.complete.sessions; }

private.otmux.killAll() # # kill all sessions except current
{
  $TMUX_CMD kill-session -a "$@"
}

otmux.rename() # <session> <?newName> # rename session (alias for session.rename)
{
  otmux.session.rename "$@"
}
otmux.rename.completion.session() { otmux.parameter.completion.session "$@"; }

otmux.session.rename() # <session> <?newName> # rename session; 1 arg=current, 2 args=named
{
  if [ -z "$1" ]; then
    error.log "usage: otmux session.rename <newName> OR otmux session.rename <session> <newName>"
    return 1
  fi
  local oldName newName
  if [ -n "$2" ]; then
    oldName="$1"
    newName="$2"
  else
    oldName=$($TMUX_CMD display-message -p '#{session_name}' 2>/dev/null)
    newName="$1"
  fi
  # SC-E.2 P2: both args fire the protected.session.renamed observer which
  # writes to multiple env files. Reject garbage at the boundary — newName
  # also lands in tmux session table.
  if [ -n "$oldName" ] && ! this.isSessionName "$oldName"; then
    error.log "session.rename: invalid old session name '$oldName'"
    return 1
  fi
  if ! this.isSessionName "$newName" || ! this.isPipeSafe "$newName"; then
    error.log "session.rename: invalid new session name '$newName' (must match tmux name format, no pipes)"
    return 1
  fi
  if [ -n "$2" ]; then
    $TMUX_CMD rename-session -t "$oldName" "$newName"
  else
    $TMUX_CMD rename-session "$newName"
  fi
  # Notify hiveMind to update env files (observer pattern)
  command -v hiveMind >/dev/null 2>&1 && hiveMind protected.session.renamed "$oldName" "$newName" 2>/dev/null
}
otmux.session.rename.completion.session() { otmux.parameter.completion.session "$@"; }

otmux.switch() # <target> # switch to session; target=session name or $id
{
  if [ -n "$1" ]; then
    $TMUX_CMD switch-client -t "$1"
  else
    $TMUX_CMD switch-client "$@"
  fi
}
otmux.switch.completion.target() { private.complete.sessions; }

otmux.last() # # switch to last session
{
  $TMUX_CMD switch-client -l
}

otmux.next() # # switch to next session
{
  $TMUX_CMD switch-client -n
}

otmux.prev() # # switch to previous session
{
  $TMUX_CMD switch-client -p
}

otmux.lock() # # lock the current session
{
  $TMUX_CMD lock-session "$@"
}

# ─────────────────────────────────────────────────────────────────────────────
# WINDOW COMMANDS
# ─────────────────────────────────────────────────────────────────────────────

private.otmux.newWindow() # <?name> <?command> # create a new window
{
  if [ -n "$1" ]; then
    $TMUX_CMD new-window -n "$1" "${@:2}"
  else
    $TMUX_CMD new-window "$@"
  fi
}

otmux.windows() # # list windows in current session
{
  $TMUX_CMD list-windows "$@"
}

private.otmux.selectWindow() # <target> # select window; target=index (0,1,2) or name
{
  if [ -n "$1" ]; then
    $TMUX_CMD select-window -t "$1"
  else
    error.log "usage: otmux selectWindow <window>"
    return 1
  fi
}
private.otmux.selectWindow.completion.target() { private.complete.windows; }

private.otmux.nextWindow() # # go to next window
{
  $TMUX_CMD next-window
}

private.otmux.prevWindow() # # go to previous window
{
  $TMUX_CMD previous-window
}

private.otmux.lastWindow() # # go to last window
{
  $TMUX_CMD last-window
}

private.otmux.killWindow() # <?target> # kill window; target=[session:]window index/name
{
  if [ -n "$1" ]; then
    $TMUX_CMD kill-window -t "$1"
  else
    $TMUX_CMD kill-window
  fi
}
private.otmux.killWindow.completion.target() { private.complete.windows; }

private.otmux.renameWindow() # <target|name> <?name> # rename window; 2 args = target + name, 1 arg = name only
{
  if [ $# -ge 2 ]; then
    $TMUX_CMD rename-window -t "$1" "$2"
  elif [ -n "$1" ]; then
    $TMUX_CMD rename-window "$1"
  else
    error.log "usage: otmux renameWindow <new-name> OR otmux renameWindow <target> <new-name>"
    return 1
  fi
}

private.otmux.moveWindow() # <target> # move window; target=destination [session:]index
{
  $TMUX_CMD move-window "$@"
}
private.otmux.moveWindow.completion.target() { private.complete.windows; }

private.otmux.swapWindow() # <target> # swap with window; target=[session:]window to swap with
{
  $TMUX_CMD swap-window "$@"
}
private.otmux.swapWindow.completion.target() { private.complete.windows; }

private.otmux.linkWindow() # <src> <dst> # link window; src=source window, dst=target session
{
  $TMUX_CMD link-window "$@"
}
private.otmux.linkWindow.completion.src() { private.complete.windows; }
private.otmux.linkWindow.completion.dst() { private.complete.sessions; }

private.otmux.unlinkWindow() # <?target> # unlink window; target=window to unlink
{
  $TMUX_CMD unlink-window "$@"
}
private.otmux.unlinkWindow.completion.target() { private.complete.windows; }

private.otmux.findWindow() # <pattern> # find window by pattern
{
  if [ -n "$1" ]; then
    $TMUX_CMD find-window "$1"
  else
    error.log "usage: otmux findWindow <pattern>"
    return 1
  fi
}

private.otmux.rotateWindow() # # rotate panes in window
{
  $TMUX_CMD rotate-window "$@"
}

private.otmux.respawnWindow() # <?command> # respawn window with command
{
  $TMUX_CMD respawn-window "$@"
}

# ─────────────────────────────────────────────────────────────────────────────
# PANE COMMANDS
# ─────────────────────────────────────────────────────────────────────────────

private.otmux.notifyHiveMind.shifted() { # <session> # observer callback after split/insert (per B5 PUML spec)
  # B5.1 — View notifies Controller after pane indices may have shifted.
  # Maps to architecture diagram: protected.panes.shifted <session>
  local sess="${1:-}"
  [ -z "$sess" ] && sess=$($TMUX_CMD display-message -p '#{session_name}' 2>/dev/null)
  [ -z "$sess" ] && return 0
  command -v hiveMind >/dev/null 2>&1 && hiveMind protected.panes.shifted "$sess" 2>/dev/null
}

private.otmux.sessionFromPane() { # <pane> # extract session name from session:window.pane target
  local pane="$1"
  echo "${pane%%:*}"
}

otmux.split() # <?target> <?command> # split current pane (or target pane)
{
  local sess
  if [[ "$1" == *[:.]*  ]] && [ -n "$1" ]; then
    local target="$1"; shift
    sess=$(private.otmux.sessionFromPane "$target")
    $TMUX_CMD split-window -t "$target" "$@"
  else
    $TMUX_CMD split-window "$@"
  fi
  private.otmux.notifyHiveMind.shifted "$sess"
}

otmux.split.h() # <?target> <?command> # split pane horizontally (side-by-side)
{
  local sess
  if [[ "$1" == *[:.]*  ]] && [ -n "$1" ]; then
    local target="$1"; shift
    sess=$(private.otmux.sessionFromPane "$target")
    $TMUX_CMD split-window -h -t "$target" "$@"
  else
    $TMUX_CMD split-window -h "$@"
  fi
  private.otmux.notifyHiveMind.shifted "$sess"
}

otmux.split.v() # <?target> <?command> # split pane vertically (top-bottom)
{
  local sess
  if [[ "$1" == *[:.]*  ]] && [ -n "$1" ]; then
    local target="$1"; shift
    sess=$(private.otmux.sessionFromPane "$target")
    $TMUX_CMD split-window -v -t "$target" "$@"
  else
    $TMUX_CMD split-window -v "$@"
  fi
  private.otmux.notifyHiveMind.shifted "$sess"
}

otmux.panes() # # list panes in current window
{
  $TMUX_CMD list-panes "$@"
}

private.otmux.selectPane() # <target> # select pane; target=U/D/L/R direction or index (0,1,2)
{
  case "$1" in
    U|u|up)    $TMUX_CMD select-pane -U ;;
    D|d|down)  $TMUX_CMD select-pane -D ;;
    L|l|left)  $TMUX_CMD select-pane -L ;;
    R|r|right) $TMUX_CMD select-pane -R ;;
    *)         $TMUX_CMD select-pane -t "$1" ;;
  esac
}


otmux.up() # # select pane above
{
  $TMUX_CMD select-pane -U
}

otmux.down() # # select pane below
{
  $TMUX_CMD select-pane -D
}

otmux.left() # # select pane to the left
{
  $TMUX_CMD select-pane -L
}

otmux.right() # # select pane to the right
{
  $TMUX_CMD select-pane -R
}

private.otmux.lastPane() # # select last pane
{
  $TMUX_CMD last-pane
}

private.otmux.killPane() # <?target> # kill pane; target=[session:][window.]pane index
{
  if [ -n "$1" ]; then
    $TMUX_CMD kill-pane -t "$1"
  else
    $TMUX_CMD kill-pane
  fi
}


private.otmux.breakPane() # # break pane into a new window
{
  $TMUX_CMD break-pane "$@"
}

private.otmux.joinPane() # <window> # join pane to window; window=destination [session:]window
{
  $TMUX_CMD join-pane "$@"
}

private.otmux.swapPane() # <sourcePane> <targetPane> # swap two panes
{
  local sourcePane=$(private.resolve.target "$1")
  local targetPane=$(private.resolve.target "$2")
  if [ -z "$sourcePane" ] || [ -z "$targetPane" ]; then
    error.log "usage: otmux pane.swap <sourcePane> <targetPane>"
    return 1
  fi
  $TMUX_CMD swap-pane -s "$sourcePane" -t "$targetPane"
}

private.otmux.movePane() # <window> # move pane; window=destination window
{
  $TMUX_CMD move-pane "$@"
}

private.otmux.resizePane() # <direction> <?amount> # resize pane; direction=U/D/L/R, amount=cells (default 5)
{
  local amount="${2:-5}"
  case "$1" in
    U|u|up)    $TMUX_CMD resize-pane -U "$amount" ;;
    D|d|down)  $TMUX_CMD resize-pane -D "$amount" ;;
    L|l|left)  $TMUX_CMD resize-pane -L "$amount" ;;
    R|r|right) $TMUX_CMD resize-pane -R "$amount" ;;
    *)         $TMUX_CMD resize-pane "$@" ;;
  esac
}

otmux.zoom() # # toggle pane zoom
{
  $TMUX_CMD resize-pane -Z
}

private.otmux.respawnPane() # <?command> # respawn pane with command
{
  $TMUX_CMD respawn-pane "$@"
}

private.otmux.pipePane() # <?command> # pipe pane output to command
{
  $TMUX_CMD pipe-pane "$@"
}

private.otmux.displayPanes() # # display pane numbers
{
  $TMUX_CMD display-panes "$@"
}

private.otmux.capturePane() # <?options> # capture pane contents
{
  $TMUX_CMD capture-pane "$@"
}

private.otmux.clearHistory() # # clear pane history
{
  $TMUX_CMD clear-history "$@"
}

# ─────────────────────────────────────────────────────────────────────────────
# LAYOUT COMMANDS
# ─────────────────────────────────────────────────────────────────────────────

otmux.layout() # <layout> <?target> # set window layout; pass all args through
{
  $TMUX_CMD select-layout "$@"
}

private.otmux.evenH() # # set even-horizontal layout
{
  $TMUX_CMD select-layout even-horizontal
}

private.otmux.evenV() # # set even-vertical layout
{
  $TMUX_CMD select-layout even-vertical
}

private.otmux.mainH() # # set main-horizontal layout
{
  $TMUX_CMD select-layout main-horizontal
}

private.otmux.mainV() # # set main-vertical layout
{
  $TMUX_CMD select-layout main-vertical
}

otmux.tiled() # <?target> # set tiled layout; target=optional window target
{
  $TMUX_CMD select-layout ${1:+-t "$1"} tiled
}

private.otmux.nextLayout() # # cycle to next layout
{
  $TMUX_CMD next-layout
}

private.otmux.prevLayout() # # cycle to previous layout
{
  $TMUX_CMD previous-layout
}

# ─────────────────────────────────────────────────────────────────────────────
# LAYOUT PERSISTENCE (save / restore across server restart)
# ─────────────────────────────────────────────────────────────────────────────
# File format: ~/config/otmux/<session>.layout.env  (env-file, OOSH convention)
#   OTMUX_LAYOUT_SESSION, OTMUX_LAYOUT_SAVED_AT
#   OTMUX_LAYOUT_WINDOW_INDICES — space-separated list of window indices
#   Per window W:
#     OTMUX_WINDOW_<W>_NAME      — window name
#     OTMUX_WINDOW_<W>_LAYOUT    — tmux native layout string (#{window_layout})
#     OTMUX_WINDOW_<W>_PANE_INDICES — space-separated pane indices
#   Per pane W.P:
#     OTMUX_WINDOW_<W>_PANE_<P>_TITLE  — pane_title (set via select-pane -T)
#     OTMUX_WINDOW_<W>_PANE_<P>_CWD    — pane_current_path
#     OTMUX_WINDOW_<W>_PANE_<P>_CMD    — pane_current_command (info only)
#
# Agnostic to agents — purely geometry + labels. Controller (hiveMind) composes
# layout.restore + content restoration (claude relaunch, role re-teaching, etc.)

: ${OTMUX_LAYOUT_DIR:=${CONFIG_PATH:-$HOME/config}/otmux}

otmux.layout.save() # <session> # serialize session layout (windows, panes, titles, cwds) to file
{
  local session="$1"
  if [ -z "$session" ]; then
    error.log "usage: otmux layout.save <session>"
    return 1
  fi
  if ! $TMUX_CMD has-session -t "$session" 2>/dev/null; then
    error.log "no tmux session: $session"
    return 1
  fi

  mkdir -p "$OTMUX_LAYOUT_DIR"
  local file="$OTMUX_LAYOUT_DIR/$session.layout.env"

  {
    echo "# otmux layout for session: $session"
    echo "# saved: $(date -u '+%Y-%m-%dT%H:%M:%SZ')"
    echo "OTMUX_LAYOUT_SESSION=\"$session\""
    echo "OTMUX_LAYOUT_SAVED_AT=\"$(date -u '+%Y-%m-%dT%H:%M:%SZ')\""

    local winIdxs
    winIdxs=$($TMUX_CMD list-windows -t "$session" -F '#{window_index}' 2>/dev/null | tr '\n' ' ')
    echo "OTMUX_LAYOUT_WINDOW_INDICES=\"${winIdxs% }\""

    local w
    for w in $winIdxs; do
      local wname wlayout
      wname=$($TMUX_CMD display-message -p -t "$session:$w" '#{window_name}' 2>/dev/null)
      wlayout=$($TMUX_CMD display-message -p -t "$session:$w" '#{window_layout}' 2>/dev/null)
      echo "OTMUX_WINDOW_${w}_NAME=\"${wname//\"/\\\"}\""
      echo "OTMUX_WINDOW_${w}_LAYOUT=\"${wlayout//\"/\\\"}\""

      local paneIdxs
      paneIdxs=$($TMUX_CMD list-panes -t "$session:$w" -F '#{pane_index}' 2>/dev/null | tr '\n' ' ')
      echo "OTMUX_WINDOW_${w}_PANE_INDICES=\"${paneIdxs% }\""

      local p
      for p in $paneIdxs; do
        local ptitle pcwd pcmd
        ptitle=$($TMUX_CMD display-message -p -t "$session:$w.$p" '#{pane_title}' 2>/dev/null)
        pcwd=$($TMUX_CMD display-message -p -t "$session:$w.$p" '#{pane_current_path}' 2>/dev/null)
        pcmd=$($TMUX_CMD display-message -p -t "$session:$w.$p" '#{pane_current_command}' 2>/dev/null)
        echo "OTMUX_WINDOW_${w}_PANE_${p}_TITLE=\"${ptitle//\"/\\\"}\""
        echo "OTMUX_WINDOW_${w}_PANE_${p}_CWD=\"${pcwd//\"/\\\"}\""
        echo "OTMUX_WINDOW_${w}_PANE_${p}_CMD=\"${pcmd//\"/\\\"}\""
      done
    done
  } > "$file"

  success.log "Layout saved: $file"
  RESULT="$file"
  echo "$file"
}
otmux.layout.save.completion.session() { otmux.parameter.completion.session "$@"; }

otmux.layout.restore() # <session> <?force> # recreate session layout from saved file (force value: --force to overwrite existing)
{
  local session="$1"
  local force="${2:-}"
  if [ -z "$session" ]; then
    error.log "usage: otmux layout.restore <session> <?--force>"
    return 1
  fi

  local file="$OTMUX_LAYOUT_DIR/$session.layout.env"
  if [ ! -f "$file" ]; then
    error.log "no layout file: $file"
    return 1
  fi

  # Guard: refuse to overwrite an existing session unless --force
  if $TMUX_CMD has-session -t "$session" 2>/dev/null; then
    if [ "$force" != "--force" ]; then
      error.log "session '$session' already exists — pass --force to apply layout anyway"
      return 1
    fi
    warn.log "session '$session' exists — applying saved layout with --force"
  fi

  # Source env file in a subshell scope to read vars
  source "$file"

  # Create session with first window + pane if missing
  if ! $TMUX_CMD has-session -t "$session" 2>/dev/null; then
    $TMUX_CMD new-session -d -s "$session"
    info.log "created session: $session"
  fi

  local w
  for w in $OTMUX_LAYOUT_WINDOW_INDICES; do
    local wname_var="OTMUX_WINDOW_${w}_NAME"
    local wlayout_var="OTMUX_WINDOW_${w}_LAYOUT"
    local pane_idx_var="OTMUX_WINDOW_${w}_PANE_INDICES"
    local wname="${!wname_var}"
    local wlayout="${!wlayout_var}"
    local pane_indices="${!pane_idx_var}"

    # Ensure window exists
    if ! $TMUX_CMD list-windows -t "$session" -F '#{window_index}' 2>/dev/null | grep -q "^${w}$"; then
      $TMUX_CMD new-window -t "$session:$w" -n "$wname" 2>/dev/null
    else
      $TMUX_CMD rename-window -t "$session:$w" "$wname" 2>/dev/null
    fi

    # Create panes to match saved count. Starting with 1 (from new-window/new-session),
    # split-window N-1 times. Direction doesn't matter — select-layout reshapes.
    local needed existing to_create
    needed=$(echo "$pane_indices" | wc -w | tr -d ' ')
    existing=$($TMUX_CMD list-panes -t "$session:$w" 2>/dev/null | wc -l | tr -d ' ')
    to_create=$((needed - existing))
    while [ "$to_create" -gt 0 ]; do
      $TMUX_CMD split-window -t "$session:$w" 2>/dev/null
      to_create=$((to_create - 1))
    done

    # Apply tmux-native layout string — reshapes to exact saved geometry
    if [ -n "$wlayout" ]; then
      $TMUX_CMD select-layout -t "$session:$w" "$wlayout" 2>/dev/null
    fi

    # Restore pane titles (one-shot; for persistent title use otmux pane.lock)
    local p
    for p in $pane_indices; do
      local title_var="OTMUX_WINDOW_${w}_PANE_${p}_TITLE"
      local title="${!title_var}"
      if [ -n "$title" ]; then
        $TMUX_CMD select-pane -t "$session:$w.$p" -T "$title" 2>/dev/null
      fi
    done
  done

  success.log "Layout restored for session '$session' from $file"
}
otmux.layout.restore.completion.session() {
  [ -d "$OTMUX_LAYOUT_DIR" ] || return 0
  local f
  for f in "$OTMUX_LAYOUT_DIR"/*.layout.env; do
    [ -f "$f" ] || continue
    basename "$f" .layout.env
  done
}
otmux.layout.restore.completion.force() { echo "--force"; }

otmux.layout.list() # # list all saved layouts
{
  if [ ! -d "$OTMUX_LAYOUT_DIR" ]; then
    info.log "no saved layouts (dir: $OTMUX_LAYOUT_DIR)"
    return 0
  fi
  printf "%-30s %-22s %s\n" "SESSION" "SAVED" "FILE"
  local f
  for f in "$OTMUX_LAYOUT_DIR"/*.layout.env; do
    [ -f "$f" ] || continue
    local name saved
    name=$(basename "$f" .layout.env)
    saved=$(grep -oE 'OTMUX_LAYOUT_SAVED_AT="[^"]*"' "$f" | head -1 | sed 's/OTMUX_LAYOUT_SAVED_AT="//;s/"//')
    printf "%-30s %-22s %s\n" "$name" "${saved:-unknown}" "$f"
  done
}
otmux.layout.list.completion() { :; }

otmux.layout.delete() # <session> # remove a saved layout file
{
  local session="$1"
  if [ -z "$session" ]; then
    error.log "usage: otmux layout.delete <session>"
    return 1
  fi
  local file="$OTMUX_LAYOUT_DIR/$session.layout.env"
  if [ ! -f "$file" ]; then
    warn.log "no layout file for: $session"
    return 1
  fi
  rm "$file"
  success.log "deleted: $file"
}
otmux.layout.delete.completion.session() {
  [ -d "$OTMUX_LAYOUT_DIR" ] || return 0
  local f
  for f in "$OTMUX_LAYOUT_DIR"/*.layout.env; do
    [ -f "$f" ] || continue
    basename "$f" .layout.env
  done
}

otmux.layout.show() # <session> # display the saved layout file for a session
{
  local session="$1"
  if [ -z "$session" ]; then
    error.log "usage: otmux layout.show <session>"
    return 1
  fi
  local file="$OTMUX_LAYOUT_DIR/$session.layout.env"
  if [ ! -f "$file" ]; then
    error.log "no layout file: $file"
    return 1
  fi
  cat "$file"
}
otmux.layout.show.completion.session() {
  otmux.layout.delete.completion.session
}

# ─────────────────────────────────────────────────────────────────────────────
# WINDOW LAYOUT — runtime getter/setter (B2 layout.save/restore is file-based;
# these are for in-memory layout-spec manipulation: clone one session's geometry
# onto another, snapshot for inspection, etc.)
# ─────────────────────────────────────────────────────────────────────────────

otmux.window.layout.get() # <?target> # return tmux layout-spec string for a window (default: current)
{
  # Returns the opaque layout-spec tmux uses internally — encodes pane geometry
  # (positions + dimensions) plus pane IDs. Callers can pass the result to
  # otmux.window.layout.set to clone the layout onto a different session.
  local target="${1:-}"
  if [ -n "$target" ]; then
    $TMUX_CMD display-message -t "$target" -p '#{window_layout}' 2>/dev/null
  else
    $TMUX_CMD display-message -p '#{window_layout}' 2>/dev/null
  fi
}
otmux.window.layout.get.completion.target() { private.complete.windows; }

otmux.window.layout.set() # <target> <spec> # apply a literal tmux layout-spec to a window
{
  # Companion to layout.get. Use case: clone geometry between sessions:
  #   spec=$(otmux window.layout.get sourceTeam:0)
  #   otmux window.layout.set targetTeam:0 "$spec"
  # Pane IDs in the spec must reference panes that EXIST in the target window —
  # tmux fails silently otherwise. Same-shape windows (same pane count) work
  # cleanly; cross-shape requires creating panes first.
  local target="$1"
  local spec="$2"
  if [ -z "$target" ] || [ -z "$spec" ]; then
    error.log "Usage: otmux window.layout.set <target> <spec>"
    return 1
  fi
  $TMUX_CMD select-layout -t "$target" "$spec" 2>/dev/null
}
otmux.window.layout.set.completion.target() { private.complete.windows; }

otmux.window.aggressive.resize() # <target> <?value:on> # toggle aggressive-resize on a window (auto-rebalance panes on attach)
{
  # `aggressive-resize on` makes a window's panes rebalance proportionally
  # whenever the window dimensions change (e.g. when a different-sized client
  # attaches). Without it, panes stay at their last computed sizes and may
  # leave dead space or clip. Used in B8/restore flows to ensure restored
  # sessions adapt to any viewer's terminal.
  local target="$1"
  local value="${2:-on}"
  if [ -z "$target" ]; then
    error.log "Usage: otmux window.aggressive.resize <target> <?value:on>"
    return 1
  fi
  case "$value" in
    on|off) ;;
    *) error.log "value must be 'on' or 'off' (got: $value)"; return 1 ;;
  esac
  $TMUX_CMD set-window-option -t "$target" aggressive-resize "$value" 2>/dev/null
}
otmux.window.aggressive.resize.completion.target() { private.complete.windows; }
otmux.window.aggressive.resize.completion.value() { echo "on"; echo "off"; }

# ─────────────────────────────────────────────────────────────────────────────
# LIST with custom format — programmatic queries
# tmux list-panes/list-windows accept -F format strings; otmux.panes/.windows
# use a fixed format. These let scripts query arbitrary fields.
# ─────────────────────────────────────────────────────────────────────────────

otmux.pane.list.format() # <format> <?target> # list panes with custom -F format string
{
  # Wrapper for `tmux list-panes -F <format>`. Default scope: -a (all sessions)
  # if <?target> omitted; otherwise restricted to the target.
  # Example: otmux pane.list.format '#{pane_index} #{pane_width}x#{pane_height}' ooshTeam
  local format="$1"
  local target="${2:-}"
  if [ -z "$format" ]; then
    error.log "Usage: otmux pane.list.format <format> <?target>"
    return 1
  fi
  if [ -n "$target" ]; then
    $TMUX_CMD list-panes -t "$target" -s -F "$format" 2>/dev/null
  else
    $TMUX_CMD list-panes -a -F "$format" 2>/dev/null
  fi
}

otmux.window.list.format() # <format> <?session> # list windows with custom -F format
{
  # Wrapper for `tmux list-windows -F <format>`. Default scope: -a (all
  # sessions); restrict by passing <?session>.
  local format="$1"
  local session="${2:-}"
  if [ -z "$format" ]; then
    error.log "Usage: otmux window.list.format <format> <?session>"
    return 1
  fi
  if [ -n "$session" ]; then
    $TMUX_CMD list-windows -t "$session" -F "$format" 2>/dev/null
  else
    $TMUX_CMD list-windows -a -F "$format" 2>/dev/null
  fi
}
otmux.window.list.format.completion.session() { private.complete.sessions; }

# ─────────────────────────────────────────────────────────────────────────────
# BUFFER COMMANDS
# ─────────────────────────────────────────────────────────────────────────────


private.otmux.showBuffer() # <?buffer> # show buffer contents; buffer=buffer name
{
  $TMUX_CMD show-buffer "$@"
}
private.otmux.showBuffer.completion.buffer() { private.complete.buffers; }

private.otmux.setBuffer() # <data> # set buffer data
{
  if [ -n "$1" ]; then
    $TMUX_CMD set-buffer "$1"
  else
    error.log "usage: otmux setBuffer <data>"
    return 1
  fi
}

private.otmux.loadBuffer() # <file> # load buffer from file
{
  if [ -n "$1" ]; then
    $TMUX_CMD load-buffer "$1"
  else
    error.log "usage: otmux loadBuffer <file>"
    return 1
  fi
}

private.otmux.saveBuffer() # <file> # save buffer to file
{
  if [ -n "$1" ]; then
    $TMUX_CMD save-buffer "$1"
  else
    error.log "usage: otmux saveBuffer <file>"
    return 1
  fi
}

private.otmux.deleteBuffer() # <?buffer> # delete buffer; buffer=buffer name
{
  $TMUX_CMD delete-buffer "$@"
}
private.otmux.deleteBuffer.completion.buffer() { private.complete.buffers; }

private.otmux.pasteBuffer() # # paste buffer contents
{
  $TMUX_CMD paste-buffer "$@"
}

private.otmux.chooseBuffer() # # interactively choose a buffer
{
  $TMUX_CMD choose-buffer "$@"
}

# ─────────────────────────────────────────────────────────────────────────────
# CLIENT COMMANDS
# ─────────────────────────────────────────────────────────────────────────────

private.otmux.detachClient() # <?client> # detach client; client=client tty (e.g., /dev/pts/0)
{
  $TMUX_CMD detach-client "$@"
}
private.otmux.detachClient.completion.client() { private.complete.clients; }

private.otmux.suspendClient() # # suspend current client
{
  $TMUX_CMD suspend-client "$@"
}

private.otmux.refreshClient() # # refresh client display
{
  $TMUX_CMD refresh-client "$@"
}

private.otmux.chooseClient() # # interactively choose a client
{
  $TMUX_CMD choose-client "$@"
}

private.otmux.chooseTree() # # interactively choose session/window
{
  $TMUX_CMD choose-tree "$@"
}

private.otmux.lockClient() # # lock the client
{
  $TMUX_CMD lock-client "$@"
}

# ─────────────────────────────────────────────────────────────────────────────
# SERVER COMMANDS
# ─────────────────────────────────────────────────────────────────────────────

private.otmux.startServer() # # start the tmux server
{
  $TMUX_CMD start-server
}

private.otmux.killServer() # # kill the tmux server
{
  $TMUX_CMD kill-server
}

private.otmux.lockServer() # # lock all clients
{
  $TMUX_CMD lock-server
}

otmux.info() # # show server information
{
  $TMUX_CMD info
}

# ─────────────────────────────────────────────────────────────────────────────
# CONFIGURATION COMMANDS
# ─────────────────────────────────────────────────────────────────────────────

otmux.source() # <?file> # source a config file (defaults to otmux config)
{
  if [ -n "$1" ]; then
    $TMUX_CMD source-file "$1"
  elif [ -f "$OTMUX_CONFIG" ]; then
    $TMUX_CMD source-file "$OTMUX_CONFIG"
    echo "Loaded: $OTMUX_CONFIG"
  else
    $TMUX_CMD source-file ~/.tmux.conf
  fi
}

otmux.reload() # # reload the otmux config (mouse, clipboard, etc.)
{
  if [ -f "$OTMUX_CONFIG" ]; then
    $TMUX_CMD source-file "$OTMUX_CONFIG"
    echo -e "${BOLD_GREEN}Reloaded:${NORMAL} $OTMUX_CONFIG"
    echo -e "${GRAY}Features: mouse support, macOS clipboard, vi keys, 256 colors${NORMAL}"
  elif [ -f ~/.tmux.conf ]; then
    $TMUX_CMD source-file ~/.tmux.conf
    echo -e "${BOLD_GREEN}Reloaded:${NORMAL} ~/.tmux.conf"
  else
    error.log "No config file found"
    return 1
  fi
}

private.otmux.configPath() # # show the otmux config file location
{
  if [ -f "$OTMUX_CONFIG" ]; then
    echo "$OTMUX_CONFIG"
  else
    echo "~/.tmux.conf"
  fi
}

# Default tmux configuration template location
OTMUX_DEFAULT_CONFIG_TEMPLATE="${OOSH_DIR:-$(dirname "$0")}/templates/user/tmux_default_config"

# Output the default tmux configuration from external template
private.default.config() {
  if [ -f "$OTMUX_DEFAULT_CONFIG_TEMPLATE" ]; then
    cat "$OTMUX_DEFAULT_CONFIG_TEMPLATE"
  else
    error.log "Template not found: $OTMUX_DEFAULT_CONFIG_TEMPLATE"
    return 1
  fi
}

otmux.config.init() # # create default tmux config if not exists (with color support)
{
  local config_file="$HOME/.tmux.conf"
  local oosh_link="$OTMUX_CONFIG"
  
  # Check if config already exists
  if [ -f "$config_file" ]; then
    echo "Config already exists: $config_file"
    echo "Use 'otmux config.reset' to overwrite with defaults"
    return 0
  fi
  
  # Create the config file
  echo "Creating default tmux config: $config_file"
  private.default.config > "$config_file"
  
  # Create symlink in oosh directory if it doesn't exist
  if [ ! -e "$oosh_link" ] && [ -n "$oosh_link" ]; then
    ln -sf "$config_file" "$oosh_link" 2>/dev/null && \
      echo "Created symlink: $oosh_link -> $config_file"
  fi
  
  # Source the config if tmux server is running
  if $TMUX_CMD list-sessions &>/dev/null; then
    $TMUX_CMD source-file "$config_file"
    echo "Config loaded into running tmux server"
  fi
  
  echo ""
  echo "Features enabled:"
  echo "  - Mouse support (click, scroll, select)"
  echo "  - Pane title headers (agent names at top border)"
  echo "  - Color support (FORCE_COLOR=1, COLORTERM=truecolor)"
  echo "  - Vi-style copy mode"
  echo "  - 50,000 line scrollback"
  echo "  - Intuitive splits: prefix + | (horizontal), prefix + - (vertical)"
  echo ""
  echo "Run 'otmux setup.default' to apply clipboard and all defaults to running server"
}

otmux.config.reset() # # reset tmux config to default (overwrites existing)
{
  local config_file="$HOME/.tmux.conf"
  local oosh_link="$OTMUX_CONFIG"
  local backup_file="$config_file.backup.$(date +%Y%m%d_%H%M%S)"
  
  # Backup existing config if present
  if [ -f "$config_file" ] && [ ! -L "$config_file" ]; then
    cp "$config_file" "$backup_file"
    echo "Backed up existing config to: $backup_file"
  fi
  
  # Create/overwrite the config file
  echo "Resetting tmux config to defaults: $config_file"
  private.default.config > "$config_file"
  
  # Ensure symlink in oosh directory
  if [ -n "$oosh_link" ]; then
    rm -f "$oosh_link" 2>/dev/null
    ln -sf "$config_file" "$oosh_link" 2>/dev/null && \
      echo "Updated symlink: $oosh_link -> $config_file"
  fi
  
  # Source the config if tmux server is running
  if $TMUX_CMD list-sessions &>/dev/null; then
    $TMUX_CMD source-file "$config_file"
    echo "Config reloaded into running tmux server"
  fi
  
  echo ""
  echo "Config reset to defaults with color support for Claude Code"
}

# ─────────────────────────────────────────────────────────────────────────────
# SETUP — apply defaults to running server
# ─────────────────────────────────────────────────────────────────────────────

otmux.setup.default() # # apply sensible defaults to running server (pane headers, clipboard, mouse, window-size)
{
  # Pane title headers — show agent name at top, bold if active
  $TMUX_CMD set -g pane-border-status top
  $TMUX_CMD set -g pane-border-format " #{?pane_active,#[bold],}#T#[default] "

  # Mouse, vi keys, colors, scrollback
  $TMUX_CMD set -g mouse on
  $TMUX_CMD setw -g mode-keys vi
  $TMUX_CMD set -g default-terminal "xterm-256color"
  $TMUX_CMD set -ga terminal-overrides ",xterm-256color:Tc"
  $TMUX_CMD set -g history-limit 50000
  $TMUX_CMD set -g renumber-windows on

  # window-size=largest — keep the geometry of the largest attached client
  # (smaller clients scroll). Without this, tron + agent attach causes the smaller
  # of the two to dictate window dimensions, often shrinking team layouts to 0-width
  # panes. MANDATORY for multi-client team sessions. Requires tmux 2.9+.
  $TMUX_CMD set -g window-size largest 2>/dev/null

  # aggressive-resize=on — window-level safety net for the same multi-client
  # scenario. Helps in edge cases where two clients attach with different
  # geometries (only the active window resizes to the focused client).
  $TMUX_CMD setw -g aggressive-resize on 2>/dev/null

  # OS-independent clipboard
  private.otmux.setup.clipboard

  success.log "defaults applied to running server (incl. window-size=largest)"
}

otmux.window.size() # <?value:largest> # set server-wide window-size (largest|smallest|latest|manual)
{
  # Convenience for adjusting after setup.default. Useful when:
  #   • Forcing latest/manual for testing or single-client sessions
  #   • Re-applying largest if tmux config drift occurred
  # Required for tronMonitor safety (see B4.2). Idempotent — safe to call repeatedly.
  local value="${1:-largest}"
  case "$value" in
    largest|smallest|latest|manual) ;;
    *) error.log "Invalid window-size: $value (use largest|smallest|latest|manual)"; return 1 ;;
  esac
  $TMUX_CMD set -g window-size "$value"
  success.log "window-size set to: $value"
}
otmux.window.size.completion.value() {
  echo "largest"
  echo "smallest"
  echo "latest"
  echo "manual"
}

# ── window.size floor: prevent 0×0 collapse in unattached sessions (B8) ────────
#
# When window-size=largest is set (server default since B4.2) and NO clients are
# attached, tmux has no client geometry to size against — windows collapse to
# 1×1. This makes background/restored agents unreadable until a client attaches.
#
# Lock mechanism: per-window switch to window-size=manual + resize-window to a
# floor (default 80×40). Persistence in OTMUX_SIZE_LOCKS so we know which windows
# are user-locked vs server-default. Unlock = revert to largest.
#
# Lock semantics (per PO decision, Option A): explicit unlock only. Real-client
# attach does NOT auto-unlock. Predictable + follows OOSH explicit > implicit.

: ${OTMUX_SIZE_LOCKS:=${CONFIG_PATH:-$HOME/config}/otmux.size.locks.env}
: ${OTMUX_SIZE_FLOOR_W:=80}
: ${OTMUX_SIZE_FLOOR_H:=40}

private.otmux.size.lock.set() { # <session:win> <width> <height> # upsert lock entry
  local key="$1" w="$2" h="$3"
  [ -z "$key" ] || [ -z "$w" ] || [ -z "$h" ] && return 1
  mkdir -p "$(dirname "$OTMUX_SIZE_LOCKS")" 2>/dev/null
  touch "$OTMUX_SIZE_LOCKS"
  # Atomic upsert via awk — fully literal match, immune to regex metachars in
  # session names (orphan tmux sessions can have escape codes / brackets in
  # their name; bug #1 fix).
  awk -v k="${key}|" 'substr($0, 1, length(k)) != k' "$OTMUX_SIZE_LOCKS" > "${OTMUX_SIZE_LOCKS}.tmp" 2>/dev/null
  mv "${OTMUX_SIZE_LOCKS}.tmp" "$OTMUX_SIZE_LOCKS"
  echo "${key}|${w}|${h}|$(date +%s)" >> "$OTMUX_SIZE_LOCKS"
}

private.otmux.size.lock.remove() { # <session:win> # delete lock entry (literal-match, regex-safe)
  local key="$1"
  [ -z "$key" ] && return 1
  [ -f "$OTMUX_SIZE_LOCKS" ] || return 0
  awk -v k="${key}|" 'substr($0, 1, length(k)) != k' "$OTMUX_SIZE_LOCKS" > "${OTMUX_SIZE_LOCKS}.tmp" 2>/dev/null
  mv "${OTMUX_SIZE_LOCKS}.tmp" "$OTMUX_SIZE_LOCKS"
}

private.otmux.size.lock.has() { # <session:win> # 0 if locked, 1 otherwise (literal-match)
  local key="$1"
  [ -z "$key" ] && return 1
  [ -f "$OTMUX_SIZE_LOCKS" ] || return 1
  awk -v k="${key}|" 'substr($0, 1, length(k)) == k {found=1; exit} END {exit !found}' "$OTMUX_SIZE_LOCKS"
}

private.otmux.size.lock.list() { # <?session> # list locked window keys (optionally filtered to session)
  [ -f "$OTMUX_SIZE_LOCKS" ] || return 0
  local sess="${1:-}"
  if [ -n "$sess" ]; then
    # Literal prefix match: session-name + ':'
    awk -v p="${sess}:" 'substr($0, 1, length(p)) == p' "$OTMUX_SIZE_LOCKS" 2>/dev/null | cut -d'|' -f1
  else
    cut -d'|' -f1 "$OTMUX_SIZE_LOCKS" 2>/dev/null
  fi
}

private.otmux.size.lock.cleanup() { # # drop entries whose session no longer exists
  [ -f "$OTMUX_SIZE_LOCKS" ] || return 0
  local kept="" key w h ts sess
  while IFS='|' read -r key w h ts; do
    [ -z "$key" ] && continue
    sess="${key%%:*}"
    if $TMUX_CMD has-session -t "$sess" 2>/dev/null; then
      kept="${kept}${key}|${w}|${h}|${ts}"$'\n'
    fi
  done < "$OTMUX_SIZE_LOCKS"
  printf '%s' "$kept" > "$OTMUX_SIZE_LOCKS"
}

# ── Shorter verb aliases for size ops — operate on current session by default ─
# `otmux size.unlock` is faster to type than `otmux window.size.unlock`.
# No args → resolve current tmux session from TMUX_PANE.

private.otmux.size.currentSession() { # # echo current tmux session name or empty
  [ -z "$TMUX_PANE" ] && return 1
  $TMUX_CMD display-message -p -t "$TMUX_PANE" '#{session_name}' 2>/dev/null
}

otmux.size.unlock() # <?session> # shorter alias — defaults to current session
{
  local s="${1:-$(private.otmux.size.currentSession)}"
  [ -z "$s" ] && { error.log "size.unlock: no session given and not running in tmux"; return 1; }
  otmux.window.size.unlock "$s"
  $TMUX_CMD set-window-option -t "${s}:" aggressive-resize on 2>/dev/null
  info.log "size.unlock: $s now dynamic (window-size=largest + aggressive-resize=on)"
}
otmux.size.unlock.completion.session() { private.complete.sessions; }

otmux.size.lock() # <?session> <?width:80> <?height:40> # shorter alias for window.size.lock on current session
{
  local s="${1:-$(private.otmux.size.currentSession)}"
  [ -z "$s" ] && { error.log "size.lock: no session given and not running in tmux"; return 1; }
  shift 2>/dev/null
  otmux.window.size.lock "$s" "$@"
}
otmux.size.lock.completion.session() { private.complete.sessions; }

otmux.layout.dynamic() # <?window> # restore current-window panes to dynamic sizing + tiled redistribute
{
  # Companion to size.unlock at WINDOW granularity. size.unlock works on a
  # whole session — this method targets just the current window, applies an
  # immediate tiled layout so panes redistribute visibly, and aligns S9 to
  # the new dynamic state. Use case (Tron 2026-05-26): operator notices one
  # window stuck at locked size; wants to free just that window without
  # touching others in the session.
  #
  # Steps:
  #   1. Resolve target window (arg or current via display-message)
  #   2. Set per-window aggressive-resize on (responds to client geometry)
  #   3. Set per-window window-size=largest (dynamic mode)
  #   4. Remove S9 lock entry for session:window (align state)
  #   5. select-layout tiled — immediate redistribute
  #   6. refresh-client -S — sync survivors
  local target="$1"
  if [ -z "$target" ]; then
    [ -z "$TMUX_PANE" ] && { error.log "layout.dynamic: no window given and not running in tmux"; return 1; }
    local sess wIdx
    sess=$($TMUX_CMD display-message -p -t "$TMUX_PANE" '#{session_name}' 2>/dev/null)
    wIdx=$($TMUX_CMD display-message -p -t "$TMUX_PANE" '#{window_index}' 2>/dev/null)
    [ -z "$sess" ] || [ -z "$wIdx" ] && { error.log "layout.dynamic: cannot resolve current window"; return 1; }
    target="${sess}:${wIdx}"
  fi
  # Validate target reaches an existing window
  if ! $TMUX_CMD list-windows -t "$target" -F '#{window_id}' 2>/dev/null | grep -q .; then
    error.log "layout.dynamic: no such window '$target'"
    return 1
  fi
  # Steps 2-3: dynamic sizing knobs (per-window)
  $TMUX_CMD set-window-option -t "$target" aggressive-resize on 2>/dev/null
  $TMUX_CMD set-window-option -t "$target" window-size largest 2>/dev/null
  # Step 4: align S9
  private.otmux.size.lock.remove "$target" 2>/dev/null
  # Step 5: immediate redistribute
  if ! $TMUX_CMD select-layout -t "$target" tiled 2>/dev/null; then
    warn.log "layout.dynamic: select-layout tiled failed for $target"
  fi
  # Step 6: client re-sync
  $TMUX_CMD refresh-client -S 2>/dev/null
  success.log "layout.dynamic: $target now dynamic (largest + tiled, S9 cleared)"
}
otmux.layout.dynamic.completion.window() {
  $TMUX_CMD list-windows -a -F "#{session_name}:#{window_index}" 2>/dev/null
}

otmux.size.status() # # shorter alias for window.size.status
{
  otmux.window.size.status "$@"
}

otmux.fit() # <?session> # resize session window to caller's terminal cols×rows (snaps to current client)
{
  # Reads the calling client's terminal dimensions via display-message and
  # applies them to the session's window. Pairs with size.unlock (=largest)
  # for multi-client setups: fit gives a one-shot snap to *your* terminal,
  # unlock returns to follow-largest behavior on next attach event.
  # `resize-window -x -y` implicitly sets window-size=manual on the window;
  # follow with `otmux size.unlock` to return to dynamic-largest.
  [ -z "$TMUX_PANE" ] && { error.log "fit: not running in tmux (TMUX_PANE empty)"; return 1; }
  local session="${1:-$(private.otmux.size.currentSession)}"
  this.isSessionName "$session" || { error.log "fit: invalid session '$session'"; return 1; }
  otmux has "$session" 2>/dev/null || { error.log "fit: '$session' is not a live tmux session"; return 1; }
  # Caller's client dims — display-message defaults to the most-recently
  # active client for the session containing TMUX_PANE. That IS the caller
  # in interactive use (they just typed the command, so they're most recent).
  local W H
  W=$($TMUX_CMD display-message -p -t "$TMUX_PANE" '#{client_width}' 2>/dev/null)
  H=$($TMUX_CMD display-message -p -t "$TMUX_PANE" '#{client_height}' 2>/dev/null)
  if ! [[ "$W" =~ ^[0-9]+$ ]] || ! [[ "$H" =~ ^[0-9]+$ ]]; then
    error.log "fit: cannot read caller client dimensions ($W x $H)"
    return 1
  fi
  $TMUX_CMD resize-window -t "$session" -x "$W" -y "$H" 2>/dev/null
  local rc=$?
  if [ $rc -eq 0 ]; then
    success.log "fit: $session → ${W}x${H} (caller terminal)"
  else
    error.log "fit: resize-window failed (rc=$rc) for $session at ${W}x${H}"
  fi
  return $rc
}
otmux.fit.completion.session() { private.complete.sessions; }

otmux.window.size.lock() # <?session> <?width:80> <?height:40> # enforce floor + lock window-size=manual
{
  # Iterates windows in <?session> (or all sessions if omitted). Skips windows
  # already wider AND taller than floor — never shrinks. Locks each modified
  # window via window-size=manual + resize-window. Persists to OTMUX_SIZE_LOCKS.
  # Idempotent: re-running on already-locked windows is a no-op.
  local session="${1:-}"
  local w="${2:-$OTMUX_SIZE_FLOOR_W}"
  local h="${3:-$OTMUX_SIZE_FLOOR_H}"

  local fmt='#{session_name}:#{window_index}|#{window_width}|#{window_height}'
  local listArgs=()
  if [ -n "$session" ]; then
    $TMUX_CMD has-session -t "$session" 2>/dev/null || {
      error.log "no such session: $session"
      return 1
    }
    listArgs=(-t "$session")
  else
    listArgs=(-a)
  fi

  local locked=0 skipped=0
  local line key cw ch
  while IFS='|' read -r key cw ch; do
    [ -z "$key" ] && continue
    if [ "${cw:-0}" -ge "$w" ] && [ "${ch:-0}" -ge "$h" ]; then
      skipped=$((skipped + 1))
      continue
    fi
    $TMUX_CMD set-window-option -t "$key" window-size manual 2>/dev/null
    $TMUX_CMD resize-window -t "$key" -x "$w" -y "$h" 2>/dev/null
    private.otmux.size.lock.set "$key" "$w" "$h"
    locked=$((locked + 1))
  done < <($TMUX_CMD list-windows "${listArgs[@]}" -F "$fmt" 2>/dev/null)

  success.log "size.lock — $locked locked at ${w}×${h}, $skipped already ≥ floor"
}
otmux.window.size.lock.completion.session() { private.complete.sessions; }

otmux.window.size.unlock() # <?session> # restore window-size=largest on locked windows
{
  # Reverts session windows (or all locked windows server-wide) to dynamic
  # sizing. Removes persistence entries. Idempotent.
  local session="${1:-}"
  local unlocked=0
  local key
  while IFS= read -r key; do
    [ -z "$key" ] && continue
    $TMUX_CMD set-window-option -t "$key" window-size largest 2>/dev/null
    private.otmux.size.lock.remove "$key"
    unlocked=$((unlocked + 1))
  done < <(private.otmux.size.lock.list "$session")

  success.log "size.unlock — $unlocked windows reverted to dynamic"
}
otmux.window.size.unlock.completion.session() { private.complete.sessions; }

otmux.window.size.status() # # tabular: SESSION:WINDOW SIZE LOCKED CLIENTS — collapsed=red, locked=yellow, small=cyan, healthy=green
{
  # First age out stale lock entries (sessions that no longer exist)
  private.otmux.size.lock.cleanup

  printf "  %-40s %-12s %-8s %s\n" "SESSION:WINDOW" "SIZE" "LOCKED" "CLIENTS"
  printf "  %-40s %-12s %-8s %s\n" "─────────────────────" "──────" "──────" "───────"

  # Build client-by-session map first (one tmux call instead of per-row).
  # Use #{session_attached} from list-sessions — more reliable than parsing
  # list-clients (which may not see clients attached via switch-client).
  local attachedBySess
  attachedBySess=$($TMUX_CMD list-sessions -F '#{session_name}|#{session_attached}' 2>/dev/null)

  local fmt='#{session_name}:#{window_index}|#{window_width}|#{window_height}'
  local key cw ch sess clients locked color marker
  while IFS='|' read -r key cw ch; do
    [ -z "$key" ] && continue
    sess="${key%%:*}"
    # Lookup attached count for this session via literal awk match
    clients=$(echo "$attachedBySess" | awk -F'|' -v s="$sess" '$1==s{print $2}')
    [ -z "$clients" ] && clients=0

    locked="no"
    private.otmux.size.lock.has "$key" && locked="yes"

    # Classification (bug #2 fix):
    #   COLLAPSED (red) — below floor AND no clients attached. Truly broken.
    #   SMALL     (cyan)— below floor but client attached. User has a small
    #                     terminal — that's their choice, not a bug.
    #   LOCKED    (yellow) — explicit window-size=manual via lock command
    #   HEALTHY   (green) — at-or-above floor, no lock
    color="${BOLD_GREEN}"
    marker=""
    local belowFloor=false
    if [ "${cw:-0}" -lt "$OTMUX_SIZE_FLOOR_W" ] || [ "${ch:-0}" -lt "$OTMUX_SIZE_FLOOR_H" ]; then
      belowFloor=true
    fi
    if [ "$belowFloor" = true ] && [ "${clients:-0}" -eq 0 ]; then
      color="${BOLD_RED}"
      marker=" [collapsed]"
    elif [ "$belowFloor" = true ]; then
      color="${BOLD_CYAN}"
      marker=" [small client]"
    elif [ "$locked" = "yes" ]; then
      color="${BOLD_YELLOW}"
    fi

    printf "  %s%-40s${NORMAL} %-12s %-8s %s%s\n" \
      "$color" "$key" "${cw}×${ch}" "$locked" "$clients" "$marker"
  done < <($TMUX_CMD list-windows -a -F "$fmt" 2>/dev/null)
}
otmux.window.size.status.completion() { :; }

otmux.window.size.floor.apply() # <?width:80> <?height:40> # sweep all UNATTACHED collapsed sessions; lock at floor — idempotent self-heal
{
  # Cron-friendly self-healing: find every UNATTACHED session whose windows are
  # below the floor, lock at floor. Sessions with ≥1 client are skipped — their
  # client is sizing them (even if smaller than floor, that's the user's choice).
  # For explicit per-session locking regardless of client state, use window.size.lock.
  local w="${1:-$OTMUX_SIZE_FLOOR_W}"
  local h="${2:-$OTMUX_SIZE_FLOOR_H}"
  local locked=0 skipped_attached=0 skipped_ok=0

  local sess attached cw ch
  while IFS='|' read -r sess attached; do
    [ -z "$sess" ] && continue
    if [ "${attached:-0}" -gt 0 ]; then
      skipped_attached=$((skipped_attached + 1))
      continue
    fi
    # Run lock for this session — it skips already-floored windows internally
    local before
    before=$(otmux.window.size.lock "$sess" "$w" "$h" 2>&1 | grep -oE '[0-9]+ locked' | grep -oE '[0-9]+')
    locked=$((locked + ${before:-0}))
  done < <($TMUX_CMD list-sessions -F '#{session_name}|#{session_attached}' 2>/dev/null)

  success.log "floor.apply — $locked windows locked at ${w}×${h} (skipped attached sessions)"
}
otmux.window.size.floor.apply.completion() { :; }

private.otmux.setup.clipboard() # # detect OS/SSH and configure clipboard bindings
{
  # Remote session: use OSC 52 so copy reaches the LOCAL machine's clipboard.
  # pbcopy/xclip on the remote box would write to the remote clipboard (useless).
  if [ -n "$SSH_CONNECTION" ] || [ -n "$SSH_TTY" ] || [ -n "$SSH_CLIENT" ]; then
    private.otmux.setup.clipboard.osc52
    return
  fi
  source "$OOSH_DIR/os"
  if os.check private.otmux.setup.clipboard; then
    $RESULT
  else
    important.log "clipboard: unsupported OS ($OSTYPE) — configure manually"
  fi
}

private.otmux.setup.clipboard.darwin() # # macOS clipboard via pbcopy/pbpaste (local session)
{
  local copyCmd="pbcopy"
  local pasteCmd="pbpaste"
  private.otmux.setup.clipboard.apply "$copyCmd" "$pasteCmd"
}

private.otmux.setup.clipboard.linux() # # Linux clipboard via xclip, xsel, or termux (local session)
{
  local copyCmd="" pasteCmd=""
  if command -v xclip &>/dev/null; then
    copyCmd="xclip -selection clipboard"
    pasteCmd="xclip -selection clipboard -o"
  elif command -v xsel &>/dev/null; then
    copyCmd="xsel --clipboard"
    pasteCmd="xsel --clipboard --output"
  elif command -v termux-clipboard-set &>/dev/null; then
    copyCmd="termux-clipboard-set"
    pasteCmd="termux-clipboard-get"
  else
    important.log "clipboard: no xclip, xsel, or termux-clipboard found — install one"
    return 1
  fi
  private.otmux.setup.clipboard.apply "$copyCmd" "$pasteCmd"
}

private.otmux.setup.clipboard.osc52() # # OSC 52 mode — tmux → terminal → LOCAL clipboard (for SSH sessions)
{
  # tmux emits OSC 52 escape sequences; the user's LOCAL terminal (iTerm2,
  # WezTerm, kitty, modern Terminal.app) intercepts and writes to the local
  # clipboard. No remote helper needed. Works through ssh transparently.
  #
  # Requires on the local terminal:
  #   iTerm2:       Preferences → General → Selection → "Applications in
  #                 terminal may access clipboard" = ON
  #   WezTerm:      on by default
  #   kitty:        clipboard_control write-clipboard write-primary (default)
  #   Terminal.app: OSC 52 support is limited; use iTerm2 if possible
  #
  $TMUX_CMD set -g set-clipboard on            # tmux emits OSC 52 on copy
  $TMUX_CMD set -g allow-passthrough on 2>/dev/null  # tmux 3.3+ (nested sessions)
  $TMUX_CMD bind -T copy-mode-vi v send-keys -X begin-selection
  # No external copy command — tmux uses set-clipboard=on to emit OSC 52
  $TMUX_CMD bind -T copy-mode-vi y send-keys -X copy-selection-and-cancel
  $TMUX_CMD bind -T copy-mode-vi Enter send-keys -X copy-selection-and-cancel
  $TMUX_CMD bind -T copy-mode-vi MouseDragEnd1Pane send-keys -X copy-selection-and-cancel
  # Paste: local clipboard → terminal types it → tmux receives it naturally.
  # No tmux binding needed for paste — user presses Cmd+V in local terminal.
  # For paste from tmux's own buffer, keep the default prefix + ] binding.

  info.log "clipboard: OSC 52 mode (remote-aware). Paste via local Cmd+V."
}

private.otmux.setup.clipboard.apply() # <copyCmd> <pasteCmd> # bind tmux copy/paste to OS clipboard (local session)
{
  local copyCmd="$1"
  local pasteCmd="$2"

  $TMUX_CMD set -g set-clipboard on
  $TMUX_CMD bind -T copy-mode-vi v send-keys -X begin-selection
  $TMUX_CMD bind -T copy-mode-vi y send-keys -X copy-pipe-and-cancel "$copyCmd"
  $TMUX_CMD bind -T copy-mode-vi Enter send-keys -X copy-pipe-and-cancel "$copyCmd"
  $TMUX_CMD bind -T copy-mode-vi MouseDragEnd1Pane send-keys -X copy-pipe-and-cancel "$copyCmd"
  $TMUX_CMD bind p run "$pasteCmd | tmux load-buffer - && tmux paste-buffer"

  info.log "clipboard: using $copyCmd / $pasteCmd"
}

otmux.setup.clipboard.remote() # # force OSC 52 (remote-aware) clipboard mode — for SSH sessions
{
  private.otmux.setup.clipboard.osc52
}

otmux.setup.clipboard.local() # # force local pbcopy/xclip clipboard mode
{
  source "$OOSH_DIR/os"
  if os.check private.otmux.setup.clipboard; then
    $RESULT
  else
    important.log "clipboard: unsupported OS ($OSTYPE)"
    return 1
  fi
}

otmux.install() # # install tmux via oo cmd and initialize config
{
  echo "Checking tmux installation..."
  
  # Use oo cmd to check and install tmux
  # oo cmd handles package manager detection via oo.pm.discover and $OOSH_PM
  oo cmd tmux
  
  # Verify installation
  if ! command -v tmux &>/dev/null; then
    error.log "tmux installation failed"
    return 1
  fi
  
  echo ""
  echo "tmux installed successfully: $(tmux -V)"
  echo ""
  
  # Initialize config
  otmux.config.init
}

private.otmux.setGlobal() # <option> <value> # set a global option
{
  $TMUX_CMD set-option -g "$@"
}

private.otmux.setWindow() # <option> <value> # set a window option
{
  $TMUX_CMD set-window-option "$@"
}

private.otmux.showGlobal() # <?option> # show global options
{
  $TMUX_CMD show-options -g "$@"
}

private.otmux.showWindow() # <?option> # show window options
{
  $TMUX_CMD show-window-options "$@"
}

private.otmux.showHooks() # # show hooks
{
  $TMUX_CMD show-hooks "$@"
}

private.otmux.setHook() # <hook> <command> # set a hook
{
  $TMUX_CMD set-hook "$@"
}

# ─────────────────────────────────────────────────────────────────────────────
# KEY BINDING COMMANDS
# ─────────────────────────────────────────────────────────────────────────────



# ─────────────────────────────────────────────────────────────────────────────
# ENVIRONMENT COMMANDS
# ─────────────────────────────────────────────────────────────────────────────

private.otmux.setEnv() # <name> <value> # set environment variable
{
  $TMUX_CMD set-environment "$@"
}

private.otmux.showEnv() # <?name> # show environment variables
{
  $TMUX_CMD show-environment "$@"
}

# ─────────────────────────────────────────────────────────────────────────────
# SEND/RUN COMMANDS
# ─────────────────────────────────────────────────────────────────────────────

private.otmux.pane.isClaudeCode() # <target> # returns 0 if pane runs Claude Code, 1 if bash/other
{
  local target="$1"
  [ -z "$target" ] && return 1
  local cmd
  cmd=$($TMUX_CMD display-message -t "$target" -p '#{pane_current_command}' 2>/dev/null)
  case "$cmd" in
    node|claude) return 0 ;;
    bash|zsh|sh)
      # Claude Code may show as bash parent — check for claude child process
      "$OOSH_DIR/claudeCode" process.running "$target" 2>/dev/null && return 0
      return 1 ;;
    *) return 1 ;;
  esac
}

otmux.send.raw() # <target> <keys> # raw send keys to pane; target=pane id, index, or direction (U/D/L/R)
{
  local target=$(private.resolve.target "$1")
  shift
  # Bug #4 defense: validate target format BEFORE send-keys
  if ! private.otmux.target.isPane "$target"; then
    error.log "otmux.send.raw: invalid pane target '$target' — refusing to send"
    return 1
  fi
  # Detect trailing Enter — add delay for TUI apps like Claude Code.
  # Without delay, the TUI receives Enter before processing the text.
  local last=""
  for last in "$@"; do :; done
  if [ "$last" = "Enter" ] && [ $# -gt 1 ]; then
    local text_count=$(($# - 1))
    $TMUX_CMD send-keys -t "$target" "${@:1:text_count}"
    sleep 0.05
    $TMUX_CMD send-keys -t "$target" Enter
  else
    $TMUX_CMD send-keys -t "$target" "$@"
  fi
}
otmux.send.key() # <target> <key> <?count:1> # send key N times to pane; key=Left/Right/Up/Down/Home/End/etc
{
  local target=$(private.resolve.target "$1")
  local key="$2"
  local count="${3:-1}"

  if [ -z "$target" ] || [ -z "$key" ]; then
    error.log "usage: otmux send.key <target> <key> <?count>"
    return 1
  fi
  # Bug #4 defense: validate target format
  if ! private.otmux.target.isPane "$target"; then
    error.log "otmux.send.key: invalid pane target '$target' — refusing to send"
    return 1
  fi

  $TMUX_CMD send-keys -t "$target" -N "$count" "$key"
}
otmux.send.key.completion.key() {
  echo "Left"
  echo "Right"
  echo "Up"
  echo "Down"
  echo "Home"
  echo "End"
  echo "BSpace"
  echo "DC"
  echo "Tab"
  echo "Enter"
}

otmux.parameter.completion.target() {
  echo "U"
  echo "D"
  echo "L"
  echo "R"
  private.complete.panes
}

otmux.parameter.completion.sourcePane() { private.complete.panes; }
otmux.parameter.completion.targetPane() { private.complete.panes; }
otmux.parameter.completion.session() { private.complete.sessions; }
otmux.parameter.completion.direction() {
  echo "U"
  echo "D"
  echo "L"
  echo "R"
}
otmux.parameter.completion.layout() {
  echo "even-horizontal"
  echo "even-vertical"
  echo "main-horizontal"
  echo "main-vertical"
  echo "tiled"
}
otmux.parameter.completion.window() { private.complete.windows; }

otmux.send.verified() # <target> <text> <?timeout:3> # send text to pane and verify delivery via capture
{
    local target=$(private.resolve.target "$1")
    local text="$2"
    local timeout="${3:-3}"

    if [ -z "$target" ]; then
        error.log "usage: otmux send.verified <target> <text> <?timeout>"
        return 1
    fi
    # Tron P0: empty/whitespace text is a silent no-op (prefix-only message
    # leaks into Claude TUIs as a prompt — agents hallucinate tasks). Shared
    # this.isEmpty predicate.
    if this.isEmpty "$text"; then
        debug.log "send.verified: empty/whitespace-only text — silent no-op (target=$target)"
        return 0
    fi
    # Bug #4 defense: validate target format
    if ! private.otmux.target.isPane "$target"; then
        error.log "otmux.send.verified: invalid pane target '$target' — refusing to send"
        return 1
    fi

    # Capture state BEFORE send
    local before
    before=$($TMUX_CMD capture-pane -t "$target" -p -S -5 2>/dev/null | tail -5)

    # Send text + Enter (using sendEnter for literal text handling)
    private.otmux.sendEnter "$target" "$text"

    # Wait for TUI to process
    sleep "$timeout"

    # Capture state AFTER send
    local after
    after=$($TMUX_CMD capture-pane -t "$target" -p -S -10 2>/dev/null | tail -10)

    # Verify: text should appear in pane OR pane state should have changed
    if [ "$before" = "$after" ]; then
        # Pane didn't change — likely blocked (permission dialog, overlay, etc.)
        error.log "send.verified FAILED: pane $target unchanged after send"
        RESULT="FAILED"
        return 1
    fi

    # Check if our text appears in the after capture
    if echo "$after" | grep -qF "$text"; then
        console.log "send.verified OK: text delivered to $target"
        RESULT="DELIVERED"
        return 0
    fi

    # Pane changed but text not visible — likely processed (agent consumed it)
    console.log "send.verified OK: pane $target changed (text likely processed)"
    RESULT="CHANGED"
    return 0
}

private.otmux.send.prefix() # # return sender prefix [@role pane] or empty string
# Reads role from the hivemind registry (single source of truth) — never from
# HIVEMIND_ROLE env var, which is set once at shell start and goes stale after
# pane swaps/moves/renames. Per PO directive (Tron P0, 2026-05-12): the registry
# is the ground truth; env reads here cause stale role leakage.
#
# Self-pane resolution MUST use $TMUX_PANE (Tron P0 #3, 2026-05-12 LATE):
# `otmux send` runs as a subprocess. A bare `tmux display-message -p` (no -t)
# returns the FOCUSED pane — i.e. wherever the user last clicked — NOT the
# caller's pane. tmux exports TMUX_PANE=%N to every pane's child processes,
# so the subprocess inherits its caller's pane id reliably. Pass it as -t
# to display-message to get the canonical session:win.pane format used in the
# registry. Without this, prefix has been wrong in production for everything
# routed through subprocess `otmux send` calls.
{
  local reg="${HIVEMIND_REGISTRY:-${CONFIG_PATH:-$HOME/config}/hivemind.roles.env}"
  [ -z "$TMUX_PANE" ] && return   # not running under tmux (CI/script) → no prefix
  local myPane
  myPane=$($TMUX_CMD display-message -p -t "$TMUX_PANE" "#{session_name}:#{window_index}.#{pane_index}" 2>/dev/null)
  [ -z "$myPane" ] && return
  [ -f "$reg" ] || return
  local myRole
  myRole=$(grep "^${myPane}|" "$reg" 2>/dev/null | head -1 | cut -d'|' -f2)
  [ -n "$myRole" ] && echo "[@${myRole} ${myPane}] "
}

private.otmux.is.key() # <token> # 0 if token is a tmux key name (no prefix), 1 if prose
{
  # SINGLE SOURCE OF TRUTH for key-vs-prose detection. Called by otmux.send to
  # decide whether to apply the sender prefix. Per PO directive (2026-05-12,
  # Tron P0): prefix is for INFORM-path prose ONLY; every key send must be
  # prefix-free. Detection covers:
  #   1. Any single non-whitespace char (digits, letters, punctuation)
  #   2. Named tmux keys (case-insensitive — covers user typos like 'enter')
  #   3. Modifier-prefix combinations: C-x, M-x, S-x, C-M-Up, etc.
  #   4. Plus-syntax shortcuts: Shift+Tab, Ctrl+C, Alt+M
  local t="$1"
  [ -z "$t" ] && return 1

  # 1. Single non-whitespace char
  [[ "$t" =~ ^[^[:space:]]$ ]] && return 0

  # 2. Named tmux keys + common aliases (case-insensitive)
  # Bash 3.2 compat: ${var,,} is bash 4+. Use tr for portable lowercase.
  local tl
  tl=$(printf '%s' "$t" | tr '[:upper:]' '[:lower:]')
  case "$tl" in
    up|down|left|right|home|end|space|tab|enter|escape|esc) return 0 ;;
    bspace|backspace|bs|btab|backtab) return 0 ;;
    dc|delete|del|ic|insert|ins) return 0 ;;
    ppage|pageup|pgup|npage|pagedown|pgdn|pgdown) return 0 ;;
    f1|f2|f3|f4|f5|f6|f7|f8|f9|f10|f11|f12) return 0 ;;
    any) return 0 ;;   # tmux 'Any' keyword
  esac

  # 3. Modifier-prefix combinations (C/M/S, possibly stacked: C-M-x, C-M-S-x)
  #    Tail must contain no spaces and no `-` (else collision with modifier).
  #    For multi-modifier `C-M-Up`, tail is `Up` (no `-`). For combo like
  #    `C-M-S-BTab`, tail is `BTab`.
  if [[ "$t" =~ ^([CMS]-){1,3}[^[:space:]-]+$ ]]; then
    return 0
  fi

  # 4. Plus-syntax (user-typed style): Shift+Tab, Ctrl+C, Alt+Enter,
  #    Ctrl+Shift+X (allow up to 3 chained modifiers via plus).
  if [[ "$t" =~ ^(Shift|Ctrl|Alt|Meta|Cmd|Super)(\+(Shift|Ctrl|Alt|Meta|Cmd|Super)){0,2}\+[^[:space:]]+$ ]]; then
    return 0
  fi

  return 1
}

otmux.send() # <target> <text...> # smart send: auto-detects keys, /commands, text+key combos
{
  local target=$(private.resolve.target "$1")
  shift

  if [ -z "$target" ]; then
    error.log "usage: otmux send <target> <text...>"
    return 1
  fi
  # Tron P0 (2026-05-12): empty / whitespace-only payload must be a silent no-op.
  # Sending bare `[@role pane] ` prefix into a Claude TUI is read as a prompt —
  # the receiving agent hallucinates a task to satisfy the apparent ask. The
  # guard must run BEFORE prefix logic, BEFORE key detection, BEFORE any tmux
  # send call. DRY predicate: this.isEmpty (defined in `this`, available
  # everywhere via kernel sourcing — same helper used by send.smart,
  # send.verified, and hiveMind.send family).
  if this.isEmpty "$*"; then
    debug.log "otmux.send: empty/whitespace-only payload — silent no-op (target=$target)"
    return 0
  fi
  # Bug #4 defense: validate target format BEFORE any send.
  # Catches garbage from a failed upstream resolve (e.g. error.log captured into
  # a target var) before tmux silently sends it to the focused pane.
  if ! private.otmux.target.isPane "$target"; then
    error.log "otmux.send: invalid pane target '$target' — refusing to send"
    return 1
  fi

  # Detect tmux key tokens (Enter, BTab, C-u, Shift+Tab, single chars, etc.).
  # Tron P0 (2026-05-12): prefix leaked on '[@role pane] Shift+Tab'. PO rule:
  # prefix is ONLY for prose to idle agents (INFORM path); ALL key sends — by
  # any name or modifier syntax — must be prefix-free. DRY: single is_key
  # helper called from every dispatch case below.
  local last="${@: -1}"
  local is_key=false
  private.otmux.is.key "$last" && is_key=true

  # Case 1: Single key only — raw keypress, no prefix
  if $is_key && [ $# -eq 1 ]; then
    otmux.send.raw "$target" "$last"
    return $?
  fi

  # Case 2: Text + trailing key — smart send text, then raw key
  # BUT: if ALL args are keys (e.g. `send pane Down Enter`, `send pane y Enter`,
  # `send pane Shift+Tab Tab Enter`), no prose to prefix — send all raw.
  if $is_key && [ $# -gt 1 ]; then
    local all_keys=true
    local a
    for a in "$@"; do
      private.otmux.is.key "$a" || { all_keys=false; break; }
    done
    if $all_keys; then
      # All-raw key chain (menu navigation) — no prefix
      for a in "$@"; do
        otmux.send.raw "$target" "$a"
        sleep 0.05
      done
      return 0
    fi
    # Mixed text + trailing key — prefix the text, raw the key
    local text_args=("${@:1:$#-1}")
    private.otmux.send.smart "$target" "${text_args[*]}"
    sleep 0.05
    otmux.send.raw "$target" "$last"
    return $?
  fi

  # Case 3+4: Pure text or /command — smart send handles prefix
  private.otmux.send.smart "$target" "$*"
}

private.otmux.send.smart() # <target> <text> # internal: accept-edits, prefix, verified send
{
  local target="$1"
  local text="$2"

  # Tron P0 defense-in-depth: never send a prefix-only message (caller-side
  # empty payload should be rejected at otmux.send, but smart can be entered
  # directly from internal paths — guard here too via shared this.isEmpty).
  if this.isEmpty "$text"; then
    debug.log "send.smart: empty/whitespace-only text — silent no-op (target=$target)"
    return 0
  fi

  # Step 1: Detect accept-edits mode and clear it (Claude Code panes only)
  if private.otmux.pane.isClaudeCode "$target"; then
    local capture
    capture=$($TMUX_CMD capture-pane -t "$target" -p -S -5 2>/dev/null)
    if echo "$capture" | grep -q '⏵⏵ accept'; then
      info.log "Accept-edits detected on $target — clearing"
      $TMUX_CMD send-keys -t "$target" Enter
      sleep 0.5
      $TMUX_CMD send-keys -t "$target" BTab
      sleep 0.3
      $TMUX_CMD send-keys -t "$target" BTab
      sleep 0.5
    fi
    # Clear input line
    $TMUX_CMD send-keys -t "$target" C-u
  fi

  # Step 2: Add sender prefix (Claude Code targets only, skip /commands)
  if private.otmux.pane.isClaudeCode "$target" && [[ "$text" != /* ]]; then
    local prefix
    prefix=$(private.otmux.send.prefix)
    [ -n "$prefix" ] && text="${prefix}${text}"
  fi

  # Step 3: Send with verification
  otmux.send.verified "$target" "$text" 3
  local rc=$?

  if [ $rc -eq 0 ]; then
    return 0
  fi

  # Step 4: Retry — send Enter and re-check
  warn.log "send: initial send may have failed on $target — retrying Enter"
  $TMUX_CMD send-keys -t "$target" Enter
  sleep 2

  local after
  after=$($TMUX_CMD capture-pane -t "$target" -p -S -10 2>/dev/null | tail -10)
  if echo "$after" | grep -qF "$text"; then
    info.log "send: message visible on $target after retry"
    return 0
  fi

  warn.log "send: message may have been consumed by $target — verify manually"
  return 0
}


private.otmux.sendEnter() # <target> <text> # send text followed by Enter; target=pane or direction (U/D/L/R)
{
  local target=$(private.resolve.target "$1")
  shift
  # Bug #4 defense: validate target format
  if ! private.otmux.target.isPane "$target"; then
    error.log "otmux.send.enter: invalid pane target '$target' — refusing to send"
    return 1
  fi
  # Send text literally (-l) to avoid interpreting tmux key names in the message,
  # then send Enter separately with a brief delay. This prevents words like
  # "Escape" or "Up" from being treated as key sequences, and improves reliability
  # with TUI prompts (Claude Code accept-edits, permission dialogs).
  if [ -n "$*" ]; then
    $TMUX_CMD send-keys -t "$target" -l "$*"
    sleep 0.05
  fi
  $TMUX_CMD send-keys -t "$target" Enter
}

private.otmux.sendKeys() # <target> <key1> [<key2>...] # send keys with inter-key delays for TUI apps
{
  # Sends each argument as a separate key with a brief delay between them.
  # Use this for TUI interactions where keys must be processed sequentially
  # (e.g. Claude Code permission prompts: sendKeys <pane> Down Enter).
  local target=$(private.resolve.target "$1")
  shift
  # Bug #4 defense: validate target format
  if ! private.otmux.target.isPane "$target"; then
    error.log "otmux.send.tui: invalid pane target '$target' — refusing to send"
    return 1
  fi
  for key in "$@"; do
    $TMUX_CMD send-keys -t "$target" "$key"
    sleep 0.05
  done
}


otmux.run() # <command> # run shell command
{
  $TMUX_CMD run-shell "$@"
}

otmux.display() # <message> # display a message
{
  $TMUX_CMD display-message "$@"
}

otmux.confirm() # <command> # confirm before running command
{
  $TMUX_CMD confirm-before "$@"
}

otmux.prompt() # <?template> # command prompt
{
  $TMUX_CMD command-prompt "$@"
}

otmux.menu() # # display menu
{
  $TMUX_CMD display-menu "$@"
}

# ─────────────────────────────────────────────────────────────────────────────
# COPY MODE COMMANDS
# ─────────────────────────────────────────────────────────────────────────────

otmux.copy() # # enter copy mode
{
  $TMUX_CMD copy-mode "$@"
}

otmux.clock() # # show clock
{
  $TMUX_CMD clock-mode "$@"
}

# ─────────────────────────────────────────────────────────────────────────────
# UTILITY COMMANDS
# ─────────────────────────────────────────────────────────────────────────────

otmux.wait() # <channel> # wait for a channel
{
  $TMUX_CMD wait-for "$@"
}

otmux.if() # <condition> <cmd1> <?cmd2> # conditional execution
{
  $TMUX_CMD if-shell "$@"
}

private.otmux.showMessages() # # show server messages
{
  $TMUX_CMD show-messages "$@"
}

otmux.commands() # # list all tmux commands
{
  $TMUX_CMD list-commands "$@"
}

# ─────────────────────────────────────────────────────────────────────────────
# GLOBAL FLAGS
# Note: UTF-8 mode (-u) is always enabled by default via TMUX_CMD
# ─────────────────────────────────────────────────────────────────────────────

otmux.256color() # <command> # force 256 color mode (stacks with UTF-8)
{
  $TMUX_CMD -2 "$@"
}

otmux.control() # <?command> # start in control mode (stacks with UTF-8)
{
  $TMUX_CMD -C "$@"
}

otmux.utf8() # <command> # force UTF-8 mode (already default, provided for compatibility)
{
  $TMUX_CMD "$@"
}

otmux.verbose() # <command> # enable verbose logging (stacks with UTF-8)
{
  $TMUX_CMD -v "$@"
}

otmux.socket() # <name> <command> # use specific socket name (stacks with UTF-8)
{
  local name="$1"
  shift
  $TMUX_CMD -L "$name" "$@"
}

private.otmux.socketPath() # <path> <command> # use specific socket path (stacks with UTF-8)
{
  local path="$1"
  shift
  $TMUX_CMD -S "$path" "$@"
}

otmux.config() # <file> <command> # use specific config file (stacks with UTF-8)
{
  local file="$1"
  shift
  $TMUX_CMD -f "$file" "$@"
}

# ─────────────────────────────────────────────────────────────────────────────
# STATUS & HELP
# ─────────────────────────────────────────────────────────────────────────────

otmux.version() # # show tmux version
{
  $TMUX_CMD -V
}

otmux.help() # # show tmux help
{
  $TMUX_CMD --help 2>&1 | head -50
  echo ""
  echo "For full documentation: man tmux"
}

otmux.tree() # <?session> # show formatted tree — Tier 2, fast path via batched tty+ps maps (A+B+C)
{
  # Architect-approved fast path (task-otmux-fast-path.md):
  #   A) Build pane→tty map via ONE tmux list-panes -aF call (was 75× otmux pane.get subprocess)
  #   B) Build tty→pid map via ONE ps scan (was 75× per-pane ps in process.find)
  #   C) Cache claude --version once (was N calls — same answer every time)
  # Combined effect: 40s → ~1.5s on a 76-pane server. Intra-call caches; nothing persists.
  if ! $TMUX_CMD list-sessions >/dev/null 2>&1; then
    echo -e "${BOLD_YELLOW}No tmux server running.${NORMAL} Use ${CYAN}'otmux new'${NORMAL} to start."
    return 1
  fi

  # ── Fast-path caches (built once at function entry) ──────────────────────
  # TTY map: pane_tty is '/dev/ttysNNN' but ps tty column omits '/dev/' — strip
  # on insert so the lookup key matches downstream.
  # Bash 3.2 compat (task #29): store as delimited strings (pane|tty\n and
  # tty|pid\n) instead of assoc arrays. Grep is slower than O(1) lookup but
  # bash 5 is the primary target — this is just to keep bash 3.2 functional.
  local TREE_TTY TREE_CLAUDE_PID TREE_VERSION
  TREE_TTY=$($TMUX_CMD list-panes -aF '#{session_name}:#{window_index}.#{pane_index}|#{pane_tty}' 2>/dev/null | sed 's|/dev/||')
  # ps tty="??" means detached process (no controlling tty). Such rows belong
  # to background workers whose args may incidentally contain "claude" text —
  # exclude them to avoid false positives in the per-pane lookup.
  TREE_CLAUDE_PID=$(ps -eo pid=,tty=,args= 2>/dev/null | awk '$2!="" && $2!="??" && /claude/ {print $2"|"$1}')
  # CLAUDE_CMD is an internal var of the claudeCode script (not exported), so
  # we use PATH lookup or the well-known install location as fallback.
  local _claude_bin
  _claude_bin=$(command -v claude 2>/dev/null)
  [ -z "$_claude_bin" ] && [ -x "$HOME/.local/bin/claude" ] && _claude_bin="$HOME/.local/bin/claude"
  if [ -n "$_claude_bin" ]; then
    TREE_VERSION=$("$_claude_bin" --version 2>/dev/null | head -1)
    TREE_VERSION="${TREE_VERSION%% (*}"
  fi

  local filter_session="$1"

  # Collect session info: name, attached status, creation date
  local session_data
  if [ -n "$filter_session" ]; then
    session_data=$($TMUX_CMD list-sessions -f "#{==:#{session_name},$filter_session}" \
      -F "#{session_name}|#{session_attached}|#{session_created}" 2>/dev/null)
  else
    session_data=$($TMUX_CMD list-sessions \
      -F "#{session_name}|#{session_attached}|#{session_created}" 2>/dev/null)
  fi

  if [ -z "$session_data" ]; then
    echo -e "${BOLD_YELLOW}No tmux sessions.${NORMAL}"
    return 0
  fi

  local session_count
  session_count=$(echo "$session_data" | wc -l | tr -d ' ')

  echo -e "${BOLD_WHITE}tmux sessions${NORMAL}"
  echo -e "${GRAY}│${NORMAL}"

  local session_i=0

  echo "$session_data" | while IFS='|' read -r sess_name sess_attached sess_created; do
    session_i=$((session_i + 1))
    local is_last=0
    [ "$session_i" -eq "$session_count" ] && is_last=1

    # Format creation date (Mon DD or Mon D)
    local sess_date
    if date -r "$sess_created" "+%b %-d" >/dev/null 2>&1; then
      sess_date=$(date -r "$sess_created" "+%b %-d")
    else
      sess_date=$(date -d "@$sess_created" "+%b %-d" 2>/dev/null || echo "unknown")
    fi

    # Session connector
    local sess_connector="├──"
    local pane_prefix="│   "
    if [ "$is_last" -eq 1 ]; then
      sess_connector="└──"
      pane_prefix="    "
    fi

    # Session label: name (attached, date) or name (date)
    local sess_label="$sess_name"
    local attach_tag=""
    if [ "$sess_attached" -gt 0 ] 2>/dev/null; then
      attach_tag="${BOLD_GREEN}attached${NORMAL}, "
    fi
    sess_label="${BOLD_CYAN}${sess_name}${NORMAL} ${GRAY}(${attach_tag}${GRAY}${sess_date})${NORMAL}"

    echo -e "${GRAY}${sess_connector}${NORMAL} ${sess_label}"

    # Collect panes for this session
    local pane_data
    pane_data=$($TMUX_CMD list-panes -t "$sess_name" -s \
      -F "#{window_index}|#{pane_index}|#{pane_title}|#{pane_current_command}" 2>/dev/null)

    local pane_count
    pane_count=$(echo "$pane_data" | wc -l | tr -d ' ')
    local pane_i=0

    echo "$pane_data" | while IFS='|' read -r win_idx pane_idx pane_title pane_cmd; do
      pane_i=$((pane_i + 1))

      local address="${win_idx}.${pane_idx}"
      local title="${pane_title:--}"

      # Pane connector
      local pane_connector="├──"
      [ "$pane_i" -eq "$pane_count" ] && pane_connector="└──"

      # When pane_cmd is bash/zsh, check for Claude child process and show version.
      # FAST PATH: pane→tty (TREE_TTY) + tty→claude-pid (TREE_CLAUDE_PID) — both
      # built once at function entry. Per-pane work = 2× O(1) bash assoc lookups.
      local display_cmd="$pane_cmd"
      if [[ "$pane_cmd" == "bash" || "$pane_cmd" == "zsh" ]]; then
        local pane_target="${sess_name}:${win_idx}.${pane_idx}"
        # Bash 3.2 compat: string-grep lookup instead of assoc array.
        local _tty _claude_pid
        _tty=$(echo "$TREE_TTY" | grep "^${pane_target}|" | head -1 | cut -d'|' -f2)
        if [ -n "$_tty" ]; then
          _claude_pid=$(echo "$TREE_CLAUDE_PID" | grep "^${_tty}|" | head -1 | cut -d'|' -f2)
          if [ -n "$_claude_pid" ] && [ -n "$TREE_VERSION" ]; then
            display_cmd="$TREE_VERSION"
          fi
        fi
      fi
      printf "${GRAY}%s%s${NORMAL} ${GRAY}%-5s${NORMAL} ${BOLD_WHITE}%-24s${NORMAL} ${GREEN}[%s]${NORMAL}\n" "$pane_prefix" "$pane_connector" "$address" "$title" "$display_cmd"
    done

    echo -e "${GRAY}${pane_prefix}${NORMAL}"
  done
}
otmux.tree.completion.session() { otmux.parameter.completion.session "$@"; }

otmux.tree.detailed() # <?session> # show three-level tree with Claude session IDs per pane — Tier 3 (A+B+C+D)
{
  # Same A+B+C caches as tree() plus D: single global agents.discover call (was N×57s per session).
  if ! $TMUX_CMD list-sessions >/dev/null 2>&1; then
    echo -e "${BOLD_YELLOW}No tmux server running.${NORMAL} Use ${CYAN}'otmux new'${NORMAL} to start."
    return 1
  fi

  # ── Fast-path caches (intra-call) ─────────────────────────────────────────
  # Bash 3.2 compat (task #29): delimited strings instead of assoc arrays.
  local TREE_TTY TREE_CLAUDE_PID TREE_VERSION TREE_DISCOVER
  TREE_TTY=$($TMUX_CMD list-panes -aF '#{session_name}:#{window_index}.#{pane_index}|#{pane_tty}' 2>/dev/null | sed 's|/dev/||')
  TREE_CLAUDE_PID=$(ps -eo pid=,tty=,args= 2>/dev/null | awk '$2!="" && $2!="??" && /claude/ {print $2"|"$1}')
  local _claude_bin
  _claude_bin=$(command -v claude 2>/dev/null)
  [ -z "$_claude_bin" ] && [ -x "$HOME/.local/bin/claude" ] && _claude_bin="$HOME/.local/bin/claude"
  if [ -n "$_claude_bin" ]; then
    TREE_VERSION=$("$_claude_bin" --version 2>/dev/null | head -1)
    TREE_VERSION="${TREE_VERSION%% (*}"
  fi
  local filter_session="$1"
  # Fix D: hoist agents.discover OUT of the per-session loop. When the caller
  # filters to a single session, call discover with that filter (1 call).
  # When no filter (full server), call once globally (was N per-session calls).
  if command -v hiveMind >/dev/null 2>&1; then
    if [ -n "$filter_session" ]; then
      TREE_DISCOVER=$(hiveMind protected.agents.discover "$filter_session" 2>/dev/null)
    else
      TREE_DISCOVER=$(hiveMind protected.agents.discover 2>/dev/null)
    fi
  fi

  local session_data
  if [ -n "$filter_session" ]; then
    session_data=$($TMUX_CMD list-sessions -f "#{==:#{session_name},$filter_session}" \
      -F "#{session_name}|#{session_attached}|#{session_created}" 2>/dev/null)
  else
    session_data=$($TMUX_CMD list-sessions \
      -F "#{session_name}|#{session_attached}|#{session_created}" 2>/dev/null)
  fi

  if [ -z "$session_data" ]; then
    echo "No tmux sessions."
    return 0
  fi

  local session_count
  session_count=$(echo "$session_data" | wc -l | tr -d ' ')
  local oosh_dir="${OOSH_DIR:-$(dirname "$0")}"

  echo "tmux sessions"
  echo "│"

  local session_i=0

  echo "$session_data" | while IFS='|' read -r sess_name sess_attached sess_created; do
    session_i=$((session_i + 1))
    local is_last=0
    [ "$session_i" -eq "$session_count" ] && is_last=1

    local sess_date
    if date -r "$sess_created" "+%b %-d" >/dev/null 2>&1; then
      sess_date=$(date -r "$sess_created" "+%b %-d")
    else
      sess_date=$(date -d "@$sess_created" "+%b %-d" 2>/dev/null || echo "unknown")
    fi

    local sess_connector="├──"
    local pane_prefix="│   "
    if [ "$is_last" -eq 1 ]; then
      sess_connector="└──"
      pane_prefix="    "
    fi

    local sess_label="$sess_name"
    local attach_tag=""
    if [ "$sess_attached" -gt 0 ] 2>/dev/null; then
      attach_tag="${BOLD_GREEN}attached${NORMAL}, "
    fi
    sess_label="${BOLD_CYAN}${sess_name}${NORMAL} ${GRAY}(${attach_tag}${GRAY}${sess_date})${NORMAL}"

    echo -e "${GRAY}${sess_connector}${NORMAL} ${sess_label}"

    local pane_data
    pane_data=$($TMUX_CMD list-panes -t "$sess_name" -s \
      -F "#{window_index}|#{pane_index}|#{pane_title}|#{pane_current_command}" 2>/dev/null)

    local pane_count
    pane_count=$(echo "$pane_data" | wc -l | tr -d ' ')
    local pane_i=0

    # Fix D: per-session discover_data sliced from the GLOBAL TREE_DISCOVER cache
    # (one hiveMind call covers all sessions, not N).
    local discover_data=""
    [ -n "$TREE_DISCOVER" ] && discover_data=$(awk -F'|' -v s="^${sess_name}:" '$1 ~ s' <<< "$TREE_DISCOVER")

    echo "$pane_data" | while IFS='|' read -r win_idx pane_idx pane_title pane_cmd; do
      pane_i=$((pane_i + 1))

      local address="${win_idx}.${pane_idx}"
      local title="${pane_title:--}"

      local pane_connector="├──"
      [ "$pane_i" -eq "$pane_count" ] && pane_connector="└──"

      # FAST PATH (A+B+C): pane→tty + tty→claude-pid + cached version.
      # Bash 3.2 compat: string-grep lookup instead of assoc array.
      local display_cmd="$pane_cmd"
      if [[ "$pane_cmd" == "bash" || "$pane_cmd" == "zsh" ]]; then
        local pane_target_ver="${sess_name}:${win_idx}.${pane_idx}"
        local _tty_ver _cpid_ver
        _tty_ver=$(echo "$TREE_TTY" | grep "^${pane_target_ver}|" | head -1 | cut -d'|' -f2)
        if [ -n "$_tty_ver" ]; then
          _cpid_ver=$(echo "$TREE_CLAUDE_PID" | grep "^${_tty_ver}|" | head -1 | cut -d'|' -f2)
          if [ -n "$_cpid_ver" ] && [ -n "$TREE_VERSION" ]; then
            display_cmd="$TREE_VERSION"
          fi
        fi
      fi
      printf "${GRAY}%s%s${NORMAL} ${GRAY}%-5s${NORMAL} ${BOLD_WHITE}%-24s${NORMAL} ${GREEN}[%s]${NORMAL}\n" "$pane_prefix" "$pane_connector" "$address" "$title" "$display_cmd"

      # Third level: only for panes running Claude Code (not plain shells).
      # Read from shared discover_data (DRY — same source as hiveMind team.status).
      local pane_target="${sess_name}:${win_idx}.${pane_idx}"
      local agent_name="" agent_sid="" is_claude="no"
      local row
      row=$(echo "$discover_data" | awk -F'|' -v t="$pane_target" '$1==t{print; exit}')
      if [ -n "$row" ]; then
        IFS='|' read -r _ agent_name _ agent_sid _ is_claude <<< "$row"
      fi

      if [ "$is_claude" = "yes" ] && { [ -n "$agent_name" ] || [ -n "$agent_sid" ]; }; then
        # Display name: prefer pane title (has @host from Option C naming).
        # Only fall back to agent_name@model when title is missing or generic.
        local sub_name="$title"
        if [ -z "$sub_name" ] || [ "$sub_name" = "-" ] || [ "$sub_name" = "bash" ]; then
          sub_name="${agent_name:-(unnamed)}"
        fi

        local sub_prefix="│     "
        [ "$pane_i" -eq "$pane_count" ] && sub_prefix="      "
        if [ -n "$agent_sid" ]; then
          printf "%s%s└ %-30s [%s]\n" "$pane_prefix" "$sub_prefix" "$sub_name" "$agent_sid"
        else
          printf "%s%s└ %s\n" "$pane_prefix" "$sub_prefix" "$sub_name"
        fi
      fi
    done

    echo -e "${GRAY}${pane_prefix}${NORMAL}"
  done
}
otmux.tree.detailed.completion.session() { otmux.parameter.completion.session "$@"; }

otmux.status() # # fast session list — Tier 1 (no per-pane subprocess calls). Use 'otmux tree' for full view.
{
  # Pure tmux queries: list-sessions for names/attach/pane-count + count panes via list-panes -aF.
  # Target: <0.5s server-wide. Was 46s (called otmux.tree → per-pane claudeCode bootstrap).
  $TMUX_CMD list-sessions >/dev/null 2>&1 || {
    echo -e "${BOLD_YELLOW}No tmux server running.${NORMAL} Use ${CYAN}'otmux new'${NORMAL} to start."
    return 1
  }
  # Build pane-count-per-session map in ONE tmux call.
  # Bash 3.2 compat (task #29): declare -A is bash 4+. Use sort|uniq -c on the
  # session-name column instead of incrementing an assoc array per row.
  local SESS_PANES_DATA
  SESS_PANES_DATA=$($TMUX_CMD list-panes -aF '#{session_name}' 2>/dev/null | sort | uniq -c | awk '{printf "%s|%s\n", $2, $1}')
  printf "${BOLD_WHITE}%-32s %-10s %-6s %s${NORMAL}\n" "SESSION" "STATUS" "PANES" "WINDOWS"
  while IFS='|' read -r name attached windows; do
    local status_label sess_panes
    if [ "$attached" -gt 0 ] 2>/dev/null; then
      status_label="${BOLD_GREEN}attached${NORMAL}"
    else
      status_label="${GRAY}detached${NORMAL}"
    fi
    sess_panes=$(echo "$SESS_PANES_DATA" | grep "^${name}|" | head -1 | cut -d'|' -f2)
    [ -z "$sess_panes" ] && sess_panes=0
    printf "${BOLD_CYAN}%-32s${NORMAL} %b %-6s %s\n" "$name" "$status_label" "$sess_panes" "$windows"
  done < <($TMUX_CMD list-sessions -F '#{session_name}|#{session_attached}|#{session_windows}' 2>/dev/null)
  echo ""
  echo -e "${GRAY}Tip:${NORMAL} ${CYAN}otmux tree${NORMAL} for per-pane view, ${CYAN}otmux tree.detailed${NORMAL} for agent UUIDs."
}

# ─────────────────────────────────────────────────────────────────────────────
# OBJECT.VERB ALIASES (tmux.object.verb style)
# ─────────────────────────────────────────────────────────────────────────────
# These provide an alternative naming convention: otmux.object.verb
# instead of otmux.verbObject, for those who prefer noun-first naming.

# --- SESSION ALIASES ---

otmux.session.kill() # <target> # kill session (alias for otmux.kill); target=session name
{
  otmux.kill "$@"
}
otmux.session.kill.completion.target() { private.complete.sessions; }

otmux.session.kill.all() # # kill all sessions except current
{
  private.otmux.killAll "$@"
}

otmux.session.details() # <session> # show all windows and panes with titles, commands, and dimensions
{
  local session="$1"

  if [ -z "$session" ]; then
    error.log "usage: otmux session.details <session-name>"
    return 1
  fi

  if ! $TMUX_CMD has-session -t "$session" 2>/dev/null; then
    error.log "session not found: $session"
    return 1
  fi

  echo -e "${BOLD_CYAN}Session: ${BOLD_WHITE}$session${NORMAL}"
  echo -e "${GRAY}═══════════════════════════════════════════════════════════════════${NORMAL}"

  local current_window=""

  $TMUX_CMD list-panes -t "$session" -s \
    -F "#{window_index}|#{window_name}|#{pane_index}|#{pane_title}|#{pane_current_command}|#{pane_width}|#{pane_height}|#{pane_active}" 2>/dev/null | \
    while IFS='|' read -r win_idx win_name pane_idx pane_title pane_cmd pane_w pane_h pane_active; do
      if [ "$win_idx" != "$current_window" ]; then
        [ -n "$current_window" ] && echo ""
        echo -e "  ${BOLD_CYAN}Window $win_idx: ${BOLD_WHITE}$win_name${NORMAL}"
        echo -e "  ${GRAY}───────────────────────────────────────────────────────────────${NORMAL}"
        printf "  ${GRAY}%-30s %-16s %-12s %-10s %s${NORMAL}\n" "Address" "Title" "Command" "Size" ""
        current_window="$win_idx"
      fi

      local address="${session}:${win_idx}.${pane_idx}"
      local size="${pane_w}x${pane_h}"
      local active_mark=""
      [ "$pane_active" = "1" ] && active_mark="(active)"

      printf "  %-30s %-16s %-12s %-10s %s\n" "$address" "${pane_title:--}" "$pane_cmd" "$size" "$active_mark"
    done

  echo ""
}
otmux.session.details.completion.session() { otmux.parameter.completion.session "$@"; }

# --- WINDOW ALIASES ---

otmux.window.new() # <?name> <?command> # create window; name=window title
{
  private.otmux.newWindow "$@"
}

otmux.window.get() # <?format:#> # get current window target; format=# for index, @ for id, name for name
{
  local format="${1:-#}"
  case "$format" in
    \#)   $TMUX_CMD display-message -p "#{window_index}" ;;
    @)    $TMUX_CMD display-message -p "#{window_id}" ;;
    name) $TMUX_CMD display-message -p "#{window_name}" ;;
    *)    $TMUX_CMD display-message -p "$format" ;;
  esac
}

otmux.window.get.target() # # get current window in target format (session:window)
{
  $TMUX_CMD display-message -p "#{session_name}:#{window_index}"
}

otmux.window.select() # <target> # select window; target=index (0,1,2) or name
{
  private.otmux.selectWindow "$@"
}
otmux.window.select.completion.target() { private.complete.windows; }

otmux.window.next() # # go to next window
{
  private.otmux.nextWindow "$@"
}

otmux.window.prev() # # go to previous window
{
  private.otmux.prevWindow "$@"
}

otmux.window.last() # # go to last active window
{
  private.otmux.lastWindow "$@"
}

otmux.window.kill() # <?target> # kill window; target=[session:]window index/name
{
  private.otmux.killWindow "$@"
}
otmux.window.kill.completion.target() { private.complete.windows; }

otmux.window.rename() # <target|name> <?name> # rename window; 2 args = target + name, 1 arg = name only
{
  private.otmux.renameWindow "$@"
}

otmux.window.move() # <target> # move window; target=destination [session:]index
{
  private.otmux.moveWindow "$@"
}
otmux.window.move.completion.target() { private.complete.windows; }

otmux.window.swap() # <target> # swap with window; target=[session:]window to swap with
{
  private.otmux.swapWindow "$@"
}
otmux.window.swap.completion.target() { private.complete.windows; }

otmux.window.link() # <src> <dst> # link window; src=source window, dst=target session
{
  private.otmux.linkWindow "$@"
}
otmux.window.link.completion.src() { private.complete.windows; }
otmux.window.link.completion.dst() { private.complete.sessions; }

otmux.window.unlink() # <?target> # unlink window; target=window to unlink
{
  private.otmux.unlinkWindow "$@"
}
otmux.window.unlink.completion.target() { private.complete.windows; }

otmux.window.find() # <pattern> # find window by pattern; pattern=search string
{
  private.otmux.findWindow "$@"
}

otmux.window.rotate() # # rotate panes within window
{
  private.otmux.rotateWindow "$@"
}

otmux.window.respawn() # <?command> # respawn window; command=shell command to run
{
  private.otmux.respawnWindow "$@"
}

# --- PANE METHODS ---

otmux.pane.list() # <?session> # list panes with addresses and titles; session=filter to session
{
  if [ -n "$1" ]; then
    $TMUX_CMD list-panes -t "$1" -s \
      -F "#{session_name}:#{window_index}.#{pane_index}  #{pane_title}  #{pane_current_command}" 2>/dev/null
  else
    otmux.panes "$@"
  fi
}
otmux.pane.list.completion.session() { otmux.parameter.completion.session "$@"; }


otmux.pane.get() # <?target> <?format:%> # get pane info; 2 args = target + format, 1 arg = format, 0 = pane_id
{
  local target_args=(-t "${TMUX_PANE:-}") format="%"
  if [ $# -ge 2 ]; then
    target_args=(-t "$1"); format="$2"
  elif [ $# -eq 1 ]; then
    format="$1"
  fi
  case "$format" in
    %)  $TMUX_CMD display-message "${target_args[@]}" -p "#{pane_id}" ;;
    \#) $TMUX_CMD display-message "${target_args[@]}" -p "#{pane_index}" ;;
    *)  $TMUX_CMD display-message "${target_args[@]}" -p "$format" ;;
  esac
}

otmux.pane.get.target() # # get executing pane in target format (session:window.pane)
{
  $TMUX_CMD display-message -t "${TMUX_PANE:-}" -p "#{session_name}:#{window_index}.#{pane_index}"
}

otmux.current() # # alias to otmux.pane.get.target showing the current pane name
{
  otmux.pane.get.target
}

otmux.pane.select() # <target> # select pane; target=U/D/L/R direction or index (0,1,2)
{
  private.otmux.selectPane "$@"
}


otmux.pane.last() # # select last active pane
{
  private.otmux.lastPane "$@"
}

otmux.pane.kill() # <?target> # kill pane; target=[session:][window.]pane index
{
  private.otmux.killPane "$@"
}


otmux.pane.break() # # break pane into new window
{
  private.otmux.breakPane "$@"
}

otmux.pane.join() # <window> # join pane to window; window=destination [session:]window
{
  private.otmux.joinPane "$@"
  # B5.1 — pane changed window: notify Controller for both source and destination sessions
  # The destination is in $1; current pane's session is the source. Refresh both.
  local destSess
  destSess=$(private.otmux.sessionFromPane "${1:-}")
  local curSess
  curSess=$($TMUX_CMD display-message -p '#{session_name}' 2>/dev/null)
  [ -n "$destSess" ] && private.otmux.notifyHiveMind.shifted "$destSess"
  [ -n "$curSess" ] && [ "$curSess" != "$destSess" ] && private.otmux.notifyHiveMind.shifted "$curSess"
}

otmux.pane.swap() # <sourcePane> <targetPane> # swap two panes; sourcePane/targetPane=session:window.pane
{
  local sourcePane=$(private.resolve.target "$1")
  local targetPane=$(private.resolve.target "$2")
  if [ -z "$sourcePane" ] || [ -z "$targetPane" ]; then
    error.log "usage: otmux pane.swap <sourcePane> <targetPane>"
    return 1
  fi
  $TMUX_CMD swap-pane -s "$sourcePane" -t "$targetPane"
  # B5.1 — pane content swapped → roles must swap too (agent moves with pane).
  # Per architecture diagram: protected.panes.swapped <session> <paneA> <paneB>.
  # More precise than panes.shifted — tells Controller to swap two specific keys
  # without re-running full discovery.
  local sess
  sess=$(private.otmux.sessionFromPane "$sourcePane")
  command -v hiveMind >/dev/null 2>&1 && hiveMind protected.panes.swapped "$sess" "$sourcePane" "$targetPane" 2>/dev/null
}

otmux.pane.move() # <window> # move pane; window=destination window
{
  private.otmux.movePane "$@"
  # B5.1 — pane moved between windows: notify both old and new sessions
  local destSess
  destSess=$(private.otmux.sessionFromPane "${1:-}")
  local curSess
  curSess=$($TMUX_CMD display-message -p '#{session_name}' 2>/dev/null)
  [ -n "$destSess" ] && private.otmux.notifyHiveMind.shifted "$destSess"
  [ -n "$curSess" ] && [ "$curSess" != "$destSess" ] && private.otmux.notifyHiveMind.shifted "$curSess"
}

otmux.pane.resize() # <direction> <?amount> # resize pane; direction=U/D/L/R, amount=cells (default 5)
{
  private.otmux.resizePane "$@"
}

otmux.pane.size.set() # <pane> <?width> <?height> # set absolute pane size (one or both dims; omit to leave unchanged)
{
  # Absolute pane sizing — wrapper for `tmux resize-pane -x W -y H`. Useful for
  # locking shell panes to a minimum height in vertically-stacked layouts where
  # tmux's auto-resize collapses small panes to 1 row when the window shrinks.
  # Caller use case (ud-po): `otmux pane.size.set upDownTeam:0.3 - 10` to give
  # an expert-shell at least 10 rows.
  local pane="$1"
  local w="${2:-}"
  local h="${3:-}"
  if [ -z "$pane" ]; then
    error.log "Usage: otmux pane.size.set <pane> <?width> <?height> (use '-' to skip a dim)"
    return 1
  fi
  local args=()
  [ -n "$w" ] && [ "$w" != "-" ] && args+=(-x "$w")
  [ -n "$h" ] && [ "$h" != "-" ] && args+=(-y "$h")
  if [ ${#args[@]} -eq 0 ]; then
    error.log "pane.size.set: provide at least one of <width>, <height>"
    return 1
  fi
  $TMUX_CMD resize-pane -t "$pane" "${args[@]}" 2>/dev/null
}
otmux.pane.size.set.completion.pane() { private.complete.panes; }

otmux.pane.size() # <pane> # display pane size as 'WxH'
{
  local pane="$1"
  [ -z "$pane" ] && { error.log "Usage: otmux pane.size <pane>"; return 1; }
  $TMUX_CMD display-message -t "$pane" -p '#{pane_width}x#{pane_height}' 2>/dev/null
}
otmux.pane.size.completion.pane() { private.complete.panes; }


otmux.pane.respawn() # <?command> # respawn pane; command=shell command to run
{
  private.otmux.respawnPane "$@"
}

otmux.pane.pipe() # <?command> # pipe pane output; command=receive piped output
{
  private.otmux.pipePane "$@"
}

otmux.pane.display() # # display pane numbers temporarily
{
  private.otmux.displayPanes "$@"
}

otmux.pane.capture() # <target> <?lines:20> # capture last N lines of visible pane output; target=session:window.pane
{
  local target=$(private.resolve.target "$1")
  local lines="${2:-20}"
  if [ -z "$target" ]; then
    error.log "usage: otmux pane.capture <target> <?lines>"
    return 1
  fi
  $TMUX_CMD capture-pane -t "$target" -p -S "-${lines}" 2>/dev/null | tail -"$lines"
}


otmux.pane.capture.visible() # <target> # capture only the visible pane area (no scrollback)
{
  local target=$(private.resolve.target "$1")
  if [ -z "$target" ]; then
    error.log "usage: otmux pane.capture.visible <target>"
    return 1
  fi
  $TMUX_CMD capture-pane -t "$target" -p 2>/dev/null
}


otmux.pane.history() # <target> <?lines:100> # capture scrollback history including content scrolled off screen
{
  local target=$(private.resolve.target "$1")
  local lines="${2:-100}"
  if [ -z "$target" ]; then
    error.log "usage: otmux pane.history <target> <?lines>"
    return 1
  fi
  $TMUX_CMD capture-pane -t "$target" -p -S "-${lines}" 2>/dev/null
}


otmux.pane.history.clear() # # clear pane scrollback history
{
  private.otmux.clearHistory "$@"
}

otmux.pane.title() # <target> <title> # set pane title; target=session:window.pane
{
  local target=$(private.resolve.target "$1")
  local title="$2"
  if [ -z "$title" ]; then
    error.log "usage: otmux pane.title <target> <title>"
    return 1
  fi
  $TMUX_CMD select-pane -t "$target" -T "$title"
}


otmux.pane.send() # <target> <text> # send text + Enter to pane; target=session:window.pane
{
  private.otmux.sendEnter "$@"
}


otmux.pane.lock() # <target> <title> # lock pane title; idempotent — auto-unlocks first if already locked
{
  local target=$(private.resolve.target "$1")
  local title="$2"
  if [ -z "$target" ] || [ -z "$title" ]; then
    error.log "usage: otmux pane.lock <target> <title>"
    return 1
  fi

  # B3.1 — auto-unlock first so pane.lock is idempotent and safe to retitle.
  # Clears any prior hook + background enforcer + allow-rename state before
  # we install the new lock. Silent: no info.log from the unlock step.
  otmux.pane.unlock "$target" >/dev/null 2>&1

  # Set the title
  $TMUX_CMD select-pane -t "$target" -T "$title"
  # Block escape-sequence-based renaming
  $TMUX_CMD set-option -p -t "$target" allow-rename off
  # Pane-level hooks: tmux 3.2+ supports set-hook -p
  if $TMUX_CMD set-hook -p -t "$target" pane-title-changed "select-pane -T \"$title\"" 2>/dev/null; then
    info.log "Locked pane $target title to: $title (with hook)"
  else
    # tmux <3.2: no pane hooks available. Start background enforcer.
    # Claude Code plan mode uses select-pane -T directly, bypassing allow-rename.
    # Background loop re-enforces every 5s until pane.unlock kills it.
    local pidFile="${TMPDIR:-/tmp}/otmux.pane.lock.$(echo "$target" | tr ':.' '_').pid"
    # Kill old enforcer if running
    [ -f "$pidFile" ] && kill "$(cat "$pidFile")" 2>/dev/null
    (
      echo $$ > "$pidFile"
      while true; do
        sleep 5
        local current
        current=$($TMUX_CMD display-message -t "$target" -p '#{pane_title}' 2>/dev/null) || break
        [ "$current" != "$title" ] && $TMUX_CMD select-pane -t "$target" -T "$title" 2>/dev/null
      done
      rm -f "$pidFile"
    ) &
    disown
    info.log "Locked pane $target title to: $title (background enforcer)"
  fi
}


otmux.pane.unlock() # <target> # unlock pane title; allows normal title changes
{
  local target=$(private.resolve.target "$1")
  if [ -z "$target" ]; then
    error.log "usage: otmux pane.unlock <target>"
    return 1
  fi
  # Remove pane-level hook (tmux 3.2+ only, safe to fail on older)
  $TMUX_CMD set-hook -up -t "$target" pane-title-changed 2>/dev/null
  # Kill background enforcer if running (tmux <3.2)
  local pidFile="${TMPDIR:-/tmp}/otmux.pane.lock.$(echo "$target" | tr ':.' '_').pid"
  [ -f "$pidFile" ] && kill "$(cat "$pidFile")" 2>/dev/null && rm -f "$pidFile"
  # Re-enable escape-sequence renaming
  $TMUX_CMD set-option -up -t "$target" allow-rename 2>/dev/null
  info.log "Unlocked pane $target title"
}


# --- LAYOUT ALIASES ---

otmux.layout.set() # <layout> # set layout; layout=even-horizontal/vertical/main-horizontal/vertical/tiled
{
  otmux.layout "$@"
}


otmux.layout.even.h() # # set even-horizontal layout (equal width columns)
{
  private.otmux.evenH "$@"
}

otmux.layout.even.v() # # set even-vertical layout (equal height rows)
{
  private.otmux.evenV "$@"
}

otmux.layout.main.h() # # set main-horizontal layout (large pane on top)
{
  private.otmux.mainH "$@"
}

otmux.layout.main.v() # # set main-vertical layout (large pane on left)
{
  private.otmux.mainV "$@"
}

otmux.layout.tiled() # # set tiled layout (grid arrangement)
{
  otmux.tiled "$@"
}

otmux.layout.next() # # cycle to next layout
{
  private.otmux.nextLayout "$@"
}

otmux.layout.prev() # # cycle to previous layout
{
  private.otmux.prevLayout "$@"
}

# --- BUFFER ALIASES ---

otmux.buffer.list() # # list all buffers
{
  otmux.buffers "$@"
}

otmux.buffer.show() # <?buffer> # show buffer contents; buffer=buffer name
{
  private.otmux.showBuffer "$@"
}
otmux.buffer.show.completion.buffer() { private.complete.buffers; }

otmux.buffer.set() # <data> # set buffer data; data=text to store
{
  private.otmux.setBuffer "$@"
}

otmux.buffer.load() # <file> # load buffer from file; file=path to read
{
  private.otmux.loadBuffer "$@"
}

otmux.buffer.save() # <file> # save buffer to file; file=path to write
{
  private.otmux.saveBuffer "$@"
}
otmux.buffer.load.completion.file() { compgen -f "$1"; }
otmux.buffer.save.completion.file() { compgen -f "$1"; }

otmux.buffer.delete() # <?buffer> # delete buffer; buffer=buffer name
{
  private.otmux.deleteBuffer "$@"
}
otmux.buffer.delete.completion.buffer() { private.complete.buffers; }

otmux.buffer.paste() # # paste buffer contents into pane
{
  private.otmux.pasteBuffer "$@"
}

otmux.buffer.choose() # # interactively choose buffer
{
  private.otmux.chooseBuffer "$@"
}

# --- CLIENT ALIASES ---

otmux.client.list() # # list connected clients with size, flags, session, idle (B6)
{
  # B6: structured format with size + flags helps diagnose layout-crush from
  # stale read-only/small clients (e.g. tronMonitor's 54x26 screen attaches).
  if ! $TMUX_CMD list-clients >/dev/null 2>&1; then
    echo "no tmux server"
    return 1
  fi
  printf "%-18s %-22s %-9s %-44s %s\n" "TTY" "SESSION" "SIZE" "FLAGS" "IDLE"
  $TMUX_CMD list-clients -F '#{client_tty}|#{client_session}|#{client_width}x#{client_height}|#{client_flags}|#{client_activity}' 2>/dev/null \
    | while IFS='|' read -r tty sess size flags activity; do
        local idle="-"
        if [ -n "$activity" ] && [ "$activity" -gt 0 ] 2>/dev/null; then
          local now
          now=$(date +%s)
          local secs=$((now - activity))
          if [ "$secs" -ge 3600 ]; then
            idle="$((secs / 3600))h $((secs % 3600 / 60))m"
          elif [ "$secs" -ge 60 ]; then
            idle="$((secs / 60))m"
          else
            idle="${secs}s"
          fi
        fi
        printf "%-18s %-22s %-9s %-44s %s\n" "$tty" "$sess" "$size" "$flags" "$idle"
      done
}

otmux.client.detach() # <?client> # detach client by tty; refreshes remaining clients to restore layout (B6)
{
  # B6: after detach, refresh-client -S forces all remaining clients to
  # re-sync sizes — crucial after killing a small read-only client that
  # was forcing window-size=smallest behavior.
  local target="$1"
  if [ -n "$target" ]; then
    if ! $TMUX_CMD detach-client -t "$target" 2>&1; then
      error.log "client.detach: failed to detach '$target'"
      return 1
    fi
    info.log "Detached client: $target"
  else
    $TMUX_CMD detach-client 2>&1
  fi
  # Trigger size re-sync on remaining clients (only if server still up)
  if $TMUX_CMD list-clients >/dev/null 2>&1; then
    $TMUX_CMD refresh-client -S 2>/dev/null
  fi
  return 0
}
otmux.client.detach.completion.client() { private.complete.clients; }

otmux.client.cleanup() # <?filter:read-only> # detach stale clients matching <filter>; refresh layout
{
  # B6: bulk-detach clients whose flags include <filter>. Default 'read-only'
  # targets monitoring clients (e.g. tronMonitor's screen→tmux attach -r) that
  # crush layout when their terminal is small.
  # Other useful filters: 'ignore-size' (won't grow but counted), or any flag.
  local filter="${1:-read-only}"
  if ! $TMUX_CMD list-clients >/dev/null 2>&1; then
    echo "no tmux server"
    return 1
  fi
  local detached=0 skipped=0
  while IFS='|' read -r tty flags; do
    [ -z "$tty" ] && continue
    if echo "$flags" | grep -q "$filter"; then
      info.log "client.cleanup: detaching $tty (matches '$filter') — flags=$flags"
      $TMUX_CMD detach-client -t "$tty" 2>/dev/null && detached=$((detached + 1))
    else
      skipped=$((skipped + 1))
    fi
  done < <($TMUX_CMD list-clients -F '#{client_tty}|#{client_flags}' 2>/dev/null)
  # Force layout re-sync on whatever survived (crucial — this restores widths)
  if $TMUX_CMD list-clients >/dev/null 2>&1; then
    $TMUX_CMD refresh-client -S 2>/dev/null
  fi
  success.log "client.cleanup: detached $detached, kept $skipped (filter='$filter')"
}
otmux.client.cleanup.completion.filter() {
  echo "read-only"
  echo "ignore-size"
  echo "attached"
  echo "focused"
}

otmux.client.cleanup.stale() # <?idleMin:30> <?maxSize:0> <?filter:read-only> # surgical detach: idle >= idleMin AND (maxSize>0 → both dims <= maxSize) AND flags match filter
{
  # D5: stale-client cleanup with idle + size gates. Bulk client.cleanup
  # detaches ALL read-only clients — that kills legitimate live monitoring.
  # This filters to ZOMBIES (idle a long time) and optionally tiny (1x3
  # phantoms from dead screen attaches).
  #
  # Use cases:
  #   tronMonitor.sync   — idleMin=60  maxSize=10  (conservative, only 1h+ tinies)
  #   scrumMaster.cycle  — idleMin=30  maxSize=0   (broad, 30min+ any size)
  local idleMin="${1:-30}"
  local maxSize="${2:-0}"
  local filter="${3:-read-only}"

  if ! $TMUX_CMD list-clients >/dev/null 2>&1; then
    return 0
  fi

  local now
  now=$(date +%s)
  local idleSecs=$((idleMin * 60))
  local detached=0 skipped=0

  while IFS='|' read -r tty flags activity width height; do
    [ -z "$tty" ] && continue
    # Flag filter
    echo "$flags" | grep -q "$filter" || { skipped=$((skipped + 1)); continue; }
    # Idle filter
    local idle=0
    [ -n "$activity" ] && [ "$activity" -gt 0 ] 2>/dev/null && \
      idle=$((now - activity))
    if [ "$idle" -lt "$idleSecs" ]; then
      skipped=$((skipped + 1))
      continue
    fi
    # Size filter (only when maxSize > 0)
    if [ "$maxSize" -gt 0 ] 2>/dev/null; then
      if [ "${width:-999}" -gt "$maxSize" ] 2>/dev/null || \
         [ "${height:-999}" -gt "$maxSize" ] 2>/dev/null; then
        skipped=$((skipped + 1))
        continue
      fi
    fi
    info.log "client.cleanup.stale: detaching $tty (idle=${idle}s size=${width}x${height} flags=$flags)"
    $TMUX_CMD detach-client -t "$tty" 2>/dev/null && detached=$((detached + 1))
  done < <($TMUX_CMD list-clients -F '#{client_tty}|#{client_flags}|#{client_activity}|#{client_width}|#{client_height}' 2>/dev/null)

  # Re-sync survivors so window-size=largest reflects the post-cleanup truth
  if [ "$detached" -gt 0 ] && $TMUX_CMD list-clients >/dev/null 2>&1; then
    $TMUX_CMD refresh-client -S 2>/dev/null
  fi
  if [ "$detached" -gt 0 ]; then
    success.log "client.cleanup.stale: detached $detached, kept $skipped (idle>=${idleMin}min, maxSize=${maxSize}, filter='$filter')"
  fi
  return 0
}
otmux.client.cleanup.stale.completion.filter() {
  echo "read-only"
  echo "ignore-size"
  echo "attached"
}

otmux.client.suspend() # # suspend current client (Ctrl+Z)
{
  private.otmux.suspendClient "$@"
}

otmux.client.refresh() # # refresh client display
{
  private.otmux.refreshClient "$@"
}

otmux.client.choose() # # interactively choose client
{
  private.otmux.chooseClient "$@"
}

otmux.client.choose.tree() # # interactively choose session/window tree
{
  private.otmux.chooseTree "$@"
}

otmux.client.lock() # # lock current client
{
  private.otmux.lockClient "$@"
}

# --- SERVER ALIASES ---

otmux.server.start() # # start tmux server
{
  private.otmux.startServer "$@"
}

otmux.server.kill() # # kill tmux server and all sessions
{
  private.otmux.killServer "$@"
}

otmux.server.lock() # # lock all clients
{
  private.otmux.lockServer "$@"
}

otmux.server.info() # # show server information
{
  otmux.info "$@"
}

# --- CONFIG ALIASES ---

otmux.config.source() # <?file> # source config file; file=path (default ~/.tmux.conf)
{
  otmux.source "$@"
}

otmux.config.set() # <option> <value> # set option; option=tmux option name
{
  otmux.set "$@"
}

otmux.config.set.global() # <option> <value> # set global option
{
  private.otmux.setGlobal "$@"
}

otmux.config.set.window() # <option> <value> # set window option
{
  private.otmux.setWindow "$@"
}

otmux.config.show() # <?option> # show options; option=specific option or all
{
  otmux.show "$@"
}

otmux.config.show.global() # <?option> # show global options
{
  private.otmux.showGlobal "$@"
}

otmux.config.show.window() # <?option> # show window options
{
  private.otmux.showWindow "$@"
}

otmux.config.show.hooks() # # show configured hooks
{
  private.otmux.showHooks "$@"
}

otmux.config.set.hook() # <hook> <command> # set hook; hook=event name, command=action
{
  private.otmux.setHook "$@"
}

# --- KEY ALIASES ---

otmux.key.bind() # <key> <command> # bind key; key=key sequence, command=tmux command
{
  otmux.bind "$@"
}

otmux.key.unbind() # <key> # unbind key; key=key sequence to remove
{
  otmux.unbind "$@"
}

otmux.key.list() # # list all key bindings
{
  otmux.keys "$@"
}

# --- ENV ALIASES ---

otmux.env.set() # <name> <value> # set environment variable; name=var name
{
  private.otmux.setEnv "$@"
}

otmux.env.show() # <?name> # show environment; name=specific var or all
{
  private.otmux.showEnv "$@"
}

# --- SEND ALIASES ---

otmux.send.enter() # <target> <text> # send text + Enter; target=pane, text=command to execute
{
  private.otmux.sendEnter "$@"
}

otmux.send.tui() # <target> <key1> [<key2>...] # send keys with inter-key delays for TUI apps (Claude Code)
{
  private.otmux.sendKeys "$@"
}


otmux.copy.clock() # # show clock in current pane
{
  otmux.clock "$@"
}

# ─────────────────────────────────────────────────────────────────────────────
# USAGE
# ─────────────────────────────────────────────────────────────────────────────

otmux.tronMonitor.setup() # <?session:TRONinterface> <?pane:0.3> # create TRON-Monitor pane and start screen
{
  local session="${1:-TRONinterface}"
  local paneIdx="${2:-0.3}"
  local target="${session}:${paneIdx}"

  # Ensure session exists
  if ! otmux.has "$session" 2>/dev/null; then
    otmux.new "$session"
  fi

  # Ensure pane exists by splitting until we reach the requested index.
  # B1.3 BOUNDARY-2: pure View — no cross-layer call to Controller helpers.
  local current needed
  needed=$((${paneIdx##*.} + 1))
  current=$($TMUX_CMD list-panes -t "${session}:${paneIdx%%.*}" 2>/dev/null | wc -l | tr -d ' ')
  while [ "${current:-0}" -lt "$needed" ]; do
    $TMUX_CMD split-window -d -t "${session}:${paneIdx%%.*}" 2>/dev/null
    $TMUX_CMD select-layout -t "${session}:${paneIdx%%.*}" tiled 2>/dev/null
    current=$($TMUX_CMD list-panes -t "${session}:${paneIdx%%.*}" 2>/dev/null | wc -l | tr -d ' ')
  done

  # Run tronMonitor setup on the target pane
  "$OOSH_DIR/tronMonitor" setup "$target"

  success.log "TRON-Monitor ready at $target"
}

otmux.usage()
{
  local this=${0##*/}
  echo "You started"
  echo "$0

  otmux - tmux wrapper for oosh
  Makes tmux commands easy to remember!

  Two naming styles available:
    verbObject:    otmux killWindow dev:0
    object.verb:   otmux window.kill dev:0

  Usage:
  $this command     Description
  ─────────────────────────────────────────────────────
  SESSIONS:
      new <name>      create new session
      attach <sess>   attach to session (a)
      detach          detach from session (d)
      sessions        list sessions (ls)
      kill <sess>     kill a session
      rename <name>   rename session
      switch <sess>   switch to session
      last/next/prev  navigate sessions

  WINDOWS:
      newWindow <n>   create window (nw)
      windows         list windows (lsw)
      selectWindow    select window (sw)
      killWindow      kill window (kw)
      renameWindow    rename window (rw)
      nextWindow      next window
      prevWindow      previous window
      findWindow      find by pattern

  PANES:
      split           split horizontally
      splitH/splitV   split h/v explicitly
      panes           list panes (lsp)
      selectPane      select pane (sp)
      up/down/l/r     navigate panes
      killPane        kill pane (kp)
      zoom            toggle zoom (z)
      resizePane      resize pane (rp)
      breakPane       break to window
      joinPane        join to window
      displayPanes    show pane numbers (dp)

  LAYOUTS:
      layout <type>   set layout
      evenH/evenV     even layouts
      mainH/mainV     main layouts
      tiled           tiled layout
      nextLayout      cycle layouts

  BUFFERS:
      buffers         list buffers (lsb)
      paste           paste buffer
      chooseBuffer    choose buffer
      saveBuffer      save to file
      loadBuffer      load from file

  CONFIG:
      source <file>   source config
      set/setGlobal   set options
      show/showGlobal show options
      bind/unbind     key bindings
      keys            list bindings (lsk)

  UTILITY:
      send <keys>     send keys to pane
      run <cmd>       run shell command
      display <msg>   show message
      copy            enter copy mode
      clock           show clock
      status          show status
      v               show version
  ─────────────────────────────────────────────────────

  OBJECT.VERB STYLE (alternative):
      session.new/list/details/kill/switch/attach
      window.new/list/kill/select/move/swap
      pane.split/list/kill/select/resize/zoom/lock/unlock
      layout.set/evenH/evenV/mainH/mainV/tiled
      buffer.list/show/set/paste/delete
      client.list/detach/suspend/lock
      server.start/kill/info
      config.source/set/show
      key.bind/unbind/list
      send.keys/enter/run/display
  ─────────────────────────────────────────────────────

  Target formats:
    Session:  name or \$id (e.g., 'dev', '\$1')
    Window:   [session:]index/name (e.g., 'dev:0', '2')
    Pane:     [session:][window.]index (e.g., 'dev:0.1')

  Examples:
    $this new dev                 # new session 'dev'
    $this splitH                  # split horizontally
    $this send 'ls -la' Enter     # send command
    $this zoom                    # toggle pane zoom
    $this layout tiled            # tiled layout
    $this attach dev              # attach to 'dev'

    # object.verb style:
    $this session.new dev         # same as above
    $this pane.splitH             # split horizontally
    $this pane.zoom               # toggle zoom
    $this window.kill dev:0       # kill window 0 in dev
  "
}

otmux.start()
{
  #echo "sourcing init"
  source this
  log.init.colors 2>/dev/null

  if [ -z "$1" ]; then
    otmux.status
    return 0
  fi

  this.start "$@"
}

# Only auto-start when otmux is INVOKED as a script — not when sourced.
# Sourcing otmux (e.g. log.live.panes.stop in log:498 sources it for the
# function definitions) used to fire otmux.start unconditionally, which
# triggers `otmux.install` (oo cmd tmux → $SUDO $OOSH_PM tmux) when
# tmux is missing. On Debian's `os platform.test debian_12` flow, that
# blocks waiting for bash-user's sudo password mid-test (test.log T37
# sources otmux, hits the install, hangs on `[sudo] password for
# bash-user:`). Mirrors the `if (this.isSourced)` guard that other oosh
# scripts use (e.g. this:1027).
if [ "${BASH_SOURCE[0]}" = "$0" ]; then
  otmux.start "$@"
fi
