Skip to content

Scope home launcher to caller workspace group - #17

Merged
lawrencecchen merged 2 commits into
mainfrom
feat-group-workspace-scope
Jun 23, 2026
Merged

lawrencecchen merged 2 commits into
mainfrom
feat-group-workspace-scope

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Jun 23, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • detect when cmux-home is launched from a grouped workspace
  • filter refreshes and pending rows to that group
  • pass group_id and group_placement=top to workspace.create, with fallback for older cmux builds

Verification

  • cargo test
  • ./scripts/cmux-home.sh --help

Paired cmux app PR: manaflow-ai/cmux#6657


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.


Summary by cubic

Scopes the home launcher to the caller’s workspace group so you only see and create workspaces in that group. Group lookup is routed through the caller workspace; new workspaces are placed at the top of the group with a fallback for older cmux builds.

  • New Features
    • Routes group lookup through the caller (CMUX_WORKSPACE_REF/CMUX_WORKSPACE_ID) using workspace.group.list, and builds a WorkspaceGroupScope.
    • Filters refreshes, lists, and single-workspace loads to group members; hides out-of-scope items and pending rows.
    • Sends group_id and group_placement=top on workspace.create; falls back to workspace.group.add + workspace.reorder relative to the group anchor if needed.
    • Adds group_id to WorkspaceStatus and threads scope through refresh, submit, created-event handling, and optimistic rows.

Written for commit 248453f. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features
    • Added end-to-end workspace group scoping in the TUI: workspace lists are filtered to your active group scope, and group visibility rules were expanded accordingly.
    • New workspaces now resolve and submit with the correct group placement; if the created workspace lands in a different group, it’s added/reordered to match the intended group.
    • Workspace refresh and optimistic updates now preserve and respect group scope, including reacting to workspace creation events.
  • Tests
    • Updated and extended unit tests for scoped refresh filtering, pending-row retention, and group-scope derivation.

@coderabbitai

coderabbitai Bot commented Jun 23, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 53cf3733-dbb9-42b7-b950-65fe5b18625e

📥 Commits

Reviewing files that changed from the base of the PR and between 51b3c43 and 248453f.

📒 Files selected for processing (1)
  • src/main.rs

📝 Walkthrough

Walkthrough

A new WorkspaceGroupScope type is introduced and threaded through the App struct, RefreshSnapshot, SubmitRequest, SubmitSuccess, and WorkspaceStatus. The refresh pipeline resolves group scope per-request, filters workspace lists to group members, and attaches group_id to each status. Visibility rules and the workspace.created event handler respect the active scope. The submit pipeline resolves group scope lazily, passes it through optimistic upserts and the API call, and uses add_workspace_to_group_top to reorder a workspace if it lands in the wrong group.

Changes

Workspace Group Scope Feature

Layer / File(s) Summary
WorkspaceGroupScope type and group_id field on core structs
src/model.rs, src/events.rs, src/main.rs
Adds group_id: Option<String> to WorkspaceStatus, defines WorkspaceGroupScope with membership helpers, and adds group_scope fields to App, RefreshSnapshot, SubmitRequest, and SubmitSuccess.
caller_group_scope resolution helpers
src/main.rs
Adds caller_group_scope, group_scope_for_workspace_keys, current_workspace_route_key, and workspace_group_list_params which call workspace.group.list and derive a WorkspaceGroupScope from caller workspace IDs/refs.
Refresh pipeline: scope computation, load filtering, apply_refresh
src/main.rs
Refresh worker computes caller_group_scope and passes it into load_workspaces/load_workspace, which filter list results to group members and attach group_id to WorkspaceStatus. apply_refresh updates self.group_scope and filters pending rows to in-scope only.
Visibility filtering and workspace.created event
src/main.rs
should_hide_workspace calls new workspace_is_in_scope to hide out-of-scope workspaces. The workspace.created event forces a full "All" refresh when group_scope is set.
Submit pipeline: scope resolution, optimistic status, API call, group reorder
src/main.rs
submit_new_workspace lazily resolves group_scope and threads it through upsert_optimistic_workspace, optimistic_workspace_status, submit_request_inner (sets group_id/group_placement on workspace.create), and add_workspace_to_group_top for post-creation reorder when the workspace lands in a different group.
Test updates
src/main.rs
Existing tests updated for group_scope: None fields and new group_id parameter. New tests cover group-scoped refresh filtering, pending-row retention within scope, and group-scope resolution from caller workspace refs.

Sequence Diagram

sequenceDiagram
  actor TUI as TUI (App)
  participant SubmitFlow as submit_new_workspace
  participant ScopeResolver as caller_group_scope
  participant API as workspace API
  participant GroupAPI as workspace.group.list
  participant RefreshWorker
  participant LoadWorkspaces as load_workspaces

  rect rgba(100, 149, 237, 0.5)
    Note over TUI, GroupAPI: Submit path
    TUI->>SubmitFlow: create new workspace
    SubmitFlow->>ScopeResolver: resolve group_scope if unset
    ScopeResolver->>GroupAPI: workspace.group.list
    GroupAPI-->>ScopeResolver: group membership data
    ScopeResolver-->>SubmitFlow: WorkspaceGroupScope
    SubmitFlow->>TUI: upsert_optimistic_workspace(group_id)
    SubmitFlow->>API: workspace.create(group_id, placement)
    API-->>SubmitFlow: created workspace
    SubmitFlow->>API: add_workspace_to_group_top (if group mismatch)
    SubmitFlow->>TUI: apply_submit_success(group_scope)
  end

  rect rgba(60, 179, 113, 0.5)
    Note over TUI, LoadWorkspaces: Refresh path
    TUI->>RefreshWorker: trigger refresh
    RefreshWorker->>ScopeResolver: caller_group_scope
    ScopeResolver->>GroupAPI: workspace.group.list
    GroupAPI-->>ScopeResolver: group membership data
    ScopeResolver-->>RefreshWorker: WorkspaceGroupScope
    RefreshWorker->>LoadWorkspaces: load_workspaces(group_scope)
    LoadWorkspaces->>LoadWorkspaces: filter by group membership
    LoadWorkspaces-->>RefreshWorker: WorkspaceStatus list with group_id
    RefreshWorker->>TUI: apply_refresh(RefreshSnapshot with group_scope)
    TUI->>TUI: update self.group_scope, filter pending rows
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • manaflow-ai/cmux-home#9: Both PRs modify src/main.rs workspace-loading/refresh paths (notably load_workspaces/refresh logic) in ways that change what WorkspaceStatus data is produced/retained, so the changes overlap at the code level despite different feature goals.

Poem

🐇 Hoppity-hop through the groups I go,
Filtering workspaces, high and low!
A group_id here, a scope right there,
The TUI only shows what's within my lair.
With anchors and members neatly aligned,
No out-of-scope workspace left behind! 🌿

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 26.83% 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 title 'Scope home launcher to caller workspace group' clearly and concisely summarizes the main objective of the changeset, which is to introduce workspace group scoping functionality throughout the cmux-home TUI application.
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.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat-group-workspace-scope

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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 `@src/main.rs`:
- Around line 643-650: The apply_refresh method and other group scope lookup
operations use `.ok().flatten()` on `caller_group_scope(...)` which silently
treats errors the same as "no group available". Instead of discarding errors,
check if the error is a known "groups unsupported" compatibility case and only
treat it as "no group" in that specific case; for other errors, preserve the
existing scope and surface the error appropriately. Apply this change to all
occurrences of the group scope lookup pattern in the apply_refresh function and
at the other locations mentioned (around lines 1072-1075 and 5586-5603) to
prevent unintended scope clearing or creation of ungrouped workspaces from
grouped launchers.
- Around line 5449-5462: The current implementation doesn't safely handle
parameter compatibility and post-create failures in the workspace creation flow.
When calling client.v2("workspace.create", params) with group_id and
group_placement, an older cmux version may reject these parameters causing the
entire operation to fail before retry. Additionally, if workspace.create
succeeds but the subsequent add_workspace_to_group_top call fails, the function
returns an error after the workspace already exists, leading to duplication on
retry. Fix this by implementing a retry mechanism that catches explicit
unsupported-parameter errors from the initial workspace.create call and retries
without the group_id and group_placement parameters, and handle any failures
from add_workspace_to_group_top after successful workspace creation as a
partial-success case (such as logging a warning or triggering a refresh) rather
than propagating the error.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: ce7b055b-4fee-4dd2-a07f-f5cea1b6e2c6

📥 Commits

Reviewing files that changed from the base of the PR and between 4b1da6b and 51b3c43.

📒 Files selected for processing (3)
  • src/events.rs
  • src/main.rs
  • src/model.rs

Comment thread src/main.rs
Comment on lines 643 to +650
fn apply_refresh(&mut self, snapshot: RefreshSnapshot) {
let previously_selected_id = self.selected_workspace().map(|ws| ws.id.clone());
self.group_scope = snapshot.group_scope;
let pending_workspaces = self
.workspaces
.iter()
.filter(|workspace| is_pending_workspace_id(&workspace.id))
.filter(|workspace| self.workspace_is_in_scope(workspace))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Don’t fail open when group scope lookup errors.

caller_group_scope(...).ok().flatten() treats a workspace.group.list failure the same as “no group”. On refresh, that loads the global workspace list and Line 645 clears the active scope; on submit, it can create an ungrouped workspace from a grouped launcher. Preserve the existing scope or surface the lookup error unless the response is a known “groups unsupported” compatibility case.

Also applies to: 1072-1075, 5586-5603

🤖 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 `@src/main.rs` around lines 643 - 650, The apply_refresh method and other group
scope lookup operations use `.ok().flatten()` on `caller_group_scope(...)` which
silently treats errors the same as "no group available". Instead of discarding
errors, check if the error is a known "groups unsupported" compatibility case
and only treat it as "no group" in that specific case; for other errors,
preserve the existing scope and surface the error appropriately. Apply this
change to all occurrences of the group scope lookup pattern in the apply_refresh
function and at the other locations mentioned (around lines 1072-1075 and
5586-5603) to prevent unintended scope clearing or creation of ungrouped
workspaces from grouped launchers.

Comment thread src/main.rs
Comment on lines +5449 to +5462
if let Some(group_scope) = &request.group_scope {
params["group_id"] = json!(&group_scope.group_id);
params["group_placement"] = json!("top");
}

let mut client = CmuxClient::new(request.socket_path.clone());
let created = client.v2("workspace.create", params)?;
let workspace_id = string_field(&created, "workspace_id")
.ok_or_else(|| anyhow!("workspace.create did not return workspace_id"))?;
if let Some(group_scope) = &request.group_scope {
let created_group_id = string_field(&created, "group_id");
if created_group_id.as_deref() != Some(group_scope.group_id.as_str()) {
add_workspace_to_group_top(&mut client, &workspace_id, group_scope)?;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Make the group fallback safe around the non-idempotent create.

The fallback only runs after workspace.create succeeds. If an older cmux rejects group_id/group_placement, submit fails before retrying without those params. Conversely, if create succeeds but workspace.group.add fails, this returns Err after the workspace already exists, so the UI restores the draft and a retry can duplicate the workspace. Retry only before a workspace is created for explicit unsupported-param errors, and treat post-create grouping failures as a partial-success path with a refresh/warning or a real compensating action.

Also applies to: 5478-5490

🤖 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 `@src/main.rs` around lines 5449 - 5462, The current implementation doesn't
safely handle parameter compatibility and post-create failures in the workspace
creation flow. When calling client.v2("workspace.create", params) with group_id
and group_placement, an older cmux version may reject these parameters causing
the entire operation to fail before retry. Additionally, if workspace.create
succeeds but the subsequent add_workspace_to_group_top call fails, the function
returns an error after the workspace already exists, leading to duplication on
retry. Fix this by implementing a retry mechanism that catches explicit
unsupported-parameter errors from the initial workspace.create call and retries
without the group_id and group_placement parameters, and handle any failures
from add_workspace_to_group_top after successful workspace creation as a
partial-success case (such as logging a warning or triggering a refresh) rather
than propagating the error.

@lawrencecchen
lawrencecchen merged commit f43472e into main Jun 23, 2026
4 checks passed
@lawrencecchen
lawrencecchen deleted the feat-group-workspace-scope branch June 23, 2026 06:57
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