The fork row said "Branch Session" on Claude Code and "Fork Session"
everywhere else, on the strength of a source comment claiming "Claude Code
calls it branching". It does not. `claude --help` documents the flag as
`--fork-session`, described as "When resuming, create a new session ID instead
of reusing the original"; the only occurrences of "branch" in its help are an
unrelated git-branch review option. The claim came from otty's own UI wording,
which I mistook for Claude's vocabulary and then wrote into the source as
fact — so the comment goes with the special case rather than being left behind
as a false statement about someone else's tool.
The split was also inconsistent with itself: Grok takes the identical
`--fork-session` flag and was already labelled "Fork Session". Every agent that
has the capability calls it forking — `codex fork`, `--fork-session` on Claude
Code and Grok, `--fork` on OpenCode — so one wording covers all four.
`fork_label` keeps returning `Option<&'static str>`: it is still the UI's single
capability gate (`None` = no verified fork command, no row offered), and
per-agent wording stays expressible should one ever genuinely diverge.
Generated commands are untouched — the existing table test still pins
`claude --resume <id> --fork-session` and the other three verbatim.
Also drops the two doc sentences that promised the per-agent label, and the
stale "Branch Session" mentions left in comments; no occurrence survives
anywhere in the tree.
A coding-agent conversation is a single thread: to try a risky direction you
either lose the one that got you there, or you don't try it. Every agent tty7
resumes already knows how to branch — `codex fork <id>`, `claude --resume <id>
--fork-session`, `opencode --session <id> --fork`, `grok --resume <id>
--fork-session` — but nothing in tty7 reached them, so the capability was
invisible from the terminal that already knows every pane's session id.
Fork is a per-agent capability beside the existing resume table
(`CLIAgent::fork_command`), not a Codex special case: it is the same `match
self` shape, it reuses the same id validation and the same launch-flag replay,
and four installed agents qualify today. Every command was checked against that
CLI's own `--help`; agents with no fork tty7 could verify return `None` and are
never offered the action, since a guessed flag shape would only ever produce a
usage error in the pane.
Flag replay needed one correctness fix to survive this. A forked pane's own
argv *is* a fork command, so relaunching it would replay the stale subcommand
and id (`codex fork <old>` → an old id as a positional prompt) or double the
modifier (`--fork-session --fork-session`). `codex fork` now sheds its
subcommand exactly as `codex resume` did, and `--fork-session` / `--fork` join
their agents' stale session-targeting lists. That also settles restore: a forked
pane restores through `resume_command`, which now drops the fork flag — a
restart continues the fork rather than branching it again.
Placement follows where the user asked from. A pane-level ask is spatial, so
the pane right-click menu offers Split Right / Left / Down / Up (pane splits
gained a `before` slot for the Left/Up half, which the tree had no way to
express). A tab-level ask isn't, so the tab context menu — inherited verbatim by
the sidebar rows, which is where the request came from — opens the fork in a new
tab with no placement question. The bare action behind the palette, the File
menu and Settings → Keybindings takes the tab-level meaning.
The three ways a fork can't run all surface rather than no-op: no session id
yet (hooks not installed) and a remote pane (the command would shell the *local*
agent) render the row disabled instead of hiding it, so the capability stays
discoverable, and the action paths that have no row to grey out say so in a
notification. Forking mid-turn is allowed but announced — agents fork from the
persisted transcript, so the turn in flight is absent from the copy — and the
parent is untouched either way.
Copy Session ID sits beside Copy Working Directory. Codex has no
copy-or-duplicate subcommand, so "copy the session" is the id: paste it into
`codex resume`, a bug report, or another tool.
Deliberately not built: any reading or writing of an agent's own session files.
tty7's exposure stays the public CLI contract plus the hook payload's session
id, so a change to Codex's rollout format or its version-numbered SQLite index
costs at most a visible shell error. Forked tabs also look exactly like their
parent, by decision — "Rename Tab" is the answer.
Closes#211
Add One Dark Pro as a ninth built-in, slotted alphabetically among the
dark themes: background #282c34, foreground #abb2bf, the classic One
Dark syntax palette for the normal ANSI slots and One Dark Pro's bright
variants for the bright ones.
Two seeds deliberately diverge from the VS Code theme's terminal set:
* The accent is the editor cursor/focus blue #528bff, not the syntax
blue #61afef — the accent doubles as the switch's checked track, and
#61afef sits at the same luminance as the #abb2bf knob (1.11:1).
* The normal red is the classic #e06c75, not the Pro terminal #e05561 —
conditioned for AA the latter lands within 37 channel-distance of the
orange-yellow #d18f52, under the 40 separability floor danger/warning
must clear.
Also bumps the theme count in README and docs (eight → nine).
The default `font_fallbacks` list was macOS-only -- Menlo, Hasklug Nerd
Font Mono, Maple Mono NF CN, Apple Color Emoji. Fallbacks resolve by
family name against installed fonts, so off macOS the whole chain matched
nothing and every glyph the primary lacked was left to the platform's own
cascade. Bundled Hack maps 1548 codepoints and zero ideographs, so on
Windows that was every Chinese character in every pane, and every emoji.
The fall-through is not only a matter of which face you get. `element.rs`
pins each wide cell to `2 x cell_width`, and Hack advances 0.60205em, so
a two-column slot is 1.2041em -- while every stock CJK face (Microsoft
YaHei, PingFang SC, Noto Sans CJK) advances 1.0em. `force_width`
left-aligns, so the ideograph hugs the left of its slot and the remaining
0.2em shows up as a gap on the right of every character. Measured on
Windows at font_size 15: left bearing 1.49px, right bearing 4.90px.
Branch the defaults per platform, keeping Maple Mono NF CN first
everywhere -- 0.6em Latin, 1.2em CJK, the one exact two-cell fit against
Hack (bearings 3.06px / 3.62px, ink centered). It stays referenced by
name only, never bundled, at ~20MB per weight.
Changing `Config::default` alone would reach nobody who already has a
`config.json`, which is every existing user. So `fallback_chain` appends
the platform's stock faces the same way it already pins Hack: a fallback
is consulted only after everything ahead of it has missed, so appending
can never displace a face the user chose, and the file is never rewritten.
Verified by driving two builds against one config naming only absent
macOS faces: before, the CJK line differed from an explicit Microsoft
YaHei chain by 3571 pixels (the cascade picked something else); after, it
is pixel-identical.
The link modifier was `mods.platform`, which gpui maps to ⌘ on macOS but
to the Win/Super key elsewhere — a key the OS mostly swallows, so on
Windows and Linux neither the hover underline nor click-to-open could be
triggered at all. `config.json`'s own docs already promised "⌘/Ctrl-click";
this is the implementation catching up.
Use `Modifiers::secondary()` (⌘ on macOS, Ctrl elsewhere) at all three
sites: the click handler, the hover probe, and the app-level modifier
tracking that pushes the state down to background panes. Not
`platform || control` — that would steal ⌃-click on macOS, where it means
"right click".
Ctrl+click still falls through to mouse-tracking TUIs when there's no
link under the cursor, since `open_link_at` reports whether it consumed
the click.
Fixes#183
Grok Build exposes a Claude Code-shaped hook surface, so tty7 can now
install into it and give grok panes live session status and
resume-after-restart, not just a brand chip.
- Owned hook file at ~/.grok/hooks/tty7.json (grok loads every JSON file
there; global hooks need no folder-trust grant), so the user's own
hooks are never touched.
- Read camelCase payload keys: grok's envelope sends sessionId, and
without it restore loses the id --resume needs.
- Relabel events that arrive through grok's Claude-compat scan of
~/.claude/settings.json, keyed on the GROK_HOOK_EVENT var its hook
runner injects — otherwise a grok pane reports Claude Code, and having
both integrations installed emits every turn under two identities.
- Resume via `grok --resume <id>`, stripping the flags that would fight
the injected id (--resume/--load/--continue/--session-id/--fork-session)
or relocate the session (--worktree/--worktree-ref).
Notification is subscribed with a matcher for elicitation_dialog only.
Grok dispatches its permission_prompt notification before the permission
system decides, so it fires on essentially every tool call, auto-approved
ones included; escalating that to the amber "needs you" state would flash
the pane and fire a desktop notification on every tool a turn runs.
The local command editor consumed every Ctrl chord at the prompt, matched
or not, so two things the shell owns quietly stopped working (#163).
^J and ^M carry accept-line's control codes — Enter by another name — but
fell into `apply_readline_ctrl`'s no-op arm, so the keys did nothing at
all. Route them through the same path Enter takes, via a shared
`accept_line`, so the completion picker and the history menu treat them
identically.
^R was recognized, but only ever opened tty7's own history menu, with no
way back to a `bindkey`ed widget (fzf, percol). Add `history_search`
(default on, Settings → Terminal → Keyboard): with it off, the edited
line is handed to the shell and the raw ^R follows it, so whatever is
bound there answers. The "shell integration never engaged" notice stays
quiet in that case — ^R reaching the PTY is then the point, not a gap.
`handoff_tab_to_shell` generalizes to `handoff_line_to_shell(chord)` to
carry the ^R handoff; the Tab path is a thin wrapper over it and its
behavior is byte-for-byte unchanged.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Three fixes for tty7's Tab completion:
- Tab is no longer swallowed when the engine has no candidates: the
locally edited line is handed off to the shell (text shipped raw,
cursor walked back, Tab sent) and the local editor suspends until the
next prompt cycle, so shell-native completion (compsys, fzf-tab, ...)
answers instead. The handoff release keys off a new entered-prompt
cycle counter rather than the raw Prompt-frame seq, so same-prompt
redraws (PS1-embedded 133;B re-emissions) cannot re-engage the editor
while zle still holds the handed-off text.
- cd/pushd/popd/rmdir complete directories only in the no-signature
path fallback; Fig 'folders' templates narrow signature slots the
same way. Symlinks now classify by their target.
- New tab_completion config field (default true) plus a Settings ->
Terminal -> Keyboard toggle; when off every Tab goes to the shell.
Resume-after-restart replayed a hardcoded per-agent command
(claude --resume <id>), dropping whatever flags the agent was
originally launched with (--dangerously-skip-permissions, --model).
The daemon's foreground poll already reads the agent's argv for
detection; keep it, stream it to the client inside AgentSessionState
(serde-default, wire-compatible both ways), persist it in the session
Leaf, and splice a conservatively-gated flag tail into the resume
command. The gate refuses anything that is not a plain flag-shaped
token sequence and falls back to the bare table command.
The Windows 133;C typed-command capture is forgeable by terminal
output, so it contributes identity only, never flags. Copilot gains a
resume entry (copilot --resume <id>, hooks already report its session
id) and Amp's threads continue verified to accept global flags.
- Double-click expands to the whole URL, email, file path, sci-notation
number, identifier chain, OSC 8 hyperlink run, or matching bracket/quote
pair containing the clicked word. Candidates only ever grow the plain
word selection, so nothing regresses below the stock word behavior.
- Chinese segments with jieba's dictionary on all platforms; Kana/Hangul
use CFStringTokenizer on macOS. The table builds lazily on a background
thread so the first double-click never pays the cost.
- Latin words glued to CJK text narrow to the clicked script's sub-run
instead of selecting the mixed blob.
- Bracket pairs (ASCII and full-width) and symmetric quotes (parity
matched) select through their match, in the grid and prompt editor alike.
- Shift+click extends the existing grid selection instead of restarting.
- Word separators are configurable (word_separators, shared by grid and
prompt editor); new 'Smart selection' toggle in Settings > Terminal.
The group key now reads the first pane's cwd instead of the focused
pane's, so switching focus between splits in different repos never
relocates the row (the branch line still follows focus). Also documents
the repo-grouped sidebar and the sidebar_grouping setting in
features.md / features.zh-CN.md.
- Diff overlay paints the gradient/opacity-aware window background instead
of the stale representative solid.
- Config hot-reload now re-runs apply_theme with the window (blur flip,
traffic-light re-pin) and re-syncs the Appearance opacity slider, so
hand-edits to config.json / theme files take effect fully.
- Document the theme background/window settings in features docs (EN + zh-CN).
A cross-platform tray / menu bar status item: the icon flips to an
attention state when any coding agent blocks on input, and its menu
lists agent panes (brand avatar + status dot, click to reveal),
switches the notification policy, forces an update check, and offers
Quit and Stop Daemon alongside the session-keeping plain quit.
macOS/Windows use tray-icon (muda menus, main-thread NSStatusItem);
Linux deliberately uses ksni (pure-Rust SNI over zbus) instead of
tray-icon's GTK+libappindicator backend so the AppImage stays lean,
with a slow-backoff retry for SNI hosts that appear after login.
Bitmaps are rasterized at runtime with resvg (already in the tree).
Gated by show_tray_icon (default on) with a Settings toggle; the 1s
foreground poll re-reads it, so toggles and config.json hot-reloads
apply live.
Slim the READMEs to an index (why / install / what's inside / benchmarks);
move feature details, keybindings, and performance notes to docs/features.md
(en + zh-CN). New tagline: a terminal workbench — shells, sessions, SSH,
coding agents. Sync the Cargo.toml description.