yog 0.0.4

yog: a balls-oriented session manager for lernie loops (egui frontend)
Documentation
#!/bin/sh
# run-main — launch the installed `yog` and keep the window equal to main's tip.
#
# The machinery behind `make run` (the Makefile is the door, as with `drive`).
# It launches `$INSTALL_BIN/yog` in the FOREGROUND and relaunches it whenever
# the installed binary changes underneath it, so a merge landing on main closes
# the old window and opens one built from the new tip, with no operator verb in
# between.
#
# Usage (all three positional, as the Makefile passes them — it owns the paths):
#   run-main <binary> <install-stamp> [workspace]
#
# It watches the STAMP, not the clock and not the binary's mtime: the stamp is
# the ONE record of which commit the installed binary was built from (written by
# `make install`, named by `make print-install-stamp`), so "the window is stale"
# is a string compare against a fact somebody else already established, and a
# half-written binary is never launched — the stamp moves after the rename.
#
# It also DISPATCHES `scripts/install-main` when main's tip is not what is
# installed. That is the same idempotent convergence `.githooks/
# reference-transaction` fires, so nothing is duplicated by running both; it is
# here so the verb stands on its own in a clone whose hooks were never armed
# (`make install-hooks`), and so a hook that failed or was missed still
# converges the moment you look at the window. Dispatch is once per tip: a
# build that fails warns and then waits for main to move again, rather than
# rebuilding every poll.
#
# It deliberately does NOT write YOG_PIDFILE. `make reload` kills the pid
# recorded there and launches a DETACHED replacement — beside this foreground
# supervisor that is two windows, both live, on one world. `run` owns its own
# relaunch; `ux`/`reload` own the pidfile instance. The two never see each other.
set -u

bin=${1:?run-main: no binary}
stamp=${2:?run-main: no install stamp}
ws=${3:-}

here=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) || exit 1
root=$(CDPATH= cd -- "$here/.." && pwd) || exit 1
poll=${YOG_RUN_POLL:-2}

installed() { cat "$stamp" 2>/dev/null; }
tip() { git -C "$root" rev-parse --verify --quiet refs/heads/main 2>/dev/null; }
short() { echo "$1" | cut -c1-12; }

# A tip we have already asked install-main to build. Seeded empty so the first
# poll converges a stale (or absent) install without waiting for a merge.
asked=

# Nothing to launch at all: converge synchronously, since there is no window to
# keep up while a detached build runs.
if [ ! -x "$bin" ]; then
  echo "run: $bin is not installed yet — building main first" >&2
  asked=$(tip)
  "$root/scripts/install-main" __build "$root" || {
    echo "run: build of main failed; see $root/${CARGO_TARGET_DIR:-target}/cicd-install.log" >&2
    exit 1
  }
fi

trap 'kill "${pid:-}" 2>/dev/null; exit 130' INT TERM

while :; do
  launched=$(installed)
  echo "run: launching $bin @ $(short "${launched:-unknown}")" >&2
  if [ -n "$ws" ]; then
    "$bin" --workspace "$ws" &
  else
    "$bin" &
  fi
  pid=$!

  restart=
  while kill -0 "$pid" 2>/dev/null; do
    sleep "$poll"
    now=$(tip)
    if [ -n "$now" ] && [ "$now" != "$(installed)" ] && [ "$now" != "$asked" ]; then
      asked=$now
      echo "run: main moved to $(short "$now") — building; the window stays up" >&2
      "$root/scripts/install-main"
    fi
    if [ "$(installed)" != "$launched" ]; then
      restart=yes
      kill "$pid" 2>/dev/null
      break
    fi
  done
  # stderr silenced: when the swap above killed it, the shell's own job-control
  # "Terminated" line reads as a crash to an operator who asked for a relaunch.
  wait "$pid" 2>/dev/null

  # Only a swap relaunches. A window the operator closed (or that crashed) ends
  # the session — `make run` is a foreground verb, not a service manager.
  [ -n "$restart" ] || exit 0
done