* ui components
* right click menu + popups
* settings screen
* various fixes
* presets in config
* zellij prompt - ui elements in the cli
* context menu shortcuts and toggle, keybindings in popups, info popups + zellij prompt notify
* smart config writing and some leftovers
* keybindings/right-click/config editors
* some wrapups
* feat(plugins): add prompt() plugin API for zellij:prompt, dialog forms, and Text labels in widgets
* theme editors
* upstream by-id action types, config env for new panes only, smaller saved menu config, web right-click, example config
* upstreamify
* rustfmt
* fix windows and theme editor layout
* add pr
* feat(web): add WebSocket heartbeat to prevent false disconnections
Idle WebSocket connections are often silently closed by network
intermediaries (e.g. Cloudflare's 100s idle timeout), causing false
"connection lost" messages even though the session is still alive.
Keep the control WebSocket alive by sending a protocol-level Ping
every 30s and closing the connection if no Pong is received within
45s. Browsers reply to Pings automatically below the JS layer, so
this keep-alive survives JS event-loop blocks and tab throttling.
* add some cleanups
* rustfmt
* add pr
---------
Co-authored-by: Aram Drevekenin <[email protected]>
* fix(kitty-graphics): zlib-compress images forwarded to the host terminal
`emit_kitty_transmit` sent every image to the host as uncompressed RGBA
base64 (`f=32,t=d`), about 20x the size of a PNG the application sent,
and re-sent it on every tab switch, pane split and resize step. Compress
the cached host payload with zlib and tag the transmit with `o=z`, which
the kitty protocol defines as part of its base feature set.
Measured with a headless client at 429x77 (8x17 px cells): a 121 KB PNG
went from 2.4 MB to 163 KB on the wire, a 943 KB PNG from 19.9 MB to
1.9 MB, with no measurable change in server CPU.
Fixes#5596
* fix: conditional zlib compression depending on outer terminal support
* rustfmt
* add pr
---------
Co-authored-by: Aram Drevekenin <[email protected]>
* refactor(plugin-api): share the keybinding protobuf conversions
The conversion between a keybinding table and its protobuf form was written
out four times: once each way for the ModeUpdate and InitialKeybinds events.
Collect them into `keybinds_to_protobuf` and `keybinds_from_protobuf`.
Decoding now drops a binding whose key is missing instead of unwrapping it,
so a malformed or foreign payload can no longer panic the conversion, and a
mode whose key fails to convert costs only that binding rather than every
binding in the mode.
* feat(nested-sessions): carry input mode and keybindings over the protocol
A session hosting a nested session has no way to learn what that session is
doing, so its plugins cannot show hints for the session the user is actually
typing into. Add the three messages that carry it:
- GuestModeUpdate, sent by a guest whenever its input mode changes, so a host
always knows which of the guest's bindings currently apply.
- RequestGuestKeybinds and GuestKeybindsUpdate, a query and its answer, for
the far larger keybinding table. Keeping the table behind a query means it
is fetched once by whoever wants it rather than pushed at every host.
The table travels as an encoded plugin-API payload in an opaque bytes field.
Describing keybindings natively here would mean restating the whole Action
schema in this contract, which the codegen compiles with only its own include
path. Reusing the existing payload also means a host and a guest built from
different Zellij versions exchange whatever both understand and silently drop
the rest, which is the behavior this wire needs.
An input mode is written under the name InputMode::from_str reads, so adding a
mode needs no change to this contract and an unknown mode is rejected rather
than misread.
Peers advertise the new HintReporting capability, so each side can tell
whether the other can take part before asking it anything.
* feat(plugin-api): expose nested session mode and keybinding events
Plugins running in a host session can see that a pane holds a nested guest
session, but they have no way to learn what that guest is doing. A status bar
in the host therefore keeps showing the host's own hints while the user is
descended into the guest, which is the wrong keybinding table for the keys the
user is actually pressing.
Add two events and one command covering that gap:
- Event::NestedSessionModeUpdate reports a guest's input mode as it changes.
- Event::NestedSessionKeybinds delivers a guest's full keybinding table.
- PluginCommand::RequestNestedSessionKeybinds asks a guest pane for that
table, which arrives asynchronously as the event above.
Both events carry the pane the guest runs in and the guest's session name, so
a plugin watching several nested panes can tell them apart. Keybindings are
requested rather than pushed because the table is large and most plugins never
need it.
* feat(nested-sessions): report guest input mode and keybindings to the host
Wire the nested session hint events end to end.
A guest announces the HintReporting capability during the handshake and, from
then on, emits a GuestModeUpdate frame whenever its input mode changes. The
mode is reported on every change rather than only while the host is descended,
so the host already holds the right mode the moment focus arrives and the
status bar does not lag behind the descent.
The host turns each such frame into Event::NestedSessionModeUpdate. It answers
PluginCommand::RequestNestedSessionKeybinds by writing a RequestGuestKeybinds
frame into the guest pane; the guest replies with its keybinding table plus its
current mode, and the host forwards the table as Event::NestedSessionKeybinds.
Requests to guests that did not advertise HintReporting are dropped rather than
written, and frames from panes whose guest is not currently live are ignored,
matching how the other guest-to-host frames are handled.
Both events and the command require the ReadApplicationState permission, the
same gate ModeUpdate uses.
* test(nested-sessions): cover guest mode and keybinding reporting
Unit tests over the contract check that every input mode survives the round
trip under its own name, that a mode name the reading side does not know is
rejected rather than guessed at, and that a keybinding table keeps its modes,
keys and actions across the wire. The undecodable and empty payload cases are
covered too, since a host and a guest may be built from different versions.
Integration tests drive two real sessions bridged through a pane: both sides
advertise hint reporting during the handshake, the guest answers a keybinding
request with a populated table plus its current mode, and the guest reports
each mode change while the user is descended into it.
* test(nested-sessions): drop unknown capabilities without losing known ones
A peer built from a newer Zellij can advertise capabilities this side has
never heard of, so pin down that they are ignored individually rather than
invalidating the whole advertisement.
* feat(nested-sessions): carry the guest's base mode alongside its input mode
A host plugin that only knows a guest's current mode cannot tell which of the
guest's bindings are inherited from its base mode. Zellij's own hint rendering
leans on that distinction: bindings the base mode already advertises are
inherited by every other mode through `shared_except` groups, so repeating them
in each mode is noise. Without the guest's base mode a host plugin either
suppresses the wrong bindings or suppresses none.
A guest with a non-Normal base mode, such as an unlock-first config, is where
this shows: assuming Normal there hides the wrong half of its bindings.
The base mode rides along on the existing mode message, so a guest that has not
switched modes yet still reports one, resolved the way the rest of Screen
resolves it (the configured default mode is the base). It stays optional on the
wire, so a name the reading side does not recognize costs only the
inherited-binding distinction rather than the whole message.
* feat(nested-sessions): report the guest's mode when the handshake completes
A guest only pushed its input mode when that mode changed, so a host that
had just picked up a nested session learned nothing about it until the user
happened to switch modes inside it. A host plugin has no other way to tell
which of its panes holds a session worth asking for keybindings, which left
it unable to act on the feature at all in the common case of a guest sitting
in its starting mode.
Send the report once on contact as well, and reuse the same client mode
lookup the keybinding answer already did.
* docs(changelog): note the nested session hint reporting events
* fix(nested-sessions): drop guest keybindings on control characters
Nothing a guest says is authenticated. Its frames travel in the terminal
stream of the pane it runs in, so any program with that pane can write them,
including a keybinding table claiming a binding on a raw control character.
A host that draws those bindings puts their keys on its own screen, where a
control character is a terminal escape in the host's output rather than a
key anyone could press.
Drop them where the guest's payload is decoded, so no host plugin inherits
the problem. Zellij spells the control keys that do exist as their own
BareKey variants, so a Char holding one describes no binding worth keeping.
* test(nested-sessions): expect hint reporting in the announce ack
The host now advertises HintReporting alongside NestedControl when it
acknowledges a guest, so the assertion on the exact capability list had to
follow.
* fix(plugin-api): drop only what has no protobuf form when writing keybindings
keybinds_to_protobuf failed the whole table on the first key or mode it could
not represent, so one unrepresentable binding cost the caller every binding:
a ModeUpdate that failed to convert entirely, and a nested session's answer
that reached its host as an empty table indistinguishable from a session with
no bindings at all.
Drop the individual mode, key or action instead, which is what the reading
half already does and what the writing half already did for actions. A table
now survives the trip between builds minus whatever they do not have in
common.
* fix(nested-sessions): keep a host's view of the guest fresh across a reconfigure
A reconfigure rewrites a session's keybindings and base mode without going
through change_mode, and keybindings are otherwise only ever sent in answer
to a request. A host that had already asked once therefore kept drawing the
table the guest had before its config was reloaded, with no way of noticing
it had gone stale, and would not have picked up the new base mode until the
user happened to switch modes inside the guest.
Report both from reconfigure, next to the shortcut broadcast that already
happens there. Answer a keybinding request from the requesting client's own
table too, since a reconfigure with write_to_disk unset rebinds only the
client that asked: the mode in the same reply already came from that client,
so the two halves now describe the same thing.
* fix: adjust protocol + API
* Update CHANGELOG.md
---------
Co-authored-by: Aram Drevekenin <[email protected]>
authorizedFetch asks the user for a token every time a request is
rejected, and getSecurityToken appends a dialog to document.body with no
singleton, so concurrent rejections open one dialog each. Authenticating
one does not release the others: each parked call is blocked on its own
await and never re-checks the now-valid cookie.
The standalone session menu surfaces this by polling /session-list every
3s while logged out, stacking a dialog per tick, but the mechanism is not
specific to that caller.
Route rejected callers through one shared pending authentication. The
first owns the prompt, the rest await its result and retry against the
credentials it established.
Requests rejected across an authentication rather than during it need
more than coalescing: a request sent before login is answered against the
credentials of the moment it was sent, so its 401 can arrive once the
shared attempt has already settled and been cleared. Track an
authentication generation, incremented in the same finally that clears
the shared promise, and sample it around each fetch. A rejection that
arrives after the generation advanced predates the credentials now held,
so retry the request rather than prompting again.
app.js, index.html and integrity.json are regenerated by `cargo xtask
assets`.
Refs #5540
* fix: keep single-pane tabs when serializing, stop the busy-spin on IPC disconnect, and make client-less CLI actions either work or fail loudly
* fix: some tab closing/ordering issues
* test: integration coverage for OSC 133 selection, scroll retention, paste-read opt-in, theme actions, and input fragmentation
* rendering-width regression tests
* fix: forward alt+mouse events into a guest pane the client is descended into
* fix: alt + [] delay in non-kitty
* allow disabling mouse hover tips while keeping the other effects
* more osc133 actions
* fix: base mode race when changing keybinding presets
* fix compilation warnings
* make releases drafts and remove diagnostic logging
* add pr
* fix(input): realign raw-byte buffer with the keyboard parser to stop stale bytes eating keystrokes
After a paste (e.g. via a clipboard manager like maccy), zellij's
current_buffer can hold up to a few stale bytes that termwiz's
InputParser has already consumed into events. Those stale bytes are then
attached to the raw payload of subsequent KeyEvent messages, desyncing
each keystroke from the bytes that produced it: keys go missing, come
out garbled, or drop into vi command mode.
The root cause is that parse_with_consumed reports consumption relative
to its internal self.buf, while stdin_handler drains a separately
accumulated current_buffer. The two can drift apart whenever a read
leaves a partial sequence that the parser holds under maybe_more.
Reconcile the two after every parse: trim current_buffer to the
parser's buffered length, so no byte the parser has already decoded is
ever forwarded with a later key event. Add InputParser::buffered_len to
the vendored termwiz for that purpose.
* fix: remove parsed bytes from front rather than back
* rustfmt
---------
Co-authored-by: Aram Drevekenin <[email protected]>
* initial chrome
* mobile state
* feat(mobile-web): implement browser→server actions (fit toggle, single-pane fullscreen, pane/tab focus) with fullscreen-truth reconciliation
* panning
* remove mobile plugin
* fix first load race
* improve web handshake
* rustfmt
* cleanups
* moar cleanups
* add bundle asset check to ci
* fix(mobile-web): tab-scope co-presence, yield single-pane to desktop clients, fix render-mode desync
* make mobile use no-ui fullscreen
* client side welcome screen for mobile
* attach web clients to first tab on tiles surface
* fix: opening new pane in mobile single mode no longer exits mobile single mode
* mobile chrome light theme support
* fix server races and flaky tests
* rustfmt
* fix flakes
* add pr
* upgrade insta, rand, sha2, strum, thiserror, tokio-tungstenite, toml, which; drop unused semver and typetag
* upgrade sysinfo to 0.39.5, bump toolchain (msrv) to 1.95.0
* remote legacy yaml config converter and thus drop serde_yaml
* upgrade unicode-width to 0.2.2
* fix(tests): run ipc roundtrip test on a larger stack
Windows Terminal does not set TERM by default, so zellij on WT was
using the native-console crossterm path that reads INPUT_RECORDs.
That path can't observe bracketed-paste sequences (`\e[200~/\e[201~`),
which is one of the reasons multi-line paste has never worked
correctly on Windows (issue #4885).
WT sets `WT_SESSION` per-process. Use that as a second trigger for
the VT input path so default WT installs get raw VT byte input (and
with it, termwiz's bracketed-paste detection, the SGR mouse format,
correct emoji delivery, etc.) without requiring the user to set TERM.
Introduce `use_vt_path()` in `os_input_output_windows.rs` as the
single source of truth for the gate. Previously the stdin handler
had one TERM check and `enable_mouse_support` / `disable_mouse_support`
each had their own — so adding `WT_SESSION` to just the stdin gate
caused mouse setup to take the native-console branch, which calls
`crossterm::event::EnableMouseCapture` and clobbers the
`ENABLE_VIRTUAL_TERMINAL_INPUT` flag the stdin path had just set.
That broke all input from WT until the helper aligned every site.
* phase 2+3
* initial mobile plugin (phases 4 + 5 + 6)
* some bugfixes
* implement stage 7
* mobile interface improvements
* improve mobile pane/tab picker
* more ui improvements and fixes
* various mobile fixes
* some mobile fixes
* mobile keyboard triggering fix
* some cleanups
* initial plugin keyboard implementation
* improve keyboard hit zones
* improve keyboard
* pinch resize
* pinching fixes
* implement scrolling + panning
* initial keyboard resizing
* fix: mobile first render issues
* keyboard responsiveness
* fix some issues with the keyboard
* implement fit
* fix mobile scrolling
* add tilde cell to compact
* improve mobile keyboard
* fix starting mobile terminal font to natural keyboard size
* fix mobile resize blank screen flash
* shadow focus for mobile
* more keyboard fixes
* improve mobile status bar
* fix: render plugins to mobile clients
* allow opening new panes/tabs from the mobile plugin
* allow configuring
* fix dot slash
* improve mobile native soft keyboard interaction
* switch to native keyboard - initial implementation
* dirty hacks, but phone keyboard works now
* adjust keyboard help strip
* fix cursor in mobile
* fix keyboard and help line
* rename keyboard to modifier bar
* allow starting new session
* make clicking session name bring us to new session menu
* allow switching to desktop from the mobile ui
* mobile welcome screen initial implementation
* mobile welcome screen fixes
* various welcome screen fixes
* more mobile fixes
* moar mobile fixes
* mobile ui redesign
* fix: swallowed backspace bug
* switch session screen improvement
* touch up some interfaces
* refactor: move mobile state to own struct
* refactor: move fit resizing to the server
* remove dead flash code
* some cleanups
* refactor: clear fit on close
* refactor: improve fit api
* refactor: pane sync functions
* refactor: mouse
* refactor: break into modules
* some cleanups for the sessions screen
* fix scrolling and other stuff
* refactor: sessions screen
* refactor: panes screen
* refactor: new session screen
* refactor: menu screen
* refactor: render
* cleanups
* refactor: plugin structure
* remove unused welcome screen path
* Rename api
* refactor input.js
* cleanup useless comments
* fit/mobile refactoring
* take out the garbage
* remove more comments
* remove mobile flash on startup/attach
* fix: various mobile startup issues
* fix space bugs
* cleanup
* rustfmt
* fix windows build
* adjust modifier bar ui
* add pr
* feat: PWA support for the web client
Adds Progressive Web App support so the Zellij web client can be
installed as a standalone app via Chromium/Firefox/Safari/iOS.
- New static asset `zellij-client/assets/manifest.webmanifest` with
required W3C members (name, start_url, scope, display, icons). All
path-bearing fields are relative to the manifest URL so a single
static file works under any reverse-proxy base_url prefix without
runtime templating.
- New icon set under `zellij-client/assets/`: icon.svg (vector),
icon-192.png and icon-512.png (purpose "any"), and
icon-maskable-512.png (purpose "maskable") rendered at 70% of
canvas height on a #080317 backdrop so Android adaptive masks
honour the W3C 80%-diameter safe zone.
- `scripts/render-pwa-icons.sh` pins the rendering toolchain
(librsvg + ImageMagick) and the maskable safe-zone geometry so
re-renders are reproducible.
- `.webmanifest` -> `application/manifest+json` mapping added to
`get_mime_type` in `zellij-client/src/web_client/utils.rs`.
- `index.html` gains <link rel="manifest">, apple-touch-icon, and
apple-mobile-web-app-* meta tags. Placed immediately after the
existing favicon link and before any <script> tag so the manifest
is in the browser's preload scanner early.
- 5 new tests cover Content-Type, required JSON members, the
relative-URL contract, index.html references, icon reachability,
and base_url invariance.
Closes#5094.
* update changelog with pull url
* feat(web): trim PWA icon set to PNGs only
Drops the duplicate `zellij-client/assets/icon.svg` (verbatim copy of
`assets/logo.svg`) and the maskable PNG variant from the manifest.
Net result is the minimal icon set most production PWAs ship:
icon-192.png and icon-512.png.
- `icon.svg` was redundant with the canonical `assets/logo.svg` at
the repo root and offered no install-criterion benefit on top of
the 192/512 PNGs.
- `icon-maskable-512.png` only benefits Android Chrome installs
(desktop browsers and iOS ignore the maskable purpose). The
Android win was small for the hexagonal Zellij glyph; a hand-tuned
maskable can be added in a follow-up if Android installs need
polish.
`scripts/render-pwa-icons.sh` now reads from `assets/logo.svg`
directly instead of the duplicate copy.
* feat(web): use existing assets/logo.png as the PWA icon
Removes the pre-rasterized 192/512 PNG variants and the render
script in favour of serving the canonical `assets/logo.png` via a
small dedicated handler. The repo's existing logo becomes the single
source of truth; the PWA picks up logo changes automatically with no
re-render step.
- `LOGO_PNG` static is loaded via `include_bytes!` from the repo
root, mirroring how `WEB_CLIENT_PAGE` reaches `assets/index.html`.
- New `serve_logo_png` handler is registered at `/assets/logo.png`
before the `get_static_asset` wildcard. axum's matchit router
gives literal paths precedence, so the explicit route always wins.
- Manifest declares a single icon entry at the actual file dimensions
(2269x2620). Browsers letterbox onto square launcher surfaces, the
same way they do for the existing favicon.
- `apple-touch-icon` retargets to `assets/logo.png`.
- `test_pwa_icon_assets_reachable` collapses to a single case.
- `scripts/render-pwa-icons.sh` and the rendering toolchain
(librsvg + ImageMagick) are no longer needed.
* feat(web): drop redundant apple-mobile-web-app meta tags
The three `apple-mobile-web-app-*` meta tags are backward-compat
scaffolding for iOS < 16.4:
- `apple-mobile-web-app-capable: yes` — iOS 16.4+ reads
`display: standalone` from the manifest.
- `apple-mobile-web-app-title: Zellij` — iOS 16.4+ reads `short_name`
from the manifest.
- `apple-mobile-web-app-status-bar-style: default` — explicitly sets
iOS's default status-bar behaviour, i.e. a no-op.
iOS 16.4 shipped in March 2023; the cohort still on iOS 15 is
essentially devices stuck on the iPhone 6s / 7 / SE 1st gen line
(2015-16 hardware), which is not the zellij audience.
`<link rel="apple-touch-icon">` is kept because iOS still ignores
manifest `icons` entries and reads only that tag for the home-screen
icon on every iOS version.
* feat(web): restore square pre-rasterized PNG icon for installability
The earlier swap to serving `assets/logo.png` directly fails
Chromium's PWA installability gate: the logo is 2269x2620 (not
square), and Chromium specifically requires "an icon at least 144
pixels square" -- a non-square icon is rejected even though the
declared `sizes` covers the dimension threshold. DevTools'
Application -> Manifest panel was misleading: it parsed and
displayed the icon, but installability is a separate stricter check.
This walks the swap back to a single committed 192x192 square PNG
(`icon-192.png`) rendered from `assets/logo.svg` with the logo
composited onto a transparent square canvas. The PNG is served by
the existing `get_static_asset` route via `include_dir!`. The
`LOGO_PNG` static, `serve_logo_png` handler, and dedicated
`/assets/logo.png` route are dropped.
apple-touch-icon retargets to icon-192.png (closer to iOS's
preferred 180px home-screen size).
Net effect on the diff: ~13KB committed PNG + a single icon entry
in the manifest, in exchange for actually meeting the
installability criterion.
* style: rustfmt
---------
Co-authored-by: Robert Jin <[email protected]>
Co-authored-by: Aram Drevekenin <[email protected]>
The workspace `windows-sys` constraint was pinned to 0.52 while the
Windows source uses APIs and namespaces from 0.59 (e.g. WriteFile in
Win32::Storage::FileSystem). CI built fine because Cargo.lock pinned
0.59, but downstream `cargo install`/`cargo binstall` did fresh
resolution and selected 0.52, breaking the build for users on Windows.
Align the manifest with the code:
- Bump workspace dep `windows-sys` from 0.52 to 0.59.
- HANDLE is now `*mut c_void` instead of `isize`: replace
`handle == 0` / `!= 0` with `is_null()`, initialise HANDLE locals
with `std::ptr::null_mut()`, and pass `null_mut()` as the
hTemplateFile argument to CreateFileW.
- Pass the process HANDLE through the exit-monitor thread as `usize`
(raw pointers are not Send; OS handles are safe to use cross-thread).
- Note: HPCON is still `isize` in 0.59, so its zero-init is unchanged.
* fix(web-client): prevent IME input text repetition
Fix bug where composing multi-character strings via IME (e.g. Chinese input method) would write the entire string N times for N characters.
Previously, parse_stdin sent the entire input buffer as raw_bytes for each KeyEvent parsed from that buffer. When IME composes "你好" (2 chars), InputParser creates 2 KeyEvents, but both carried the full "你好" bytes, causing it to be written twice.
Now, when multiple events are parsed from one buffer, each Char KeyEvent carries only its own UTF-8 bytes. Single-event buffers (most ASCII input) remain unchanged.
Fixes#4974
* fix: guard per-char raw_bytes with modifiers check
Only use per-character UTF-8 encoding for unmodified Char events in multi-event buffers. Modified keys (e.g. Ctrl+C) retain the original buffer to preserve correct VT bytes. Also clarify the comment.
* test(web-client): cover IME raw byte handling
* rustfmt
---------
Co-authored-by: Aram Drevekenin <[email protected]>
* fix: kitty keyboard cleanup on error path + 2026 DECRPM state 3 handling
lib.rs: handle_error now exits kitty keyboard protocol (ESC[<1u) before
printing the backtrace, matching the normal exit path. Previously the
kitty keyboard mode was left active on crash, corrupting subsequent
terminal input.
stdin_ansi_parser.rs: synchronized_output_from_bytes now treats DECRPM
state 3 (permanently set) as enabled, matching states 1 and 2. State 3
is a valid "supported and on" response that was previously falling through
to None (treated as unsupported).
* docs: update changelog
* refactor(client): unify terminal teardown/setup sequences
- Extract named constants for all terminal escape sequences
- Add terminal_teardown_message() helper used by all three exit paths,
fixing several pre-existing inconsistencies in the process:
- handle_error was missing RESET_STYLE and SHOW_CURSOR
- remote client had kitty keyboard exit after EXIT_ALTERNATE_SCREEN
instead of before
- goodbye path had goto positioned in the alt screen before exit
- RESET_STYLE/SHOW_CURSOR are now sent after EXIT_ALTERNATE_SCREEN
so they take effect in the normal screen
- Replace write() with write_all() throughout; drop redundant let _ =
on unwrap() calls
* docs: update changelog
* fix(web-client): use base URL when switching sessions
Session switch redirected to /{session_name} instead of
{base_url}/{session_name}, breaking reverse proxy setups.
Use getBaseUrl() to construct the redirect URL, consistent with
how auth.js and connection.js already handle base URL.
Fixes#4980
Co-authored-by: Copilot <[email protected]>
* fix(web-client): encode session name in redirect URL
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
* docs: update changelog
---------
Co-authored-by: Copilot <[email protected]>
Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
Co-authored-by: Thomas Linford <[email protected]>
- Consume unused CompilationResult from embed_resource::compile in build.rs
- Gate std::io::prelude import with #[cfg(unix)] since it's only used by
daemonize_web_server which is unix-only
Co-authored-by: divens <[email protected]>
* initial implementation
* tests
* fix: always report to zellij, only report to app if it wants a report
* rustfmt
* fix: always report capabilities to panes requesting them
* rustfmt
* add pr
* fix: do not unblock blocking commands for other unblock conditions
* update deps
* allow trailing : in file path detection and change log format to match
Use cmd.status() instead of cmd.output() on Windows to prevent pipe
handle inheritance from blocking indefinitely. cmd.output() creates
pipe handles that the grandchild web server process inherits via
CreateProcess bInheritHandles, keeping them open and preventing EOF.
Co-authored-by: divens <[email protected]>