Skip to content

Add project grouping to dashboard#16

Open
ClSlaid wants to merge 6 commits into
hueyexe:mainfrom
ClSlaid:feature/project-dashboard
Open

Add project grouping to dashboard#16
ClSlaid wants to merge 6 commits into
hueyexe:mainfrom
ClSlaid:feature/project-dashboard

Conversation

@ClSlaid
Copy link
Copy Markdown

@ClSlaid ClSlaid commented May 16, 2026

Result

image

Problem

OpenCode Ensemble currently treats teams as globally scoped in the dashboard and several maintenance paths. That creates two practical issues once a user runs multiple OpenCode instances or works across multiple project directories:

  • The dashboard has no project grouping, so teams from different working directories appear in one flat list and same-name teams are hard to distinguish.
  • Recovery/cleanup code can accidentally operate across project boundaries because team names were globally unique assumptions in parts of the code.

Solution

  • Add a lightweight project layer keyed by the team lead's working directory.
  • Scope active team-name uniqueness to project_id instead of globally.
  • Return projects[].teams from /api/state while keeping the existing flat teams array for compatibility.
  • Add a collapsible docs-style project outline to the dashboard with coarse project/team status and hover details.
  • Scope recovery and purge safety checks so duplicate team names across projects do not cause ambiguous cleanup or branch deletion.
  • Allow team_create to accept an optional project_name; otherwise generate a short random display name.
  • Make database migrations atomic so failed cross-version upgrades roll back cleanly.

Verification

  • bun run typecheck
  • bun test
  • bun run build

@ClSlaid ClSlaid requested a review from hueyexe as a code owner May 16, 2026 01:30
Comment thread src/tools/team-create.ts
// Check if team name already exists
const existing = deps.db.query("SELECT id FROM team WHERE name = ? AND status = 'active'").get(args.name)
const projectId = deps.directory
const existing = deps.db.query("SELECT id FROM team WHERE name = ? AND project_id = ? AND status = 'active'").get(args.name, projectId)
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Same-name teams are now allowed across projects, but branch/worktree names remain global. team_spawn still builds worktree names from only teamName/memberName, and preservedBranchName() still writes to ensemble/preserved/${teamName}/${memberName}. Two projects can now both create my-team/alice, but both would target the same worktree/preserved branch namespace.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Suggested fix: use a stable team-scoped identifier in branch/worktree names rather than the display team name. team.id seems safer than team.name because names are now only unique within a project.
For example:

  • worktree name: ensemble-${teamId}-${memberName}
  • preserved branch: ensemble/preserved/${teamId}/${memberName}

Then update the preservation/listing call sites to use that same namespace.

Comment thread src/tools/team-cleanup.ts

if (purge.includes("*")) {
return deps.db.query("SELECT id, name, time_updated FROM team WHERE status = 'archived' ORDER BY time_updated DESC, name ASC")
return deps.db.query("SELECT id, name, project_id, time_updated FROM team WHERE status = 'archived' ORDER BY time_updated DESC, name ASC")
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

purge: ['*'] still selects archived teams globally, but the branch cleanup later runs using the current deps.directory. That can purge archived team records from other projects while listing/deleting preserved branches in the current project’s repo path.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Suggestion: scope purge target resolution to the current project unless cross-project purge is explicitly intended.

For purge: ['*'] - filter archived teams with project_id = deps.directory. For explicit names, you could either apply the same current project filter or add an explicit project selector. If crossproject purge remains supported the preview should show project/path and branch cleanup should run against each target project path instead of always using the current deps.directory..

Comment thread src/schema.ts
time_updated INTEGER NOT NULL,
lead_agent TEXT
);
INSERT INTO team (id, name, project_id, lead_session_id, status, delegate, time_created, time_updated, lead_agent)
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Existing teams are migrated into project "default", but startup recovery now filters stale members by input.directory. Pre-migration teams stuck in busy can be skipped after upgrade because their project_id is "default" rather than the current directory.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

handle legacy "default" teams during recovery;

A conservative option is to let start up recovery include project_id = 'default' as a legacy fallback when filtering by input.directory. a more complete option is to migrate active "default" teams into the current project on first startup if there is no ambiguity.

A regression test should cover a version-7 DB with a busy member migrated to "default" -- then startup recovery from a real project dir.

@hueyexe hueyexe added the enhancement New feature or request label May 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants