Skip to content

fix: clipboard from a pane on by default, and patch-note style release notes - #598

Merged
devswha merged 3 commits into
mainfrom
fix/osc52-default-and-release-notes
Oct 8, 2026
Merged

devswha merged 3 commits into
mainfrom
fix/osc52-default-and-release-notes

Conversation

@devswha

@devswha devswha commented Oct 8, 2026 •

Copy link
Copy Markdown
Owner

Two changes the owner asked for, one commit each.

1. Clipboard from a pane (OSC 52) is on by default

#570 turned Settings → Terminal → Clipboard from a pane off by default, so copying from vim, tmux and Claude Code silently did nothing. It is on again; the switch stays for anyone running output they do not trust.

Migration: a new storage key. Settings are saved as one whole record, so any 0.4.1 install whose user changed any setting has terminalOsc52: false stored without choosing it. The choice now lives under paneClipboard (default true) and terminalOsc52 is ignored and dropped by sanitizeSettings, which already discards unknown keys. That is the simplest correct option:

  • a 0.4.1 record loads as on (its false cannot be told apart from a real choice, so it is not trusted);
  • an off chosen from now on is written under paneClipboard and stays off across saves and reloads;
  • no version field or one-off migration state is needed. A settings version would do the same with more code.

Known edge: a tab still running the 0.4.1 bundle that saves settings writes its own record, which has no paneClipboard, so a new "off" would read as on again until it is re-chosen. Tabs pick up the new bundle on reload, and settings already have no cross-tab sync (each tab saves its in-memory record), so this is no worse than any other setting.

Tests (src/lib/settings.test.ts, "clipboard from a pane"): fresh default on; a 0.4.1 record with terminalOsc52: false loads as on; an off chosen after the change survives a save, a reload and an unrelated change. A mutation that falls back to the old key fails the second case.

Also: the setting's description and code comments say what is true now, ko/ja/zh entries are updated, and there is a CHANGELOG line under Unreleased → Changed. No doc in docs/, DESIGN.md, INSTALL.md or README.md mentions the default.

2. Patch-note style GitHub release notes

scripts/release-notes.ts printed the whole CHANGELOG section (181 lines for v0.4.1). It now prints the English lists from release-summaries.json (### New features, ### Improvements, ### Bug fixes, a list with no lines left out), then the full section inside <details><summary>Full changelog</summary> with blank lines around it so GitHub renders the markdown. Lines come through readSummary, so they are trimmed and capped exactly as an install shows them. Every validation is unchanged (version format, the three version sources, exactly one heading, nonempty notes, all four languages). scripts/release-notes.test.ts pins the shape, the order and an empty list left out. docs/development.md → Releasing and scripts/AGENTS.md describe the new body.

The published v0.4.1 release is not touched here; the lead regenerates it after the merge.

v0.4.1 rendered with this script (bun scripts/release-notes.ts 0.4.1)

New features

  • Typing /effort in a Claude Code chat opens a card to pick the effort for this session
  • Settings → Appearance can keep waiting and the latest agents on top of the Agents list
  • A finished agent you opened in the web UI can lose its dot, as it would in herdr (opt-in)
  • The guide shows how to give the app a public HTTPS address with Portal

Improvements

  • A wrong access token waits longer after five tries, up to a minute
  • A program in a pane copies to your clipboard only once you allow it in Settings → Terminal
  • Dialogs keep Tab inside them, and screen readers announce tabs, panes and status lines
  • Add PC, Reconnect PC and Update remote bridge open as a bottom sheet on a phone
  • Updating a remote PC that is already current reuses its bridge without a new download
  • Alerts go only to https push services and never follow a redirect
  • Long drafts wrap, and the scrollbar no longer covers text when the page is zoomed

Bug fixes

  • A long line of unclosed brackets or broken escape codes no longer freezes the chat
  • On Windows, more Claude Code and Codex panes show their chat instead of an error
  • Your own devices are recognised by their Tailscale login again, without pairing
  • A Claude Code chat shows what a slash command answered, and /goal is suggested
  • Settings, the palette, Add PC and file previews stack in order; Escape closes the top one
  • The plugin's start keeps the app on its port while herdr is away
  • A secret or typed text no longer reaches a pane you have already left
  • Gajae Code's selection and startup menus show their choices, not only arrow keys
Full changelog

Added

  • /effort sent from a Claude Code chat opens a card with the effort levels, low to max, so the
    level no longer has to be set in the terminal. A pick applies to this session only, as a pick
    from the /model card does, and leaves the default for new sessions alone. /effort is also in
    the chat's command list now.
    (#594,
    #523 by @suho-han)
  • Settings → Terminal has a Clipboard from a pane switch, off by default: a program running in a
    pane can no longer put text on your clipboard unless you turn it on. Programs that copy this way
    (vim, tmux, Claude Code) copy again once it is on.
    (#570 by @radicor)
  • The guide's Behind a reverse proxy shows how to give the app a public HTTPS address with
    Portal v2.6.1 or later, behind a long random token
    and with a visitor's Tailscale-User-Login header dropped.
    (#229 by @rabbitson87)
  • Settings → Appearance → Agents order → Activity keeps a waiting agent on top of the Agents
    list and orders the rest by their latest state change, as herdr's agents panel keeps the latest
    work in view: the agent you just sent a message to stays on top while it runs and after it
    finishes, and a new one starts there. herdr's own order and the workspace rows are unchanged.
    Workspaces (herdr's order) remains the default.
    (#529 by @phirschybar)
  • Settings → Appearance → Quiet opened finishes (off by default): a finished agent you have
    opened in the web UI loses its dot and reads as ready, as viewing it in herdr would make it.
    herdr's DONE otherwise stands until herdr itself shows the pane. It is remembered per PC in
    this browser. (#529 by @phirschybar)

Changed

  • A wrong access token is refused with a growing wait after five tries, up to a minute, whether it
    is typed into the sign-in form or sent with a request. Each visitor behind tailscale serve has
    their own count, and a browser holding an old token never stops you signing in with the new one.
    (#570 by @radicor)
  • The app now sends itself a Content-Security-Policy, so third-party content rendered in a chat —
    math, agent marks — cannot run script in the app.
    (#570 by @radicor)
  • A web-push subscription must be an https endpoint.
    (#570 by @radicor)

Fixed

  • Gajae Code's selection menus show their choices instead of only arrow-key buttons, including
    startup selectors shown before the pane reports that it is waiting. Answers move to the selected
    row and recheck the menu before confirming.
    (#593)
  • Secret input and the Codex follow-up fallback validate the live screen, so a password
    prompt or collapsed question queue in scrollback cannot send input into the current program.
    (#566)
  • The PC's Tailscale login is read from a real Tailscale user id. These ids are too large for a
    JavaScript number, so the owner was not recognised and the owner's own devices had to pair. Two
    logins with neighbouring ids are also no longer taken for one.
    (#572)
  • On Windows, a Claude Code pane started with a second account's ~/.claude-* directory as its
    CLAUDE_CONFIG_DIR shows its chat. Before, the pane fell back to ~/.claude, so the chat said
    Conversation unavailable and only the terminal worked. Windows does not let the server read
    another process's environment, so the store is the one among the server's own
    CLAUDE_CONFIG_DIR, ~/.claude and the ~/.claude-* directories beside it that holds the
    Claude process's own record, checked against the time the process started. A directory
    elsewhere is not looked in. Claude Code processes reported as claude.exe are
    recognized too.
    (#563 by @David-Sousa-Web)
  • A long line of brackets, \( or underscores that never close, as an agent prints in a log or a
    minified file, no longer freezes the chat: a megabyte of them took a minute or more to read, and
    now takes milliseconds. What every message shows is unchanged.
    (#574)
  • On a mirrored pane (Windows, where herdr cannot attach a terminal), text typed while a message
    was still being sent no longer reaches the pane once you have left it, or left it and opened it
    again, before the text's turn came.
    (#576)
  • The plugin's start keeps the app on its port when the app's own server holds it but cannot
    reach herdr. It used to move the app to another port beside the running one and blame another
    program; now it says that the app runs there without herdr and exits, and the app answers
    again on its port once herdr is back.
    (#577)
  • On Windows, a Codex pane shows its chat when Codex stored its paths with the \\?\ prefix, as
    it does for a canonical Windows path (\\?\D:\work for D:\work). Before, the chat said
    Conversation unavailable: the session's file seemed to lie outside Codex's store, and none of
    the threads matched the pane's directory as herdr reports it.
    (#582 by @David-Sousa-Web)
  • In a Claude Code pane, the chat shows what a slash command answered, so a /goal that Claude
    Code refuses says why in the chat instead of only in the terminal. /goal is also among the
    commands the message box suggests.
    (#583)
  • Settings opened over a file preview is visible above it; Escape and Back close Settings
    first, preserving the preview and its history entry until the file itself is closed.
    (#568)
  • Command palette buttons keep their native Enter action; IME commit and cancel keys
    stay with text input, and arrow navigation keeps the selected result visible.
    (#567)
  • Long drafts in the message box wrap and keep a narrow scroll cue in reserved space, so the
    scrollbar no longer covers text at fractional zoom.
    (#522 by @suho-han)
  • A secret sent from a pane you then left and opened again, while another browser kept the pane
    open, is no longer typed into the pane: you send it again from the pane you opened.
    (#588)
  • A Claude Code slash command whose answer holds a long run of broken terminal escape codes no
    longer stalls the server while the chat reads it. Its answer also drops a link cut off before
    its end and a stray B after a charset switch, and an answer with both output and errors
    shows both.
    (#588)
  • The command palette opened over Settings and a file preview shows above both, instead of
    taking the keyboard unseen beneath them.
    (#588)
  • The plugin's start no longer takes another program on the app's port for the app because
    its answer says ok: it treats it as any other program there, moving the app to a free port,
    or saying the port is taken when PORT is set.
    (#588)
  • A 500 from the server no longer repeats the system's own error text, which carried absolute paths
    and the herdr socket location; it names a short id you can quote in a bug report instead.
    (#570 by @radicor)
  • Settings, the command palette, the file viewer, the file browser and the new-workspace dialog keep
    Tab inside them and give the focus back to whatever opened them.
    (#570 by @radicor)
  • The tabs of a workspace name the pane region they govern, so a screen reader announces the tab and
    the pane together.
    (#570 by @radicor)
  • Agent headings in a chat no longer pose as the app's own page structure; they sit below the app's
    own headings and look the same as before.
    (#570 by @radicor)
  • The "reconnecting" line and the composer's terminal-only hint are announced when they appear.
    (#570 by @radicor)
  • A chat locked out by the token gate, the "Last checked" line under Settings → About (with its
    date in your language) and a remote PC's state word in the sidebar are translated like the rest of
    the UI.
    (#570 by @radicor)
  • The alerts menu item now says the same thing the same way in every state.
    (#570 by @radicor)
  • Held terminal input typed while disconnected is forgotten after a day.
    (#570 by @radicor)
  • A link printed in the terminal opens only if it is an http(s) address, on both link paths.
    (#570 by @radicor)
  • A second tab open on one pane no longer sends a message the first tab is already sending: it
    sees that send on its way and holds back. Two tabs that press Send at nearly the same moment can still
    both send it.
    (#570 by @radicor)
  • A row's ⋯ menu is capped to the room its button leaves and scrolls instead of being cut off by the
    viewport, so the pane picker of a tab with many panes keeps every entry reachable with the pointer
    as well as the keyboard. Before, items below the fold were rendered but unreachable.
    (#570 by @radicor)
  • A row menu open while the window crosses the 640 px breakpoint now switches between bottom sheet and
    popover instead of keeping the form it opened with.
    (#570 by @radicor)
  • The workspace drawer a narrow window opened is closed again when the window is widened past 768 px,
    so narrowing it no longer brings back a drawer and its scrim unasked.
    (#570 by @radicor)
  • Add PC, Reconnect PC and Update remote bridge open as a bottom sheet on a phone, like
    every other dialog, and keep clear of the on-screen keyboard. Before, the one native dialog stayed
    a centred card on a phone.
    (#570 by @radicor)
  • Remove PC's first click is a quiet ghost button that only arms the removal; the second is the
    red one, as revoking a device already was.
    (#570 by @radicor)
  • A PC's rename, connect and disconnect buttons disable while their request is in flight, so a double
    click no longer sends two overlapping requests.
    (#570 by @radicor)
  • A failed pane or workspace rename keeps the field open with what you typed, so a network blip no
    longer makes you write the name again.
    (#570 by @radicor)
  • A workspace reorder that fails no longer undoes a later, successful reorder.
    (#570 by @radicor)
  • A tab watching several busy panes gives up its oldest cached conversation answers when they grow
    past a byte budget, not only past sixteen of them.
    (#570 by @radicor)
  • The usage meters' note is the same size as every other advisory and empty state.
    (#570 by @radicor)
  • The PDF viewer's page colour, the pill radii, the tab dot and the pairing-code size come from
    design tokens now, and the pairing code follows the compact density setting.
    (#570 by @radicor)
  • Updating an already current remote bridge verifies and reuses it without downloading or
    restarting it again, while a bridge from a newer app is left running instead of downgraded.
    A PC that waits on such a conflict says so in the sidebar and under the header, with a
    Reconnect button, instead of asking for setup approval.
    (#519 by @suho-han)
  • A wrong access token sent through a proxy on this PC with a made-up X-Forwarded-For address
    that itself says " via " now counts against the limit every visitor through that proxy shares,
    like any other wrong token. Before, each such try started a fresh count.
    (#592)
  • A web-push subscription must be an https address, with no exception for this PC, and an alert is
    never sent on where a push service redirects it, so an alert can never be posted to a service
    running on this PC. (#592)
  • In the Add PC dialog opened over a file preview, Tab moves through the dialog's own controls
    and Escape closes the dialog alone, leaving the preview beneath it open.
    (#592)

Checks

  • bun run check fast: ok (1817 pass, 6 platform skips, 0 fail).
  • No browser regression covers OSC 52 (terminal-copy-regression.ts covers drag and Ctrl+C copy), so none was run for it.

Codex review (gpt-6-astra, read-only)

  • Fixed (P2): summary text was put into the markdown as-is, so "…\n\n<!--" could hide the whole changelog fold. The gate now refuses a summary line with a line break in any language, and the English lines escape <. A test covers both and fails without the fix.
  • Declined, documented (P2): a tab still on the 0.4.1 bundle that saves settings drops paneClipboard, so a new "off" reads as on until it is chosen again. A versioned storage key would stop that, but then every setting changed in such a tab would be lost instead. It lasts only until that tab reloads, and every setting added before this one had the same gap.
  • Pre-existing, not in this diff (P2): settings have no cross-tab sync, so a stale tab's next save writes its own copy of every setting, this one included. Reported to the lead as a follow-up.

…cords

vim, tmux and Claude Code copy through OSC 52; with the switch off since
#570, copying from them silently did nothing. The switch stays in
Settings -> Terminal.

Settings are saved as one whole record, so 0.4.1 wrote
terminalOsc52: false with any change at all. The choice now lives under
a new key, paneClipboard, and the old key is ignored: a 0.4.1 record
loads as on, and an off chosen from now on is stored under the new key
and stays off.
The release body was the whole CHANGELOG section: 181 lines for v0.4.1.
It now opens with the English lists from release-summaries.json under
New features, Improvements and Bug fixes (an empty list left out), as an
install shows them, and folds the full section under <details>. Every
existing validation stays.
@coderabbitai

coderabbitai Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration
  • Configuration used: Repository: devswha/herdr-web-ui/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: d4fe6243-6fc0-4a58-b4b8-641f1f4b3161
📥 Commits

Reviewing files that changed from the base of the PR and between b223a3e and 1439d97.

📒 Files selected for processing (12)
  • CHANGELOG.md
  • docs/development.md
  • scripts/AGENTS.md
  • scripts/release-notes.test.ts
  • scripts/release-notes.ts
  • src/components/PaneTerminal.tsx
  • src/components/SettingsDialog.tsx
  • src/lib/i18n.ja.ts
  • src/lib/i18n.ko.ts
  • src/lib/i18n.zh.ts
  • src/lib/settings.test.ts
  • src/lib/settings.ts
 __________________________________________
< Performing energy-intensive code review. >
 ------------------------------------------
  \
   \   (\__/)
       (•ㅅ•)
       /   づ
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

…notes

A line break or a raw < in release-summaries.json could turn a line into markup or hide the Full changelog fold behind an HTML comment (Codex review). The gate now refuses a line with a break in any language, and the English lines escape <.

@devswha devswha left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Independent review of 1439d97 (lead session; lane fix-notes wrote d0d0b75, 99047e6 and 1439d97).

Verdict: merge. The owner asked for both changes.

What I ran, in a scratch copy of 1439d97:

Check Result
src/lib/settings.test.ts, scripts/release-notes.test.ts, server/release-notes.test.ts 69 pass, 0 fail
settings.test.ts with main's src/lib/settings.ts the 3 new "clipboard from a pane" cases fail (fresh default on; a 0.4.1 terminalOsc52: false record loads as on; a new off survives save/reload)

Read:

  • The new key paneClipboard is the simplest correct migration: every 0.4.1 save wrote terminalOsc52: false whether or not it was chosen, so it cannot be trusted. The remaining edge (a tab still on the 0.4.1 bundle saving settings drops a new off until it is chosen again) fails toward copying working, which is the side the owner wants, and ends when that tab reloads.
  • The release notes are the English patch-note lists, then the full CHANGELOG section in <details> with the blank lines GitHub needs. Every earlier validation stays; summary lines with a line break are refused and < is escaped, so no line can open a tag or hide the fold (Codex astra's verified finding, fixed in 1439d97).
  • The adversarial pass ran on gpt-6-astra (high). Its cross-tab note (no storage-event sync in SettingsProvider) predates this PR.

After merging I will regenerate the published v0.4.1 notes with this script.

@devswha
devswha merged commit 92c9b0c into main Oct 8, 2026
3 of 4 checks passed
@devswha
devswha deleted the fix/osc52-default-and-release-notes branch October 8, 2026 09:14
devswha added a commit that referenced this pull request Oct 8, 2026
## What

Anonymous install and update counts, announced and on by default
(opt-out):

- **When:** one event when an install first runs, one each time it runs
a new version, nothing in between. No event is sent before the app has
shown its one-time notice (a line under the header with **What is
sent**, **Turn off** and **Dismiss**), and the first one waits ten
minutes after it, so **Turn off** on the notice stops it. The notice is
recorded only once it was painted in a visible tab, and stays up until
the server confirms the switch is off.
- **What:** `event` (`install`/`update`), a random `install_id` made on
the PC, `version`, `previous_version`, `os`, `arch`, `install_method`
(`plugin`/`managed`/`source`). Nothing about terminals, agents, files or
accounts.
- **Off switch:** Settings → About → **Anonymous usage counts** shows
the next event exactly as it would be sent and turns it off.
`HERDR_WEB_TELEMETRY=0`, `DO_NOT_TRACK=1`, `CI` and the test
environments block it whatever the switch says.
- **Where it does nothing:** only the real entrypoint
(`server/index.ts`) passes a `Telemetry`, so `createServer` in tests,
remote-PC bridges and the demo answer `/api/telemetry` with 404 and the
app shows neither the notice nor the switch.
- **Receiver:** `telemetry/` is a Cloudflare Worker over one D1 table.
It never reads the sender's address or any header but the content type,
stores the day rather than the time, keeps each (install, event,
version) once, and has Workers Logs off. Deploy steps and the queries to
read the counts are in `telemetry/README.md`.

A failed send is logged once and tried again at the next start; it never
loops. Turning the switch off aborts a send in flight, the state file is
re-read before each decision (two servers on one state directory agree),
and a state directory that cannot be written leaves the app running with
telemetry silent.

## Contract

- `GET /api/telemetry` → `TelemetryStatus`; `POST /api/telemetry` `{
enabled?, notice_seen?: true }` with `x-herdr-update: 1` and same-origin
(`shared/telemetry.ts`, catalogued in `shared/protocol.ts`).
- `site/demo/transport.ts` answers 404 like a server without telemetry.
- `telemetry/` is added to `tsconfig.json` and to the unit-test glob in
`scripts/ci-tests.ts`.

## Before merging / releasing

- The lead deploys the Worker right after the merge (the D1 database
exists and its id is in `telemetry/wrangler.toml`; the account's
workers.dev subdomain is `devswha`, so `TELEMETRY_URL` in
`server/telemetry.ts` is right). Until then every send fails quietly:
one log line per start, retried at the next start.

## Verification

- `bun run test:unit`: 1790 pass, 0 fail (before rebasing onto v0.4.1);
after the rebase `bun run typecheck` and the telemetry, Worker and i18n
tests pass.
- `bun run build` passes.
- New tests: `server/telemetry.test.ts` (no send before the notice,
install once, update from the last reported version, switch, environment
blocks, retry after a refused send, route validation),
`telemetry/worker.test.ts` (stored fields, no address, refusals), and a
`telemetry API` block in `server/api.contract.test.ts` (404 without
telemetry, notice and switch through HTTP, token gate). The contract
block was not run locally; CI runs it.
- Browser check against a real server and a stand-in receiver
(Chromium): the notice shows on first open and the install event arrives
once; **What is sent** opens Settings → About; the switch turns sending
off on the server; after a reload the notice is not shown again; the
notice wraps cleanly at 390px in Korean.

## Review pass (merge lane)

- Merged `main` (v0.4.1, #598, #524); the changelog entry sits under `##
[Unreleased]`.
- Codex `gpt-6-astra` adversarial pass: six findings verified and fixed
in c74606b and 2f8a0bc, each with a test that fails without the fix
(`server/telemetry.test.ts`, `telemetry/worker.test.ts`); the rest are
listed in a comment below.
- `bun run check fast`: 1837 pass, 0 fail.
`server/api.contract.test.ts`: 96 pass. `scripts/update-browser-qa.ts`:
4 PASS.
- Browser check (Chromium, a real server and a stand-in receiver, 4 s
grace): the notice shows, nothing is sent until the grace passed and
then one install event arrives; a failed **Turn off** keeps the notice
with "Could not turn it off. Try again.", the retry closes it, the
switch is off on the server and nothing is sent after the grace; the
failure line fits at 390px.
@devswha devswha mentioned this pull request Oct 9, 2026
devswha added a commit that referenced this pull request Oct 9, 2026
Release v0.4.2 from `main` at cc9701c. Stop before merge: the maintainer
merges and runs the release.

## What's in it
- Version 0.4.2 in `package.json` and `herdr-plugin.toml`.
- CHANGELOG: `## [Unreleased]` cut into `## [0.4.2] - 2026-10-09`,
compare links updated, the two #598 entries given their PR link. Outside
contributors are credited as GitHub lists them as authors.
- `release-summaries.json`: 0.4.2 patch notes in en/ko/ja/zh (new 5,
improved 5, fixed 4).
- `REMOTE_BUNDLE_VERSION` raised to `"22"`: the remote-PC bundle ships
`server/`, `shared/` and `dist/` (`scripts/build-remote-bundle.ts`), and
all three changed since remote-v21 (#543, #438, #599, #600, #601, #605
and others). A `remote-v22` tag is needed on the merge commit before the
release.

## PRs since v0.4.1
#598, #524, #599, #603, #600, #601, #605, #438, #610, #607, #543

## Whole-release review
Codex astra (gpt-6-astra) over `v0.4.1..cc9701c`, looking for cross-PR
problems: **no release blocker**. One Medium, verified by reading the
code and left as a follow-up: in an OpenCode chat, "Show the whole
output" fetched while a tool is still running can keep the older text if
the tool later rewrites its output, because the output reference carries
no revision. It affects only what is displayed until the row remounts;
the fix needs a new protocol field, so it is out of scope for a release
PR.

## Checks
- `bun run check fast`: green locally (1928 pass, 6 skip, 0 fail;
typecheck, build, generated types).
- `server/release-notes.test.ts` and `scripts/release-notes.test.ts`: 27
pass; `bun scripts/release-notes.ts 0.4.2` renders the notes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant