diff --git a/docs/shell/fish.mdx b/docs/shell/fish.mdx
index 898f109..a3141c5 100644
--- a/docs/shell/fish.mdx
+++ b/docs/shell/fish.mdx
@@ -188,6 +188,7 @@ The header comment says the file should stay empty, but it holds the following:
| ctrl-p | `_pj_bind`: `pj`, the task runner on the `pj` cable ([Scripts](/shell/scripts#pj)), then repaint |
| ctrl-a | `omp-session`, then repaint: resume or start omp for the current repo by branch and session age, opening the `omp-sessions` cable only when nothing qualifies ([Scripts](/shell/scripts#omp-session)) |
| ctrl-w | `atuin ai inline`: natural-language command overlay |
+| ctrl-z | `_jobs_bind`: with one background job, `fg` it; with several, pick one from the `jobs` cable ([television](/tools/television#jobs)) and `fg` that; with none, nothing. While a job holds the terminal, ctrl-z is still the tty's suspend, so the key toggles suspend and resume |
| ctrl-g | `lazygit` |
| down | `_pj_bind_down`: opens the `pj` cable on an idle, empty prompt, otherwise fish's `down-or-search` |
| ctrl-c | Clears the command line, runs `clear` (screen and kitty scrollback), repaints |
@@ -211,6 +212,9 @@ The pickers run `tv --inline`, which opens on the line below the prompt and leav
| `_tv_inline ARGS` | `tv ARGS --inline` | `\033[A` (cursor up), returns tv's status |
| `_gho_bind` | `gho` | `\033[A` if the cwd did not change; `\n` after a cd, so the prompt lands below onefetch |
| `_pj_bind [--quiet]` | `pj` with `PJ_STATE` set to a temp file | `\033[A` for `open` (cancelled) or empty (no task sources); `\n` for `ran` (the task printed output) |
+| `_jobs_bind` | `fg` of the only job, or `jobs` with `TV_JOBS` set to a temp file holding the job table | `\033[A` (via `_tv_inline`) when the picker is cancelled |
+
+`_jobs_bind` runs `fg` by replacing the command line with ` __jobs_bind_fg ` (leading space: kept out of history) and executing it, not from inside the binding. A binding runs while fish's reader still owns the terminal with extended keys on, so a job started with `&` that had never held the terminal got ctrl-z and ctrl-c as escape sequences instead of signals. `__jobs_bind_fg` erases the echoed prompt line (`\033[A\033[2K` on `/dev/tty`) and runs `fg` with its "Send job … to foreground" message sent to `/dev/null`, so resuming leaves nothing on screen. Text already typed is put back at the next prompt by a one-shot `fish_prompt` handler.
`omp-session` (ctrl-a) does its own `\033[A` when its `omp-sessions` picker is cancelled, so the binding only repaints.
@@ -268,6 +272,7 @@ Every file in `functions/` is autoloaded by name. Several also exist as POSIX sc
| `_nvm_version_deactivate` | nvm.fish internal: removes a version's `bin` from `PATH` |
| `_gho_bind` | ctrl-o handler: `gho`, then cursor fix and repaint (see [Key bindings](#key-bindings)) |
| `_pj_bind` | ctrl-p handler: `pj` with `PJ_STATE`, then cursor fix and repaint. `--quiet` sets `PJ_QUIET=1` |
+| `_jobs_bind` | ctrl-z handler: `fg` the only job or pick one from the `jobs` cable (see [Key bindings](#key-bindings)) |
| `_pj_bind_down` | Down-arrow handler: `_pj_bind --quiet` on an empty idle prompt, else `down-or-search` |
| `_tv_inline` | `tv $argv --inline`, then moves the cursor up one line on `/dev/tty` |
| `ai` | `gum choose` among `claude`, `opencode`, `codex`, `hermes`, `fractal`. `claude` runs with `--dangerously-skip-permissions` |
diff --git a/docs/tools/television.mdx b/docs/tools/television.mdx
index 75b6f16..96e4e47 100644
--- a/docs/tools/television.mdx
+++ b/docs/tools/television.mdx
@@ -62,7 +62,7 @@ Global keys added on top of the defaults:
## Sorting
-Every custom cable except `agents` (a fixed list, see [agents](#agents)) and `omp-sessions` (sorted by [`omp-session`](/shell/scripts#omp-session) itself) pipes its source through [`mtime-sort`](/shell/scripts#mtime-sort), which orders lines newest-first by the mtime of the path in the line's last tab-separated field (`--repo` stats `/.git/index` first). tv then ranks match quality and frecency on top of that order. All custom cables set `shell = "bash"`, because `$SHELL` is fish.
+Every custom cable except `agents` (a fixed list, see [agents](#agents)), `omp-sessions` (sorted by [`omp-session`](/shell/scripts#omp-session) itself), and `jobs` (fish's job order) pipes its source through [`mtime-sort`](/shell/scripts#mtime-sort), which orders lines newest-first by the mtime of the path in the line's last tab-separated field (`--repo` stats `/.git/index` first). tv then ranks match quality and frecency on top of that order. All custom cables set `shell = "bash"`, because `$SHELL` is fish.
## Custom cables
@@ -75,8 +75,9 @@ Every custom cable except `agents` (a fixed list, see [agents](#agents)) and `om
| [`obsidian`](#obsidian) | `cable/obsidian.toml` | `tv obsidian` |
| [`agents`](#agents) | `cable/agents.toml` | `tv agents` |
| [`omp-sessions`](#omp-sessions) | `cable/omp-sessions.toml` | `tv omp-sessions`, ctrl-a (`omp-session`) when it has nothing to resume |
+| [`jobs`](#jobs) | `cable/jobs.toml` | ctrl-z in fish (`_jobs_bind`) with two or more background jobs |
-The keys are bound in fish (`fish_user_key_bindings.fish`), zsh (`keybinds.zsh`), and bash (`keybinds.bash`); bash has no ctrl-a binding. See [fish](/shell/fish#key-bindings) and [bash and zsh](/shell/bash-zsh).
+The keys are bound in fish (`fish_user_key_bindings.fish`), zsh (`keybinds.zsh`), and bash (`keybinds.bash`); bash has no ctrl-a binding, and ctrl-z is fish only. See [fish](/shell/fish#key-bindings) and [bash and zsh](/shell/bash-zsh).
### ghq-repos
@@ -87,15 +88,18 @@ Entries are `namepath`: tv shows and matches the name and prints the path.
| Repos | `ghq list` under `ghq root`, plus the chezmoi source (`~/.local/share/chezmoi`) listed as `github.com/prdlk/dotfiles` | `mtime-sort --repo` |
| Vaults | Every directory under `~/Documents/Obsidian` (depth 3) that contains `.obsidian`, named relative to that directory | `mtime-sort` |
-**Preview.** `onefetch --no-art` for git directories, otherwise `eza --tree --level 2` (falling back to `ls -la`).
+**Preview.** Two commands; ctrl-f cycles. The default is the commit graph for git directories: `git log --graph --all`, newest 300 commits, as short hash, relative date, subject and refs. The second is `onefetch --no-art`. Non-git directories show `eza --tree --level 2` (falling back to `ls -la`) in both.
+
+[serie](https://github.com/lusingander/serie) cannot draw inside the preview pane: it is a fullscreen TUI that paints its graph with kitty images and has no print mode. ctrl-g opens it instead.
| Key | Action |
| --- | --- |
| enter | Print the path. The `gho` function cds into it, clears, and runs `onefetch` in git repos |
| ctrl-e | `edit`: `cd && nvim .` on `/dev/tty` |
| ctrl-b | `browse`: `cd && gop` in fork mode, so tv stays open ([gop](/shell/scripts#gop)) |
+| ctrl-g | `history`: `serie` on `/dev/tty` in fork mode for git directories; q returns to the picker |
-**Needs:** `ghq`, `fd`, `onefetch`, `mtime-sort`.
+**Needs:** `ghq`, `fd`, `git`, `serie`, `onefetch`, `mtime-sort`.
### git-files
@@ -154,3 +158,16 @@ Source `omp-session --list`: the [omp](/ai/omp) sessions of the current git repo
The preview runs `omp-session --preview`: title, last-write time and cwd, then the text of every user and assistant message. enter runs `resume` in execute mode, `omp-session --resume `, which `exec`s `omp --resume `.
**Needs:** `omp-session`, `omp`.
+
+### jobs
+
+The current fish shell's background jobs, stopped or running, newest first. tv runs as a child process and cannot read the shell's job table, so `_jobs_bind` ([fish](/shell/fish#key-bindings)) writes it to a temp file named by `$TV_JOBS` and the source just `cat`s that file; opened any other way the list is empty. Entries are `% `; enter prints the id and the binding runs `fg %`.
+
+The preview finds the job's processes by process group (procps `ps -g` matches sessions, so it filters `ps -e -o pgid,pid`) and shows the first process's cwd and start time, then `ps --forest` with PID, STAT (`T` = stopped), elapsed time, CPU, RSS, and arguments. The preview script avoids `{`/`}` because tv would parse them as templates and leave `{split:…}` unexpanded.
+
+| Key | Action |
+| --- | --- |
+| enter | Print the job id; `_jobs_bind` runs `fg` on it |
+| ctrl-d | `kill` then `reload_source`: SIGTERM to the job's process group, then SIGCONT so a stopped job receives it. Once the group is gone (polled up to 1 s) the row is removed from `$TV_JOBS`, so the reloaded list drops it; a job that ignores SIGTERM stays listed. Fish reports the termination at the next prompt |
+
+**Needs:** `ps`, fish.