feat: projects → workspaces → tabs

This commit is contained in:
Mohamed Boudra
2026-03-01 18:22:11 +07:00
parent 2e201cab09
commit 080fb74642
43 changed files with 4054 additions and 2654 deletions
+448
View File
@@ -0,0 +1,448 @@
# Projects → Workspaces → Tabs (Full-Stack Plan)
This plan replaces the current “agents list + single chat view” IA with:
- **Projects** (grouped by a stable `projectKey`)
- **Workspaces** per project (today: local Paseo worktrees + main checkout; future: cloud VM)
- A **workspace main view** that is a **tabbed panel** where each tab is either an **agent** chat or a **terminal**
No legacy UI is kept: we redirect and fully rework the current routes and RPCs that don’t make sense anymore.
---
## TL;DR
- **Sidebar** becomes **Projects → Workspaces**; still supports drag reorder (projects + workspaces) persisted on-device.
- **Project key**: **remote when available**; otherwise **local repo root / workspace root** (CWD fallback).
- **Workspaces**: show **main checkout + all Paseo worktrees**, including empty workspaces.
- **Main view**: opening a workspace shows a **tab bar** with **all agents in that workspace**; horizontally scrollable.
- **Tabs** are a discriminated union: `agent` or `terminal` (terminals are not special).
- **Right sidebar** stays **Changes + Files**, scoped to the current workspace; **terminals removed** from the right sidebar.
- **File Explorer RPC**: replace agent-scoped explorer/download-token RPC with **workspace-scoped** versions.
- **Agent data** is loaded once at daemon startup and kept queryable in-memory (no expensive directory rescans at runtime).
- **Splits**: not implemented now, but abstractions should not block future VSCode-style splits.
---
## Definitions & IDs
### Project
A **Project** is a grouping container keyed by `projectKey`.
**`projectKey` derivation (must match user decisions):**
1. If git remote is available: use normalized remote key (e.g. `remote:github.com/org/repo`).
2. Else (git with no remote OR not git): use the **workspace/repo root path** (local key).
Notes:
- For Paseo-owned worktrees, prefer grouping via the **shared repo root** when available (so multiple workspaces still group under one project even without a remote).
- For plain directories, the root path is the directory itself.
### Workspace
A **Workspace** is an environment you can “open” and run agents/terminals within.
Today:
- **Local main checkout** (the normal repo root) is a workspace and **cannot be archived**.
- **Local Paseo worktrees** are workspaces (including empty ones).
Future:
- Workspace could point to a **cloud VM** or other remote compute target.
Workspace identity:
- `workspaceId` should be stable and unique within a server.
- For local: derive from canonical absolute `workspaceRoot` (and/or from the worktree metadata id when we add one).
### Tab
Tabs are the only top-level “things” in the workspace main view.
**Discriminated union (locked):**
```ts
type WorkspaceTab =
| { kind: "agent"; tabId: string; agentId: string }
| { kind: "terminal"; tabId: string; terminalId: string }
```
Terminals are not special: they are just `kind: "terminal"` tabs.
### Panel (future splits)
We do **not** implement splits now. We still design tab/panel state so it can evolve into a layout model:
```ts
type PanelLayout =
| { kind: "single"; panelId: string }
// later: | { kind: "split"; direction: "row"|"column"; a: PanelLayout; b: PanelLayout; sizes: number[] }
```
For this milestone, `PanelLayout` is always `{ kind: "single" }`.
---
## UX (Agreed)
### Sidebar (Left)
- Shows **Projects** (project icon + name + status dot).
- Each project expands to show **Workspaces**.
- Workspaces list includes:
- **Main checkout workspace** (always present, not archivable)
- **All Paseo worktrees** for that project (show all, including empty workspaces)
- Workspace row shows:
- **Branch name**
- **Created at**
- **No path**
- **Drag reorder**:
- Projects reorder globally
- Workspaces reorder within a project
- Persisted on-device
- **No agents** are shown in the sidebar (agents live in tabs + in the All Agents list).
### Main View (Workspace)
- Opening a workspace shows a workspace-scoped screen.
- Top area contains a **tab bar** with all agent tabs in that workspace:
- horizontally scrollable
- switching tabs focuses the selected agent/terminal
- **Remember last focus**:
- When returning to a workspace, restore the last focused tab (agent or terminal).
- When opening a workspace for the first time: pick the most recent agent, else open the draft agent flow.
### Tabs + Icons
- Each tab type provides its own icon.
- Terminal tabs: terminal icon.
- Agent tabs: status icon (running/idle/needs-attention/error) or provider icon if needed.
### Right Sidebar (Explorer Sidebar)
- Remains **Changes + Files** only.
- Scope is the **workspace you are looking at**.
- Selection of agent/terminal tab does **not** change the right sidebar scope.
- Terminals are removed from the right sidebar entirely.
- Gestures/overlay behavior are unchanged (mobile).
### Mobile-specific
- Keep existing overlay sidebars and gestures unchanged.
- Use a **tab switcher** pattern (bottom sheet / switcher) for tab navigation.
- Header is simplified:
- shows only workspace name (branch name)
- has space for a **plus** button
- Plus button:
- opens the **draft agent flow**, pre-scoped to the current workspace.
---
## Data Model (Client)
### On-device persisted state
Persist (per `serverId`):
- `projectOrder: projectKey[]`
- `workspaceOrderByProject: Record<projectKey, workspaceId[]>`
- `lastOpenedWorkspaceByProject: Record<projectKey, workspaceId>`
- `lastFocusedTabByWorkspace: Record<workspaceId, tabId>`
- `openTabsByWorkspace: Record<workspaceId, WorkspaceTab[]>` (optional; can be derived initially from agents/terminals list but should be persistable)
Default ordering:
- If no persisted order:
- Projects sorted by `createdAt/firstSeenAt` (or by name as fallback)
- Workspaces sorted by **createdAt** (locked decision)
### Derived view models
Compute:
- `projects[]` from workspace + checkout metadata
- `projectStatusDot` aggregated from agent statuses in all workspaces in that project
- `workspaceStatusDot` aggregated from agent statuses in that workspace
---
## Data Model (Server)
### Agents: load once, query often
Current pain:
- Hydrating agents by scanning directories is expensive.
Target behavior:
- On daemon startup, perform a single load/scan and build an in-memory index:
- by `agentId`
- by `workspaceRoot`
- by `projectKey`
- by status (running, attention, errored, etc.)
- No periodic rescans at runtime.
- When an agent is created/updated/removed, update the in-memory index incrementally.
- This aligns with the PID lock: we don’t support concurrent disk mutation anyway.
- Future: replace with local SQL DB, but not in this milestone.
### Workspaces (local)
Source of truth:
- Main checkout: derived from repo root / current connection cwd policy.
- Paseo worktrees: `paseo_worktree_list_*` plus metadata.
Worktree metadata needs `createdAt`:
- Add `createdAt` to Paseo worktree metadata (new version).
- Backfill for existing worktrees using filesystem stat of metadata file or worktree directory.
---
## RPC / Protocol Changes (Server ↔ App)
### Replace: File Explorer request (agent-scoped → workspace-scoped)
**Current (to remove):**
- `file_explorer_request` requires `agentId`.
**New (replacement; same message type name or updated schema, but old behavior removed):**
- `file_explorer_request` keyed by `workspaceId` (or `workspaceRoot`) instead of `agentId`.
- Requests can succeed even if there are **no agents** in the workspace (empty workspace is valid).
Rules:
- All explorer operations are rooted at `workspaceRoot`.
- Path traversal is prevented (no `..`, enforce prefix).
### Replace: File download token request (agent-scoped → workspace-scoped)
**Current (to remove):**
- `file_download_token_request` requires `agentId`.
**New:**
- `file_download_token_request` requires `workspaceId` (or `workspaceRoot`) and `path`.
- Token resolution is workspace-rooted with the same traversal protections.
### Worktree list RPC: include `createdAt`
Update `paseo_worktree_list_response` to include:
- `createdAt` (ISO string)
- `workspaceId` (optional if client can derive; preferred to include for stability)
Ensure the list includes:
- all Paseo worktrees for the requested repo/project
- enough info to render workspace row: branch name + createdAt
### Checkout lite payload: ensure it contains enough for project grouping
For workspace/project grouping on the client, ensure we have:
- `isGit`
- `remoteUrl` (nullable)
- `currentBranch`
- `isPaseoOwnedWorktree`
- `mainRepoRoot` (when worktree-owned)
- (optional) `repoRoot` for non-worktree git repos
---
## App Changes (packages/app)
### Routes
Replace/redirect:
- `/h/:serverId` should no longer redirect to “draft agent”; it should land on:
- last opened workspace, or
- a workspace picker (the new sidebar is the primary picker)
- `/h/:serverId/agent` and `/h/:serverId/agent/:agentId` are replaced with:
- `/h/:serverId/workspace/:workspaceId` (main workspace screen)
- `/h/:serverId/workspace/:workspaceId/draft-agent` (draft flow, pre-scoped)
No legacy route behavior remains; old routes redirect.
### Left Sidebar rewrite
Replace the current agent list sidebar with:
- Project list (icon + name + status dot)
- Nested workspace list (branch name + createdAt)
- Drag reorder persisted on-device
### Workspace screen
New top-level screen composition:
- Header: workspace name (branch) + plus button
- Tab strip (horizontal)
- Active tab content (agent chat or terminal UI)
- Right sidebar: changes + file explorer (workspace-scoped)
### Tabs system
Implement `WorkspaceTab` union:
- `agent` tabs map to existing agent chat component (but now hosted within the workspace screen).
- `terminal` tabs map to existing terminal component previously shown in the right sidebar.
Remember focus:
- Persist `lastFocusedTabByWorkspace`.
### Terminal migration
- Remove terminal UI from right sidebar.
- Expose terminals through tabs:
- create terminal -> adds a `terminal` tab
- show terminal output in the main area
### File explorer state migration
Update file explorer client state/actions:
- Key state by `workspaceId` (not `agentId`).
- Update all call sites to use the new RPC parameters.
---
## Status Dots (Sidebar)
### Agent → Workspace → Project aggregation
Define a simple severity ordering, e.g.:
1. Error
2. Needs attention
3. Running
4. Idle / none
Compute:
- workspace status = max severity across agents in that workspace
- project status = max severity across all workspaces in that project
No agents are displayed in the sidebar; only aggregated signal.
---
## User Flows (Agreed)
### Open app → open a workspace
1. User opens app.
2. Left sidebar shows Projects.
3. User selects a Project.
4. User selects a Workspace (main checkout or a worktree).
5. Workspace screen opens:
- tab strip shows all agents in that workspace
- restores last focused tab if available
- otherwise picks most recent agent
- otherwise opens draft agent flow pre-scoped to the workspace
### Create a new agent in a workspace
1. User is inside a workspace.
2. User taps plus in header.
3. Draft agent flow opens, pre-scoped to that workspace root.
4. On create:
- new agent appears as a new `agent` tab in the workspace
- it becomes focused
### Open a terminal
1. User taps plus (or a “new tab” affordance).
2. Selects “Terminal”.
3. A `terminal` tab is created and focused.
4. Terminal is fully usable inside the tab panel.
### Switch tabs (mobile)
1. User opens tab switcher.
2. Selects an agent/terminal tab.
3. Workspace focuses that tab and persists focus.
### Reorder projects and workspaces
1. User drags a project to reorder.
2. Order persists on-device and survives app restart.
3. User expands a project and drags a workspace row to reorder within the project.
4. Order persists on-device and survives app restart.
---
## Acceptance Criteria
### Sidebar / Navigation
- Projects are grouped by `projectKey`:
- remote when available
- otherwise local repo/workspace root key
- Sidebar shows **project icon** for each project.
- Each project lists workspaces (main checkout + all Paseo worktrees), including empty workspaces.
- Workspace rows show **branch name + createdAt**, and **do not show path**.
- Projects and workspaces can be drag-reordered; order is persisted on-device.
- Sidebar does not list agents anywhere.
### Workspace main view
- Opening a workspace shows a tabbed main view (not a single chat screen).
- Tab bar contains all agents in the workspace; horizontally scrollable.
- Last focused tab is restored when returning to a workspace.
- Header shows workspace name and a plus button.
- Plus opens draft agent flow pre-scoped to workspace.
- Mobile uses a tab switcher; sidebars/gestures unchanged.
### Tabs / Terminals
- Tabs are a discriminated union: `agent` or `terminal`.
- Terminals are accessible only as tabs (no terminal section in the right sidebar).
- Each tab type can render a custom icon; agent tabs can render status.
### Explorer / Changes / Files
- Right sidebar contains only Changes + Files, scoped to the opened workspace.
- File explorer and download token requests are workspace-scoped (no agentId in protocol).
- File explorer works for empty workspaces (no agents yet).
### Migration / No legacy
- Old agent-centric routes redirect to the new workspace routes.
- Old agent-scoped file explorer RPC is removed/replaced (no legacy behavior left behind).
---
## Implementation Phases (Suggested)
### Phase 0 — Scaffolding
- Add shared types: `Project`, `Workspace`, `WorkspaceTab`.
- Add on-device storage keys and migration scaffolding (no UI yet).
### Phase 1 — Server protocol + data
- Replace file explorer + download token RPCs to be workspace-scoped.
- Ensure checkout-lite has enough fields for grouping.
- Add worktree `createdAt` and include it in worktree list response.
- Ensure “main checkout workspace” can be represented as a workspace.
- Ensure agent index loads once at startup and is queryable without rescans.
### Phase 2 — App navigation rewrite
- Introduce workspace routes and redirect old routes.
- Add new workspace screen layout (header + tabs + explorer sidebar).
### Phase 3 — Sidebar rewrite
- Implement Projects → Workspaces sidebar UI.
- Implement project icon wiring.
- Implement drag reorder + persisted ordering.
- Implement project/workspace status dots from agent status aggregation.
### Phase 4 — Tabs + terminals migration
- Implement tab state + last focused persistence.
- Move terminals out of right sidebar and into tabs.
- Implement mobile tab switcher.
### Phase 5 — Polish + verification
- Verify flows end-to-end (open workspace, tabs, explorer, plus flows).
- Confirm no legacy route/UI remains.
- Typecheck and targeted runtime smoke tests.
---
## Verification Checklist
- `npm run typecheck` passes after each phase.
- Explorer sidebar still behaves the same (gestures/overlay).
- No references remain to “file explorer by agentId” or “download token by agentId”.
- Terminals are not rendered in the right sidebar.
- Workspace list includes empty worktrees and main checkout.
- Reorder persistence works and survives app restart.