Every directory and archive resource carries `unknown`: move-out for a home folder whose program writes nothing there (the node-engine, delivered in mesh-host #117, moves the rest to ~/.local/state/mesh-moved/<module>/ with a list), module for ~/.ssh and ~/.claude, program elsewhere; `knows` keeps what a program or the operator relies on (powerlevel10k's compiled files, i3's scripts and the asus/g14 drop-ins, dunst's slack rule, betterbird's records, node-env's forge credential, issue 1033). Two shared folders are no longer declared. Each module's README says it. checks/owned holds every manifest to it; checks/sshdir stays while main's merge check runs it (issue 1035). Tests that pin a manifest follow the new key only. Reviewed in three rounds.
i3
The window manager as a module (novox/hq ADR 0208, research 026, to-be 42 phase 2, step 4).
- Claims
node-display-sessionand serves its verbsreload,workspacesandwindows. - Requires
x11-display, which onlyxorgon the same machine provides. - Packages:
i3-wm, anddex, which the configuration has always run for XDG autostart.dexwas missing on the desktop, so its autostart entries never started there. - Environment (ADR 0203):
XDG_CURRENT_DESKTOP=i3andXDG_SESSION_DESKTOP=i3. They reach shells, the session (throughxorg's block) and the user manager (environment.d). The portal keys off the first. - Session code (ADR 0208 §4):
exec i3 --shmlog-size=26214400in thexinitrcslotlast. That is the end ofxorg's block in~/.xinitrc.
What it owns
| path | class (ADR 0182) | what |
|---|---|---|
~/.config/i3/ |
owned directory (move-out) |
what the module declares, and scripts/ (named in knows: the screenshot binding in the main file runs scripts/screenshot.sh, kept until its owner exists); any other file is moved out by the node-engine to ~/.local/state/mesh-moved/i3/ with a list, never deleted (ADR 0422 §4) |
~/.config/i3/config.d/ |
owned directory (move-out) |
what the module declares, and 10-asus.conf and 20-g14.conf (named in knows, kept until the laptop's hardware module exists); any other file is moved out by the node-engine to ~/.local/state/mesh-moved/i3/ with a list, never deleted (ADR 0422 §4) |
~/.config/i3/config |
owned (the found file kept once) | the main configuration, from files/config |
/etc/lemurs/wms/i3 |
owned | the login manager's session entry: exec systemd-cat -t x-session /bin/sh "$HOME/.xinitrc", the session's output in the journal (journalctl -t x-session) because the login manager's pipe has no reader |
Drop-ins. ~/.config/i3/config.d/ holds what this module declares and the files named in knows; a
file of yours put there is moved out (ADR 0422). The include is the last line of the main file, so every
variable set there ($mod, $ws1…$ws10) is in scope. Files are read in name order.
The reload watcher
The bundle runs the watcher for as long as the runtime runs it (ADR 0198). It replaces the
predecessor's i3-reload-watcher script and its user unit, so this needs no user-scoped unit:
- every 2 s it looks at
configandconfig.d/*.conf(size and time); - at its start, if the files differ from what the running i3 loaded, that counts as a change;
- after a change, once the files have been still for one more interval, it checks the result with
i3 -C; - it reloads only a configuration without errors. Otherwise it keeps the running one and records the errors.
What it has done is in the answers of reload and i3_config_check. It polls rather than using
inotify, so a file replaced by rename and a directory created later are seen without a fresh watch.
It reloads i3 only. Re-running the bar, the compositor and the notifier is each of those modules'
own business.
Tools
| tool | what | |
|---|---|---|
node-display-session.reload |
a | checks, then reloads, keeping windows. Refused with the errors when the check fails; force reloads anyway |
node-display-session.workspaces |
r | number, name, output, visible, focused, urgent |
node-display-session.windows |
r | container id, X id, class, instance, role, title, workspace, output, focused, urgent, floating, fullscreen, scratchpad, marks; narrowed to one workspace |
i3_focus |
d | a window by criteria, or a workspace |
i3_move |
d | windows to a workspace or output; a workspace to an output |
i3_layout_save |
d | a workspace's arrangement as a named layout of placeholders (class, instance, role), in ~/.local/state/mesh/i3/layouts/ |
i3_layout_restore |
d | lays a saved layout back; name: list lists them |
i3_exec |
d | starts a program as i3's child, optionally on a workspace |
i3_kill |
d | closes the matching windows, or the focused one when asked; with no arguments it closes nothing |
i3_bindings |
r | every binding by mode, with its command and file, from what i3 loaded |
i3_config_check |
r | i3 -C on the files on disk (errors with file and line), the files i3 loaded, the watcher's record |
i3_marks |
r | each mark and its window |
i3_scratchpad |
r/d | list, show or move into the scratchpad |
The tools speak i3's IPC on its socket in the account's runtime directory, and need no display. Every
value a caller gives is quoted before it reaches an i3 command. With no i3 running, the answer is
{"error": "no-graphical-session", …}. The bundle's binary is i3-tools, so nothing that looks for
i3 by name finds it.
What it improves over today's configuration
Both workstations ran the same configuration. These changes are against it:
- dead:
exec lxpolkit(installed on neither machine), the unused$refresh_i3status, and the predecessor'srice_setcomment; - duplicates:
exec xdg-desktop-portal(it is D-Bus activated);exec xrdb -merge ~/.Xresources(xorg's session start merges it);- the
picom,nm-applet,blueman-appletandnextcloudexecs. Each also has an XDG autostart entry thatdexstarts, and both machines carry those entries;
- font:
JetBrainsMono Nerd Font 11for titles, replacing Hack (research 026/04); - terminal:
$mod+Returnrunsi3-sensible-terminal, which starts whatever$TERMINALnames (the terminal module's contribution), instead of naming xterm; - reloads: the watcher never reloads into a broken configuration.
Moved to their own modules' drop-ins (the companion modules of research 026). These leave this file because each module carries them now:
| lines | now in |
|---|---|
the launcher bindings ($mod+d, $mod+t, $mod+Shift+t) and the power menu ($mod+Escape) |
rofi's 50-rofi.conf, which also adds $mod+Shift+w |
the greenclip daemon and its menu ($mod+period) |
clipmenu's 50-clipmenu.conf and its session line |
the wallpaper key ($mod+Shift+b) |
feh's 50-feh.conf |
| both bars | i3status-rust's 60-i3status-rust.conf |
the keyring prompt (unlock-keyring.sh) |
gone: gnome-keyring unlocks the keyring through PAM at login |
the peripherals' tray (exec … polychromatic-tray-applet) |
polychromatic's contribution to node-display-session |
The theme picker ($mod+Shift+d) goes too. It was the predecessor's tool for its theme variables,
and settings take its place once issue 168 closes. $mod+Delete (loginctl lock-session) stays here,
because screen-lock relies on it. The test TestTheMainFileAndEveryModulesDropInLoadTogether
loads this file with every catalogue module's drop-in through i3 -C, so no two of them bind one
key.
Kept until their owners exist. A marked section holds the operator's own scripts: volume, games
volume, the sessions launcher and the screenshot binding. Each leaves when the module that owns it is
written. The peripherals' tray applet left it for the polychromatic module.
What it keeps, and what it moves out
Kept, because they work for the operator today: ~/.config/i3/scripts/ (the screenshot binding runs
scripts/screenshot.sh), and config.d/10-asus.conf and 20-g14.conf, each until its owner exists. Both are
named in knows. Anything else in ~/.config/i3/ (such as unlock-keyring.sh) or in config.d/ is moved
out by the node-engine to ~/.local/state/mesh-moved/i3/ with a list, never deleted (ADR 0422 §4). Left
alone, outside the folders: the /usr/share/xsessions entries (the package's, no longer offered by lemurs), and i3's restart
state and logs.
The one-off migration (ADR 0182)
- Before the push that assigns
i3: doxorg's migration (its README). From that push on, your lines belowxorg's block in~/.xinitrcno longer run. - Disable the predecessor's watcher:
systemctl --user disable --now i3-reload-watcher.service. Then remove~/.config/systemd/user/i3-reload-watcher.serviceand~/scripts/i3-reload-watcher. One thing reloads i3 now. Kept running, it would alsoi3-msg restarton every change, without checking first. Keepi3-bar-watchdog.serviceas it is: the bar isi3status-rust's, and that module decides. - Delete
/etc/lemurs/wms/i3wm(seelemurs). config.d/fragments:10-asus.confand20-g14.conf(the laptop) belong to that machine model's hardware module. They are named inknows, so they stay until it exists. The desktop no longer carries them.50-slack.confis not an i3 file: the Slack rule is dunst's (dunstrc.d, seedunst).99-tmp-wine-focus.conf(the desktop), a test of a predecessor change by its own comment, is moved out at the first look with a list; nothing is lost.
- The main file's predecessor copy is kept once by the host and written over. Nothing to do.
What changes when it is assigned
| g14 | shanks | |
|---|---|---|
| packages | none (i3-wm and dex present) |
dex installed |
~/.config/i3/config |
written: the improved configuration | the same |
| running i3 | the watcher reloads it once the file changes. i3 keeps every window, and the title font becomes JetBrains Mono. Assigned without rofi, clipmenu, feh and i3status-rust, the reload takes away the bars and those keys until they are assigned too, so assign them in the same push. The dropped duplicate execs end nothing that runs |
the same |
/etc/lemurs/wms/i3 |
new: offered as i3 at the next boot |
the same |
~/.xinitrc |
xorg's block now ends in exec i3: the lines below it stop running at the next login |
the same |
~/.config/environment.d/50-mesh.conf, environment.sh |
gain XDG_CURRENT_DESKTOP=i3, XDG_SESSION_DESKTOP=i3 |
the same. Its user manager lacked them, and gets them at the next login |
| next login | XDG autostart as before | XDG autostart runs for the first time: Slack, JetBrains Toolbox, Nextcloud, the FortiClient tray, the print applet, the geoclue demo agent and snap's user daemon start from their entries |
One reload on assignment is the one live change. To avoid it, assign during a session you are about to end, or accept it: a reload keeps every window and workspace.
Blockers
fontsfirst (to-be 42 orders it first). Withoutttf-jetbrains-mono-nerd, i3's titles andi3status-rust's bars fall back to pango's default face. On 2026-10-04 the laptop had the package, and the desktop only a hand-copied file of the face.- None for the watcher. The runtime restarts with every push that changes it, after the push has
written the files. So at its start the watcher compares the files on disk with what the running i3
loaded (
GET_CONFIG), and reloads when they differ. The push that assignsi3, or changes its configuration, is therefore reloaded although it also restarted the watcher.
Other modules' lines are contributions (changed 2026-10-05, novox/hq ADR 0212)
node-display-session receives config contributions, and this module places them with
${contribution:node-display-session:config} near the end of its configuration, each module's
under a line naming it. ~/.config/i3/config.d/ holds only what is declared and named in knows: the include at the very
end reads it after the mesh's lines. The catalogue-wide test composes every module's contribution as
the controller does and checks the result with i3 -C.
Files it does not know
Each directory or archive this module declares says how it treats a file it did not write (ADR 0422, ADR 0416).
~/.config/i3: holds only what this module declares andscripts/(the main file's screenshot binding runsscripts/screenshot.sh; kept until its owner exists); anything else is moved out by the node-engine to~/.local/state/mesh-moved/i3/with a list, never deleted (ADR 0422 §4).~/.config/i3/config.d: holds only what this module declares and10-asus.conf,20-g14.conf(the laptop's, kept until its hardware module exists); anything else is moved out by the node-engine to~/.local/state/mesh-moved/i3/with a list, never deleted (ADR 0422 §4).