diff --git a/docs/chezmoi/externals-and-ignore.mdx b/docs/chezmoi/externals-and-ignore.mdx
index cafae6b..e43bd76 100644
--- a/docs/chezmoi/externals-and-ignore.mdx
+++ b/docs/chezmoi/externals-and-ignore.mdx
@@ -100,6 +100,7 @@ mise tool versions stay local to each machine and are not managed.
.config/mimeapps.list
.config/systemd
.config/systemd/**
+.local/bin/kitty-day-save
.local/bin/niri-launch
.config/fish/auto-Niri.fish
.local/bin/cp-appid
@@ -107,7 +108,7 @@ mise tool versions stay local to each machine and are not managed.
{{ end }}
```
-The block is emitted, and these paths skipped, unless the OS is `linux` and the role is `desktop` or `laptop`. `mac` and `server` machines, and any non-Linux machine regardless of role, never receive the niri compositor config (`dot_config/niri/`), fontconfig (`dot_config/fontconfig/fonts.conf`), default-app associations (`dot_config/mimeapps.list`), the fish auto-start hook (`dot_config/fish/auto-Niri.fish`), the tmux window-title helper, or the `niri-launch` and `cp-appid` scripts. The `noctalia`, `niri-dynamic-workspaces`, and `systemd` rules match nothing in the current source tree; they only take effect if those configs are added later.
+The block is emitted, and these paths skipped, unless the OS is `linux` and the role is `desktop` or `laptop`. `mac` and `server` machines, and any non-Linux machine regardless of role, never receive the niri compositor config (`dot_config/niri/`), fontconfig (`dot_config/fontconfig/fonts.conf`), default-app associations (`dot_config/mimeapps.list`), the fish auto-start hook (`dot_config/fish/auto-Niri.fish`), the tmux window-title helper, the systemd user units (`dot_config/systemd/user/kitty-day-save.service`), or the `niri-launch`, `cp-appid` and `kitty-day-save` scripts. The `noctalia` and `niri-dynamic-workspaces` rules match nothing in the current source tree; they only take effect if those configs are added later.
| Machine | Desktop layer |
| --- | --- |
diff --git a/docs/chezmoi/scripts.mdx b/docs/chezmoi/scripts.mdx
index 0598e9a..a82f599 100644
--- a/docs/chezmoi/scripts.mdx
+++ b/docs/chezmoi/scripts.mdx
@@ -14,7 +14,7 @@ Six scripts run during `chezmoi apply`. Four live in `.chezmoiscripts/`, a direc
chezmoi writes the target state.Install gh extensions.Install and prune agent skills.
- Enable systemd user units on Linux.
+ Enable systemd user units on Linux (nirinit, kitty-day-save).bat cache, font cache.Rebuild bat cache when its theme or config changes.Reinstall yazi plugins when the manifest changes.
@@ -93,9 +93,10 @@ A skipped run still exits 0, so chezmoi records this content as done and a plain
`.chezmoiscripts/run_once_after_30-user-services.sh.tmpl` enables session-critical systemd user units on Linux.
- If `~/.config/systemd/user/nirinit.service` exists and `systemctl` is available, runs `systemctl --user daemon-reload` and `systemctl --user enable nirinit.service`. Failures are ignored.
+- Same check for `~/.config/systemd/user/kitty-day-save.service` (from `dot_config/systemd/user/`, so Linux desktop/laptop roles only), then `systemctl --user enable --now kitty-day-save.service`. See [kitty-day-save](/shell/scripts#kitty-day-save).
- On non-Linux systems it renders to `#!/bin/sh` / `exit 0`.
-Other personal automation units (brain-lens, omp-loop, rclone, hermes, voxtype) are deliberately not enabled; the script comment says to enable them by hand per machine. The repo does not currently contain a `dot_config/systemd/` directory, so the unit file has to exist on the machine already for this script to do anything.
+Other personal automation units (brain-lens, omp-loop, rclone, hermes, voxtype) are deliberately not enabled; the script comment says to enable them by hand per machine. Their unit files are not in the repo, so they have to exist on the machine already.
## 40-post [#40-post]
diff --git a/docs/desktop/index.mdx b/docs/desktop/index.mdx
index 49a063d..e4d681a 100644
--- a/docs/desktop/index.mdx
+++ b/docs/desktop/index.mdx
@@ -6,7 +6,7 @@ sidebar:
---
:::note[Role gating]
-`.chezmoiignore.tmpl` skips the niri layer unless the machine runs Linux **and** its role is `desktop` or `laptop`. The gated targets are `~/.config/niri`, `~/.config/noctalia`, `~/.config/niri-dynamic-workspaces`, `~/.config/fontconfig`, `~/.config/mimeapps.list`, `~/.config/systemd`, `~/.local/bin/niri-launch`, `~/.local/bin/cp-appid`, `~/.config/fish/auto-Niri.fish`, and `~/.config/tmux/niri-title.sh`. On `mac` and `server` roles these are not applied.
+`.chezmoiignore.tmpl` skips the niri layer unless the machine runs Linux **and** its role is `desktop` or `laptop`. The gated targets are `~/.config/niri`, `~/.config/noctalia`, `~/.config/niri-dynamic-workspaces`, `~/.config/fontconfig`, `~/.config/mimeapps.list`, `~/.config/systemd`, `~/.local/bin/kitty-day-save`, `~/.local/bin/niri-launch`, `~/.local/bin/cp-appid`, `~/.config/fish/auto-Niri.fish`, and `~/.config/tmux/niri-title.sh`. On `mac` and `server` roles these are not applied.
`~/.config/hypr`, `~/.config/waybar`, the `omarchy-*` scripts, and the `niri-launch-ai` / `-browser` / `-terminal` / `-webapp` wrappers are **not** listed there, so chezmoi applies them on every role and OS. Only the exact path `.local/bin/niri-launch` is ignored, not the wrappers that call it.
:::
diff --git a/docs/shell/scripts.mdx b/docs/shell/scripts.mdx
index c31260b..6a3b52a 100644
--- a/docs/shell/scripts.mdx
+++ b/docs/shell/scripts.mdx
@@ -41,6 +41,7 @@ Several scripts below also exist as fish functions of the same name (`ai`, `c2p`
| [`ghn`](#ghn) | sh | Create a private repo, clone it, print the path |
| [`git-summary`](#git-summary) | sh | Aggregate a repo's `AGENTS.md` files with code2prompt |
| [`gop`](#gop) | sh | Open the current repo's web page |
+| [`kitty-day-save`](#kitty-day-save) | bash | Save kitty's daily session right before shutdown/reboot |
| [`launch_omp_livediff_overlay`](#launch_omp_livediff_overlay) | sh | omp overlay plus a livediff split in kitty |
| [`lc-elir`](#lc-elir) | sh | Run `/leetcode-eli5` via omp on picked solutions |
| [`mdcp`](#mdcp) | sh | Fetch a URL as Markdown, copy it, preview it |
@@ -189,6 +190,17 @@ Opens a repo's web page for any forge whose URL layout is `https:///` socket in `/proc/net/unix` (3 s timeout each) and exits, which releases the lock. The kitty that owns today's session saves it and holds further saves for 60 s; other kitties ignore the flush. See [kitty daily sessions](/terminal/kitty#daily-sessions).
+
+**Needs:** `systemd-inhibit`, `gdbus`, `kitten`. Linux desktop/laptop roles only (`.chezmoiignore.tmpl`).
+
## launch_omp_livediff_overlay
```sh
diff --git a/docs/terminal/kitty.mdx b/docs/terminal/kitty.mdx
index 7affb44..db9b564 100644
--- a/docs/terminal/kitty.mdx
+++ b/docs/terminal/kitty.mdx
@@ -61,7 +61,7 @@ Colors come from the active Omarchy theme; it is included last so it wins. `syml
| `allow_remote_control` | `socket-only` |
| `listen_on` | `unix:@kitty-{kitty_pid}` |
| `enabled_layouts` | `splits:split_axis=vertical,stack` |
-| Tab bar | `powerline`, `angled`, aligned left, top edge, `tab_title_template "{custom}"` |
+| Tab bar | `powerline`, `slanted`, centered, top edge, always shown (`tab_bar_min_tabs 1`, so the stack-layout indicator is visible with one tab), `tab_title_template "{custom}"` |
:::note
Transparency uses kitty's own alpha, so text stays opaque. This alpha is also what lets the compositor blur the window: Hyprland enables blur globally and opts out every window except terminal classes.
@@ -86,7 +86,7 @@ The socket also exports `KITTY_LISTEN_ON` into each pane. nvim reads it to know
| Key | Action |
| --- | --- |
| `super+n` | Vertical split 50/50 (`launch_vs50`) |
-| `super+z` | Toggle `stack` layout (zoom) |
+| `super+z` | Toggle `stack` layout (zoom); the tab title gains a layers icon and pane count (`nf-md-layers 3`) while other panes are hidden |
| `ctrl+shift+t`, `super+enter` | New tab in current cwd |
| `ctrl+space`, `ctrl+shift+j` | Horizontal split, 30% (`launch_hs`) |
| `ctrl+enter`, `ctrl+shift+l` | Vertical split, 40% (`launch_vs`) |
@@ -100,7 +100,6 @@ The socket also exports `KITTY_LISTEN_ON` into each pane. nvim reads it to know
| `super+a` | Spawn a separate kitty (`--class=kitty-herdr`, `--config ~/.config/kitty/herdr.conf`) |
| `ctrl+shift+c` / `ctrl+shift+v` | Copy / paste clipboard |
| `alt+q w e r t y u i o` | Go to tab 1–9 |
-| `ctrl+shift+d` | `day_session.py` picker: reopen any archived day's session |
| `ctrl+h/j/k/l` | `pass_keys.py`: move to neighbor window left/bottom/top/right, or pass the key through |
| `super+left` / `super+right` | Resize window narrower / wider |
| `super+up` / `super+down` | Resize window taller / shorter (by 3) |
@@ -118,10 +117,12 @@ The tmux and herdr hosts are separate kitty processes because `launch` cannot ta
| Directive | Role |
| --- | --- |
-| `geninclude day_session.py` | Creates today's (empty) session file and emits `startup_session ` whenever no running kitty owns today's session. Ownership is an `flock` on `$XDG_RUNTIME_DIR/kitty-day-.kitty-session.lock`, held until that kitty exits, so closing kitty and launching it again always reopens the day, even if other kitties (or yesterday's) are still running. A second concurrent instance starts plain instead of duplicating today's tabs. |
+| `geninclude day_session.py` | Creates today's (empty) session file and emits `startup_session ` whenever no running kitty owns today's session. Ownership is an `flock` on `$XDG_RUNTIME_DIR/kitty-day-.kitty-session.lock`, held until that kitty exits, so closing kitty and launching it again always reopens the day, even if other kitties (or yesterday's) are still running. A second concurrent instance starts plain instead of duplicating today's tabs. A kitty started with `--session` for another day never takes the lock. |
| `watcher day_session.py` | About 1 s after a window opens, closes, resizes or splits, changes focus or finishes a command (catches `cd`), and every 30 s for output from long-running programs, rewrites the session this instance started with. Title changes are ignored so spinners don't trigger saves. Skips the save once the session has no windows, so closing kitty keeps the last good state. |
-| `map ctrl+shift+d kitten day_session.py` | Picker over every archived day, newest first; opens the choice with `goto_session`. Past days are snapshots: tabs opened from them are not saved back. |
-| `tab_bar_filter session:~ or session:^$` | Tab bar shows only the active day's tabs plus tabs outside any session. |
+
+Earlier days open with the [`kitty-days`](/tools/television#kitty-days) tv cable (`tv kitty-days`), each in its own kitty (`kitty --session `). A past day is a read-only snapshot: nothing in that kitty saves it back. Today's file opened that way, while no kitty owns it, becomes the live day again.
+
+Before shutdown or reboot, the `kitty-day-save.service` user unit runs [`kitty-day-save`](/shell/scripts#kitty-day-save) under a logind delay inhibitor. On `PrepareForShutdown(true)` it sends `kitten day_session.py flush` to every `@kitty-` socket: the owning kitty saves at once (all windows are still alive), then skips saves for 60 s so windows dying during the shutdown can't shrink the file. [`30-user-services`](/chezmoi/scripts#30-user-services) enables the unit.
What a restored window gets back:
@@ -211,12 +212,12 @@ Kitten `ssh` config: for `hostname goliath`, `share_connections yes`.
| File | Purpose |
| --- | --- |
-| `tab_bar.py` | Provides `draw_title` for `{custom}` in the tab template. Title is `