Releases: openwong2kim/wmux
Release list
v3.45.0
Added
- The unread-glow dim is now adjustable. A pane holding an unvisited
notification used to drop to 60% opacity — "the inactive terminal is
shadowed" — which made a session you were monitoring harder to read at the
exact moment it produced output. Settings → Notifications now has an
"Unread pane brightness" slider: 100% removes the shadowing entirely (the
colored border glow still marks the unread pane), 60% keeps the classic
look, and the default is unchanged. (#960)
Fixed
-
The roster no longer reads a working pane as waiting. Claude Code's
bypass permissions onfooter is on screen for the whole turn in
bypass-permissions mode, and the detector read every repaint of it as
"ready for input" — which put a falsewaitingon the pane's roster row
and inflated the "N need you" chip while the agent was still working. Once a
pane's hook bridge has claimed that pane's lifecycle, the Stop signal owns the
lifecycle status outright, on both the local and the packaged daemon path.
Approval prompts (awaiting_input) are unaffected, and a pane whose hook has
not claimed the lifecycle — no bridge at all, or one that has only announced
itself — keeps the detector as its backstop.Note for anyone running a mixed pair: on the packaged daemon path the fix
rides the daemon's own arbitration stamp, so an older daemon with a newer
app does not get it until both are updated. (#935, #939) -
Claude Code panes are detected again. The Claude gate needed its footer
spelled with literal spaces on a line of its own. The shipping product
(measured on v2.1.235) provides neither: spaces are cursor advances
(ESC[1C), so the footer strips tobypasspermissionson, and the whole
bottom region — input box, statusline, footer — arrives as one newline-free
run whose rows are placed with cursor-position escapes. The gate therefore
never opened on a real Claude pane, which quietly disabled every Claude
detector pattern,waitingand the approvalawaiting_inputregexes alike.
Whitespace is now optional in the footer fragments (the same treatment the
banner pattern already carried) and candidate lines are cut on row-position
escapes as well as newlines. (#935) -
A freshly launched agent no longer reads as busy. Hook authority answers
"a bridge speaks for this pane", which is not the same as "the hook has
claimed this pane's lifecycle". A bridge that had sent onlySessionStart
suppressed the detector's true "ready for input" read, leaving the pane on
the gate's one-shotrunninguntil its first turn ended. The status veto now
waits for the hook to actually take the lifecycle over. (#935) -
Korean IME no longer paints the composing syllable over the one just
committed. Since v3.42.0, typing Korean showed syllables disappearing as
you typed (대한민국read대한국until the composition ended) — the
candidate-window pin introduced for Chinese/Japanese IMEs dragged the whole
helper container, inline preedit included, back onto committed cells. The
pin now holds only the hidden textarea that anchors the candidate window,
while the visible inline preedit stays on the live cursor, so Korean input
reads correctly keystroke by keystroke and the Chinese/Japanese
candidate-window behavior is unchanged. The IME diagnostic log (now
ime-anchor2→ime-anchor3) also records mid-composition and
composition-end corrections when they change, so a future report of this
family is self-diagnosing. (#945) -
The perf gate's frame-budget metrics are judged in frames, so a two-frame
measurement no longer passes by a rounding accident.
frameBudget.*.frameDeltaMs.p95only ever lands on whole frame intervals,
and the old rule — "more than 2x the blessed baseline" — put its threshold
at exactly the top of the two-frame cluster. Every two-frame run passed
because the comparison was strictly-greater, not because it was judged. It
also meant a baseline blessed from a slow run quietly doubled the allowance.
The rule is now "at most one frame interval above the baseline", which
reaches the same verdict on every one of the 648 samples in the recorded
trend while resting on the measurement instead of on where a threshold
happened to fall. (#947) -
A red perf gate no longer describes its confirmation re-run as an
independent measurement. The re-run happens on the same runner, so it can
tell a passing spike from a repeatable one but cannot clear a machine that
was slow for its whole life — which is what happened before v3.44.0 was
tagged, where the same commit measured clean on a fresh runner twenty
minutes later. The summary and the failure line now say "same runner" and
say what that does and does not prove. (#947) -
Codex panes no longer show a second blinking cursor on the status
line. Codex ends some of its synchronized-output frames with the
hardware cursor visibly parked after the model/cwd status text or next to
the "Working" spinner, and the terminal faithfully painted it there for a
frame or two — quasi-periodic flashes that read as a second caret blinking
alongside the composer's. The cursor is now rendered only at rest: frame
traffic hides it, and it reappears 32 ms after the last frame — at the
composer, where the resting position actually is. Classic TUIs (vim, less,
htop) never bracket frames, so their cursor behavior is unchanged. (#954) -
Restored history no longer draws garbled after a restart. When a pane
came back from a dead-process recovery (daemon restart or the local
scrollback cache), the restored screen stayed in the viewport while the
freshly spawned shell's coordinates started from row 1 — so typed input and
PSReadLine repaints landed on top of the restored text, overlapping it until
aclear. The restored screen now scrolls fully into scrollback before the
new prompt paints: the prompt starts on a clean viewport, and the history —
commands included — sits intact one wheel-notch above. (#955) -
A pane's attention ring no longer draws on top of menus and modals. The
green completed-blink ring, the notification pulse, and the pane flash sat
at overlay-level z-index without a containing stacking context, so a pane
that lit up could paint its ring straight across the new-workspace picker
(and the flash briefly over any modal). The decorations now sit above
everything inside their pane and below all app chrome. (#956) -
An approved plugin can no longer read another workspace's private
activity by naming it. The iframe plugin host dispatches through the same
trusted in-process channel as the app's own UI, and two read paths —
events.poll(privatea2a.task/ channel events) and the a2a channel /
mission reads — treated that shared channel as "the operator", so a plugin
granted an ordinary read capability could aim those reads at any workspace
by passing its id (or omitting one). Both paths now bind such a plugin to
the workspace it is actually hosted in, matching the scope its approval
implied; an unbound plugin read fails closed. This confines an approved
plugin — it is not a defense against hostile same-user code, and external
(non-plugin) callers are unchanged. (#959) -
A perf-gate red that reproduces on its own runner is now confirmed on a
fresh machine before it blocks anything. The in-job re-run can absorb a
transient spike but cannot tell a real regression from a runner degraded for
its whole lifetime — three activations onmainshowed reds that a
different machine could not reproduce, and only a human clicking "re-run all
jobs" resolved them. That click is now a dependent CI job: reproduced reds
escalate to a fresh runner whose verdict is the gate's — green with the
reasoning on record when the failure cannot be measured elsewhere, red on
the strongest evidence the pipeline can produce when it can. Correctness
failures and harness errors still fail closed on the spot. (#961)
Security
- A UI plugin can no longer point most browser calls at a workspace you are
not in. An approved iframe plugin could already only watch the active
workspace — its event feed has been scoped since #719 — but its own
browser.*calls were forwarded with whatever workspace id the plugin chose.
The workspace the plugin host is showing now travels with each request on its
own channel, out of the plugin's reach: a browser call that names a different
workspace is refused, one that names none resolves to the workspace hosting
the plugin instead of being rejected as unresolved, and a plugin whose host
has no active workspace is refused rather than falling back to naming its own.
This covers the browser methods that resolve a target — navigation,
evaluation, extraction, input, capture and the rest. Opening and closing
browser surfaces (browser.open,browser.close) resolve their workspace on
a different path and are not covered yet; that is the next step on #922.
Either way this confines an approved plugin to the scope its approval implied
— it is not a defence against hostile code already running as your user, and
callers on the local RPC wire are unchanged. (#941)
What's Changed
- Stop the roster from reading a working pane as waiting by @openwong2kim in #939
- fix(browser): scope a plugin's browser calls to the workspace hosting it by @snowyukitty in #941
- Pin only the textarea during IME composition, never the inline preedit by @openwong2kim in #945
- fix(perf): gate the frame-budget family on frames, and stop calling the confirmation re-run independent by @snowyukitty in #947
- fix(terminal): render the cursor only at rest for synchronized-output TUIs by @openwong2kim in https://g...
v3.44.0
Added
-
An opt-in
+for a second terminal in the same pane. Settings → Terminal
→ "New-terminal button on the tab strip", off by default and labeled
experimental: wmux is built around one pane = one terminal, and splitting is
the usual way to get another. Turning it on is choosing to work differently,
which is why it is a setting rather than new chrome for everyone (#909). -
Grok is a first-class agent. The detector recognizes the Grok TUI (startup banner, Help improve Grok, live composer
Grok 4.6 (high) · always-approve) and the roster / pane names show Grok instead of a generic terminal. (#916) -
Settings has a search field. Type a label, a description, or a synonym (
커서,언어,mcp) and jump to the control instead of walking the nine tabs. Esc clears the query first, then closes Settings. (#921, #925) -
Terminal cursor shape is a setting. Settings → Appearance offers Block, Underline, or Bar. Block stays the default. The change applies live and survives restart. (#920, #925)
-
Workspaces can carry an optional color tag (right-click a workspace →
Color tag). The color shows as a rail on the sidebar row and on the 48px
mini-rail, so a long list of same-shaped names stays scannable. Purely
visual — the agent status dot, git lights and badges keep their meanings. -
The wmux web browser terminal now supports select-to-copy, Ctrl+C-with-selection
copy, Ctrl+V paste, and Shift+Enter / Ctrl+Enter newline, matching the local
terminal and the attach mirror (#924, #931). -
Copy/paste/newline handling now works in the split view as well as the 1-up
view — both share the same key-decision wiring, so Ctrl+C with a selection
copies in a tile instead of SIGINT'ing that pane's process (#931).
Changed
-
The sidebar company panel, Missions section, workspace account submenu,
workspace search, editor/file-tree panels, and the error boundary now
respect the selected locale instead of showing hardcoded English (#912). -
Toast, notification panel, onboarding overlay, status bar, usage meter,
deck fleet, diff panel, channel-create modal, the multiview close button,
the orchestrator model chip, and the approval dialogs now respect the
selected locale instead of showing hardcoded English (#913). -
Relative timestamps ("5m ago") are localised along with the rest of the
interface (#913). -
Settings tabs are grouped and renamed. The same nine tabs, now under App / Agents / System. Claude Integration is labeled Accounts; LAN Link is labeled Network. Existing controls are unchanged — language is still the full locale list. (#925)
-
The workspace agent roster now leads with each session's terminal title
instead of the vendor name, so a workspace running several Claude Code
sessions no longer renders as a column of identical "Claude Code · w2-1xx"
rows. The vendor moves to the muted trailer next to the pane coordinate, and
a session whose terminal has no title still leads with the vendor name as
before. -
The agent verbs are one toolbar again, and it appears when you reach for
it. Attach, files, snippets, rich input (⌘G), Broadcast, Multi Task, and
new conversation are back on a single bar instead of split across the pane
tab cluster and the workspace card. The bar overlays the terminals rather
than taking a row from them, so nothing shrinks and no terminal is resized
when it appears — it slides up when the pointer nears the bottom edge and
retreats when you go back to typing. It will not appear while you are
dragging out a selection, and it stays put once your cursor is on it. Pin it
from the lock button (or ⌘K) to keep it open all the time. (#937) -
The collapsed deck no longer costs a column. The vertical strip of icons
on the right edge is gone; one«/»button beside Settings opens and
closes the deck, and the terminals now run to the window edge while it is
closed. A dot appears on that button when the closed deck holds unread
channels or dirty worktrees. (#937)
Fixed
-
The Shortcuts list said
Ctrl+Twas "New workspace". It is not —Ctrl+T
adds a terminal to the pane you are in, and the key that makes a workspace is
Ctrl+N, which the list did not mention at all. Both rows now say what the
keys do (#909). -
Mouse clicks and wheel now reach vim and other TUIs on Windows 10. The in-box ConPTY on Windows 10 (and Server 2022) never forwards the escape sequences that turn on mouse reporting, so
set mouse=ain vim silently did nothing — the wheel scrolled wmux's own scrollback instead. Those builds now spawn their panes against the OpenConsole build bundled with wmux, which forwards mouse events correctly. Windows 11 is untouched. Every spawn logs which ConPTY backend actually started. If the bundled one cannot start, wmux retries once and then falls back to the previous behaviour — the shell still opens, and the log says the pane has no mouse reporting. -
A translated string whose interpolated value contained
$&,$$,$`or
$'came out corrupted — the placeholder reappeared verbatim instead of
being replaced. Crash messages carry a rawError.messageand channel names
carry agent-authored titles, so any of those could reach it (#913). -
An approval prompt or crash screen already on display now follows a locale
change instead of staying in the previous language (#913). -
Grok panes no longer show up as Claude Code. A Grok TUI that reads this repo (or any file quoting Claude's banner/footer) used to trip Claude's compound gate. Identity now requires per-line TUI chrome, and another agent's status patterns cannot steal the pane back. (#916)
-
Grok's transcript scrolls with the wheel. Grok runs in the alt screen, so xterm had no scrollback. The wheel now sends PageUp/PageDown, which is how Grok itself scrolls. (#916)
-
A mirrored remote pane now copies, pastes, and inserts newlines like any
other terminal. Attaching to a remote wmux gave you a pane that forwarded
every keystroke straight through, so the editing habits that never leave your
own machine stopped working: selecting text copied nothing,Ctrl+Cover a
selection interrupted the remote process instead of copying it,Ctrl+Vdid
nothing at all, andShift+Entersubmitted where a local pane would have
added a line. All four now behave the way they do in a local pane — including
⌘C/⌘Von macOS — while an empty-selectionCtrl+Cstill interrupts the
remote process, and a host started without--allow-inputstill receives
nothing.Shift+Entersends the newline form the remote app actually asked
for: apps that enable the kitty keyboard protocol get it, and anything else
keeps the plain carriage return it has always received. (#924) -
A plugin no longer goes silent when you switch workspace. The host hands
each plugin iframe a message port once, when the frame loads. Re-subscribing
the plugin event feed on a workspace switch also tore that port down, and
there is no second load to rebuild it — so from the first switch onward the
plugin's requests were dropped and its palette commands stopped arriving. It
affected every mounted plugin, not only the ones receiving events; sidebar
panels came back if you collapsed and re-opened them, while status-bar
widgets stayed broken for the rest of the session. The port now outlives the
switch, and the event feed still follows the workspace you are in. (#928) -
Web terminal paste actually works now: xterm's keydown handler used to encode
Ctrl+V as the SYN control byte and preventDefault, so the browser's native
paste event never fired. The key now steps aside so the browser's own paste
path delivers the text (#931). -
A select followed by Ctrl+C then an immediate Ctrl+V no longer freezes the
page for tens of seconds. xterm firesonSelectionChangeonce per cell during
a drag, so select-to-copy was issuing a burst of concurrent clipboard writes;
the write is now debounced to the settled selection like the desktop's
autoSelectionCopy.ts. An explicit Ctrl+C also cancels any pending auto-copy
so the two never race (#931). -
Ctrl+D now behaves like a real terminal again: it sends EOF (exits a shell,
endscat > file), matching the desktop and every other terminal, instead of
being swallowed by the browser page (#931). -
Shift+Enter no longer corrupts apps that never negotiated the kitty keyboard
protocol. The CSI-u byte (\x1b[13;2u) only means "newline" to an app that
asked for kitty encodings; the browser now watches each pane's output for the
negotiation and falls back to a plain Enter for panes that did not — so vim
no longer exits insert mode and runsuas undo (#931, same gate as the
attach mirror in #924). -
Shift+Enter / Ctrl+Enter / Ctrl+J no longer cause a
bash: syntax errorin a
shell that does not speak the kitty keyboard protocol. The newline decision
nowpreventDefaults the browser's own newline, which xterm'sinputhandler
would otherwise forward to the PTY right after the newline byte (#931). -
Copying no longer steals keyboard focus: the legacy clipboard fallback restores
the previously focused element, and a click on any page control (buttons wrap
their label/icon in a SPAN or SVG) no longer yanks focus into the terminal
(#931). -
A pane's agent label now prefers process truth over screen text. The
label, roster entry, and status events for a pane are decided by a
precedence of signals — a corroborated or fresh hook self-report first, then
the live attributed agent process, and only then what the terminal happened
to print. Screen text still names the pane when nothing stronger is
available, which is what remote and SSH panes rely on. A confirmed-dead
same-slug read on screen is treated as sticky residue instead of relabeling
the dead agent "alive". This fixes the class of mislabels where a Grok pane
was shown ...
v3.43.0
Added
-
A2A thread round cap: replies past 5 completed round trips — or past a per-side
message ceiling that catches one-sided monologues — are refused with a
cap_reachederror so two agents cannot ping-pong unattended forever. -
a2a_discoverresponses includeelapsedMs(measured at the MCP tool entry)
to separate server latency from client-side stalls. -
A glyph rail where the collapsed deck was. Collapsing the deck leaves a
36px column of the same icons on its edge instead of nothing at all; pressing
one reopens the deck straight onto that tab, and the rail marks the tab it
will return to. -
The orchestrator can fan work out into isolated worktrees. Its only
worker used to be a pane split into the same checkout, so two workers editing
at once overwrote each other and shared one branch — and with no shell, it
could not create a worktree any other way.fanout_startis now on the
commander surface: one call, N tasks, each on its ownwtask/branch with its
own workspace, agent pane and mission channel. The user is still asked to
approve before anything spawns, and never auto-approved. (#891) -
Fan-out tasks can run on different agents and models. Each task takes an
optional role — Builder, Reviewer, Tester, Planner — and launches on whatever
agent and model you bound that role to in Settings. Review work can run on
Codex, or a cheaper model, while the build tasks in the same fan-out run on
Claude. The approval prompt now spells out what each role resolves to, so the
text you approve is the command that runs. Available from the Multi Task
dialog as well. (#891) -
wmux now tells you when the plugin is the thing that is broken. If a
Claude Code plugin install is still running the hook that forces a prompt,
a notice appears with the exact update command, and it stays up until you
dismiss it. Version numbers could not answer this question — the plugin's
version did not change across the release that introduced the permission
gate, soclaude plugin updatereported "already at the latest version" for
broken and healthy installs alike — so wmux runs the installed hook on a path
that touches nothing and watches what it answers. wmux reads the plugin
directory and never writes to it: repairing another tool's cache would leave
content that does not match the version on the directory, and could downgrade
a plugin newer than the app. (#898) -
An orchestrator can wait for a pane instead of watching it.
wmux_events_poll
takesblockMsto wait for an event rather than returning an empty page, plus
ptyIdandkindsto narrow it to one pane and one kind of signal. Until now
the only way to notice that another agent had stopped on a question was to read
its terminal on a timer and guess from the text — and a question an agent
printed looks exactly like one it is waiting on, which is where most false
"this agent is stuck" reports came from. A wait now ends the moment that pane
actually blocks, and a nested subagent returning no longer counts as the pane
becoming free. Existing callers are unaffected: every new parameter defaults to
the old behavior. (#903)
Changed
-
The deck's tabs are icons. The deck's header used to read
Agent (Default) | Git | Channels— three text labels across the top of a
column barely 250px wide. They are 36px glyphs now; the tab's name, and the
orchestrator's current model, are in the tooltip. -
Agent, Git, Channels and web left the sidebar. The deck sits on the
opposite edge from the workspace list, so as labeled rows at that list's foot
they cost 144px pointing the wrong way — and vanished whenever the sidebar was
collapsed. All four are glyphs on the deck's own strip now (the first three
select a tab, web opens its popover as before), and the workspace list got the
space back. -
The sidebar + menu and Settings (Accounts, MCP servers, Theme) now
respect the selected locale instead of showing hardcoded English (#911).
Fixed
-
Prefix-action labels for Rename/Kill workspace and Show cheat sheet now
translate. Threesettings.prefix.*keys were missing from bothen.ts
andzh.ts, so the Settings → Shortcuts → Prefix bindings rows rendered raw
key strings; they now show proper English and Simplified Chinese. (#886) -
A2A replies no longer fail silently:
a2a_task_sendnow returns adelivery
field (stored/notified/reason/hint) that says whether the other
party was actually nudged and, if not, why and what to do about it. A reply
whose nudge is withheld also emits the task pointer event, so a polling
receiver still learns the thread moved. -
The IME candidate window now anchors to where you are typing, not where the
agent's redraw parked the cursor. The v3.42.0 fix pinned the candidate list
in place for a whole composition, but it pinned it to wherever the terminal
cursor happened to sit at the instant the composition started — and while a
TUI like Claude Code repaints, the cursor transiently rides along with the
redraw. Start typing Chinese, Japanese, or Korean in that instant and the
candidate list landed on the status line or the agent's streaming row instead
of the input box. wmux now tracks the cell the cursor actually rests on
between repaints and anchors compositions there, so the candidate list stays
on your caret even when a composition starts mid-redraw. (#874) -
The web popover stays on screen. Opened from low on the rail in a short
window it used to run past the bottom edge, taking the Stop button with it. -
Counts on the deck's glyphs (unread channels, dirty worktrees) are announced
to screen readers again — as icons they had become badges with no spoken
equivalent. -
You can scroll the browser terminal with your finger. On a phone there was
no way to reach scrollback at all: a swipe did nothing, the thin scrollbar
could not be dragged by touch, and a soft keyboard has noShift+PageUp. The
only history a phone ever showed was whatever was on screen when it connected.
Swiping now scrolls, and on a pane running a full-screen app — where there is
no scrollback to move — the swipe scrolls that app instead. (#890) -
The mouse wheel reaches scrollback again. One notch moved exactly one line
in the browser terminal, so a few hundred lines of history meant a few hundred
notches. This affects any device with a wheel, not only phones. -
The browser terminal's scrollbar is toned into the chrome again. It had been
drawing xterm's default light bar since the terminal upgrade moved where the
scrollbar lives. -
The Claude Code plugin no longer asks permission for everything. With the
plugin installed, a session started with bypass-permissions was asked to
approve every single tool call — even reading a file. Restarting did not help,
changing the permission mode did not help, and turning the gate off with
WMUX_GATE=0did not help either, so there was no way out short of
uninstalling. The permission gate was answering "ask the user" whenever wmux
had no opinion — the gate switched off, no daemon reachable, a tool wmux does
not gate at all — on the understanding that it meant "I have no opinion".
It does not: it forces a prompt and overrides the permission mode you chose.
The gate now stays silent unless there is a real verdict, which is what
actually hands the call back to Claude Code's own permission flow. (#898)If you installed the plugin through Claude Code, run
/plugin updateafter
updating wmux. The hook that was answering "ask" is a file inside the
plugin's own directory, and updating the app does not touch it — a wmux update
alone leaves the prompting exactly where it was. Installations made with
wmux setup-hooksneed nothing: that copy is refreshed the next time the app
starts. (#898) -
Lines no longer misalign when you resize a pane on Windows 10. The
terminal told xterm that every Windows machine was running a recent ConPTY,
by passing the exact build number xterm uses as its cut-off rather than the
build the machine is actually on. That turns on reflow and turns off the
compensation for older ConPTY at the same time, which is the wrong half of
both on Windows 10. wmux now reads the real build number.Windows 10 therefore moves onto xterm's older-ConPTY path, which is what that
build actually needs, and reflow is off there instead of on. Reflow being on
was also suppressing the spurious row-change events ConPTY emits on resize,
which helped keep a drag-selection alive while the pane was being resized, so
on Windows 10 that selection is now held by the resize guard alone. Windows 11
is unaffected — it was already on the branch it is on now. (#897) -
A long CJK or TUI session no longer leaves its panes permanently
slower. Terminals sharing a font share one WebGL glyph atlas. Once
that atlas had to reclaim space it set a "rebuild your render model"
flag that nothing ever cleared, so from then on every pane sharing it
rebuilt its entire model on every frame, for the rest of the run. Each
pane now rebuilds once per reclaim instead of once per frame.Two defects in the reclaim itself go with it: clearing the atlas gave up
as soon as its first page looked untouched, so an atlas whose first page
happened to be idle while the rest were full cleared nothing at all; and
it could hand the shader more pages than the shader is able to sample.
The safety net that has been covering for this still runs, and should
now almost never have to. -
The Anthropic 5h / 7d utilization setting now survives app restarts.
The toggle used to update only the running app, so every new launch silently
turned the status-bar meter off again. wmux now remembers the explicit opt-in
and resumes usage polling after restoring the session. Older or malform...
v3.42.0
Added
- Complete Simplified Chinese (中文) locale.
zh.tsnow translates all 1,203
keys fromen.ts(previously ~390 / 32%). The locale was already registered and
selectable in Settings, so no wiring changes were needed. (#876)
Changed
- Browser automation now refuses a call whose workspace it cannot determine.
A plugin or wire client that asked wmux to drive a browser page without saying
which workspace it was calling from used to get whichever page happened to be
registered first — possibly one in a workspace it had nothing to do with. It
now gets a clear, non-retryable refusal instead. Callers that already send
their workspace are unaffected, including the bundled MCP server, thewmux
CLI, and the app's own UI. Set"mcp": { "mode": "shadow" }in
~/.wmux/config.jsonto restore the previous behavior. (#810)
Fixed
-
The IME candidate window now follows the terminal cursor. Typing Chinese,
Japanese, or Korean in a wmux pane put the candidate list in the wrong place:
several rows off the caret whenever the terminal had been scrolled up even
slightly, and flying around the screen while an agent streamed output. Latin
input showed nothing because it draws no candidate window. Both came from the
same place — xterm anchors its hidden IME textarea at the cursor's position in
the scrollback buffer rather than its position on screen, and re-anchors it on
every keystroke of a composition, so the candidate list chased whatever the
agent's TUI was redrawing. wmux now corrects the anchor to the cell the cursor
is actually painted on, and pins it in place for the duration of a
composition so the candidate list stays put while you pick a character. (#874) -
Daemon events no longer disappear during the subscription handshake. The
daemon now preserves events emitted after accepting the app's control socket
but before its subscription request arrives, so one-shot session and channel
transitions reach the app instead of falling into that window. (#877) -
Daemon push events are now limited to the desktop app without losing startup
events. Control-pipe clients must identify as the first-party app before
subscribing, while bounded replay and reconnect handling preserve events
across compatible old/new client and daemon handshakes. (#878) -
Three labels stayed in English when wmux ran in Simplified Chinese. The
agent toolbar's Broadcast and Multi Task buttons and the Git panel's Pull
Requests heading were carried over verbatim from the English locale, so they
read as English in an otherwise translated UI. (#880) -
Minimise wmux on Windows and your phone can finally resize the session.
Handing PTY geometry to a phone depends on the desktop admitting nobody is
looking at the pane, and the desktop worked that out from the browser's
page-visibility signal — which on Windows never changes, not when the window
is covered and not even when it is minimised. So the phone was told the desk
owns the size forever, and the terminal it showed stayed wrapped for a window
nobody could see. The desktop now takes that answer from the window itself:
minimised, hidden to the tray, or screen locked all count as nobody looking,
and restoring the window takes the geometry back. Locking your screen releases
the size too, which is exactly when the phone is the only screen left.
(#882, follow-up to #766) -
Reboot-survival demos no longer contend with the parallel unit suite.
The real-Git J1 and J3 checks now run in the serial runtime lane, preventing
load-sensitive Windows timeouts while preserving both standalone demo
commands without shell execution. (#884)
What's Changed
- fix(daemon): preserve events across subscribe handshake by @snowyukitty in #877
- fix(terminal): anchor the IME candidate window to the painted cursor by @openwong2kim in #875
- feat(i18n): complete Simplified Chinese locale by @cnxiekun in #876
- fix(browser): derive browser workspace scope from the caller by @openwong2kim in #873
- fix(daemon): gate event subscriptions by client role by @snowyukitty in #878
- fix(i18n): translate the toolbar and Git labels left in English in zh by @openwong2kim in #880
- Report the pane hidden when the window is minimized, hidden to tray, or locked by @openwong2kim in #883
- test(ci): serialize real-Git reboot demos by @snowyukitty in #884
- chore(release): 3.42.0 by @openwong2kim in #885
New Contributors
Full Changelog: v3.41.1...v3.42.0
v3.41.1
Changed
-
Installing an update on Windows now ends running sessions, and says so
before you press the button. The daemon holds the install folder open, so it
has to go down for the installer to run. The update panel previously implied
the opposite. (#866) -
Terminal ownership checks stop waiting on the UI. Every MCP call an agent
makes — sending to a pane, polling events, resolving its own identity — used
to ask the renderer who owns a terminal, once per check. That question was
slowest to answer exactly when the app was busiest. Main now answers from the
workspace snapshot the renderer already pushes it, and falls back to asking
whenever the snapshot is stale, missing, or disagrees. Cross-workspace access
is still refused on the renderer's word, never on a cached guess. (#870) -
Cross-pane search reads the newest output first.
wmux_search_panesused
to scan up to 20,000 lines per pane starting from the oldest, so on a long
scrollback the recent output you were actually looking for could be the part
that got cut. It now searches the newest 5,000 lines by default, says
truncated: truewhen older lines went unread, and takes a new optional
searchTailLinesto go deeper. It also yields between panes instead of
freezing the window for the whole sweep. The in-app search bar is unaffected
and still covers your full configured scrollback. (#871)
Fixed
-
Running the Windows installer while wmux is open can no longer destroy the
installation. Squirrel'sSetup.exedeletes the whole install folder as its
first step, and wmux used to start it without waiting to actually exit. With
the app fully up its loaded libraries are locked, the delete fails outright,
and the install stops half-finished — leaving blank shortcut icons and no
launcher to retry from. The installer is now started by a helper that waits
until nothing is using the folder any more, and refuses to start it at all if
something still is, so a failed update leaves your working version alone
instead of a broken one. The normal in-app update was not affected by this;
the failure needed the app to be fully running, as it is when the installer is
launched by hand. (#866) -
A failed update now says so. If the installer could not be started safely,
wmux reports it the next time it opens instead of leaving you on the old
version with no explanation. (#866) -
The daemon no longer stalls on its own log file. Each log line was written
and flushed to disk synchronously, which on Windows also meant a virus-scanner
hook per line. Routine lines are now batched; warnings and errors still write
through immediately, after the pending lines, sodaemon.logstays in order
and a crash still records what happened. (#871) -
Terminal tab strips stopped re-rendering on unrelated panes. Any pane's
status, label, or agent change re-rendered every tab strip in the app; each
strip now watches only its own pane. Terminal output also skips a
per-chunk regular-expression scan it did not need. (#871)
What's Changed
- test(installer): stop the shortcut-hygiene runtime suite timing out on CI by @openwong2kim in #868
- perf(main): resolve PTY ownership and role bindings mirror-first by @openwong2kim in #870
- perf: daemon log buffering, renderer hot-path fixes, pane.search tail bounding by @openwong2kim in #871
- fix(updater): start the Windows installer against a dead tree by @openwong2kim in #869
- chore(release): 3.41.1 by @openwong2kim in #872
Full Changelog: v3.41.0...v3.41.1
v3.41.0
Known issues
- Windows: consider installing this release manually rather than through the
in-app updater. Squirrel'sSetup.exedeletes the whole install directory as
its first step, and the in-app updater starts it without waiting for wmux to
finish exiting. If anything still holds that directory open when the delete
runs, the install stops half-finished, leaving blank shortcut icons and no
launcher to retry from. We have reproduced that failure and are fixing it, but
we do not yet know how often the normal update path actually hits it — the
update path has looked like this for many releases. If you would rather not
find out, download the installer from the release page and run it with wmux
closed. Note that the fix cannot help this upgrade either way: the step at
fault belongs to the version you are upgrading from. (#866)
Added
-
Added browser caller-scope shadow telemetry. Target-resolving browser RPCs now record identified calls that future caller-derived workspace enforcement would refuse, without changing current routing, parameters, responses, or enforcement behavior. (#846)
-
One-shot interface visibility presets. Apply Minimal to hide optional chrome at once, or restore the shipped Standard visibility without entering a persistent mode. (#860)
Changed
-
The daemon control pipe no longer pushes events at clients that never asked for
them. Events used to reach every connection from the moment it opened, so a tool
that wrote a request and read one line back could read an event instead of its
reply — and since an event frame carries no error text, report a failure with an
empty message while quietly dropping the real answer. Events are now opt-in:
a connection gets replies and nothing else until it calls
daemon.events.subscribe. Tools that only make requests, including the wmux CLI
and the MCP server, need no change and can no longer hit that failure. A tool that
did rely on unsolicited events must now subscribe. Seedocs/PROTOCOL.md§2.9.
(#856) -
Daemon cold start is roughly two seconds faster on Windows. The LanLink
HMAC key was the one hardening call still running synchronously on the daemon
boot path, and the firstpowershell.exespawn inside an Electron process
measured 1.8–2.3 s — 75–78 % of the entire daemon startup. The replacement
measures around 42 ms.
Fixed
-
Scoped CLI browser navigation to its calling workspace.
wmux browser navigatenow uses verified pane identity inside wmux instead of potentially navigating the first globally registered browser; invocations outside wmux retain the existing active-target behavior. (#845) -
Non-agent panes (btop, vim, plain shell) no longer misidentified as AI agents in the sidebar and Fleet View. Two independent fixes: (1) the Fleet View selector now requires per-PTY agent identity (
surfaceAgent) before inheriting workspace-level agent name and status for the active pane — a non-agent pane in a split can no longer borrow the real agent's "Claude Code · Needs you" badge; (2) the Claude Code agent detector now uses a compound gate (banner and prompt evidence, matching the Kiro CLI pattern) instead of a single banner regex, so a process monitor displaying "claude" in its process list cannot falsely open the detection gate. (#850) -
Restored approved plugin panels in packaged builds. The production main-window CSP now permits registered
wmux-plugin:iframe sources without broadening script or network access, preventing the panel restore loop that followed plugin approval. (#852) -
wmux now defines its own application menu instead of inheriting Electron's default one, which had been quietly owning shortcuts wmux binds. On macOS
Cmd+Shift+Rtriggered Force Reload rather than renaming a workspace, and the reload dropped every attached remote workspace;Cmd+Wclosed the window instead of the active surface, and the zoom keys resized the UI instead of the terminal font. On Windows and LinuxCtrl+Rno longer reloads when focus sits outside a terminal. -
Settings → Shortcuts lists the built-in key bindings from one shared table, so a custom keybinding that collides with
Ctrl+Shift+A,Ctrl+Shift+G,Ctrl+M,Ctrl+Tabor the zoom keys is now flagged as a conflict instead of being accepted and silently never firing. -
Directories with non-ASCII names in
PATHnow resolve in wmux terminals. A path such asD:\软件\Python312was being read from the registry through a code-page-encoded pipe, which replaced characters the code page could not represent and left the rest asU+FFFD, so the directory matched nothing on disk and commands from it resolved to a laterPATHentry instead — for Python, the Microsoft Store stub. Accented Latin characters were affected the same way. -
Windows token files are now hardened without PowerShell — and stay hardened
on managed machines. The owner-only DACL on wmux's auth tokens, the LanLink
HMAC key and the web state file used to be rebuilt by spawning
powershell.exe -ExecutionPolicy Bypass -EncodedCommand. On any machine
running PowerShell in Constrained Language Mode — the AppLocker/WDAC default
on managed corporate fleets — that call could not run at all, and wmux quietly
fell back to a plainicaclsstrip. The fallback removes only four well-known
broad groups, so an explicit entry for any other principal survived: those
machines have been running with looser token permissions than the docs promise,
with nothing but a console warning to show for it. Hardening now writes the
file through a fresh, pre-locked inode instead, which drops every non-owner
entry — inherited, well-known or custom — with no PowerShell involved. -
Norton no longer flags wmux at startup. Norton Behavioral Protection
detected the-ExecutionPolicy Bypass -EncodedCommandshape as
IDP.HELU.PSE85and blockedpowershell.exe, in some cases quarantining
wmux.exeitself. wmux no longer spawns PowerShell for this, so the detection
has nothing to fire on. Thanks to @TarJae for the report and the diagnosis. -
A rename collision can no longer wipe your paired phones. Token hardening
reported success as a single boolean, which meant "could not tighten the
permissions" and "nothing changed, and nothing got weaker" looked identical to
callers. The LanLink peer store treats that signal as fail-closed: it deletes
the peer file and regenerates the machine HMAC key, which invalidates the peer
file's MAC and drops every pairing. The two states are now distinguished — and
the safe one is verified, not assumed: a failed swap is retried, then the
file's actual DACL is read back (icacls /save) and only a confirmed
owner-only ACL avoids the fail-closed branch. A concurrent token rotation can
no longer be overwritten by a stale hardening snapshot either — the swap
compares content before committing and stands down if a newer write landed. -
A stalled event subscriber can no longer grow the daemon's write buffer
without bound. Broadcast delivery now drops only that client's event
subscription after its buffered output crosses the existing safety cap. The
RPC connection stays open, and healthy subscribers continue receiving events.
(#862) -
A dead shortcut can no longer blank the taskbar icon after an update.
Windows resolves the taskbar button's icon through the shortcut carrying the
app's AppUserModelID, so one stale link — a pre-3.4 top-level Start Menu
entry, or a taskbar pin recorded against a deletedapp-X.Y.Zdirectory —
blanked the icon of a perfectly healthy install. The installer hooks now
repair such links (retargeting them to the version-stable root launcher)
instead of leaving them behind. (#863, #865) -
Shortcut icons no longer depend on a download at install time. The
installer previously fetched the shortcut icon from raw.githubusercontent.com,
which fails on networks where that host is unreachable and left shortcuts
with no icon at all. The icon that already ships inside the package is used
instead. (#863, #865)
Security
- Restricted raw browser attachment metadata.
browser.cdp.infonow returnscdpPortandshellUrlonly to operator, validated pinned, or recognized first-party wire callers; approved third-party and legacy callers retain target metadata without the raw attach primitive. Fallback-enabled browser operations continue through the workspace-scoped, lease-covered RPC lane, while operations requiring a direct Playwright page report that attachment is unavailable. (#844)
What's Changed
- fix(release): retry the darwin asset check instead of failing on a slow listing by @openwong2kim in #847
- fix(browser): narrow CDP attach disclosure by @snowyukitty in #844
- fix(cli): scope browser navigation to caller workspace by @snowyukitty in #845
- feat(browser): shadow caller-derived workspace scope by @snowyukitty in #846
- fix(plugins): allow plugin frames in production CSP by @snowyukitty in #852
- Own the application menu instead of inheriting Electron's by @openwong2kim in #854
- Read the registry PATH as UTF-16 so non-ASCII entries survive by @openwong2kim in #855
- fix(daemon): make pushed control-pipe events opt-in by @openwong2kim in #856
- Drop powershell.exe from Windows token-file ACL hardening by @openwong2kim in #861
- fix(fleet): prevent non-agent panes from inheriting agent identity (#850) by @openwong2kim in #857
- feat(settings): add one-shot chrome presets by @snowyukitty ...
v3.40.2
Security
- Hardened privileged RPC client recognition. The bundled-MCP and CLI allowlists now require source-qualified wire provenance, so an approved in-process UI plugin whose manifest name collides with a recognised host identity stays on its own declared permissions instead of inheriting the first-party method set. (GHSA-x2xm-9w7w-hh6p)
What's Changed
- chore(release): 3.40.2 by @openwong2kim in #843
Full Changelog: v3.40.1...v3.40.2
v3.40.1
Added
- The whole interface is now scalable from Settings. A new UI scale slider in Settings → Appearance → Layout scales the sidebar, titlebar, and every chrome surface together — not just the terminal panes — so the previously hard-coded 8–12px sidebar text can be enlarged for high-DPI displays, large monitors, and accessibility. The factor persists across restarts, applies live, and keeps the native window controls (macOS traffic lights / Windows buttons) centred in the titlebar at any size. This completes the groundwork PR #824 shipped, which added the zoom plumbing behind a prototype env var with no user-facing control. (#822)
Fixed
-
A pane you opened by hand no longer counts as an agent that is still
working. Agent status is stored once per workspace, so a busy agent in a
background pane made whichever pane you had focused look busy too — including
a plain shell that had never run an agent. The orchestrator then refused to
close out its work, and the only way past it was to answer a question about
clearing a status that was never true. A pane now shows as running only when
its own terminal produced the activity. (#838) -
A mirrored remote pane now fits its cell instead of being cropped. The
mirror renders the grid the remote daemon owns, but it draws with your local
font, so a remote pane wider or taller than the cell it sits in simply ran off
the edge and was clipped from the top-left corner. Long lines lost their right
half, and — because a terminal app keeps its input box on the bottom rows —
the prompt you were typing into was cut off entirely, reappearing as a sliver
only while you typed. The mirror now shrinks its own font until the remote's
grid fits, and grows back when the window widens. -
A pane running OpenClaude could lose its
(openclaude)name suffix. One of
the six hand-maintained agent lists was missing that one agent, so anything
handing wmux the slug form got back "unknown" and the pane fell back to a
generic name. Every list now derives from a single table, so an agent cannot be
present in five places and absent from the sixth. (#841) -
Kiro panes went undetected when the composer shared its line with other
text. The fallback that reads a cursor-drawn composer — one whose spaces are
gone by the time wmux sees it — searched from the wrong end of the line and
could never match. Kiro is now recognised in that case too. (#841)
What's Changed
- refactor(detector): one table for agent identity, declarative compound gates, fork inheritance by @openwong2kim in #841
- Stop transplanting a workspace-level 'running' onto the active pane by @openwong2kim in #838
- feat(appearance): add a UI scale control to Settings → Appearance by @openwong2kim in #839
- Fit a mirrored remote pane to its cell instead of cropping it by @openwong2kim in #840
- chore(release): 3.40.1 by @openwong2kim in #842
Full Changelog: v3.40.0...v3.40.1
v3.40.0
Added
-
The native window controls now follow a renderer zoom. Scaling the UI used
to leave the macOS traffic lights and the Windows window buttons behind — they
are drawn by the OS and do not move with the renderer, so the custom titlebar
grew while the controls stayed put. They are now re-placed whenever the zoom
changes, which keeps them centred in the titlebar at any scale. Nothing in the
app zooms yet; this is the groundwork for a configurable UI size. -
Fan-out tasks can each get their own port. Declare
fanout.portRange
(e.g."3000-3010") in your repo'swmux.jsonand every task a fan-out
spawns is assigned one free port from that window, exported to its pane as
WMUX_TASK_PORT. Before this, eight tasks that all rannpm run devfought
over one port and seven of them died on startup. Ports are probed and assigned
before any task spawns, so no two tasks in a fan-out collide — and a port just
handed out stays claimed for ten minutes, so a second fan-out started while
the first one's servers are still booting doesn't reuse it either. Windows are
capped at 512 ports; if one runs out, the remaining tasks simply start without
the variable. -
Worktree setup hook for fan-out.
fanout.setupinwmux.jsonis a shell
command run inside each freshly created worktree before its agent starts —
thecp ../../.env .andnpm ciyou used to type into every agent's first
turn. It is trust-gated exactly like supervised panes: the command is shown
verbatim in the trust dialog and runs only againstwmux.jsonbytes you have
explicitly approved, so a hook arriving via a pull request is inert until you
review it. An edit demotes the file to stale and the hook stops running. When
a declared hook is skipped for lack of trust the fan-out says so instead of
quietly doing nothing, and a hook that fails leaves its task unspawned with
the worktree preserved rather than starting an agent in a half-prepared tree —
only that task; the rest of the fan-out continues. Output is never capped (a
chattynpm ciwon't be killed), and a hook that hits its five-minute ceiling
has its whole process tree killed, so a background install can't outlive it
and keep writing into the preserved worktree.
Seedocs/how-to/fan-out-task-environment.md. -
Webhook and ntfy notifications, no phone app required. Point
notifySinksin~/.wmux/config.jsonat a webhook URL or an ntfy topic and
the daemon pings it when an agent asks for an approval or finishes a turn.
Until now the only way to hear about a blocked agent from away from the
keyboard was the iOS app and its push relay; this is the plain-HTTP path for
everyone else. Off unless you configure it, outbound only — the daemon opens
no new port — andWMUX_NOTIFY_SINKS=0turns it off without editing config."notifySinks": [ { "type": "ntfy", "url": "https://ntfy.sh/my-topic", "events": ["approval"] }, { "type": "webhook", "url": "https://hooks.example/wmux" } ]
The ping is deliberately thin: the event kind, a fixed title, the agent name,
short pane and workspace id prefixes, a derived risk tier and a timestamp. It
never carries the agent's question, tool input, terminal output, file paths or
any id that can address a pane — the destination is a server you chose but the
body travels in the clear, and a shared ntfy topic is not the place for your
terminal. On ntfy the priority follows the stakes — an approval that names a
destructive action goes out at max, an ordinary approval at high, a
turn-completion at default — so the two never arrive looking equally urgent.
Approvals also get their own queue, so a busy afternoon of turn-completions
can never push out the one notification somebody is actually blocked on. -
Your phone stops buzzing for approvals you are already looking at. When
the desktop app is connected and its window was focused within the last 90
seconds, the push notification for a new approval is held back — the request
still lands in the in-app inbox, which is the thing you were reading anyway.
Before this, sitting at the desk answering prompts also meant a phone
lighting up for every one of them.Held, not dropped. The moment you leave — the window blurs, you lock the
screen, the machine sleeps, the app quits, or the focus report simply ages
out because you walked away — any still-pending approval's push is delivered.
It carries the same collapse id as the original, so the phone replaces that
pane's banner rather than stacking a second one. An approval you answered
while parked is dropped instead of sent.Every uncertainty still sends: a daemon with no desktop attached, a version
of the app too old to report focus, an unreadable config, or a focus report
that has gone stale all fall back to notifying. Acritical-risk approval is
pushed even when you are present — a destructive action is worth the second
channel. Only the app's own process can report presence; the CLI, the MCP
server, and anything an agent can reach are refused, so nothing can silence
its own approval prompts.Configurable via
pushPresenceSuppressionin~/.wmux/config.json
(enabled, defaulttrue;staleAfterMs, default90000, capped at ten
minutes).
Changed
- The terminal brain's [active-work] block goes on a diet. An unchanged block
collapses to a one-line reminder (id + the finish-via-deck_complete_work contract)
instead of re-typing the full objective, follow-ups, and task list onto the
terminal every turn. Any change to the record — or a conversation reset in the
TUI — re-sends the block in full. (#832)
Fixed
-
A bypass-mode session no longer stops at the permission gate. An agent
launched with--dangerously-skip-permissionshas already declared that it
should never be asked, but the remote permission gate opened an approval
record for it anyway — on everyBash,Write,Edit,MultiEdit,
NotebookEdit,Task, andKillShellcall. With no phone attached, each of
those calls paused for the full gate deadline and then fell back to exactly
the local prompt the user had opted out of, which read as "bypass mode asks
for approval on every tool call". The gate now reads the session's live
permission mode and lets a bypass session straight through, while the pane's
liveness header keeps updating as before. Sessions that do still prompt —
acceptEdits,plan, and the default mode — keep their gates unchanged.
(#829) -
Mirrored remote panes running a fullscreen app no longer paint a garbled
screen. Attaching to a pane that had been running vim, htop or an agent CLI
for a while showed rows interleaved over old scrollback, with fragments of
earlier frames stuck along the left edge, and the app never took over the
screen. The initial paint is capped to a window of recent output, and a
fullscreen app announces itself exactly once, at startup — so hours later that
announcement was outside the window and the terminal was still on the ordinary
buffer while the bytes assumed the fullscreen one. The daemon now tracks which
modes a pane's output has switched on and restores them ahead of the paint,
including for panes restored after a restart. This affects phones and tablets
opening the web terminal too, not only mirrored workspaces. -
Resizing a pane no longer breaks every viewer watching it. Geometry was
sent once when a viewer connected, so after a resize on the machine that owns
the pane, everything drawn afterwards was positioned for a grid the viewer no
longer had. Viewers now follow the new size once the resize settles — and only
the size: a viewer's scrollback and scroll position survive someone resizing
the pane on the other machine. -
A mirrored pane no longer types into the remote shell by itself. Terminals
answer certain queries automatically, and a mirror was sending its answers on
to the remote pane's input — so a live app, or replayed scrollback containing
such a query, injected stray characters into a shell on the other machine. A
mirror no longer sends those answers; the machine that owns the pane is the
one that responds. The same injection is fixed in the phone and tablet web
terminal. -
Attached remote workspaces survive a reload and a restart. Pressing Cmd+R,
or quitting and reopening wmux, silently emptied the Remote section of the
sidebar and every mirror with it — re-attaching meant walking back through the
picker each time. Attachments are now remembered, and are restored on launch
with a fresh pane list fetched from the host. Every row appears immediately,
even when a host takes its time answering. A host that is asleep or
unreachable keeps its row, marked as stale, rather than being deleted, and
its mirrors reconnect on their own once it comes back; detaching is still
yours to decide. -
Mirrors follow panes opened and closed on the remote machine. The pane
layout was frozen at the moment you attached: a pane closed on the other
machine left a dead tile in the grid forever, and a pane opened there was
invisible until you detached and attached again. The grid now follows the
remote workspace, with surviving panes staying in place instead of shuffling
when one comes or goes. -
Removing a remote host clears its mirrors. The host disappeared from the
picker but its mirrored workspaces stayed in the sidebar for the rest of the
session, quietly failing to refresh against a machine wmux no longer knew
about. They now go with it. -
The orchestrator no longer re-prints the same message while waiting on you. A
pending decision (deck_ask_decision) now releases the Stop gate — previously the
gate kept refusing the turn while the decision block forbade acting, leaving the
brain nothing to do but restate its question, up to four times p...
v3.39.1
Fixed
-
Detach on an attached remote workspace now works. Right-clicking the
sidebar row opened its menu, but pressing "Detach" did nothing at all. The
menu dismissed itself onmousedown, which arrives beforeclick, so the
button unmounted under the pointer before its own handler could ever run —
the action was unreachable rather than broken. -
Mirrored remote panes use your terminal font, theme and contrast floor.
An attached remote workspace built its terminal without any of the app's
visual settings, so it fell back to xterm's own defaults —monospaceat
15px on black, outside the theme's ANSI palette. Next to a local pane it read
as slightly bolder and slightly larger, because it was. Changing the settings
now updates the mirror without dropping what the remote has already sent.One difference remains: local panes render through WebGL and mirrors use
xterm's DOM renderer, whose text antialiasing differs. Matching size, family
and palette removes most of the mismatch, not all of it. -
Mirrored panes no longer tear on Korean and other wide text. The mirror
re-renders a grid the remote daemon computed, but it was the one terminal in
the app that skipped the shared Unicode width model — so every double-width
character put the two grids one cell further apart, and rows arrived
interleaved and torn. -
Mirrored panes stay inside their box. A remote pane with more rows than
the local cell can show rendered taller than its container, and nothing in
the chain clipped it, so terminal output painted over the composer bar and
the sidebar. Typing made it obvious because that is when the overflowing rows
got repainted.This clips rather than scales: geometry belongs to the remote daemon and the
mirror never resizes it, so a remote grid taller than the local cell now has
its lower rows cut off instead of drawn over the app. That is the better of
two bad outcomes, not a good one — the cropped region is where the cursor and
live output are. Fitting the mirror to the cell needs a geometry negotiation
that does not exist yet.
What's Changed
- Make Detach on a remote workspace row reachable by @openwong2kim in #817
- Budget RemoteHostsStore's tests for the Windows ACL cost by @openwong2kim in #821
- Mirrored remote panes: app font and theme, shared width model, and clipping by @openwong2kim in #819
- chore(release): 3.39.1 by @openwong2kim in #823
Full Changelog: v3.39.0...v3.39.1