slate: pty terminal default + runtime tmux toggle; loopback owner-pairing; slate-shell node renderer + serve auto-launch
Three stream-readiness changes (ship-prep, HARD STOP before merge/tag).
1. Terminal default → pure pty; tmux opt-in via a runtime setting (slate/main.sgl): new persisted app-db setting slate/terminals-use-tmux (default off). tmux-available? reads it live, so a fresh terminal honors the current value with no rebuild (the live-demo affordance). Command-palette toggle + set-options! settable. tmux code untouched; removed the unused hardcoded tmux-backing.
2. Silent owner-pairing default on a loopback bind (slate-cli config/main): silent-pair-grants-for-host keys the grant posture on the bind address — loopback → full owner posture (fs:rw:~ fs:ro:/ exec:full pty:on) with no env var, so opening a terminal works out of the box; non-loopback → the safe restricted default. New --host serve option; banner reports the mode. SLATESILENTPAIR_OWNER preserved as an override. +7 unit tests.
3. slate-shell node renderer + serve auto-launch (slate-shell/main, slate-cli launch/main): slate-shell --url <origin> is a pure webview onto a running node (no lantern-system/services); bare slate-shell keeps the embedded standalone fallback. slate serve auto-launches slate-shell when on $PATH; it is also rung 1 of the renderer ladder. (Live window render needs a companion lantern nav-handler fix, landing via dev-redirect.)
packages/slate-cli/src/slate-cli/config.sgl | 37 ++++++++++++++++++++++++++++++++++++-
packages/slate-cli/src/slate-cli/launch.sgl | 34 +++++++++++++++++++++++++++++++++-
packages/slate-cli/src/slate-cli/main.sgl | 71 +++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
packages/slate-cli/test/test-silent-pair.sgl | 43 +++++++++++++++++++++++++++++++++++++++++++
packages/slate-shell/src/slate-shell/main.sgl | 30 ++++++++++++++++++++++++++++--
packages/slate/src/slate/main.sgl | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
6 files changed, 254 insertions(+), 34 deletions(-)packages/slate-cli/src/slate-cli/config.sglmodified
default-silent-pair-grants owner-silent-pair-grants served-origins-for-port config-with-served-origins) config-with-served-origins loopback-host? silent-pair-grants-for-host) (begin (and (>= (string-length s) (string-length prefix)) (string=? (substring s 0 (string-length prefix)) prefix))) ;; A bind HOST names the loopback interface iff it is one of the local ;; loopback authorities. Only a loopback bind gets the owner silent-pair ;; posture by default (Change 3, David 2026-07-24): the sole client that can ;; reach a loopback bind is a process on THIS machine, so auto-granting the ;; owner posture is safe. A non-loopback bind (0.0.0.0 / a network interface) ;; is NOT loopback, so it keeps the restricted default unless the owner ;; explicitly opts in — the node never hands full owner access to a ;; potentially network-reachable client without a deliberate choice. (define (loopback-host? host) (or (string=? host "127.0.0.1") (string=? host "localhost") (string=? host "::1") (string=? host "[::1]"))) ;; The silent-pair grant posture for a given BIND HOST (Change 3, David ;; 2026-07-24). Keyed on the bind address, NOT an env var: ;; - a loopback bind (the default `slate serve`) → the full OWNER posture, ;; so the local "try it" / auto-launched slate-shell experience works with ;; no env var (opening a terminal needs pty:on + exec:full); ;; - a non-loopback bind (0.0.0.0 / a network interface) → the safe ;; restricted default (fs:rw:~ only); owner caps are never auto-granted to ;; a potentially network-reachable client; ;; - SLATE_SILENT_PAIR_OWNER=1 remains an explicit override that forces the ;; owner posture regardless of bind (preserved; no longer REQUIRED locally). (define (silent-pair-grants-for-host host) (if (or (owner-env-override?) (loopback-host? host)) owner-silent-pair-grants default-silent-pair-grants)) (define (owner-env-override?) (let ((v (getenv "SLATE_SILENT_PAIR_OWNER"))) (and v (or (string=? v "1") (string=? v "true") (string=? v "yes"))))) ;; $XDG_STATE_HOME/slate, else ~/.local/state/slate. Windows ;; (%LOCALAPPDATA%) is a named follow-up; here we target POSIX. (define (discover-state-dir)packages/slate-cli/src/slate-cli/launch.sglmodified
open-renderer! detect-chromium chromium-candidates build-app-argv) build-app-argv detect-slate-shell serve-launch-shell!) (begin ;; ---- rung 1: Slate Shell (the native window, design §3.2) ------------- ;; The slate-shell binary (the ex-slate-desktop Lantern wrapper) renders a ;; running node's origin as a chromeless native window. It is preferred over ;; a browser when installed. `slate-shell --url <origin>` is its node- ;; renderer role (a pure webview onto the node, system access over the WS). (define (detect-slate-shell) (command-exists? "slate-shell")) (define (slate-shell-argv url) (list "--url" url)) ;; PATH probe order (design §3.2 / spike probe 2). google-chrome ;; first (the spike's environment), then the common chromium builds. (define chromium-names (define (launch-plan url) (let ((chrome (detect-chromium))) (cond ;; Rung 1: the native Slate Shell window when installed (design §3.2). ((detect-slate-shell) #{ rung: 'slate-shell browser: "slate-shell" url: url argv: (slate-shell-argv url) }) (chrome #{ rung: 'chromium-app browser: chrome (let* ((plan (launch-plan url)) (rung (dict-ref plan rung: 'print))) (cond ((eq? rung 'slate-shell) (announce "Opening Slate in a native window (slate-shell)…") (unless (spawn-detached "slate-shell" (dict-ref plan argv: '())) (print-url url))) ((eq? rung 'chromium-app) (announce (string-append "Opening Slate in " (dict-ref plan browser: "chrome") " (app mode)…")) ;; win: App Paths registry probe -> chrome.exe --app=<url>, else ;; (spawn-detached "cmd" (list "/c" "start" "" url)) ;;; `slate serve` hook: if slate-shell is installed, auto-launch it as a ;;; node renderer onto `url` (the click-to-run local-app experience). Unlike ;;; open-renderer!, this does NOT fall back to a browser — serve stays ;;; headless when slate-shell is absent (the caller already printed the ;;; origin). Returns #t iff the shell was spawned. (define (serve-launch-shell! url) (and (detect-slate-shell) (begin (announce "slate-shell found on PATH — opening the native window…") (or (spawn-detached "slate-shell" (slate-shell-argv url)) (begin (announce " (slate-shell failed to spawn; staying headless)") #f))))) (define (spawn-detached cmd argv) (guard (e (#t #f)) (let ((p (apply process-spawn cmd argv)))packages/slate-cli/src/slate-cli/main.sglmodified
(define (run-serve! args) (with-async (let* ((opts (parse-serve-opts args)) (host (bind-host-of opts)) (config (config-from-opts opts))) (assert-serving-safe! config) (let* ((candidates (port-candidates-for opts config)) (bound (bring-up-serve! config candidates))) (bound (bring-up-serve! config host candidates))) (cond ((not bound) (error "slate serve: no loopback port available" candidates)) (else (print-serve-banner config (car bound) (caddr bound)) (print-serve-banner config host (car bound) (caddr bound)) ;; Change 2: auto-launch slate-shell as a native node renderer ;; when it is on $PATH (the click-to-run local-app experience); ;; stay headless (just the banner above) when it is absent. (serve-launch-shell! (origin-url (car bound))) (forever-sleep))))))) (define (run-launch! args) (with-async (let* ((opts (parse-serve-opts args)) (host (bind-host-of opts)) (config (config-from-opts opts))) (assert-serving-safe! config) (let* ((candidates (port-candidates-for opts config)) (bound (bring-up-serve! config candidates))) (bound (bring-up-serve! config host candidates))) (cond (bound (print-serve-banner config (car bound) (caddr bound)) (print-serve-banner config host (car bound) (caddr bound)) (open-renderer! (origin-url (car bound))) (forever-sleep)) (else ;; Bind on the first free candidate port, start the background loops ;; (revocation watch + session reaper), and return (port listener ;; daemon), or #f when every candidate is busy. (define (bring-up-serve! config candidates) (define (bring-up-serve! config host candidates) (let* ((store (origins-load! (make-origins-store (origins-path config)))) (pstate (make-pair-state)) (registry (make-conn-registry)) (hub (make-session-hub)) (instance (make-instance-id)) (bound (try-bind candidates config store pstate registry hub instance))) (bound (try-bind host candidates config store pstate registry hub instance))) (when bound ;; Revocation support (finding #1): reload origins.json every ;; tick + drop live connections whose origin was revoked. (start-reaper! hub)) bound)) (define (try-bind ports config store pstate registry hub instance) (define (try-bind host ports config store pstate registry hub instance) (let loop ((ps ports)) (cond ((null? ps) #f) (let* ((port (car ps)) (daemon (make-daemon port instance config store pstate registry hub)) (listener (guard (e (#t #f)) (start-listener! loopback-host port daemon (start-listener! host port daemon (daemon-handlers))))) (if listener (list port listener daemon) (define (origin-url port) (string-append "http://localhost:" (number->string port) "/")) (define (print-serve-banner config port daemon) (define (print-serve-banner config host port daemon) (let ((store (dict-ref daemon origins: #f)) (instance (dict-ref daemon instance: ""))) (display "===========================================================\n") (display " Slate — local capability daemon\n") (display (string-append " origin : " (origin-url port) "\n")) (display (string-append " node ws : ws://" loopback-host ":" (display (string-append " node ws : ws://" host ":" (number->string port) "/node/ws (/ws alias)\n")) (display (string-append " app mode : " (if (config-app-dir config) (string-append "proxy " (config-app-upstream config))) "\n")) (display (string-append " instance : " instance "\n")) ;; Change 3: on a loopback bind the owner is silently paired with the ;; full owner posture (no env var). A non-loopback bind keeps the ;; explicit pairing gate — flag which mode this run is in. (display (string-append " pairing : " (if (loopback-host? host) "loopback — owner auto-paired (silent)" "non-loopback — explicit pairing required") "\n")) (display (string-append " paired : " (number->string (length (origins-list store))) " origin(s)\n")) ;; ---- option parsing (shared by serve + launch) ----------------- ;; #{ app-dir: port: app-upstream: } (define (parse-serve-opts args) (let loop ((a args) (opts #{ app-dir: #f port: #f app-upstream: #f silent-pair?: #t })) (let loop ((a args) (opts #{ app-dir: #f port: #f app-upstream: #f silent-pair?: #t host: #f })) (cond ((null? a) opts) ((string=? (car a) "--no-silent-pair") (loop (cdr a) (dict-set opts silent-pair?: #f))) ;; --host <addr>: the bind address (default loopback 127.0.0.1). Only a ;; loopback bind gets owner silent-pairing by default (Change 3); a ;; non-loopback bind (0.0.0.0 / a network interface) is an explicit, ;; advanced choice that keeps the restricted posture. NB: the Host-header ;; gate still restricts reachable authorities to the loopback names, so a ;; non-loopback bind is not, by itself, a network-exposed serve. ((string=? (car a) "--host") (if (pair? (cdr a)) (loop (cddr a) (dict-set opts host: (cadr a))) (opt-error "--host needs an address"))) ((string=? (car a) "--app-dir") (if (pair? (cdr a)) (loop (cddr a) (dict-set opts app-dir: (cadr a))) n (opt-error (string-append "invalid port: " s))))) ;; The silent-pair grant posture (§8-Q2). The shipped PUBLIC default is ;; the safe `fs:rw:~` only; a machine owner opts into the full owner ;; posture (exec/pty/read-all) explicitly with SLATE_SILENT_PAIR_OWNER ;; — a deliberate per-machine choice, NOT a stray env that silently ;; widens caps (it only ever ADDS to the owner's own machine; the config ;; `silent-pair-grants:` key is the general seam a config loader will use). ;; The effective bind host for this run: --host wins, else loopback. (define (bind-host-of opts) (or (dict-ref opts host: #f) loopback-host)) ;; The silent-pair grant posture (§8-Q2, Change 3 2026-07-24) is keyed on the ;; BIND ADDRESS by `silent-pair-grants-for-host` (in (slate-cli config), next ;; to the grant constants): a loopback bind (the default) → full owner posture ;; with no env var; a non-loopback bind → the safe restricted default; ;; SLATE_SILENT_PAIR_OWNER stays an explicit override. ;; MULTI-USER CAVEAT: on a shared machine another local user could in principle ;; connect to the loopback port — but the peer-UID accept gate refuses a ;; different-user process before a byte is read, so owner silent-pairing stays ;; bound to the same Unix user that runs the daemon. (define (config-from-opts opts) (make-config dev-mode?: (env-flag? "SLATE_DEV") app-dir: (dict-ref opts app-dir: #f) app-upstream: (dict-ref opts app-upstream: #f) silent-pair-enabled?: (dict-ref opts silent-pair?: #t) silent-pair-grants: (if (env-flag? "SLATE_SILENT_PAIR_OWNER") owner-silent-pair-grants #f))) silent-pair-grants: (silent-pair-grants-for-host (bind-host-of opts)))) ;; Port candidates: an explicit --port wins; else local-build mode ;; (--app-dir) defaults to 32650 (a DISTINCT origin from proxy mode, (display " --app-dir <path> serve a local PWA build (default port 32650)\n") (display " --app-upstream <url> reverse-proxy upstream (default https://useslate.org)\n") (display " --port <n> bind a specific port\n") (display " --host <addr> bind address (default 127.0.0.1; a loopback\n") (display " bind auto-pairs the owner with the full posture)\n") (display " --no-silent-pair disable §2.5 auto-pairing of the served origin\n")) ))packages/slate-cli/test/test-silent-pair.sglmodified
(make-config state-dir: dir silent-pair-grants: owner-silent-pair-grants) PORT))) (assert-true (string-contains-spec? (grants-for-origin config served-http) "exec:full")))));; ============================================================;; Change 3 (David 2026-07-24): silent owner-pairing is the DEFAULT on a;; loopback bind, keyed on the BIND ADDRESS (no env var). A non-loopback;; bind keeps the safe restricted default. This is what makes the local;; "try it" / auto-launched slate-shell experience work — opening a;; terminal needs pty:on + exec:full, which the old default withheld.;; NB: these run with SLATE_SILENT_PAIR_OWNER unset in the test env, so;; the branches are purely bind-address-driven.;; ============================================================(test-group "Change 3 loopback bind → owner silent-pair posture" (test "loopback host detection" (assert-true (and (loopback-host? "127.0.0.1") (loopback-host? "localhost") (loopback-host? "::1")))) (test "0.0.0.0 / a network interface is NOT loopback" (assert-false (or (loopback-host? "0.0.0.0") (loopback-host? "192.168.1.10")))) (test "loopback bind → the FULL owner posture (exec:full + pty:on)" (let ((g (silent-pair-grants-for-host "127.0.0.1"))) (assert-true (and (string-contains-spec? g "exec:full") (string-contains-spec? g "pty:on"))))) (test "localhost bind → owner posture too" (assert-true (string-contains-spec? (silent-pair-grants-for-host "localhost") "pty:on"))) (test "non-loopback bind → the safe restricted default (no exec/pty)" (let ((g (silent-pair-grants-for-host "0.0.0.0"))) (assert-true (string-contains-spec? g "fs:rw:~")) (assert-false (string-contains-spec? g "exec:full")) (assert-false (string-contains-spec? g "pty:on")))) (test "the loopback owner posture flows to the served origin end-to-end" (let* ((dir (make-temp-directory)) (config (config-with-served-origins (make-config state-dir: dir silent-pair-grants: (silent-pair-grants-for-host "127.0.0.1")) PORT))) (assert-true (string-contains-spec? (grants-for-origin config served-http) "pty:on")))) (test "a non-loopback served daemon withholds pty from the served origin" (let* ((dir (make-temp-directory)) (config (config-with-served-origins (make-config state-dir: dir silent-pair-grants: (silent-pair-grants-for-host "0.0.0.0")) PORT))) (assert-false (string-contains-spec? (grants-for-origin config served-http) "pty:on")))));; ============================================================;; MEDIUM-1 (Fable): the silent-pair kill switch. With silent pairing;; disabled, a served origin is NOT eligible (falls through to normalpackages/slate-shell/src/slate-shell/main.sglmodified
(begin (define (usage) (display "Usage: slate [--session NAME] launch the Slate window\n") (display "Usage: slate --url <origin> render a running slate node (e.g. http://localhost:32640)\n") (display " slate [--session NAME] launch the Slate window\n") (display " slate open <path> [--session NAME] open a file in a running Slate\n") (display " slate run <cmd> [--session NAME] run a command in a running Slate\n") (display " slate eval <expr> [--session NAME] eval an expression there\n") services: (list (make-slate-client-service session))) title: "Slate" width: 1200 height: 800 transparent: #t)) ;; ---- node-renderer role (the pivot's canonical Shell) ----------------- ;; `slate-shell --url http://localhost:<port>` opens a native window that is ;; a PURE RENDERER onto a running slate node's origin (design ;; [[topics/slate-distribution-architecture]] §1.3). lantern-app's assets: ;; field loads an http(s) URL directly (no lantern:// scheme), so the window ;; shows the SAME PWA the node serves, connecting to the node's WS ;; same-origin — exactly like a browser tab at that URL, just chromeless and ;; native. System access (fs/pty) flows over the node, NOT lantern-system, so ;; this role loads NO lantern-system plugin and NO slate-client service (both ;; speak the lantern:// bridge a node-origin page never uses). This is what ;; `slate serve` auto-launches for the click-to-run local-app experience. (define (run-server-url url) (lantern-run (lantern-app name: "Slate" assets: url modules: (list 'core)) title: "Slate" width: 1200 height: 800 transparent: #t)) ;; ---- client role ------------------------------------------------------ ;; A client subcommand takes ONE positional (the path/cmd/expr) plus an ;; optional --session. Missing positional → usage error. ;; runs the window loop and exits via the quit watcher, so it does not exit here. (define (main . _) (let* ((argv (command-line)) (args (if (pair? argv) (cdr argv) (list)))) (args0 (if (pair? argv) (cdr argv) (list))) ;; `--url <origin>` selects the node-renderer role (a pure webview ;; onto a running slate node). It is server-only: when present we ;; render that origin and ignore client subcommands. (pu (extract-opt args0 "--url")) (url (car pu)) (args (cdr pu))) (cond (url (run-server-url url)) ((null? args) (run-server #f)) ((string=? (car args) "open") (exit (run-client "open" path: (cdr args)))) ((string=? (car args) "run") (exit (run-client "run" cmd: (cdr args))))packages/slate/src/slate/main.sglmodified
;; `glow:` renders; #f = the frame gets a `no-fx` class that forces --glow ;; off in CSS regardless of theme. Persisted like the theme name. slate/effects: #t ;; Terminal backend default (design §7). #f = a plain pty (a bare shell in ;; a pty) is the default "Open terminal" backend — the solid, well-worn path ;; a new user hits first. #t = tmux control-mode ("better terminal") becomes ;; the default instead (when a tmux server is present on the node). Either ;; way tmux stays fully reachable; this only flips which backend a FRESH ;; terminal opens with. Persisted (localStorage) + boot-restored + settable ;; live from the command palette / set-options!; a newly-opened terminal ;; reads the current value at open time, so a live flip needs no rebuild. slate/terminals-use-tmux: #f ;; Focus-follows-mouse: hovering a pane focuses it. Global toggle, default ;; ON (desktop); the JS hover handler no-ops on touch. Persisted + boot- ;; restored; init.sgl-settable. (guard (e (#t #f)) (local-storage-set! ffm-pref-key (if on? "1" "0")))) (define (ffm-on? state) (dict-ref state slate/focus-follows-mouse: #t)) ;; Terminals-use-tmux master switch (design §7), persisted as "1"/"0" — same ;; guarded shape as the effects/ffm prefs. Default OFF: a plain pty is the ;; default terminal backend. When ON, "Open terminal" opens a tmux control-mode ;; "better terminal" instead (when a tmux server is present on the node). tmux ;; stays reachable either way; this only flips the DEFAULT. Read live by ;; tmux-available? at terminal-open time, so a flip + a fresh terminal needs no ;; rebuild. Degrades to the default (off) natively / when storage is blocked. (define tmux-default-pref-key "slate.pref/terminals-use-tmux") (define (tmux-default-pref-load) (guard (e (#t #f)) (local-storage-get tmux-default-pref-key))) (define (tmux-default-pref-save! on?) (guard (e (#t #f)) (local-storage-set! tmux-default-pref-key (if on? "1" "0")))) (define (terminals-use-tmux? state) (dict-ref state slate/terminals-use-tmux: #f)) ;; The translucency AMOUNTS (t-a130): app-db numbers, persisted as their decimal ;; string, guarded like the other prefs. Accessors clamp defensively so a bad ;; persisted value never breaks the lowering. (cand "Switch Space" "" "prompt:space") (cand (if (ffm-on? state) "Focus-follows-mouse: on" "Focus-follows-mouse: off") "hover a pane to focus it" "ffm-toggle") (cand (if (terminals-use-tmux? state) "Terminals use tmux: on" "Terminals use tmux: off") "default new terminals to the tmux \"better terminal\"" "terminals-use-tmux:toggle") (cand "Switch theme…" (string-append (number->string (length (theme-catalog))) " color schemes · light + dark") (ffm-pref-save! next) (with-echo (dict-set (close-prompt state) slate/focus-follows-mouse: next) (string-append "focus-follows-mouse " (if next "on" "off"))))) ;; Terminals-use-tmux toggle (persisted). Flips the default terminal ;; backend: off = plain pty, on = tmux control-mode "better terminal". ;; tmux-available? reads the setting live at terminal-open time, so the ;; NEXT "Open terminal" honors the new value with no rebuild (the live-demo ;; affordance). Existing terminals are unaffected. Echoes a hint when tmux ;; is turned on but no tmux server was found on the node. ((string=? event "terminals-use-tmux:toggle") (let ((next (not (terminals-use-tmux? state)))) (tmux-default-pref-save! next) (with-echo (dict-set (close-prompt state) slate/terminals-use-tmux: next) (cond ((not next) "terminals use tmux: off (plain pty)") ((and *tmux-caps* (dict-ref *tmux-caps* available?: #f)) "terminals use tmux: on — new terminals open tmux") (else "terminals use tmux: on — no tmux server found; new terminals stay pty"))))) ;; On-demand translucency (t-a130): flip the flag (persisted). render adds/ ;; drops the frame `translucent` class, whose CSS composites a theme-aware ;; alpha (via color-mix on the live --bg-* tokens) so the desktop / demo ;; source's provider (and argv, for a real shell) changes. ((string=? event "open-terminal") (cond ;; tmux-by-default (design §7): when the node is up AND a tmux server ;; is startable, open a `tmux -C` control-mode "better terminal" — a ;; live tmux client with a window strip + nested panes. Degrades to a ;; plain pty below when tmux is absent (or terminal/backing: 'plain). ;; tmux "better terminal" (design §7) — OPT-IN via the ;; `slate/terminals-use-tmux:` setting (default off). When it is on AND ;; the node has a tmux server, open a `tmux -C` control-mode terminal — ;; a live tmux client with a window strip + nested panes. Otherwise the ;; plain-pty arm below runs (the default backend). ((and (node-ready?) (tmux-available?)) (with-echo (close-prompt (open-tmux-terminal state)) "opened tmux terminal")) ;; A real shell on the node (the familiar CLI over the WS) when it is ;; ================================================================ ;; tmux control-mode integration (P3, [[t-8269]]) ;; ================================================================ ;; Opening a terminal is tmux-by-default over the node (design §7): a ;; `tmux -C` control session on a node kind:process session drives ONE Slate ;; terminal pane whose interior is the selected window's nested pane tree + ;; a bottom window strip. Degrades to a plain pty when tmux is absent. ;; A plain pty is the DEFAULT terminal backend (design §7). tmux control-mode ;; (the "better terminal": a `tmux -C` control session on a node kind:process ;; session driving ONE Slate pane with a nested pane tree + a bottom window ;; strip) is opt-in via the `slate/terminals-use-tmux:` setting. When that ;; setting is on AND a tmux server is present on the node, "Open terminal" ;; opens the tmux backend; otherwise it opens a plain pty. (define *tmux-caps* #f) ; #{ available?: version: } once probed, else #f (define *tmux-clients* #{}) ; outer view name -> #{ client: control: } (define *tmux-seq* 0) (define *tmux-backing* 'tmux) ; 'tmux | 'plain (terminal/backing: override) (define tmux-exit-missing (list 'no-exit)) ;; "tmux 3.6a\n" -> "3.6a"; #f if the output is not a tmux version line. (loop (+ i 1)))) #f)) ;; #t iff the tmux "better terminal" is BOTH opted-in (the live ;; `slate/terminals-use-tmux:` setting) AND actually available on the node (a ;; probed tmux server). Read at terminal-open time, so a live toggle of the ;; setting takes effect for the NEXT terminal with no rebuild. (define (tmux-available?) (and (not (eq? *tmux-backing* 'plain)) (and (terminals-use-tmux? *state*) *tmux-caps* (dict-ref *tmux-caps* available?: #f))) ;; ---- version floor + capabilities (design §9) ------------------ (let ((ff (ffm-pref-load))) (when (and ff (string=? ff "0")) (set! *state* (dict-set *state* slate/focus-follows-mouse: #f)))) ;; Restore the terminals-use-tmux switch (default off = plain pty; only "1" ;; makes tmux the default terminal backend). (let ((tx (tmux-default-pref-load))) (when (and tx (string=? tx "1")) (set! *state* (dict-set *state* slate/terminals-use-tmux: #t)))) ;; Restore the translucency amounts (content/chrome opacity). (let ((o (num-pref-load content-opacity-pref-key))) (when o (set! *state* (dict-set *state* slate/opacity: (clamp-num o 10 100)))))