mirror of
https://github.com/theniceboy/.config.git
synced 2026-07-16 22:01:21 +08:00
194 lines
7.2 KiB
Python
194 lines
7.2 KiB
Python
#!/usr/bin/env python3
|
|
"""Alt-U / Alt-E sibling reordering in the goals panel.
|
|
|
|
Covers the confirmed scenarios:
|
|
1. Alt-E swaps a goal down among siblings; cursor follows.
|
|
2. Boundary: Alt-E on the last sibling is a no-op.
|
|
3. Alt-U swaps up (does NOT nest, unlike m+u).
|
|
4. Threads do not cross from one goal into another goal's threads.
|
|
5. Mixed siblings: a thread swaps past a sub-goal under the same parent.
|
|
"""
|
|
import sys, os, subprocess, time
|
|
sys.path.insert(0, os.path.dirname(__file__))
|
|
from visual_lib import *
|
|
|
|
TARGET = "test:0.0"
|
|
AGENT = os.environ.get("AGENT_BIN", "/home/testuser/bin/agent")
|
|
TRACKER_SERVER = "/home/testuser/bin/tracker-server"
|
|
ROWS, COLS = 50, 110
|
|
|
|
|
|
def capture_rows():
|
|
return capture(TARGET, ROWS, COLS)
|
|
|
|
|
|
def dump(label):
|
|
capture_rows()
|
|
print(f"\n--- {label} ---")
|
|
for r in range(ROWS):
|
|
rt = row_text(r)
|
|
if rt.strip():
|
|
print(f" {r:2d}: |{rt}|")
|
|
|
|
|
|
def selected_row():
|
|
capture_rows()
|
|
for r in range(ROWS):
|
|
if row_has_bg(r, "238") > 5:
|
|
return r
|
|
return -1
|
|
|
|
|
|
def selected_text():
|
|
r = selected_row()
|
|
return row_text(r) if r >= 0 else ""
|
|
|
|
|
|
def navigate_to(text):
|
|
for _ in range(80):
|
|
capture_rows()
|
|
for r in range(ROWS):
|
|
if row_has_bg(r, "238") > 5:
|
|
if text in row_text(r):
|
|
return True
|
|
break
|
|
send_keys(TARGET, "e", 0.06)
|
|
for _ in range(80):
|
|
capture_rows()
|
|
for r in range(ROWS):
|
|
if row_has_bg(r, "238") > 5:
|
|
if text in row_text(r):
|
|
return True
|
|
break
|
|
send_keys(TARGET, "u", 0.06)
|
|
return False
|
|
|
|
|
|
def row_of(text):
|
|
capture_rows()
|
|
for r in range(ROWS):
|
|
if text in row_text(r):
|
|
return r
|
|
return -1
|
|
|
|
|
|
def open_panel():
|
|
subprocess.run(
|
|
["tmux", "send-keys", "-t", TARGET,
|
|
f"{AGENT} palette --mode goals --window=@0 --path=/tmp --session-name=test --window-name=test0",
|
|
"Enter"],
|
|
capture_output=True)
|
|
time.sleep(1.5)
|
|
capture_rows()
|
|
|
|
|
|
def reseed(scenario):
|
|
"""Wipe store and seed the given scenario's tree."""
|
|
subprocess.run(["pkill", "-f", "palette"], capture_output=True)
|
|
time.sleep(0.4)
|
|
cache = os.path.expanduser("~/.cache/agent")
|
|
subprocess.run(["rm", "-rf", cache], capture_output=True)
|
|
subprocess.run(["mkdir", "-p", cache], capture_output=True)
|
|
if scenario == "goals":
|
|
a = subprocess.run([AGENT, "goal", "add-goal", "--title", "Goal A"], capture_output=True, text=True).stdout.strip()
|
|
b = subprocess.run([AGENT, "goal", "add-goal", "--title", "Goal B"], capture_output=True, text=True).stdout.strip()
|
|
subprocess.run([AGENT, "goal", "add-goal", "--title", "Goal C"], capture_output=True)
|
|
return a, b
|
|
if scenario == "threads":
|
|
a = subprocess.run([AGENT, "goal", "add-goal", "--title", "Goal A"], capture_output=True, text=True).stdout.strip()
|
|
subprocess.run([AGENT, "goal", "add-goal", "--title", "Goal B"], capture_output=True)
|
|
subprocess.run([AGENT, "goal", "add-thread", "--name", "T1", "--goal", a], capture_output=True)
|
|
subprocess.run([AGENT, "goal", "add-thread", "--name", "T2", "--goal", a], capture_output=True)
|
|
subprocess.run([AGENT, "goal", "add-thread", "--name", "T3", "--goal", "goal-b"], capture_output=True)
|
|
return a
|
|
if scenario == "mixed":
|
|
a = subprocess.run([AGENT, "goal", "add-goal", "--title", "Parent"], capture_output=True, text=True).stdout.strip()
|
|
subprocess.run([AGENT, "goal", "add-goal", "--title", "Sub X", "--parent", a], capture_output=True)
|
|
subprocess.run([AGENT, "goal", "add-thread", "--name", "Thread T", "--goal", a], capture_output=True)
|
|
subprocess.run([AGENT, "goal", "add-goal", "--title", "Sub Y", "--parent", a], capture_output=True)
|
|
return a
|
|
|
|
|
|
def setup():
|
|
subprocess.run(["tmux", "kill-session", "-t", "test"], capture_output=True)
|
|
time.sleep(0.2)
|
|
subprocess.run(["go", "build", "-o", TRACKER_SERVER, "./cmd/tracker-server"],
|
|
cwd="/home/testuser/agent-tracker", capture_output=True)
|
|
subprocess.run(["pkill", "-f", "tracker-server"], capture_output=True)
|
|
time.sleep(0.2)
|
|
subprocess.Popen([TRACKER_SERVER], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
time.sleep(0.5)
|
|
subprocess.run(["tmux", "new-session", "-d", "-s", "test", "-x", str(COLS), "-y", str(ROWS)],
|
|
capture_output=True)
|
|
time.sleep(0.3)
|
|
|
|
|
|
def main():
|
|
setup()
|
|
|
|
# ── 1 & 3: goal swap down + up (no nest) ──
|
|
print("=== Scenario: root goals ===")
|
|
reseed("goals")
|
|
open_panel()
|
|
dump("initial: Goal A / Goal B / Goal C")
|
|
if not navigate_to("Goal B"):
|
|
check("nav to Goal B", False); return 1
|
|
|
|
send_keys(TARGET, "alt+e", 0.4)
|
|
dump("Alt-E on Goal B (expect A C B)")
|
|
check("Alt-E: Goal B moved below Goal C", row_of("Goal C") < row_of("Goal B"))
|
|
check("Alt-E: cursor follows Goal B", "Goal B" in selected_text())
|
|
|
|
# ── 2: boundary no-op (Goal B is now last) ──
|
|
before_last = row_of("Goal B")
|
|
send_keys(TARGET, "alt+e", 0.4)
|
|
dump("Alt-E on Goal B again (boundary — expect no change)")
|
|
check("Alt-E boundary: no-op (Goal B still last)", row_of("Goal B") == before_last)
|
|
|
|
# ── 3: Alt-U swaps up, does NOT nest ──
|
|
if not navigate_to("Goal B"):
|
|
check("nav to Goal B (2)", False); return 1
|
|
send_keys(TARGET, "alt+u", 0.4)
|
|
dump("Alt-U on Goal B (expect swap up, NOT nest under Goal A)")
|
|
check("Alt-U: Goal B swapped above a goal (no nesting)", row_of("Goal B") < row_of("Goal C"))
|
|
# nesting would indent Goal B; verify Goal B row has root indent (1 leading space = panel padding)
|
|
gbb = [row_text(r) for r in range(ROWS) if "Goal B" in row_text(r)]
|
|
if gbb:
|
|
lead = len(gbb[0]) - len(gbb[0].lstrip())
|
|
check("Alt-U: Goal B stayed at root depth (no indent)", lead == 1, f"lead={lead}: |{gbb[0]}|")
|
|
|
|
# ── 4: threads don't cross goal boundary ──
|
|
print("\n=== Scenario: threads under two goals ===")
|
|
reseed("threads")
|
|
open_panel()
|
|
dump("initial: Goal A{T1,T2} Goal B{T3}")
|
|
if not navigate_to("T2"):
|
|
check("nav to T2", False); return 1
|
|
t2_before = row_of("T2")
|
|
send_keys(TARGET, "alt+e", 0.4)
|
|
dump("Alt-E on T2 (last under Goal A — expect no-op, must NOT join Goal B)")
|
|
check("Alt-E on last thread: no-op", row_of("T2") == t2_before)
|
|
check("Alt-E: T2 did not move below T3 / Goal B", row_of("T2") < row_of("T3"))
|
|
|
|
# swap T2 up within Goal A
|
|
send_keys(TARGET, "alt+u", 0.4)
|
|
dump("Alt-U on T2 (expect T2 above T1 under Goal A)")
|
|
check("Alt-U: T2 swapped above T1", row_of("T2") < row_of("T1"))
|
|
|
|
# ── 5: mixed siblings (thread swaps past sub-goal) ──
|
|
print("\n=== Scenario: mixed children under Parent ===")
|
|
reseed("mixed")
|
|
open_panel()
|
|
dump("initial: Parent { Sub X, Thread T, Sub Y }")
|
|
if not navigate_to("Thread T"):
|
|
check("nav to Thread T", False); return 1
|
|
send_keys(TARGET, "alt+e", 0.4)
|
|
dump("Alt-E on Thread T (expect swap past Sub Y)")
|
|
check("Alt-E mixed: Thread T moved below Sub Y", row_of("Sub Y") < row_of("Thread T"))
|
|
|
|
print(summary())
|
|
return 0 if FAIL == 0 else 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|