Skip to content

Add embers backend#4

Open
Pajn wants to merge 4 commits into
mainfrom
embers
Open

Add embers backend#4
Pajn wants to merge 4 commits into
mainfrom
embers

Conversation

@Pajn
Copy link
Copy Markdown
Owner

@Pajn Pajn commented May 24, 2026

Summary by CodeRabbit

  • New Features

    • Optional Embers backend added alongside tmux, with automatic backend selection and embers-aware UI (picker, sidebar, popups); statusline remains tmux-only
    • Embers client/library exposes session/buffer operations and preview capture used by the UI
  • Documentation

    • README, config docs, and schema updated with backend/embers usage, env vars, and quickstart notes
  • Tests

    • New smoke and integration tests covering Embers scenarios
  • Chores

    • Rust toolchain and CI pinned to 1.95.0

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 24, 2026

📝 Walkthrough

Walkthrough

This PR adds optional Embers multiplexer backend support to Wisp alongside the existing tmux integration. It introduces a new wisp-embers client library, a configurable backend system with auto-detection, and refactors the CLI to dispatch operations (session activation, preview, sidebar handling) per-backend while maintaining tmux-only features like statusline integration.

Changes

Embers Backend Support

Layer / File(s) Summary
Workspace, toolchain, and CI
Cargo.toml, rust-toolchain.toml, .github/workflows/*
Add wisp-embers to workspace dependencies/exclude, pin Rust toolchain to 1.95.0, and consolidate system dependency installation in CI.
Configuration types and docs
crates/wisp-config/src/lib.rs, docs/config.schema.toml, docs/configuration.md
Add BackendKind/BackendConfig, partial-config merging, WISP_BACKEND/WISP_EMBERS_SOCKET env overrides, and docs/schema entries for embers options.
wisp-embers crate (client library)
crates/wisp-embers/Cargo.toml, crates/wisp-embers/src/lib.rs, crates/wisp-embers/tests/*
New embers client exposing EmbersClient API (connect, snapshot, session/buffer ops, previews), snapshot data models, error types, helpers (geometry, node traversal), and integration/unit tests.
App domain: BackendSnapshot and domain builder
crates/wisp-app/src/lib.rs, crates/wisp-app/Cargo.toml
Introduce BackendSnapshot enum, change CandidateSources to backend: BackendSnapshot, refactor build_domain_state to dispatch tmux/embers builders and use native_id.
Binary: runtime backend and sidebar integration
crates/wisp-bin/Cargo.toml, crates/wisp-bin/src/main.rs
Add RuntimeBackend (tmux/embers), backend resolution/loading, embers-aware preview provider and picker polling, SidebarTarget abstraction, embers join/reload flows, backend-aware session ops, and statusline rejection for embers.
Preview & core adjustments
crates/wisp-preview/src/lib.rs, crates/wisp-core/src/*
Rename preview error variant to SessionCapture, map capture failures accordingly, and replace tmux_id with native_id across domain/view/reduce/benches and tests.
Smoke tests and README
crates/wisp-bin/tests/smoke.rs, README.md
Add smoke tests for embers doctor/statusline behaviors and update README with embers build/install instructions, capability notes, and quality gates.

🎯 4 (Complex) | ⏱️ ~60 minutes

🐰 A new backend hops into view,
Embers dancing alongside tmux true,
Config flows and clients dance the same,
Sessions switch, buffers join the game,
Wisp now speaks two multiplexer tongues!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.52% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title "Add embers backend" directly describes the primary change: introducing a new backend (embers) alongside the existing tmux backend throughout the codebase.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/wisp-app/src/lib.rs`:
- Around line 479-481: Rename the misleading SessionRecord::tmux_id field to a
generic name (e.g., native_id or backend_id) and update all places that
construct or access it: change the struct definition in wisp-core and update the
constructor call in crates/wisp-app/src/lib.rs (the SessionRecord literal where
tmux_id: Some(session.native_id.clone()) is set), adapters that populate/read
this field from backends (tmux and Embers), any serialization/deserialization or
DB mapping code, and tests/examples; ensure matching Option types (Some/None)
and update imports/usages to the new symbol name so compilation and runtime
behavior remain the same.

In `@crates/wisp-bin/src/main.rs`:
- Around line 1339-1405: The UI loop contains fallible backend calls
(sidebar_requires_handoff, persist_sidebar_ui_state,
reconcile_sidebar_for_current_context,
client.join_buffer_to_current_session_root, runtime.rename_session,
reload_sidebar_runtime_state) that can early-return and skip terminal teardown;
refactor so terminal raw-mode/alternate-screen cleanup is always executed on any
early exit by moving these backend operations into a fallible helper that
returns Result and ensuring the outer loop uses an RAII guard or explicit
finally-style cleanup (e.g., a TerminalTeardown guard constructed before
entering the loop or calling a restore function in a match on the helper's
Result) so that even when those functions return Err the terminal teardown runs.
Ensure references to sidebar_runtime, backend, and runtime/session mutation
remain valid across this change.
- Around line 1141-1154: The buffer is created with client.create_buffer and
then a follow-up call like client.create_floating_for_buffer_in_current_session
may fail and leave an orphaned buffer; wrap the follow-up call so that on error
you clean up the newly created buffer (use the client method that removes
buffers such as client.delete_buffer or the repo's equivalent) before returning
the error, and apply the same rollback pattern to the other two occurrences (the
sidebar-popup float path and the sidebar-pane join path referenced in the
comment); make sure to reference buffer_id from the create_buffer call and call
the remove/delete API with that id in the error arm.
- Line 2520: The worker loop currently uses queue.lock().ok() which swallows a
poisoned mutex and causes the worker to exit silently; change the lock handling
in the loop that reads from queue (the while let Some(work_item) =
queue.lock().ok().and_then(|mut queue| queue.pop_front()) { ... }) to explicitly
handle PoisonError: if lock() returns Ok(guard) use it; if Err(poison) recover
with poison.into_inner() (or otherwise obtain the inner guard), log the poison
error with context (e.g., which git worker and a descriptive message) and
continue processing so pending git status work is not dropped; ensure you still
call queue.pop_front() on the recovered guard and keep the rest of the loop
logic unchanged.

In `@rust-toolchain.toml`:
- Line 2: The pinned Rust toolchain in rust-toolchain.toml (channel = "1.95.0")
is inconsistent with the workspace MSRV declared as rust-version = "1.90" in the
root Cargo.toml; decide which MSRV to use and make them match: either update the
root Cargo.toml rust-version to "1.95" (or "1.95.0") if the code/deps require
newer Rust, or change rust-toolchain.toml channel to "1.90" (or "1.90.0") if
1.90 is intended; ensure both the rust-toolchain.toml channel and the root
Cargo.toml rust-version fields are identical after the change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0d46f70c-eb23-404a-9a83-f7431bc3c155

📥 Commits

Reviewing files that changed from the base of the PR and between 3f2619e and dc5af98.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (16)
  • Cargo.toml
  • README.md
  • crates/wisp-app/Cargo.toml
  • crates/wisp-app/src/lib.rs
  • crates/wisp-bin/Cargo.toml
  • crates/wisp-bin/src/main.rs
  • crates/wisp-bin/tests/smoke.rs
  • crates/wisp-config/src/lib.rs
  • crates/wisp-core/src/view.rs
  • crates/wisp-embers/Cargo.toml
  • crates/wisp-embers/src/lib.rs
  • crates/wisp-embers/tests/integration.rs
  • crates/wisp-preview/src/lib.rs
  • docs/config.schema.toml
  • docs/configuration.md
  • rust-toolchain.toml

Comment thread crates/wisp-app/src/lib.rs Outdated
Comment thread crates/wisp-bin/src/main.rs
Comment thread crates/wisp-bin/src/main.rs
Comment thread crates/wisp-bin/src/main.rs Outdated
Comment thread rust-toolchain.toml
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
crates/wisp-embers/src/lib.rs (1)

405-426: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Don’t detach the buffer before the cross-session join is known to succeed.

If JoinBufferAtNode fails after detach_buffer_record, the buffer is left unattached and disappears from the original session. This needs either an atomic server-side move or explicit rollback using the original location.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/wisp-embers/src/lib.rs` around lines 405 - 426, The code detaches the
buffer via detach_buffer_record(runtime, state, buffer_id) before issuing the
cross-session request JoinBufferAtNode, which can leave the buffer unattached if
the request fails; change the flow in the join logic so you first record the
original location (using buffer_location), then perform the remote request
(runtime.block_on(state.client.request_message(...)) calling
NodeRequest::JoinBufferAtNode) and only call detach_buffer_record when the
request and apply_session_layout_response(...) succeed; alternatively, if you
must detach first implement an explicit rollback that reattaches the original
location on any failure by using the saved original location to restore the
buffer record.
crates/wisp-bin/src/main.rs (1)

1336-1337: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Stop hard-coding default for Embers projections.

build_embers_state stores client focus under snapshot.context.client_id, but this path always rebuilds session items with Some(DEFAULT_CLIENT_ID). On a real Embers snapshot that means state.current_session_id(...) and state.previous_session_id(...) never resolve, so current/previous markers break. The same hard-coded key is reused in the later reload paths too.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/wisp-bin/src/main.rs` around lines 1336 - 1337, The call to
rebuild_session_items_for_picker_mode is hard-coded with
Some(DEFAULT_CLIENT_ID), which prevents session lookup from using the actual
Embers snapshot client id (snapshot.context.client_id) and breaks
state.current_session_id/previous_session_id resolution; change the call site(s)
to pass the real client id from the state/snapshot (e.g., use the client id
stored at snapshot.context.client_id via the current state object) instead of
DEFAULT_CLIENT_ID, and update any other reload paths that reuse
DEFAULT_CLIENT_ID to similarly forward the snapshot.context.client_id so
current/previous markers resolve correctly.
.github/workflows/ci.yml (1)

47-48: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Run the Embers tests in CI.

This workflow only executes default-feature tests, so the new #[cfg(feature = "embers")] coverage added in this PR never runs. cargo clippy --all-features only proves the code compiles; it does not execute those tests.

Suggested change
-      - name: Run tests
-        run: cargo test --workspace --all-targets
+      - name: Run tests
+        run: cargo test --workspace --all-targets --all-features
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 47 - 48, The CI step named "Run tests"
currently runs tests without feature flags; update its command so embers-enabled
tests execute by running cargo test with all features and targets (e.g. replace
`cargo test --workspace --all-targets` with `cargo test --workspace
--all-targets --all-features`) so that `#[cfg(feature = "embers")]` tests are
exercised in CI.
♻️ Duplicate comments (1)
crates/wisp-embers/src/lib.rs (1)

667-695: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Clean up the temporary root buffer when AddRootTab fails.

This creates a buffer and then performs a second request. If the second step fails, the buffer is orphaned in the mux and in the local cache.

Suggested rollback pattern
     let buffer_id = create_buffer_record(
         runtime,
         state,
         &command,
         &title,
         Some(directory),
         &BTreeMap::new(),
     )?;
     let response = runtime.block_on(state.client.request_message(ClientMessage::Session(
         SessionRequest::AddRootTab {
             request_id: state.client.next_request_id(),
             session_id,
             title,
             buffer_id: Some(buffer_id),
             child_node_id: None,
         },
     )))?;
     match response {
         ServerResponse::SessionSnapshot(response) => {
             state
                 .client
                 .state_mut()
                 .apply_session_snapshot(response.snapshot);
             Ok(())
         }
-        _ => Err(EmbersError::UnexpectedResponse("add root tab")),
+        _ => {
+            let _ = runtime.block_on(state.client.request_message(ClientMessage::Buffer(
+                BufferRequest::Kill {
+                    request_id: state.client.next_request_id(),
+                    buffer_id,
+                    force: true,
+                },
+            )));
+            Err(EmbersError::UnexpectedResponse("add root tab"))
+        }
     }
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In @.github/workflows/ci.yml:
- Around line 47-48: The CI step named "Run tests" currently runs tests without
feature flags; update its command so embers-enabled tests execute by running
cargo test with all features and targets (e.g. replace `cargo test --workspace
--all-targets` with `cargo test --workspace --all-targets --all-features`) so
that `#[cfg(feature = "embers")]` tests are exercised in CI.

In `@crates/wisp-bin/src/main.rs`:
- Around line 1336-1337: The call to rebuild_session_items_for_picker_mode is
hard-coded with Some(DEFAULT_CLIENT_ID), which prevents session lookup from
using the actual Embers snapshot client id (snapshot.context.client_id) and
breaks state.current_session_id/previous_session_id resolution; change the call
site(s) to pass the real client id from the state/snapshot (e.g., use the client
id stored at snapshot.context.client_id via the current state object) instead of
DEFAULT_CLIENT_ID, and update any other reload paths that reuse
DEFAULT_CLIENT_ID to similarly forward the snapshot.context.client_id so
current/previous markers resolve correctly.

In `@crates/wisp-embers/src/lib.rs`:
- Around line 405-426: The code detaches the buffer via
detach_buffer_record(runtime, state, buffer_id) before issuing the cross-session
request JoinBufferAtNode, which can leave the buffer unattached if the request
fails; change the flow in the join logic so you first record the original
location (using buffer_location), then perform the remote request
(runtime.block_on(state.client.request_message(...)) calling
NodeRequest::JoinBufferAtNode) and only call detach_buffer_record when the
request and apply_session_layout_response(...) succeed; alternatively, if you
must detach first implement an explicit rollback that reattaches the original
location on any failure by using the saved original location to restore the
buffer record.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 7fb83e26-3343-47df-aa50-f3bc219750af

📥 Commits

Reviewing files that changed from the base of the PR and between dc5af98 and c8f3562.

📒 Files selected for processing (14)
  • .github/workflows/ci.yml
  • .github/workflows/prepare-release.yml
  • .github/workflows/release.yml
  • Cargo.toml
  • crates/wisp-app/src/lib.rs
  • crates/wisp-bin/src/main.rs
  • crates/wisp-core/benches/projections.rs
  • crates/wisp-core/src/domain.rs
  • crates/wisp-core/src/reduce.rs
  • crates/wisp-core/src/view.rs
  • crates/wisp-embers/Cargo.toml
  • crates/wisp-embers/src/lib.rs
  • crates/wisp-preview/src/lib.rs
  • rust-toolchain.toml

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant