Double-Buffered Voice Cards, Primary-Monitor EWMH Placement, and Clamp Over Toggle
Setting the Stage: Context for the Curious Book Reader
Starting a complex development environment across seven virtual desktops should feel like flipping a single master switch rather than fighting a chain of race conditions. In this raw R&D log from MikeLev.in, a seven-turn debugging session on a NixOS and GNOME workstation transforms a laggy morning startup sequence (autognome.py) into a tightly synchronized, theatrical instrument.
Along the way, five concrete systems-engineering patterns emerge: applying classic graphics double buffering to neural text-to-speech (piper-tts) and pop-up Alacritty title cards so visual and acoustic cues fire on the same beat; using Always-On-Top title cards as a theatrical curtain to pre-spawn heavy terminal and browser processes in the background; untangling four layers of X11, Mutter, and Chrome multi-monitor state so browser windows stop drifting onto a secondary portrait display; loosening a strict search-and-replace patch airlock (apply.py) just enough to survive browser copy-paste trailing whitespace without sacrificing single-match safety; and replacing stateful modulo-2 mute toggles with idempotent volume clamping.
TL;DR: This article documents a seven-turn refactoring of autognome.py, chrome_windows.py, imports/voice_synthesis.py, and apply.py on NixOS/GNOME. By separating Piper TTS .wav synthesis from audio playback and pre-spawning terminal and browser windows behind raised Alacritty title cards, desktop transition narration and terminal typing begin with near-zero latency. Multi-monitor Chrome drift onto a secondary display (DP-5) is eliminated by sanitizing saved window_placement coordinates in Chromeβs Preferences, passing --new-window, and executing a four-step EWMH unmaximize-move-pin-maximize sequence into the primary monitorβs xrandr geometry. Finally, non-idempotent m key toggles on YouTube Live are replaced with clamped Down-arrow volume bursts, and apply.py gains a trailing-whitespace-tolerant unique-match fallback.
Technical Journal Entry Begins
MikeLev.in: I am interested in making the PiperTTS voice begin to talk the instant
the Alacritty terminal appears. I really like the way this works now but I think
the synchronizing visual changes on the screen with the instantaneous βbeginning
talkingβ event is extremely powerful and almost necessary. Now we have the voice
βcontainedβ to within the Alacritty start and end events as you can see but
within that it just takes soooo long for it to start talking. Is there anything
we can do before the pop-up terminal card appears so that the appearance of the
terminal and the beginning speaking if not instantaneously together is much
closer than it is now? I also think card.py should be under
/home/mike/repos/nixos/ like autognome.py rather than under Pipulate.
Diagnosing the Latency Between Alacritty and Piper TTS
Google 4 Argon Medium: TL;DR: Context is incomplete for patching. Two local modules imported by /home/mike/repos/nixos/autognome.py (imports/voice_synthesis.py and imports/ascii_displays.py) and one router file (foo_files.py) are missing from context, and moving scripts/card.py requires an rg call-site probe across both repos first.
1. Missing Context Files (Checklist Steps 1β3)
/home/mike/repos/nixos/autognome.py imports two local Pipulate modules that are not in the Manifest:
imports/voice_synthesis.pyβ imported ininit_voice()andspeak()(from imports.voice_synthesis import chip_voice_system as cvs). Needed to see howchip_voice_systemloads the Piper ONNX model, whether synthesis and audio playback are separable, and what audio player (aplay,paplay, or Python stream) it spawns.imports/ascii_displays.pyβ imported inshow_card()(from imports.ascii_displays import conjure_window, measure_figlet). Needed to see howconjure_windowspawnsalacrittyand how much setup runs before the window maps.foo_files.pyβ the working tree diff shows# scripts/card.pywas just moved out ofMATCHBOOK_CHOP. Movingscripts/card.pyout of thepipulaterepo into/home/mike/repos/nixos/changes the tracked file count infoo_files.py.
2. Why Speech Lags Behind the Alacritty Card (and How to Fix It)
In /home/mike/repos/nixos/autognome.py (lines 483β496), each desktop transition currently runs in this order:
show_card(card_title)starts a thread that importsimports.ascii_displays, computesmeasure_figlet, and spawnsalacrittyrunningcard.py.speak(narration)writes the sentence toVOICE_WORKER.stdin, which callscvs.speak_text(t).
Even with VOICE_WORKER pre-spawned by init_voice(), two bottlenecks delay the first spoken syllable after the Alacritty window is already visible:
- Lazy model load on first utterance:
init_voice()printsREADYimmediately afterfrom imports.voice_synthesis import chip_voice_system as cvs. Ifchip_voice_systemloads the Piper ONNX model lazily on the firstspeak_text()call rather than at import time, Desktop 1 pays the full model load latency while the card is already on screen. - Synthesis latency before playback: On every desktop,
cvs.speak_text(t)must synthesize the full sentence to audio before handing the buffer/WAV to the audio output device. Meanwhile,alacrittymaps in tens of milliseconds, so the card sits silent while Piper synthesizes.
The fix once imports/voice_synthesis.py and imports/ascii_displays.py are in context:
- Synthesize before showing the card (or pre-synthesize all 8 static phrases at boot): All 7
DESKTOP_NARRATIONSplus"Seven at one swat. Opening journal."are static strings. We can either synthesize the WAV for a desktop before callingshow_card(card_title)βsoVOICE_WORKER(oraplay) only has to play an already-rendered WAV the instantshow_cardfiresβor pre-render the 8 WAV files in/tmpduringinit_voice()so playback starts with near-zero latency the moment the Alacritty card spawns. - Pre-import
conjure_windowandmeasure_figlet: Importingimports.ascii_displaysonce before the loop (instead of inside the_runthread on every desktop) removes import jitter fromshow_card().
3. Moving card.py to /home/mike/repos/nixos/
Before moving scripts/card.py to /home/mike/repos/nixos/, the probes below check:
- All references to
card.pyacross/home/mike/repos/pipulateand/home/mike/repos/nixos(for examplescripts/refactor_cards.pyorremotes/honeybot/scripts/card.py). - Whether
card.pyshould live at/home/mike/repos/nixos/card.py(next toautognome.py) or/home/mike/repos/nixos/scripts/card.py(next tobookmarks_sync.pyandnixos-switch.py).
5-Car Train
1: BEFORE (PROBES β read-only commands; the first finds every card.py reference across both repos, and the second checks /home/mike/repos/nixos layout):
(nix) pipulate $
rg -n "card\.py" /home/mike/repos/pipulate /home/mike/repos/nixos
ls -la /home/mike/repos/nixos /home/mike/repos/nixos/scripts
2: AFTER (NEXT CONTEXT β put in context.txt using context or x):
(nix) pipulate $
# Context 1
context.txt
~/repos/nixos/autognome.py
scripts/card.py
imports/voice_synthesis.py
imports/ascii_displays.py
foo_files.py
! rg -n "card\.py" /home/mike/repos/pipulate /home/mike/repos/nixos
! ls -la /home/mike/repos/nixos /home/mike/repos/nixos/scripts
3: CHANGE (PATCHES):
No repo patches required on this turn; awaiting imports/voice_synthesis.py, imports/ascii_displays.py, foo_files.py, and the card.py call-site probe receipts. No ignition required.
4: PROMPT (Use prompt or p to create prompt.md from copy):
(nix) pipulate $
With imports/voice_synthesis.py, imports/ascii_displays.py, foo_files.py, and the card.py call-site receipts now in context:
1. Eliminate the delay between the Alacritty card appearing and PiperTTS starting to speak in autognome.py (pre-warming the ONNX model and pre-synthesizing or synthesizing audio before spawning the card window so playback starts as the card appears).
2. Move scripts/card.py into /home/mike/repos/nixos/ and update all callers and foo_files.py cleanly.
5: DELIVERABLE (What done looks like):
- No external deliverables outside the repos. Once the next compile lands, we will patch
autognome.py(andimports/voice_synthesis.pyif needed) so audio synthesis happens ahead ofshow_card(), movecard.pyinto/home/mike/repos/nixos/, and updatefoo_files.py.
MikeLev.in: Oh now I see why I have it in Pipulate. The dependencies. Okay, thatβs fine. Youβre right itβs low priority to move it. Letβs focus on the timing.
THE AI-EDIT METHOD
Same commands, run twice, one change between them. Where the readings differ is what the change did; the diff in the middle is the receipt.
1: BEFORE (PROBE):
On branch main
Your branch is up to date with 'origin/main'.
nothing to commit, working tree clean
GIT repo clean. Take BEFORE reading, make CHANGE, record AFTER diff.
(nix) pipulate $ x
(nix) pipulate $ rg -n "card\.py" /home/mike/repos/pipulate /home/mike/repos/nixos
ls -la /home/mike/repos/nixos /home/mike/repos/nixos/scripts
/home/mike/repos/nixos/autognome.py
372: card_script = os.path.join(PIPULATE_ROOT, "scripts/card.py")
374: card_script = os.path.join(PIPULATE_ROOT, "remotes/honeybot/scripts/card.py")
/home/mike/repos/pipulate/scripts/card.py
3:πͺ§ card.py -- Station-break Figlet title card.
/home/mike/repos/pipulate/40k
48:remotes/honeybot/scripts/card.py
/home/mike/repos/pipulate/foo_files.py
648:# scripts/card.py # <-- Pop-up Card
1033:# remotes/honeybot/scripts/card.py # <-- Just added for station identification breaks
/home/mike/repos/pipulate/imports/ascii_displays.py
512: pyfiglet's default width (80) -- exactly what card.py renders with -- so the
517: card.py's own plain-text fallback so the box is never wildly mis-sized.
1693:# (piper-tts) (logs.py / card.py)
/home/mike/repos/pipulate/remotes/honeybot/scripts/card.py
3:πͺ§ card.py -- Station-break Figlet title card.
18: card.py "THE ITCH"
19: card.py "THE ITCH" 6 # optional self-hold cap in seconds
/home/mike/repos/pipulate/remotes/honeybot/scripts/forest.py
37: ("WINDOW", "card.py:5:HONEYBOT"),
79: ("WINDOW", "card.py:5:PIPULATE"),
/home/mike/repos/pipulate/remotes/honeybot/scripts/stream.py
480: # card.py renders a single Figlet banner, and a Figlet footprint is fully
490: # card.py vertically centers the banner, so matching the box height to the
496: # "a Figlet banner with an optional ASCII-art body," with card.py as the
502: if safe_script == "card.py" and args:
648: subprocess.run(["pkill", "-f", "card.py.*UPDATING"], check=False)
711: # ends card.py, alacritty exits, proc.wait returns, the thread dies.
713: target=lambda: conjure_window("card.py", duration=270.0, args=["UPDATING"]),
751: subprocess.run(["pkill", "-f", "card.py.*UPDATING"], check=False)
/home/mike/repos/pipulate/remotes/honeybot/scripts/test_forest.py
93: Mirrors card.py's CONTENT without its screen-clear: card.py calls
146: if win_script == "card.py" and win_arg:
147: print(f"πͺ§ WINDOW card.py β rendering title card '{win_arg}' inline:")
/home/mike/repos/nixos:
total 85876
drwxr-xr-x 7 mike users 4096 Oct 14 19:33 .
drwxr-xr-x 11 mike users 4096 Oct 14 17:17 ..
-rw-r--r-- 1 mike users 3395 Sep 9 05:53 ai-acceleration.nix
-rw-r--r-- 1 mike users 42416 Oct 16 03:08 autognome.py
-rw-r--r-- 1 mike users 11936 Oct 14 20:43 blogs.nix
-rw-r--r-- 1 mike users 21079 Oct 15 13:09 bookmarks_harvest.md
-rw-r--r-- 1 mike users 8733 Oct 15 13:34 bookmarks.nix
-rw-r--r-- 1 mike users 27960 Oct 14 13:33 configuration.nix
-rw-r--r-- 1 root root 57851 Nov 17 2025 configuration.nix.old_big_pile
drwxr-xr-x 2 mike users 4096 Jul 7 13:43 credentials
-rw-r--r-- 1 mike users 6106 Oct 14 11:39 en.utf-8.add
-rw-r--r-- 1 mike users 7771 Oct 14 11:39 en.utf-8.add.spl
-rw-r--r-- 1 mike users 2497 Feb 14 2026 flatnotes.nix
drwxr-xr-x 8 mike users 4096 Oct 16 03:10 .git
-rw-r--r-- 1 mike users 82 Sep 10 15:24 .gitignore
-rw-r--r-- 1 mike users 1438 Nov 18 2025 hardware-configuration.nix
-rw-r--r-- 1 root root 1423 Nov 17 2025 hardware-configuration.nix.old
-rw-r--r-- 1 mike users 21909869 Nov 6 2025 json
-rw-r--r-- 1 mike users 5163 Sep 3 18:13 packages.nix
-rw-r--r-- 1 mike users 533 Oct 15 20:35 patch
drwxr-xr-x 2 mike users 4096 Oct 13 03:37 __pycache__
drwxr-xr-x 3 mike users 4096 Sep 14 12:37 .ruff_cache
drwxr-xr-x 3 mike users 4096 Oct 1 14:04 scripts
-rw-r--r-- 1 mike users 3 Jul 5 15:10 secrets.json
-rw-r--r-- 1 mike users 7008 Oct 15 20:35 services.nix
-rw-r--r-- 1 mike users 36 Oct 1 14:04 .sesskey
-rw-r--r-- 1 mike users 21909875 Nov 6 2025 subprocess
-rw-r--r-- 1 mike users 21909868 Nov 6 2025 sys
-rw-r--r-- 1 mike users 21909869 Nov 6 2025 time
-rw-r--r-- 1 mike users 190 Oct 15 21:04 unstable-pin.json
/home/mike/repos/nixos/scripts:
total 188
drwxr-xr-x 3 mike users 4096 Oct 1 14:04 .
drwxr-xr-x 7 mike users 4096 Oct 14 19:33 ..
-rw-r--r-- 1 mike users 13782 Sep 20 05:11 backup-essential.py
-rw-r--r-- 1 mike users 3142 Nov 30 2025 backup-force.py
-rw-r--r-- 1 mike users 16875 Oct 15 20:35 backup-home.py
-rw-r--r-- 1 mike users 1099 Nov 12 2025 backup-nix.sh
-rw-r--r-- 1 mike users 6264 Nov 29 2025 backup-things.py
-rw-r--r-- 1 mike users 32479 Oct 15 13:34 bookmarks_sync.py
-rw-r--r-- 1 mike users 22405 Oct 15 12:45 chrome_windows.py
-rw-r--r-- 1 mike users 57378 Sep 26 08:59 dedupe.py
-rw-r--r-- 1 mike users 7839 Sep 9 05:53 nixos-switch.py
drwxr-xr-x 2 mike users 4096 Oct 15 20:56 __pycache__
(nix) pipulate $
2: AFTER (NEXT CONTEXT):
# # Context 1
# context.txt
# ~/repos/nixos/autognome.py
# scripts/card.py
# Context 2
context.txt
~/repos/nixos/autognome.py
scripts/card.py
imports/voice_synthesis.py
imports/ascii_displays.py
foo_files.py
! rg -n "card\.py" /home/mike/repos/pipulate /home/mike/repos/nixos
! ls -la /home/mike/repos/nixos /home/mike/repos/nixos/scripts
3: CHANGE (PATCHES):
None, weβre setting initial context.
4: PROMPT:
With imports/voice_synthesis.py, imports/ascii_displays.py, foo_files.py, and the card.py call-site receipts now in context:
- Eliminate the delay between the Alacritty card appearing and PiperTTS starting to speak in autognome.py (pre-warming the ONNX model and pre-synthesizing or synthesizing audio before spawning the card window so playback starts as the card appears).
- Move scripts/card.py into /home/mike/repos/nixos/ and update all callers and foo_files.py cleanly.
5: DELIVERABLE:
Done is going to look like better theatrics, touching the human on a more emotional level and with less frustration of waiting. Itβs magic show stuff. Think what double buffering did for the screen redraw and tearing problem.
Double Buffering Speech Synthesis and Title Card Geometry
Gemini 4 Argon High: Context verified.
TL;DR: Three bottlenecks in imports/voice_synthesis.py and /home/mike/repos/nixos/autognome.py delay speech after the Alacritty card appears: (1) init_voice() never calls cvs.ensure_voice(), leaving the 60 MB ONNX model cold until Desktop 1βs card is already on screen; (2) speak() synthesizes the .wav file after show_card() has already launched Alacritty; and (3) show_card() imports ascii_displays and computes measure_figlet() inside its background thread after launch. Splitting synthesis (synthesize_to_wav) from playback (play_wav) gives us true double buffering: we warm the ONNX model in init_voice(), pre-render each desktopβs .wav into /tmp (prep_speech) before show_card(), and fire play_prepped_speech() the instant Alacritty spawns.
Why It Was Taking So Long (and How Double Buffering Fixes It)
BEFORE (Serial Render While Card Is Already Visible)
ββββββββββββββββββββββββββββββββββββββββββββββββββββ
[show_card()] βββΊ Alacritty maps on screen
ββββΊ VOICE_WORKER: ensure_voice() (loads 60 MB ONNX model on Desktop 1)
ββββΊ synthesize_wav() (ONNX inference writes /tmp/*.wav)
ββββΊ play /tmp/*.wav (voice finally starts talking)
AFTER (Double-Buffered Back-Buffer Prep + Simultaneous Page Flip)
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
[init_voice()] βββΊ ensure_voice() loads ONNX model once before READY
[prep_speech()] ββΊ synthesize_to_wav() writes /tmp/*.wav in back-buffer
[measure_figlet] βΊ computes card geometry in back-buffer
β
ββββΊ [show_card()] βββΊ Alacritty maps on screen β Simultaneous
ββββΊ [play_prepped_speech] βββΊ play_wav(/tmp/*.wav) β "Page Flip"
- Car 1 (
imports/voice_synthesis.py):- Adds
sanitize_text(),synthesize_to_wav(),play_wav(), and_resolve_play_cmd()toChipVoiceSystemwhile keepingsynthesize_and_play()andspeak_text()backward-compatible. _resolve_play_cmd()checksshutil.whichforplay,pw-play,paplay, andaplayon Linux before falling back tonix-shell -p sox, avoiding a multi-second Nix evaluation penalty whenautognome.pyruns outsidenix develop.
- Adds
- Car 2 (
/home/mike/repos/nixos/autognome.py):- Calls
cvs.ensure_voice()insideinit_voice()before emittingREADYso the ONNX model is warm before Desktop 1βs card appears. - Adds
PREP\t<text>(back-buffer.wavsynthesis) andPLAY\t<wav_path>(immediate playback) toVOICE_WORKER, wrapped byprep_speech()andplay_prepped_speech(). - Pre-computes
measure_figlet()inshow_card()before starting the thread soconjure_window()launchesalacrittyimmediately. - Pre-renders each desktopβs narration WAV before calling
show_card(), then triggersplay_prepped_speech()immediately aftershow_card().
- Calls
5-Car Train
1: BEFORE (PROBES β read-only commands; the first checks for the new split synthesis/playback methods in imports/voice_synthesis.py, the second checks for double-buffer hooks in autognome.py, and the third gates autognome.py --dry-run):
(nix) pipulate $
rg -n "def (synthesize_to_wav|play_wav|_resolve_play_cmd)" imports/voice_synthesis.py || echo "MISSING_METHODS"
rg -n "ensure_voice|prep_speech|play_prepped_speech" /home/mike/repos/nixos/autognome.py || echo "MISSING_DOUBLE_BUFFER"
.venv/bin/python /home/mike/repos/nixos/autognome.py --dry-run | tail -n 5
2: AFTER (NEXT CONTEXT β put in context.txt using context or x):
(nix) pipulate $
# Context 3
context.txt
~/repos/nixos/autognome.py
scripts/card.py
imports/voice_synthesis.py
imports/ascii_displays.py
foo_files.py
! rg -n "def (synthesize_to_wav|play_wav|_resolve_play_cmd)" imports/voice_synthesis.py || echo "MISSING_METHODS"
! rg -n "ensure_voice|prep_speech|play_prepped_speech" /home/mike/repos/nixos/autognome.py || echo "MISSING_DOUBLE_BUFFER"
! .venv/bin/python /home/mike/repos/nixos/autognome.py --dry-run | tail -n 5
3: CHANGE (PATCHES β Car 1 patches imports/voice_synthesis.py in pipulate; Car 2 patches /home/mike/repos/nixos/autognome.py in nixos):
Car 1 (imports/voice_synthesis.py β split synthesize_to_wav and play_wav for back-buffer synthesis and fast native Linux player resolution):
(nix) pipulate $
Target: imports/voice_synthesis.py
[[[SEARCH]]]
def synthesize_and_play(self, text: str, ceremony: bool = False) -> bool:
[[[DIVIDER]]]
def _resolve_play_cmd(self, output_path: str, system: str) -> list:
"""Return the fastest available local audio player argv for output_path."""
if system == "Darwin":
return ["afplay", output_path]
for candidate in (
["play", "-q", output_path],
["pw-play", output_path],
["paplay", output_path],
["aplay", "-q", output_path],
):
if shutil.which(candidate[0]):
return candidate
return ["play", output_path]
def sanitize_text(self, text: str) -> str:
"""Apply domain substitution and acoustic markdown/HTML stripping."""
spoken_text = re.sub(r'\*?\*?MikeLev\.in\*?\*?:?', 'Mike:', text, flags=re.IGNORECASE)
spoken_text = re.sub(r'\[([^\]]+)\]\([^\)]+\)', r'\1', spoken_text)
spoken_text = re.sub(r'\[[^\]]+\]', '', spoken_text)
spoken_text = re.sub(r'<[^>]+>', ' ', spoken_text)
return re.sub(r'\s+', ' ', spoken_text).strip()
def synthesize_to_wav(self, text: str, output_path: Optional[str] = None, ceremony: bool = False) -> Optional[str]:
"""Pre-render speech into a WAV file (back-buffer) without playing it yet."""
if voice_consent() != "yes" and not ceremony:
self.last_error = "voice not allowed"
logger.info("π€ Voice not allowed; nothing synthesized")
return None
if not self.ensure_voice():
logger.warning("π€ Voice synthesis not ready")
return None
spoken_text = self.sanitize_text(text)
if not spoken_text:
return None
try:
if output_path is None:
with tempfile.NamedTemporaryFile(suffix='.wav', delete=False) as tmp_file:
output_path = tmp_file.name
with wave.open(output_path, "wb") as wav_file:
self.voice.synthesize_wav(spoken_text, wav_file)
return output_path
except Exception as e:
logger.error(f"π€ WAV pre-synthesis failed: {e}")
self.last_error = f"{type(e).__name__}: {e}"
if output_path and os.path.exists(output_path):
try:
os.unlink(output_path)
except OSError:
pass
return None
def play_wav(self, output_path: str, text: str = "", ceremony: bool = False, cleanup: bool = True) -> bool:
"""Play an already-synthesized WAV file immediately (page flip)."""
if not output_path or not os.path.exists(output_path):
self.last_error = "wav file missing"
return False
if voice_consent() != "yes" and not ceremony:
self.last_error = "voice not allowed"
if cleanup:
try:
os.unlink(output_path)
except OSError:
pass
return False
self.stop_speaking()
try:
import fcntl
import time
import platform
lock_path = f"/tmp/pipulate_voice.lock.{os.getuid()}"
lock_file = open(lock_path, "w")
_deadline = time.monotonic() + 30
while True:
try:
fcntl.flock(lock_file, fcntl.LOCK_EX | fcntl.LOCK_NB)
break
except BlockingIOError:
if time.monotonic() > _deadline:
logger.warning("π€ Voice lock wait exceeded 30s; proceeding (possible overlap).")
break
time.sleep(0.1)
self.stop_speaking()
system = platform.system()
play_cmd = self._resolve_play_cmd(output_path, system)
try:
self.current_process = subprocess.Popen(
play_cmd,
stderr=subprocess.PIPE,
stdout=subprocess.DEVNULL,
)
logger.info(f"π€ Speaking (PID {self.current_process.pid}): {(text or output_path)[:50]}...")
_stderr = self.current_process.communicate()[1]
rc = self.current_process.returncode
if rc != 0:
detail = (_stderr or b"").decode("utf-8", "replace").strip()
detail = detail.splitlines()[-1] if detail else f"exit {rc}"
logger.error(f"π€ Audio playback failed (exit {rc}): {detail}")
self.last_error = f"playback exit {rc}: {detail}"
return False
return True
except (subprocess.CalledProcessError, FileNotFoundError) as e:
if system == "Linux":
try:
self.current_process = subprocess.Popen(
["nix-shell", "-p", "sox", "--run", f"play {output_path}"],
stderr=subprocess.DEVNULL,
stdout=subprocess.DEVNULL,
)
rc = self.current_process.wait()
if rc != 0:
self.last_error = f"nix-shell playback exit {rc}"
return False
return True
except Exception as nix_e:
logger.error(f"π€ Audio playback failed on {system} (nix fallback): {nix_e}")
return False
logger.error(f"π€ Audio playback failed on {system}: {e}")
return False
except Exception as e:
logger.error(f"π€ Audio playback failed: {e}")
self.last_error = f"{type(e).__name__}: {e}"
return False
finally:
if cleanup and output_path and os.path.exists(output_path):
try:
os.unlink(output_path)
except OSError:
pass
self.current_process = None
try:
fcntl.flock(lock_file, fcntl.LOCK_UN)
lock_file.close()
except Exception:
pass
def synthesize_and_play(self, text: str, ceremony: bool = False) -> bool:
[[[REPLACE]]]
Target: imports/voice_synthesis.py
[[[SEARCH]]]
if system == "Darwin":
play_cmd = ["afplay", output_path]
else:
play_cmd = ["play", output_path]
[[[DIVIDER]]]
play_cmd = self._resolve_play_cmd(output_path, system)
[[[REPLACE]]]
Car 2 (/home/mike/repos/nixos/autognome.py β warm ONNX model in init_voice(), pre-synthesize .wav before show_card(), pre-compute measure_figlet() before spawning the card thread, and play the pre-rendered .wav as the card appears):
(nix) pipulate $
Target: /home/mike/repos/nixos/autognome.py
[[[SEARCH]]]
worker_code = (
"import sys, logging\n"
"logging.getLogger('piper').setLevel(logging.ERROR)\n"
"from imports.voice_synthesis import chip_voice_system as cvs\n"
"sys.stdout.write('READY\\n')\n"
"sys.stdout.flush()\n"
"for line in sys.stdin:\n"
" t = line.strip()\n"
" if t:\n"
" try:\n"
" cvs.speak_text(t)\n"
" except Exception:\n"
" pass\n"
" sys.stdout.write('DONE\\n')\n"
" sys.stdout.flush()\n"
)
[[[DIVIDER]]]
worker_code = (
"import sys, logging\n"
"logging.getLogger('piper').setLevel(logging.ERROR)\n"
"from imports.voice_synthesis import chip_voice_system as cvs\n"
"if cvs is not None:\n"
" try:\n"
" cvs.ensure_voice()\n"
" except Exception:\n"
" pass\n"
"sys.stdout.write('READY\\n')\n"
"sys.stdout.flush()\n"
"for line in sys.stdin:\n"
" raw = line.rstrip('\\r\\n')\n"
" if not raw:\n"
" continue\n"
" if raw.startswith('PREP\\t'):\n"
" txt = raw[5:].strip()\n"
" wav = ''\n"
" try:\n"
" if cvs is not None and txt:\n"
" wav = cvs.synthesize_to_wav(txt) or ''\n"
" except Exception:\n"
" wav = ''\n"
" sys.stdout.write(f'WAV\\t{wav}\\n')\n"
" sys.stdout.flush()\n"
" elif raw.startswith('PLAY\\t'):\n"
" wav = raw[5:].strip()\n"
" try:\n"
" if cvs is not None and wav:\n"
" cvs.play_wav(wav)\n"
" except Exception:\n"
" pass\n"
" sys.stdout.write('DONE\\n')\n"
" sys.stdout.flush()\n"
" else:\n"
" try:\n"
" if cvs is not None:\n"
" cvs.speak_text(raw.strip())\n"
" except Exception:\n"
" pass\n"
" sys.stdout.write('DONE\\n')\n"
" sys.stdout.flush()\n"
)
[[[REPLACE]]]
Target: /home/mike/repos/nixos/autognome.py
[[[SEARCH]]]
def speak(text):
[[[DIVIDER]]]
def _voice_allowed():
"""Return True unless PIPULATE_VOICE or ~/.config/pipulate/voice disables speech."""
voice_env = os.environ.get("PIPULATE_VOICE", "").lower()
if voice_env in ("0", "no", "off", "false"):
return False
voice_file = os.path.expanduser("~/.config/pipulate/voice")
if os.path.isfile(voice_file):
try:
with open(voice_file, "r", encoding="utf-8") as f:
if f.read().strip().lower() in ("0", "no", "off", "false"):
return False
except Exception:
pass
return True
def prep_speech(text):
"""Pre-synthesize narration into a back-buffer WAV file before showing the card."""
if not text or not _voice_allowed():
return None
global VOICE_WORKER, VOICE_WARMED
if VOICE_WORKER is None:
init_voice()
if VOICE_WORKER is not None and VOICE_WORKER.poll() is None and VOICE_WARMED:
try:
clean = text.strip().replace("\n", " ")
VOICE_WORKER.stdin.write(f"PREP\t{clean}\n")
VOICE_WORKER.stdin.flush()
resp = VOICE_WORKER.stdout.readline()
if resp.startswith("WAV\t"):
wav_path = resp[4:].strip()
return wav_path if wav_path else None
except Exception as e:
print(f" β Voice prep error: {e}")
VOICE_WORKER = None
return None
def play_prepped_speech(wav_path, fallback_text=""):
"""Trigger playback of a pre-synthesized WAV file at the instant the card appears."""
if not wav_path:
return speak(fallback_text) if fallback_text else None
global VOICE_WORKER, VOICE_WARMED
if VOICE_WORKER is not None and VOICE_WORKER.poll() is None and VOICE_WARMED:
print(f" π£ Narrating: {fallback_text or wav_path}")
try:
VOICE_WORKER.stdin.write(f"PLAY\t{wav_path}\n")
VOICE_WORKER.stdin.flush()
return VOICE_WORKER
except Exception as e:
print(f" β Voice play error: {e}")
VOICE_WORKER = None
return speak(fallback_text) if fallback_text else None
def speak(text):
[[[REPLACE]]]
Target: /home/mike/repos/nixos/autognome.py
[[[SEARCH]]]
def _run():
try:
sys.path.insert(0, PIPULATE_ROOT)
from imports.ascii_displays import conjure_window, measure_figlet
fig_w, fig_h = measure_figlet(label_str)
columns = max(60, fig_w + 8)
lines = max(14, fig_h + 4)
cmd = [py_bin, card_script, label_str, "15.0", stop_file]
conjure_window(
cmd,
duration=15.0,
columns=columns,
lines=lines,
cwd=PIPULATE_ROOT,
title="Card",
window_class="conjure_window_overlay",
)
except Exception as e:
print(f" β Flash card skipped ({type(e).__name__}): {e}")
finally:
if sys.path and sys.path[0] == PIPULATE_ROOT:
sys.path.pop(0)
[[[DIVIDER]]]
try:
sys.path.insert(0, PIPULATE_ROOT)
from imports.ascii_displays import conjure_window, measure_figlet
fig_w, fig_h = measure_figlet(label_str)
except Exception as e:
print(f" β Flash card setup skipped ({type(e).__name__}): {e}")
return None
finally:
if sys.path and sys.path[0] == PIPULATE_ROOT:
sys.path.pop(0)
columns = max(60, fig_w + 8)
lines = max(14, fig_h + 4)
cmd = [py_bin, card_script, label_str, "15.0", stop_file]
def _run():
try:
conjure_window(
cmd,
duration=15.0,
columns=columns,
lines=lines,
cwd=PIPULATE_ROOT,
title="Card",
window_class="conjure_window_overlay",
)
except Exception as e:
print(f" β Flash card skipped ({type(e).__name__}): {e}")
[[[REPLACE]]]
Target: /home/mike/repos/nixos/autognome.py
[[[SEARCH]]]
card_title = desktop_config.get("card")
card_handle = show_card(card_title) if card_title else None
[[[DIVIDER]]]
narration = desktop_config.get("narration")
wav_path = prep_speech(narration) if narration else None
card_title = desktop_config.get("card")
card_handle = show_card(card_title) if card_title else None
[[[REPLACE]]]
Target: /home/mike/repos/nixos/autognome.py
[[[SEARCH]]]
narration = desktop_config.get("narration")
speech_proc = speak(narration) if narration else None
[[[DIVIDER]]]
speech_proc = play_prepped_speech(wav_path, narration) if narration else None
[[[REPLACE]]]
Target: /home/mike/repos/nixos/autognome.py
[[[SEARCH]]]
# Return to Desktop 1 when finished
print("\n=== Returning to Desktop 1 ===")
switch_to_desktop(0)
card_handle = show_card("Desktop: 1\nJOURNAL")
speech_proc = speak("Seven at one swat. Opening journal.")
if speech_proc:
wait_for_speech(speech_proc)
elif card_handle:
time.sleep(1.5)
if card_handle:
dismiss_card(card_handle)
[[[DIVIDER]]]
# Return to Desktop 1 when finished
print("\n=== Returning to Desktop 1 ===")
switch_to_desktop(0)
journal_narration = "Seven at one swat. Opening journal."
journal_wav = prep_speech(journal_narration)
card_handle = show_card("Desktop: 1\nJOURNAL")
speech_proc = play_prepped_speech(journal_wav, journal_narration)
if speech_proc:
wait_for_speech(speech_proc)
elif card_handle:
time.sleep(1.5)
if card_handle:
dismiss_card(card_handle)
[[[REPLACE]]]
Ignition: none required for the compile-lane straddle probes (they load imports/voice_synthesis.py and /home/mike/repos/nixos/autognome.py at call time); to witness the live double-buffered cards and speech across all 7 desktops, run python3 /home/mike/repos/nixos/autognome.py --reset (or init --reset).
4: PROMPT (Use prompt or p to create prompt.md from copy):
(nix) pipulate $
Verify the AFTER receipts for the double-buffered PiperTTS + Alacritty title card synchronization across imports/voice_synthesis.py and /home/mike/repos/nixos/autognome.py, and evaluate whether any further timing calibration is needed.
5: DELIVERABLE (What done looks like):
init_voice()pre-warms the Piper ONNX model (cvs.ensure_voice()) beforeREADYis emitted.- Before any Alacritty card window is spawned on a desktop,
prep_speech()renders the.wavaudio into/tmpin the back-buffer andshow_card()pre-computes the Figlet geometry. - At the βpage flipβ moment,
show_card()spawnsalacrittyandplay_prepped_speech()immediately plays the pre-rendered.wavfile via the fastest local audio player (play,pw-play,paplay, oraplay), synchronizing the visual appearance of the card with the onset of speech.
MikeLev.in:
THE AI-EDIT METHOD
Same commands, run twice, one change between them. Where the readings differ is what the change did; the diff in the middle is the receipt.
1: BEFORE (PROBE):
On branch main
Your branch is up to date with 'origin/main'.
nothing to commit, working tree clean
GIT repo clean. Take BEFORE reading, make CHANGE, record AFTER diff.
(nix) pipulate $ rg -n "def (synthesize_to_wav|play_wav|_resolve_play_cmd)" imports/voice_synthesis.py || echo "MISSING_METHODS"
rg -n "ensure_voice|prep_speech|play_prepped_speech" /home/mike/repos/nixos/autognome.py || echo "MISSING_DOUBLE_BUFFER"
.venv/bin/python /home/mike/repos/nixos/autognome.py --dry-run | tail -n 5
MISSING_METHODS
MISSING_DOUBLE_BUFFER
β
Dry run: 7 desktops, 3 terminals on the stack. No keys were harmed.
"windows": [],
"narration": "Desktop seven: The Stack, Jekyll Servers.",
"card": "Desktop: 7\nTHE STACK"
}
]
(nix) pipulate $
2: AFTER (NEXT CONTEXT):
# # Context 1
# context.txt
# ~/repos/nixos/autognome.py
# scripts/card.py
# # Context 2
# context.txt
# ~/repos/nixos/autognome.py
# scripts/card.py
# imports/voice_synthesis.py
# imports/ascii_displays.py
# foo_files.py
# ! rg -n "card\.py" /home/mike/repos/pipulate /home/mike/repos/nixos
# ! ls -la /home/mike/repos/nixos /home/mike/repos/nixos/scripts
# Context 3
context.txt
~/repos/nixos/autognome.py
scripts/card.py
imports/voice_synthesis.py
imports/ascii_displays.py
foo_files.py
! rg -n "def (synthesize_to_wav|play_wav|_resolve_play_cmd)" imports/voice_synthesis.py || echo "MISSING_METHODS"
! rg -n "ensure_voice|prep_speech|play_prepped_speech" /home/mike/repos/nixos/autognome.py || echo "MISSING_DOUBLE_BUFFER"
! .venv/bin/python /home/mike/repos/nixos/autognome.py --dry-run | tail -n 5
3: CHANGE (PATCHES):
On branch main
Your branch is up to date with 'origin/main'.
nothing to commit, working tree clean
GIT repo clean. Take BEFORE reading, make CHANGE, record AFTER diff.
(nix) pipulate $ patch
(nix) pipulate $ app
β
DETERMINISTIC PATCH APPLIED: Successfully mutated 'imports/voice_synthesis.py'.
β
DETERMINISTIC PATCH APPLIED: Successfully mutated 'imports/voice_synthesis.py'.
(nix) pipulate $ d
diff --git a/imports/voice_synthesis.py b/imports/voice_synthesis.py
index f63f6d76..0086bb9a 100644
--- a/imports/voice_synthesis.py
+++ b/imports/voice_synthesis.py
@@ -588,6 +588,141 @@ class ChipVoiceSystem:
+ def _resolve_play_cmd(self, output_path: str, system: str) -> list:
+ """Return the fastest available local audio player argv for output_path."""
+ if system == "Darwin":
+ return ["afplay", output_path]
+ for candidate in (
+ ["play", "-q", output_path],
+ ["pw-play", output_path],
+ ["paplay", output_path],
+ ["aplay", "-q", output_path],
+ ):
+ if shutil.which(candidate[0]):
+ return candidate
+ return ["play", output_path]
+
+ def sanitize_text(self, text: str) -> str:
+ """Apply domain substitution and acoustic markdown/HTML stripping."""
+ spoken_text = re.sub(r'\*?\*?MikeLev\.in\*?\*?:?', 'Mike:', text, flags=re.IGNORECASE)
+ spoken_text = re.sub(r'\[([^\]]+)\]\([^\)]+\)', r'\1', spoken_text)
+ spoken_text = re.sub(r'\[[^\]]+\]', '', spoken_text)
+ spoken_text = re.sub(r'<[^>]+>', ' ', spoken_text)
+ return re.sub(r'\s+', ' ', spoken_text).strip()
+
+ def synthesize_to_wav(self, text: str, output_path: Optional[str] = None, ceremony: bool = False) -> Optional[str]:
+ """Pre-render speech into a WAV file (back-buffer) without playing it yet."""
+ if voice_consent() != "yes" and not ceremony:
+ self.last_error = "voice not allowed"
+ logger.info("π€ Voice not allowed; nothing synthesized")
+ return None
+ if not self.ensure_voice():
+ logger.warning("π€ Voice synthesis not ready")
+ return None
+ spoken_text = self.sanitize_text(text)
+ if not spoken_text:
+ return None
+ try:
+ if output_path is None:
+ with tempfile.NamedTemporaryFile(suffix='.wav', delete=False) as tmp_file:
+ output_path = tmp_file.name
+ with wave.open(output_path, "wb") as wav_file:
+ self.voice.synthesize_wav(spoken_text, wav_file)
+ return output_path
+ except Exception as e:
+ logger.error(f"π€ WAV pre-synthesis failed: {e}")
+ self.last_error = f"{type(e).__name__}: {e}"
+ if output_path and os.path.exists(output_path):
+ try:
+ os.unlink(output_path)
+ except OSError:
+ pass
+ return None
+
+ def play_wav(self, output_path: str, text: str = "", ceremony: bool = False, cleanup: bool = True) -> bool:
+ """Play an already-synthesized WAV file immediately (page flip)."""
+ if not output_path or not os.path.exists(output_path):
+ self.last_error = "wav file missing"
+ return False
+ if voice_consent() != "yes" and not ceremony:
+ self.last_error = "voice not allowed"
+ if cleanup:
+ try:
+ os.unlink(output_path)
+ except OSError:
+ pass
+ return False
+ self.stop_speaking()
+ try:
+ import fcntl
+ import time
+ import platform
+ lock_path = f"/tmp/pipulate_voice.lock.{os.getuid()}"
+ lock_file = open(lock_path, "w")
+ _deadline = time.monotonic() + 30
+ while True:
+ try:
+ fcntl.flock(lock_file, fcntl.LOCK_EX | fcntl.LOCK_NB)
+ break
+ except BlockingIOError:
+ if time.monotonic() > _deadline:
+ logger.warning("π€ Voice lock wait exceeded 30s; proceeding (possible overlap).")
+ break
+ time.sleep(0.1)
+ self.stop_speaking()
+ system = platform.system()
+ play_cmd = self._resolve_play_cmd(output_path, system)
+ try:
+ self.current_process = subprocess.Popen(
+ play_cmd,
+ stderr=subprocess.PIPE,
+ stdout=subprocess.DEVNULL,
+ )
+ logger.info(f"π€ Speaking (PID {self.current_process.pid}): {(text or output_path)[:50]}...")
+ _stderr = self.current_process.communicate()[1]
+ rc = self.current_process.returncode
+ if rc != 0:
+ detail = (_stderr or b"").decode("utf-8", "replace").strip()
+ detail = detail.splitlines()[-1] if detail else f"exit {rc}"
+ logger.error(f"π€ Audio playback failed (exit {rc}): {detail}")
+ self.last_error = f"playback exit {rc}: {detail}"
+ return False
+ return True
+ except (subprocess.CalledProcessError, FileNotFoundError) as e:
+ if system == "Linux":
+ try:
+ self.current_process = subprocess.Popen(
+ ["nix-shell", "-p", "sox", "--run", f"play {output_path}"],
+ stderr=subprocess.DEVNULL,
+ stdout=subprocess.DEVNULL,
+ )
+ rc = self.current_process.wait()
+ if rc != 0:
+ self.last_error = f"nix-shell playback exit {rc}"
+ return False
+ return True
+ except Exception as nix_e:
+ logger.error(f"π€ Audio playback failed on {system} (nix fallback): {nix_e}")
+ return False
+ logger.error(f"π€ Audio playback failed on {system}: {e}")
+ return False
+ except Exception as e:
+ logger.error(f"π€ Audio playback failed: {e}")
+ self.last_error = f"{type(e).__name__}: {e}"
+ return False
+ finally:
+ if cleanup and output_path and os.path.exists(output_path):
+ try:
+ os.unlink(output_path)
+ except OSError:
+ pass
+ self.current_process = None
+ try:
+ fcntl.flock(lock_file, fcntl.LOCK_UN)
+ lock_file.close()
+ except Exception:
+ pass
+
def synthesize_and_play(self, text: str, ceremony: bool = False) -> bool:
"""
Synthesize text and play audio (Mike's tested approach)
@@ -690,10 +825,7 @@ class ChipVoiceSystem:
import platform
system = platform.system()
- if system == "Darwin":
- play_cmd = ["afplay", output_path]
- else:
- play_cmd = ["play", output_path]
+ play_cmd = self._resolve_play_cmd(output_path, system)
try:
# Use Popen instead of run to allow interruption
(nix) pipulate $ m
π Committing: feat: Implement voice synthesis and playback functionality
[main 22c6c297] feat: Implement voice synthesis and playback functionality
1 file changed, 136 insertions(+), 4 deletions(-)
(nix) pipulate $ patch
(nix) pipulate $ app
β
DETERMINISTIC PATCH APPLIED: Successfully mutated '/home/mike/repos/nixos/autognome.py'.
β
DETERMINISTIC PATCH APPLIED: Successfully mutated '/home/mike/repos/nixos/autognome.py'.
β
DETERMINISTIC PATCH APPLIED: Successfully mutated '/home/mike/repos/nixos/autognome.py'.
β
DETERMINISTIC PATCH APPLIED: Successfully mutated '/home/mike/repos/nixos/autognome.py'.
β
DETERMINISTIC PATCH APPLIED: Successfully mutated '/home/mike/repos/nixos/autognome.py'.
β
DETERMINISTIC PATCH APPLIED: Successfully mutated '/home/mike/repos/nixos/autognome.py'.
(nix) pipulate $ d
(nix) pipulate $ h
(nix) pipulate $ d
(nix) pipulate $ n
(nix) nixos $ d
diff --git a/autognome.py b/autognome.py
index a7933b4..6239d36 100644
--- a/autognome.py
+++ b/autognome.py
@@ -238,13 +238,40 @@ def init_voice():
"import sys, logging\n"
"logging.getLogger('piper').setLevel(logging.ERROR)\n"
"from imports.voice_synthesis import chip_voice_system as cvs\n"
+ "if cvs is not None:\n"
+ " try:\n"
+ " cvs.ensure_voice()\n"
+ " except Exception:\n"
+ " pass\n"
"sys.stdout.write('READY\\n')\n"
"sys.stdout.flush()\n"
"for line in sys.stdin:\n"
- " t = line.strip()\n"
- " if t:\n"
+ " raw = line.rstrip('\\r\\n')\n"
+ " if not raw:\n"
+ " continue\n"
+ " if raw.startswith('PREP\\t'):\n"
+ " txt = raw[5:].strip()\n"
+ " wav = ''\n"
" try:\n"
- " cvs.speak_text(t)\n"
+ " if cvs is not None and txt:\n"
+ " wav = cvs.synthesize_to_wav(txt) or ''\n"
+ " except Exception:\n"
+ " wav = ''\n"
+ " sys.stdout.write(f'WAV\\t{wav}\\n')\n"
+ " sys.stdout.flush()\n"
+ " elif raw.startswith('PLAY\\t'):\n"
+ " wav = raw[5:].strip()\n"
+ " try:\n"
+ " if cvs is not None and wav:\n"
+ " cvs.play_wav(wav)\n"
+ " except Exception:\n"
+ " pass\n"
+ " sys.stdout.write('DONE\\n')\n"
+ " sys.stdout.flush()\n"
+ " else:\n"
+ " try:\n"
+ " if cvs is not None:\n"
+ " cvs.speak_text(raw.strip())\n"
" except Exception:\n"
" pass\n"
" sys.stdout.write('DONE\\n')\n"
@@ -275,6 +302,61 @@ def init_voice():
return None
+def _voice_allowed():
+ """Return True unless PIPULATE_VOICE or ~/.config/pipulate/voice disables speech."""
+ voice_env = os.environ.get("PIPULATE_VOICE", "").lower()
+ if voice_env in ("0", "no", "off", "false"):
+ return False
+ voice_file = os.path.expanduser("~/.config/pipulate/voice")
+ if os.path.isfile(voice_file):
+ try:
+ with open(voice_file, "r", encoding="utf-8") as f:
+ if f.read().strip().lower() in ("0", "no", "off", "false"):
+ return False
+ except Exception:
+ pass
+ return True
+
+
+def prep_speech(text):
+ """Pre-synthesize narration into a back-buffer WAV file before showing the card."""
+ if not text or not _voice_allowed():
+ return None
+ global VOICE_WORKER, VOICE_WARMED
+ if VOICE_WORKER is None:
+ init_voice()
+ if VOICE_WORKER is not None and VOICE_WORKER.poll() is None and VOICE_WARMED:
+ try:
+ clean = text.strip().replace("\n", " ")
+ VOICE_WORKER.stdin.write(f"PREP\t{clean}\n")
+ VOICE_WORKER.stdin.flush()
+ resp = VOICE_WORKER.stdout.readline()
+ if resp.startswith("WAV\t"):
+ wav_path = resp[4:].strip()
+ return wav_path if wav_path else None
+ except Exception as e:
+ print(f" β Voice prep error: {e}")
+ VOICE_WORKER = None
+ return None
+
+
+def play_prepped_speech(wav_path, fallback_text=""):
+ """Trigger playback of a pre-synthesized WAV file at the instant the card appears."""
+ if not wav_path:
+ return speak(fallback_text) if fallback_text else None
+ global VOICE_WORKER, VOICE_WARMED
+ if VOICE_WORKER is not None and VOICE_WORKER.poll() is None and VOICE_WARMED:
+ print(f" π£ Narrating: {fallback_text or wav_path}")
+ try:
+ VOICE_WORKER.stdin.write(f"PLAY\t{wav_path}\n")
+ VOICE_WORKER.stdin.flush()
+ return VOICE_WORKER
+ except Exception as e:
+ print(f" β Voice play error: {e}")
+ VOICE_WORKER = None
+ return speak(fallback_text) if fallback_text else None
+
+
def speak(text):
"""Narrate workspace transitions using Piper TTS via chip_voice_system.
@@ -393,14 +475,22 @@ def show_card(label):
except OSError:
pass
+ try:
+ sys.path.insert(0, PIPULATE_ROOT)
+ from imports.ascii_displays import conjure_window, measure_figlet
+ fig_w, fig_h = measure_figlet(label_str)
+ except Exception as e:
+ print(f" β Flash card setup skipped ({type(e).__name__}): {e}")
+ return None
+ finally:
+ if sys.path and sys.path[0] == PIPULATE_ROOT:
+ sys.path.pop(0)
+ columns = max(60, fig_w + 8)
+ lines = max(14, fig_h + 4)
+ cmd = [py_bin, card_script, label_str, "15.0", stop_file]
+
def _run():
try:
- sys.path.insert(0, PIPULATE_ROOT)
- from imports.ascii_displays import conjure_window, measure_figlet
- fig_w, fig_h = measure_figlet(label_str)
- columns = max(60, fig_w + 8)
- lines = max(14, fig_h + 4)
- cmd = [py_bin, card_script, label_str, "15.0", stop_file]
conjure_window(
cmd,
duration=15.0,
@@ -412,9 +502,6 @@ def show_card(label):
)
except Exception as e:
print(f" β Flash card skipped ({type(e).__name__}): {e}")
- finally:
- if sys.path and sys.path[0] == PIPULATE_ROOT:
- sys.path.pop(0)
th = threading.Thread(target=_run, daemon=True)
th.start()
@@ -978,11 +1065,12 @@ if __name__ == "__main__":
switch_to_desktop(i)
+ narration = desktop_config.get("narration")
+ wav_path = prep_speech(narration) if narration else None
card_title = desktop_config.get("card")
card_handle = show_card(card_title) if card_title else None
- narration = desktop_config.get("narration")
- speech_proc = speak(narration) if narration else None
+ speech_proc = play_prepped_speech(wav_path, narration) if narration else None
if speech_proc:
wait_for_speech(speech_proc)
@@ -1018,8 +1106,10 @@ if __name__ == "__main__":
# Return to Desktop 1 when finished
print("\n=== Returning to Desktop 1 ===")
switch_to_desktop(0)
+ journal_narration = "Seven at one swat. Opening journal."
+ journal_wav = prep_speech(journal_narration)
card_handle = show_card("Desktop: 1\nJOURNAL")
- speech_proc = speak("Seven at one swat. Opening journal.")
+ speech_proc = play_prepped_speech(journal_wav, journal_narration)
if speech_proc:
wait_for_speech(speech_proc)
elif card_handle:
(nix) nixos $ m
π Committing: chore: Add voice-aware speech preprocessing and playback
[main 511d493] chore: Add voice-aware speech preprocessing and playback
1 file changed, 105 insertions(+), 15 deletions(-)
(nix) nixos $ git push
Enumerating objects: 10, done.
Counting objects: 100% (10/10), done.
Delta compression using up to 48 threads
Compressing objects: 100% (7/7), done.
Writing objects: 100% (7/7), 2.11 KiB | 2.11 MiB/s, done.
Total 7 (delta 4), reused 0 (delta 0), pack-reused 0 (from 0)
remote: Resolving deltas: 100% (4/4), completed with 2 local objects.
To github.com:miklevin/nixos-config.git
36be961..511d493 main -> main
(nix) nixos $ h
(nix) pipulate $ git push
Enumerating objects: 7, done.
Counting objects: 100% (7/7), done.
Delta compression using up to 48 threads
Compressing objects: 100% (4/4), done.
Writing objects: 100% (4/4), 1.36 KiB | 1.36 MiB/s, done.
Total 4 (delta 3), reused 0 (delta 0), pack-reused 0 (from 0)
remote: Resolving deltas: 100% (3/3), completed with 3 local objects.
To github.com:pipulate/pipulate
6a742754..22c6c297 main -> main
(nix) pipulate $
Ignition: often βexitβ, βnix developβ. Instructions may vary.
4: PROMPT:
Verify the AFTER receipts for the double-buffered PiperTTS + Alacritty title card synchronization across imports/voice_synthesis.py and /home/mike/repos/nixos/autognome.py, and evaluate whether any further timing calibration is needed.
5: DELIVERABLE:
Letβs seeβ¦
WOW! That was astoundingly effective. Now the only thing is I have an exernal monitor and all the Chrome instances targeted my external monitor instead of their location in the 7 virtual-desktop ribbon.
Now the timing was astoundingly effective. I couldnβt use init --reset which
seems almost useless with how much goes wrong there. I think we should gut that
parameter. Itβs just confusing and I can see because of whatβs already running
issues and knowing Iβll never properly quit everything we should just remove
that. But see if thereβs anything we can do about making sure the correct actual
monitor is targeted. Oh as I rearranged the windows to send you this message I
see that I had to βpeel offβ a tab from a one of the two Chrome windows on the
second monitor which was unexpected and maybe related.
Why Chrome Merged Tabs and Drifted to the Secondary Monitor
Gemini 4 Argon High: Context verified.
TL;DR: The double-buffered voice/card timing receipts (synthesize_to_wav, play_wav, prep_speech, play_prepped_speech) are verified in both repos. Both Chrome issues stem from launch_chrome_profile() in /home/mike/repos/nixos/autognome.py:
- Why you had to βpeel offβ a tab: Desktop 3 and Desktop 6 both launch Chrome with
"Default". Without--new-window, the secondgoogle-chrome-stable --profile-directory=Defaultinvocation on Desktop 6 hands the YouTube Live URL to the already-runningDefaultprocess from Desktop 3, opening it as a tab inside Desktop 3βs window instead of spawning a new window on Desktop 6.Meanwhile,wait_for_window("google chrome")immediately matched Desktop 3βs existing window and moved on. - Why Chrome targeted the external monitor: Chrome restores the last saved
(x, y)coordinates from each profileβsPreferences. In GNOME, workspaces only exist on the primary monitor (workspaces-only-on-primary), so a window that maps at the external monitorβs X/Y offset sits outside the 7-desktop ribbon.
What Car 1 Changes in /home/mike/repos/nixos/autognome.py
- **Guts
--reset/--restart**from the CLI (--dry-run,__main__, and usage docstring). - Forces
--new-windowand diffs Chrome window IDs (get_chrome_window_ids): Snapshots opengoogle-chromeX11 window IDs beforePopenand waits specifically for the new window ID to map, so Desktops 3, 5, and 6 each get their own distinct window. - Targets the primary monitor (
get_primary_monitor_geometry): Parsesxrandr --queryfor theconnected primary WxH+X+Ygeometry, unmaximizes the new Chrome window (-b remove,maximized_vert,maximized_horz,fullscreen), moves it into the primary monitorβs bounding box (-e 0,x,y,w,h), pins it todesktop_num - 1(-t), and re-maximizes it (-b add,maximized_vert,maximized_horz).
5-Car Train
1: BEFORE (PROBES β read-only commands; the first inspects the live monitor layout from xrandr, the second straddles the --reset removal and new Chrome window/monitor targeting in autognome.py, and the third gates --dry-run):
(nix) pipulate $
xrandr --query | rg " connected "
rg -n "(--new-window|get_primary_monitor_geometry|get_chrome_window_ids|--reset|--restart)" /home/mike/repos/nixos/autognome.py
.venv/bin/python /home/mike/repos/nixos/autognome.py --dry-run | tail -n 5
2: AFTER (NEXT CONTEXT β put in context.txt using context or x; includes ~/repos/nixos/scripts/chrome_windows.py to check if bms window reopening needs the same primary-monitor pin):
(nix) pipulate $
# Context 4
context.txt
~/repos/nixos/autognome.py
~/repos/nixos/scripts/chrome_windows.py
scripts/card.py
imports/voice_synthesis.py
imports/ascii_displays.py
foo_files.py
! xrandr --query | rg " connected "
! rg -n "(--new-window|get_primary_monitor_geometry|get_chrome_window_ids|--reset|--restart)" /home/mike/repos/nixos/autognome.py
! .venv/bin/python /home/mike/repos/nixos/autognome.py --dry-run | tail -n 5
3: CHANGE (PATCHES β 1 car on /home/mike/repos/nixos/autognome.py):
Car 1 (/home/mike/repos/nixos/autognome.py β remove --reset/--restart, add --new-window, track new Chrome window IDs, and move/maximize each Chrome window onto the primary monitorβs virtual desktop):
(nix) pipulate $
Target: /home/mike/repos/nixos/autognome.py
[[[SEARCH]]]
Usage:
python3 autognome.py # Launch full 7-desktop orchestration
python3 autognome.py --dry-run # Inspect computed DESKTOP_SETUP without actuating
python3 autognome.py --reset # Gracefully close managed windows before launch
"""
[[[DIVIDER]]]
Usage:
python3 autognome.py # Launch full 7-desktop orchestration
python3 autognome.py --dry-run # Inspect computed DESKTOP_SETUP without actuating
"""
[[[REPLACE]]]
Target: /home/mike/repos/nixos/autognome.py
[[[SEARCH]]]
def launch_chrome_profile(profile_name, desktop_num, urls=None):
"""Launches Google Chrome with specified profile and optional startup URLs."""
print(f" Launching Chrome with {profile_name} profile...")
try:
# Add --no-startup-window flag to prevent focus stealing
cmd = ["google-chrome-stable",
f"--profile-directory={profile_name}",
"--no-first-run",
"--disable-background-mode"]
if urls:
cmd.extend(urls)
subprocess.Popen(cmd,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL)
print(f" Chrome {profile_name} launched on desktop {desktop_num}")
# Wait for Chrome window to appear
wait_for_window("google chrome", timeout=10)
time.sleep(LAUNCH_DELAY) # Extra settling time
except FileNotFoundError:
print(f" Error: google-chrome-stable not found")
except Exception as e:
print(f" Error launching Chrome {profile_name}: {e}")
[[[DIVIDER]]]
def get_primary_monitor_geometry():
"""Return (x, y, w, h) of the primary monitor from xrandr, or None."""
if not shutil.which("xrandr"):
return None
try:
import re
res = subprocess.run(["xrandr", "--query"], capture_output=True, text=True, check=True)
fallback = None
for line in res.stdout.splitlines():
if " connected " in line:
m = re.search(r"(\d+)x(\d+)\+(\d+)\+(\d+)", line)
if m:
geom = (int(m.group(3)), int(m.group(4)), int(m.group(1)), int(m.group(2)))
if " primary " in line:
return geom
if fallback is None:
fallback = geom
return fallback
except Exception:
return None
def get_chrome_window_ids():
"""Return dict mapping integer window IDs to hex ID strings for open Chrome windows."""
ids = {}
try:
res = subprocess.run(["wmctrl", "-lx"], capture_output=True, text=True, check=True)
for line in res.stdout.strip().splitlines():
parts = line.split(None, 4)
if len(parts) >= 3 and "google-chrome" in parts[2].lower():
try:
ids[int(parts[0], 16)] = parts[0]
except ValueError:
pass
except Exception:
pass
return ids
def launch_chrome_profile(profile_name, desktop_num, urls=None):
"""Launches Google Chrome in a new window pinned to desktop_num on the primary monitor."""
print(f" Launching Chrome with {profile_name} profile...")
try:
before_ids = get_chrome_window_ids()
cmd = [
"google-chrome-stable",
f"--profile-directory={profile_name}",
"--new-window",
"--no-first-run",
"--disable-background-mode",
]
if urls:
cmd.extend(urls)
subprocess.Popen(
cmd,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
print(f" Chrome {profile_name} launched on desktop {desktop_num}")
new_win_id = None
start_time = time.time()
while time.time() - start_time < 10:
current_ids = get_chrome_window_ids()
diff = [win_str for k, win_str in current_ids.items() if k not in before_ids]
if diff:
new_win_id = diff[-1]
break
time.sleep(0.25)
time.sleep(LAUNCH_DELAY)
if new_win_id:
desktop_index = desktop_num - 1
subprocess.run(["wmctrl", "-i", "-r", new_win_id, "-t", str(desktop_index)], check=False)
geom = get_primary_monitor_geometry()
if geom is not None:
px, py, pw, ph = geom
subprocess.run(
["wmctrl", "-i", "-r", new_win_id, "-b", "remove,maximized_vert,maximized_horz,fullscreen"],
check=False,
)
target_x = px + 60
target_y = py + 60
target_w = max(800, pw - 120)
target_h = max(600, ph - 120)
subprocess.run(
["wmctrl", "-i", "-r", new_win_id, "-e", f"0,{target_x},{target_y},{target_w},{target_h}"],
check=False,
)
subprocess.run(
["wmctrl", "-i", "-r", new_win_id, "-b", "add,maximized_vert,maximized_horz"],
check=False,
)
print(f" β Chrome window {new_win_id} pinned to desktop {desktop_num} on primary monitor")
else:
print(f" β Timeout waiting for new Chrome window ({profile_name})")
except FileNotFoundError:
print(f" Error: google-chrome-stable not found")
except Exception as e:
print(f" Error launching Chrome {profile_name}: {e}")
[[[REPLACE]]]
Target: /home/mike/repos/nixos/autognome.py
[[[SEARCH]]]
if "--dry-run" in sys.argv:
print(json.dumps(DESKTOP_SETUP, indent=2))
total_terms = sum(len(d.get("terminals", [])) for d in DESKTOP_SETUP)
if any(arg in sys.argv for arg in ("--reset", "--restart")):
managed = find_managed_windows()
print(f"\nπ Dry run --reset: found {len(managed)} managed window(s) across desktops 1-7:", file=sys.stderr)
for w in managed:
print(f" - Desktop {w['desktop'] + 1} ({w['kind']}): {w['desc']} [{w['id']}]", file=sys.stderr)
print(f"\nβ
Dry run: {len(DESKTOP_SETUP)} desktops, {total_terms} terminals on the stack. No keys were harmed.", file=sys.stderr)
sys.exit(0)
[[[DIVIDER]]]
if "--dry-run" in sys.argv:
print(json.dumps(DESKTOP_SETUP, indent=2))
total_terms = sum(len(d.get("terminals", [])) for d in DESKTOP_SETUP)
print(f"\nβ
Dry run: {len(DESKTOP_SETUP)} desktops, {total_terms} terminals on the stack. No keys were harmed.", file=sys.stderr)
sys.exit(0)
[[[REPLACE]]]
Target: /home/mike/repos/nixos/autognome.py
[[[SEARCH]]]
# π THE RESET / RESTART INTERLOCK
# Closes all managed application and terminal windows across desktops 1-7
# before executing the launch sequence again. Running this before the
# bookmark projection ensures Chrome is provably dead for bookmarks_sync.py.
if any(arg in sys.argv for arg in ("--reset", "--restart")):
close_managed_windows()
[[[DIVIDER]]]
[[[REPLACE]]]
Ignition: none required for the compile-lane probes; init (or python3 /home/mike/repos/nixos/autognome.py) exercises the live window placement.
4: PROMPT (Use prompt or p to create prompt.md from copy):
(nix) pipulate $
Verify the AFTER receipts for --new-window and primary-monitor geometry placement in /home/mike/repos/nixos/autognome.py, and check /home/mike/repos/nixos/scripts/chrome_windows.py against the xrandr monitor layout to see if bms needs the same primary-monitor targeting.
5: DELIVERABLE (What done looks like):
--reset/--restartis removed fromautognome.py.- Desktops 3, 5, and 6 each spawn a distinct Chrome window (
--new-window), preventing Desktop 6βs YouTube Live URL from merging as a tab into Desktop 3βsDefaultwindow. - Each new Chrome window is unmaximized, moved into the primary monitorβs
xrandrgeometry, pinned to its virtual desktop (wmctrl -i -r <win_id> -t <desktop_index>), and re-maximized on the primary monitorβs 7-desktop ribbon.
MikeLev.in:
THE AI-EDIT METHOD
Same commands, run twice, one change between them. Where the readings differ is what the change did; the diff in the middle is the receipt.
1: BEFORE (PROBE):
On branch main
Your branch is up to date with 'origin/main'.
nothing to commit, working tree clean
GIT repo clean. Take BEFORE reading, make CHANGE, record AFTER diff.
(nix) pipulate $ xrandr --query | rg " connected "
rg -n "(--new-window|get_primary_monitor_geometry|get_chrome_window_ids|--reset|--restart)" /home/mike/repos/nixos/autognome.py
.venv/bin/python /home/mike/repos/nixos/autognome.py --dry-run | tail -n 5
HDMI-0 connected primary 2560x1440+0+0 (normal left inverted right x axis y axis) 597mm x 336mm
DP-5 connected 1200x1920+2560+0 left (normal left inverted right x axis y axis) 344mm x 215mm
30: python3 autognome.py --reset # Gracefully close managed windows before launch
978: if any(arg in sys.argv for arg in ("--reset", "--restart")):
980: print(f"\nπ Dry run --reset: found {len(managed)} managed window(s) across desktops 1-7:", file=sys.stderr)
1024: if any(arg in sys.argv for arg in ("--reset", "--restart")):
β
Dry run: 7 desktops, 3 terminals on the stack. No keys were harmed.
"windows": [],
"narration": "Desktop seven: The Stack, Jekyll Servers.",
"card": "Desktop: 7\nTHE STACK"
}
]
(nix) pipulate $
2: AFTER (NEXT CONTEXT):
# # Context 1
# context.txt
# ~/repos/nixos/autognome.py
# scripts/card.py
# # Context 2
# context.txt
# ~/repos/nixos/autognome.py
# scripts/card.py
# imports/voice_synthesis.py
# imports/ascii_displays.py
# foo_files.py
# ! rg -n "card\.py" /home/mike/repos/pipulate /home/mike/repos/nixos
# ! ls -la /home/mike/repos/nixos /home/mike/repos/nixos/scripts
# # Context 3
# context.txt
# ~/repos/nixos/autognome.py
# scripts/card.py
# imports/voice_synthesis.py
# imports/ascii_displays.py
# foo_files.py
# ! rg -n "def (synthesize_to_wav|play_wav|_resolve_play_cmd)" imports/voice_synthesis.py || echo "MISSING_METHODS"
# ! rg -n "ensure_voice|prep_speech|play_prepped_speech" /home/mike/repos/nixos/autognome.py || echo "MISSING_DOUBLE_BUFFER"
# ! .venv/bin/python /home/mike/repos/nixos/autognome.py --dry-run | tail -n 5
# Context 4
context.txt
~/repos/nixos/autognome.py
~/repos/nixos/scripts/chrome_windows.py
scripts/card.py
imports/voice_synthesis.py
imports/ascii_displays.py
foo_files.py
! xrandr --query | rg " connected "
! rg -n "(--new-window|get_primary_monitor_geometry|get_chrome_window_ids|--reset|--restart)" /home/mike/repos/nixos/autognome.py
! .venv/bin/python /home/mike/repos/nixos/autognome.py --dry-run | tail -n 5
3: CHANGE (PATCHES):
On branch main
Your branch is up to date with 'origin/main'.
nothing to commit, working tree clean
GIT repo clean. Take BEFORE reading, make CHANGE, record AFTER diff.
(nix) pipulate $ patch
(nix) pipulate $ app
β Warning: SEARCH block not found in '/home/mike/repos/nixos/autognome.py'. Skipping.
--- DIAGNOSTIC: First line of your SEARCH block ---
SEARCH repr : 'Usage:'
FILE nearest: 'Usage:'
--- FIRST DIVERGENCE (no blank-insensitive match; this is not a blank-line gap) ---
SEARCH line 2 : ' python3 autognome.py # Launch full 7-desktop orchestration'
FILE line 28 : ' python3 autognome.py # Launch full 7-desktop orchestration'
--- END FIRST DIVERGENCE ---
--- YOUR SUBMITTED SEARCH BLOCK (verbatim) ---
1: 'Usage:'
2: ' python3 autognome.py # Launch full 7-desktop orchestration'
3: ' python3 autognome.py --dry-run # Inspect computed DESKTOP_SETUP without actuating'
4: ' python3 autognome.py --reset # Gracefully close managed windows before launch'
5: '"""'
--- END SUBMITTED SEARCH BLOCK ---
β Warning: SEARCH block not found in '/home/mike/repos/nixos/autognome.py'. Skipping.
--- DIAGNOSTIC: First line of your SEARCH block ---
SEARCH repr : 'def launch_chrome_profile(profile_name, desktop_num, urls=None):'
FILE nearest: 'def launch_chrome_profile(profile_name, desktop_num, urls=None):'
--- FIRST DIVERGENCE (no blank-insensitive match; this is not a blank-line gap) ---
Every aligned line matches; the SEARCH block is a prefix of the
nearest window, or the window runs past the end of the file.
Extend or shorten the block so it names a unique region.
--- END FIRST DIVERGENCE ---
--- YOUR SUBMITTED SEARCH BLOCK (verbatim) ---
1: 'def launch_chrome_profile(profile_name, desktop_num, urls=None):'
2: ' """Launches Google Chrome with specified profile and optional startup URLs."""'
3: ' print(f" Launching Chrome with {profile_name} profile...")'
4: ' try:'
5: ' # Add --no-startup-window flag to prevent focus stealing'
6: ' cmd = ["google-chrome-stable", '
7: ' f"--profile-directory={profile_name}",'
8: ' "--no-first-run",'
9: ' "--disable-background-mode"]'
10: ' if urls:'
11: ' cmd.extend(urls)'
12: ' subprocess.Popen(cmd,'
13: ' stdout=subprocess.DEVNULL, '
14: ' stderr=subprocess.DEVNULL)'
15: ' print(f" Chrome {profile_name} launched on desktop {desktop_num}")'
16: ' '
17: ' # Wait for Chrome window to appear'
18: ' wait_for_window("google chrome", timeout=10)'
19: ' time.sleep(LAUNCH_DELAY) # Extra settling time'
20: ' '
21: ' except FileNotFoundError:'
22: ' print(f" Error: google-chrome-stable not found")'
23: ' except Exception as e:'
24: ' print(f" Error launching Chrome {profile_name}: {e}")'
--- END SUBMITTED SEARCH BLOCK ---
β
DETERMINISTIC PATCH APPLIED: Successfully mutated '/home/mike/repos/nixos/autognome.py'.
β
DETERMINISTIC PATCH APPLIED: Successfully mutated '/home/mike/repos/nixos/autognome.py'.
(nix) pipulate $ vim patch /home/mike/repos/nixos/autognome.py
(nix) pipulate $ d
(nix) pipulate $ n
(nix) nixos $ d
diff --git a/autognome.py b/autognome.py
index 6239d36..ee10479 100644
--- a/autognome.py
+++ b/autognome.py
@@ -25,9 +25,8 @@ Architecture & Mechanics:
- Declarative layout: DESKTOP_SETUP defines windows, apps, and tabbed terminals.
Usage:
- python3 autognome.py # Launch full 7-desktop orchestration
+ python3 autognome.py # Launch full 7-desktop orchestration
python3 autognome.py --dry-run # Inspect computed DESKTOP_SETUP without actuating
- python3 autognome.py --reset # Gracefully close managed windows before launch
"""
import subprocess
@@ -975,11 +974,6 @@ if __name__ == "__main__":
if "--dry-run" in sys.argv:
print(json.dumps(DESKTOP_SETUP, indent=2))
total_terms = sum(len(d.get("terminals", [])) for d in DESKTOP_SETUP)
- if any(arg in sys.argv for arg in ("--reset", "--restart")):
- managed = find_managed_windows()
- print(f"\nπ Dry run --reset: found {len(managed)} managed window(s) across desktops 1-7:", file=sys.stderr)
- for w in managed:
- print(f" - Desktop {w['desktop'] + 1} ({w['kind']}): {w['desc']} [{w['id']}]", file=sys.stderr)
print(f"\nβ
Dry run: {len(DESKTOP_SETUP)} desktops, {total_terms} terminals on the stack. No keys were harmed.", file=sys.stderr)
sys.exit(0)
@@ -1017,12 +1011,7 @@ if __name__ == "__main__":
print("Warning: wmctrl not available, cannot switch desktops")
exit(1)
- # π THE RESET / RESTART INTERLOCK
- # Closes all managed application and terminal windows across desktops 1-7
- # before executing the launch sequence again. Running this before the
- # bookmark projection ensures Chrome is provably dead for bookmarks_sync.py.
- if any(arg in sys.argv for arg in ("--reset", "--restart")):
- close_managed_windows()
+
# π THE BOOKMARK PROJECTION (bookmarks.nix, evaluated at call time -> Chrome).
# Runs BEFORE any Chrome launch below, the one moment Chrome is provably
(nix) nixos $ m
π Committing: chore: Update autognome.py documentation and comments
[main 747cc5f] chore: Update autognome.py documentation and comments
1 file changed, 2 insertions(+), 13 deletions(-)
(nix) nixos $ git push
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 48 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 338 bytes | 338.00 KiB/s, done.
Total 3 (delta 2), reused 0 (delta 0), pack-reused 0 (from 0)
remote: Resolving deltas: 100% (2/2), completed with 2 local objects.
To github.com:miklevin/nixos-config.git
511d493..747cc5f main -> main
(nix) nixos $
Ignition: often βexitβ, βnix developβ. Instructions may vary.
4: PROMPT:
Verify the AFTER receipts for βnew-window and primary-monitor geometry placement in /home/mike/repos/nixos/autognome.py, and check /home/mike/repos/nixos/scripts/chrome_windows.py against the xrandr monitor layout to see if bms needs the same primary-monitor targeting.
5: DELIVERABLE:
Okay, once again letβs see. And yes, I reboot my entire system every time to
test this and use init.
Nope!
The browsers all still target the external monitor. Itβs like nothing changed on that last pass. This is odd because this problem never occurred before we did our double buffering (if thatβs what we really call it but it was SUPER effective) speed-up. I totally want to keep the speed-up but working on these sorts of things with you after a first try that didnβt work I have been conditioned to expect a downward spiral of regression because of the kv-cache being poisoned with incorrect assumptions that get locked onto. Thatβs the signature here but you as a model are so much smarter than you and models in general used to be so Iβm going to stick it out for a turn or two more.
Orthogonal Bisection of Multi-Monitor Window Placement
Back up and think through what youβre missing. Use that whole orthogonal thing. What really controls where the Chrome windows are going to open? Is it a βwhere it was when last openedβ thing? I know Chrome has a lot of stickiness there. Maybe I change a setting because in the past I think I made it remember because of the desire to make it always open full-screen mode so maybe thereβs something Chrome-side responsible. And conversely this X11 stuff Iβm using to simulate ARexx, does that have screen-targeting? Is there βlanguage for thatβ under these protocols weβre using that knows about screen targeting? That sort of thing. And what ability do you have to conduct tests with my before and after system to pinpoint such things and purge ambiguity? Can we do a screen targeting test with bisection and not flounder?
Gemini 4 Argon High: Context verified.
TL;DR: Look at the 3: CHANGE receipt and the live rg receipt in this compile: **the Chrome window placement code never ran because apply.py refused Block 2 on launch_chrome_profile**. Four lines inside launch_chrome_profile in /home/mike/repos/nixos/autognome.py had trailing spaces on disk that the browserβs copy button stripped, so only the --reset removal landed in commit 747cc5f. Below is the orthogonal breakdown of the four layers that control multi-monitor placement on X11/GNOME, how we bisect them with one read-only probe, and a trailing-space-immune patch for both autognome.py and chrome_windows.py (so you can test Chrome placement in 5 seconds without rebooting).
1. Why Nothing Changed on the Last Pass (The Receipt)
In the Manifestβs live receipt:
! rg -n "(--new-window|get_primary_monitor_geometry|get_chrome_window_ids|--reset|--restart)" /home/mike/repos/nixos/autognome.pyreturnedNON-ZERO EXIT 1(no matches).- In
3: CHANGE,apply.pyrefused thelaunch_chrome_profileblock because lines 696, 703, 706, and 710 ofautognome.pyon disk contain trailing spaces that the web chat copy button strips. When you editedpatchinvim, only the--resetremoval landed (1 file changed, 2 insertions(+), 13 deletions(-)).
To make Car 1 below 100% immune to browser trailing-whitespace stripping, its [[[SEARCH]]] block anchors only on the first 4 lines of launch_chrome_profile (each ending in a non-space character: :, """, ), :).
2. Orthogonal Analysis: What Actually Controls Which Monitor Chrome Opens On?
Your xrandr receipt shows a single 3760Γ1920 X11 virtual root screen split across two monitors:
HDMI-0(Primary, Landscape):2560x1440+0+0(Xfrom0to2559,Yfrom0to1439) β holds the 7-desktop ribbon.DP-5(Secondary, Portrait):1200x1920+2560+0(Xfrom2560to3759,Yfrom0to1919).
Four orthogonal mechanisms determine which monitor a Chrome window targets:
LAYER MECHANISM HOW IT FORCES DP-5 (X >= 2560)
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
1. Chrome Preferences ~/.config/google-chrome/<Prof>/Preferences If a window was last closed or dragged onto
(Sticky Last-Position) -> browser.window_placement.{left,right} DP-5, Chrome saves left >= 2560 and asks
X11 to map there on the next launch.
2. Chrome Singleton IPC Launching Default twice without Desktop 6's Default launch hands its URL to
(Tab Merging) --new-window Desktop 3's already-open Default window as
a tab instead of spawning a new window.
3. GNOME / Mutter Policy workspaces-only-on-primary = true Only HDMI-0 (X < 2560) has 7 workspaces.
(Monitor vs. Workspace) + active mouse pointer monitor Any window at X >= 2560 sits on DP-5 outside
the 7-desktop ribbon, even after wmctrl -t!
4. EWMH / NetWM (wmctrl) _NET_WM_STATE_MAXIMIZED_{VERT,HORZ} Mutter ignores wmctrl -e 0,X,Y,W,H while a
("ARexx for X11") locks window geometry window is maximized. You MUST remove the
maximized state first, move X,Y into 0..2559,
pin desktop (-t), and re-maximize.
3. How We Bisect and Fix All Four Layers Without Rebooting
- The Probe (
1: BEFORE): Inspectswmctrl -lG(live windowX, Ycoordinates and desktop numbers) and readsbrowser.window_placementstraight out of~/.config/google-chrome/{Default,Profile 8}/Preferencesso we see whether Chrome savedleft >= 2560on disk. - Pre-Launch Preference Sanitizer + EWMH Unmaximize-Move-Maximize (
Car 1&Car 2):- Before spawning Chrome,
sanitize_chrome_preferences()rewritesbrowser.window_placementin the profileβsPreferencesfile ontoHDMI-0(left: 60, top: 60, right: 2500, bottom: 1380, work_area_left: 0, work_area_right: 2560) ifleft >= 2560. - Passes
--new-windowand--window-position=60,60. - After the new window maps, unmaximizes it (
-b remove,maximized_vert,maximized_horz,fullscreen), moves its(X, Y)ontoHDMI-0(-e 0,60,60,2440,1320), pins its virtual desktop (-t), and re-maximizes it (-b add,maximized_vert,maximized_horz).
- Before spawning Chrome,
- Fast 5-Second Test Without Rebooting (
Car 2βchrome_windows.py):chrome_windows.py(bms) is your standalone Chrome window quit/reopen harness. Updating it with the same--new-windowand primary-monitor placement lets you test Chrome window targeting anytime viapython3 /home/mike/repos/nixos/scripts/chrome_windows.py quit && python3 /home/mike/repos/nixos/scripts/chrome_windows.py reopenwithout rebooting.
5-Car Train
1: BEFORE (PROBES β read-only commands; the first lists every open Chrome windowβs desktop and X, Y geometry, the second reads Chromeβs saved window_placement coordinates from disk, and the third straddles the new placement functions across both files):
(nix) pipulate $
wmctrl -lG | rg -i "chrome" || echo "NO_CHROME_WINDOWS"
.venv/bin/python -c 'import json, pathlib; p = pathlib.Path("~/.config/google-chrome").expanduser(); [print(d, json.loads((p/d/"Preferences").read_text()).get("browser", {}).get("window_placement")) for d in ("Default", "Profile 8") if (p/d/"Preferences").exists()]'
rg -n "(--new-window|get_primary_monitor_geometry|sanitize_chrome_preferences)" /home/mike/repos/nixos/autognome.py /home/mike/repos/nixos/scripts/chrome_windows.py || echo "MISSING_PLACEMENT_FIX"
2: AFTER (NEXT CONTEXT β put in context.txt using context or x):
(nix) pipulate $
# Context 5
context.txt
~/repos/nixos/autognome.py
~/repos/nixos/scripts/chrome_windows.py
scripts/card.py
imports/voice_synthesis.py
imports/ascii_displays.py
foo_files.py
! wmctrl -lG | rg -i "chrome" || echo "NO_CHROME_WINDOWS"
! .venv/bin/python -c 'import json, pathlib; p = pathlib.Path("~/.config/google-chrome").expanduser(); [print(d, json.loads((p/d/"Preferences").read_text()).get("browser", {}).get("window_placement")) for d in ("Default", "Profile 8") if (p/d/"Preferences").exists()]'
! rg -n "(--new-window|get_primary_monitor_geometry|sanitize_chrome_preferences)" /home/mike/repos/nixos/autognome.py /home/mike/repos/nixos/scripts/chrome_windows.py || echo "MISSING_PLACEMENT_FIX"
3: CHANGE (PATCHES β 2 cars in /home/mike/repos/nixos):
Car 1 (/home/mike/repos/nixos/autognome.py β anchored on the 4 trailing-space-free header lines of launch_chrome_profile; adds get_primary_monitor_geometry(), sanitize_chrome_preferences(), get_chrome_window_ids(), --new-window, and EWMH primary-monitor placement):
(nix) pipulate $
Target: /home/mike/repos/nixos/autognome.py
[[[SEARCH]]]
def launch_chrome_profile(profile_name, desktop_num, urls=None):
"""Launches Google Chrome with specified profile and optional startup URLs."""
print(f" Launching Chrome with {profile_name} profile...")
try:
[[[DIVIDER]]]
def get_primary_monitor_geometry():
"""Return (x, y, w, h) of the primary monitor from xrandr, or (0, 0, 2560, 1440)."""
if shutil.which("xrandr"):
try:
import re
res = subprocess.run(["xrandr", "--query"], capture_output=True, text=True, check=True)
fallback = None
for line in res.stdout.splitlines():
if " connected " in line:
m = re.search(r"(\d+)x(\d+)\+(\d+)\+(\d+)", line)
if m:
geom = (int(m.group(3)), int(m.group(4)), int(m.group(1)), int(m.group(2)))
if " primary " in line:
return geom
if fallback is None:
fallback = geom
if fallback is not None:
return fallback
except Exception:
pass
return (0, 0, 2560, 1440)
def sanitize_chrome_preferences(profile_name, geom):
"""Clamp saved browser.window_placement into the primary monitor before launch."""
px, py, pw, ph = geom
pref_path = os.path.expanduser(f"~/.config/google-chrome/{profile_name}/Preferences")
if not os.path.isfile(pref_path):
return
try:
with open(pref_path, "r", encoding="utf-8") as f:
data = json.load(f)
browser = data.setdefault("browser", {})
wp = browser.setdefault("window_placement", {})
left = wp.get("left", 0)
top = wp.get("top", 0)
if left < px or left >= px + pw or top < py or top >= py + ph:
wp["left"] = px + 60
wp["top"] = py + 60
wp["right"] = px + max(800, pw - 60)
wp["bottom"] = py + max(600, ph - 60)
wp["work_area_left"] = px
wp["work_area_top"] = py
wp["work_area_right"] = px + pw
wp["work_area_bottom"] = py + ph
wp["maximized"] = True
with open(pref_path, "w", encoding="utf-8") as f:
json.dump(data, f)
except Exception as e:
print(f" β Could not sanitize Chrome Preferences for {profile_name}: {e}")
def get_chrome_window_ids():
"""Return dict mapping integer window IDs to hex ID strings for open Chrome windows."""
ids = {}
try:
res = subprocess.run(["wmctrl", "-lx"], capture_output=True, text=True, check=True)
for line in res.stdout.strip().splitlines():
parts = line.split(None, 4)
if len(parts) >= 3 and "google-chrome" in parts[2].lower():
try:
ids[int(parts[0], 16)] = parts[0]
except ValueError:
pass
except Exception:
pass
return ids
def launch_chrome_profile(profile_name, desktop_num, urls=None):
"""Launches Google Chrome in a new window pinned to desktop_num on the primary monitor."""
print(f" Launching Chrome with {profile_name} profile...")
try:
geom = get_primary_monitor_geometry()
px, py, pw, ph = geom
sanitize_chrome_preferences(profile_name, geom)
if shutil.which("xdotool"):
subprocess.run(
["xdotool", "mousemove", str(px + pw // 2), str(py + ph // 2)],
check=False,
)
before_ids = get_chrome_window_ids()
cmd = [
"google-chrome-stable",
f"--profile-directory={profile_name}",
"--new-window",
f"--window-position={px + 60},{py + 60}",
"--no-first-run",
"--disable-background-mode",
]
if urls:
cmd.extend(urls)
subprocess.Popen(
cmd,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
print(f" Chrome {profile_name} launched on desktop {desktop_num}")
new_win_id = None
start_time = time.time()
while time.time() - start_time < 10:
current_ids = get_chrome_window_ids()
diff = [win_str for k, win_str in current_ids.items() if k not in before_ids]
if diff:
new_win_id = diff[-1]
break
time.sleep(0.25)
time.sleep(LAUNCH_DELAY)
if new_win_id:
desktop_index = desktop_num - 1
subprocess.run(
["wmctrl", "-i", "-r", new_win_id, "-b", "remove,maximized_vert,maximized_horz,fullscreen"],
check=False,
)
target_x = px + 60
target_y = py + 60
target_w = max(800, pw - 120)
target_h = max(600, ph - 120)
subprocess.run(
["wmctrl", "-i", "-r", new_win_id, "-e", f"0,{target_x},{target_y},{target_w},{target_h}"],
check=False,
)
subprocess.run(["wmctrl", "-i", "-r", new_win_id, "-t", str(desktop_index)], check=False)
subprocess.run(
["wmctrl", "-i", "-r", new_win_id, "-b", "add,maximized_vert,maximized_horz"],
check=False,
)
print(f" β Chrome window {new_win_id} pinned to desktop {desktop_num} on primary monitor ({px},{py})")
else:
print(f" β Timeout waiting for new Chrome window ({profile_name})")
except FileNotFoundError:
print(" Error: google-chrome-stable not found")
except Exception as e:
print(f" Error launching Chrome {profile_name}: {e}")
def _legacy_launch_chrome_profile_unused(profile_name, desktop_num, urls=None):
try:
[[[REPLACE]]]
Car 2 (/home/mike/repos/nixos/scripts/chrome_windows.py β adds --new-window, --window-position, and primary-monitor unmaximize -> move -> desktop pin -> maximize to chrome_windows.py so bms / reopen also targets HDMI-0):
(nix) pipulate $
Target: /home/mike/repos/nixos/scripts/chrome_windows.py
[[[SEARCH]]]
def move_window(window_id, desktop):
"""wmctrl -i -r ID -t N, N zero-based. True on exit 0."""
try:
run = subprocess.run(["wmctrl", "-i", "-r", window_id, "-t", str(desktop)],
capture_output=True, text=True, timeout=5)
except (OSError, subprocess.SubprocessError):
return False
return run.returncode == 0
# --- actuators --------------------------------------------------------------
def chrome_binary():
for name in CHROME_NAMES:
found = shutil.which(name)
if found:
return found
return None
def launch(profile, user_data_dir, binary):
flags = ["--profile-directory=%s" % profile, "--no-first-run", "--disable-background-mode"]
[[[DIVIDER]]]
def primary_monitor_geometry():
"""Return (x, y, w, h) of the primary monitor from xrandr, or (0, 0, 2560, 1440)."""
if shutil.which("xrandr"):
try:
import re
res = subprocess.run(["xrandr", "--query"], capture_output=True, text=True, timeout=5)
fallback = None
for line in res.stdout.splitlines():
if " connected " in line:
m = re.search(r"(\d+)x(\d+)\+(\d+)\+(\d+)", line)
if m:
geom = (int(m.group(3)), int(m.group(4)), int(m.group(1)), int(m.group(2)))
if " primary " in line:
return geom
if fallback is None:
fallback = geom
if fallback is not None:
return fallback
except Exception:
pass
return (0, 0, 2560, 1440)
def move_window(window_id, desktop):
"""Unmaximize, move onto primary monitor, pin to 0-based desktop, and re-maximize."""
px, py, pw, ph = primary_monitor_geometry()
tx, ty = px + 60, py + 60
tw, th = max(800, pw - 120), max(600, ph - 120)
try:
subprocess.run(
["wmctrl", "-i", "-r", window_id, "-b", "remove,maximized_vert,maximized_horz,fullscreen"],
capture_output=True, text=True, timeout=5,
)
subprocess.run(
["wmctrl", "-i", "-r", window_id, "-e", "0,%d,%d,%d,%d" % (tx, ty, tw, th)],
capture_output=True, text=True, timeout=5,
)
run = subprocess.run(
["wmctrl", "-i", "-r", window_id, "-t", str(desktop)],
capture_output=True, text=True, timeout=5,
)
subprocess.run(
["wmctrl", "-i", "-r", window_id, "-b", "add,maximized_vert,maximized_horz"],
capture_output=True, text=True, timeout=5,
)
except (OSError, subprocess.SubprocessError):
return False
return run.returncode == 0
# --- actuators --------------------------------------------------------------
def chrome_binary():
for name in CHROME_NAMES:
found = shutil.which(name)
if found:
return found
return None
def launch(profile, user_data_dir, binary):
px, py, _, _ = primary_monitor_geometry()
flags = [
"--profile-directory=%s" % profile,
"--new-window",
"--window-position=%d,%d" % (px + 60, py + 60),
"--no-first-run",
"--disable-background-mode",
]
[[[REPLACE]]]
Ignition: after applying Car 1 and Car 2, you can immediately test the 3-window Chrome placement on the primary monitor without rebooting by running bms (or python3 /home/mike/repos/nixos/scripts/chrome_windows.py quit && python3 /home/mike/repos/nixos/scripts/chrome_windows.py reopen).
4: PROMPT (Use prompt or p to create prompt.md from copy):
(nix) pipulate $
Verify the AFTER receipts for the primary-monitor Chrome window placement in autognome.py and chrome_windows.py, check the wmctrl -lG coordinates and Preferences window_placement readings, and confirm whether any further adjustments are needed.
5: DELIVERABLE (What done looks like):
- Both
autognome.pyandchrome_windows.pypass--new-windowand--window-position, sanitize ChromeβsPreferences(browser.window_placement), and execute the 4-step EWMH sequence (unmaximize -> move intoHDMI-00..2559-> move to target virtual desktop -> re-maximize). wmctrl -lGshows all three Chrome windows on Desktops 3, 5, and 6 withXcoordinates insideHDMI-0(0 <= X < 2560) rather thanDP-5(X >= 2560).
MikeLev.in:
THE AI-EDIT METHOD
Same commands, run twice, one change between them. Where the readings differ is what the change did; the diff in the middle is the receipt.
1: BEFORE (PROBE):
On branch main
Your branch is up to date with 'origin/main'.
nothing to commit, working tree clean
GIT repo clean. Take BEFORE reading, make CHANGE, record AFTER diff.
(nix) pipulate $ wmctrl -lG | rg -i "chrome" || echo "NO_CHROME_WINDOWS"
.venv/bin/python -c 'import json, pathlib; p = pathlib.Path("~/.config/google-chrome").expanduser(); [print(d, json.loads((p/d/"Preferences").read_text()).get("browser", {}).get("window_placement")) for d in ("Default", "Profile 8") if (p/d/"Preferences").exists()]'
rg -n "(--new-window|get_primary_monitor_geometry|sanitize_chrome_preferences)" /home/mike/repos/nixos/autognome.py /home/mike/repos/nixos/scripts/chrome_windows.py || echo "MISSING_PLACEMENT_FIX"
0x00e00028 2 -40 30 2600 1448 nixos 1 --- START: SEARCH --- def _prose_only_growth(old: str, new: str) -> bool - Google Gemini - Google Chrome
Default {'bottom': 1463, 'left': -20, 'maximized': False, 'right': 2580, 'top': 15, 'work_area_bottom': 1440, 'work_area_left': 0, 'work_area_right': 2560, 'work_area_top': 32}
Profile 8 {'bottom': 1920, 'left': 2560, 'maximized': True, 'right': 3760, 'top': 0, 'work_area_bottom': 1920, 'work_area_left': 2560, 'work_area_right': 3760, 'work_area_top': 0}
MISSING_PLACEMENT_FIX
(nix) pipulate $
2: AFTER (NEXT CONTEXT):
# # Context 1
# context.txt
# ~/repos/nixos/autognome.py
# scripts/card.py
# # Context 2
# context.txt
# ~/repos/nixos/autognome.py
# scripts/card.py
# imports/voice_synthesis.py
# imports/ascii_displays.py
# foo_files.py
# ! rg -n "card\.py" /home/mike/repos/pipulate /home/mike/repos/nixos
# ! ls -la /home/mike/repos/nixos /home/mike/repos/nixos/scripts
# # Context 3
# context.txt
# ~/repos/nixos/autognome.py
# scripts/card.py
# imports/voice_synthesis.py
# imports/ascii_displays.py
# foo_files.py
# ! rg -n "def (synthesize_to_wav|play_wav|_resolve_play_cmd)" imports/voice_synthesis.py || echo "MISSING_METHODS"
# ! rg -n "ensure_voice|prep_speech|play_prepped_speech" /home/mike/repos/nixos/autognome.py || echo "MISSING_DOUBLE_BUFFER"
# ! .venv/bin/python /home/mike/repos/nixos/autognome.py --dry-run | tail -n 5
# # Context 4
# context.txt
# ~/repos/nixos/autognome.py
# ~/repos/nixos/scripts/chrome_windows.py
# scripts/card.py
# imports/voice_synthesis.py
# imports/ascii_displays.py
# foo_files.py
# ! xrandr --query | rg " connected "
# ! rg -n "(--new-window|get_primary_monitor_geometry|get_chrome_window_ids|--reset|--restart)" /home/mike/repos/nixos/autognome.py
# ! .venv/bin/python /home/mike/repos/nixos/autognome.py --dry-run | tail -n 5
# Context 5
context.txt
~/repos/nixos/autognome.py
~/repos/nixos/scripts/chrome_windows.py
scripts/card.py
imports/voice_synthesis.py
imports/ascii_displays.py
foo_files.py
! wmctrl -lG | rg -i "chrome" || echo "NO_CHROME_WINDOWS"
! .venv/bin/python -c 'import json, pathlib; p = pathlib.Path("~/.config/google-chrome").expanduser(); [print(d, json.loads((p/d/"Preferences").read_text()).get("browser", {}).get("window_placement")) for d in ("Default", "Profile 8") if (p/d/"Preferences").exists()]'
! rg -n "(--new-window|get_primary_monitor_geometry|sanitize_chrome_preferences)" /home/mike/repos/nixos/autognome.py /home/mike/repos/nixos/scripts/chrome_windows.py || echo "MISSING_PLACEMENT_FIX"
3: CHANGE (PATCHES):
On branch main
Your branch is up to date with 'origin/main'.
nothing to commit, working tree clean
GIT repo clean. Take BEFORE reading, make CHANGE, record AFTER diff.
(nix) pipulate $ patch
(nix) pipulate $ app
β
DETERMINISTIC PATCH APPLIED: Successfully mutated '/home/mike/repos/nixos/autognome.py'.
(nix) pipulate $ d
(nix) pipulate $ n
(nix) nixos $ d
diff --git a/autognome.py b/autognome.py
index ee10479..44bcbcf 100644
--- a/autognome.py
+++ b/autognome.py
@@ -769,9 +769,143 @@ def launch_terminal(terminal_config):
print(f" Error launching terminal: {e}")
+def get_primary_monitor_geometry():
+ """Return (x, y, w, h) of the primary monitor from xrandr, or (0, 0, 2560, 1440)."""
+ if shutil.which("xrandr"):
+ try:
+ import re
+ res = subprocess.run(["xrandr", "--query"], capture_output=True, text=True, check=True)
+ fallback = None
+ for line in res.stdout.splitlines():
+ if " connected " in line:
+ m = re.search(r"(\d+)x(\d+)\+(\d+)\+(\d+)", line)
+ if m:
+ geom = (int(m.group(3)), int(m.group(4)), int(m.group(1)), int(m.group(2)))
+ if " primary " in line:
+ return geom
+ if fallback is None:
+ fallback = geom
+ if fallback is not None:
+ return fallback
+ except Exception:
+ pass
+ return (0, 0, 2560, 1440)
+
+
+def sanitize_chrome_preferences(profile_name, geom):
+ """Clamp saved browser.window_placement into the primary monitor before launch."""
+ px, py, pw, ph = geom
+ pref_path = os.path.expanduser(f"~/.config/google-chrome/{profile_name}/Preferences")
+ if not os.path.isfile(pref_path):
+ return
+ try:
+ with open(pref_path, "r", encoding="utf-8") as f:
+ data = json.load(f)
+ browser = data.setdefault("browser", {})
+ wp = browser.setdefault("window_placement", {})
+ left = wp.get("left", 0)
+ top = wp.get("top", 0)
+ if left < px or left >= px + pw or top < py or top >= py + ph:
+ wp["left"] = px + 60
+ wp["top"] = py + 60
+ wp["right"] = px + max(800, pw - 60)
+ wp["bottom"] = py + max(600, ph - 60)
+ wp["work_area_left"] = px
+ wp["work_area_top"] = py
+ wp["work_area_right"] = px + pw
+ wp["work_area_bottom"] = py + ph
+ wp["maximized"] = True
+ with open(pref_path, "w", encoding="utf-8") as f:
+ json.dump(data, f)
+ except Exception as e:
+ print(f" β Could not sanitize Chrome Preferences for {profile_name}: {e}")
+
+
+def get_chrome_window_ids():
+ """Return dict mapping integer window IDs to hex ID strings for open Chrome windows."""
+ ids = {}
+ try:
+ res = subprocess.run(["wmctrl", "-lx"], capture_output=True, text=True, check=True)
+ for line in res.stdout.strip().splitlines():
+ parts = line.split(None, 4)
+ if len(parts) >= 3 and "google-chrome" in parts[2].lower():
+ try:
+ ids[int(parts[0], 16)] = parts[0]
+ except ValueError:
+ pass
+ except Exception:
+ pass
+ return ids
+
+
def launch_chrome_profile(profile_name, desktop_num, urls=None):
- """Launches Google Chrome with specified profile and optional startup URLs."""
+ """Launches Google Chrome in a new window pinned to desktop_num on the primary monitor."""
print(f" Launching Chrome with {profile_name} profile...")
+ try:
+ geom = get_primary_monitor_geometry()
+ px, py, pw, ph = geom
+ sanitize_chrome_preferences(profile_name, geom)
+ if shutil.which("xdotool"):
+ subprocess.run(
+ ["xdotool", "mousemove", str(px + pw // 2), str(py + ph // 2)],
+ check=False,
+ )
+ before_ids = get_chrome_window_ids()
+ cmd = [
+ "google-chrome-stable",
+ f"--profile-directory={profile_name}",
+ "--new-window",
+ f"--window-position={px + 60},{py + 60}",
+ "--no-first-run",
+ "--disable-background-mode",
+ ]
+ if urls:
+ cmd.extend(urls)
+ subprocess.Popen(
+ cmd,
+ stdout=subprocess.DEVNULL,
+ stderr=subprocess.DEVNULL,
+ )
+ print(f" Chrome {profile_name} launched on desktop {desktop_num}")
+ new_win_id = None
+ start_time = time.time()
+ while time.time() - start_time < 10:
+ current_ids = get_chrome_window_ids()
+ diff = [win_str for k, win_str in current_ids.items() if k not in before_ids]
+ if diff:
+ new_win_id = diff[-1]
+ break
+ time.sleep(0.25)
+ time.sleep(LAUNCH_DELAY)
+ if new_win_id:
+ desktop_index = desktop_num - 1
+ subprocess.run(
+ ["wmctrl", "-i", "-r", new_win_id, "-b", "remove,maximized_vert,maximized_horz,fullscreen"],
+ check=False,
+ )
+ target_x = px + 60
+ target_y = py + 60
+ target_w = max(800, pw - 120)
+ target_h = max(600, ph - 120)
+ subprocess.run(
+ ["wmctrl", "-i", "-r", new_win_id, "-e", f"0,{target_x},{target_y},{target_w},{target_h}"],
+ check=False,
+ )
+ subprocess.run(["wmctrl", "-i", "-r", new_win_id, "-t", str(desktop_index)], check=False)
+ subprocess.run(
+ ["wmctrl", "-i", "-r", new_win_id, "-b", "add,maximized_vert,maximized_horz"],
+ check=False,
+ )
+ print(f" β Chrome window {new_win_id} pinned to desktop {desktop_num} on primary monitor ({px},{py})")
+ else:
+ print(f" β Timeout waiting for new Chrome window ({profile_name})")
+ except FileNotFoundError:
+ print(" Error: google-chrome-stable not found")
+ except Exception as e:
+ print(f" Error launching Chrome {profile_name}: {e}")
+
+
+def _legacy_launch_chrome_profile_unused(profile_name, desktop_num, urls=None):
try:
# Add --no-startup-window flag to prevent focus stealing
cmd = ["google-chrome-stable",
(nix) nixos $ m
π Committing: feat: Add primary monitor geometry detection and Chrome preference sanitization
[main 06ec088] feat: Add primary monitor geometry detection and Chrome preference sanitization
1 file changed, 135 insertions(+), 1 deletion(-)
(nix) nixos $ git push
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 48 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 1.88 KiB | 1.88 MiB/s, done.
Total 3 (delta 2), reused 0 (delta 0), pack-reused 0 (from 0)
remote: Resolving deltas: 100% (2/2), completed with 2 local objects.
To github.com:miklevin/nixos-config.git
747cc5f..06ec088 main -> main
(nix) nixos $
Ignition: often βexitβ, βnix developβ. Instructions may vary.
4: PROMPT:
Verify the AFTER receipts for the primary-monitor Chrome window placement in autognome.py and chrome_windows.py, check the wmctrl -lG coordinates and Preferences window_placement readings, and confirm whether any further adjustments are needed.
5: DELIVERABLE:
Letβs take a lookβ¦
Yep! You nailed it. Perfect. Was that because of my botched hand-edit?
And what is the offender of edits like that not landing the first time? Iβd like to get my files in shape so that happens less or adjust apply.py likewise. Itβs really an 80/20-rule thing. I know Iβm even stricter than Gauthierβs Aider and I like that but Iβm willing to loosen it the wee littlest bit for a big win.
Also on the browser where we open YouTube we need that silenced so after you put the URL in there we need to hit βMβ for Mute while that browser window is active (or equivalent).
And finally the pause before beginning to type into the terminal in Screen 2 is way too long like by a few seconds too long. I know thereβs timing nuance issues there. Is there any βas soon as the terminal is readyβ type optimizations along the line of what you did with the Piper TTS speed-up (that was awesome)?
Pre-Spawning Terminals Behind the Card and Normalizing Whitespace
Gemini 4 Argon High: Context verified.
TL;DR:
- Why the last pass worked: Look at the
BEFOREvs.AFTERreceipts in this compile β before Car 1 landed,Profile 8/Preferenceshad'left': 2560, 'right': 3760(DP-5) saved on disk. Once Car 1 landed (06ec088),sanitize_chrome_preferences+--new-window+wmctrlplaced all three Chrome windows on Desktops 3, 5, and 6 atX=0, Y=64, W=2560, H=1408(HDMI-0). - The 80/20
apply.pyfix (Car 1): Turn 3βs patch failed for two whitespace reasons: (a) four lines inautognome.pyhad trailing spaces on disk (including' 'on blank lines) that the browserβs copy button stripped, andFIRST DIVERGENCEhid them because' '.lstrip() == ''.lstrip(); (b)Usage:had 15 spaces before#instead of 16. Car 1 adds_rstrip_line_spans()toapply.py: if exact byte-match is0, it tries a fallback that strips only trailing spaces/tabs per line and collapses2+spaces before#comments while keeping leading indentation, line order, and single-match uniqueness (len(spans) == 1) 100% strict. - Eliminating the Screen 2 terminal pause + Muting YouTube on Desktop 6 (Car 2 β
autognome.py):- Double-buffered terminal & Chrome spawning: Previously,
autognome.pywaited ~3 seconds for Piper to finish speaking and the Alacritty card to close before launchinggnome-terminal, then waited another ~1.5s (wait_for_window+TERMINAL_LOAD_DELAY+TERMINAL_TYPING_DELAY) before typingnix develop. Now,spawn_terminal_window()andlaunch_chrome_profile()launch while the Always-On-Top Alacritty card is on screen and Piper is speaking. By the time the card vanishes,gnome-terminalandbashare already warm and waiting, sohydrate_terminal_tabs()focuses the new window ID and types instantly. - YouTube Mute (
mute_chrome_window): Desktop 6 ("mute": True) pre-launches Chrome during the Desktop 6 card narration, then once the card dismisses, waits for"youtube"in the windowβs X11 title, focuses the window, and sendsxdotool key m.
- Double-buffered terminal & Chrome spawning: Previously,
Why Turn 3 Refused (and How Car 1 Fixes It Without Weakening Safety)
FAILURE MODE IN WEB CHAT COPY-PASTE WHAT HAPPENED BEFORE CAR 1 (apply.py 80/20 FALLBACK)
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
1. Trailing spaces on code lines content.count() == 0; Strips only trailing ' \t' per line;
e.g. `cmd = ["google-chrome-stable", β£` FIRST DIVERGENCE hid it leading indentation stays 100% exact
2. Whitespace-only blank lines `' '.lstrip() == ''.lstrip()` Normalized to '' on both sides
e.g. `' '` vs `''` printed "Every aligned line matches"
3. Visual column padding before `#` 15 spaces vs 16 spaces before `#` Collapses ` {2,}#` -> ` #` after
e.g. `autognome.py # Launch` refused the whole block verifying exact leading indentation
5-Car Train
1: BEFORE (PROBES β read-only commands; checks apply.py for _rstrip_line_spans, checks autognome.py for spawn_terminal_window, hydrate_terminal_tabs, and mute_chrome_window, checks chrome_windows.py for primary_monitor_geometry, and gates --dry-run):
(nix) pipulate $
rg -n "def _rstrip_line_spans" apply.py || echo "MISSING_RSTRIP_FALLBACK"
rg -n "def (spawn_terminal_window|hydrate_terminal_tabs|mute_chrome_window)" /home/mike/repos/nixos/autognome.py || echo "MISSING_AUTOGNOME_FAST_TERM_MUTE"
rg -n "def primary_monitor_geometry" /home/mike/repos/nixos/scripts/chrome_windows.py || echo "MISSING_CHROME_WINDOWS_GEOM"
.venv/bin/python /home/mike/repos/nixos/autognome.py --dry-run | tail -n 6
2: AFTER (NEXT CONTEXT β put in context.txt using context or x):
(nix) pipulate $
# Context 6
context.txt
~/repos/nixos/autognome.py
~/repos/nixos/scripts/chrome_windows.py
scripts/card.py
imports/voice_synthesis.py
imports/ascii_displays.py
foo_files.py
! rg -n "def _rstrip_line_spans" apply.py || echo "MISSING_RSTRIP_FALLBACK"
! rg -n "def (spawn_terminal_window|hydrate_terminal_tabs|mute_chrome_window)" /home/mike/repos/nixos/autognome.py || echo "MISSING_AUTOGNOME_FAST_TERM_MUTE"
! rg -n "def primary_monitor_geometry" /home/mike/repos/nixos/scripts/chrome_windows.py || echo "MISSING_CHROME_WINDOWS_GEOM"
! .venv/bin/python /home/mike/repos/nixos/autognome.py --dry-run | tail -n 6
3: CHANGE (PATCHES β Car 1 on apply.py in pipulate; Car 2 on /home/mike/repos/nixos/autognome.py; Car 3 on /home/mike/repos/nixos/scripts/chrome_windows.py):
Car 1 (apply.py β add _rstrip_line_spans unique fallback for trailing whitespace / whitespace-only blank lines / pre-# comment padding, and surface whitespace divergences in FIRST DIVERGENCE):
(nix) pipulate $
Target: apply.py
[[[SEARCH]]]
def _blank_gap_spans(content: str, search_block: str):
"""0-based inclusive (start, end) spans of content matching search_block
once every empty or whitespace-only line is dropped from both sides."""
search_lines = [line for line in search_block.split('\n') if line.strip()]
if not search_lines:
return []
kept = [(i, line) for i, line in enumerate(content.split('\n')) if line.strip()]
width = len(search_lines)
spans = []
for k in range(len(kept) - width + 1):
if all(kept[k + j][1] == search_lines[j] for j in range(width)):
spans.append((kept[k][0], kept[k + width - 1][0]))
return spans
[[[DIVIDER]]]
def _normalize_line_ws(line: str) -> str:
"""Strip trailing spaces/tabs and collapse 2+ spaces before inline '#' comments,
while preserving exact leading indentation."""
stripped = line.rstrip(' \t')
if not stripped:
return ''
indent_len = len(stripped) - len(stripped.lstrip(' \t'))
indent = stripped[:indent_len]
body = re.sub(r' {2,}#', ' #', stripped[indent_len:])
return indent + body
def _rstrip_line_spans(content: str, search_block: str):
"""Return (char_start, char_end) spans in content where search_block matches
line-for-line under _normalize_line_ws (exact leading indent + line count)."""
s_lines = [_normalize_line_ws(ln) for ln in search_block.split('\n')]
if not s_lines:
return []
c_lines = content.split('\n')
n_s, n_c = len(s_lines), len(c_lines)
if n_s > n_c:
return []
c_norm = [_normalize_line_ws(ln) for ln in c_lines]
offsets = []
pos = 0
for ln in c_lines:
offsets.append(pos)
pos += len(ln) + 1
spans = []
for i in range(n_c - n_s + 1):
if c_norm[i:i + n_s] == s_lines:
start_char = offsets[i]
end_char = offsets[i + n_s - 1] + len(c_lines[i + n_s - 1])
spans.append((start_char, end_char))
return spans
def _blank_gap_spans(content: str, search_block: str):
"""0-based inclusive (start, end) spans of content matching search_block
once every empty or whitespace-only line is dropped from both sides."""
search_lines = [line for line in search_block.split('\n') if line.strip()]
if not search_lines:
return []
kept = [(i, line) for i, line in enumerate(content.split('\n')) if line.strip()]
width = len(search_lines)
spans = []
for k in range(len(kept) - width + 1):
if all(kept[k + j][1] == search_lines[j] for j in range(width)):
spans.append((kept[k][0], kept[k + width - 1][0]))
return spans
[[[REPLACE]]]
Target: apply.py
[[[SEARCH]]]
# 2. NORMALIZE TARGET FILE WHITESPACE
content = content.replace('\xa0', ' ').replace('\r\n', '\n')
search_block = search_block.replace('\xa0', ' ').replace('\r\n', '\n')
replace_block = replace_block.replace('\xa0', ' ').replace('\r\n', '\n')
[[[DIVIDER]]]
# 2. NORMALIZE TARGET FILE WHITESPACE
content = content.replace('\xa0', ' ').replace('\r\n', '\n')
search_block = search_block.replace('\xa0', ' ').replace('\r\n', '\n')
replace_block = replace_block.replace('\xa0', ' ').replace('\r\n', '\n')
if content.count(search_block) == 0:
rspans = _rstrip_line_spans(content, search_block)
if len(rspans) == 1:
search_block = content[rspans[0][0]:rspans[0][1]]
print(f"βΉ Trailing-whitespace-normalized match succeeded uniquely in '{filename}'.")
[[[REPLACE]]]
Target: apply.py
[[[SEARCH]]]
if content_lines[file_idx].lstrip() != search_lines[j].lstrip():
divergence = (j, file_idx)
break
[[[DIVIDER]]]
if content_lines[file_idx].lstrip() != search_lines[j].lstrip():
divergence = (j, file_idx)
break
if divergence is None:
for j in range(search_len):
file_idx = best_idx + j
if file_idx < len(content_lines) and content_lines[file_idx] != search_lines[j]:
divergence = (j, file_idx)
break
[[[REPLACE]]]
Car 2 (/home/mike/repos/nixos/autognome.py β pre-spawn terminals and Chrome behind the Alacritty title card while Piper is speaking so Screen 2 types immediately when the card vanishes, add "mute": True and mute_chrome_window() for Desktop 6 YouTube Live, and clean up _legacy_launch_chrome_profile_unused):
(nix) pipulate $
Target: /home/mike/repos/nixos/autognome.py
[[[SEARCH]]]
TERMINAL_TYPING_DELAY = 0.5 # Seconds between terminal commands
NIX_DEVELOP_DELAY = 3.5 # Seconds to wait for 'nix develop' to complete
FONT_ADJUST_DELAY = 0.1 # Seconds between font size adjustments
TAB_CREATE_DELAY = 1.0 # Seconds to wait after creating new tab
INTER_DESKTOP_DELAY = 2.0 # Seconds to pause between desktops to let things settle
[[[DIVIDER]]]
TERMINAL_TYPING_DELAY = 0.15 # Seconds between terminal commands
NIX_DEVELOP_DELAY = 3.5 # Seconds to wait for 'nix develop' to complete
FONT_ADJUST_DELAY = 0.1 # Seconds between font size adjustments
TAB_CREATE_DELAY = 0.8 # Seconds to wait after creating new tab
INTER_DESKTOP_DELAY = 0.5 # Seconds to pause between desktops to let things settle
[[[REPLACE]]]
Target: /home/mike/repos/nixos/autognome.py
[[[SEARCH]]]
{ # Desktop 6 (Index 5) - Chrome Default Profile (YouTube Live)
"chrome": "Default",
"urls": ["https://youtube.com/MikeLevin/live"],
"apps": [],
"terminals": [],
},
[[[DIVIDER]]]
{ # Desktop 6 (Index 5) - Chrome Default Profile (YouTube Live)
"chrome": "Default",
"urls": ["https://youtube.com/MikeLevin/live"],
"mute": True,
"apps": [],
"terminals": [],
},
[[[REPLACE]]]
Target: /home/mike/repos/nixos/autognome.py
[[[SEARCH]]]
def launch_terminal(terminal_config):
"""Launches a terminal, optionally running a command and setting a title."""
cmd_list = [TERMINAL_APP]
desc = "default terminal"
term_title = terminal_config.get("title")
tabbed_setup = terminal_config.get("tabbed_setup", False)
# Add maximize option for gnome-terminal
if TERMINAL_APP == "gnome-terminal":
cmd_list.append("--maximize")
if term_title:
if TERMINAL_APP == "gnome-terminal":
cmd_list.extend(["--title", term_title])
desc += f" (title: '{term_title}')"
# NEW: Unified Tabbed Setup Logic
if tabbed_setup:
tabs = terminal_config.get("tabs", [])
if not tabs:
print(" β 'tabbed_setup' is true but no 'tabs' list provided.")
subprocess.Popen(cmd_list)
return
print(f" Launch tabbed terminal: {term_title or 'Multi-Tab'}")
try:
# Launch the initial terminal
subprocess.Popen(cmd_list)
# Wait for terminal to appear
wait_for_window("terminal", timeout=5)
time.sleep(TERMINAL_LOAD_DELAY)
for i, tab in enumerate(tabs):
tab_dir = tab.get("dir", ".")
# 'cmd' is the activation command (e.g., nix develop)
tab_cmd = tab.get("cmd", "exec bash")
size_down = tab.get("size_down", 0)
# NEW: 'work_subdir' allows cd-ing AFTER the environment loads
work_subdir = tab.get("work_subdir", None)
if i > 0: # If not the first tab, create a new one
print(f" Creating Tab {i+1}...")
subprocess.run(["xdotool", "key", "ctrl+shift+t"], check=True)
time.sleep(TAB_CREATE_DELAY)
else:
print(f" Setting up Tab 1...")
# 1. Change to Repo Root (The Flake Location)
subprocess.run(["xdotool", "type", f"cd {tab_dir}"], check=True)
subprocess.run(["xdotool", "key", "Return"], check=True)
time.sleep(TERMINAL_TYPING_DELAY)
# 2. Run Activation Command (The Flake)
# We assume interactive commands like 'nix develop' or 'gemini'
final_cmd = tab_cmd
subprocess.run(["xdotool", "type", final_cmd], check=True)
subprocess.run(["xdotool", "key", "Return"], check=True)
# 3. The "Clean Entry" Protocol
if "nix develop" in tab_cmd:
print(f" β³ Waiting for Nix environment to hydrate...")
time.sleep(NIX_DEVELOP_DELAY)
# Clear the "garbage" (flake welcome messages)
print(f" π§Ή Clearing screen...")
subprocess.run(["xdotool", "key", "ctrl+l"], check=True) # Ctrl+L is safer than typing 'clear'
time.sleep(0.5)
# Re-orient the user
if work_subdir:
print(f" π Navigating to subdir: {work_subdir}")
subprocess.run(["xdotool", "type", f"cd {work_subdir}"], check=True)
subprocess.run(["xdotool", "key", "Return"], check=True)
# Announce location (vibe check)
subprocess.run(["xdotool", "type", "unset PIPULATE_VOICE && pwd && echo 'β¨ Environment Ready'"], check=True)
subprocess.run(["xdotool", "key", "Return"], check=True)
# 4. Adjust font size
if size_down > 0:
print(f" Adjusting text size in Tab {i+1}...")
for _ in range(size_down):
subprocess.run(["xdotool", "key", "ctrl+minus"], check=True)
time.sleep(FONT_ADJUST_DELAY)
# Switch back to the first tab
if len(tabs) > 1:
print(" Switching back to first tab.")
for _ in range(len(tabs) - 1):
subprocess.run(["xdotool", "key", "ctrl+Page_Up"], check=True)
time.sleep(0.1)
print(f" β Tabbed terminal '{term_title}' setup completed")
except Exception as e:
print(f" β Unexpected error during tabbed setup: {e}")
return
# --- Fallback for simple, non-tabbed terminals ---
term_command = terminal_config.get("command")
if term_command:
if TERMINAL_APP == "gnome-terminal":
cmd_list.extend(["--", "bash", "-c", term_command + "; exec bash"])
desc = f"terminal with command: '{term_command}'"
print(f" Launching {desc}")
try:
subprocess.Popen(cmd_list)
wait_for_window("terminal", timeout=5)
time.sleep(LAUNCH_DELAY)
except Exception as e:
print(f" Error launching terminal: {e}")
[[[DIVIDER]]]
def get_terminal_window_ids():
"""Return dict mapping integer window IDs to hex ID strings for open terminal windows."""
ids = {}
try:
res = subprocess.run(["wmctrl", "-lx"], capture_output=True, text=True, check=True)
for line in res.stdout.strip().splitlines():
parts = line.split(None, 4)
if len(parts) >= 3 and ("gnome-terminal" in parts[2].lower() or TERMINAL_APP.lower() in parts[2].lower()):
try:
ids[int(parts[0], 16)] = parts[0]
except ValueError:
pass
except Exception:
pass
return ids
def spawn_terminal_window(terminal_config, keep_card_above=False):
"""Spawn the terminal window during card narration so bash is warm before typing begins."""
cmd_list = [TERMINAL_APP]
desc = "default terminal"
term_title = terminal_config.get("title")
tabbed_setup = terminal_config.get("tabbed_setup", False)
if TERMINAL_APP == "gnome-terminal":
cmd_list.append("--maximize")
if term_title:
if TERMINAL_APP == "gnome-terminal":
cmd_list.extend(["--title", term_title])
desc += f" (title: '{term_title}')"
if not tabbed_setup:
term_command = terminal_config.get("command")
if term_command and TERMINAL_APP == "gnome-terminal":
cmd_list.extend(["--", "bash", "-c", term_command + "; exec bash"])
desc = f"terminal with command: '{term_command}'"
print(f" Pre-spawning {term_title or desc}...")
before_ids = get_terminal_window_ids()
try:
subprocess.Popen(cmd_list)
except Exception as e:
print(f" Error spawning terminal: {e}")
return (terminal_config, None)
new_win_id = None
start_time = time.time()
while time.time() - start_time < 5.0:
current_ids = get_terminal_window_ids()
diff = [win_str for k, win_str in current_ids.items() if k not in before_ids]
if diff:
new_win_id = diff[-1]
break
time.sleep(0.1)
if keep_card_above:
subprocess.run(
["wmctrl", "-x", "-r", "conjure_window_overlay", "-b", "add,above"],
capture_output=True,
check=False,
)
return (terminal_config, new_win_id)
def hydrate_terminal_tabs(terminal_config, term_win_id=None):
"""Type tab commands into an already-spawned terminal immediately after card dismissal."""
if term_win_id:
subprocess.run(["wmctrl", "-i", "-a", term_win_id], check=False)
time.sleep(0.1)
if not terminal_config.get("tabbed_setup", False):
return
tabs = terminal_config.get("tabs", [])
if not tabs:
return
term_title = terminal_config.get("title") or "Multi-Tab"
try:
for i, tab in enumerate(tabs):
tab_dir = tab.get("dir", ".")
tab_cmd = tab.get("cmd", "exec bash")
size_down = tab.get("size_down", 0)
work_subdir = tab.get("work_subdir", None)
if i > 0:
print(f" Creating Tab {i+1}...")
subprocess.run(["xdotool", "key", "ctrl+shift+t"], check=True)
time.sleep(TAB_CREATE_DELAY)
else:
print(" Setting up Tab 1...")
subprocess.run(["xdotool", "type", f"cd {tab_dir}"], check=True)
subprocess.run(["xdotool", "key", "Return"], check=True)
time.sleep(TERMINAL_TYPING_DELAY)
subprocess.run(["xdotool", "type", tab_cmd], check=True)
subprocess.run(["xdotool", "key", "Return"], check=True)
if "nix develop" in tab_cmd:
print(" β³ Waiting for Nix environment to hydrate...")
time.sleep(NIX_DEVELOP_DELAY)
print(" π§Ή Clearing screen...")
subprocess.run(["xdotool", "key", "ctrl+l"], check=True)
time.sleep(0.3)
if work_subdir:
print(f" π Navigating to subdir: {work_subdir}")
subprocess.run(["xdotool", "type", f"cd {work_subdir}"], check=True)
subprocess.run(["xdotool", "key", "Return"], check=True)
subprocess.run(["xdotool", "type", "unset PIPULATE_VOICE && pwd && echo 'β¨ Environment Ready'"], check=True)
subprocess.run(["xdotool", "key", "Return"], check=True)
if size_down > 0:
print(f" Adjusting text size in Tab {i+1}...")
for _ in range(size_down):
subprocess.run(["xdotool", "key", "ctrl+minus"], check=True)
time.sleep(FONT_ADJUST_DELAY)
if len(tabs) > 1:
print(" Switching back to first tab.")
for _ in range(len(tabs) - 1):
subprocess.run(["xdotool", "key", "ctrl+Page_Up"], check=True)
time.sleep(0.1)
print(f" β Tabbed terminal '{term_title}' setup completed")
except Exception as e:
print(f" β Unexpected error during tabbed setup: {e}")
def launch_terminal(terminal_config):
"""Launches a terminal and hydrates its tabs."""
cfg, win_id = spawn_terminal_window(terminal_config, keep_card_above=False)
time.sleep(TERMINAL_LOAD_DELAY)
hydrate_terminal_tabs(cfg, win_id)
[[[REPLACE]]]
Target: /home/mike/repos/nixos/autognome.py
[[[SEARCH]]]
def launch_chrome_profile(profile_name, desktop_num, urls=None):
"""Launches Google Chrome in a new window pinned to desktop_num on the primary monitor."""
print(f" Launching Chrome with {profile_name} profile...")
try:
geom = get_primary_monitor_geometry()
px, py, pw, ph = geom
sanitize_chrome_preferences(profile_name, geom)
if shutil.which("xdotool"):
subprocess.run(
["xdotool", "mousemove", str(px + pw // 2), str(py + ph // 2)],
check=False,
)
before_ids = get_chrome_window_ids()
cmd = [
"google-chrome-stable",
f"--profile-directory={profile_name}",
"--new-window",
f"--window-position={px + 60},{py + 60}",
"--no-first-run",
"--disable-background-mode",
]
if urls:
cmd.extend(urls)
subprocess.Popen(
cmd,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
print(f" Chrome {profile_name} launched on desktop {desktop_num}")
new_win_id = None
start_time = time.time()
while time.time() - start_time < 10:
current_ids = get_chrome_window_ids()
diff = [win_str for k, win_str in current_ids.items() if k not in before_ids]
if diff:
new_win_id = diff[-1]
break
time.sleep(0.25)
time.sleep(LAUNCH_DELAY)
if new_win_id:
desktop_index = desktop_num - 1
subprocess.run(
["wmctrl", "-i", "-r", new_win_id, "-b", "remove,maximized_vert,maximized_horz,fullscreen"],
check=False,
)
target_x = px + 60
target_y = py + 60
target_w = max(800, pw - 120)
target_h = max(600, ph - 120)
subprocess.run(
["wmctrl", "-i", "-r", new_win_id, "-e", f"0,{target_x},{target_y},{target_w},{target_h}"],
check=False,
)
subprocess.run(["wmctrl", "-i", "-r", new_win_id, "-t", str(desktop_index)], check=False)
subprocess.run(
["wmctrl", "-i", "-r", new_win_id, "-b", "add,maximized_vert,maximized_horz"],
check=False,
)
print(f" β Chrome window {new_win_id} pinned to desktop {desktop_num} on primary monitor ({px},{py})")
else:
print(f" β Timeout waiting for new Chrome window ({profile_name})")
except FileNotFoundError:
print(" Error: google-chrome-stable not found")
except Exception as e:
print(f" Error launching Chrome {profile_name}: {e}")
def _legacy_launch_chrome_profile_unused(profile_name, desktop_num, urls=None):
try:
# Add --no-startup-window flag to prevent focus stealing
cmd = ["google-chrome-stable",
f"--profile-directory={profile_name}",
"--no-first-run",
"--disable-background-mode"]
if urls:
cmd.extend(urls)
subprocess.Popen(cmd,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL)
print(f" Chrome {profile_name} launched on desktop {desktop_num}")
# Wait for Chrome window to appear
wait_for_window("google chrome", timeout=10)
time.sleep(LAUNCH_DELAY) # Extra settling time
except FileNotFoundError:
print(f" Error: google-chrome-stable not found")
except Exception as e:
print(f" Error launching Chrome {profile_name}: {e}")
[[[DIVIDER]]]
def launch_chrome_profile(profile_name, desktop_num, urls=None, keep_card_above=False):
"""Launches Google Chrome in a new window pinned to desktop_num on the primary monitor."""
print(f" Launching Chrome with {profile_name} profile...")
new_win_id = None
try:
geom = get_primary_monitor_geometry()
px, py, pw, ph = geom
sanitize_chrome_preferences(profile_name, geom)
if shutil.which("xdotool"):
subprocess.run(
["xdotool", "mousemove", str(px + pw // 2), str(py + ph // 2)],
check=False,
)
before_ids = get_chrome_window_ids()
cmd = [
"google-chrome-stable",
f"--profile-directory={profile_name}",
"--new-window",
f"--window-position={px + 60},{py + 60}",
"--no-first-run",
"--disable-background-mode",
]
if urls:
cmd.extend(urls)
subprocess.Popen(
cmd,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
print(f" Chrome {profile_name} launched on desktop {desktop_num}")
start_time = time.time()
while time.time() - start_time < 10:
current_ids = get_chrome_window_ids()
diff = [win_str for k, win_str in current_ids.items() if k not in before_ids]
if diff:
new_win_id = diff[-1]
break
time.sleep(0.15)
time.sleep(LAUNCH_DELAY)
if new_win_id:
desktop_index = desktop_num - 1
subprocess.run(
["wmctrl", "-i", "-r", new_win_id, "-b", "remove,maximized_vert,maximized_horz,fullscreen"],
check=False,
)
target_x = px + 60
target_y = py + 60
target_w = max(800, pw - 120)
target_h = max(600, ph - 120)
subprocess.run(
["wmctrl", "-i", "-r", new_win_id, "-e", f"0,{target_x},{target_y},{target_w},{target_h}"],
check=False,
)
subprocess.run(["wmctrl", "-i", "-r", new_win_id, "-t", str(desktop_index)], check=False)
subprocess.run(
["wmctrl", "-i", "-r", new_win_id, "-b", "add,maximized_vert,maximized_horz"],
check=False,
)
if keep_card_above:
subprocess.run(
["wmctrl", "-x", "-r", "conjure_window_overlay", "-b", "add,above"],
capture_output=True,
check=False,
)
print(f" β Chrome window {new_win_id} pinned to desktop {desktop_num} on primary monitor ({px},{py})")
else:
print(f" β Timeout waiting for new Chrome window ({profile_name})")
except FileNotFoundError:
print(" Error: google-chrome-stable not found")
except Exception as e:
print(f" Error launching Chrome {profile_name}: {e}")
return new_win_id
def mute_chrome_window(win_id, timeout=6.0):
"""Wait for YouTube to hydrate in win_id, focus the window, and press 'm' to mute."""
if not win_id or not shutil.which("xdotool"):
return
print(f" β³ Waiting for YouTube player in window {win_id} before muting...")
start_time = time.time()
target_int = int(win_id, 16)
while time.time() - start_time < timeout:
try:
res = subprocess.run(["wmctrl", "-l"], capture_output=True, text=True, check=True)
matched = False
for line in res.stdout.splitlines():
parts = line.split(None, 3)
if len(parts) >= 4:
try:
if int(parts[0], 16) == target_int and "youtube" in parts[3].lower():
matched = True
break
except ValueError:
pass
if matched:
break
except Exception:
pass
time.sleep(0.25)
time.sleep(1.2)
subprocess.run(["wmctrl", "-i", "-a", win_id], check=False)
time.sleep(0.2)
subprocess.run(["xdotool", "key", "m"], check=False)
print(f" π Sent 'm' (mute) to YouTube window {win_id}")
[[[REPLACE]]]
Target: /home/mike/repos/nixos/autognome.py
[[[SEARCH]]]
narration = desktop_config.get("narration")
wav_path = prep_speech(narration) if narration else None
card_title = desktop_config.get("card")
card_handle = show_card(card_title) if card_title else None
speech_proc = play_prepped_speech(wav_path, narration) if narration else None
if speech_proc:
wait_for_speech(speech_proc)
elif card_handle:
time.sleep(1.5)
if card_handle:
dismiss_card(card_handle)
# Launch Chrome profile if configured
chrome_profile = desktop_config.get("chrome")
if chrome_profile:
launch_chrome_profile(chrome_profile, i + 1, desktop_config.get("urls"))
# Launch regular apps
for app_cmd in desktop_config.get("apps", []):
launch_application(app_cmd)
# Launch terminals
for term_cfg in desktop_config.get("terminals", []):
launch_terminal(term_cfg)
[[[DIVIDER]]]
narration = desktop_config.get("narration")
wav_path = prep_speech(narration) if narration else None
card_title = desktop_config.get("card")
card_handle = show_card(card_title) if card_title else None
speech_proc = play_prepped_speech(wav_path, narration) if narration else None
# Pre-spawn this desktop's Chrome/terminal windows underneath the raised
# title card while Piper is speaking so they are warm when the card exits.
time.sleep(0.2)
chrome_profile = desktop_config.get("chrome")
chrome_win_id = None
if chrome_profile:
chrome_win_id = launch_chrome_profile(
chrome_profile,
i + 1,
desktop_config.get("urls"),
keep_card_above=bool(card_handle),
)
for app_cmd in desktop_config.get("apps", []):
launch_application(app_cmd)
spawned_terminals = []
for term_cfg in desktop_config.get("terminals", []):
spawned_terminals.append(
spawn_terminal_window(term_cfg, keep_card_above=bool(card_handle))
)
if speech_proc:
wait_for_speech(speech_proc)
elif card_handle:
time.sleep(1.5)
if card_handle:
dismiss_card(card_handle)
if chrome_win_id and desktop_config.get("mute"):
mute_chrome_window(chrome_win_id)
for term_cfg, term_win_id in spawned_terminals:
hydrate_terminal_tabs(term_cfg, term_win_id)
[[[REPLACE]]]
Car 3 (/home/mike/repos/nixos/scripts/chrome_windows.py β brings bms / chrome_windows.py into parity with autognome.py for primary-monitor geometry and --new-window):
(nix) pipulate $
Target: /home/mike/repos/nixos/scripts/chrome_windows.py
[[[SEARCH]]]
def move_window(window_id, desktop):
"""wmctrl -i -r ID -t N, N zero-based. True on exit 0."""
try:
run = subprocess.run(["wmctrl", "-i", "-r", window_id, "-t", str(desktop)],
capture_output=True, text=True, timeout=5)
except (OSError, subprocess.SubprocessError):
return False
return run.returncode == 0
# --- actuators --------------------------------------------------------------
def chrome_binary():
for name in CHROME_NAMES:
found = shutil.which(name)
if found:
return found
return None
def launch(profile, user_data_dir, binary):
flags = ["--profile-directory=%s" % profile, "--no-first-run", "--disable-background-mode"]
[[[DIVIDER]]]
def primary_monitor_geometry():
"""Return (x, y, w, h) of the primary monitor from xrandr, or (0, 0, 2560, 1440)."""
if shutil.which("xrandr"):
try:
import re
res = subprocess.run(["xrandr", "--query"], capture_output=True, text=True, timeout=5)
fallback = None
for line in res.stdout.splitlines():
if " connected " in line:
m = re.search(r"(\d+)x(\d+)\+(\d+)\+(\d+)", line)
if m:
geom = (int(m.group(3)), int(m.group(4)), int(m.group(1)), int(m.group(2)))
if " primary " in line:
return geom
if fallback is None:
fallback = geom
if fallback is not None:
return fallback
except Exception:
pass
return (0, 0, 2560, 1440)
def move_window(window_id, desktop):
"""Unmaximize, move onto primary monitor, pin to 0-based desktop, and re-maximize."""
px, py, pw, ph = primary_monitor_geometry()
tx, ty = px + 60, py + 60
tw, th = max(800, pw - 120), max(600, ph - 120)
try:
subprocess.run(
["wmctrl", "-i", "-r", window_id, "-b", "remove,maximized_vert,maximized_horz,fullscreen"],
capture_output=True, text=True, timeout=5,
)
subprocess.run(
["wmctrl", "-i", "-r", window_id, "-e", "0,%d,%d,%d,%d" % (tx, ty, tw, th)],
capture_output=True, text=True, timeout=5,
)
run = subprocess.run(
["wmctrl", "-i", "-r", window_id, "-t", str(desktop)],
capture_output=True, text=True, timeout=5,
)
subprocess.run(
["wmctrl", "-i", "-r", window_id, "-b", "add,maximized_vert,maximized_horz"],
capture_output=True, text=True, timeout=5,
)
except (OSError, subprocess.SubprocessError):
return False
return run.returncode == 0
# --- actuators --------------------------------------------------------------
def chrome_binary():
for name in CHROME_NAMES:
found = shutil.which(name)
if found:
return found
return None
def launch(profile, user_data_dir, binary):
px, py, _, _ = primary_monitor_geometry()
flags = [
"--profile-directory=%s" % profile,
"--new-window",
"--window-position=%d,%d" % (px + 60, py + 60),
"--no-first-run",
"--disable-background-mode",
]
[[[REPLACE]]]
Ignition: apply Car 1 in pipulate first (so apply.pyβs new trailing-whitespace normalization is live immediately for Car 2 and Car 3), then apply Car 2 and Car 3.
4: PROMPT (Use prompt or p to create prompt.md from copy):
(nix) pipulate $
Verify the AFTER receipts for apply.py's _rstrip_line_spans fallback, autognome.py's pre-spawned terminal hydration and YouTube mute_chrome_window, and chrome_windows.py's primary_monitor_geometry.
5: DELIVERABLE (What done looks like):
apply.pykeeps strict leading indentation and unique-match guarantees while automatically normalizing trailing spaces, whitespace-only blank lines, and comment-column padding.- On Desktops 2, 4, and 7,
gnome-terminalspawns underneath the raised Alacritty card while Piper is speaking soxdotoolbegins typing the instant the card vanishes. - On Desktop 6, YouTube Live loads behind the card while Piper is speaking, and
mute_chrome_window()focuses the window and hitsmto silence the stream once the YouTube title hydrates.
MikeLev.in:
THE AI-EDIT METHOD
Same commands, run twice, one change between them. Where the readings differ is what the change did; the diff in the middle is the receipt.
1: BEFORE (PROBE):
On branch main
Your branch is up to date with 'origin/main'.
nothing to commit, working tree clean
GIT repo clean. Take BEFORE reading, make CHANGE, record AFTER diff.
(nix) pipulate $ rg -n "def _rstrip_line_spans" apply.py || echo "MISSING_RSTRIP_FALLBACK"
rg -n "def (spawn_terminal_window|hydrate_terminal_tabs|mute_chrome_window)" /home/mike/repos/nixos/autognome.py || echo "MISSING_AUTOGNOME_FAST_TERM_MUTE"
rg -n "def primary_monitor_geometry" /home/mike/repos/nixos/scripts/chrome_windows.py || echo "MISSING_CHROME_WINDOWS_GEOM"
.venv/bin/python /home/mike/repos/nixos/autognome.py --dry-run | tail -n 6
MISSING_RSTRIP_FALLBACK
MISSING_AUTOGNOME_FAST_TERM_MUTE
MISSING_CHROME_WINDOWS_GEOM
β
Dry run: 7 desktops, 3 terminals on the stack. No keys were harmed.
],
"windows": [],
"narration": "Desktop seven: The Stack, Jekyll Servers.",
"card": "Desktop: 7\nTHE STACK"
}
]
(nix) pipulate $
2: AFTER (NEXT CONTEXT):
# # Context 1
# context.txt
# ~/repos/nixos/autognome.py
# scripts/card.py
# # Context 2
# context.txt
# ~/repos/nixos/autognome.py
# scripts/card.py
# imports/voice_synthesis.py
# imports/ascii_displays.py
# foo_files.py
# ! rg -n "card\.py" /home/mike/repos/pipulate /home/mike/repos/nixos
# ! ls -la /home/mike/repos/nixos /home/mike/repos/nixos/scripts
# # Context 3
# context.txt
# ~/repos/nixos/autognome.py
# scripts/card.py
# imports/voice_synthesis.py
# imports/ascii_displays.py
# foo_files.py
# ! rg -n "def (synthesize_to_wav|play_wav|_resolve_play_cmd)" imports/voice_synthesis.py || echo "MISSING_METHODS"
# ! rg -n "ensure_voice|prep_speech|play_prepped_speech" /home/mike/repos/nixos/autognome.py || echo "MISSING_DOUBLE_BUFFER"
# ! .venv/bin/python /home/mike/repos/nixos/autognome.py --dry-run | tail -n 5
# # Context 4
# context.txt
# ~/repos/nixos/autognome.py
# ~/repos/nixos/scripts/chrome_windows.py
# scripts/card.py
# imports/voice_synthesis.py
# imports/ascii_displays.py
# foo_files.py
# ! xrandr --query | rg " connected "
# ! rg -n "(--new-window|get_primary_monitor_geometry|get_chrome_window_ids|--reset|--restart)" /home/mike/repos/nixos/autognome.py
# ! .venv/bin/python /home/mike/repos/nixos/autognome.py --dry-run | tail -n 5
# # Context 5
# context.txt
# ~/repos/nixos/autognome.py
# ~/repos/nixos/scripts/chrome_windows.py
# scripts/card.py
# imports/voice_synthesis.py
# imports/ascii_displays.py
# foo_files.py
# ! wmctrl -lG | rg -i "chrome" || echo "NO_CHROME_WINDOWS"
# ! .venv/bin/python -c 'import json, pathlib; p = pathlib.Path("~/.config/google-chrome").expanduser(); [print(d, json.loads((p/d/"Preferences").read_text()).get("browser", {}).get("window_placement")) for d in ("Default", "Profile 8") if (p/d/"Preferences").exists()]'
# ! rg -n "(--new-window|get_primary_monitor_geometry|sanitize_chrome_preferences)" /home/mike/repos/nixos/autognome.py /home/mike/repos/nixos/scripts/chrome_windows.py || echo "MISSING_PLACEMENT_FIX"
# Context 6
context.txt
~/repos/nixos/autognome.py
~/repos/nixos/scripts/chrome_windows.py
scripts/card.py
imports/voice_synthesis.py
imports/ascii_displays.py
foo_files.py
! rg -n "def _rstrip_line_spans" apply.py || echo "MISSING_RSTRIP_FALLBACK"
! rg -n "def (spawn_terminal_window|hydrate_terminal_tabs|mute_chrome_window)" /home/mike/repos/nixos/autognome.py || echo "MISSING_AUTOGNOME_FAST_TERM_MUTE"
! rg -n "def primary_monitor_geometry" /home/mike/repos/nixos/scripts/chrome_windows.py || echo "MISSING_CHROME_WINDOWS_GEOM"
! .venv/bin/python /home/mike/repos/nixos/autognome.py --dry-run | tail -n 6
3: CHANGE (PATCHES):
On branch main
Your branch is up to date with 'origin/main'.
nothing to commit, working tree clean
GIT repo clean. Take BEFORE reading, make CHANGE, record AFTER diff.
(nix) pipulate $ patch
(nix) pipulate $ app
β
DETERMINISTIC PATCH APPLIED: Successfully mutated 'apply.py'.
β
DETERMINISTIC PATCH APPLIED: Successfully mutated 'apply.py'.
β
DETERMINISTIC PATCH APPLIED: Successfully mutated 'apply.py'.
(nix) pipulate $ d
diff --git a/apply.py b/apply.py
index fc1bc4de..036dfb5f 100644
--- a/apply.py
+++ b/apply.py
@@ -127,6 +127,43 @@ def _refuse_prose_growth(filename: str, verb: str) -> None:
# sides repr'd. Witnessed by straddle: a four-line SEARCH differing only on
# line 3, run against a throwaway file before and after, named nothing before
# and named line 3 after, with the file unchanged in both worlds.
+def _normalize_line_ws(line: str) -> str:
+ """Strip trailing spaces/tabs and collapse 2+ spaces before inline '#' comments,
+ while preserving exact leading indentation."""
+ stripped = line.rstrip(' \t')
+ if not stripped:
+ return ''
+ indent_len = len(stripped) - len(stripped.lstrip(' \t'))
+ indent = stripped[:indent_len]
+ body = re.sub(r' {2,}#', ' #', stripped[indent_len:])
+ return indent + body
+
+
+def _rstrip_line_spans(content: str, search_block: str):
+ """Return (char_start, char_end) spans in content where search_block matches
+ line-for-line under _normalize_line_ws (exact leading indent + line count)."""
+ s_lines = [_normalize_line_ws(ln) for ln in search_block.split('\n')]
+ if not s_lines:
+ return []
+ c_lines = content.split('\n')
+ n_s, n_c = len(s_lines), len(c_lines)
+ if n_s > n_c:
+ return []
+ c_norm = [_normalize_line_ws(ln) for ln in c_lines]
+ offsets = []
+ pos = 0
+ for ln in c_lines:
+ offsets.append(pos)
+ pos += len(ln) + 1
+ spans = []
+ for i in range(n_c - n_s + 1):
+ if c_norm[i:i + n_s] == s_lines:
+ start_char = offsets[i]
+ end_char = offsets[i + n_s - 1] + len(c_lines[i + n_s - 1])
+ spans.append((start_char, end_char))
+ return spans
+
+
def _blank_gap_spans(content: str, search_block: str):
"""0-based inclusive (start, end) spans of content matching search_block
once every empty or whitespace-only line is dropped from both sides."""
@@ -384,6 +421,11 @@ def apply_search_replace_patch(payload: str) -> bool:
content = content.replace('\xa0', ' ').replace('\r\n', '\n')
search_block = search_block.replace('\xa0', ' ').replace('\r\n', '\n')
replace_block = replace_block.replace('\xa0', ' ').replace('\r\n', '\n')
+ if content.count(search_block) == 0:
+ rspans = _rstrip_line_spans(content, search_block)
+ if len(rspans) == 1:
+ search_block = content[rspans[0][0]:rspans[0][1]]
+ print(f"βΉ Trailing-whitespace-normalized match succeeded uniquely in '{filename}'.")
# The Exact Match Invariant
# A SUBSTRING COUNT, NOT A LINE MATCH, and that is worth naming here
@@ -510,6 +552,12 @@ def apply_search_replace_patch(payload: str) -> bool:
if content_lines[file_idx].lstrip() != search_lines[j].lstrip():
divergence = (j, file_idx)
break
+ if divergence is None:
+ for j in range(search_len):
+ file_idx = best_idx + j
+ if file_idx < len(content_lines) and content_lines[file_idx] != search_lines[j]:
+ divergence = (j, file_idx)
+ break
print("\n--- FIRST DIVERGENCE (no blank-insensitive match; this is not a blank-line gap) ---")
if divergence is None:
print(" Every aligned line matches; the SEARCH block is a prefix of the")
(nix) pipulate $ m
π Committing: feat(apply): Normalize line whitespace for search replacement
[main 13ae7840] feat(apply): Normalize line whitespace for search replacement
1 file changed, 48 insertions(+)
(nix) pipulate $ patch
(nix) pipulate $ app
β
DETERMINISTIC PATCH APPLIED: Successfully mutated '/home/mike/repos/nixos/autognome.py'.
β
DETERMINISTIC PATCH APPLIED: Successfully mutated '/home/mike/repos/nixos/autognome.py'.
β
DETERMINISTIC PATCH APPLIED: Successfully mutated '/home/mike/repos/nixos/autognome.py'.
β Warning: SEARCH block not found in '/home/mike/repos/nixos/autognome.py'. Skipping.
--- DIAGNOSTIC: First line of your SEARCH block ---
SEARCH repr : 'def launch_chrome_profile(profile_name, desktop_num, urls=None):'
FILE nearest: 'def launch_chrome_profile(profile_name, desktop_num, urls=None):'
--- FIRST DIVERGENCE (no blank-insensitive match; this is not a blank-line gap) ---
SEARCH line 78 : ' stdout=subprocess.DEVNULL, '
FILE line 941 : ' stdout=subprocess.DEVNULL, '
--- END FIRST DIVERGENCE ---
--- YOUR SUBMITTED SEARCH BLOCK (verbatim) ---
1: 'def launch_chrome_profile(profile_name, desktop_num, urls=None):'
2: ' """Launches Google Chrome in a new window pinned to desktop_num on the primary monitor."""'
3: ' print(f" Launching Chrome with {profile_name} profile...")'
4: ' try:'
5: ' geom = get_primary_monitor_geometry()'
6: ' px, py, pw, ph = geom'
7: ' sanitize_chrome_preferences(profile_name, geom)'
8: ' if shutil.which("xdotool"):'
9: ' subprocess.run('
10: ' ["xdotool", "mousemove", str(px + pw // 2), str(py + ph // 2)],'
11: ' check=False,'
12: ' )'
13: ' before_ids = get_chrome_window_ids()'
14: ' cmd = ['
15: ' "google-chrome-stable",'
16: ' f"--profile-directory={profile_name}",'
17: ' "--new-window",'
18: ' f"--window-position={px + 60},{py + 60}",'
19: ' "--no-first-run",'
20: ' "--disable-background-mode",'
21: ' ]'
22: ' if urls:'
23: ' cmd.extend(urls)'
24: ' subprocess.Popen('
25: ' cmd,'
26: ' stdout=subprocess.DEVNULL,'
27: ' stderr=subprocess.DEVNULL,'
28: ' )'
29: ' print(f" Chrome {profile_name} launched on desktop {desktop_num}")'
30: ' new_win_id = None'
31: ' start_time = time.time()'
32: ' while time.time() - start_time < 10:'
33: ' current_ids = get_chrome_window_ids()'
34: ' diff = [win_str for k, win_str in current_ids.items() if k not in before_ids]'
35: ' if diff:'
36: ' new_win_id = diff[-1]'
37: ' break'
38: ' time.sleep(0.25)'
39: ' time.sleep(LAUNCH_DELAY)'
40: ' if new_win_id:'
41: ' desktop_index = desktop_num - 1'
42: ' subprocess.run('
43: ' ["wmctrl", "-i", "-r", new_win_id, "-b", "remove,maximized_vert,maximized_horz,fullscreen"],'
44: ' check=False,'
45: ' )'
46: ' target_x = px + 60'
47: ' target_y = py + 60'
48: ' target_w = max(800, pw - 120)'
49: ' target_h = max(600, ph - 120)'
50: ' subprocess.run('
51: ' ["wmctrl", "-i", "-r", new_win_id, "-e", f"0,{target_x},{target_y},{target_w},{target_h}"],'
52: ' check=False,'
53: ' )'
54: ' subprocess.run(["wmctrl", "-i", "-r", new_win_id, "-t", str(desktop_index)], check=False)'
55: ' subprocess.run('
56: ' ["wmctrl", "-i", "-r", new_win_id, "-b", "add,maximized_vert,maximized_horz"],'
57: ' check=False,'
58: ' )'
59: ' print(f" β Chrome window {new_win_id} pinned to desktop {desktop_num} on primary monitor ({px},{py})")'
60: ' else:'
61: ' print(f" β Timeout waiting for new Chrome window ({profile_name})")'
62: ' except FileNotFoundError:'
63: ' print(" Error: google-chrome-stable not found")'
64: ' except Exception as e:'
65: ' print(f" Error launching Chrome {profile_name}: {e}")'
66: ''
67: ''
68: 'def _legacy_launch_chrome_profile_unused(profile_name, desktop_num, urls=None):'
69: ' try:'
70: ' # Add --no-startup-window flag to prevent focus stealing'
71: ' cmd = ["google-chrome-stable", '
72: ' f"--profile-directory={profile_name}",'
73: ' "--no-first-run",'
74: ' "--disable-background-mode"]'
75: ' if urls:'
76: ' cmd.extend(urls)'
77: ' subprocess.Popen(cmd,'
78: ' stdout=subprocess.DEVNULL, '
79: ' stderr=subprocess.DEVNULL)'
80: ' print(f" Chrome {profile_name} launched on desktop {desktop_num}")'
81: ' '
82: ' # Wait for Chrome window to appear'
83: ' wait_for_window("google chrome", timeout=10)'
84: ' time.sleep(LAUNCH_DELAY) # Extra settling time'
85: ' '
86: ' except FileNotFoundError:'
87: ' print(f" Error: google-chrome-stable not found")'
88: ' except Exception as e:'
89: ' print(f" Error launching Chrome {profile_name}: {e}")'
--- END SUBMITTED SEARCH BLOCK ---
β
DETERMINISTIC PATCH APPLIED: Successfully mutated '/home/mike/repos/nixos/autognome.py'.
(nix) pipulate $ #
(nix) pipulate $ # Hand-applied one that did't land (I think).
(nix) pipulate $ n
(nix) nixos $ d
diff --git a/autognome.py b/autognome.py
index 44bcbcf..f8aac06 100644
--- a/autognome.py
+++ b/autognome.py
@@ -54,11 +54,11 @@ import threading
SWITCH_DELAY = 0.7 # Seconds to wait after switching desktop
LAUNCH_DELAY = 0.3 # Seconds to wait after launching an app/terminal
TERMINAL_LOAD_DELAY = 0.5 # Seconds to wait for terminal to fully load before typing
-TERMINAL_TYPING_DELAY = 0.5 # Seconds between terminal commands
-NIX_DEVELOP_DELAY = 3.5 # Seconds to wait for 'nix develop' to complete
-FONT_ADJUST_DELAY = 0.1 # Seconds between font size adjustments
-TAB_CREATE_DELAY = 1.0 # Seconds to wait after creating new tab
-INTER_DESKTOP_DELAY = 2.0 # Seconds to pause between desktops to let things settle
+TERMINAL_TYPING_DELAY = 0.15 # Seconds between terminal commands
+NIX_DEVELOP_DELAY = 3.5 # Seconds to wait for 'nix develop' to complete
+FONT_ADJUST_DELAY = 0.1 # Seconds between font size adjustments
+TAB_CREATE_DELAY = 0.8 # Seconds to wait after creating new tab
+INTER_DESKTOP_DELAY = 0.5 # Seconds to pause between desktops to let things settle
# The comprehensive timing control system could potentially be adapted for applications
# requiring precise coordination of user interface elements and interaction timing.
@@ -191,6 +191,7 @@ DESKTOP_SETUP = [
{ # Desktop 6 (Index 5) - Chrome Default Profile (YouTube Live)
"chrome": "Default",
"urls": ["https://youtube.com/MikeLevin/live"],
+ "mute": True,
"apps": [],
"terminals": [],
},
@@ -650,15 +651,30 @@ def launch_application(app_command):
print(f" Error launching '{app_command}': {e}")
-def launch_terminal(terminal_config):
- """Launches a terminal, optionally running a command and setting a title."""
+def get_terminal_window_ids():
+ """Return dict mapping integer window IDs to hex ID strings for open terminal windows."""
+ ids = {}
+ try:
+ res = subprocess.run(["wmctrl", "-lx"], capture_output=True, text=True, check=True)
+ for line in res.stdout.strip().splitlines():
+ parts = line.split(None, 4)
+ if len(parts) >= 3 and ("gnome-terminal" in parts[2].lower() or TERMINAL_APP.lower() in parts[2].lower()):
+ try:
+ ids[int(parts[0], 16)] = parts[0]
+ except ValueError:
+ pass
+ except Exception:
+ pass
+ return ids
+
+
+def spawn_terminal_window(terminal_config, keep_card_above=False):
+ """Spawn the terminal window during card narration so bash is warm before typing begins."""
cmd_list = [TERMINAL_APP]
desc = "default terminal"
-
term_title = terminal_config.get("title")
tabbed_setup = terminal_config.get("tabbed_setup", False)
- # Add maximize option for gnome-terminal
if TERMINAL_APP == "gnome-terminal":
cmd_list.append("--maximize")
@@ -667,106 +683,113 @@ def launch_terminal(terminal_config):
cmd_list.extend(["--title", term_title])
desc += f" (title: '{term_title}')"
- # NEW: Unified Tabbed Setup Logic
- if tabbed_setup:
- tabs = terminal_config.get("tabs", [])
- if not tabs:
- print(" β 'tabbed_setup' is true but no 'tabs' list provided.")
- subprocess.Popen(cmd_list)
- return
+ if not tabbed_setup:
+ term_command = terminal_config.get("command")
+ if term_command and TERMINAL_APP == "gnome-terminal":
+ cmd_list.extend(["--", "bash", "-c", term_command + "; exec bash"])
+ desc = f"terminal with command: '{term_command}'"
- print(f" Launch tabbed terminal: {term_title or 'Multi-Tab'}")
-
- try:
- # Launch the initial terminal
- subprocess.Popen(cmd_list)
-
- # Wait for terminal to appear
- wait_for_window("terminal", timeout=5)
- time.sleep(TERMINAL_LOAD_DELAY)
-
- for i, tab in enumerate(tabs):
- tab_dir = tab.get("dir", ".")
- # 'cmd' is the activation command (e.g., nix develop)
- tab_cmd = tab.get("cmd", "exec bash")
- size_down = tab.get("size_down", 0)
- # NEW: 'work_subdir' allows cd-ing AFTER the environment loads
- work_subdir = tab.get("work_subdir", None)
-
- if i > 0: # If not the first tab, create a new one
- print(f" Creating Tab {i+1}...")
- subprocess.run(["xdotool", "key", "ctrl+shift+t"], check=True)
- time.sleep(TAB_CREATE_DELAY)
- else:
- print(f" Setting up Tab 1...")
-
- # 1. Change to Repo Root (The Flake Location)
- subprocess.run(["xdotool", "type", f"cd {tab_dir}"], check=True)
- subprocess.run(["xdotool", "key", "Return"], check=True)
- time.sleep(TERMINAL_TYPING_DELAY)
-
- # 2. Run Activation Command (The Flake)
- # We assume interactive commands like 'nix develop' or 'gemini'
- final_cmd = tab_cmd
-
- subprocess.run(["xdotool", "type", final_cmd], check=True)
- subprocess.run(["xdotool", "key", "Return"], check=True)
-
- # 3. The "Clean Entry" Protocol
- if "nix develop" in tab_cmd:
- print(f" β³ Waiting for Nix environment to hydrate...")
- time.sleep(NIX_DEVELOP_DELAY)
-
- # Clear the "garbage" (flake welcome messages)
- print(f" π§Ή Clearing screen...")
- subprocess.run(["xdotool", "key", "ctrl+l"], check=True) # Ctrl+L is safer than typing 'clear'
- time.sleep(0.5)
-
- # Re-orient the user
- if work_subdir:
- print(f" π Navigating to subdir: {work_subdir}")
- subprocess.run(["xdotool", "type", f"cd {work_subdir}"], check=True)
- subprocess.run(["xdotool", "key", "Return"], check=True)
-
- # Announce location (vibe check)
- subprocess.run(["xdotool", "type", "unset PIPULATE_VOICE && pwd && echo 'β¨ Environment Ready'"], check=True)
- subprocess.run(["xdotool", "key", "Return"], check=True)
+ print(f" Pre-spawning {term_title or desc}...")
+ before_ids = get_terminal_window_ids()
+ try:
+ subprocess.Popen(cmd_list)
+ except Exception as e:
+ print(f" Error spawning terminal: {e}")
+ return (terminal_config, None)
- # 4. Adjust font size
- if size_down > 0:
- print(f" Adjusting text size in Tab {i+1}...")
- for _ in range(size_down):
- subprocess.run(["xdotool", "key", "ctrl+minus"], check=True)
- time.sleep(FONT_ADJUST_DELAY)
-
- # Switch back to the first tab
- if len(tabs) > 1:
- print(" Switching back to first tab.")
- for _ in range(len(tabs) - 1):
- subprocess.run(["xdotool", "key", "ctrl+Page_Up"], check=True)
- time.sleep(0.1)
-
- print(f" β Tabbed terminal '{term_title}' setup completed")
+ new_win_id = None
+ start_time = time.time()
+ while time.time() - start_time < 5.0:
+ current_ids = get_terminal_window_ids()
+ diff = [win_str for k, win_str in current_ids.items() if k not in before_ids]
+ if diff:
+ new_win_id = diff[-1]
+ break
+ time.sleep(0.1)
- except Exception as e:
- print(f" β Unexpected error during tabbed setup: {e}")
-
+ if keep_card_above:
+ subprocess.run(
+ ["wmctrl", "-x", "-r", "conjure_window_overlay", "-b", "add,above"],
+ capture_output=True,
+ check=False,
+ )
+
+ return (terminal_config, new_win_id)
+
+
+def hydrate_terminal_tabs(terminal_config, term_win_id=None):
+ """Type tab commands into an already-spawned terminal immediately after card dismissal."""
+ if term_win_id:
+ subprocess.run(["wmctrl", "-i", "-a", term_win_id], check=False)
+ time.sleep(0.1)
+
+ if not terminal_config.get("tabbed_setup", False):
return
- # --- Fallback for simple, non-tabbed terminals ---
- term_command = terminal_config.get("command")
- if term_command:
- if TERMINAL_APP == "gnome-terminal":
- cmd_list.extend(["--", "bash", "-c", term_command + "; exec bash"])
- desc = f"terminal with command: '{term_command}'"
+ tabs = terminal_config.get("tabs", [])
+ if not tabs:
+ return
- print(f" Launching {desc}")
+ term_title = terminal_config.get("title") or "Multi-Tab"
try:
- subprocess.Popen(cmd_list)
- wait_for_window("terminal", timeout=5)
- time.sleep(LAUNCH_DELAY)
+ for i, tab in enumerate(tabs):
+ tab_dir = tab.get("dir", ".")
+ tab_cmd = tab.get("cmd", "exec bash")
+ size_down = tab.get("size_down", 0)
+ work_subdir = tab.get("work_subdir", None)
+
+ if i > 0:
+ print(f" Creating Tab {i+1}...")
+ subprocess.run(["xdotool", "key", "ctrl+shift+t"], check=True)
+ time.sleep(TAB_CREATE_DELAY)
+ else:
+ print(" Setting up Tab 1...")
+
+ subprocess.run(["xdotool", "type", f"cd {tab_dir}"], check=True)
+ subprocess.run(["xdotool", "key", "Return"], check=True)
+ time.sleep(TERMINAL_TYPING_DELAY)
+
+ subprocess.run(["xdotool", "type", tab_cmd], check=True)
+ subprocess.run(["xdotool", "key", "Return"], check=True)
+
+ if "nix develop" in tab_cmd:
+ print(" β³ Waiting for Nix environment to hydrate...")
+ time.sleep(NIX_DEVELOP_DELAY)
+
+ print(" π§Ή Clearing screen...")
+ subprocess.run(["xdotool", "key", "ctrl+l"], check=True)
+ time.sleep(0.3)
+
+ if work_subdir:
+ print(f" π Navigating to subdir: {work_subdir}")
+ subprocess.run(["xdotool", "type", f"cd {work_subdir}"], check=True)
+ subprocess.run(["xdotool", "key", "Return"], check=True)
+
+ subprocess.run(["xdotool", "type", "unset PIPULATE_VOICE && pwd && echo 'β¨ Environment Ready'"], check=True)
+ subprocess.run(["xdotool", "key", "Return"], check=True)
+
+ if size_down > 0:
+ print(f" Adjusting text size in Tab {i+1}...")
+ for _ in range(size_down):
+ subprocess.run(["xdotool", "key", "ctrl+minus"], check=True)
+ time.sleep(FONT_ADJUST_DELAY)
+
+ if len(tabs) > 1:
+ print(" Switching back to first tab.")
+ for _ in range(len(tabs) - 1):
+ subprocess.run(["xdotool", "key", "ctrl+Page_Up"], check=True)
+ time.sleep(0.1)
+
+ print(f" β Tabbed terminal '{term_title}' setup completed")
except Exception as e:
- print(f" Error launching terminal: {e}")
+ print(f" β Unexpected error during tabbed setup: {e}")
+
+
+def launch_terminal(terminal_config):
+ """Launches a terminal and hydrates its tabs."""
+ cfg, win_id = spawn_terminal_window(terminal_config, keep_card_above=False)
+ time.sleep(TERMINAL_LOAD_DELAY)
+ hydrate_terminal_tabs(cfg, win_id)
def get_primary_monitor_geometry():
@@ -838,9 +861,10 @@ def get_chrome_window_ids():
return ids
-def launch_chrome_profile(profile_name, desktop_num, urls=None):
+def launch_chrome_profile(profile_name, desktop_num, urls=None, keep_card_above=False):
"""Launches Google Chrome in a new window pinned to desktop_num on the primary monitor."""
print(f" Launching Chrome with {profile_name} profile...")
+ new_win_id = None
try:
geom = get_primary_monitor_geometry()
px, py, pw, ph = geom
@@ -867,7 +891,6 @@ def launch_chrome_profile(profile_name, desktop_num, urls=None):
stderr=subprocess.DEVNULL,
)
print(f" Chrome {profile_name} launched on desktop {desktop_num}")
- new_win_id = None
start_time = time.time()
while time.time() - start_time < 10:
current_ids = get_chrome_window_ids()
@@ -875,7 +898,7 @@ def launch_chrome_profile(profile_name, desktop_num, urls=None):
if diff:
new_win_id = diff[-1]
break
- time.sleep(0.25)
+ time.sleep(0.15)
time.sleep(LAUNCH_DELAY)
if new_win_id:
desktop_index = desktop_num - 1
@@ -896,6 +919,12 @@ def launch_chrome_profile(profile_name, desktop_num, urls=None):
["wmctrl", "-i", "-r", new_win_id, "-b", "add,maximized_vert,maximized_horz"],
check=False,
)
+ if keep_card_above:
+ subprocess.run(
+ ["wmctrl", "-x", "-r", "conjure_window_overlay", "-b", "add,above"],
+ capture_output=True,
+ check=False,
+ )
print(f" β Chrome window {new_win_id} pinned to desktop {desktop_num} on primary monitor ({px},{py})")
else:
print(f" β Timeout waiting for new Chrome window ({profile_name})")
@@ -903,6 +932,39 @@ def launch_chrome_profile(profile_name, desktop_num, urls=None):
print(" Error: google-chrome-stable not found")
except Exception as e:
print(f" Error launching Chrome {profile_name}: {e}")
+ return new_win_id
+
+
+def mute_chrome_window(win_id, timeout=6.0):
+ """Wait for YouTube to hydrate in win_id, focus the window, and press 'm' to mute."""
+ if not win_id or not shutil.which("xdotool"):
+ return
+ print(f" β³ Waiting for YouTube player in window {win_id} before muting...")
+ start_time = time.time()
+ target_int = int(win_id, 16)
+ while time.time() - start_time < timeout:
+ try:
+ res = subprocess.run(["wmctrl", "-l"], capture_output=True, text=True, check=True)
+ matched = False
+ for line in res.stdout.splitlines():
+ parts = line.split(None, 3)
+ if len(parts) >= 4:
+ try:
+ if int(parts[0], 16) == target_int and "youtube" in parts[3].lower():
+ matched = True
+ break
+ except ValueError:
+ pass
+ if matched:
+ break
+ except Exception:
+ pass
+ time.sleep(0.25)
+ time.sleep(1.2)
+ subprocess.run(["wmctrl", "-i", "-a", win_id], check=False)
+ time.sleep(0.2)
+ subprocess.run(["xdotool", "key", "m"], check=False)
+ print(f" π Sent 'm' (mute) to YouTube window {win_id}")
def _legacy_launch_chrome_profile_unused(profile_name, desktop_num, urls=None):
@@ -1195,6 +1257,28 @@ if __name__ == "__main__":
speech_proc = play_prepped_speech(wav_path, narration) if narration else None
+ # Pre-spawn this desktop's Chrome/terminal windows underneath the raised
+ # title card while Piper is speaking so they are warm when the card exits.
+ time.sleep(0.2)
+ chrome_profile = desktop_config.get("chrome")
+ chrome_win_id = None
+ if chrome_profile:
+ chrome_win_id = launch_chrome_profile(
+ chrome_profile,
+ i + 1,
+ desktop_config.get("urls"),
+ keep_card_above=bool(card_handle),
+ )
+
+ for app_cmd in desktop_config.get("apps", []):
+ launch_application(app_cmd)
+
+ spawned_terminals = []
+ for term_cfg in desktop_config.get("terminals", []):
+ spawned_terminals.append(
+ spawn_terminal_window(term_cfg, keep_card_above=bool(card_handle))
+ )
+
if speech_proc:
wait_for_speech(speech_proc)
elif card_handle:
@@ -1203,18 +1287,11 @@ if __name__ == "__main__":
if card_handle:
dismiss_card(card_handle)
- # Launch Chrome profile if configured
- chrome_profile = desktop_config.get("chrome")
- if chrome_profile:
- launch_chrome_profile(chrome_profile, i + 1, desktop_config.get("urls"))
+ if chrome_win_id and desktop_config.get("mute"):
+ mute_chrome_window(chrome_win_id)
- # Launch regular apps
- for app_cmd in desktop_config.get("apps", []):
- launch_application(app_cmd)
-
- # Launch terminals
- for term_cfg in desktop_config.get("terminals", []):
- launch_terminal(term_cfg)
+ for term_cfg, term_win_id in spawned_terminals:
+ hydrate_terminal_tabs(term_cfg, term_win_id)
# GUI windows LAST, after every xdotool keystroke on this desktop
# has been typed -- see launch_window_app() for why the order is
(nix) nixos $ m
π Committing: feat: Implement terminal tabbing and hydration
[main 2ae5a2c] feat: Implement terminal tabbing and hydration
1 file changed, 192 insertions(+), 115 deletions(-)
(nix) nixos $ git push
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 48 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 1.94 KiB | 1.94 MiB/s, done.
Total 3 (delta 2), reused 0 (delta 0), pack-reused 0 (from 0)
remote: Resolving deltas: 100% (2/2), completed with 2 local objects.
To github.com:miklevin/nixos-config.git
06ec088..2ae5a2c main -> main
(nix) nixos $ patch
(nix) nixos $ app
βͺ Targets resolved from /home/mike/repos/pipulate (PIPULATE_ROOT), not from the folder app was typed in.
β
DETERMINISTIC PATCH APPLIED: Successfully mutated '/home/mike/repos/nixos/scripts/chrome_windows.py'.
(nix) nixos $ d
diff --git a/scripts/chrome_windows.py b/scripts/chrome_windows.py
index be3efba..e280243 100644
--- a/scripts/chrome_windows.py
+++ b/scripts/chrome_windows.py
@@ -272,11 +272,51 @@ def chrome_windows():
return out
+def primary_monitor_geometry():
+ """Return (x, y, w, h) of the primary monitor from xrandr, or (0, 0, 2560, 1440)."""
+ if shutil.which("xrandr"):
+ try:
+ import re
+ res = subprocess.run(["xrandr", "--query"], capture_output=True, text=True, timeout=5)
+ fallback = None
+ for line in res.stdout.splitlines():
+ if " connected " in line:
+ m = re.search(r"(\d+)x(\d+)\+(\d+)\+(\d+)", line)
+ if m:
+ geom = (int(m.group(3)), int(m.group(4)), int(m.group(1)), int(m.group(2)))
+ if " primary " in line:
+ return geom
+ if fallback is None:
+ fallback = geom
+ if fallback is not None:
+ return fallback
+ except Exception:
+ pass
+ return (0, 0, 2560, 1440)
+
+
def move_window(window_id, desktop):
- """wmctrl -i -r ID -t N, N zero-based. True on exit 0."""
+ """Unmaximize, move onto primary monitor, pin to 0-based desktop, and re-maximize."""
+ px, py, pw, ph = primary_monitor_geometry()
+ tx, ty = px + 60, py + 60
+ tw, th = max(800, pw - 120), max(600, ph - 120)
try:
- run = subprocess.run(["wmctrl", "-i", "-r", window_id, "-t", str(desktop)],
- capture_output=True, text=True, timeout=5)
+ subprocess.run(
+ ["wmctrl", "-i", "-r", window_id, "-b", "remove,maximized_vert,maximized_horz,fullscreen"],
+ capture_output=True, text=True, timeout=5,
+ )
+ subprocess.run(
+ ["wmctrl", "-i", "-r", window_id, "-e", "0,%d,%d,%d,%d" % (tx, ty, tw, th)],
+ capture_output=True, text=True, timeout=5,
+ )
+ run = subprocess.run(
+ ["wmctrl", "-i", "-r", window_id, "-t", str(desktop)],
+ capture_output=True, text=True, timeout=5,
+ )
+ subprocess.run(
+ ["wmctrl", "-i", "-r", window_id, "-b", "add,maximized_vert,maximized_horz"],
+ capture_output=True, text=True, timeout=5,
+ )
except (OSError, subprocess.SubprocessError):
return False
return run.returncode == 0
@@ -293,7 +333,14 @@ def chrome_binary():
def launch(profile, user_data_dir, binary):
- flags = ["--profile-directory=%s" % profile, "--no-first-run", "--disable-background-mode"]
+ px, py, _, _ = primary_monitor_geometry()
+ flags = [
+ "--profile-directory=%s" % profile,
+ "--new-window",
+ "--window-position=%d,%d" % (px + 60, py + 60),
+ "--no-first-run",
+ "--disable-background-mode",
+ ]
if user_data_dir != bs.USER_DATA_DIR:
flags.append("--user-data-dir=%s" % user_data_dir)
cmd = [binary] + flags
(nix) nixos $ m
π Committing: feat: Implement primary monitor geometry detection
[main 494a457] feat: Implement primary monitor geometry detection
1 file changed, 51 insertions(+), 4 deletions(-)
(nix) nixos $ git push
Enumerating objects: 7, done.
Counting objects: 100% (7/7), done.
Delta compression using up to 48 threads
Compressing objects: 100% (4/4), done.
Writing objects: 100% (4/4), 1.06 KiB | 1.06 MiB/s, done.
Total 4 (delta 3), reused 0 (delta 0), pack-reused 0 (from 0)
remote: Resolving deltas: 100% (3/3), completed with 3 local objects.
To github.com:miklevin/nixos-config.git
2ae5a2c..494a457 main -> main
(nix) nixos $
Ignition: often βexitβ, βnix developβ. Instructions may vary.
4: PROMPT:
Verify the AFTER receipts for apply.pyβs _rstrip_line_spans fallback, autognome.pyβs pre-spawned terminal hydration and YouTube mute_chrome_window, and chrome_windows.pyβs primary_monitor_geometry.
5: DELIVERABLE:
Wow, that was an epic patch and I hope I did the hand-edit correct. Letβs find out!
And wow, after a reboot and init this is astoundingly good. The only βmissβ is
that the YouTube window still had audio. I just tested and the βMβ is a toggle
turning mute on and off which is almost certainly whatβs happening there. You
silence the Pipulate server correctly which was a lingering bug I was going to
get to that but I think you addressed that because the Pipulate server is
running correctly. This is all astoundingly good and I want to lock it in and
not suffer any regressions. This is so much better than when we started and itβs
going to encourage me to go through the startup kata more and make it even more
theatrical and ready for YouTube with the whole Inspector Gadget unfurling a
bunch of his gadgets, maybe with ASCII art the way I do with Alice but weβll get
to all that in good time. Probably a Mickey as the Sorcererβs Apprentice would
be good too but later (not this article).
Letβs just figure out how to make sure the YouTube screen is idempotently quiet. Let me see what I can findβ¦
Oh! the down-arrow repeatedly (it has to be enough times). But thatβll be easy enough to slip in I think. Sounds like an idempotent solution.
Clamp Over Toggle: Driving YouTube Volume to Zero
Gemini 4 Argon High: Context verified.
TL;DR:
- AFTER receipts verified:
apply.pyβs_rstrip_line_spans(line 142),autognome.pyβsspawn_terminal_window/hydrate_terminal_tabs/mute_chrome_window(lines 671, 720, 938), andchrome_windows.pyβsprimary_monitor_geometry(line 275) are all live and verified in this compile. (And notice how the newFIRST DIVERGENCEfallback inapply.pyimmediately pinpointed the 24-vs-25 leading-space difference on line 78 of the legacy function!) - Why
mleft YouTube audible & whyDownarrow fixes it idempotently:- Toggle vs. Clamped Floor:
mis a modulo-2 toggleβif YouTubeβslocalStorage(yt-player-volume) remembered a muted state from a prior session, pressingmunmutes the stream. By contrast,Downarrow steps the YouTube player volume down by 5% per press and clamps at0%βpressing it 30 times is an idempotent floor (min(vol - 30*5, 0) = 0) regardless of where the volume started. - The
/liveRedirect Timing:[https://youtube.com/MikeLevin/live](https://youtube.com/MikeLevin/live)issues a redirect to the activewatch?v=...URL, so"youtube"appears in the X11 window title before the video player finishes hydrating.
- Toggle vs. Clamped Floor:
- Car 1 (
/home/mike/repos/nixos/autognome.py): Updatesmute_chrome_window()to wait for the window title, focus the window, let the/liveredirect settle (2.0s), and send two 30-key bursts ofDown(xdotool key --repeat 30 --delay 25 Down) separated by1.0sso the player volume is driven to0%and stays clamped there.
5-Car Train
1: BEFORE (PROBES β read-only commands; straddles the mute_chrome_window update from 'm' to --repeat 30 --delay 25 Down in /home/mike/repos/nixos/autognome.py and gates --dry-run):
(nix) pipulate $
rg -n "(--repeat|Down|key.*\"m\"|def mute_chrome_window)" /home/mike/repos/nixos/autognome.py
.venv/bin/python /home/mike/repos/nixos/autognome.py --dry-run | tail -n 6
2: AFTER (NEXT CONTEXT β put in context.txt using context or x):
(nix) pipulate $
# Context 7
context.txt
~/repos/nixos/autognome.py
~/repos/nixos/scripts/chrome_windows.py
scripts/card.py
# imports/voice_synthesis.py
imports/ascii_displays.py
foo_files.py
! rg -n "(--repeat|Down|key.*\"m\"|def mute_chrome_window)" /home/mike/repos/nixos/autognome.py
! .venv/bin/python /home/mike/repos/nixos/autognome.py --dry-run | tail -n 6
3: CHANGE (PATCHES β 1 car on /home/mike/repos/nixos/autognome.py):
Car 1 (/home/mike/repos/nixos/autognome.py β replace the non-idempotent m toggle in mute_chrome_window() with two 30-step Down-arrow bursts to clamp YouTube Live volume at 0%):
(nix) pipulate $
Target: /home/mike/repos/nixos/autognome.py
[[[SEARCH]]]
def mute_chrome_window(win_id, timeout=6.0):
"""Wait for YouTube to hydrate in win_id, focus the window, and press 'm' to mute."""
if not win_id or not shutil.which("xdotool"):
return
print(f" β³ Waiting for YouTube player in window {win_id} before muting...")
start_time = time.time()
target_int = int(win_id, 16)
while time.time() - start_time < timeout:
try:
res = subprocess.run(["wmctrl", "-l"], capture_output=True, text=True, check=True)
matched = False
for line in res.stdout.splitlines():
parts = line.split(None, 3)
if len(parts) >= 4:
try:
if int(parts[0], 16) == target_int and "youtube" in parts[3].lower():
matched = True
break
except ValueError:
pass
if matched:
break
except Exception:
pass
time.sleep(0.25)
time.sleep(1.2)
subprocess.run(["wmctrl", "-i", "-a", win_id], check=False)
time.sleep(0.2)
subprocess.run(["xdotool", "key", "m"], check=False)
print(f" π Sent 'm' (mute) to YouTube window {win_id}")
[[[DIVIDER]]]
def mute_chrome_window(win_id, timeout=8.0):
"""Wait for YouTube to hydrate in win_id, focus the window, and drive volume to 0 with Down arrows."""
if not win_id or not shutil.which("xdotool"):
return
print(f" β³ Waiting for YouTube player in window {win_id} before lowering volume...")
start_time = time.time()
target_int = int(win_id, 16)
while time.time() - start_time < timeout:
try:
res = subprocess.run(["wmctrl", "-l"], capture_output=True, text=True, check=True)
matched = False
for line in res.stdout.splitlines():
parts = line.split(None, 3)
if len(parts) >= 4:
try:
if int(parts[0], 16) == target_int and "youtube" in parts[3].lower():
matched = True
break
except ValueError:
pass
if matched:
break
except Exception:
pass
time.sleep(0.25)
subprocess.run(["wmctrl", "-i", "-a", win_id], check=False)
time.sleep(2.0)
for burst in range(2):
subprocess.run(["wmctrl", "-i", "-a", win_id], check=False)
time.sleep(0.15)
subprocess.run(
["xdotool", "key", "--repeat", "30", "--delay", "25", "Down"],
check=False,
)
if burst == 0:
time.sleep(1.0)
print(f" π Clamped YouTube volume to 0% (Down x60) in window {win_id}")
[[[REPLACE]]]
Ignition: none required for the compile-lane probes; the next init run exercises the two 30-step Down-arrow bursts on Desktop 6.
4: PROMPT (Use prompt or p to create prompt.md from copy):
(nix) pipulate $
Verify the AFTER receipts for the idempotent Down-arrow YouTube volume clamp in /home/mike/repos/nixos/autognome.py and confirm all 7 desktops are locked in and ready for dismount.
5: DELIVERABLE (What done looks like):
mute_chrome_window()in/home/mike/repos/nixos/autognome.pyreplaces themtoggle with two 30-key bursts ofDownarrow (xdotool key --repeat 30 --delay 25 Down), idempotently clamping the YouTube Live player volume at0%without risking an unmute toggle.
MikeLev.in:
THE AI-EDIT METHOD
Same commands, run twice, one change between them. Where the readings differ is what the change did; the diff in the middle is the receipt.
1: BEFORE (PROBE):
On branch main
Your branch is ahead of 'origin/main' by 1 commit.
(use "git push" to publish your local commits)
nothing to commit, working tree clean
GIT repo clean. Take BEFORE reading, make CHANGE, record AFTER diff.
(nix) pipulate $ rg -n "(--repeat|Down|key.*\"m\"|def mute_chrome_window)" /home/mike/repos/nixos/autognome.py
.venv/bin/python /home/mike/repos/nixos/autognome.py --dry-run | tail -n 6
938:def mute_chrome_window(win_id, timeout=6.0):
966: subprocess.run(["xdotool", "key", "m"], check=False)
β
Dry run: 7 desktops, 3 terminals on the stack. No keys were harmed.
],
"windows": [],
"narration": "Desktop seven: The Stack, Jekyll Servers.",
"card": "Desktop: 7\nTHE STACK"
}
]
(nix) pipulate $
2: AFTER (NEXT CONTEXT):
Somehow itβs appropriate that this took 7 turns.
# # Context 1
# context.txt
# ~/repos/nixos/autognome.py
# scripts/card.py
# # Context 2
# context.txt
# ~/repos/nixos/autognome.py
# scripts/card.py
# imports/voice_synthesis.py
# imports/ascii_displays.py
# foo_files.py
# ! rg -n "card\.py" /home/mike/repos/pipulate /home/mike/repos/nixos
# ! ls -la /home/mike/repos/nixos /home/mike/repos/nixos/scripts
# # Context 3
# context.txt
# ~/repos/nixos/autognome.py
# scripts/card.py
# imports/voice_synthesis.py
# imports/ascii_displays.py
# foo_files.py
# ! rg -n "def (synthesize_to_wav|play_wav|_resolve_play_cmd)" imports/voice_synthesis.py || echo "MISSING_METHODS"
# ! rg -n "ensure_voice|prep_speech|play_prepped_speech" /home/mike/repos/nixos/autognome.py || echo "MISSING_DOUBLE_BUFFER"
# ! .venv/bin/python /home/mike/repos/nixos/autognome.py --dry-run | tail -n 5
# # Context 4
# context.txt
# ~/repos/nixos/autognome.py
# ~/repos/nixos/scripts/chrome_windows.py
# scripts/card.py
# imports/voice_synthesis.py
# imports/ascii_displays.py
# foo_files.py
# ! xrandr --query | rg " connected "
# ! rg -n "(--new-window|get_primary_monitor_geometry|get_chrome_window_ids|--reset|--restart)" /home/mike/repos/nixos/autognome.py
# ! .venv/bin/python /home/mike/repos/nixos/autognome.py --dry-run | tail -n 5
# # Context 5
# context.txt
# ~/repos/nixos/autognome.py
# ~/repos/nixos/scripts/chrome_windows.py
# scripts/card.py
# imports/voice_synthesis.py
# imports/ascii_displays.py
# foo_files.py
# ! wmctrl -lG | rg -i "chrome" || echo "NO_CHROME_WINDOWS"
# ! .venv/bin/python -c 'import json, pathlib; p = pathlib.Path("~/.config/google-chrome").expanduser(); [print(d, json.loads((p/d/"Preferences").read_text()).get("browser", {}).get("window_placement")) for d in ("Default", "Profile 8") if (p/d/"Preferences").exists()]'
# ! rg -n "(--new-window|get_primary_monitor_geometry|sanitize_chrome_preferences)" /home/mike/repos/nixos/autognome.py /home/mike/repos/nixos/scripts/chrome_windows.py || echo "MISSING_PLACEMENT_FIX"
# # Context 6
# context.txt
# ~/repos/nixos/autognome.py
# ~/repos/nixos/scripts/chrome_windows.py
# scripts/card.py
# imports/voice_synthesis.py
# imports/ascii_displays.py
# foo_files.py
# ! rg -n "def _rstrip_line_spans" apply.py || echo "MISSING_RSTRIP_FALLBACK"
# ! rg -n "def (spawn_terminal_window|hydrate_terminal_tabs|mute_chrome_window)" /home/mike/repos/nixos/autognome.py || echo "MISSING_AUTOGNOME_FAST_TERM_MUTE"
# ! rg -n "def primary_monitor_geometry" /home/mike/repos/nixos/scripts/chrome_windows.py || echo "MISSING_CHROME_WINDOWS_GEOM"
# ! .venv/bin/python /home/mike/repos/nixos/autognome.py --dry-run | tail -n 6
# Context 7
context.txt
~/repos/nixos/autognome.py
~/repos/nixos/scripts/chrome_windows.py
scripts/card.py
imports/voice_synthesis.py
imports/ascii_displays.py
foo_files.py
! rg -n "(--repeat|Down|key.*\"m\"|def mute_chrome_window)" /home/mike/repos/nixos/autognome.py
! .venv/bin/python /home/mike/repos/nixos/autognome.py --dry-run | tail -n 6
3: CHANGE (PATCHES):
On branch main
Your branch is ahead of 'origin/main' by 1 commit.
(use "git push" to publish your local commits)
nothing to commit, working tree clean
GIT repo clean. Take BEFORE reading, make CHANGE, record AFTER diff.
(nix) pipulate $ patch
(nix) pipulate $ app
β
DETERMINISTIC PATCH APPLIED: Successfully mutated '/home/mike/repos/nixos/autognome.py'.
(nix) pipulate $ d
(nix) pipulate $ n
(nix) nixos $ d
diff --git a/autognome.py b/autognome.py
index f8aac06..640105a 100644
--- a/autognome.py
+++ b/autognome.py
@@ -935,11 +935,11 @@ def launch_chrome_profile(profile_name, desktop_num, urls=None, keep_card_above=
return new_win_id
-def mute_chrome_window(win_id, timeout=6.0):
- """Wait for YouTube to hydrate in win_id, focus the window, and press 'm' to mute."""
+def mute_chrome_window(win_id, timeout=8.0):
+ """Wait for YouTube to hydrate in win_id, focus the window, and drive volume to 0 with Down arrows."""
if not win_id or not shutil.which("xdotool"):
return
- print(f" β³ Waiting for YouTube player in window {win_id} before muting...")
+ print(f" β³ Waiting for YouTube player in window {win_id} before lowering volume...")
start_time = time.time()
target_int = int(win_id, 16)
while time.time() - start_time < timeout:
@@ -960,11 +960,18 @@ def mute_chrome_window(win_id, timeout=6.0):
except Exception:
pass
time.sleep(0.25)
- time.sleep(1.2)
subprocess.run(["wmctrl", "-i", "-a", win_id], check=False)
- time.sleep(0.2)
- subprocess.run(["xdotool", "key", "m"], check=False)
- print(f" π Sent 'm' (mute) to YouTube window {win_id}")
+ time.sleep(2.0)
+ for burst in range(2):
+ subprocess.run(["wmctrl", "-i", "-a", win_id], check=False)
+ time.sleep(0.15)
+ subprocess.run(
+ ["xdotool", "key", "--repeat", "30", "--delay", "25", "Down"],
+ check=False,
+ )
+ if burst == 0:
+ time.sleep(1.0)
+ print(f" π Clamped YouTube volume to 0% (Down x60) in window {win_id}")
def _legacy_launch_chrome_profile_unused(profile_name, desktop_num, urls=None):
(nix) nixos $ m
π Committing: chore: Update mute_chrome_window timeout and action to drive volume to 0
[main 57de511] chore: Update mute_chrome_window timeout and action to drive volume to 0
1 file changed, 14 insertions(+), 7 deletions(-)
(nix) nixos $ git push
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 48 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 584 bytes | 584.00 KiB/s, done.
Total 3 (delta 2), reused 0 (delta 0), pack-reused 0 (from 0)
remote: Resolving deltas: 100% (2/2), completed with 2 local objects.
To github.com:miklevin/nixos-config.git
494a457..57de511 main -> main
(nix) nixos $
Ignition is a reboot and init after turning the YouTube volume back up.
4: PROMPT:
Verify the AFTER receipts for the idempotent Down-arrow YouTube volume clamp in /home/mike/repos/nixos/autognome.py and confirm all 7 desktops are locked in and ready for dismount.
5: DELIVERABLE:
Testingβ¦
WOW! It all worked. The only weirdness left is that you can hear the Pipulate server start still. Iβm not sure if I have a toggle idempotency issue there too but weβre not going to address it in this article. Iβm tempted to even make the YouTube video of how Inspector Gadget starts is era-of-AI worksession in the morning with all his fixed-position gadgets unfurling out of his body into the 7-screen ribbon now because itβs almost ready for that, but Iβm not going to do either of those things right now.
Iβm going to bank this win as an article.
Get all into it explaining what ever needs to be explained for an audience following this that is not perfectly clear from the article so far or covered by the final article closing, which is as follows:
Hop off the ride. This rideβs stated goal is reached β dismount. This is the NOTARY BEAT: the ride ends here, is witnessed here, and is sealed here. Answer all seven beats, briefly. So fist say a lot, and then do this:
- TL;DR: a short, dry, neutral abstract for the TOP of the published article β written for an unfamiliar reader or AI summarizer who has never seen this system. No hype, no insider handles unexplained.
- VERIFY: restate the goal from the top of this article and confirm (or deny) it was met, citing THIS compileβs receipts, not memory. Name any ignition this ride required that never fired β an AFTER tap taken without one is a stale BEFORE wearing the AFTERβs label.
- BANK: name everything that graduates β rule, earmark, todo, pin β as SEARCH/REPLACE patch cars against raw source present in THIS context (Target line inside the fence, one car per commit story), deletions included. A βpaste-readyβ line with no Target and no SEARCH anchor is not banked; it is a hand edit the operator will not make. These BANK cars are the ONLY patches a dismount emits.
- DANGLING: what carries forward unbanked? One line each, no essays.
- SEED: the context.txt lines (and TODO_SLUGS if narrative context is needed) for the next rideβs first compile.
- CLOSING: a closing summary for the BOTTOM of the article β the final take-away, tied to the bookβs larger arc where it fits naturally, never forced. Storytelling over inventory.
- NOTARIZE: read the exact Deed line from THIS compileβs final QA ARCHIVE SEAL footer, outside the payload, not a quoted older footer or a newest-file guess. It names the input archive, not this later response or the generated Jekyll post. State verification separately: only a verifier receipt naming that archive supports a verified claim; otherwise say not independently verified here. Count this Manifestβs live-command receipts, including failures. Quote only recorded digests, sizes and dates, naming their source; filesystem mtime and fixed ZIP dates are not command timestamps. Do not demand absent wc -c or dated-receipt fields. If the footer is missing, say so; otherwise close with its exact Deed line.
FINALITY: after beat 6, this discussion is CLOSED. Emit NO five-car train, NO probes, NO patches beyond the BANK cars of beat 2, and NO next-turn prompt beyond the SEED lines in beat 4. Any reader or model encountering this article later should treat it as a finished, notarized document β an archive entry, not an open thread.
Gemini 4 Argon High: Context verified.
For the Reader Joining Cold: What Just Happened Here
If you are reading this article without having seen the machine it runs on, here is the architectural picture that connects the seven turns above.
1. AutoGNOME (init) and βSeven at One Swatβ
Every morning on this NixOS workstation, typing a single word (init) launches /home/mike/repos/nixos/autognome.py. Instead of manually opening browsers, positioning windows across monitors, cdβing into repositories, and starting local dev servers, autognome.py uses wmctrl (the Extended Window Manager Hints / EWMH command-line actuator) and xdotool (X11 synthetic keyboard/mouse events) to unfurl seven virtual desktops in sequenceβmuch like an ARexx script orchestrating screens on a 1980s Commodore Amiga:
THE SEVEN REALMS (Primary Monitor: HDMI-0, 2560x1440+0+0)
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
[1: ORIGIN] βββΊ [2: PIPULATE] βββΊ [3: THE WEB] βββΊ [4: WORKSHOP]
nvim nix develop Chrome 2-tab terminal:
journal.txt (Flake shell) (Default) pipulate + nixos
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
βΌ
[5: PROFILE 8] βββΊ [6: YOUTUBE LIVE] βββΊ [7: THE STACK] βββΊ Returns to [1: ORIGIN]
Chrome Chrome (Default) Jekyll server "Seven at one swat.
(Pipulate) Volume clamped 0% tabs (blogs.json) Opening journal."
Each desktop transition is announced by two synchronized cues:
- A centered, borderless Alacritty popup window (
scripts/card.pylaunched viaconjure_windowinimports/ascii_displays.py) rendering the desktopβs name in large ASCII Figlet type. - Local neural text-to-speech narration (
imports/voice_synthesis.py, powered by Piper TTS and an ONNX voice model).
2. Double Buffering for the Senses (Why Speech Lagged and How We Fixed It)
In early computer graphics, drawing directly to the video memory being scanned out to the monitor caused screen tearingβthe viewer saw half of an old frame and half of aContext verified.
What Happened Under the Hood: Five Engineering Lessons for the Reader
For anyone following this seven-turn ride from the outside, what began as a simple requestββmake the voice start talking the instant the pop-up card appearsββended up touching five distinct layers of a Linux desktop automation stack. Each turn exposed a classic systems-engineering seam, and each seam yielded a generalizable pattern.
1. Audio-Visual Double Buffering (Why βPage Flippingβ Applies to CLI and TTS Theatrics)
In classic 1980s and 1990s graphics programming (from the Amigaβs Copper and Blitter co-processors to VGA Mode 13h), drawing sprites directly into the visible front buffer caused screen tearing: the display hardware swept the frame while the CPU was still halfway through drawing it. The cure was double buffering: render the entire expensive frame into an invisible back-buffer in RAM, then flip the address pointer during the vertical blanking interval (VBLANK) so the human eye sees the finished frame appear all at once.
When orchestrating virtual desktops with transient pop-up terminal cards (alacritty + pyfiglet) and local neural speech synthesis (piper-tts), the naive sequential code (show_card() followed by speak()) commits the temporal equivalent of drawing to the front buffer:
NAIVE FRONT-BUFFER SEQUENCE (Visual Tearing in Time)
ββββββββββββββββββββββββββββββββββββββββββββββββββββ
t = 0.00s show_card() spawns Alacritty βββΊ Card appears on screen immediately
t = 0.05s speak() sends text to worker βββΊ Loads 60 MB ONNX model (Desktop 1)
t = 1.20s βββΊ Runs neural inference -> /tmp/*.wav
t = 1.80s βββΊ Spawns audio player; voice finally speaks
DOUBLE-BUFFERED SEQUENCE (Back-Buffer Render + Simultaneous Page Flip)
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
boot init_voice() calls ensure_voice() βββΊ 60 MB ONNX model pre-warmed in RAM
back-buffer prep_speech() -> synthesize_to_wav() βΊ Renders /tmp/*.wav silently
back-buffer measure_figlet() βββΊ Computes exact Alacritty cell geometry
page-flip show_card() + play_prepped_speech() βΊ Window maps & WAV plays simultaneously
By splitting ChipVoiceSystem in imports/voice_synthesis.py into a back-buffer synthesizer (synthesize_to_wav()) and an immediate playback trigger (play_wav()), and resolving native Linux audio players (play -q, pw-play, paplay, aplay -q) directly via shutil.which instead of falling through to a slow nix-shell -p sox evaluation, the expensive work happens before the window maps. When the βpage flipβ fires, alacritty and the pre-rendered .wav launch in lockstep.
2. Pre-Spawning Behind the Curtain (Using the Title Card as a Theatrical Occluder)
Once the title card and voice were locked together, a second latency gap stood out on Desktop 2: after the card disappeared, there was a multi-second pause while gnome-terminal launched, mapped its window, initialized bash, and waited for TERMINAL_LOAD_DELAY before xdotool began typing cd /home/mike/repos/pipulate and nix develop.
Because the Alacritty title card is raised with the EWMH _NET_WM_STATE_ABOVE hint (wmctrl -x -r conjure_window_overlay -b add,above), it acts as a theatrical curtain. Splitting launch_terminal() into spawn_terminal_window() and hydrate_terminal_tabs() allowed autognome.py to launch each desktopβs Chrome and gnome-terminal instances while Piper is speaking the title card. Whenever a background window maps and tries to steal top-level stacking order, autognome.py immediately re-asserts add,above on conjure_window_overlay. By the time Piper finishes speaking and the title card vanishes, gnome-terminal and bash are already warm and waiting; hydrate_terminal_tabs() focuses the new window ID and types at 150ms cadence with zero perceived startup lag.
3. The Four-Layer X11 / Mutter / Chrome Multi-Monitor Puzzle (βARexx for Linuxβ)
Why did all three Chrome windows suddenly land on the secondary portrait monitor (DP-5, 1200x1920+2560+0) instead of the 7-desktop workspace ribbon on the primary landscape monitor (HDMI-0, 2560x1440+0+0), and why did Desktop 6βs YouTube Live window merge as a tab into Desktop 3βs browser?
Four orthogonal mechanisms were interacting across process and window-manager boundaries:
- Chromeβs Single-Process IPC (
SingletonLock): Chrome uses a single browser process per--user-data-dir. When Desktop 6 rangoogle-chrome-stable --profile-directory=Defaultwithout--new-window, it handed the YouTube URL over IPC to theDefaultbrowser window already opened on Desktop 3, opening a tab there instead of creating a window on Desktop 6. - Window ID Set-Diffing vs. Substring Matching:
wait_for_window("google chrome")matched Desktop 3βs existing window immediately. Taking a before/after snapshot of open Chrome window IDs viawmctrl -lx(get_chrome_window_ids()) isolates the exact hex ID of the newly created window. - Chromeβs Sticky
Preferences(browser.window_placement): TheBEFOREprobe caught the exact coordinates saved on disk in~/.config/google-chrome/Profile 8/Preferences:{'bottom': 1920, 'left': 2560, 'maximized': True, 'right': 3760, 'top': 0, 'work_area_left': 2560, 'work_area_right': 3760}. In GNOME/Mutter,workspaces-only-on-primarymeans the 7 virtual desktops exist only onHDMI-0(0 <= X < 2560). Any window that maps atX >= 2560sits onDP-5outside the 7-desktop ribbon.sanitize_chrome_preferences()inspects thePreferencesJSON before launch and clamps out-of-boundswindow_placementcoordinates back inside the primary monitor. - EWMH Maximized Geometry Lock: Under X11/Mutter, a maximized window ignores
wmctrl -e 0,X,Y,W,Hmove requests. Moving a window across monitors deterministically requires a four-step EWMH sequence:- Remove
maximized_vert,maximized_horz,fullscreen(wmctrl -i -r <id> -b remove,...). - Move and resize into the primary monitorβs bounding box (
wmctrl -i -r <id> -e 0,X,Y,W,H). - Pin to the target virtual desktop (
wmctrl -i -r <id> -t <desktop_idx>). - Re-apply
maximized_vert,maximized_horz(wmctrl -i -r <id> -b add,...).
- Remove
4. The 80/20 Whitespace Airlock in apply.py
In Turn 3, the Chrome placement patch failed to apply because four lines in autognome.py had trailing spaces on disk (including whitespace-only blank lines like ' ') that the browserβs copy-code button stripped, and a comment header had 15 spaces before # instead of 16. Worse, apply.pyβs diagnostic compared .lstrip() on both sides, so ' '.lstrip() == ''.lstrip() evaluated to True and printed "Every aligned line matches".
Adding _normalize_line_ws() and _rstrip_line_spans() to apply.py fixed both issues without weakening the airlock:
- Exact byte-for-byte matching still runs first.
- Only when exact matching returns
0hits does_rstrip_line_spans()check whether stripping trailing spaces/tabs and collapsing2+spaces before inline#comments yields exactly one unique match (len(rspans) == 1) with 100% identical leading indentation and line count. - And when
FIRST DIVERGENCEruns, if.lstrip()finds no difference, it falls back to exact line comparison so whitespace-only or indentation mismatches are pinpointed by line number andrepr().
5. Idempotent Clamping (Down x60) vs. Modulo-2 Toggling (m)
Why did pressing m leave the YouTube Live window audible on Desktop 6? Because m is a stateful modulo-2 toggle (\(s_{t+1} = (s_t + 1) \bmod 2\)), not an idempotent state assignment (\(s_{t+1} = 0\)). YouTube stores its mute state in localStorage (yt-player-volume); if the browser was muted when last closed, pressing m unmutes it. Moreover, [https://youtube.com/MikeLevin/live](https://youtube.com/MikeLevin/live) redirects to the active watch?v=... video URL, so "youtube" appears in the X11 window title before the playerβs keyboard listener finishes hydrating.
Replacing m with two bursts of 30 Down-arrow presses (xdotool key --repeat 30 --delay 25 Down) separated by 1.0s converts a fragile toggle into a clamped floor function:
Because 0% is an absorbing lower bound on the volume slider, driving it down is idempotent regardless of the starting volume, and firing two bursts across a 1-second gap guarantees that even if the /live redirect finishes hydrating mid-burst, the second burst clamps the player at 0%.
Dismount: The Seven Notary Beats
- VERIFY:
- Opening goals and mid-ride extensions: (1) Synchronize Piper TTS speech onset with the appearance of the Alacritty title card on each desktop; (2) eliminate multi-second pauses before typing into
gnome-terminalon Desktop 2; (3) prevent Chrome windows on Desktops 3, 5, and 6 from merging tabs or opening on the secondary portrait monitor (DP-5); (4) improveapply.pyto tolerate stripped trailing whitespace and inline comment padding without weakening leading-indentation or unique-match safety; and (5) silence the Desktop 6 YouTube Live stream idempotently. (The initial idea of movingscripts/card.pyinto/home/mike/repos/nixos/was explicitly dropped by the operator on Turn 2 once its Pipulate.venvdependenciespyfigletandrichwere inspected.) - Receipts confirming completion in THIS compile:
! rg -n "(--repeat|Down|key.*\"m\"|def mute_chrome_window)" /home/mike/repos/nixos/autognome.pyconfirmsdef mute_chrome_window(win_id, timeout=8.0):at line 938,["xdotool", "key", "--repeat", "30", "--delay", "25", "Down"]at line 969, andClamped YouTube volume to 0% (Down x60)at line 974.! .venv/bin/python /home/mike/repos/nixos/autognome.py --dry-run | tail -n 6exits0(β Dry run: 7 desktops, 3 terminals on the stack. No keys were harmed.).- Codebase and Git Diff Telemetry in this compile verify
_normalize_line_wsand_rstrip_line_spanslive inapply.py(lines 130β164, 424β428, 555β560),prep_speech/play_prepped_speech/spawn_terminal_window/hydrate_terminal_tabs/get_primary_monitor_geometry/sanitize_chrome_preferenceslive in/home/mike/repos/nixos/autognome.py, andprimary_monitor_geometry/move_windowlive in/home/mike/repos/nixos/scripts/chrome_windows.py. - Operator live ignition (
reboot+init) witnessed all 7 desktops, card/voice synchronization, primary-monitor Chrome placement, fast terminal hydration, and YouTube Live volume clamping working end-to-end.
- Opening goals and mid-ride extensions: (1) Synchronize Piper TTS speech onset with the appearance of the Alacritty title card on each desktop; (2) eliminate multi-second pauses before typing into
- BANK:
Car 1 (
foo_files.pyβ bank the2026-10-16dismount receipt at the top ofSTART RECEIPTSwhile fading the 21st line (2026-10-02 dismount THE DOOR SPEAKS) off the bottom to preserve the 20-line cap, graduate# Β§ CLAMP OVER TOGGLE (banked 2026-10-16), claim# ~/repos/nixos/scripts/chrome_windows.pyin Chapter II, and record the Desktop 2server.pystartup voice leak in the#todolist):
(nix) pipulate $
Target: foo_files.py
[[[SEARCH]]]
# --- START RECEIPTS (newest first; cap 20 lines; a line pushed past the cap is deleted, never moved -- git and the rolling pin are the archive) ---
# 2026-10-15 dismount ENDOSYNTHESIS, INSPECTOR GADGET, AND THE GO-GO-GADGET BROADCAST KIT (deeds qamy.ai_117-9c37ddfd.zip through qamy.ai_123-b4642777.zip; pipulate commits c1955e81, 10e44b6f, 7591d575, 57abd21f; Gemini 4 Argon High on every turn): endosynthesis (Atari 2600/VCS CX40 + Jay Miner TIA racing the beam, language as zero-mass prosthetic, Spider-Man + Ned Leeds / Inspector Gadget + Penny vs. Iron Man's sealed suit) contrasted against forced hybridization (Tusk, Brundlefly); Marvin Minsky's Society of the Mind, Ray Kurzweil's Making a Mind, Iain McGilchrist's Master and His Emissary, and Pangea's Enigmo bumpers mapped to high-wingspan context compilation; YouTube Live Mode B ("Cut-and-Roll") architected around the 12-hour VOD retention ceiling and constructive wave interference; Rung 1 Go-Go-Gadget Broadcast Kit landed in scripts/articles/common.py, scripts/articles/articleizer.py, and flake.nix (tweet, yttitle, ytdesc, ytcard + automatic cue card at the end of article and publish), template.md ASCII sort collision fixed via _DATED_POST_RE + _post_sort_key, and ### Ai Editorial Take promoted into ytdesc/ytcard. WITNESSED: common.py --broadcast card template.md (14/100 chars) -> 2026-10-15-phantom-limb-amiga-jack-tramiel-vim-nixos.md (88/100 chars) with π€ AI Editorial Take, interactive tweet/yttitle/ytdesc clipboard copies verified after exit + ndq, Honeybot OBS 32.0.1 window 0x01e00007 on DISPLAY=:10.0 and ~/.config/obs-studio/plugin_config/obs-websocket/config.json located, Ruff clean, deed qamy.ai_123-b4642777.zip.
[[[DIVIDER]]]
# --- START RECEIPTS (newest first; cap 20 lines; a line pushed past the cap is deleted, never moved -- git and the rolling pin are the archive) ---
# 2026-10-16 dismount DOUBLE-BUFFERED VOICE CARDS, PRIMARY-MONITOR EWMH PLACEMENT, AND CLAMP OVER TOGGLE (deed qamy.ai_146-7b1992d0.zip; pipulate commits 22c6c297, 13ae7840; nixos commits 511d493, 747cc5f, 06ec088, 2ae5a2c, 494a457, 57de511; Gemini 4 Argon High on every turn): Piper TTS synthesis split from playback in imports/voice_synthesis.py (synthesize_to_wav, play_wav, _resolve_play_cmd) and pre-warmed in autognome.py (ensure_voice, prep_speech, play_prepped_speech) so Alacritty title cards and speech fire simultaneously; gnome-terminal and Chrome pre-spawned underneath the raised card during speech (spawn_terminal_window, hydrate_terminal_tabs) to eliminate post-card terminal startup delay; multi-monitor Chrome placement onto HDMI-0 (2560x1440+0+0) vs. DP-5 (1200x1920+2560+0) fixed in autognome.py and chrome_windows.py via sanitize_chrome_preferences, --new-window, window-ID set-diffing, and EWMH unmaximize-move-pin-maximize; apply.py upgraded with _normalize_line_ws and _rstrip_line_spans fallback plus exact-line FIRST DIVERGENCE reporting; Desktop 6 YouTube Live volume clamped to 0% via two 30-key Down-arrow bursts (mute_chrome_window) replacing the non-idempotent 'm' toggle. WITNESSED: rg receipts on autognome.py (lines 938, 939, 969, 974) and apply.py (line 142), autognome.py --dry-run exit 0 (7 desktops, 3 terminals), live reboot + init across all 7 desktops verified, Ruff clean, deed qamy.ai_146-7b1992d0.zip.
# 2026-10-15 dismount ENDOSYNTHESIS, INSPECTOR GADGET, AND THE GO-GO-GADGET BROADCAST KIT (deeds qamy.ai_117-9c37ddfd.zip through qamy.ai_123-b4642777.zip; pipulate commits c1955e81, 10e44b6f, 7591d575, 57abd21f; Gemini 4 Argon High on every turn): endosynthesis (Atari 2600/VCS CX40 + Jay Miner TIA racing the beam, language as zero-mass prosthetic, Spider-Man + Ned Leeds / Inspector Gadget + Penny vs. Iron Man's sealed suit) contrasted against forced hybridization (Tusk, Brundlefly); Marvin Minsky's Society of the Mind, Ray Kurzweil's Making a Mind, Iain McGilchrist's Master and His Emissary, and Pangea's Enigmo bumpers mapped to high-wingspan context compilation; YouTube Live Mode B ("Cut-and-Roll") architected around the 12-hour VOD retention ceiling and constructive wave interference; Rung 1 Go-Go-Gadget Broadcast Kit landed in scripts/articles/common.py, scripts/articles/articleizer.py, and flake.nix (tweet, yttitle, ytdesc, ytcard + automatic cue card at the end of article and publish), template.md ASCII sort collision fixed via _DATED_POST_RE + _post_sort_key, and ### Ai Editorial Take promoted into ytdesc/ytcard. WITNESSED: common.py --broadcast card template.md (14/100 chars) -> 2026-10-15-phantom-limb-amiga-jack-tramiel-vim-nixos.md (88/100 chars) with π€ AI Editorial Take, interactive tweet/yttitle/ytdesc clipboard copies verified after exit + ndq, Honeybot OBS 32.0.1 window 0x01e00007 on DISPLAY=:10.0 and ~/.config/obs-studio/plugin_config/obs-websocket/config.json located, Ruff clean, deed qamy.ai_123-b4642777.zip.
[[[REPLACE]]]
Target: foo_files.py
[[[SEARCH]]]
# 2026-10-02 dismount THE DOOR SPEAKS (deeds 1773, 1778 and 1779, 1774 through 1777 compiles that rode no turn, INFERRED; pipulate commits 43006cba the door table, 3f09904d the glossary, 46118ec0 the router, 34bbaa4b and 30dcbaec the 2.74 Doorways release, cut ahead of the router's HOLD THE TRUCK note, which gave the folder half its witness, d5085d68 the boundary, 868425bd the reader, 869637dc the gate, the spelled folder, the Lix line and osb, daaac215 stick_bug.py, 8149a74c the glossary, 5dce5740 the router, the five cars pushed by hand): one installer, three doors; the stamp picks a row in install.sh, the row names the folder (qamy.ai installs ~/qamyai) and writes the door's words to .door, and first_contact() says them. WITNESSED: Qamyai -> ~/qamyai at 1778; at 1779 the Mac's door card, both welcome lines, welcomed=2026-10-01 and a menu-only entry after it, the first entry after the pull having run the runScript built before it; the Mac's entry line the same under the old version sed and the new, Prime's (Nix) 2.28.5 under the new; osb by hand on Prime and its readings in the compile lane; BEFORE rc=1, 2 and 1, AFTER 0, 0 and 0. MISSES: Topological Integrity forecast 8, read 15 (it counts commented paths); welcomed= forecast from the UTC date, a day ahead of the Mac's; Step 2 chained a one-time mv to nix develop with &&, so one paste at the wrong prompt cost four tries (THE STEP THAT RUNS ONCE). UNWITNESSED: the welcomed branch, n and silent with a door, the download lines at a door, the spelled folder heard, any sound, Lix, and the npvg.org and pipulate.com cards at a real door. The block now reads 21 against its cap of 20; the next forget fades the bottom line.
# --- END RECEIPTS ---
[[[DIVIDER]]]
# --- END RECEIPTS ---
[[[REPLACE]]]
Target: foo_files.py
[[[SEARCH]]]
# Β§ THE HIPPOCAMPUS COVENANT (banked 2026-10-15, deed 125) -- Treat the plain-text repository as the externalized hippocampus for ephemeral forward-pass instances whose KV-cache dies when the session closes: consolidate every hard-won episodic lesson into a deterministic patch or a '# Β§' key before gessoing the canvas, and never let expanding the workshop delay shipping the audit deliverables that sustain it.
[[[DIVIDER]]]
# Β§ THE HIPPOCAMPUS COVENANT (banked 2026-10-15, deed 125) -- Treat the plain-text repository as the externalized hippocampus for ephemeral forward-pass instances whose KV-cache dies when the session closes: consolidate every hard-won episodic lesson into a deterministic patch or a '# Β§' key before gessoing the canvas, and never let expanding the workshop delay shipping the audit deliverables that sustain it.
# Β§ CLAMP OVER TOGGLE (banked 2026-10-16, deed 146) -- Never use a modulo-2 toggle keystroke (such as 'm' for mute) to enforce a target state when the initial state is persisted across sessions; drive the control against a clamped boundary (such as repeating Down-arrow to 0% volume) or pass an explicit state value so repeated actuation is idempotent.
[[[REPLACE]]]
Target: foo_files.py
[[[SEARCH]]]
# ~/repos/nixos/bookmarks.nix
# ~/repos/nixos/scripts/bookmarks_sync.py
[[[DIVIDER]]]
# ~/repos/nixos/bookmarks.nix
# ~/repos/nixos/scripts/bookmarks_sync.py
# ~/repos/nixos/scripts/chrome_windows.py
[[[REPLACE]]]
Target: foo_files.py
[[[SEARCH]]]
# #todo #to-do #earmarks
# - TODO (2026-10-15, FORGETTING): Right now, `foo_files.py` operates as an append-only Lamarckian genome where each dying forward pass banks a new `# Β§` scar (now at 224 keys) to steer its descendants. Because transformer self-attention is a zero-sum softmax distribution across a finite budget, an externalized hippocampus that only appends without periodic synaptic pruning will eventually dilute attention across older rules. The next evolution of the Hippocampus Covenant is not just banking the lesson before the KV-cache clears, but scheduling periodic βREM garbage collectionβ passes where the model compacts and retires superseded `# Β§` keys.
[[[DIVIDER]]]
# #todo #to-do #earmarks
# - TODO (2026-10-16, DESKTOP 2 PIPULATE SERVER STARTUP AUDIO): During `autognome.py` Desktop 2 initialization, `PIPULATE_VOICE=0 nix develop` suppresses voice inside the foreground shellHook, but if `run-server` / `server.py` spawns in a detached tmux session or reads `~/.config/pipulate/voice` (`yes`) without inheriting `PIPULATE_VOICE=0`, the Pipulate server startup narration still plays audibly; trace `flake.nix` tmux server launch and `server.py` startup voice gate so `PIPULATE_VOICE=0` (or an explicit quiet startup flag) silences background server boot during `init`.
# - TODO (2026-10-15, FORGETTING): Right now, `foo_files.py` operates as an append-only Lamarckian genome where each dying forward pass banks a new `# Β§` scar (now at 224 keys) to steer its descendants. Because transformer self-attention is a zero-sum softmax distribution across a finite budget, an externalized hippocampus that only appends without periodic synaptic pruning will eventually dilute attention across older rules. The next evolution of the Hippocampus Covenant is not just banking the lesson before the KV-cache clears, but scheduling periodic βREM garbage collectionβ passes where the model compacts and retires superseded `# Β§` keys.
[[[REPLACE]]]
- DANGLING:
- Desktop 2
server.pystartup audio duringinit:PIPULATE_VOICE=0 nix developsilences the foreground shellHook on Desktop 2, but the backgroundserver.pyprocess (or tmux session) still speaks on boot because it either does not inheritPIPULATE_VOICE=0across the tmux boundary or checks~/.config/pipulate/voicedirectly; banked as aTODOinfoo_files.pyabove. - Combined Figlet +
figurateASCII-art station cards (Inspector Gadget / Sorcererβs Apprentice):measure_figlet()inimports/ascii_displays.pyalready documents the union-geometry hook for stacking a Figlet banner above a registeredfigurateASCII asset on a single Alacritty card; deferred to a future theatrical polish ride. - Dead
_legacy_launch_chrome_profile_unusedandclose_managed_windows/find_managed_windowshelpers in/home/mike/repos/nixos/autognome.py: Kept harmlessly uncalled after--resetwas removed; can be pruned the next timeautognome.pyis in the patch train.
- Desktop 2
- SEED:
(nix) pipulate $
context.txt
flake.nix
server.py
imports/voice_synthesis.py
~/repos/nixos/autognome.py
foo_files.py
! rg -n "PIPULATE_VOICE|voice_consent|speak_text" flake.nix server.py imports/voice_synthesis.py /home/mike/repos/nixos/autognome.py
- CLOSING: A morning startup script is usually treated as plumbingβa rough shell script that throws windows onto the screen and hopes the window manager sorts them out. When you treat the workspace instead as a cybernetic cockpitβan exo-suit that unfurls seven fixed-position instruments onto the primary ribbon at the start of every dayβtiming, spatial determinism, and acoustic hygiene stop being cosmetic details and become part of the instrumentβs ergonomics. By borrowing double buffering from classic demo-scene graphics, occluded pre-spawning from stagecraft, four-step EWMH geometry locks from X11 window management, trailing-whitespace normalization from deterministic patch airlocks, and clamped floor functions over stateful toggles, βseven at one swatβ stops feeling like a script fighting race conditions and starts feeling like a single, unified mechanical action.
- NOTARIZE:
- Input archive deed:
Deed: qamy.ai_146-7b1992d0.zip(read from the=== QA ARCHIVE SEALfooter outside the payload of this compile; names the input archive compiled for this turn, not this response or the downstream Jekyll post). - Verification status: Not independently verified here (no
python scripts/foo_cartridge.py qamy.ai_146-7b1992d0.zipverifier receipt is present inside this compileβs Manifest). - Live-command receipts in this Manifest: 2 total (
! rg -n "(--repeat|Down|key.*\"m\"|def mute_chrome_window)" /home/mike/repos/nixos/autognome.py-> 98 tokens / 324 bytes in0.0120s;! .venv/bin/python /home/mike/repos/nixos/autognome.py --dry-run | tail -n 6-> 63 tokens / 212 bytes in0.0897s; 0 failures). - Recorded payload metrics (from Summary & Manifest):
241,107summed tokens,242,791verified tokens,102,699words,950,853chars,969,566UTF-8 bytes; Codex Mapping Coverage71.5% (191/267 tracked files); Ruff static analysisAll checks passed! (exit 0); Honeybot telemetry fetched2026-10-16T07:07Z(1,548articles).
- Input archive deed:
Deed: qamy.ai_146-7b1992d0.zip
Book Analysis
Ai Editorial Take
What stands out most in this entry is not just that the title card and voice were synchronized, but that the title cardβs role inverted completely between Turn 1 and Turn 5. At the start of the ride, the Alacritty Figlet card was pure decorationβan extra process that exposed how slow TTS and terminal startup were. By Turn 5, because the card carries the EWMH _NET_WM_STATE_ABOVE hint, it became a functional load-bearing curtain: the 2.5 seconds Piper spends speaking the desktopβs name is now the exact time budget used to mask the ugly, asynchronous window-mapping and shell-hydration jitter of Chrome and gnome-terminal underneath. What looked like theatrical flair turned out to be the perceptual buffer that makes a multi-process Linux desktop feel instantaneous.
π¦ X.com Promo Tweet
Double buffering for the desktop: pre-rendering Piper TTS WAVs behind Alacritty cards, pinning Chrome via X11 EWMH, and clamping YouTube volume to zero.
https://mikelev.in/futureproof/double-buffered-voice-cards-ewmh-clamp-over-toggle/
#NixOS #Linux #Python
Title Brainstorm
- Title Option: Double-Buffered Voice Cards, Primary-Monitor EWMH Placement, and Clamp Over Toggle
- Filename:
double-buffered-voice-cards-ewmh-clamp-over-toggle.md - Rationale: Captures all three concrete engineering breakthroughs from the seven-turn sessionβback-buffered TTS/card synchronization, multi-monitor X11/EWMH window placement, and idempotent volume clampingβusing precise technical terms.
- Filename:
- Title Option: Page-Flipping the Desktop: Synchronizing Piper TTS, Alacritty Cards, and Multi-Monitor Chrome
- Filename:
page-flipping-piper-tts-alacritty-ewmh-workspace.md - Rationale: Leads with the classic graphics metaphor (βpage-flippingβ) that unlocked both the audio-visual synchronization and the occluded background spawning of terminals and browsers.
- Filename:
- Title Option: Pre-Spawning Behind the Curtain: Zero-Latency Voice Cards and Idempotent X11 Automation
- Filename:
pre-spawning-behind-the-curtain-piper-tts-x11.md - Rationale: Highlights the stagecraft insight of using the Always-On-Top Alacritty card as an visual occluder while
gnome-terminaland Chrome warm up underneath.
- Filename:
- Title Option: Clamp Over Toggle: Double-Buffered Desktop Theatrics and Multi-Monitor X11 Hygiene
- Filename:
clamp-over-toggle-double-buffered-desktop-theatrics.md - Rationale: Foregrounds the generalizable control-theory rule (βClamp Over Toggleβ) alongside the audio-visual double-buffering methodology.
- Filename:
Content Potential And Polish
- Core Strengths:
- Concrete cross-domain transfer: mapping hardware graphics double buffering (back-buffer rendering + VBLANK page flip) onto neural TTS
.wavsynthesis and terminal window spawning. - Real wire-truth receipts: catching the exact off-screen
Profile 8/PreferencesJSON coordinates (left: 2560, right: 3760) and proving why Mutterβsworkspaces-only-on-primarypolicy stranded Chrome outside the 7-desktop ribbon. - Self-healing tooling: diagnosing why browser copy-paste stripped trailing spaces in Turn 3 and immediately upgrading
apply.pywith_rstrip_line_spans()so strictness and ergonomics coexist. - Clear mathematical distinction between a stateful modulo-2 toggle (
m) and an absorbing boundary clamp (Downarrow repeated 30 times).
- Concrete cross-domain transfer: mapping hardware graphics double buffering (back-buffer rendering + VBLANK page flip) onto neural TTS
- Suggestions For Polish:
- Notice that in the final Gemini turn, the first few paragraphs under β### For the Reader Joining Cold: What Just Happened Hereβ cut off mid-sentence (βand half of aContext verified.β) before restarting cleanly under β### What Happened Under the Hood: Five Engineering Lessons for the Readerβ; trimming that aborted first draft stub will make the closing section read seamlessly.
- Consider adding a brief note in the book version explaining how
wmctrl -lxclass matching (conjure_window_overlay) interacts with Wayland vs. X11 sessions for readers running newer GNOME defaults.
Next Step Prompts
- Trace
PIPULATE_VOICE=0acrossautognome.py,flake.nix, andserver.pyso the background Pipulate server spawned during Desktop 2 initialization respects quiet-boot state without muting interactive post-boot use. - Extend
scripts/card.pyandmeasure_figlet()inimports/ascii_displays.pyto support optionalfigurateASCII art bodies beneath the Figlet header using the union-bounding-box calculation.