Q166: workspaces can be edited, made primary and removed

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ZacharyZhang-NYandClaude Opus 5.5 committed 2026-10-09 03:32:03 -04:00
1 parent 20c37362f3
commit e8722cd815
12 files changed
+247 -9

No files matched your search

+4
View File
@@ -55,6 +55,10 @@ export const zh: Record<string, string> = {
"Show older runs": "显示更早的运行",
"Target date": "目标日期",
"Edit company": "编辑公司",
"Make primary": "设为主工作区",
"Remove this workspace?": "移除这个工作区?",
"Remove this workspace? Its folder stays on disk.": "移除这个工作区?文件夹仍保留在磁盘上。",
"Edit workspace": "编辑工作区",
"Who runs the work": "由谁推进工作",
"AI CEO: runs tasks without asking": "AI CEO:无需询问直接执行任务",
"AI Co-Founder: asks before risky tasks": "AI 联合创始人:高风险任务先征求同意",
@@ -1,3 +1,4 @@
import { WorkspaceRow } from "./WorkspaceRow";
import { CompanyDialog } from "./CompanyDialog";
import { ScopeNote } from "./ScopeNote";
import { useState, type FormEvent } from "react";
@@ -124,15 +125,7 @@ export function WorkspacePanel({
</div>
<ScopeNote scope="company_folders" company={organization.name} />
{workspaces.map((workspace) => (
<div key={workspace.id} className="row" aria-current={workspace.id === focusedWorkspaceId ? "true" : undefined} style={{ padding: "8px 0", borderBottom: "1px solid var(--border-soft)", background: workspace.id === focusedWorkspaceId ? "var(--accent-soft)" : undefined }}>
<div style={{ minWidth: 0 }}>
<div style={{ fontWeight: 540 }}>{workspace.name}</div>
<div style={{ fontSize: 12, color: "var(--text-3)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{workspace.localPath}</div>
</div>
<div style={{ marginLeft: "auto" }}>
{workspace.isPrimary && <Pill tone="violet">{t("Primary")}</Pill>}
</div>
</div>
<WorkspaceRow key={workspace.id} workspace={workspace} projects={projects} focused={workspace.id === focusedWorkspaceId} actions={actions} />
))}
<Dialog title={t("Add workspace")} open={createOpen} submitLabel={t("Create")} onClose={() => setCreateOpen(false)} onSubmit={submit}>
@@ -0,0 +1,71 @@
import { useState, type FormEvent } from "react";
import type { ProjectRecord, WorkspaceRecord } from "../../../../shared/domain";
import { Button, Field, Icon, IconButton, Pill } from "../../shared/ui";
import { Dialog } from "../../shared/Dialog";
import type { SnapshotActions } from "../../app/useSnapshot";
import { t } from "../../i18n/i18n";
/** One workspace with its upkeep: edit its name, folder and project, make it primary, or remove it once nothing open uses it. */
export function WorkspaceRow({ workspace, projects, focused, actions }: { workspace: WorkspaceRecord; projects: ProjectRecord[]; focused: boolean; actions: SnapshotActions }) {
const [editing, setEditing] = useState(false);
const [confirmRemove, setConfirmRemove] = useState(false);
const [error, setError] = useState<string | null>(null);
const act = (operation: () => Promise<unknown>, after?: () => void) => {
setError(null);
operation().then(after, (reason: unknown) => setError(reason instanceof Error ? reason.message : String(reason)));
};
const orgId = workspace.orgId;
const submit = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
const form = new FormData(event.currentTarget);
const value = (key: string) => String(form.get(key) ?? "");
act(() => actions.run(() => window.agentCompany.updateWorkspace({
orgId, workspaceId: workspace.id, name: value("name"), localPath: value("localPath"), projectId: value("projectId") || null,
})), () => setEditing(false));
};
const browse = () => {
void window.agentCompany.pickDirectory().then((result) => {
const input = document.querySelector<HTMLInputElement>(`input[data-workspace-path="${workspace.id}"]`);
if (result.ok && result.data && input) input.value = result.data;
});
};
return (
<div role="group" aria-label={workspace.name} aria-current={focused ? "true" : undefined} style={{ padding: "8px 0", borderBottom: "1px solid var(--border-soft)", background: focused ? "var(--accent-soft)" : undefined }}>
<div className="row" style={{ gap: 8 }}>
<div style={{ minWidth: 0, flex: 1 }}>
<div style={{ fontWeight: 540 }}>{workspace.name}</div>
<div style={{ fontSize: 12, color: "var(--text-3)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{workspace.localPath}</div>
</div>
{workspace.isPrimary
? <Pill tone="violet">{t("Primary")}</Pill>
: <Button size="small" tone="ghost" onClick={() => act(() => actions.run(() => window.agentCompany.setPrimaryWorkspace({ orgId, workspaceId: workspace.id })))}>{t("Make primary")}</Button>}
<IconButton icon="edit" label={t("Edit {name}", { name: workspace.name })} onClick={() => setEditing(true)} />
<Button size="small" tone="ghost" onClick={() => setConfirmRemove(true)}>{t("Remove")}</Button>
</div>
{confirmRemove && (
<div className="row" role="alertdialog" aria-label={t("Remove this workspace?")} style={{ gap: 8, marginTop: 6 }}>
<span style={{ fontSize: 13 }}>{t("Remove this workspace? Its folder stays on disk.")}</span>
<Button size="small" tone="primary" onClick={() => act(() => actions.run(() => window.agentCompany.deleteWorkspace({ orgId, workspaceId: workspace.id })))}>{t("Remove")}</Button>
<Button size="small" tone="ghost" onClick={() => { setConfirmRemove(false); setError(null); }}>{t("Cancel")}</Button>
</div>
)}
{error && <div className="form-error" role="alert">{error}</div>}
<Dialog title={t("Edit workspace")} open={editing} submitLabel={t("Save")} onClose={() => setEditing(false)} onSubmit={submit}>
<Field label={t("Name")}><input className="input" name="name" defaultValue={workspace.name} required /></Field>
<Field label={t("Local path")}>
<div className="row" style={{ gap: 6 }}>
<input className="input" name="localPath" data-workspace-path={workspace.id} defaultValue={workspace.localPath} required style={{ flex: 1 }} />
<Button tone="ghost" onClick={browse}><Icon name="folder" size={16} /> {t("Browse")}</Button>
</div>
</Field>
<Field label={t("Project")}>
<select className="input" name="projectId" defaultValue={workspace.projectId ?? ""}>
<option value="">{t("None")}</option>
{projects.map((project) => <option key={project.id} value={project.id}>{project.name}</option>)}
</select>
</Field>
{error && <div className="form-error" role="alert">{error}</div>}
</Dialog>
</div>
);
}