Skip to content

0.1.0-prerelease.5 - 2026-09-26

Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 26 Sep 09:50
427be86

Release Notes

Fixed (the copy chord no longer overwrites a program's own copy)

  • In a pane whose program reads the mouse — Claude Code in full-screen mode,
    vim, lazygit — the copy chord used to write the last text termherd had copied
    over the clipboard, on every press, once anything had been copied anywhere:
    the program's own copy of a drag was silently replaced by stale text (#316).
    The shell no longer keeps a copy cache; the terminal reports whether it holds
    a selection at all (including one scrolled out of view), and the chord asks
    it to copy or does nothing. Over MCP, copy with nothing selected now
    reports inert / no-context in that pane too.

Fixed (the stdio server could choose what termherd executes)

  • set_option listed shell.program / shell.args as writable beside
    theme — and their value is what every shell session runs at the next
    launch, so any client of termherd-mcp could set it unattended. Both are now
    read-only over MCP; list_options and the schema resource carry a
    writable flag per id, so a model can tell before it tries (#283).
  • A value that does not fit an option's kind (a non-array palette, a theme
    outside dark/light) is now refused with a JSON-RPC error and nothing
    is written. It used to be written anyway, with a warning in the answer; that
    warnings text is gone.

Fixed (the mouse reaches a program that reads it)

  • A full-screen program with mouse reporting on — Claude Code's /diff and
    /resume, vim, lazygit, fzf, less, tmux — now receives clicks, drags and
    (when it asked for them) bare moves, in the SGR or X10 encoding it
    negotiated. Only wheel notches were ever forwarded before; a press over such
    a program was consumed as a local text selection and the program never heard
    of it (#155). While a program reads the mouse the terminal draws no selection
    of its own and a right-click reaches the program instead of pasting; hold
    Shift to select text out of it, and the link modifier still opens a path
    or URL.
  • mouse_in_session answers forwarded for such a program, beside
    selection and ignored, and an agent can now drive a mouse-mode TUI
    through the act → wait → observe loop. It rejects a session that has
    exited rather than claiming its terminal did something.
  • A drag that leaves the pane still ends with its release, on the border
    cell; a modifier pressed or released mid-drag no longer hands half the
    gesture to the other owner; and motion is reported to the program once
    per cell rather than once per pixel.

Added (a pointer inside a terminal, over MCP)

  • mouse_in_session places one mouse event at a cell of a session's
    terminal — the pointer counterpart of run_in_session (#300). kind is
    press / release / click / drag / move, col / row are 0-based
    cells of the visible screen, button is optional. A cell
    outside the pane's geometry, or an unknown word, rejects the whole call
    before anything applies. The answer's pointer field says what the terminal
    did: selection (a press then a drag selects the text between them, both
    cells included; a bare click clears it) or ignored.
  • The event travels the wheel's path end to end (Event::TerminalPointer →
    PtyHost::pointer → the terminal thread); the gesture rule lives once in
    core, read by the shell to answer and by the terminal to place the
    selection with its live offset. It landed before #155 (above) so that fix
    could be verified by the agent that wrote it.

Added (hidden terminal hyperlinks)

  • A link whose label hides its URL — an OSC 8 hyperlink, the way gh,
    ls --hyperlink and rendered markdown print #76 over an issue URL — now
    underlines on hover and opens on Cmd/Ctrl+click like a printed URL (#84).
    The Screen snapshot carries every hyperlink as a span over its cells, and
    the pointer answers that span before it scans the visible text; a
    hyperlink laid over text that is itself URL-shaped opens the hyperlink.

Fixed (list_sessions over MCP)

  • list_sessions answered a bare JSON array, which MCP clients reject on their
    schema check — every call failed before the caller saw a row. The sessions now
    ride in a sessions field, like every other tool's answer (#299).
  • Every tool now shapes its structured answer at one seam, which refuses
    anything but an object, and a sweep reads the tool list from the router so a
    tool added later cannot slip past the rule.

Added (a repository in the sidebar, before it has any session)

  • A sidebar row is no longer born only from a session. + Add a repo opens
    the native folder picker, and dropping a folder on the window does the same
    thing — a dropped file is ignored (#279). The row carries the usual $ and
    🤖 launch buttons, says No sessions yet until it has one, and sorts to the
    top until then. A ✕ takes it back out; it appears only on rows you added,
    since a discovered project has no declaration to drop.
  • Hand-added repositories live in ~/.termherd/metadata.json, beside stars and
    renames. Nothing is written under ~/.claude.
  • Over MCP: add_repo and forget_repo, answering about the sidebar row —
    membership, not what the window happens to be drawing — and declared on
    each project in snapshot and in the ⌘⇧S capture.

Changed (clipboard mouse gestures)

  • The two classic terminal clipboard conventions are settings now, both off by
    default: terminal.copy_on_select and terminal.paste_on_right_click (#36).
    Copy-on-select changes behaviour. It was already there and
    unconditional — a drag release and a double-click both copied — so what this
    adds is the off position, and off is the default. Set
    "terminal": { "copy_on_select": true } to keep what you had. A value that
    is neither true nor false leaves its own gesture off with a warning
    rather than failing the whole file, like a malformed colour.
  • The right-click paste is addressed to the pane under the pointer, not the
    focused one, and focuses it; a confirmation prompt refuses it exactly as it
    refuses the paste chord.

Fixed (copy chord)

  • The copy chord reads the terminal's live on-screen selection, and prefers it
    to the cache of the last copied text (#36). The cache was previously the only
    thing it read, and only an automatic copy filled it — so with copy-on-select
    off, a mouse selection could not be copied by any means. Preferring the live
    highlight also fixes the older half of the bug: with a stale cache and a
    fresh selection, the chord used to copy what had been copied before.

Fixed (nested launches)

  • A shell opened by a termherd that was itself launched from a termherd shell
    now starts (#244). The shell integration hands zsh a private ZDOTDIR, so
    everything descending from that shell — including another termherd — inherited
    it and read it as the user's own home. Session ids restart at 1 with each run,
    so the inherited directory was usually the same one the new session was
    about to write: its generated .zshenv sourced itself and the shell died on
    job table full or recursion limit exceeded without reaching a prompt,
    leaving its session on starting. Where the ids differed it started, but
    replayed another session's hooks and never reached the user's own .zshrc.
    A directory termherd generated is no longer replayed from — recognised by its
    name, since a nested instance can run under a different TMPDIR — and the zsh
    recipe now exports TERMHERD_ORIG_ZDOTDIR beside the private one, so a
    ZDOTDIR the user set themselves survives any depth of nesting rather than
    degrading to $HOME.

Fixed (session status)

  • Sessions no longer sit on starting forever, which made wait_for_status
    settle only by expiring and left the close confirmation unarmed for a shell
    (#236). Two independent holes, both reproduced outside termherd on a raw PTY,
    so neither was an artefact of a debug build:
    • A plain shell had no status source at all. The fold only understood
      Claude's dialect (glyph titles, OSC 9), and a shell emits none of it. Every
      session now runs with an OSC 133 shell-integration snippet — zsh via a
      private ZDOTDIR, bash via --rcfile, fish via --init-command, each
      replaying the user's own startup files first — and its prompt/command marks
      fold into the same SessionStatus. Where the injection cannot apply (an
      unknown shell, an unwritable temp directory) the PTY's foreground process
      group stands in, retired for good by the first mark or Claude signal so it
      can never contradict what the terminal says about itself. The bash and fish
      recipes need a command-line argument, which is never added to the platform's
      default program — it would demote the login shell to an ordinary one and
      change which startup files run — so those two are integrated only when the
      shell is named in the settings; zsh needs no argument and always is. ConPTY
      exposes no foreground group, so that fallback is silent on Windows.
    • The Claude channel was switchable off by the user. With
      CLAUDE_CODE_DISABLE_TERMINAL_TITLE set in ~/.claude/settings.json the
      CLI emits no OSC title at all — and that env block outranks the
      environment termherd spawns with, so exporting the variable back had no
      effect. A Claude launch now passes a private --settings overlay, which
      outranks it in turn and merges with (never replaces) the user's settings.
      --settings arrived in Claude Code 1.0.61, which the README now states as
      the CLI floor for launching Claude sessions — an older one rejects the flag
      and the launch fails rather than merely losing its status.
  • The CLI's own product name (✳ Claude Code), which it reports as its title
    until it has something session-specific to say, no longer renames the hosting
    tab — a tab would have traded its project name for the program's.
  • A session's private temp files — its shell-integration directory, its mcp
    config with the live bridge's bearer token, its settings overlay — are now
    deleted when the session is torn down instead of accumulating for good.

Added (tab reorder)

  • Open session tabs can be reordered by drag-and-drop (#25, FR5). Press a tab
    and drag it onto another slot; the carried tab fades and the drop slot is
    outlined, and the reorder commits on release (a plain click still just
    activates the tab). The order lives solely in core::Workspace::move_tab (a
    pure mutator that keeps the active tab pointed at the same tab); the iced tab
    strip holds only transient pointer state, so there is no rival tab tree.

Fixed (scroll proptest)

  • Relaxed the emitted_lines_never_drift_more_than_one_line scroll proptest
    (#98): it reconstructed the cumulative input in f32, so rounding noise over
    a long delta stream could nudge the drift just past one line (~1e-6) and fail
    a sound accumulator. It now sums in f64 with a small epsilon; the found
    counterexample is kept as a regression seed.

Added (search snippet)

  • Search results now show what matched (#58). A content hit (in a session's
    first prompt, indexed transcript text, or slug) renders the matched line in
    muted text under the session row, windowed around the hit and clipped to the
    sidebar; title-only hits stay single-line. The location is computed by a pure
    core::browser::content_snippet returning the line plus the match's byte
    range, unit-tested for content/title/slug hits and long-line truncation.

Fixed (tab status)

  • Tab activity dots (and the focused-terminal badge) stayed frozen on the
    status a session had when it opened (#18). Claude CLI only emits its
    busy/idle/attention OSC status stream when it detects iTerm2 as the host, so
    the PTY adapter now advertises TERM_PROGRAM=iTerm.app (with a version) on
    every spawned session. The headless status plumbing was already correct; it
    simply never received any signals to react to.

Added (terminal links)

  • Terminal links are now clickable (#28). URLs in terminal output
    (http/https/file/ftp) are detected per row; holding Ctrl/Cmd
    underlines the link under the pointer and shows a hand cursor, and
    Ctrl/Cmd+click opens it in the OS default handler. Detection is a pure
    core::links scan (trailing punctuation and unbalanced brackets trimmed);
    the open is a new Effect::OpenUrl performed by the shell.

Install termherd-app 0.1.0-prerelease.5

Install prebuilt binaries via shell script

curl --proto '=https' --tlsv1.2 -LsSf https://github.com/Termherd/termherd/releases/download/v0.1.0-prerelease.5/termherd-app-installer.sh | sh

Install prebuilt binaries via powershell script

powershell -ExecutionPolicy Bypass -c "irm https://github.com/Termherd/termherd/releases/download/v0.1.0-prerelease.5/termherd-app-installer.ps1 | iex"

Download termherd-app 0.1.0-prerelease.5

File Platform Checksum
termherd-app-aarch64-apple-darwin.tar.xz Apple Silicon macOS checksum
termherd-app-x86_64-apple-darwin.tar.xz Intel macOS checksum
termherd-app-x86_64-pc-windows-msvc.zip x64 Windows checksum
termherd-app-aarch64-unknown-linux-gnu.tar.xz ARM64 Linux checksum
termherd-app-x86_64-unknown-linux-gnu.tar.xz x64 Linux checksum