Conversation
…sandbox setting on the chat page.
…onsible to sandbox switches
|
@claude review this |
|
Claude encountered an error —— View job I'll analyze this and get back to you. |
|
@claude review this |
|
Claude finished @DIodide's task in 2m 22s —— View job PR Review: Feat/workspaces
Overall this is a solid feature addition — the harness/sandbox decoupling is a good architectural direction and the backend (Convex schema, auth patterns, FastAPI simplification) is well done. A few issues worth addressing before merge, with one significant structural concern. Critical: Massive Code Duplication
Options:
Backend Issues1. Inefficient .take(100)
.then((conversations) =>
conversations.filter((conversation) => !conversation.workspaceId).slice(0, 50),
);This fetches 100 conversations to in-memory filter down to 50 non-workspace ones. A user with many workspace conversations could end up with fewer than 50 non-workspace conversations shown, even when they have more. Consider a dedicated 2. No There's 3. async with httpx.AsyncClient() as http_client:
await create_sandbox_record(http_client, ...)The route handler already receives the shared 4. .take(1000)
return results.length;This is expensive. For a count you only need pagination info ( Schema / Data Model5. sandboxId: v.id("sandboxes"),The PR description says "harnesses still have default sandbox, but may want to remove this in the future." The workspace model has the same tight coupling — a workspace without a sandbox can't be created. If sandbox-less workspaces are a future direction, this should be 6. if (workspace.harnessId !== args.harnessId) {
throw new Error("Workspace harness mismatch");
}But Frontend Issues7. Possible redirect loop ( if (settings.workspacesMode !== "workspaces") {
throw redirect({ to: "/chat" });
}If 8. navigate({
to: "/workspaces",
search: { workspaceId: activeWorkspace._id },
replace: true,
});
Minor Nits
What's Done Well
|
# Conflicts: # apps/web/src/routes/chat/index.tsx # apps/web/src/routes/harnesses/$harnessId.tsx # apps/web/src/routes/onboarding.tsx
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…hortcut - Pastel color picker for workspace sidebar backgrounds; color field added to the workspaces schema and create/update mutations - RoseCurveSpinner and ThinkingFiveSpinner now precompute their time=0 frame as initial SVG attributes so they're visible during SSR and the hydration gap (previously empty until the rAF loop started post-hydrate) - Replace lingering Loader2 arc spinners in the workspaces route with RoseCurveSpinner; wire ThinkingFiveSpinner into the empty-chat state - Cmd+Opt+1..9 (Mac) / Ctrl+Alt+1..9 (Win/Linux) quick-switches between the first 9 workspaces; holding the modifier shows a shortcut badge on each workspace button. Guards editable targets, open dialogs, and window blur. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds a cmdk-powered command palette with navigation, chat (new/cancel/ toggle sidebar), workspace (create/rename/switch), and dynamic harness/sandbox commands. Palette mounts once at the root and registers route-scoped commands via useRegisterCommands. Also fixes a race where new sessions could land on /onboarding despite having existing harnesses: api.harnesses.list returns [] before Convex has the Clerk JWT, which the redirect effect treated as "no harnesses." Both /chat and /workspaces now gate the redirect on useConvexAuth().isAuthenticated.
|
Pushed 5ee3554 with two changes: Command palette (Cmd+K / Cmd+Shift+P)
Onboarding redirect race fix Fix gates the redirect in both |
…t+R rename - Header "N Skills" is now a dropdown that adds/removes skills on the harness - Combined redundant sandbox indicators into a single cube-icon dropdown with panel toggle and sandbox swap - Cmd+Shift+R renames the active workspace
|
@claude review this |
|
Claude finished @jon3350's task in 5m 17s —— View job PR Review: Feat/workspaces (updated — latest 4 commits)
Good progress since the last review. Three of the previous concerns are now addressed. The command palette implementation is well-architected. A few issues worth looking at below. Previously Raised — Now Resolved ✅
Issues Worth Addressing1. The cascade runs one for (const conversation of conversations) {
const messages = await ctx.db.query("messages")...collect();
for (const message of messages) {
await ctx.db.delete(message._id); // sequential awaits
}
await ctx.db.delete(conversation._id);
}Convex mutations have complexity limits. A workspace with 50 conversations × 100 messages each = 5,050 awaited ops in one mutation — this can timeout. The inner message loop should use await Promise.all(messages.map((m) => ctx.db.delete(m._id)));2. Duplicate Escape handler in command palette ( useEffect(() => {
if (!open) return;
const onEsc = (e: KeyboardEvent) => {
if (e.key === "Escape") setOpen(false);
};
document.addEventListener("keydown", onEsc);
...
}, [open, setOpen]);
3. When a workspace has no if (activeWorkspace.harnessId) {
setActiveHarnessId(activeWorkspace.harnessId);
} else if (...harnesses?.some(...)) {
setActiveHarnessId(activeHarnessId); // keep existing
} else if (harnesses?.length) {
const started = harnesses.find((h) => h.status === "started");
setActiveHarnessId(started?._id ?? harnesses[0]._id); // silent fallback
} else {
setActiveHarnessId(null);
}So selecting "None" in the workspace editor doesn't mean "no harness" — it means "pick whatever." The "None" label in the sidebar ( If the intent is that harness-less workspaces still use whatever harness is selected, the sidebar label should reflect the runtime harness instead of "None." If the intent is truly "no harness," the fallback chain needs to be gated on 4. const deleteWorkspace = useMutation({
onSuccess: () => { resetRenameWorkspaceDialog(); ... },
onError: () => { toast.error("Failed to delete workspace"); },
});If deletion fails, the dialog stays open with Minor Nits5. export type CommandIcon =
| LucideIcon
| React.ComponentType<{ className?: string }>;
6.
7. When "Browse skills…" is clicked, What's Well Done in These Commits
|
|
@claude review this |
|
Claude finished @jon3350's task in 13m 20s —— View job PR Review: Feat/workspaces (commit 470d63d)
Good work — the latest commit cleanly addresses all 7 issues from the previous review. Here's the accounting: Resolved from Last Review ✅
Still Open from First ReviewThese were flagged in the initial review but haven't been addressed across any of the commits. Flagging them again for tracking: 1. Code duplication ( The two files still share ~90% of logic. Any change to the chat flow (streaming, MCP errors, message queue, attachments, etc.) needs to be made in both files. This is the highest-maintenance debt in the PR. 2. .take(100)
.then((conversations) =>
conversations.filter((conversation) => !conversation.workspaceId).slice(0, 50),
);Still fetches up to 100 to filter down to 50 non-workspace conversations. A user with lots of workspace conversations may see fewer than 50 basic-mode conversations even when more exist. 3. .take(1000);
return results.length;This is still an expensive count-by-read. A cheaper approach would be to cap at a reasonable limit and return 4. async with httpx.AsyncClient() as http_client:
await create_sandbox_record(http_client, ...)The New ObservationThe The critical |
Added workspaces feature.
Harnesses have been decoupled from sandboxes.
Added manage sandboxes setting.
In settings, you can select whether or not you want to use workspaces or basic layout.
Harnesses still have default sandbox, but may want to remove this in the future.