Structural repair: - 13 pages were the same document twice (each exactly 2x its HEAD size), a second frontmatter block whose closing fence had been glued onto a body line by a frontmatter rewrite that re-read its own output. Un-doubled by cutting at the second frontmatter and keeping the copy whose body is a superset; the orphan census reports 0 lost tokens against HEAD. - log.md: one entry per date, bare `## YYYY-MM-DD` headings, newest first — 66 dated headings merged into 34, titles demoted to bold leads, every content line verified present. The file had been descending at the head and ascending at the tail. Ownership moves: - New `LLM/frank-medium.md` is the locator for the retired Dankserver endpoint: era windows quoted from each era page's own banner, no numbers restated, and the two unresolved date conflicts recorded instead of silently resolved. `frank-archon.md` links to it instead of restating the pre-rename window. - The four Pipeline-Parallelism stubs (frontmatter only, no body) are gone; `Multi-Node Runtimes.md` owns runtime construction, launch, mixed-vendor verdicts and cross-split spec decode, and the four links that pointed at the stubs were repointed at it or at the drafter page. - Authentik's three stub pages fold into `Authentik.md`, with the process detail and the outpost reference split out so the parent stays under the length hint. Verified corrections (host census + live probe, 2026-10-19): - The MXFP4 checkpoint two pages called the "on-disk rollback" is gone: /home/dank/models/ holds StillDeadcode, rad and tpl.jinja. Stated once, on the era page that owns that payload; the swap page now links to it. - frank-medium-radiance-mxfp4: the 4.24/7 accept rate belongs to the DFlash2 arm measured on the FP8 target, not to the MXFP4 era; the 3-family soak run census is kept once (155 requests) instead of twice (~120 and 155). - Fleet shapes re-confirmed against the live endpoints before touching any page: 3x200000 fp8 KV, 2x200704, 2x262144. Index rows are regenerated, not hand-carved. Net -1,849 vault lines.
16 KiB
type, title, description, tags, timestamp
| type | title | description | tags | timestamp | ||||||
|---|---|---|---|---|---|---|---|---|---|---|
| reference | Noctalia and Niri | The noctalia shell on niri across workstations: config layers, remote IPC, custom palettes, output rotation and pinning. |
|
2026-10-05T00:00:00-07:00 |
Noctalia and Niri
Noctalia (/usr/bin/noctalia) is the desktop shell on niri, running on both
the workstation and blankpad. Since the 2026-09-10 unification both run the
bare noctalia v5 binary (niri spawn-at-startup "noctalia"); the old
Quickshell-hosted v4 architecture (qs -c noctalia-shell, JSON config) is
retired on both hosts.
Config layers
- Hand-edit declarative layer:
~/.config/noctalia/<name>.toml(e.g.config.toml). Main config:~/.config/noctalia/config.toml; the niri config includesnoctalia.kdlfor display settings. - Runtime state file
~/.local/state/noctalia/settings.tomlloads LAST and overrides declarative values for the same keys — never hand-edit it; UI/IPC writes land there. - Main reference:
noctalia-referencefacts — IPCnoctalia msg <command>replaced theqs -c noctalia-shell ipc callwrapper; app templates live at/usr/share/noctalia/assets/templates/(gtk, ghostty, alacritty, hyprland, sway, wezterm, helix, btop, starship, qt, kde, cava, scroll, mango, umbriel, foot, emacs, kitty); palette generationnoctalia theme [image].
Remote IPC over ssh (the D-Bus trap)
noctalia msg <command> resolves the running instance via the D-Bus session
bus. sshd-spawned non-login shells lack DBUS_SESSION_BUS_ADDRESS, so plain
ssh exec prints error: noctalia is not running even when the shell IS
running — do not conclude "shell dead" from this error. Working remote
procedure (runs in the systemd user-manager context where the bus address
exists):
systemd-run --user --pipe --wait noctalia msg <command> [args]
Verified: msg status returns live JSON state; unknown panel ids return an
error listing all valid panel ids (clipboard, control-center, launcher,
polkit, session, setup-wizard, test, tray-drawer, wallpaper).
Verified v5 msg verb grammar (keybinds-safe): panel-toggle <id>,
session lock|suspend|lock-and-suspend|logout|reboot|shutdown,
volume-up [step], volume-down, volume-mute, mic-mute,
media next|previous|playPause (graceful no-op, rc 0, with no active
player), brightness-up [target] [step], brightness-down,
settings-open [context], settings-toggle [context], config-reload,
wallpaper-get [connector], wallpaper-set <path>, wallpaper-next,
color-scheme-get|set, theme-mode-get, templates-apply,
screenshot-fullscreen.
Wallpaper directory semantics: empty/unset [wallpaper] directory
resolves to XDG Pictures via user-dirs.dirs; an explicit directory key
overrides. Fleet default pattern: point directory at the repo-tracked
wallpapers dir so git-tracked wallpapers serve the shell without copying
files. After writing config.toml over ssh the file watcher hot-reloads
automatically; config-reload via systemd-run forces it immediately. Note:
the current wallpaper stays the built-in default until the next rotation
or an explicit wallpaper-next; wallpaper-get then reporting a path
inside the configured directory is the confirmation the config took effect.
Lock chain (unified): swayidle-lock.service ExecStart on both hosts:
/usr/bin/swayidle -w timeout 300 '/usr/bin/noctalia msg session lock' before-sleep '…' after-resume '…'. msg resolves via the session bus, so
no WAYLAND_DISPLAY pinning is needed in unit context.
Custom-palette recolor (without restarting the session)
Why custom, never the community cache:
~/.local/state/noctalia/community-palettes/*.json is a sync cache —
.catalog/palettes.json carries an md5 per palette and noctalia re-fetches
from https://api.noctalia.dev/palettes, so edits there get reverted.
Custom palettes live at ~/.config/noctalia/palettes/<Name>.json
(NOCTALIA_CONFIG_HOME then XDG_CONFIG_HOME honored) and are never
synced. Missing/invalid file → silent fallback to the builtin palette.
Palette JSON shape = the community file's shape: dark/light maps of
mPrimary, mOnPrimary, mSecondary, mTertiary, mError, mSurface,
mSurfaceVariant, mOutline, mShadow, mHover plus a terminal block
(foreground, background, normal.{black..white}, bright.{…},
cursor, cursorText, selectionFg, selectionBg). NOTE: noctalia theme -o emits M3 snake_case tokens (on_primary_container, …) — that is the
generator format, not this one. Copy an existing community JSON and mutate
with jq.
Procedure:
-
Read the live scheme first — never assume:
noctalia msg color-scheme-get(e.g.community Gruber Darker);noctalia msg theme-mode-get. -
Back up settings:
cp -a ~/.local/state/noctalia/settings.toml /tmp/noctalia-settings-backup-$(date +%s).toml. -
Build the palette from the current one (jq keeps structure):
mkdir -p ~/.config/noctalia/palettes jq '.dark.mPrimary = "#f43841" | .dark.terminal.normal.yellow = "#f43841" | .dark.terminal.bright.yellow = "#ff4f58" | .dark.terminal.foreground = "#f43841"' \ "$HOME/.local/state/noctalia/community-palettes/Gruber%20Darker.json" \ > ~/.config/noctalia/palettes/"Gruber Red.json"Prefer a hex already present in the palette (its own
mErrorred) so the theme stays internally consistent. Set BOTHnormalandbrightvariants, and bothdarkandlightif the user toggles modes. -
Switch (persists
theme.source = custom+theme.custom_palette):noctalia msg color-scheme-set custom "Gruber Red"→ok; confirm withcolor-scheme-get. -
Re-render every configured template:
noctalia msg templates-apply. Regenerates~/.config/ghostty/themes/noctalia,~/.config/gtk-{3,4}.0/noctalia.css, kcolorscheme, foot, wezterm, starship, etc. Symlinked-into-dotfiles paths show up as modified files worth committing. -
Reload surfaces: compositor —
niri msg action load-config-file(reload-configis NOT a valid niri subcommand); ghostty — see the trap below.
Verification: ghostty +show-config | grep -E '^(foreground|palette)'
(proves the theme file resolves — note it prints only the delta from
defaults; cross-check with ghostty +show-config --default before
concluding a key is unset); noctalia config validate → ✓ Config is valid; visual via noctalia msg screenshot-fullscreen (saves to
~/Pictures/screenshot_<ts>.png and ALSO overwrites the clipboard with the
PNG — warn the user); grep -c <old-hex> ~/.config/noctalia/palettes/*.json
→ 0 for no leftovers.
Track it in the dotfiles repo (~/.omp, the command-center —
scripts/deploy.sh is a stow-equivalent over dotfiles/<pkg>/ trees):
mirror the shape for the new palette so git pull + deploy.sh is the
whole propagation story — move the JSON into
~/.omp/dotfiles/noctalia/.config/noctalia/palettes/ and symlink it back
into ~/.config/noctalia/palettes/; re-set the custom palette (re-reads
through the symlink). Do the link by hand for one file instead of running
deploy.sh — sync mode quarantines and replaces every divergent live file.
Traps:
- NEVER
kill -USR1ghostty to reload. It does not reload config on this fleet — it kills/respawns the terminal (took out Frank's window 2026-09-13). Correct: the default keybindctrl+shift+,=reload_config(perghostty +show-config --default). Existing windows keep old colors until reloaded; new windows inherit immediately. - Palette hex values are NOT a proxy for what the user sees: in
Gruber Darkerthe accent (mPrimary) is yellow#ffdd33, so "change the yellow" means the accent too, not just terminal index 3. - Saturating terminal text makes ANSI 1/9 indistinguishable from default text (grep highlights flatten) — flag it and offer a lighter red.
- Contrast sanity:
#f43841on#181818≈ 4.6:1 (dark-on-color beats white-on-color here, ~3.9:1 — keepmOnPrimarydark). - Revert =
noctalia msg color-scheme-set community "<original name>"; the community cache is untouched by the whole flow. - Generated theme files symlinked into the dotfiles repo (
gtk-3.0/gtk-4.0noctalia.css, ghostty theme) show up as tracked modifications aftertemplates-apply; commit them together with the new palette JSON so every machine gets the same recolor.
Offline template render (when templates-apply never ran)
Failure signature: niri failed to read included config from ~/.config/niri/noctalia.kdl: No such file or directory — the desktop runs
but with DEFAULT config (niri 26.04 falls back, does not exit): no custom
keybinds/rules, no noctalia autostart, default output placement. ghostty:
theme "noctalia" not found, tried ~/.config/ghostty/themes/noctalia.
alacritty: import of ~/.config/alacritty/themes/noctalia.toml missing.
Root cause pattern: upstream dotfiles commits theme switches (niri include,
ghostty theme = noctalia, alacritty import) that depend on noctalia
TEMPLATE-GENERATED files. Those files are per-host outputs (rendered from
the local palette) and are intentionally NOT in the repo; on any host where
templates-apply never ran, they don't exist.
Fix (offline, works while a session runs) — built-in templates live at
/usr/share/noctalia/assets/templates/ (declared in builtin.toml); render
each needed one with the live palette:
# niri include (plain host-local file; do NOT symlink into repo — regenerated per host)
noctalia theme --theme-json ~/.config/noctalia/colors.json \
--render "/usr/share/noctalia/assets/templates/niri/niri.kdl:$HOME/.config/niri/noctalia.kdl"
mkdir -p ~/.config/ghostty/themes && \
noctalia theme --theme-json ~/.config/noctalia/colors.json \
--render "/usr/share/noctalia/assets/templates/ghostty/ghostty:$HOME/.config/ghostty/themes/noctalia"
mkdir -p ~/.config/alacritty/themes && \
noctalia theme --theme-json ~/.config/noctalia/colors.json \
--render "/usr/share/noctalia/assets/templates/alacritty/alacritty.toml:$HOME/.config/alacritty/themes/noctalia.toml"
All template tokens (primary, surface, error, primary_container, …) resolve from the flat 16-key colors.json; container tokens are derived, never blank.
Verify: niri validate → rc 0; ghostty +validate-config → rc 0; next
login journal shows loaded config from config.kdl with NO parse WARN,
custom output placement (niri msg outputs vs display.kdl), noctalia
autostarted (pgrep noctalia).
Traps: noctalia msg templates-apply on 5.0.1 returns "ok" but applies
NOTHING (this build's opt-in config schema does not act on it) — do not
trust its exit code, use the explicit --render calls above. Niri config
errors at boot are NOT fatal — the compositor keeps running on defaults,
which reads as "some settings silently stopped working", not a crash.
Regenerate after any palette change; the rendered files are static
snapshots.
Niri output rotation and pinning
Verified on blankpad, niri 26.04 (two PM1601 externals + eDP-1 laptop panel).
Ground truth first: niri msg outputs (connector names, current mode,
scale, transform, logical pos/size) and niri --version. Output names are
connector names (eDP-1, DP-2, HDMI-A-1), not EDID model strings —
the EDID strings printed before them are decoration. On blankpad the live
config splits via include "./cfg/*.kdl" from ~/.config/niri/config.kdl,
and cfg/display.kdl is a symlink into the dotfiles repo — edit the
repo path (that is what gets committed); check with ls -ld.
Transform direction is counter-clockwise (niri msg output <NAME> transform --help): 90 = content rotated 90° CCW ⇒ the panel's top
bezel sits on the LEFT; 270 = top bezel RIGHT; 180 = upside down;
flipped-90/flipped-270 = mirrored variants. 90 vs 270 is a
physical-stand fact no tool can read (portable monitors expose no
orientation in EDID/MCCS) — pick one, apply, and hand the user the one-liner
to flip; don't stall on the question.
Two traps when editing the config:
- An
output "X" { … }block resets every property you don't restate to its default. Pinning position oneDP-1without repeatingscale 1.25silently drops the laptop panel to scale 1.0. Restatescale(andmode) in every block you touch. - Niri re-runs auto-layout for outputs with no pinned position. Pinning
only the externals shoved
eDP-1from0,0to3936,0. Pin every output's position to keep the left-to-right order. Positions are logical pixels at each output's own scale; x-offsets chain (eDP-1 logical width 1536 → DP-2 at 1536 → +1200 portrait width → HDMI-A-1 at 2736):
output "DP-2" { scale 1 transform "90" position x=1536 y=0 }
output "HDMI-A-1" { scale 1 transform "90" position x=2736 y=0 }
output "eDP-1" { scale 1.25 position x=0 y=0 } // MUST restate scale
Apply and verify:
niri validate -c ~/.config/niri/config.kdl # exit 0
niri msg action load-config-file # live reload; NOT reload-config
sleep 1; niri msg outputs | grep -E '^Output|Transform|Logical|Scale'
Success looks like Logical size: 1200x1920 (swapped) plus
Transform: 90° counter-clockwise on each rotated panel, positions matching
the intended chain. One-off rotation without touching config (temporary):
niri msg output DP-2 transform 90. Commit via the dotfiles repo
(~/.omp, main; push to the local Gitea permitted): git pull --ff-only
→ add the display.kdl → commit → push. Pitfalls: reload-config is not a
niri subcommand; never SIGUSR1 a terminal to "reload" (niri config reloads
do not touch ghostty themes); no man page for niri on this host — niri <sub> --help and /usr/share/doc/niri/default-config.kdl are the docs.
GPU RGB over I2C (EVGA iCX3)
Turn RGB on/off (or read it back) on an EVGA FTW3 / iCX3-controller GPU (the 3090 on dankclaw) using the GPU's own nvidia i2c bus. No display engine, no X, no daemon — the mode is EEPROM-saved and survives reboot.
- Find the controller: the nvidia i2c driver exposes the card's lighting
ports as
/dev/i2c-N; on the 3090 FTW3 that is ports 1,5,6,7,8 → i2c-1..i2c-5, and only i2c-1 carries a live slave (NVIDIA port 1). Probe each bus. The iCX3 MCU answers at address 0x2D./dev/i2c-*areroot:i2c 660— run withsudo(dank has passwordless sudo). - Protocol (raw I2C_RDWR, NOT the SMBus ioctls) — write the register
byte first, then the payload:
- enable
0xB2write04 C6 EB EA 15(required once before mode writes) - mode
0xC0write09 m0 m1 m2 m3 .. .. .. .. SYNC; m0..m3 are the four zone modes,MODE_OFF = 0x00; OFF =09 00 00 00 00 FF FF FF FF 00 - save
0x90write04 9E EB 00 90(commit to EEPROM — the reboot-persistence step) - fw
0xB1block read (firmware version) - Read the mode back: write reg
0xC0, then read 16 bytes; bytes 1..4 are the zone modes (0x00 = OFF), bytes 6..9 the per-zone LED counts.
- enable
- Verify persistence: after a reboot, read the mode — all four zone
modes
0x00means the EEPROM save held (confirmed 2026-09-19 post-reboot). - Tool: a ~90-line I2C_RDWR reader/writer lived at
/tmp/evga_raw.c(evga_raw <bus> mode|off|save|enable|fw) —/tmpis wiped by reboot, so recreate it if needed; OpenRGB'sEVGAGPUv3Controller.cppcarries the same protocol plus the full colour API. - Gotchas: raw I2C_RDWR only (the iCX3 answers the raw form, not the SMBus
ioctls); the
enablewrite must precede the first mode write in a session; without thesavewrite the mode is RAM-only and resets on power cycle.
Related
- Command Center Dotfiles — the stow-equivalent deploy this page links into
- Hermes — the agent that drove the CDP daemon alongside this desktop
- GGUF Compatibility — unrelated but shares the "read the file, not the name" lesson