diff options
| author | David Heinemeier Hansson <david@hey.com> | 2026-08-11 02:04:12 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-08-11 03:04:12 +0300 |
| commit | 350f001320ec4a119f09c38157973126962a6121 (patch) | |
| tree | 408ddcf37955f9b1258fa1b0f1fa5f10e504944b /docs/next | |
| parent | 42789c8e1d86a3d5ea529b1b047172bfd6979a21 (diff) | |
feat: keep the outer terminal window title in sync with the session (#2627)
* feat: keep the outer terminal window title in sync with the session
Herdr emulates the terminals in its panes, so an OSC 0/OSC 2 title
written inside a pane stops at Herdr and never reaches the terminal
Herdr itself runs in. That outer title is what window managers read for
title bars, tab bars, and group bars, so it kept showing whatever the
shell or ssh happened to leave behind.
Add `ui.window_title`, rendered from {hostname}, {workspace}, {tab},
{pane}, and {terminal_title}, and push it to the foreground client
whenever it changes. It renders on the server, so {hostname} names the
machine the panes actually run on rather than the machine a thin remote
client runs on, and it is gated on a pending render so an idle loop
never pays for it.
A title is only remembered as delivered once a foreground client takes
it, so the first client to attach is written to rather than skipped.
`client.window_title.set` still wins over the configured title, and
clearing it now hands the title back to `ui.window_title` instead of
only "herdr".
* fix: deliver the outer window title to a newly attached client
ClientConnected assigns the foreground client directly rather than going
through promote_client_to_foreground, so clearing the sent-title cache
there missed the case that matters most: attaching a second terminal to a
running session. The title was usually unchanged, so the sync returned
early and the new terminal kept whatever its shell or ssh had left.
Key the cache on the client that received the title instead of relying on
every foreground assignment to invalidate it.
* perf: keep hidden pane output off the window title path
Output from a hidden or background pane sets needs_render without setting
needs_full_render, and the retained render plan then skips presentation
for it entirely. Syncing the title on needs_render meant every coalesced
hidden-output tick still formatted, sanitized, and allocated a title that
could not have changed, against the hidden-source early exit AGENTS.md
requires.
Every input to the title is app state, which always requests a full
render, so gate on that instead. The one exception is the focused pane's
own terminal title, which arrives through PTY parsing, so ask for a full
render when that changes and the configured title uses it.
* fix: only cache a window title a client writer received
* fix: carry an api set window title across a live handoff
* fix: make outer window title updates event-driven
---------
Co-authored-by: Ogulcan Celik <ogulcancelik@gmail.com>
Diffstat (limited to 'docs/next')
11 files changed, 58 insertions, 6 deletions
diff --git a/docs/next/CHANGELOG.md b/docs/next/CHANGELOG.md index a912c438..52dd9f0a 100644 --- a/docs/next/CHANGELOG.md +++ b/docs/next/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased ### Added +- Herdr now keeps the outer terminal window title in sync with the session through `ui.window_title`, so window managers and terminal tab bars show the active workspace and the host the panes actually run on. - The desktop tab bar now has configurable right-aligned status entries for zoom state, hostname, date/time, literal text, and asynchronously refreshed command output. - Optional `keys.move_tab_previous` and `keys.move_tab_next` bindings now reorder the active tab in place, wrapping at either end. - Optional `keys.resize_pane_left`, `keys.resize_pane_down`, `keys.resize_pane_up`, and `keys.resize_pane_right` bindings now resize the focused pane in one keystroke without entering resize mode. diff --git a/docs/next/website/src/content/docs/cli-reference.mdx b/docs/next/website/src/content/docs/cli-reference.mdx index 7ce6c314..a8d859b4 100644 --- a/docs/next/website/src/content/docs/cli-reference.mdx +++ b/docs/next/website/src/content/docs/cli-reference.mdx @@ -341,7 +341,7 @@ terminal, or agent target. It prints newline-delimited JSON `terminal.frame` records with base64-encoded ANSI bytes, then a `terminal.closed` record when the server closes the stream. Multiple observers can watch the same terminal without taking input, resize, scroll, or takeover authority. -`terminal title clear` restores Herdr's default outer terminal window title. +`terminal title clear` hands the outer terminal window title back to `ui.window_title`. ## Output waits diff --git a/docs/next/website/src/content/docs/configuration.mdx b/docs/next/website/src/content/docs/configuration.mdx index 59ce7b2e..90614c53 100644 --- a/docs/next/website/src/content/docs/configuration.mdx +++ b/docs/next/website/src/content/docs/configuration.mdx @@ -289,6 +289,21 @@ Command entries run immediately and then at `interval_seconds` without blocking Separators appear only between visible entries. Set `tab_bar_right_separator = ""` for direct concatenation. On a narrow tab row, the complete status area yields to the tabs and their controls. +### Outer terminal window title + +Herdr emulates the terminals in its panes, so an `OSC 0`/`OSC 2` title written inside a pane stops at Herdr. Herdr writes its own title to the terminal it runs in, which is what window managers and terminal tab bars read: + +```toml +[ui] +window_title = "{hostname}: {workspace}" +``` + +Tokens are `{hostname}`, `{workspace}`, `{tab}`, `{pane}` (the focused pane's manual name), and `{terminal_title}` (the focused pane's own terminal title with spinner frames stripped). Write `{{` and `}}` for literal braces. A token with no value renders empty. + +The title renders on the Herdr server, so `{hostname}` names the machine the panes run on, including when you attach with `herdr --remote` or run `herdr` over SSH. Set `window_title = ""` to leave the outer terminal title alone. + +`client.window_title.set` overrides the configured title until `client.window_title.clear` hands it back. + Agent status uses compact colored dots by default. To distinguish blocked, working, done, idle, and unknown states by shape as well as color, choose **distinct symbols** in Settings or configure: ```toml diff --git a/docs/next/website/src/content/docs/ja/cli-reference.mdx b/docs/next/website/src/content/docs/ja/cli-reference.mdx index 3cc1e13b..1521d593 100644 --- a/docs/next/website/src/content/docs/ja/cli-reference.mdx +++ b/docs/next/website/src/content/docs/ja/cli-reference.mdx @@ -308,7 +308,7 @@ herdr terminal title clear ``` ダイレクトアタッチからは `ctrl+b q` でデタッチします。リテラルの `ctrl+b` は `ctrl+b ctrl+b` で送ります。 -`terminal title clear` は Herdr のデフォルトの外側ターミナルウィンドウタイトルを復元します。 +`terminal title clear` は外側ターミナルウィンドウタイトルを `ui.window_title` に戻します。 ## 出力待機 diff --git a/docs/next/website/src/content/docs/ja/configuration.mdx b/docs/next/website/src/content/docs/ja/configuration.mdx index 9f7c0e5e..a8f3570f 100644 --- a/docs/next/website/src/content/docs/ja/configuration.mdx +++ b/docs/next/website/src/content/docs/ja/configuration.mdx @@ -255,6 +255,21 @@ yellow = "#f9e2af" サイドバーは Herdr のメインダッシュボードです。サイズ、折りたたみモード、Agent パネルの並び順、マウスの挙動、ペインの境界線、その他の表示設定については、[設定リファレンス](/docs/config-reference/)で `ui.` を検索してください。 +### 外側のターミナルのウィンドウタイトル + +Herdr はペイン内のターミナルをエミュレートするため、ペイン内で書き込まれた `OSC 0`/`OSC 2` のタイトルは Herdr で止まります。Herdr は自身が動作しているターミナルに独自のタイトルを書き込み、ウィンドウマネージャーやターミナルのタブバーはこれを読み取ります: + +```toml +[ui] +window_title = "{hostname}: {workspace}" +``` + +トークンは `{hostname}`、`{workspace}`、`{tab}`、`{pane}`(フォーカス中のペインの手動名)、`{terminal_title}`(フォーカス中のペイン自身のターミナルタイトルからスピナーを取り除いたもの)です。波括弧そのものを書くには `{{` と `}}` を使います。値のないトークンは空になります。 + +タイトルは Herdr サーバー上で生成されるため、`herdr --remote` で接続した場合や SSH 越しに `herdr` を実行した場合でも、`{hostname}` はペインが動作しているマシンを指します。外側のターミナルのタイトルに触れないようにするには `window_title = ""` を設定します。 + +`client.window_title.set` は設定されたタイトルを上書きし、`client.window_title.clear` がそれを戻します。 + ### サイドバーの行レイアウト 展開されたデスクトップサイドバーでは、`rows` の各内側の配列が 1 行として描画されます。完全なデフォルトレイアウトは次のとおりです: diff --git a/docs/next/website/src/content/docs/ja/socket-api.mdx b/docs/next/website/src/content/docs/ja/socket-api.mdx index 7b9ee4cc..f5ef6f75 100644 --- a/docs/next/website/src/content/docs/ja/socket-api.mdx +++ b/docs/next/website/src/content/docs/ja/socket-api.mdx @@ -273,7 +273,7 @@ reason の候補は `shown`、`disabled`、`rate_limited`、`no_foreground_clien {"id":"req_title_clear","method":"client.window_title.clear","params":{}} ``` -`client.window_title.clear` は Herdr のデフォルトタイトルを復元します。レスポンスは `type: "client_window_title"` で、`changed` と、`set`、`cleared`、`no_foreground_client` のいずれかの reason を含みます。 +`client.window_title.clear` はタイトルを `ui.window_title` に戻します。レスポンスは `type: "client_window_title"` で、`changed` と、`set`、`cleared`、`no_foreground_client` のいずれかの reason を含みます。 Worktree メソッドは Git チェックアウトを Herdr ワークスペースとして管理します。`worktree.create` はチェックアウトを作成し、新しい `workspace`、`tab`、`root_pane`、`worktree` のレコードを返します。要求されたブランチがローカルに既存ならそれをチェックアウトし、なければ要求されたベースまたは `HEAD` からブランチを作成します。`worktree.open` は既存のチェックアウトを開くか、すでに開いているワークスペースを返します。`worktree.remove` はリンクされた子ワークスペースに対して `git worktree remove` を実行し、ブランチは決して削除しません。 diff --git a/docs/next/website/src/content/docs/socket-api.mdx b/docs/next/website/src/content/docs/socket-api.mdx index 86551c03..430cf4bf 100644 --- a/docs/next/website/src/content/docs/socket-api.mdx +++ b/docs/next/website/src/content/docs/socket-api.mdx @@ -375,7 +375,7 @@ Set or clear the foreground client's outer terminal window title: {"id":"req_title_clear","method":"client.window_title.clear","params":{}} ``` -`client.window_title.clear` restores Herdr's default title. The response is +`client.window_title.clear` hands the title back to `ui.window_title`. The response is `type: "client_window_title"` with `changed` and reason `set`, `cleared`, or `no_foreground_client`. diff --git a/docs/next/website/src/content/docs/zh-cn/cli-reference.mdx b/docs/next/website/src/content/docs/zh-cn/cli-reference.mdx index baebfcc8..b5468021 100644 --- a/docs/next/website/src/content/docs/zh-cn/cli-reference.mdx +++ b/docs/next/website/src/content/docs/zh-cn/cli-reference.mdx @@ -308,7 +308,7 @@ herdr terminal title clear ``` 从直接附加中用 `ctrl+b q` 分离。用 `ctrl+b ctrl+b` 发送字面的 `ctrl+b`。 -`terminal title clear` 恢复 Herdr 默认的外层终端窗口标题。 +`terminal title clear` 把外层终端窗口标题交还给 `ui.window_title`。 ## 输出等待 diff --git a/docs/next/website/src/content/docs/zh-cn/configuration.mdx b/docs/next/website/src/content/docs/zh-cn/configuration.mdx index 0cb9ce42..b83d10ca 100644 --- a/docs/next/website/src/content/docs/zh-cn/configuration.mdx +++ b/docs/next/website/src/content/docs/zh-cn/configuration.mdx @@ -255,6 +255,21 @@ yellow = "#f9e2af" 侧边栏是 Herdr 的主仪表盘。在[配置参考](/docs/config-reference/)中搜索 `ui.`,可查看尺寸、折叠模式、Agent 面板排序、鼠标行为、窗格边框和其他显示设置。 +### 外层终端窗口标题 + +Herdr 会模拟各窗格中的终端,因此窗格内写入的 `OSC 0`/`OSC 2` 标题只会停留在 Herdr。Herdr 会向自己所运行的终端写入自己的标题,窗口管理器和终端标签栏读取的正是这个标题: + +```toml +[ui] +window_title = "{hostname}: {workspace}" +``` + +可用记号为 `{hostname}`、`{workspace}`、`{tab}`、`{pane}`(聚焦窗格的手动名称)和 `{terminal_title}`(聚焦窗格自身的终端标题,已去除加载动画字符)。用 `{{` 和 `}}` 表示字面花括号。没有值的记号会渲染为空。 + +标题在 Herdr 服务器上生成,因此即使通过 `herdr --remote` 连接或经由 SSH 运行 `herdr`,`{hostname}` 仍然指向运行窗格的那台机器。设置 `window_title = ""` 可以不改动外层终端标题。 + +`client.window_title.set` 会覆盖配置的标题,直到 `client.window_title.clear` 将其交还。 + ### 侧边栏行布局 展开的桌面侧边栏会将 `rows` 中的每个内层数组渲染为一行。以下是完整的默认布局: diff --git a/docs/next/website/src/content/docs/zh-cn/socket-api.mdx b/docs/next/website/src/content/docs/zh-cn/socket-api.mdx index 269e6a3d..255c33e2 100644 --- a/docs/next/website/src/content/docs/zh-cn/socket-api.mdx +++ b/docs/next/website/src/content/docs/zh-cn/socket-api.mdx @@ -273,7 +273,7 @@ herdr notification show "build failed" --body "api workspace" --position top-lef {"id":"req_title_clear","method":"client.window_title.clear","params":{}} ``` -`client.window_title.clear` 恢复 Herdr 的默认标题。响应是 `type: "client_window_title"`,带 `changed` 和 `set`、`cleared` 或 `no_foreground_client` 之一的 reason。 +`client.window_title.clear` 把标题交还给 `ui.window_title`。响应是 `type: "client_window_title"`,带 `changed` 和 `set`、`cleared` 或 `no_foreground_client` 之一的 reason。 Worktree 方法把 Git 检出作为 Herdr 工作区管理。`worktree.create` 创建检出,并返回新的 `workspace`、`tab`、`root_pane` 和 `worktree` 记录。请求的分支已在本地存在时检出它;否则从请求的 base 或 `HEAD` 创建分支。`worktree.open` 打开已有检出,或返回已打开的工作区。`worktree.remove` 对关联的子工作区运行 `git worktree remove`,从不删除分支。 diff --git a/docs/next/website/src/data/config-reference.json b/docs/next/website/src/data/config-reference.json index 6024aaab..bcc3f02e 100644 --- a/docs/next/website/src/data/config-reference.json +++ b/docs/next/website/src/data/config-reference.json @@ -754,6 +754,12 @@ "description": "Text inserted between visible right-aligned tab bar entries." }, { + "key": "ui.window_title", + "type": "string", + "default": "\"{hostname}: {workspace}\"", + "description": "Title Herdr writes to the terminal it runs in. Tokens: {hostname}, {workspace}, {tab}, {pane}, {terminal_title}. Empty leaves the outer title alone." + }, + { "key": "ui.agent_panel_sort", "type": "enum", "default": "\"spaces\"", |
