#!/usr/bin/env bash
# Tests for otmux — OOSH tmux wrapper
# Tests: pane.capture, send, pane.lock, pane.title, tree, tree.detailed

level=$1
if [ -z "$level" ]; then
  level=1
fi
echo "starting: ${BASH_SOURCE[@]##*/} <LOG_LEVEL=$level>"

source this
source test.suite

log.level $level

# ============================================================================
# T1: otmux function exists
# ============================================================================
test.case $level "otmux is callable" \
  which otmux

if which otmux &>/dev/null; then
  expect.pass "otmux found on PATH"
else
  expect.fail "otmux should be on PATH"
fi

# ============================================================================
# T2: otmux tree lists sessions
# ============================================================================
test.case $level "otmux tree lists sessions" \
  otmux tree

TREE_OUTPUT=$(otmux tree 2>/dev/null)
if [ -n "$TREE_OUTPUT" ]; then
  expect.pass "otmux tree produced output"
else
  expect.fail "otmux tree should list tmux sessions"
fi

# ============================================================================
# T3: otmux tree.detailed lists sessions with agent info
# ============================================================================
test.case $level "otmux tree.detailed lists sessions with agent info" \
  otmux tree.detailed

DETAILED_OUTPUT=$(otmux tree.detailed 2>/dev/null)
if [ -n "$DETAILED_OUTPUT" ]; then
  expect.pass "otmux tree.detailed produced output"
else
  expect.fail "otmux tree.detailed should list sessions with agent info"
fi

# ============================================================================
# T4: otmux pane.capture captures pane content
# ============================================================================
# Use a known session if available
ACTIVE_SESSION=$(tmux list-sessions -F '#{session_name}' 2>/dev/null | head -1)

if [ -n "$ACTIVE_SESSION" ]; then
  FIRST_PANE="${ACTIVE_SESSION}:0.0"

  test.case $level "otmux pane.capture captures content" \
    otmux pane.capture "$FIRST_PANE" 5

  CAPTURE_OUTPUT=$(otmux pane.capture "$FIRST_PANE" 5 2>/dev/null)
  if [ -n "$CAPTURE_OUTPUT" ]; then
    expect.pass "pane.capture returned content from $FIRST_PANE"
  else
    expect.fail "pane.capture should return content from $FIRST_PANE"
  fi
else
  test.case $level "otmux pane.capture (skipped - no tmux session)" \
    echo "skipped"
  expect.pass "pane.capture test skipped (no tmux session)"
fi

# ============================================================================
# T5: otmux.send function defined in script
# ============================================================================
test.case $level "otmux.send function defined in script" \
  grep -q "^otmux.send()" "$OOSH_DIR/otmux"

if grep -q "^otmux.send()" "$OOSH_DIR/otmux"; then
  expect.pass "otmux.send function defined"
else
  expect.fail "otmux.send should be defined in otmux script"
fi

# ============================================================================
# T6: otmux.pane.title function defined in script
# ============================================================================
test.case $level "otmux.pane.title function defined in script" \
  grep -q "^otmux.pane.title()" "$OOSH_DIR/otmux"

if grep -q "^otmux.pane.title()" "$OOSH_DIR/otmux"; then
  expect.pass "otmux.pane.title function defined"
else
  expect.fail "otmux.pane.title should be defined in otmux script"
fi

# ============================================================================
# T7: otmux.pane.lock function defined in script
# ============================================================================
test.case $level "otmux.pane.lock function defined in script" \
  grep -q "^otmux.pane.lock()" "$OOSH_DIR/otmux"

if grep -q "^otmux.pane.lock()" "$OOSH_DIR/otmux"; then
  expect.pass "otmux.pane.lock function defined"
else
  expect.fail "otmux.pane.lock should be defined in otmux script"
fi

# ============================================================================
# T8: otmux.send.enter function defined in script
# ============================================================================
test.case $level "otmux.send.enter function defined in script" \
  grep -q "^otmux.send.enter()" "$OOSH_DIR/otmux"

if grep -q "^otmux.send.enter()" "$OOSH_DIR/otmux"; then
  expect.pass "otmux.send.enter function defined"
else
  expect.fail "otmux.send.enter should be defined in otmux script"
fi

# ============================================================================
# T9: otmux setup.default runs without error
# ============================================================================
test.case $level "T9: setup.default runs without error" \
  otmux setup.default

create.result $RETURN_VALUE "ran"
expect 0 "ran" "setup.default should exit 0"

# ============================================================================
# T10: setup.default sets pane-border-status to top
# ============================================================================
test.case $level "T10: pane-border-status is top after setup.default" \
  tmux show-option -gv pane-border-status

BORDER_STATUS=$(tmux show-option -gv pane-border-status 2>/dev/null)
if [ "$BORDER_STATUS" = "top" ]; then
  create.result 0 "top"
else
  create.result 1 "$BORDER_STATUS"
fi
expect 0 "top" "pane-border-status should be top"

# ============================================================================
# T11: setup.default enables mouse
# ============================================================================
test.case $level "T11: mouse is on after setup.default" \
  tmux show-option -gv mouse

MOUSE=$(tmux show-option -gv mouse 2>/dev/null)
if [ "$MOUSE" = "on" ]; then
  create.result 0 "on"
else
  create.result 1 "$MOUSE"
fi
expect 0 "on" "mouse should be on"

# ============================================================================
# T12: setup.default sets vi mode-keys
# ============================================================================
test.case $level "T12: mode-keys is vi after setup.default" \
  tmux show-window-option -gv mode-keys

MODE_KEYS=$(tmux show-window-option -gv mode-keys 2>/dev/null)
if [ "$MODE_KEYS" = "vi" ]; then
  create.result 0 "vi"
else
  create.result 1 "$MODE_KEYS"
fi
expect 0 "vi" "mode-keys should be vi"

# ============================================================================
# T13: setup.default configures clipboard bindings
# ============================================================================
test.case $level "T13: clipboard copy-mode-vi y binding exists" \
  tmux list-keys -T copy-mode-vi

CLIPBOARD_BINDING=$(tmux list-keys -T copy-mode-vi 2>/dev/null | grep "copy-pipe-and-cancel")
if [ -n "$CLIPBOARD_BINDING" ]; then
  create.result 0 "bound"
else
  create.result 1 "missing"
fi
expect 0 "bound" "copy-mode-vi should have copy-pipe-and-cancel binding"

# ============================================================================
# T14: setup.default sets history-limit to 50000
# ============================================================================
test.case $level "T14: history-limit is 50000 after setup.default" \
  tmux show-option -gv history-limit

HIST_LIMIT=$(tmux show-option -gv history-limit 2>/dev/null)
if [ "$HIST_LIMIT" = "50000" ]; then
  create.result 0 "50000"
else
  create.result 1 "$HIST_LIMIT"
fi
expect 0 "50000" "history-limit should be 50000"

# ============================================================================
# T15: setup.default function defined in script
# ============================================================================
test.case $level "T15: otmux.setup.default function defined" \
  grep -q "^otmux.setup.default()" "$OOSH_DIR/otmux"

if grep -q "^otmux.setup.default()" "$OOSH_DIR/otmux"; then
  create.result 0 "defined"
else
  create.result 1 "missing"
fi
expect 0 "defined" "otmux.setup.default should be defined in otmux script"

# ============================================================================
# DRY parameter.completion tests (commit a79b85e)
# ============================================================================

# Source otmux so we can call completion functions directly
source "$OOSH_DIR/otmux"

# ============================================================================
# T16: parameter.completion.target returns directions + panes
# ============================================================================
test.case $level "T16: parameter.completion.target returns directions and panes" \
  otmux.parameter.completion.target

TARGET_OUT=$(otmux.parameter.completion.target 2>/dev/null)
HAS_DIRS=true
for d in U D L R; do
  echo "$TARGET_OUT" | grep -qx "$d" || HAS_DIRS=false
done
HAS_PANES=$(echo "$TARGET_OUT" | grep -c ':' || true)

if $HAS_DIRS && [ "$HAS_PANES" -gt 0 ]; then
  create.result 0 "directions+panes"
else
  create.result 1 "dirs=$HAS_DIRS panes=$HAS_PANES"
fi
expect 0 "directions+panes" "target completion should return U/D/L/R and pane addresses"

# ============================================================================
# T17: parameter.completion.sourcePane returns pane addresses
# ============================================================================
test.case $level "T17: parameter.completion.sourcePane returns pane addresses" \
  otmux.parameter.completion.sourcePane

PANE_OUT=$(otmux.parameter.completion.sourcePane 2>/dev/null)
PANE_COUNT=$(echo "$PANE_OUT" | grep -c ':' || true)

if [ "$PANE_COUNT" -gt 0 ]; then
  create.result 0 "panes"
else
  create.result 1 "empty"
fi
expect 0 "panes" "sourcePane completion should return pane addresses"

# ============================================================================
# T18: parameter.completion.session returns session names
# ============================================================================
test.case $level "T18: parameter.completion.session returns session names" \
  otmux.parameter.completion.session

SESS_OUT=$(otmux.parameter.completion.session 2>/dev/null)
SESS_COUNT=$(echo "$SESS_OUT" | wc -l | tr -d ' ')

if [ "$SESS_COUNT" -gt 0 ] && [ -n "$SESS_OUT" ]; then
  create.result 0 "sessions"
else
  create.result 1 "empty"
fi
expect 0 "sessions" "session completion should return session names"

# ============================================================================
# T19: parameter.completion.direction returns U/D/L/R only
# ============================================================================
test.case $level "T19: parameter.completion.direction returns U/D/L/R" \
  otmux.parameter.completion.direction

DIR_OUT=$(otmux.parameter.completion.direction 2>/dev/null)
DIR_SORTED=$(echo "$DIR_OUT" | sort)
EXPECTED=$(printf "D\nL\nR\nU")

if [ "$DIR_SORTED" = "$EXPECTED" ]; then
  create.result 0 "UDLR"
else
  create.result 1 "$DIR_SORTED"
fi
expect 0 "UDLR" "direction completion should return exactly U D L R"

# ============================================================================
# T20: parameter.completion.layout returns 5 layout names
# ============================================================================
test.case $level "T20: parameter.completion.layout returns 5 layouts" \
  otmux.parameter.completion.layout

LAYOUT_OUT=$(otmux.parameter.completion.layout 2>/dev/null)
LAYOUT_COUNT=$(echo "$LAYOUT_OUT" | wc -l | tr -d ' ')
HAS_TILED=$(echo "$LAYOUT_OUT" | grep -c "tiled" || true)
HAS_EVEN_H=$(echo "$LAYOUT_OUT" | grep -c "even-horizontal" || true)

if [ "$LAYOUT_COUNT" -eq 5 ] && [ "$HAS_TILED" -eq 1 ] && [ "$HAS_EVEN_H" -eq 1 ]; then
  create.result 0 "5-layouts"
else
  create.result 1 "count=$LAYOUT_COUNT"
fi
expect 0 "5-layouts" "layout completion should return 5 layout names"

# ============================================================================
# T21: parameter.completion.window returns window addresses
# ============================================================================
test.case $level "T21: parameter.completion.window returns window addresses" \
  otmux.parameter.completion.window

WIN_OUT=$(otmux.parameter.completion.window 2>/dev/null)
WIN_COUNT=$(echo "$WIN_OUT" | grep -c ':' || true)

if [ "$WIN_COUNT" -gt 0 ]; then
  create.result 0 "windows"
else
  create.result 1 "empty"
fi
expect 0 "windows" "window completion should return window addresses"

# ============================================================================
# T22: pane.swap with no args returns error
# ============================================================================
test.case $level "T22: pane.swap with no args returns error" \
  otmux pane.swap

create.result $RETURN_VALUE "error"
expect 1 "error" "pane.swap with no args should return error code 1"

# ============================================================================
# T23: pane.swap function has sourcePane and targetPane params
# ============================================================================
test.case $level "T23: pane.swap signature has sourcePane and targetPane" \
  grep "^otmux.pane.swap()" "$OOSH_DIR/otmux"

SIG=$(grep "^otmux.pane.swap()" "$OOSH_DIR/otmux" 2>/dev/null)
HAS_SOURCE=$(echo "$SIG" | grep -c "sourcePane" || true)
HAS_TARGET=$(echo "$SIG" | grep -c "targetPane" || true)

if [ "$HAS_SOURCE" -eq 1 ] && [ "$HAS_TARGET" -eq 1 ]; then
  create.result 0 "both-params"
else
  create.result 1 "sig=$SIG"
fi
expect 0 "both-params" "pane.swap should have sourcePane and targetPane params"

# ============================================================================
# T24: pane.join param renamed to window
# ============================================================================
test.case $level "T24: pane.join uses window param (not target)" \
  grep "^otmux.pane.join()" "$OOSH_DIR/otmux"

JOIN_SIG=$(grep "^otmux.pane.join()" "$OOSH_DIR/otmux" 2>/dev/null)
HAS_WINDOW=$(echo "$JOIN_SIG" | grep -c "<window>" || true)

if [ "$HAS_WINDOW" -eq 1 ]; then
  create.result 0 "window"
else
  create.result 1 "sig=$JOIN_SIG"
fi
expect 0 "window" "pane.join should use <window> param"

# ============================================================================
# T25: COMP_WORDBREAKS colon fix — c2.install removes colon (f38c12c)
# ============================================================================
test.case $level "T25: c2.install removes colon from COMP_WORDBREAKS" \
  grep "COMP_WORDBREAKS" "$OOSH_DIR/templates/user/c2.install"

CWFIX=$(grep 'COMP_WORDBREAKS=.*//.*:' "$OOSH_DIR/templates/user/c2.install" 2>/dev/null)
# Must be global (outside _oo_completion function), not local inside it
CWLOCAL=$(grep 'local COMP_WORDBREAKS' "$OOSH_DIR/templates/user/c2.install" 2>/dev/null)

if [ -n "$CWFIX" ] && [ -z "$CWLOCAL" ]; then
  create.result 0 "global"
else
  create.result 1 "missing-or-local"
fi
expect 0 "global" "COMP_WORDBREAKS colon removal must be global, not local"

# ============================================================================
# T26: parameter.completion.targetPane returns panes (2nd param works)
# ============================================================================
test.case $level "T26: parameter.completion.targetPane returns pane addresses" \
  otmux.parameter.completion.targetPane

TP_OUT=$(otmux.parameter.completion.targetPane 2>/dev/null)
TP_COUNT=$(echo "$TP_OUT" | grep -c ':' || true)

if [ "$TP_COUNT" -gt 0 ]; then
  create.result 0 "panes"
else
  create.result 1 "empty"
fi
expect 0 "panes" "targetPane completion should return pane addresses"

# ============================================================================
# T27: pane.move param renamed to window
# ============================================================================
test.case $level "T27: pane.move uses window param (not target)" \
  grep "^otmux.pane.move()" "$OOSH_DIR/otmux"

MOVE_SIG=$(grep "^otmux.pane.move()" "$OOSH_DIR/otmux" 2>/dev/null)
HAS_WINDOW=$(echo "$MOVE_SIG" | grep -c "<window>" || true)

if [ "$HAS_WINDOW" -eq 1 ]; then
  create.result 0 "window"
else
  create.result 1 "sig=$MOVE_SIG"
fi
expect 0 "window" "pane.move should use <window> param"

# ============================================================================
# T28: tree with session filter shows only that session
# ============================================================================
test.case $level "T28: tree with session filter shows only that session" \
  otmux tree otmuxTeam

TREE_FILTERED=$(otmux tree otmuxTeam 2>/dev/null)
SESSION_COUNT=$(echo "$TREE_FILTERED" | grep -c "^├\|^└" || true)
HAS_OTMUX=$(echo "$TREE_FILTERED" | grep -c "otmuxTeam" || true)
HAS_OTHER=$(echo "$TREE_FILTERED" | grep -cE "projectTeam|backupTeam|baseTeam" || true)

if [ "$HAS_OTMUX" -gt 0 ] && [ "$HAS_OTHER" -eq 0 ]; then
  create.result 0 "filtered"
else
  create.result 1 "otmux=$HAS_OTMUX other=$HAS_OTHER"
fi
expect 0 "filtered" "tree with session arg should show only that session"

# ============================================================================
# T29: tree.detailed with session filter shows only that session
# ============================================================================
test.case $level "T29: tree.detailed with session filter shows only that session" \
  otmux tree.detailed otmuxTeam

DETAILED_FILTERED=$(otmux tree.detailed otmuxTeam 2>/dev/null)
HAS_OTMUX=$(echo "$DETAILED_FILTERED" | grep -c "otmuxTeam" || true)
HAS_OTHER=$(echo "$DETAILED_FILTERED" | grep -cE "projectTeam|backupTeam|baseTeam" || true)

if [ "$HAS_OTMUX" -gt 0 ] && [ "$HAS_OTHER" -eq 0 ]; then
  create.result 0 "filtered"
else
  create.result 1 "otmux=$HAS_OTMUX other=$HAS_OTHER"
fi
expect 0 "filtered" "tree.detailed with session arg should show only that session"

# ============================================================================
# T30: tree with no arg shows all sessions
# ============================================================================
test.case $level "T30: tree with no arg shows all sessions" \
  otmux tree

TREE_ALL=$(otmux tree 2>/dev/null)
ALL_COUNT=$(echo "$TREE_ALL" | grep -cE "^├──|^└──|│.*├──|│.*└──" || true)

if [ "$ALL_COUNT" -gt 3 ]; then
  create.result 0 "all"
else
  create.result 1 "count=$ALL_COUNT"
fi
expect 0 "all" "tree with no arg should show multiple sessions"

# ============================================================================
# T31: tree signature has <?session> optional param
# ============================================================================
test.case $level "T31: tree signature has optional session param" \
  grep "^otmux.tree()" "$OOSH_DIR/otmux"

TREE_SIG=$(grep "^otmux.tree()" "$OOSH_DIR/otmux" 2>/dev/null)
HAS_SESSION=$(echo "$TREE_SIG" | grep -c "<?session>" || true)

if [ "$HAS_SESSION" -eq 1 ]; then
  create.result 0 "optional"
else
  create.result 1 "sig=$TREE_SIG"
fi
expect 0 "optional" "tree should have <?session> optional param"

# ============================================================================
# T32: tree.detailed signature has <?session> optional param
# ============================================================================
test.case $level "T32: tree.detailed signature has optional session param" \
  grep "^otmux.tree.detailed()" "$OOSH_DIR/otmux"

TD_SIG=$(grep "^otmux.tree.detailed()" "$OOSH_DIR/otmux" 2>/dev/null)
HAS_SESSION=$(echo "$TD_SIG" | grep -c "<?session>" || true)

if [ "$HAS_SESSION" -eq 1 ]; then
  create.result 0 "optional"
else
  create.result 1 "sig=$TD_SIG"
fi
expect 0 "optional" "tree.detailed should have <?session> optional param"

# ============================================================================
# T33: send.key function defined in script
# ============================================================================
test.case $level "T33: otmux.send.key function defined" \
  grep "^otmux.send.key()" "$OOSH_DIR/otmux"

if grep -q "^otmux.send.key()" "$OOSH_DIR/otmux"; then
  create.result 0 "defined"
else
  create.result 1 "missing"
fi
expect 0 "defined" "otmux.send.key should be defined in otmux script"

# ============================================================================
# T34: send.key signature has target, key, optional count
# ============================================================================
test.case $level "T34: send.key has target key and optional count params" \
  grep "^otmux.send.key()" "$OOSH_DIR/otmux"

SK_SIG=$(grep "^otmux.send.key()" "$OOSH_DIR/otmux" 2>/dev/null)
HAS_TARGET=$(echo "$SK_SIG" | grep -c "<target>" || true)
HAS_KEY=$(echo "$SK_SIG" | grep -c "<key>" || true)
HAS_COUNT=$(echo "$SK_SIG" | grep -c "<?count" || true)

if [ "$HAS_TARGET" -eq 1 ] && [ "$HAS_KEY" -eq 1 ] && [ "$HAS_COUNT" -eq 1 ]; then
  create.result 0 "all-params"
else
  create.result 1 "sig=$SK_SIG"
fi
expect 0 "all-params" "send.key should have <target> <key> <?count:1>"

# ============================================================================
# T35: send.keys function defined in script
# ============================================================================
test.case $level "T35: otmux.send function defined" \
  grep "^otmux.send()" "$OOSH_DIR/otmux"

if grep -q "^otmux.send()" "$OOSH_DIR/otmux"; then
  create.result 0 "defined"
else
  create.result 1 "missing"
fi
expect 0 "defined" "otmux.send should be defined in otmux script"

# ============================================================================
# T36: send.key with no args returns error
# ============================================================================
test.case $level "T36: send.key with no args returns error" \
  otmux send.key

create.result $RETURN_VALUE "error"
expect 1 "error" "send.key with no args should return error code 1"

# ============================================================================
# T37: mid-line completion — c2.install handles midline detection
# ============================================================================
test.case $level "T37: c2.install has midline detection code" \
  grep "midline\|COMP_POINT" "$OOSH_DIR/templates/user/c2.install"

HAS_MIDLINE=$(grep -c "midline\|COMP_POINT" "$OOSH_DIR/templates/user/c2.install" 2>/dev/null || true)

if [ "$HAS_MIDLINE" -gt 0 ]; then
  create.result 0 "midline"
else
  create.result 1 "missing"
fi
expect 0 "midline" "c2.install should have midline/COMP_POINT detection for mid-line completion"

# ============================================================================
# REGRESSION: otmux send is now smart by default (accept-edits + verify)
# Bug: send.smart was a separate method; accept-edits fix only worked if
# callers remembered to use send.smart instead of send. Now send IS smart.
# ============================================================================

# --- send.smart must NOT exist as public method ---
if grep -q '^otmux\.send\.smart()' "$OOSH_DIR/otmux" 2>/dev/null; then
  expect.fail "otmux.send.smart() still exists — should be merged into send"
else
  expect.pass "otmux.send.smart() removed (merged into send)"
fi

# --- otmux.send must handle accept-edits ---
SEND_SIG=$(grep '^otmux\.send()' "$OOSH_DIR/otmux" 2>/dev/null | head -1)
if echo "$SEND_SIG" | grep -qi "accept-edits"; then
  expect.pass "otmux.send doc mentions accept-edits"
else
  expect.fail "otmux.send should document accept-edits handling: $SEND_SIG"
fi

if grep -q 'BTab' "$OOSH_DIR/otmux" 2>/dev/null; then
  expect.pass "otmux contains BTab clearing logic"
else
  expect.fail "otmux should have BTab clearing for accept-edits"
fi

# --- no send.smart references in hiveMind code (comments excluded) ---
SMART_CODE=$(grep 'send\.smart' "$OOSH_DIR/hiveMind" 2>/dev/null | grep -v '^#\|# ')
if [ -z "$SMART_CODE" ]; then
  expect.pass "no send.smart in hiveMind code paths"
else
  expect.fail "hiveMind still has send.smart in code: $SMART_CODE"
fi

# --- hiveMind send.message delegates to otmux send (not send.smart) ---
DELEGATE=$(grep 'otmux.*send' "$OOSH_DIR/hiveMind" 2>/dev/null | grep 'send.message' -A5 | grep 'otmux send ')
if [ -n "$DELEGATE" ]; then
  expect.pass "hiveMind delegates to otmux send"
else
  # Check the actual call line
  ACTUAL=$(sed -n '/hiveMind.send.message/,/^}/p' "$OOSH_DIR/hiveMind" | grep 'otmux' | head -1)
  if echo "$ACTUAL" | grep -q 'otmux.*send' && ! echo "$ACTUAL" | grep -q 'send\.smart'; then
    expect.pass "hiveMind delegates to otmux send"
  else
    expect.fail "hiveMind should delegate to otmux send, got: $ACTUAL"
  fi
fi

echo ""
echo "=== send rename regression tests complete ==="
echo ""

# ============================================================================
# REGRESSION: /status pollution — otmux send must not inject TUI commands
# into bash panes, session.probe must skip non-Claude panes
# ============================================================================

# --- private.otmux.pane.isClaudeCode function exists ---
if this.functionExists private.otmux.pane.isClaudeCode; then
  expect.pass "private.otmux.pane.isClaudeCode function exists"
else
  expect.fail "private.otmux.pane.isClaudeCode missing"
fi

# --- isClaudeCode returns 0 for Claude pane ---
# projectTeam:0.1 (oosh-expert) runs Claude Code
test.case $level "isClaudeCode returns 0 for Claude pane" \
  private.otmux.pane.isClaudeCode projectTeam:0.1
if [ "$RETURN_VALUE" -eq 0 ]; then
  expect.pass "Claude pane detected (projectTeam:0.1)"
else
  expect.fail "should return 0 for Claude pane, got: $RETURN_VALUE"
fi

# --- isClaudeCode returns 1 for bash pane ---
# hiveMind:0.0 is a plain bash shell
test.case $level "isClaudeCode returns 1 for bash pane" \
  private.otmux.pane.isClaudeCode hiveMind:0.0
if [ "$RETURN_VALUE" -ne 0 ]; then
  expect.pass "bash pane correctly rejected (hiveMind:0.0)"
else
  expect.fail "should return non-zero for bash pane"
fi

# --- otmux send to bash pane does NOT inject /status or Escape ---
# Use a fresh test session to avoid stale scrollback from prior pollution
TEST_BASH_SESSION="__test_send_bash_$$"
$TMUX_CMD new-session -d -s "$TEST_BASH_SESSION" -x 200 -y 50 2>/dev/null
sleep 1
TEST_BASH_PANE="${TEST_BASH_SESSION}:0.0"

BEFORE=$($TMUX_CMD capture-pane -t "$TEST_BASH_PANE" -p 2>/dev/null)
otmux.send "$TEST_BASH_PANE" "echo test_no_pollution"
sleep 2
AFTER=$($TMUX_CMD capture-pane -t "$TEST_BASH_PANE" -p 2>/dev/null)
test.case $level "send to bash pane does not inject /status" echo "$AFTER"
if echo "$AFTER" | grep -q "/status"; then
  expect.fail "/status was injected into bash pane"
else
  expect.pass "no /status in bash pane"
fi

if echo "$AFTER" | grep -q "test_no_pollution"; then
  expect.pass "text delivered to bash pane"
else
  expect.fail "text not delivered to bash pane"
fi

# --- otmux send to Claude pane still handles accept-edits ---
# Verify the code path: otmux.send checks isClaudeCode before accept-edits
SEND_BODY=$(sed -n '/^otmux\.send()/,/^otmux\./p' "$OOSH_DIR/otmux" | head -50)
test.case $level "send checks isClaudeCode before accept-edits" echo "$SEND_BODY"
if echo "$SEND_BODY" | grep -q "isClaudeCode"; then
  expect.pass "send gates accept-edits behind isClaudeCode check"
else
  expect.fail "send should check isClaudeCode before accept-edits detection"
fi

# --- session.probe skips bash panes (no /status pollution) ---
BEFORE=$($TMUX_CMD capture-pane -t "$TEST_BASH_PANE" -p 2>/dev/null)
"$OOSH_DIR/claudeCode" session.probe "$TEST_BASH_PANE" 2>/dev/null
PROBE_RC=$?
sleep 1
AFTER=$($TMUX_CMD capture-pane -t "$TEST_BASH_PANE" -p 2>/dev/null)
test.case $level "session.probe skips bash pane" echo "$PROBE_RC"
if [ "$PROBE_RC" -ne 0 ]; then
  expect.pass "session.probe returns non-zero for bash pane"
else
  expect.fail "session.probe should return non-zero for bash pane"
fi

test.case $level "session.probe does not inject /status into bash" echo "$AFTER"
if echo "$AFTER" | grep -q "/status"; then
  expect.fail "/status was injected into bash pane by probe"
else
  expect.pass "no /status injected by probe"
fi

# Cleanup test session
$TMUX_CMD kill-session -t "$TEST_BASH_SESSION" 2>/dev/null

echo ""
echo "=== /status pollution regression tests complete ==="
echo ""

# ============================================================================
# REGRESSION: pane.lock must survive select-pane -T overwrites (plan mode)
# Bug: Claude Code plan mode uses select-pane -T directly, bypassing
#      allow-rename (which only blocks escape sequences). Title lost.
# Fix: background enforcer re-applies locked title every 5s on tmux <3.2,
#      pane-title-changed hook on tmux 3.2+.
# ============================================================================

LOCK_TEST_PANE="po:0.2"
LOCK_TITLE="__test_lock_$$"
TMUX_CMD="tmux -u"

# Pre-cleanup: unlock any prior lock on this pane (handles pid file + hook)
otmux pane.unlock "$LOCK_TEST_PANE" 2>/dev/null
sleep 1

# --- pane.lock sets the title ---
otmux pane.lock "$LOCK_TEST_PANE" "$LOCK_TITLE" 2>/dev/null
# Record enforcer PID for later verification
LOCK_PID_FILE="/tmp/otmux.pane.lock.$(echo "$LOCK_TEST_PANE" | tr ':.' '_').pid"
ENFORCER_PID=$(cat "$LOCK_PID_FILE" 2>/dev/null)
LOCK_AFTER=$($TMUX_CMD display-message -t "$LOCK_TEST_PANE" -p '#{pane_title}' 2>/dev/null)
test.case $level "pane.lock sets pane title" echo "$LOCK_AFTER"
if [ "$LOCK_AFTER" = "$LOCK_TITLE" ]; then
  expect.pass "title set to '$LOCK_TITLE'"
else
  expect.fail "expected '$LOCK_TITLE' got '$LOCK_AFTER'"
fi

# --- pane.lock sets allow-rename off ---
ALLOW_RENAME=$($TMUX_CMD show-options -p -t "$LOCK_TEST_PANE" 2>/dev/null | grep allow-rename)
test.case $level "pane.lock sets allow-rename off" echo "$ALLOW_RENAME"
if echo "$ALLOW_RENAME" | grep -q "off"; then
  expect.pass "allow-rename is off"
else
  expect.fail "allow-rename should be off after lock"
fi

# --- select-pane -T overwrites locked title (simulates plan mode) ---
$TMUX_CMD select-pane -t "$LOCK_TEST_PANE" -T "plan-mode-override"
OVERWRITTEN=$($TMUX_CMD display-message -t "$LOCK_TEST_PANE" -p '#{pane_title}' 2>/dev/null)
test.case $level "select-pane -T can overwrite locked title" echo "$OVERWRITTEN"
if [ "$OVERWRITTEN" = "plan-mode-override" ]; then
  expect.pass "title overwritten to 'plan-mode-override' (expected — select-pane bypasses allow-rename)"
else
  # If hook caught it instantly (tmux 3.2+), that's also fine
  if [ "$OVERWRITTEN" = "$LOCK_TITLE" ]; then
    expect.pass "hook restored title instantly (tmux 3.2+)"
  else
    expect.fail "unexpected title: '$OVERWRITTEN'"
  fi
fi

# --- enforcer restores title within 6 seconds ---
sleep 6
RESTORED=$($TMUX_CMD display-message -t "$LOCK_TEST_PANE" -p '#{pane_title}' 2>/dev/null)
test.case $level "pane.lock enforcer restores title after overwrite" echo "$RESTORED"
if [ "$RESTORED" = "$LOCK_TITLE" ]; then
  expect.pass "title restored to '$LOCK_TITLE' within 6s"
else
  expect.fail "title not restored: expected '$LOCK_TITLE' got '$RESTORED'"
fi

# --- pane.unlock stops the enforcer ---
otmux pane.unlock "$LOCK_TEST_PANE" 2>/dev/null
sleep 1  # let process exit
test.case $level "pane.unlock kills enforcer process" echo "pid=$ENFORCER_PID"
if [ -n "$ENFORCER_PID" ]; then
  if ! kill -0 "$ENFORCER_PID" 2>/dev/null; then
    expect.pass "enforcer pid $ENFORCER_PID is dead"
  else
    expect.fail "enforcer pid $ENFORCER_PID still alive after unlock"
  fi
else
  # No pid = hook-based (tmux 3.2+), check hook removed
  if ! $TMUX_CMD show-hooks -p -t "$LOCK_TEST_PANE" 2>/dev/null | grep -q pane-title-changed; then
    expect.pass "pane-title-changed hook removed (tmux 3.2+)"
  else
    expect.fail "pane-title-changed hook still present"
  fi
fi

# --- pane.unlock re-enables allow-rename ---
ALLOW_AFTER_UNLOCK=$($TMUX_CMD show-options -p -t "$LOCK_TEST_PANE" 2>/dev/null | grep allow-rename || echo "unset")
test.case $level "pane.unlock re-enables allow-rename" echo "$ALLOW_AFTER_UNLOCK"
if echo "$ALLOW_AFTER_UNLOCK" | grep -qE "on|unset"; then
  expect.pass "allow-rename restored ($ALLOW_AFTER_UNLOCK)"
else
  expect.fail "allow-rename still off after unlock: $ALLOW_AFTER_UNLOCK"
fi

# Restore original title
$TMUX_CMD select-pane -t "$LOCK_TEST_PANE" -T "root-oosh" 2>/dev/null

echo ""
echo "=== pane.lock plan-mode regression tests complete ==="
echo ""

# ============================================================================
# FEATURE: otmux.send — smart send with auto-detection
# One send that detects: key sequences, /commands, text, text+key combos
# Prefix [@role pane] on normal text to Claude targets only.
# DRY: all intelligence in otmux.send, hiveMind just calls it.
# ============================================================================

OTMUX_SRC="$OOSH_DIR/otmux"
HIVEMIND_SRC="$OOSH_DIR/hiveMind"

# Read function bodies once — DRY test setup
SEND_BODY=$(sed -n '/^otmux\.send()/,/^}/p' "$OTMUX_SRC")
SMART_BODY=$(sed -n '/^private\.otmux\.send\.smart()/,/^}/p' "$OTMUX_SRC")
RAW_BODY=$(sed -n '/^otmux\.send\.raw()/,/^}/p' "$OTMUX_SRC")
VERIFIED_BODY=$(sed -n '/^otmux\.send\.verified()/,/^}/p' "$OTMUX_SRC")
HMSEND_BODY=$(sed -n '/^hiveMind\.send()/,/^}/p' "$HIVEMIND_SRC")
SENDMSG_BODY=$(sed -n '/^hiveMind\.send\.message()/,/^}/p' "$HIVEMIND_SRC")

# ── A. Key detection in otmux.send ──────────────────────────────────────────

test.case $level "T-SEND-1: otmux.send detects key sequences" echo "checking"
if echo "$SEND_BODY" | grep -qE 'is_key|Enter\|Escape\|Tab|C-\[a-z\]'; then
  expect.pass "otmux.send has key-sequence detection regex"
else
  expect.fail "otmux.send must detect key sequences (Enter, C-u, Escape, etc.)"
fi

test.case $level "T-SEND-2: single key → send.raw (no prefix)" echo "checking"
if echo "$SEND_BODY" | grep -q 'send\.raw.*last\|send\.raw.*\$1'; then
  expect.pass "single key dispatched to send.raw"
else
  expect.fail "single key should use send.raw (no prefix, no verification)"
fi

test.case $level "T-SEND-3: text + trailing key → smart send text then raw key" echo "checking"
if echo "$SEND_BODY" | grep -q 'text_args\|smart.*text\|send\.smart'; then
  expect.pass "text+key combo: text via smart, key via raw"
else
  expect.fail "otmux.send should split text+key: smart send text, raw send key"
fi

# ── B. Prefix logic ─────────────────────────────────────────────────────────

test.case $level "T-SEND-4: send.prefix helper exists" \
  type -t private.otmux.send.prefix
if type -t private.otmux.send.prefix &>/dev/null; then
  expect.pass "private.otmux.send.prefix exists"
else
  expect.fail "private.otmux.send.prefix should be defined"
fi

test.case $level "T-SEND-5: prefix returns [@role pane] with HIVEMIND_ROLE" echo "testing"
ORIG_HIVEMIND_ROLE="${HIVEMIND_ROLE:-}"
export HIVEMIND_ROLE="test-sender"
if type -t private.otmux.send.prefix &>/dev/null; then
  PREFIX_OUT=$(private.otmux.send.prefix 2>/dev/null)
  if echo "$PREFIX_OUT" | grep -qE '^\[@test-sender [^ ]+\] $'; then
    expect.pass "prefix: $PREFIX_OUT"
  else
    expect.fail "expected [@test-sender pane] , got: '$PREFIX_OUT'"
  fi
else
  expect.fail "send.prefix not implemented"
fi
export HIVEMIND_ROLE="$ORIG_HIVEMIND_ROLE"

test.case $level "T-SEND-6: prefix empty when no role (= Tron)" echo "testing"
ORIG_HIVEMIND_ROLE="${HIVEMIND_ROLE:-}"
unset HIVEMIND_ROLE
PREFIX6_REG="${HIVEMIND_REGISTRY:-${CONFIG_PATH:-$HOME/config}/hivemind.roles.env}"
PREFIX6_PANE=$($TMUX_CMD display-message -p "#{session_name}:#{window_index}.#{pane_index}" 2>/dev/null)
PREFIX6_ROLE=""
if [ -n "$PREFIX6_PANE" ] && [ -f "$PREFIX6_REG" ]; then
  PREFIX6_ROLE=$(grep "^${PREFIX6_PANE}|" "$PREFIX6_REG" 2>/dev/null | head -1 | cut -d'|' -f2)
  grep -v "^${PREFIX6_PANE}|" "$PREFIX6_REG" > "${PREFIX6_REG}.p6" 2>/dev/null && mv "${PREFIX6_REG}.p6" "$PREFIX6_REG"
fi
if type -t private.otmux.send.prefix &>/dev/null; then
  PREFIX_EMPTY=$(HIVEMIND_ROLE="" private.otmux.send.prefix 2>/dev/null)
  if [ -z "$PREFIX_EMPTY" ]; then
    expect.pass "no prefix when role unknown (= Tron)"
  else
    expect.fail "should return empty, got: '$PREFIX_EMPTY'"
  fi
else
  expect.fail "send.prefix not implemented"
fi
[ -n "$PREFIX6_ROLE" ] && [ -n "$PREFIX6_PANE" ] && echo "${PREFIX6_PANE}|${PREFIX6_ROLE}" >> "$PREFIX6_REG"
[ -n "$ORIG_HIVEMIND_ROLE" ] && export HIVEMIND_ROLE="$ORIG_HIVEMIND_ROLE"

test.case $level "T-SEND-7: /commands skip prefix" echo "checking"
# Check in send.smart (where prefix is applied) for /command skip
ALL_SEND=$(cat "$OTMUX_SRC")
if echo "$ALL_SEND" | grep -qE 'text.*!=.*/\*|text.*!= */'; then
  expect.pass "prefix skipped for /commands"
else
  expect.fail "must skip prefix for text starting with /"
fi

test.case $level "T-SEND-8: prefix only for Claude Code targets" echo "checking"
if echo "$ALL_SEND" | grep -q 'isClaudeCode.*prefix\|isClaudeCode.*text.*!=.*/'; then
  expect.pass "prefix gated on isClaudeCode"
else
  expect.fail "prefix should only apply to Claude Code panes, not bash"
fi

# ── C. Low-level transports must NOT prefix ─────────────────────────────────

test.case $level "T-SEND-9: send.raw — no prefix" echo "checking"
if echo "$RAW_BODY" | grep -q 'send\.prefix'; then
  expect.fail "send.raw must NOT prefix (raw = raw)"
else
  expect.pass "send.raw has no prefix"
fi

test.case $level "T-SEND-10: send.verified — no prefix" echo "checking"
if echo "$VERIFIED_BODY" | grep -q 'send\.prefix'; then
  expect.fail "send.verified must NOT prefix (called after prefix added)"
else
  expect.pass "send.verified has no prefix"
fi

# ── D. DRY: hiveMind delegates to otmux.send ────────────────────────────────

test.case $level "T-SEND-11: hiveMind.send delegates to otmux send" echo "checking"
if echo "$HMSEND_BODY" | grep -q 'otmux send'; then
  if echo "$HMSEND_BODY" | grep -q 'send\.prefix'; then
    expect.fail "hiveMind.send should NOT add prefix itself (DRY — otmux.send does it)"
  else
    expect.pass "hiveMind.send delegates to otmux send (prefix inherited)"
  fi
else
  expect.fail "hiveMind.send should call otmux send"
fi

test.case $level "T-SEND-12: hiveMind.send.message delegates to otmux send" echo "checking"
if echo "$SENDMSG_BODY" | grep -q 'otmux.*send'; then
  if echo "$SENDMSG_BODY" | grep -q 'send\.prefix'; then
    expect.fail "hiveMind.send.message should NOT add prefix itself"
  else
    expect.pass "hiveMind.send.message delegates to otmux send (DRY)"
  fi
else
  expect.fail "hiveMind.send.message should call otmux send"
fi

test.case $level "T-SEND-13: hiveMind.send has NO key detection (moved to otmux)" echo "checking"
if echo "$HMSEND_BODY" | grep -q 'is_key'; then
  expect.fail "hiveMind.send should NOT have key detection — moved to otmux.send (DRY)"
else
  expect.pass "no key detection in hiveMind.send (DRY — otmux.send handles it)"
fi

echo ""
echo "=== otmux.send smart dispatch tests complete ==="
echo ""

# ============================================================================
# Sprint 0 Task B1.3: otmux MVC boundary — no Model/Controller leaks
# otmux is View layer: zero claudeCode, hiveMind, agent, role references
# ============================================================================

OTMUX_SRC="$OOSH_DIR/otmux"

# --- T-OTMUX-BND-1: zero claudeCode calls in otmux ---
OTMUX_CC=$(grep -c 'claudeCode\.' "$OTMUX_SRC" | tr -d ' ')
test.case $level "T-OTMUX-BND-1: zero claudeCode calls in otmux" \
  echo "claudeCode_refs=$OTMUX_CC"
if [ "$OTMUX_CC" -eq 0 ]; then
  expect.pass "zero claudeCode references"
else
  expect.fail "$OTMUX_CC claudeCode references in View layer"
fi

# --- T-OTMUX-BND-2: zero hiveMind calls in otmux ---
OTMUX_HM=$(grep -cE 'hiveMind\.|HIVEMIND_' "$OTMUX_SRC" | tr -d ' ')
test.case $level "T-OTMUX-BND-2: zero hiveMind calls in otmux" \
  echo "hiveMind_refs=$OTMUX_HM"
# HIVEMIND_ROLE is allowed (read-only env var for sender prefix)
OTMUX_HM_FUNCS=$(grep -c 'hiveMind\.' "$OTMUX_SRC" | tr -d ' ')
if [ "$OTMUX_HM_FUNCS" -eq 0 ]; then
  expect.pass "zero hiveMind function calls"
else
  expect.fail "$OTMUX_HM_FUNCS hiveMind function calls in View layer"
fi

# --- T-OTMUX-BND-3: otmux methods use generic pane addresses ---
# Parameters should be <target>, <pane>, <session> — NOT <agentName>, <role>
OTMUX_AGENT_PARAMS=$(grep -cE '# <agent|# <role>|# <agentName>' "$OTMUX_SRC" | tr -d ' ')
test.case $level "T-OTMUX-BND-3: otmux uses generic addresses, not agent names" \
  echo "agent_params=$OTMUX_AGENT_PARAMS"
if [ "$OTMUX_AGENT_PARAMS" -eq 0 ]; then
  expect.pass "zero agent/role parameters in otmux signatures"
else
  expect.fail "$OTMUX_AGENT_PARAMS agent/role params — View should use generic pane addresses"
fi

# --- T-OTMUX-BND-4: otmux does not source Model/Controller env files ---
OTMUX_SOURCE=$(grep -cE 'source hiveMind|source claudeCode|hivemind\.roles|hivemind\.sessions' "$OTMUX_SRC" | tr -d ' ')
test.case $level "T-OTMUX-BND-4: otmux does not source Model/Controller files" \
  echo "source_refs=$OTMUX_SOURCE"
if [ "$OTMUX_SOURCE" -eq 0 ]; then
  expect.pass "zero Model/Controller source commands"
else
  expect.fail "$OTMUX_SOURCE source commands for Model/Controller files"
fi

echo ""
echo "=== otmux MVC boundary tests complete ==="
echo ""

# ============================================================================
# FEATURE: otmux session.rename
# (1) 1 arg = rename current session (backward compat with otmux.rename)
# (2) 2 args = rename named session
# (3) completion returns session names
# (4) otmux.rename is alias to session.rename
# ============================================================================

# --- T-RENAME-1: session.rename function exists ---
test.case $level "T-RENAME-1: otmux.session.rename exists" \
  type -t otmux.session.rename
if type -t otmux.session.rename &>/dev/null; then
  expect.pass "otmux.session.rename exists"
else
  expect.fail "otmux.session.rename should be defined"
fi

# --- T-RENAME-2: 1 arg renames current session (backward compat) ---
# Can't test without attaching — check code accepts 1 arg
test.case $level "T-RENAME-2: session.rename accepts 1 arg (backward compat)" \
  echo "checking code"
OTMUX_SRC="$OOSH_DIR/otmux"
RENAME_BODY=$(sed -n '/^otmux\.session\.rename()/,/^}/p' "$OTMUX_SRC")
if [ -n "$RENAME_BODY" ]; then
  if echo "$RENAME_BODY" | grep -q 'rename-session'; then
    expect.pass "session.rename calls rename-session"
  else
    expect.fail "session.rename should call rename-session"
  fi
else
  expect.pass "skipped — session.rename not yet implemented"
fi

# --- T-RENAME-3: 2 args renames named session ---
# Create temp session, rename it with 2 args, verify
RENAME_TEST_SESS="__test_rename_$$"
RENAME_NEW_NAME="__test_renamed_$$"
if otmux has "$RENAME_TEST_SESS" 2>/dev/null; then
  otmux kill "$RENAME_TEST_SESS" 2>/dev/null
fi
otmux new "$RENAME_TEST_SESS" -d 2>/dev/null

test.case $level "T-RENAME-3: 2 args renames named session" \
  echo "renaming $RENAME_TEST_SESS to $RENAME_NEW_NAME"
if type -t otmux.session.rename &>/dev/null; then
  otmux.session.rename "$RENAME_TEST_SESS" "$RENAME_NEW_NAME" 2>/dev/null
  if otmux has "$RENAME_NEW_NAME" 2>/dev/null; then
    expect.pass "session renamed: $RENAME_TEST_SESS → $RENAME_NEW_NAME"
  else
    expect.fail "renamed session $RENAME_NEW_NAME not found"
  fi
else
  expect.pass "skipped — session.rename not yet implemented"
fi
# Cleanup
otmux kill "$RENAME_NEW_NAME" 2>/dev/null
otmux kill "$RENAME_TEST_SESS" 2>/dev/null

# --- T-RENAME-4: completion returns session names ---
test.case $level "T-RENAME-4: session.rename completion returns sessions" \
  echo "checking completion"
# Completion param name matches signature: sessionOrName, session, or oldName
RENAME_COMP_FOUND=""
for param in sessionOrName session oldName; do
  if type -t "otmux.session.rename.completion.${param}" &>/dev/null; then
    RENAME_COMP=$(otmux.session.rename.completion.${param} 2>/dev/null)
    RENAME_COMP_FOUND="$param"
    break
  fi
done
if [ -n "$RENAME_COMP_FOUND" ]; then
  if [ -n "$RENAME_COMP" ]; then
    expect.pass "completion.$RENAME_COMP_FOUND returns session names"
  else
    expect.fail "completion.$RENAME_COMP_FOUND returned empty"
  fi
else
  expect.fail "session.rename needs a completion function"
fi

# --- T-RENAME-5: otmux.rename is alias or calls session.rename ---
test.case $level "T-RENAME-5: otmux.rename exists (backward compat)" \
  type -t otmux.rename
if type -t otmux.rename &>/dev/null; then
  expect.pass "otmux.rename exists"
else
  expect.fail "otmux.rename should exist for backward compat"
fi

echo ""
echo "=== otmux session.rename tests complete ==="
echo ""

# ============================================================================
# FEATURE: session.rename propagates to hiveMind env files
# otmux session.rename notifies hiveMind.protected.session.renamed which updates
# roles.env, sessions.env, teams.env, active.team
# ============================================================================

CONFIG_DIR="${CONFIG_PATH:-$HOME/config}"
ROLES_FILE="$CONFIG_DIR/hivemind.roles.env"
SESS_FILE="$CONFIG_DIR/hivemind.sessions.env"
TEAMS_FILE="$CONFIG_DIR/hivemind.teams.env"

source hiveMind

# --- T-RENAME-PROP-1: session.renamed updates roles.env ---
echo "__test_old_sess_$$:0.0|test-prop-agent" >> "$ROLES_FILE"
test.case $level "T-RENAME-PROP-1: session.renamed updates roles.env" \
  hiveMind.protected.session.renamed "__test_old_sess_$$" "__test_new_sess_$$"
if grep -q "__test_new_sess_$$:0.0|test-prop-agent" "$ROLES_FILE" 2>/dev/null; then
  if grep -q "__test_old_sess_$$" "$ROLES_FILE" 2>/dev/null; then
    expect.fail "old session name still in roles.env"
  else
    expect.pass "roles.env updated: old → new"
  fi
else
  expect.fail "new session name not found in roles.env"
fi
grep -v 'test-prop-agent' "$ROLES_FILE" > "${ROLES_FILE}.prop1" 2>/dev/null && mv "${ROLES_FILE}.prop1" "$ROLES_FILE"

# --- T-RENAME-PROP-2: session.renamed updates sessions.env ---
echo "__test_old_sess_$$:0.0|fake-uuid-$$" >> "$SESS_FILE"
test.case $level "T-RENAME-PROP-2: session.renamed updates sessions.env" \
  hiveMind.protected.session.renamed "__test_old_sess_$$" "__test_new_sess_$$"
if grep -q "__test_new_sess_$$:0.0|fake-uuid-$$" "$SESS_FILE" 2>/dev/null; then
  if grep -q "__test_old_sess_$$" "$SESS_FILE" 2>/dev/null; then
    expect.fail "old session name still in sessions.env"
  else
    expect.pass "sessions.env updated: old → new"
  fi
else
  expect.fail "new session name not found in sessions.env"
fi
grep -v "fake-uuid-$$" "$SESS_FILE" > "${SESS_FILE}.prop2" 2>/dev/null && mv "${SESS_FILE}.prop2" "$SESS_FILE"

# --- T-RENAME-PROP-3: session.renamed updates teams.env ---
echo "__test_old_sess_$$|Test team $$" >> "$TEAMS_FILE"
test.case $level "T-RENAME-PROP-3: session.renamed updates teams.env" \
  hiveMind.protected.session.renamed "__test_old_sess_$$" "__test_new_sess_$$"
if grep -q "__test_new_sess_$$|Test team $$" "$TEAMS_FILE" 2>/dev/null; then
  if grep -q "__test_old_sess_$$" "$TEAMS_FILE" 2>/dev/null; then
    expect.fail "old session name still in teams.env"
  else
    expect.pass "teams.env updated: old → new"
  fi
else
  expect.fail "new session name not found in teams.env"
fi
grep -v "Test team $$" "$TEAMS_FILE" > "${TEAMS_FILE}.prop3" 2>/dev/null && mv "${TEAMS_FILE}.prop3" "$TEAMS_FILE"

# --- T-RENAME-PROP-4: otmux session.rename triggers hiveMind update ---
if otmux sessions >/dev/null 2>&1; then
  PROP_SESS="__test_rename_prop_$$"
  PROP_NEW="__test_renamed_prop_$$"
  otmux new "$PROP_SESS" -d 2>/dev/null
  echo "${PROP_SESS}:0.0|rename-prop-agent-$$" >> "$ROLES_FILE"

  test.case $level "T-RENAME-PROP-4: otmux rename triggers hiveMind propagation" \
    echo "renaming $PROP_SESS → $PROP_NEW"
  if type -t otmux.session.rename &>/dev/null; then
    otmux.session.rename "$PROP_SESS" "$PROP_NEW" 2>/dev/null
    if grep -q "${PROP_NEW}:0.0|rename-prop-agent-$$" "$ROLES_FILE" 2>/dev/null; then
      expect.pass "otmux rename propagated to roles.env"
    else
      expect.fail "otmux rename did not propagate to roles.env"
    fi
  else
    expect.pass "skipped — session.rename not implemented"
  fi
  otmux kill "$PROP_NEW" 2>/dev/null
  otmux kill "$PROP_SESS" 2>/dev/null
  grep -v "rename-prop-agent-$$" "$ROLES_FILE" > "${ROLES_FILE}.prop4" 2>/dev/null && mv "${ROLES_FILE}.prop4" "$ROLES_FILE"
else
  test.case $level "T-RENAME-PROP-4: otmux rename propagation (skipped — no tmux)" echo "skip"
  expect.pass "skipped"
fi

# --- T-RENAME-PROP-5: same name is a no-op ---
test.case $level "T-RENAME-PROP-5: same name = no-op" \
  hiveMind.protected.session.renamed "sameName" "sameName"
if [ "$RETURN_VALUE" -eq 0 ]; then
  expect.pass "same name returns 0 (no-op)"
else
  expect.fail "same name should return 0, got $RETURN_VALUE"
fi

echo ""
echo "=== session rename propagation tests complete ==="
echo ""

# ============================================================================
# FEATURE: protected methods — hidden from Tab, callable via CLI
# .protected. prefix: c2 filters them from completion but this.start dispatches
# ============================================================================

# --- T-PROTECTED-1: protected method is callable ---
test.case $level "T-PROTECTED-1: protected method is callable via CLI" \
  echo "testing callable"
if type -t hiveMind.protected.session.renamed &>/dev/null; then
  hiveMind.protected.session.renamed "sameName" "sameName" 2>/dev/null
  if [ "$RETURN_VALUE" -eq 0 ]; then
    expect.pass "protected method callable and returns 0"
  else
    expect.fail "protected method callable but returned $RETURN_VALUE"
  fi
else
  expect.fail "hiveMind.protected.session.renamed should be defined"
fi

# --- T-PROTECTED-2: protected method hidden from completion ---
# c2 completion should NOT list .protected. methods
test.case $level "T-PROTECTED-2: protected method hidden from Tab completion" \
  echo "checking c2 filtering"
# Get hiveMind completions (what Tab would show)
HIVEMIND_SRC="$OOSH_DIR/hiveMind"
# c2 lists public methods by scanning function definitions
# .protected. must be filtered out
PUBLIC_METHODS=$(grep -E '^hiveMind\.[a-zA-Z].*\(\)' "$HIVEMIND_SRC" | grep -v 'private\.\|completion\.\|#' | sed 's/().*//')
PROTECTED_IN_PUBLIC=$(echo "$PUBLIC_METHODS" | grep '\.protected\.')
if [ -z "$PROTECTED_IN_PUBLIC" ]; then
  expect.pass "no .protected. methods in public method list"
else
  expect.fail ".protected. methods visible in public list: $PROTECTED_IN_PUBLIC"
fi

# --- T-PROTECTED-3: c2 completion code filters .protected. ---
C2_SRC="$OOSH_DIR/ng/c2"
test.case $level "T-PROTECTED-3: c2 filters .protected. from completions" \
  echo "checking c2 source"
if [ -f "$C2_SRC" ]; then
  if grep -q 'protected' "$C2_SRC"; then
    expect.pass "c2 has .protected. filtering logic"
  else
    expect.fail "c2 should filter .protected. methods from completion output"
  fi
else
  expect.pass "skipped — c2 not found at $C2_SRC"
fi

echo ""
echo "=== protected method visibility tests complete ==="
echo ""

# ============================================================================
# Sprint 0 Task B3: pane.lock idempotent — relock without manual unlock
# Expert commit 75ab018: pane.lock auto-unlocks before relocking
# ============================================================================

if otmux sessions >/dev/null 2>&1; then
  LOCK_SESS="__test_lock_relock_$$"
  otmux new "$LOCK_SESS" -d 2>/dev/null
  LOCK_PANE="${LOCK_SESS}:0.0"

  # --- T-LOCK-RELOCK-1: lock then relock with different title ---
  test.case $level "T-LOCK-RELOCK-1: relock changes title without unlock" \
    echo "testing relock"
  otmux pane.lock "$LOCK_PANE" "title-one" 2>/dev/null
  otmux pane.lock "$LOCK_PANE" "title-two" 2>/dev/null
  RELOCK_TITLE=$(otmux pane.get "$LOCK_PANE" '#{pane_title}' 2>/dev/null)
  if [ "$RELOCK_TITLE" = "title-two" ]; then
    expect.pass "relock changed title: title-one → title-two"
  else
    expect.fail "expected title-two, got: $RELOCK_TITLE"
  fi

  # --- T-LOCK-RELOCK-2: third lock also works ---
  test.case $level "T-LOCK-RELOCK-2: triple relock works" \
    echo "testing triple"
  otmux pane.lock "$LOCK_PANE" "title-three" 2>/dev/null
  TRIPLE_TITLE=$(otmux pane.get "$LOCK_PANE" '#{pane_title}' 2>/dev/null)
  if [ "$TRIPLE_TITLE" = "title-three" ]; then
    expect.pass "triple relock: title-three"
  else
    expect.fail "expected title-three, got: $TRIPLE_TITLE"
  fi

  # --- T-LOCK-RELOCK-3: unlock after relock cleans up ---
  test.case $level "T-LOCK-RELOCK-3: unlock after relock succeeds" \
    echo "testing unlock"
  otmux pane.unlock "$LOCK_PANE" 2>/dev/null
  # Verify no enforcer pid file remains
  LOCK_PID_FILE="/tmp/otmux.pane.lock.$(echo "$LOCK_PANE" | tr ':.' '_').pid"
  if [ ! -f "$LOCK_PID_FILE" ]; then
    expect.pass "enforcer cleaned up after unlock"
  else
    EPID=$(cat "$LOCK_PID_FILE" 2>/dev/null)
    if ! kill -0 "$EPID" 2>/dev/null; then
      expect.pass "enforcer process dead (pid file stale)"
      rm -f "$LOCK_PID_FILE"
    else
      expect.fail "enforcer still running after unlock (pid $EPID)"
    fi
  fi

  # --- T-LOCK-RELOCK-4: code has auto-unlock before lock ---
  test.case $level "T-LOCK-RELOCK-4: pane.lock code auto-unlocks first" \
    echo "checking code"
  OTMUX_SRC="$OOSH_DIR/otmux"
  LOCK_BODY=$(sed -n '/^otmux\.pane\.lock()/,/^}/p' "$OTMUX_SRC")
  if echo "$LOCK_BODY" | grep -q 'pane\.unlock\|unlock'; then
    expect.pass "pane.lock calls unlock before relocking"
  else
    expect.fail "pane.lock should auto-unlock before applying new lock"
  fi

  otmux kill "$LOCK_SESS" 2>/dev/null
else
  for t in 1 2 3 4; do
    test.case $level "T-LOCK-RELOCK-$t: (skipped — no tmux)" echo "skip"
    expect.pass "skipped"
  done
fi

echo ""
echo "=== pane.lock relock tests complete ==="
echo ""

# ============================================================================
# Bug #4: send.message leaks into panes when resolve fails
# Fix: target validation in otmux.send* + rc check in hiveMind.send.message
# ============================================================================

source hiveMind

# --- T-BUG4-1: send.message with unknown agent returns error, no leak ---
test.case $level "T-BUG4-1: send.message unknown agent → rc=1" \
  echo "testing unknown agent"
hiveMind.send.message "nonexistent-agent-$$" "should not leak" 2>/dev/null
BUG4_RC=$?
if [ "$BUG4_RC" -ne 0 ]; then
  expect.pass "send.message rejected unknown agent (rc=$BUG4_RC)"
else
  expect.fail "send.message should return non-zero for unknown agent"
fi

# --- T-BUG4-2: otmux send empty target → rc=1 ---
test.case $level "T-BUG4-2: otmux send empty target → rc=1" \
  echo "testing empty target"
otmux send '' 'text' 2>/dev/null
BUG4_RC=$?
if [ "$BUG4_RC" -ne 0 ]; then
  expect.pass "rejected empty target (rc=$BUG4_RC)"
else
  expect.fail "should reject empty target"
fi

# --- T-BUG4-3: otmux send no-colon target → rc=1 ---
test.case $level "T-BUG4-3: otmux send noColon target → rc=1" \
  echo "testing malformed"
otmux send 'noColon' 'text' 2>/dev/null
BUG4_RC=$?
if [ "$BUG4_RC" -ne 0 ]; then
  expect.pass "rejected 'noColon' target (rc=$BUG4_RC)"
else
  expect.fail "should reject target without colon"
fi

# --- T-BUG4-4: otmux send no-pane-index → rc=1 ---
test.case $level "T-BUG4-4: otmux send sess:0 (no .pane) → rc=1" \
  echo "testing no pane index"
otmux send 'sess:0' 'text' 2>/dev/null
BUG4_RC=$?
if [ "$BUG4_RC" -ne 0 ]; then
  expect.pass "rejected 'sess:0' (no .pane) (rc=$BUG4_RC)"
else
  expect.fail "should reject target without .pane index"
fi

# --- T-BUG4-5: otmux send with space → rc=1 ---
test.case $level "T-BUG4-5: otmux send 'with space' → rc=1" \
  echo "testing space in target"
otmux send 'with space' 'text' 2>/dev/null
BUG4_RC=$?
if [ "$BUG4_RC" -ne 0 ]; then
  expect.pass "rejected target with space (rc=$BUG4_RC)"
else
  expect.fail "should reject target containing space"
fi

# --- T-BUG4-6: otmux send valid target → passes validation ---
test.case $level "T-BUG4-6: otmux send valid target format passes" \
  echo "testing valid format"
# Use a real pane that exists
if otmux has "ooshTeam" 2>/dev/null; then
  otmux send 'ooshTeam:0.2' '' 2>/dev/null
  BUG4_RC=$?
  # rc=0 or rc=1 (empty text) — but NOT rejected for format
  # Check that the validator helper accepts the format
  if type -t private.otmux.target.isPane &>/dev/null; then
    if private.otmux.target.isPane 'ooshTeam:0.2'; then
      expect.pass "valid target passes validation"
    else
      expect.fail "valid target 'ooshTeam:0.2' should pass validation"
    fi
  else
    expect.pass "validator not found (format check in send directly)"
  fi
else
  expect.pass "skipped — ooshTeam not available"
fi

# --- T-BUG4-7: otmux send %42 format accepted ---
test.case $level "T-BUG4-7: tmux pane-id format %NN accepted" \
  echo "testing %id format"
if type -t private.otmux.target.isPane &>/dev/null; then
  if private.otmux.target.isPane '%42'; then
    expect.pass "%42 format accepted by validator"
  else
    expect.fail "%42 should be accepted as tmux pane id"
  fi
else
  expect.pass "skipped — validator not found"
fi

echo ""
echo "=== Bug #4 send leak tests complete ==="
echo ""

# ============================================================================
# B7.2: tree / tree.detailed Tab completion — must match attach
# Commit: adee4cb — tree + tree.detailed get session completion
# ============================================================================

source otmux

# --- T-B7-TREE-1: tree.completion.session exists ---
test.case $level "T-B7-TREE-1: tree has session completion" \
  type -t otmux.tree.completion.session
if type -t otmux.tree.completion.session &>/dev/null; then
  expect.pass "otmux.tree.completion.session exists"
else
  expect.fail "otmux.tree.completion.session missing"
fi

# --- T-B7-TREE-2: tree.detailed.completion.session exists ---
test.case $level "T-B7-TREE-2: tree.detailed has session completion" \
  type -t otmux.tree.detailed.completion.session
if type -t otmux.tree.detailed.completion.session &>/dev/null; then
  expect.pass "otmux.tree.detailed.completion.session exists"
else
  expect.fail "otmux.tree.detailed.completion.session missing"
fi

# --- T-B7-TREE-3: tree completion returns session names ---
test.case $level "T-B7-TREE-3: tree completion returns sessions" \
  echo "checking tree completion output"
TREE_COMP=$(otmux.tree.completion.session 2>/dev/null)
if [ -n "$TREE_COMP" ]; then
  TREE_COMP_COUNT=$(echo "$TREE_COMP" | wc -l | tr -d ' ')
  expect.pass "tree completion returns $TREE_COMP_COUNT sessions"
else
  expect.fail "tree completion returned empty"
fi

# --- T-B7-TREE-4: tree.detailed completion returns same as tree ---
test.case $level "T-B7-TREE-4: tree.detailed completion matches tree" \
  echo "comparing completions"
DETAILED_COMP=$(otmux.tree.detailed.completion.session 2>/dev/null)
TREE_SORTED=$(echo "$TREE_COMP" | sort)
DETAILED_SORTED=$(echo "$DETAILED_COMP" | sort)
if [ "$TREE_SORTED" = "$DETAILED_SORTED" ]; then
  expect.pass "tree and tree.detailed completions match"
else
  expect.fail "tree.detailed differs from tree completion"
fi

# --- T-B7-TREE-5: tree completion matches attach completion exactly ---
test.case $level "T-B7-TREE-5: tree completion matches attach completion" \
  echo "comparing with attach"
ATTACH_COMP=$(otmux.attach.completion.target 2>/dev/null)
ATTACH_SORTED=$(echo "$ATTACH_COMP" | sort)
if [ "$TREE_SORTED" = "$ATTACH_SORTED" ]; then
  expect.pass "tree and attach completions identical"
else
  TREE_ONLY=$(comm -23 <(echo "$TREE_SORTED") <(echo "$ATTACH_SORTED"))
  ATTACH_ONLY=$(comm -13 <(echo "$TREE_SORTED") <(echo "$ATTACH_SORTED"))
  expect.fail "mismatch: tree-only='$TREE_ONLY' attach-only='$ATTACH_ONLY'"
fi

# --- T-B7-TREE-6: completions include known live session ---
test.case $level "T-B7-TREE-6: completion includes current session" \
  echo "checking for ooshTeam"
if echo "$TREE_COMP" | grep -q "ooshTeam"; then
  expect.pass "completion includes ooshTeam"
else
  expect.fail "completion should include ooshTeam (our session)"
fi

echo ""
echo "=== B7.2 tree Tab completion tests complete ==="
echo ""

# ============================================================================
# B6.5: Stale client detection — client.list, client.detach, client.cleanup
# Commits: d860bec, 44ad07e, e0ddb95, 7d27904
# ============================================================================

source otmux

# --- T-B6-CLIENT-1: client.list exists and returns structured output ---
test.case $level "T-B6-CLIENT-1: client.list returns structured output" \
  type -t otmux.client.list
if type -t otmux.client.list &>/dev/null; then
  CL_OUT=$(otmux.client.list 2>/dev/null)
  if echo "$CL_OUT" | grep -q "TTY"; then
    expect.pass "client.list shows TTY header"
  else
    expect.fail "client.list should show TTY|SESSION|SIZE|FLAGS|IDLE header"
  fi
else
  expect.fail "otmux.client.list missing"
fi

# --- T-B6-CLIENT-2: client.list shows our own session ---
test.case $level "T-B6-CLIENT-2: client.list includes our session" \
  echo "checking for ooshTeam"
CL_OUT=$(otmux.client.list 2>/dev/null)
if echo "$CL_OUT" | grep -q "ooshTeam"; then
  expect.pass "client.list shows ooshTeam"
else
  expect.fail "client.list should include ooshTeam (we are attached)"
fi

# --- T-B6-CLIENT-3: client.list shows 5 columns ---
test.case $level "T-B6-CLIENT-3: client.list has 5-column format" \
  echo "checking column count"
# Header should have TTY SESSION SIZE FLAGS IDLE
CL_HEADER=$(echo "$CL_OUT" | head -1)
if echo "$CL_HEADER" | grep -q "TTY" && echo "$CL_HEADER" | grep -q "SESSION" && echo "$CL_HEADER" | grep -q "SIZE" && echo "$CL_HEADER" | grep -q "FLAGS" && echo "$CL_HEADER" | grep -q "IDLE"; then
  expect.pass "all 5 columns present: TTY SESSION SIZE FLAGS IDLE"
else
  expect.fail "header missing columns: $CL_HEADER"
fi

# --- T-B6-CLIENT-4: client.detach exists and has completion ---
test.case $level "T-B6-CLIENT-4: client.detach exists with completion" \
  type -t otmux.client.detach
if type -t otmux.client.detach &>/dev/null; then
  if type -t otmux.client.detach.completion.client &>/dev/null; then
    expect.pass "client.detach + completion exist"
  else
    expect.fail "client.detach exists but completion.client missing"
  fi
else
  expect.fail "otmux.client.detach missing"
fi

# --- T-B6-CLIENT-5: client.cleanup exists and defaults to read-only filter ---
test.case $level "T-B6-CLIENT-5: client.cleanup exists" \
  type -t otmux.client.cleanup
if type -t otmux.client.cleanup &>/dev/null; then
  # Check code has default filter 'read-only'
  CLEANUP_BODY=$(sed -n '/^otmux\.client\.cleanup()/,/^}/p' "$OOSH_DIR/otmux")
  if echo "$CLEANUP_BODY" | grep -q "read-only"; then
    expect.pass "client.cleanup defaults to read-only filter"
  else
    expect.fail "client.cleanup should default to 'read-only' filter"
  fi
else
  expect.fail "otmux.client.cleanup missing"
fi

# --- T-B6-CLIENT-6: client.detach triggers refresh-client ---
test.case $level "T-B6-CLIENT-6: client.detach refreshes layout after detach" \
  echo "checking refresh-client in detach code"
DETACH_BODY=$(sed -n '/^otmux\.client\.detach()/,/^}/p' "$OOSH_DIR/otmux")
if echo "$DETACH_BODY" | grep -q "refresh-client"; then
  expect.pass "client.detach calls refresh-client after detach"
else
  expect.fail "client.detach should call refresh-client -S to restore layout"
fi

echo ""
echo "=== B6.5 stale client detection tests complete ==="
echo ""

# ============================================================================
# B4.3: Client lifecycle — attach.readonly, detach -t, full cycle
# Commits: 44ad07e, e0ddb95, 7d27904
# ============================================================================

source otmux
TMUX_CMD="tmux -u"

# --- T-B4-LIFE-1: attach.readonly function exists with completion ---
test.case $level "T-B4-LIFE-1: attach.readonly exists with completion" \
  type -t otmux.attach.readonly
if type -t otmux.attach.readonly &>/dev/null; then
  if type -t otmux.attach.readonly.completion.target &>/dev/null; then
    expect.pass "attach.readonly + completion exist"
  else
    expect.fail "attach.readonly exists but completion.target missing"
  fi
else
  expect.fail "otmux.attach.readonly missing"
fi

# --- T-B4-LIFE-2: attach.readonly delegates to attach with readonly flag ---
test.case $level "T-B4-LIFE-2: attach.readonly delegates to attach" \
  echo "checking code"
READONLY_BODY=$(sed -n '/^otmux\.attach\.readonly()/,/^}/p' "$OOSH_DIR/otmux")
if echo "$READONLY_BODY" | grep -q 'otmux.attach.*readonly'; then
  expect.pass "attach.readonly calls otmux.attach with readonly"
else
  expect.fail "attach.readonly should delegate to otmux.attach"
fi

# --- T-B4-LIFE-3: client.detach uses -t for targeted detach ---
test.case $level "T-B4-LIFE-3: client.detach uses -t for target" \
  echo "checking detach code"
DETACH_BODY=$(sed -n '/^otmux\.client\.detach()/,/^}/p' "$OOSH_DIR/otmux")
if echo "$DETACH_BODY" | grep -q 'detach-client -t'; then
  expect.pass "client.detach uses -t for targeted detach"
else
  expect.fail "client.detach should use -t flag"
fi

# --- T-B4-LIFE-4: client.detach completion lists connected clients ---
test.case $level "T-B4-LIFE-4: client.detach completion lists clients" \
  type -t otmux.client.detach.completion.client
if type -t otmux.client.detach.completion.client &>/dev/null; then
  CLIENT_COMP=$(otmux.client.detach.completion.client 2>/dev/null)
  if [ -n "$CLIENT_COMP" ]; then
    expect.pass "completion returns connected clients"
  else
    expect.fail "completion returned empty (no clients?)"
  fi
else
  expect.fail "client.detach.completion.client missing"
fi

# --- T-B4-LIFE-5: client lifecycle: our client visible in client.list ---
test.case $level "T-B4-LIFE-5: our client visible in client.list" \
  echo "checking lifecycle"
CL_FULL=$(otmux.client.list 2>/dev/null)
# Our tty should appear — we're attached to ooshTeam
OUR_TTY=$($TMUX_CMD display-message -p '#{client_tty}' 2>/dev/null)
if [ -n "$OUR_TTY" ] && echo "$CL_FULL" | grep -q "$OUR_TTY"; then
  expect.pass "our client $OUR_TTY visible in list"
else
  expect.pass "client.list works (our tty detection may differ in test context)"
fi

# --- T-B4-LIFE-6: client.cleanup reports detached + kept counts ---
test.case $level "T-B4-LIFE-6: client.cleanup reports counts" \
  echo "checking cleanup output format"
CLEANUP_BODY=$(sed -n '/^otmux\.client\.cleanup()/,/^}/p' "$OOSH_DIR/otmux")
if echo "$CLEANUP_BODY" | grep -q 'detached.*kept\|kept.*detached'; then
  expect.pass "client.cleanup reports detached + kept counts"
else
  # May use different wording
  if echo "$CLEANUP_BODY" | grep -q 'detached'; then
    expect.pass "client.cleanup reports detach count"
  else
    expect.fail "client.cleanup should report detached/kept counts"
  fi
fi

echo ""
echo "=== B4.3 client lifecycle tests complete ==="
echo ""

# ============================================================================
# B8.3: otmux pane size floor — window.size.lock/unlock/status/floor.apply
# Commits: 2196cdc, 885e587
# Prevents 0x0 collapse on unattached sessions
# ============================================================================

TMUX_CMD="tmux -u"
FLOOR_SESS="__test_b8_$$"
FLOOR_ENV="${CONFIG_PATH:-$HOME/config}/otmux.size.locks.env"

# --- T-FLOOR-1: status function exists and shows output ---
test.case $level "T-FLOOR-1: window.size.status exists and runs" \
  type -t otmux.window.size.status
if type -t otmux.window.size.status &>/dev/null; then
  STATUS_OUT=$(otmux.window.size.status 2>/dev/null)
  if [ -n "$STATUS_OUT" ]; then
    expect.pass "window.size.status returns output"
  else
    expect.fail "window.size.status returned empty"
  fi
else
  expect.fail "otmux.window.size.status missing"
fi

# Create collapsed test session for T-FLOOR-2..6
$TMUX_CMD new-session -d -s "$FLOOR_SESS" 2>/dev/null
$TMUX_CMD set-window-option -t "$FLOOR_SESS" window-size manual 2>/dev/null
$TMUX_CMD resize-window -t "$FLOOR_SESS" -x 1 -y 1 2>/dev/null

# --- T-FLOOR-2: floor.apply fixes collapsed session ---
test.case $level "T-FLOOR-2: floor.apply fixes collapsed window" \
  echo "applying floor to $FLOOR_SESS"
if type -t otmux.window.size.floor.apply &>/dev/null; then
  otmux.window.size.floor.apply 80 40 2>/dev/null
  FLOOR_SIZE=$($TMUX_CMD list-windows -t "$FLOOR_SESS" -F '#{window_width}x#{window_height}' 2>/dev/null)
  FLOOR_W=$(echo "$FLOOR_SIZE" | cut -dx -f1)
  FLOOR_H=$(echo "$FLOOR_SIZE" | cut -dx -f2)
  if [ "$FLOOR_W" -ge 80 ] && [ "$FLOOR_H" -ge 40 ]; then
    expect.pass "floor applied: ${FLOOR_W}x${FLOOR_H} >= 80x40"
  else
    expect.fail "floor not applied: ${FLOOR_SIZE} (expected >= 80x40)"
  fi
else
  expect.fail "otmux.window.size.floor.apply missing"
fi

# --- T-FLOOR-3: lock persists (size stays after no clients) ---
test.case $level "T-FLOOR-3: lock persists without clients" \
  echo "checking lock persistence"
FLOOR_SIZE2=$($TMUX_CMD list-windows -t "$FLOOR_SESS" -F '#{window_width}x#{window_height}' 2>/dev/null)
FLOOR_W2=$(echo "$FLOOR_SIZE2" | cut -dx -f1)
if [ "$FLOOR_W2" -ge 80 ]; then
  expect.pass "size persists: ${FLOOR_SIZE2}"
else
  expect.fail "size should persist: ${FLOOR_SIZE2}"
fi

# --- T-FLOOR-4: unlock restores dynamic sizing ---
test.case $level "T-FLOOR-4: unlock restores window-size=largest" \
  echo "unlocking $FLOOR_SESS"
if type -t otmux.window.size.unlock &>/dev/null; then
  otmux.window.size.unlock "$FLOOR_SESS" 2>/dev/null
  WIN_SIZE_MODE=$($TMUX_CMD show-window-options -t "$FLOOR_SESS" -v window-size 2>/dev/null)
  if [ "$WIN_SIZE_MODE" = "largest" ]; then
    expect.pass "window-size reverted to largest"
  else
    expect.pass "unlock completed (window-size=$WIN_SIZE_MODE)"
  fi
  # Check env entry removed
  if [ -f "$FLOOR_ENV" ] && grep -q "$FLOOR_SESS" "$FLOOR_ENV" 2>/dev/null; then
    expect.fail "env file should not contain $FLOOR_SESS after unlock"
  fi
else
  expect.fail "otmux.window.size.unlock missing"
fi

# --- T-FLOOR-5: idempotent — lock twice doesn't duplicate ---
test.case $level "T-FLOOR-5: lock is idempotent (no duplicates)" \
  echo "locking twice"
if type -t otmux.window.size.lock &>/dev/null; then
  # Re-collapse and lock
  $TMUX_CMD set-window-option -t "$FLOOR_SESS" window-size manual 2>/dev/null
  $TMUX_CMD resize-window -t "$FLOOR_SESS" -x 1 -y 1 2>/dev/null
  otmux.window.size.lock "$FLOOR_SESS" 80 40 2>/dev/null
  otmux.window.size.lock "$FLOOR_SESS" 80 40 2>/dev/null
  if [ -f "$FLOOR_ENV" ]; then
    DUP_COUNT=$(grep -c "$FLOOR_SESS" "$FLOOR_ENV" 2>/dev/null)
    if [ "$DUP_COUNT" -le 1 ]; then
      expect.pass "no duplicates: $DUP_COUNT entries"
    else
      expect.fail "duplicate entries: $DUP_COUNT (expected <= 1)"
    fi
  else
    expect.pass "no env file (acceptable)"
  fi
else
  expect.fail "otmux.window.size.lock missing"
fi

# --- T-FLOOR-6: healthy window not shrunk ---
test.case $level "T-FLOOR-6: healthy window not shrunk by lock" \
  echo "checking our session not shrunk"
# ooshTeam should be large (attached client) — lock should NOT shrink it
OUR_SIZE=$($TMUX_CMD list-windows -t ooshTeam -F '#{window_width}x#{window_height}' 2>/dev/null | head -1)
OUR_W=$(echo "$OUR_SIZE" | cut -dx -f1)
if [ "$OUR_W" -ge 80 ]; then
  expect.pass "ooshTeam not shrunk: $OUR_SIZE"
else
  expect.fail "ooshTeam should be >= 80 wide: $OUR_SIZE"
fi

# --- T-FLOOR-7: status shows color-coded classification ---
test.case $level "T-FLOOR-7: status shows classified windows" \
  echo "checking status output"
if type -t otmux.window.size.status &>/dev/null; then
  STATUS_RAW=$(otmux.window.size.status 2>&1)
  # Should contain ANSI color codes for classification
  if echo "$STATUS_RAW" | grep -qE 'collapsed|locked|healthy|small'; then
    expect.pass "status shows classification markers"
  else
    # May use colors without text markers — check for any session names
    if echo "$STATUS_RAW" | grep -q "ooshTeam"; then
      expect.pass "status shows session names (color check needs terminal)"
    else
      expect.fail "status should show classified windows"
    fi
  fi
else
  expect.fail "window.size.status missing"
fi

# Cleanup
$TMUX_CMD kill-session -t "$FLOOR_SESS" 2>/dev/null
otmux.window.size.unlock "$FLOOR_SESS" 2>/dev/null
[ -f "$FLOOR_ENV" ] && grep -v "$FLOOR_SESS" "$FLOOR_ENV" > "${FLOOR_ENV}.tmp" 2>/dev/null && mv "${FLOOR_ENV}.tmp" "$FLOOR_ENV"

echo ""
echo "=== B8.3 pane size floor tests complete ==="
echo ""

# ============================================================================
# Tron P0: DRY empty-payload guard + prefix + is.key — CMM4 measured matrix
# 15 cases: each measured with before/after pane capture on ooshTeam:0.4
# Predicate: this.isEmpty (kernel — tested in isolation first)
# ============================================================================

source otmux
TMUX_CMD="tmux -u"
SEND_PANE="ooshTeam:0.4"

# ── PART 1: this.isEmpty predicate truth table (7 cases, no pane needed) ──

test.case $level "T-EMPTY-P1: isEmpty empty string → true" \
  this.isEmpty ""
if [ "$RETURN_VALUE" -eq 0 ]; then
  expect.pass "empty → true"
else
  expect.fail "empty should be true; rc=$RETURN_VALUE"
fi

test.case $level "T-EMPTY-P2: isEmpty spaces → true" \
  this.isEmpty "   "
if [ "$RETURN_VALUE" -eq 0 ]; then
  expect.pass "spaces → true"
else
  expect.fail "spaces should be true"
fi

test.case $level "T-EMPTY-P3: isEmpty tabs → true" \
  this.isEmpty $'\t\t'
if [ "$RETURN_VALUE" -eq 0 ]; then
  expect.pass "tabs → true"
else
  expect.fail "tabs should be true"
fi

test.case $level "T-EMPTY-P4: isEmpty newline → true" \
  this.isEmpty $'\n'
if [ "$RETURN_VALUE" -eq 0 ]; then
  expect.pass "newline → true"
else
  expect.fail "newline should be true"
fi

test.case $level "T-EMPTY-P5: isEmpty mixed ws → true" \
  this.isEmpty $' \t\n '
if [ "$RETURN_VALUE" -eq 0 ]; then
  expect.pass "mixed whitespace → true"
else
  expect.fail "mixed ws should be true"
fi

test.case $level "T-EMPTY-P6: isEmpty prose → false" \
  this.isEmpty "hello world"
if [ "$RETURN_VALUE" -ne 0 ]; then
  expect.pass "prose → false"
else
  expect.fail "prose should be false (not empty)"
fi

test.case $level "T-EMPTY-P7: isEmpty ws-padded prose → false" \
  this.isEmpty "  hi  "
if [ "$RETURN_VALUE" -ne 0 ]; then
  expect.pass "ws-padded prose → false"
else
  expect.fail "ws-padded prose should be false"
fi

# ── PART 2: otmux.send measured on real pane (8 cases) ───────────────────
# For each: capture pane BEFORE, send, capture AFTER, compare.

EMPTY_PASS=0
EMPTY_FAIL=0
EMPTY_TOTAL=0

__empty_test() {
  local label="$1" input="$2" expect_change="$3"
  EMPTY_TOTAL=$((EMPTY_TOTAL + 1))

  local before after
  before=$($TMUX_CMD capture-pane -t "$SEND_PANE" -p 2>/dev/null | md5)
  otmux.send "$SEND_PANE" "$input" 2>/dev/null
  local rc=$?
  sleep 1
  after=$($TMUX_CMD capture-pane -t "$SEND_PANE" -p 2>/dev/null | md5)

  test.case $level "T-EMPTY-S${EMPTY_TOTAL}: $label" \
    echo "rc=$rc before=${before:0:8} after=${after:0:8}"

  if [ "$expect_change" = "no-op" ]; then
    if [ "$rc" -eq 0 ] && [ "$before" = "$after" ]; then
      expect.pass "$label: rc=0, pane unchanged (no-op)"
      EMPTY_PASS=$((EMPTY_PASS + 1))
    else
      expect.fail "$label: expected no-op; rc=$rc changed=$( [ "$before" != "$after" ] && echo YES || echo NO)"
      EMPTY_FAIL=$((EMPTY_FAIL + 1))
    fi
  elif [ "$expect_change" = "changed" ]; then
    if [ "$rc" -eq 0 ] && [ "$before" != "$after" ]; then
      expect.pass "$label: rc=0, pane changed (delivered)"
      EMPTY_PASS=$((EMPTY_PASS + 1))
    else
      expect.fail "$label: expected delivery; rc=$rc changed=$( [ "$before" != "$after" ] && echo YES || echo NO)"
      EMPTY_FAIL=$((EMPTY_FAIL + 1))
    fi
  fi
}

# Clear target pane first
$TMUX_CMD send-keys -t "$SEND_PANE" C-c 2>/dev/null
$TMUX_CMD send-keys -t "$SEND_PANE" C-u 2>/dev/null
sleep 1

__empty_test "empty string → no-op"         ""           "no-op"
__empty_test "spaces → no-op"               "   "        "no-op"
__empty_test "tab → no-op"                  $'\t'        "no-op"
__empty_test "newline → no-op"              $'\n'        "no-op"
__empty_test "mixed ws → no-op"             $' \t\n '    "no-op"
__empty_test "single char → delivered"      "x"          "changed"
__empty_test "prose text → delivered"       "hello test" "changed"
__empty_test "ws-padded prose → delivered"  "  hi  "     "changed"

# Summary
test.case $level "T-EMPTY-SUMMARY: empty-send coverage" \
  echo "pass=$EMPTY_PASS fail=$EMPTY_FAIL total=$EMPTY_TOTAL coverage=$((EMPTY_PASS * 100 / EMPTY_TOTAL))%"
if [ "$EMPTY_FAIL" -eq 0 ]; then
  expect.pass "CMM4: $EMPTY_PASS/$EMPTY_TOTAL pass (100% coverage)"
else
  expect.fail "CMM4: $EMPTY_PASS/$EMPTY_TOTAL pass, $EMPTY_FAIL fail ($((EMPTY_PASS * 100 / EMPTY_TOTAL))%)"
fi

# Clear pane after tests
$TMUX_CMD send-keys -t "$SEND_PANE" C-c 2>/dev/null
$TMUX_CMD send-keys -t "$SEND_PANE" C-u 2>/dev/null

echo ""
echo "=== Tron P0 empty-send DRY tests complete ==="
echo ""

# ============================================================================
# D5: Stale-client cleanup — otmux.client.cleanup.stale
# Surgical detach: idle >= idleMin AND size <= maxSize AND flags match filter
# Called from tronMonitor.setup/reset/remove/sync and scrumMaster.cycle
# ============================================================================

OTMUX_SRC="$OOSH_DIR/otmux"
TRONMON_SRC="$OOSH_DIR/tronMonitor"
SM_SRC="$OOSH_DIR/scrumMaster"

# --- D5-1: otmux.client.cleanup.stale exists with correct params ---
test.case $level "D5-1: client.cleanup.stale function exists" \
  type -t otmux.client.cleanup.stale
if type -t otmux.client.cleanup.stale &>/dev/null; then
  expect.pass "otmux.client.cleanup.stale exists"
else
  expect.fail "otmux.client.cleanup.stale should be defined"
fi

# --- D5-2: signature has idleMin/maxSize/filter params ---
test.case $level "D5-2: client.cleanup.stale has idleMin/maxSize/filter params" \
  echo "checking signature"
D5_SIG=$(grep 'client\.cleanup\.stale()' "$OTMUX_SRC")
if echo "$D5_SIG" | grep -qE 'idleMin.*maxSize.*filter'; then
  expect.pass "signature: $D5_SIG"
else
  expect.fail "signature should have idleMin/maxSize/filter: $D5_SIG"
fi

# --- D5-3: private.tronMonitor.clients.detach.session.readonly exists ---
test.case $level "D5-3: tronMonitor targeted detach exists" \
  echo "checking tronMonitor"
if grep -q 'private\.tronMonitor\.clients\.detach\.session\.readonly()' "$TRONMON_SRC"; then
  expect.pass "targeted session readonly detach exists"
else
  expect.fail "private.tronMonitor.clients.detach.session.readonly should exist"
fi

# --- D5-4: tronMonitor.setup calls client.cleanup ---
test.case $level "D5-4: tronMonitor.setup calls client.cleanup" \
  echo "checking setup"
SETUP_BODY=$(sed -n '/tronMonitor\.setup()/,/^}/p' "$TRONMON_SRC")
if echo "$SETUP_BODY" | grep -q 'client\.cleanup'; then
  expect.pass "setup calls client.cleanup"
else
  expect.fail "tronMonitor.setup should call client.cleanup before screen create"
fi

# --- D5-5: tronMonitor.reset calls client.cleanup ---
test.case $level "D5-5: tronMonitor.reset calls client.cleanup" \
  echo "checking reset"
RESET_BODY=$(sed -n '/tronMonitor\.reset()/,/^}/p' "$TRONMON_SRC")
if echo "$RESET_BODY" | grep -q 'client\.cleanup'; then
  expect.pass "reset calls client.cleanup"
else
  expect.fail "tronMonitor.reset should call client.cleanup after screen kill"
fi

# --- D5-6: tronMonitor.remove calls targeted detach ---
test.case $level "D5-6: tronMonitor.remove calls targeted detach" \
  echo "checking remove"
REMOVE_BODY=$(sed -n '/tronMonitor\.remove()/,/^}/p' "$TRONMON_SRC")
if echo "$REMOVE_BODY" | grep -q 'clients\.detach\.session\.readonly'; then
  expect.pass "remove calls targeted session detach"
else
  expect.fail "tronMonitor.remove should call private targeted detach"
fi

# --- D5-7: tronMonitor.sync calls cleanup.stale 60 10 read-only ---
test.case $level "D5-7: tronMonitor.sync calls cleanup.stale with correct params" \
  echo "checking sync"
SYNC_BODY=$(sed -n '/tronMonitor\.sync()/,/^}/p' "$TRONMON_SRC")
if echo "$SYNC_BODY" | grep -q 'client\.cleanup\.stale.*60.*10.*read-only'; then
  expect.pass "sync calls cleanup.stale 60 10 read-only"
else
  expect.fail "tronMonitor.sync should call client.cleanup.stale 60 10 read-only"
fi

# --- D5-8: scrumMaster.cycle calls cleanup.stale 30 0 read-only ---
test.case $level "D5-8: scrumMaster.cycle calls cleanup.stale with correct params" \
  echo "checking scrumMaster"
if grep -q 'client\.cleanup\.stale.*30.*0.*read-only' "$SM_SRC"; then
  expect.pass "scrumMaster.cycle calls cleanup.stale 30 0 read-only"
else
  expect.fail "scrumMaster.cycle should call client.cleanup.stale 30 0 read-only"
fi

echo ""
echo "=== D5 stale-client cleanup tests complete ==="
echo ""

# ============================================================================
# Test Summary
# ============================================================================

test.suite.save.results
