From 71342325115711465518e2e794369d3082160ea8 Mon Sep 17 00:00:00 2001 From: mecattaf Date: Thu, 17 Sep 2026 10:43:59 +0200 Subject: [PATCH 1/4] nvim: load the theme module from nvim-lua/, so a pull ahead of a switch works MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Opening nvim on the coordinator after pulling #417, before its switch: lua/plugins/lualine.lua:144: module 'theme' not found lualine.lua is a per-file RAW link, so it already had the new `require('theme')`; lua/theme.lua was a NEW nvimRaw entry, and per-file links only appear on a switch. Exactly the "checkout ahead of its switch" case the design doc promised would degrade gracefully, and nvim did not. - theme.lua moves to home/dot_local/bin/nvim-lua/. ~/.local/bin is ONE out-of-store link into the checkout, and init.lua appends ~/.local/bin/nvim-lua/?.lua to package.path before plugins load (the same path claude_slash.lua already rides), so require('theme') resolves as soon as the checkout has the file — no rebuild in either direction. - nvim.nix drops the lua/theme.lua nvimRaw entry. - lualine.lua pcalls the require, with noir's two colours as fallback, so a missing module can never cost the status line again. - `theme`'s live reload (`require("theme").reload()` over --remote-expr) is unchanged: same module name, same package.path. Verified on the coordinator's CURRENT (pre-switch) generation: nvim --headless fires VeryLazy, require('theme') returns the noir palette, lualine loads and sets its highlights. home-manager-generation builds. Co-Authored-By: Claude Opus 5 --- docs/theme-switcher-2026-09-17.md | 2 +- home/dot_config/nvim/lua/plugins/lualine.lua | 5 ++++- .../nvim/lua => dot_local/bin/nvim-lua}/theme.lua | 12 ++++++++++-- home/nvim.nix | 5 +++-- 4 files changed, 18 insertions(+), 6 deletions(-) rename home/{dot_config/nvim/lua => dot_local/bin/nvim-lua}/theme.lua (90%) diff --git a/docs/theme-switcher-2026-09-17.md b/docs/theme-switcher-2026-09-17.md index 2da1b66f0..aed38bd0a 100644 --- a/docs/theme-switcher-2026-09-17.md +++ b/docs/theme-switcher-2026-09-17.md @@ -44,7 +44,7 @@ palette value does. | kitty | `include ${HOME}/.config/theme/kitty.conf` (kitty.conf) | `kitten @ --to unix:@kitty- load-config` per instance (sockets from `/proc/net/unix`) | | niri | `include optional=true "~/.config/theme/niri.kdl"` LAST in config.kdl; sections merge, later wins | `niri msg action load-config-file` | | herdr | `[theme] name = "terminal"`: every token is an ANSI slot | nothing — kitty reports the bg change via DEC 2031 (`CSI ?997;n`), herdr re-queries OSC 10/11/4 and repaints chrome + every pane | -| nvim | `lua/theme.lua` dofile()s `theme.lua`; catppuccin/bufferline/lualine read it | `nvim --server --remote-expr` → `require('theme').reload()` per instance | +| nvim | `~/.local/bin/nvim-lua/theme.lua` dofile()s `theme.lua`; catppuccin/bufferline/lualine read it | `nvim --server --remote-expr` → `require('theme').reload()` per instance | | fish | `conf.d/colors.fish` sources the fragment; re-sources at the next prompt when the pointer moved | automatic (no universal variables: `fish_variables` is tracked) | | starship | already ANSI-named; the one `#F47B85` became `red` | follows kitty | | Claude Code | `theme` key in `~/.claude.json` ← `meta` `claude_code=` | written tmp+rename; takes effect on next start | diff --git a/home/dot_config/nvim/lua/plugins/lualine.lua b/home/dot_config/nvim/lua/plugins/lualine.lua index f6cd7d5c6..c8b2bf9e5 100644 --- a/home/dot_config/nvim/lua/plugins/lualine.lua +++ b/home/dot_config/nvim/lua/plugins/lualine.lua @@ -141,7 +141,10 @@ components.location = { function M.setup() -- Palette colours are resolved here, not at module load, so theme.reload() -- can call setup() again after a `theme` switch. - local T = require('theme').palette() + -- pcall: a missing theme module must never cost the status line (noir + -- fallback values, the same as theme.lua's own). + local ok, theme = pcall(require, 'theme') + local T = ok and theme.palette() or { green = '#9BE963', red = '#F47B85' } components.external_changes.color.fg = T.green components.recording.color.fg = T.red require('lualine').setup({ diff --git a/home/dot_config/nvim/lua/theme.lua b/home/dot_local/bin/nvim-lua/theme.lua similarity index 90% rename from home/dot_config/nvim/lua/theme.lua rename to home/dot_local/bin/nvim-lua/theme.lua index 7a7180ec7..44acfa176 100644 --- a/home/dot_config/nvim/lua/theme.lua +++ b/home/dot_local/bin/nvim-lua/theme.lua @@ -1,11 +1,19 @@ --- lua/theme.lua — the palette every colour in this config comes from. +-- theme.lua — the palette every colour in the nvim config comes from. -- -- Home Manager renders ~/.config/themes//theme.lua for each theme -- (home/themes/*.nix); ~/.config/theme is the symlink ~/.local/bin/theme -- retargets. This module dofile()s the current fragment and applies it to -- catppuccin, bufferline and lualine — at startup from plugins.lua.in, and -- again on `theme `, which calls reload() over --remote-expr on every --- running nvim. RAW (live-edit), see home/nvim.nix nvimRaw. +-- running nvim. +-- +-- Lives in ~/.local/bin/nvim-lua/, not ~/.config/nvim/lua/ (2026-09-17): that +-- directory is ONE out-of-store link into the checkout (home.nix +-- `home.file.".local/bin"`) and init.lua adds it to package.path before +-- plugins load, so require('theme') resolves the moment the checkout has this +-- file. Under nvim/lua/ it needed its own per-file link from a rebuild, and +-- lualine.lua (already linked) failed with "module 'theme' not found" on any +-- machine whose checkout was ahead of its switch. local M = {} local FRAGMENT = vim.fn.expand('~/.config/theme/theme.lua') diff --git a/home/nvim.nix b/home/nvim.nix index fe1482ef4..7d2b15649 100644 --- a/home/nvim.nix +++ b/home/nvim.nix @@ -180,8 +180,9 @@ let "lua/plugins/nvim-tree.lua" "lua/plugins/telescope.lua" "lua/plugins/lualine.lua" - # the theme switcher's palette loader (docs/theme-switcher-2026-09-17.md) - "lua/theme.lua" + # NOT lua/theme.lua: the theme switcher's palette loader lives in + # ~/.local/bin/nvim-lua/ (a whole-dir RAW link on init.lua's package.path), + # so a checkout pulled ahead of its switch still finds it. See its header. ]; # every binary the nvim config shells out to (replaces mason; covers all plugin CLIs) From e3d1c70e77e116875e85e6adeee74f2a633e04a7 Mon Sep 17 00:00:00 2001 From: mecattaf Date: Tue, 22 Sep 2026 17:10:00 +0200 Subject: [PATCH 2/4] seats: cc and cc2 launchers start outside $HOME and set their own config dir; drop the dead claude-credentials seed Two Claude Code seats live on the coordinator: cc in ~/.claude (the personal Claude Max login) and cc2 in ~/.claude-work (the leger.run login). Tom re-logged both by hand on 2026-09-22; this makes the arrangement durable. - fish: cc/cac/cc2/cac2 become one launcher. Claude Code never saves workspace trust for $HOME (docs: "trust acceptance is held for the current session only and is not written to disk"), so a seat typed in ~ showed "Accessing workspace" on every launch; the launcher now moves into $CLAUDE_ENVELOPE (default ~/today) when started from ~. `cc` clears CLAUDE_CONFIG_DIR explicitly: a terminal opened from a cc2 session inherits it, and the old alias then silently started cc2 (measured with a stub claude under runtime-test). TALLY_SEAT names the seat to hooks and receipts. cc3 lines are untouched. - secrets: remove the claude-credentials seed and its age file. The token had been dead since the 2026-08-04 rotation, and a seed that fires whenever ~/.claude/.credentials.json is absent could only revert a fresh /login after a rebuild. Verified: nix eval of the coordinator's age.secrets no longer lists it and the other nineteen are intact. - AGENTS.md: the seats paragraph (which login is which directory, the launch rule, where the meters are). Not a rebuild, not a merge; both are Tom's. Co-Authored-By: Claude Fable 5.1 --- AGENTS.md | 13 +++++++++ home/dot_config/fish/config.fish | 44 ++++++++++++++++++++++++++----- modules/secrets.nix | 35 +++++------------------- secrets.nix | 10 ++----- secrets/claude-credentials.age | Bin 819 -> 0 bytes 5 files changed, 58 insertions(+), 44 deletions(-) delete mode 100644 secrets/claude-credentials.age diff --git a/AGENTS.md b/AGENTS.md index 3686dc7fe..4f14a3b2f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,3 +1,16 @@ +**Claude seats (2026-09-22).** Two Claude Code logins live on the coordinator +and nowhere else: `cc` is `~/.claude` (the personal Claude Max login, config file +`~/.claude.json`), `cc2` is `~/.claude-work` (the leger.run Claude Max login, +config file `~/.claude-work/.claude.json`), selected only by `CLAUDE_CONFIG_DIR` +in the fish launchers (`home/dot_config/fish/config.fish`). Both share the same +skills and settings links; login, history, sessions and trust are per seat, so a +session id resumes only on the seat and from the cwd that created it +(`claude-sessions` fans out over the seats). Claude never saves workspace trust +for `$HOME`, so the launchers move into `$CLAUDE_ENVELOPE` (default `~/today`) +when typed from `~`; never start a seat in the home directory. Logins are hand +`/login`s, never a delivered secret (the old claude-credentials seed was removed +this day). The seat meters are `~/.local/state/tally-rewrite/meters/.json`. + **Physical seats (2026-09-16, supersedes older headless/client-only wording below).** Tom is returning the coordinator to primary-desktop duty with two upright LG 5K displays side by side at scale 2. Both coordinator and client have diff --git a/home/dot_config/fish/config.fish b/home/dot_config/fish/config.fish index 35b0df12b..5fa17cde4 100644 --- a/home/dot_config/fish/config.fish +++ b/home/dot_config/fish/config.fish @@ -42,13 +42,43 @@ alias l.="eza -a | grep -E \"^\.\"" alias cd='z' # zi command -# Claude Code -alias cc='claude --dangerously-skip-permissions' -alias cac='claude --continue --dangerously-skip-permissions' -# Second Claude account (work). CLAUDE_CONFIG_DIR isolates login, history and -# state in ~/.claude-work; skills/settings are the same dotfiles links (home.nix). -alias cc2='env CLAUDE_CONFIG_DIR=$HOME/.claude-work claude --dangerously-skip-permissions' -alias cac2='env CLAUDE_CONFIG_DIR=$HOME/.claude-work claude --continue --dangerously-skip-permissions' +# Claude Code: two seats, one launcher shape (2026-09-22). +# cc / cac -> ~/.claude the personal Claude Max login (gmail org) +# cc2 / cac2 -> ~/.claude-work the leger.run Claude Max login +# CLAUDE_CONFIG_DIR isolates login, history and state per seat; skills and +# settings are the same dotfiles links for both (home.nix). TALLY_SEAT names the +# seat to hooks and receipts. Claude Code never saves workspace trust for $HOME +# (docs, security: "trust acceptance is held for the current session only and +# is not written to disk"), so a seat started in ~ shows "Accessing workspace" +# on every launch. Both launchers therefore move into the desk envelope when +# typed from ~; the shell stays there afterwards, which is where the desk +# session's files belong (~/today/CLAUDE.md section 4). +set -q CLAUDE_ENVELOPE; or set -gx CLAUDE_ENVELOPE $HOME/today +function __claude_seat --description 'start one Claude seat outside $HOME' + set -l seat $argv[1] + set -l config_dir $argv[2] + set -l args $argv[3..] + if test (pwd) = $HOME + if not test -d $CLAUDE_ENVELOPE + echo "$seat: \$CLAUDE_ENVELOPE ($CLAUDE_ENVELOPE) does not exist; cd into a project first" >&2 + return 1 + end + builtin cd $CLAUDE_ENVELOPE + echo "($seat: started in $CLAUDE_ENVELOPE, not in ~)" >&2 + end + # A terminal opened from inside a cc2 session already carries + # CLAUDE_CONFIG_DIR, so `cc` must clear it explicitly or it silently starts + # cc2 (measured 2026-09-22). Each seat sets its own value or none. + if test -n "$config_dir" + env CLAUDE_CONFIG_DIR=$config_dir TALLY_SEAT=$seat claude --dangerously-skip-permissions $args + else + env -u CLAUDE_CONFIG_DIR TALLY_SEAT=$seat claude --dangerously-skip-permissions $args + end +end +function cc; __claude_seat cc "" $argv; end +function cac; __claude_seat cc "" --continue $argv; end +function cc2; __claude_seat cc2 $HOME/.claude-work $argv; end +function cac2; __claude_seat cc2 $HOME/.claude-work --continue $argv; end # Third Claude account (2026-09-05), same rotation: state in ~/.claude-3. alias cc3='env CLAUDE_CONFIG_DIR=$HOME/.claude-3 claude --dangerously-skip-permissions' alias cac3='env CLAUDE_CONFIG_DIR=$HOME/.claude-3 claude --continue --dangerously-skip-permissions' diff --git a/modules/secrets.nix b/modules/secrets.nix index a9ec1e9af..135c0bd19 100644 --- a/modules/secrets.nix +++ b/modules/secrets.nix @@ -23,35 +23,12 @@ in config = lib.mkIf cfg.enable ( lib.mkMerge [ - # Claude Code OAuth credential — coordinator ONLY, and the recipient tier in - # ../secrets.nix now matches (aug04 ruling), so this MUST stay host-gated: - # no other host can decrypt the ciphertext, and declaring an undecryptable - # secret fails activation. The zenbook was already excluded (jul12 ruling: - # the laptop is a standalone backup operator for when the coordinator is - # unreachable, so it logs in with its OWN fresh OAuth session instead of - # inheriting the coordinator's token — two devices refreshing one shared - # token can race and sign each other out); the nas joined it aug04 for the - # simpler reason that it has no `claude` and never spent the token. - (lib.mkIf (config.networking.hostName == "coordinator") { - age.secrets.claude-credentials = { - file = ../secrets/claude-credentials.age; - owner = "tom"; - group = "users"; - mode = "600"; - }; - - # Seed the Claude Code OAuth credential once into a WRITABLE path Claude owns — - # agenix delivers a read-only /run/agenix symlink, but Claude must rewrite the - # file on token refresh, so copy rather than link, and only if absent. - system.userActivationScripts.seedClaudeCreds.text = '' - cred="$HOME/.claude/.credentials.json" - if [ ! -e "$cred" ] && [ -r "${config.age.secrets.claude-credentials.path}" ]; then - mkdir -p "$HOME/.claude" - cp "${config.age.secrets.claude-credentials.path}" "$cred" - chmod 600 "$cred" - fi - ''; - }) + # (claude-credentials — the Claude Code OAuth token seeded into + # ~/.claude/.credentials.json — was declared and copied here until + # 2026-09-22. The token had been dead since its August 4 rotation, and a + # seed that fires whenever the file is absent can only ever revert a fresh + # `/login` after a rebuild. Both Claude seats now log in by hand, once, + # on the coordinator: cc into ~/.claude, cc2 into ~/.claude-work.) # NOT ungated any more (2026-08-28). This block delivers ssh-user-key and # atuin-key, whose ciphertexts are encrypted to the `delivered` tier — and diff --git a/secrets.nix b/secrets.nix index 9327bae0a..5d94f6b0b 100644 --- a/secrets.nix +++ b/secrets.nix @@ -150,14 +150,8 @@ in # cleanup — as agenix ciphertext it survives reflash and never needs re-minting. "secrets/immich-api-key.age".publicKeys = editors ++ coordinatorOnly; - # Claude Code OAuth credential. Demoted from the common tier to coordinator-only - # (2026-08-04, Tom's ruling): the nas holds no `claude` binary and never ran an - # agent, so it had no use for the token; zenbook-duo was already excluded from - # delivery (jul12 ruling — it logs in with its OWN session, since two devices - # refreshing one shared token race and sign each other out). That left the - # coordinator as the only real consumer, so the recipient tier now says so — - # a token this wide should not be decryptable by boxes that never spend it. - "secrets/claude-credentials.age".publicKeys = editors ++ coordinatorOnly; + # (claude-credentials.age removed 2026-09-22: the seat logins are hand + # `/login`s on the coordinator, never a delivered secret; see modules/secrets.nix.) # Brother HL-L2445DW Web Based Management admin password, set 2026-08-21 when # the printer's forced default-password change gated its move onto the thomas diff --git a/secrets/claude-credentials.age b/secrets/claude-credentials.age deleted file mode 100644 index 8d0a443d1f05539f6ed678fd6dfb2b1a41f4eb47..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 819 zcmV-31I+wkXJsvAZewzJaCB*JZZ2Jb4NK! zZg^ujLNzaOcQRsXcP~L~>+MGipItPHr-FZf`bWHd{RQEpl_O?FjuSxtImQ!-a&S7l9A3N0-yAToL}NJLm#b4+1GYIJ!kL0WDx zG*DAYM{a33GE7xQLN;Y>Yi~<%LvS!O3M~}2(FDnxh87!7ony?UC7Y1Oguk=GQP z9`f|;zUJ{788B`!ZfbhrulOYyXNCKS5bB1X6XH$CMnUMGfPB^uzQT%a6$p4Ttaj#T zI|P%9{w(FEtr2CVc#*07ik*MxU~`(v7Hn61qC-lVLS&CGzv{VANT5H_XX|fc!>|)v;xSu+ zx9v|73UjlO*X|Bp5ckty6p?CXeGMN|nY`0YEVLAwy0G-5#mMq>+PxA~MI{3VMieF; ztS-+5HXlE=CNjdqDop9>YX=D5lkZzYuIrGFug)}4vnOa~8s3}+QsSz5Y3}yN+IR+C zrEM;O;t$Ug5c^Brf~>13c}lChB0VGU+Yr@ xrE=mJWyjX}C8+3eoTFIWP6-<zD#kH&tAY#;yt From c0aa66656391dbbaa00f4c8803e5782d38296a8b Mon Sep 17 00:00:00 2001 From: mecattaf Date: Tue, 22 Sep 2026 22:53:59 +0200 Subject: [PATCH 3/4] ghostty: kitty.conf's twin for cmux Browser's libghostty panes home/dot_config/ghostty/config.ghostty mirrors kitty.conf key for key so a cmux terminal pane looks like a kitty window, and names the gap wherever Ghostty has no equivalent key. home/home.nix adds ghostty to configDirs so the out-of-store symlink is declared: ~/.config/ghostty already points into this checkout (created 2026-09-17) but is absent from generation 239's home-manager-files, so it is an orphan today and this is what makes it legitimate. home/themes/default.nix renders a per-theme ghostty fragment alongside kitty.conf. docs/theme-switcher-2026-09-17.md gains the consumer row. Co-Authored-By: Claude Opus 5 --- docs/theme-switcher-2026-09-17.md | 1 + home/dot_config/ghostty/config.ghostty | 83 ++++++++++++++++++++++++++ home/home.nix | 1 + home/themes/default.nix | 43 +++++++++++++ 4 files changed, 128 insertions(+) create mode 100644 home/dot_config/ghostty/config.ghostty diff --git a/docs/theme-switcher-2026-09-17.md b/docs/theme-switcher-2026-09-17.md index aed38bd0a..63db2c7c8 100644 --- a/docs/theme-switcher-2026-09-17.md +++ b/docs/theme-switcher-2026-09-17.md @@ -42,6 +42,7 @@ palette value does. | consumer | joins via | live reload on `theme ` | |---|---|---| | kitty | `include ${HOME}/.config/theme/kitty.conf` (kitty.conf) | `kitten @ --to unix:@kitty- load-config` per instance (sockets from `/proc/net/unix`) | +| ghostty (cmux Browser panes) | `config-file = ?/home/tom/.config/theme/ghostty` LAST in ghostty/config.ghostty (a config-file loads after its parent, so the fragment wins; absolute because Ghostty resolves relative paths against the symlinked config's dir) | nothing — libghostty reads the config when cmux opens a surface; new panes follow | | niri | `include optional=true "~/.config/theme/niri.kdl"` LAST in config.kdl; sections merge, later wins | `niri msg action load-config-file` | | herdr | `[theme] name = "terminal"`: every token is an ANSI slot | nothing — kitty reports the bg change via DEC 2031 (`CSI ?997;n`), herdr re-queries OSC 10/11/4 and repaints chrome + every pane | | nvim | `~/.local/bin/nvim-lua/theme.lua` dofile()s `theme.lua`; catppuccin/bufferline/lualine read it | `nvim --server --remote-expr` → `require('theme').reload()` per instance | diff --git a/home/dot_config/ghostty/config.ghostty b/home/dot_config/ghostty/config.ghostty new file mode 100644 index 000000000..bbca8b32f --- /dev/null +++ b/home/dot_config/ghostty/config.ghostty @@ -0,0 +1,83 @@ +# ghostty — kitty.conf's twin (2026-09-17). +# +# The only Ghostty on the fleet today is libghostty inside cmux Browser's +# terminal panes (cmux-tui; ~/.local/opt/cmux-browser-nightly, not packaged yet). +# Its settings page's "Use Ghostty config" reads THIS file, +# $XDG_CONFIG_HOME/ghostty/config.ghostty. Every value below mirrors kitty.conf +# key for key, so a cmux pane looks like a kitty window — a kitty change +# belongs here too. Where Ghostty has no equivalent, the kitty key is named +# and the gap stated. The colours are NOT here: they join through the theme +# fragment at the bottom, like kitty's `include ${HOME}/.config/theme/kitty.conf`. + +# shell fish — kitty.conf says why: the login shell is bash fleet-wide (#236), +# fish stays the terminal UI. +command = fish +shell-integration = fish + +# Font — kitty's EXPLICIT families (2026-08-21): Liga SFMono Nerd Font, bold is +# SemiBold on purpose (the weight the fontconfig fallback actually served). +# Ghostty takes family + style, where kitty takes the full face name. +font-family = Liga SFMono Nerd Font +font-family-bold = Liga SFMono Nerd Font +font-style-bold = SemiBold +font-family-italic = Liga SFMono Nerd Font +font-style-italic = Italic +font-family-bold-italic = Liga SFMono Nerd Font +font-style-bold-italic = SemiBold Italic +# font_size 16.0 (2026-09-16): 14 + one legacy ctrl+= press. +font-size = 16 +# disable_ligatures never: ligatures are Ghostty's default, nothing to set. + +# Cursor: cursor_blink_interval 0. kitty's cursor_trail has no Ghostty +# equivalent (cursor_trail_color in the fragment is kitty-only). +cursor-style = block +cursor-style-blink = false + +# window_padding_width 10 +window-padding-x = 10 +window-padding-y = 10 + +# detect_urls yes / open_url_with default (xdg-open here too). +# underline_hyperlinks always: Ghostty underlines a link on hover only. +link-url = true +# confirm_os_window_close 0 +confirm-close-surface = false +# scrollback_lines 100000 — Ghostty caps scrollback in BYTES per surface +# (lazily allocated, compressed when idle): 100 MB ≈ 100k lines of 1 KB. +scrollback-limit = 100000000 +# enable_audio_bell no — Ghostty's default rings the system bell; cmux may +# still flag the surface (attention/title), never sound. +bell-features = no-system,no-audio +# copy_on_select clipboard +copy-on-select = clipboard +# THE THIN-CLIENT CLIPBOARD CONTRACT (kitty.conf, 2026-09-11), the OSC 52 +# half: a remote program may WRITE this seat's clipboard (write-clipboard) and +# a remote READ prompts (read-clipboard-ask). `clipboard-read = allow` would +# let anything the coordinator runs exfiltrate the clipboard silently, so it +# must never be set. +clipboard-write = allow +clipboard-read = ask + +# map ctrl+equal / ctrl+minus — kitty's legacy increase_font_size step is a +# fixed +2.0pt (0.48.0 boss.py); Ghostty's default step is 1, so pass the amount. +keybind = ctrl+equal=increase_font_size:2 +keybind = ctrl+minus=decrease_font_size:2 +# kitty_mod+h / kitty_mod+g (kitty-scrollback.nvim) and enabled_layouts splits +# have no Ghostty side: cmux owns splits, tabs and the scrollback viewer. + +# Frosted terminal (2026-09-16): background_opacity 0.96. The compositor blur +# rides on the kitty app-ids in niri's window-rules.kdl; cmux is a different +# app-id, so its panes are translucent without the frost until a rule lands. +background-opacity = 0.96 +# kitty does not dim inactive splits; Ghostty's default is 0.7. +unfocused-split-opacity = 1 + +# Colours: the theme fragment home/theme.nix renders from home/themes/*.nix +# under ~/.config/themes//ghostty, joined through the ~/.config/theme +# pointer that ~/.local/bin/theme retargets. Ghostty resolves a relative +# config-file against the directory of THIS file — which is a symlink into the +# checkout — so the path is absolute; $HOME is /home/tom on every host +# (home.homeDirectory). `?` = no error while the fragment is not rendered yet. +# A config-file loads AFTER the file that names it, so the fragment wins. +# No live reload: cmux reads the config when it opens a surface. +config-file = ?/home/tom/.config/theme/ghostty diff --git a/home/home.nix b/home/home.nix index b63c9ee47..023f07d84 100644 --- a/home/home.nix +++ b/home/home.nix @@ -69,6 +69,7 @@ let configDirs = [ "niri" "kitty" + "ghostty" # kitty.conf's twin, read by libghostty in cmux Browser's panes "fish" "starship" "yt-dlp" diff --git a/home/themes/default.nix b/home/themes/default.nix index 1fe24ea63..9d87628bb 100644 --- a/home/themes/default.nix +++ b/home/themes/default.nix @@ -68,6 +68,48 @@ let inactive_border_color ${t.ground.base} ''; + # Ghostty (libghostty inside cmux Browser's terminal panes): the kitty + # fragment, key for key, in Ghostty's `key = value` grammar. + ghostty = t: '' + ${header t "#"} + # Joined by ghostty/config.ghostty's `config-file = ?/home/tom/.config/theme/ghostty`. + background = ${t.ground.base} + foreground = ${t.fg} + selection-background = ${t.selection.bg} + selection-foreground = ${t.selection.fg} + cursor-color = ${t.cursor} + cursor-text = ${t.cursorText} + + # black + palette = 0=${t.ansi.black} + palette = 8=${t.ansi.brightBlack} + # red + palette = 1=${t.red} + palette = 9=${t.red} + # green + palette = 2=${t.green} + palette = 10=${t.green} + # yellow + palette = 3=${t.yellow} + palette = 11=${t.yellow} + # blue + palette = 4=${t.blue} + palette = 12=${t.blue} + # magenta + palette = 5=${t.magenta} + palette = 13=${t.magenta} + # cyan + palette = 6=${t.cyan} + palette = 14=${t.cyan} + # white + palette = 7=${t.ansi.white} + palette = 15=${t.ansi.brightWhite} + + # splits: as kitty, the divider is niri's INACTIVE window border. + split-divider-color = ${t.border.inactive} + unfocused-split-fill = ${t.ground.base} + ''; + niri = t: '' ${header t "//"} // Joined by niri/config.kdl's `include optional=true "~/.config/theme/niri.kdl"`, @@ -168,6 +210,7 @@ rec { render = t: { "kitty.conf" = kitty t; + "ghostty" = ghostty t; "niri.kdl" = niri t; "colors.fish" = fish t; "theme.lua" = lua t; From fb29b4e08401898657c294be824a2a2babbf9e24 Mon Sep 17 00:00:00 2001 From: mecattaf Date: Tue, 22 Sep 2026 22:54:03 +0200 Subject: [PATCH 4/4] disko: record the 2026-09-19 disk ownership facts (comments only) Comments only, verified: zero non-comment added or removed lines across the three files, so no partition table, device id, size, mountpoint or filesystem change. Records which disks belong to whom and which depart with the worker chassis. Per FRONT-10 no agent invents or enforces the return date. Co-Authored-By: Claude Opus 5 --- hosts/coordinator/disko.nix | 11 +++++++++++ hosts/nas/disko.nix | 10 ++++++++++ hosts/worker/disko.nix | 16 ++++++++++++++++ 3 files changed, 37 insertions(+) diff --git a/hosts/coordinator/disko.nix b/hosts/coordinator/disko.nix index 7140edc8e..e0f067d0d 100644 --- a/hosts/coordinator/disko.nix +++ b/hosts/coordinator/disko.nix @@ -64,6 +64,17 @@ # Wiped blank before the physical move (no partition table, no bootloader), # then laid out here. # + # ── OWNERSHIP (Tom, 2026-09-19) ───────────────────────────────────────────── + # THIS DISK BELONGS TO SODIMO. It is one of exactly two sodimo-owned items in + # the fleet, the other being the `worker` chassis. It is here only because the + # 2026-08-30 transition fitted Tom's own 1TB (26051Y809195) into worker and + # moved this one across. + # + # It LEAVES with worker (2+ months out as of 2026-09-19; per FRONT-10 no agent + # invents or enforces that date). /home lives on it, so that departure is a + # migration off this disk, not an unplug — plan it as one. Afterwards the + # coordinator runs on its own 1TB anchor 25140U804698 with no secondary. + # # ── The rule this disk exists to enforce ──────────────────────────────────── # The 1TB anchor holds the OS and only the OS: the nix store and everything # NixOS derives from it, /etc, /var, and the local-models weight collection. diff --git a/hosts/nas/disko.nix b/hosts/nas/disko.nix index 87df34a1e..6736258f4 100644 --- a/hosts/nas/disko.nix +++ b/hosts/nas/disko.nix @@ -42,6 +42,16 @@ # generated media (media.nix). Random-I/O state belongs here: it would be # wear on the eMMC and seek chatter on the HDD. The mountpoint move was # non-destructive — same filesystem, never reformatted. + # + # ── In the 2026-09-19 disk shuffle ────────────────────────────────────────── + # This Fanxiang is Tom's and is a CANDIDATE TO MOVE to the fanless desk NUC: + # 256GB is ample for a seat that only runs Chromium. In that option the 1TB + # freed by the worker return lands here instead, taking /mnt/fast to 1TB. + # The alternative sends the freed 1TB straight to the NUC and leaves this + # disk alone. Undecided as of 2026-09-19; either way nothing is purchased. + # If this disk does move, /mnt/fast is offline in the interim — it was 49% + # of 234G at the 2026-09-18 census and carries PostgreSQL, Navidrome state, + # Immich generated media and the remote journal. Check those before moving. disko.devices.disk.journal = { type = "disk"; device = "/dev/disk/by-id/nvme-Fanxiang_S500Pro_256GB_26040259615000015"; diff --git a/hosts/worker/disko.nix b/hosts/worker/disko.nix index 0f8f06405..9fd20028d 100644 --- a/hosts/worker/disko.nix +++ b/hosts/worker/disko.nix @@ -6,6 +6,22 @@ # 500GB paired against the coordinator's 1TB was the fleet's dual-node # capacity ceiling. # + # ── OWNERSHIP (Tom, 2026-09-19) ───────────────────────────────────────────── + # THIS DISK IS TOM'S, NOT SODIMO'S. sodimo owns exactly two items in this + # fleet: the `worker` chassis and the 500GB SN7100 260538801482 — and that + # 500GB is fitted in the COORDINATOR today, not here. When worker goes back + # (2+ months out as of 2026-09-19; per FRONT-10 no agent invents or enforces + # that date), the 500GB returns to this chassis and THIS 1TB COMES OUT. + # + # Do not read "worker's boot disk" as "unavailable". That is a filesystem + # fact, not an ownership fact, and conflating the two sent the silent-desk + # planning down a wrong path until it was corrected on 2026-09-19. + # + # The freed 1TB is the fleet's ENTIRE NVMe surplus. It is spoken for: either + # it becomes the fanless desk NUC's disk, or it upgrades the NAS /mnt/fast + # (256GB -> 1TB) and the NAS's Fanxiang 256GB goes to the NUC instead. + # Undecided. Standing constraint (Tom, 2026-09-19): NO NEW M.2 IS PURCHASED. + # # ── Why the attr is `w1t` and NOT `main` ──────────────────────────────────── # disko derives GPT partition NAMES from the attr: `main` yields # `disk-main-ESP` / `disk-main-root`. The coordinator's anchor uses those same