Skip to content

Releases: openwong2kim/wmux

v3.45.0

Choose a tag to compare

@github-actions github-actions released this 21 Aug 01:26
3c5d550

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 on footer 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 false waiting on 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 to bypasspermissionson, 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, waiting and the approval awaiting_input regexes 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 only SessionStart
    suppressed the detector's true "ready for input" read, leaving the pane on
    the gate's one-shot running until 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-anchor2ime-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.p95 only 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
    a clear. 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 (private a2a.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 on main showed 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...
Read more

v3.44.0

Choose a tag to compare

@github-actions github-actions released this 18 Aug 12:07
72bbcc4

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+T was "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=a in 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 raw Error.message and 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+C over a
    selection interrupted the remote process instead of copying it, Ctrl+V did
    nothing at all, and Shift+Enter submitted where a local pane would have
    added a line. All four now behave the way they do in a local pane — including
    ⌘C / ⌘V on macOS — while an empty-selection Ctrl+C still interrupts the
    remote process, and a host started without --allow-input still receives
    nothing. Shift+Enter sends 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 fires onSelectionChange once 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,
    ends cat > 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 runs u as undo (#931, same gate as the
    attach mirror in #924).

  • Shift+Enter / Ctrl+Enter / Ctrl+J no longer cause a bash: syntax error in a
    shell that does not speak the kitty keyboard protocol. The newline decision
    now preventDefaults the browser's own newline, which xterm's input handler
    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 ...

Read more

v3.43.0

Choose a tag to compare

@github-actions github-actions released this 16 Aug 04:55
4ba75f0

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_reached error so two agents cannot ping-pong unattended forever.

  • a2a_discover responses include elapsedMs (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_start is now on the
    commander surface: one call, N tasks, each on its own wtask/ 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, so claude plugin update reported "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
    takes blockMs to wait for an event rather than returning an empty page, plus
    ptyId and kinds to 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.
    Three settings.prefix.* keys were missing from both en.ts
    and zh.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_send now returns a delivery
    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 no Shift+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=0 did 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 update after
    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-hooks need 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...

Read more

v3.42.0

Choose a tag to compare

@github-actions github-actions released this 13 Aug 23:52
3c68155

Added

  • Complete Simplified Chinese (中文) locale. zh.ts now translates all 1,203
    keys from en.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, the wmux
    CLI, and the app's own UI. Set "mcp": { "mode": "shadow" } in
    ~/.wmux/config.json to 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

New Contributors

Full Changelog: v3.41.1...v3.42.0

v3.41.1

Choose a tag to compare

@github-actions github-actions released this 12 Aug 22:38
6815ab0

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_panes used
    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: true when older lines went unread, and takes a new optional
    searchTailLines to 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's Setup.exe deletes 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, so daemon.log stays 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

Choose a tag to compare

@github-actions github-actions released this 11 Aug 23:54
1408cbf

Known issues

  • Windows: consider installing this release manually rather than through the
    in-app updater.
    Squirrel's Setup.exe deletes 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. See docs/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 first powershell.exe spawn 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 navigate now 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+R triggered Force Reload rather than renaming a workspace, and the reload dropped every attached remote workspace; Cmd+W closed the window instead of the active surface, and the zoom keys resized the UI instead of the terminal font. On Windows and Linux Ctrl+R no 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+Tab or the zoom keys is now flagged as a conflict instead of being accepted and silently never firing.

  • Directories with non-ASCII names in PATH now resolve in wmux terminals. A path such as D:\软件\Python312 was being read from the registry through a code-page-encoded pipe, which replaced characters the code page could not represent and left the rest as U+FFFD, so the directory matched nothing on disk and commands from it resolved to a later PATH entry 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 plain icacls strip. 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 -EncodedCommand shape as
    IDP.HELU.PSE85 and blocked powershell.exe, in some cases quarantining
    wmux.exe itself. 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 deleted app-X.Y.Z directory —
    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.info now returns cdpPort and shellUrl only 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

Read more

v3.40.2

Choose a tag to compare

@github-actions github-actions released this 09 Aug 15:30
5edffeb

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

Full Changelog: v3.40.1...v3.40.2

v3.40.1

Choose a tag to compare

@github-actions github-actions released this 08 Aug 08:55
b7481ab

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

Choose a tag to compare

@github-actions github-actions released this 07 Aug 19:57
36d12c3

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's wmux.json and 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 ran npm run dev fought
    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.setup in wmux.json is a shell
    command run inside each freshly created worktree before its agent starts —
    the cp ../../.env . and npm ci you 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 against wmux.json bytes 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
    chatty npm ci won'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.
    See docs/how-to/fan-out-task-environment.md.

  • Webhook and ntfy notifications, no phone app required. Point
    notifySinks in ~/.wmux/config.json at 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 — and WMUX_NOTIFY_SINKS=0 turns 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. A critical-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 pushPresenceSuppression in ~/.wmux/config.json
    (enabled, default true; staleAfterMs, default 90000, 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-permissions has already declared that it
    should never be asked, but the remote permission gate opened an approval
    record for it anyway — on every Bash, Write, Edit, MultiEdit,
    NotebookEdit, Task, and KillShell call. 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...

Read more

v3.39.1

Choose a tag to compare

@github-actions github-actions released this 07 Aug 02:58
edd912d

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 on mousedown, which arrives before click, 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 — monospace at
    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

Full Changelog: v3.39.0...v3.39.1