From 64d4476f5a8d247eba6b4426feb20001f25a0508 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Thu, 9 Jul 2026 21:07:56 -0400 Subject: [PATCH] Five shell fixes: pinned Work header, honest close targets, a draggable sidebar top MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Work page is now a flush route: the title, project overview, and column headers stay put and each board column scrolls its own cards (List and Timeline scroll inside their own region). The page no longer scrolls its header away under 300 tasks. - Team page drops the hiring-pipeline block (and its orphaned component and i18n key) — four columns of mostly-zero counters said nothing the roster doesn't. - Clicking outside the search overlay closes it; the Close button stops being the only exit. - The sidebar's top strip is a window drag region now (the topbar only covers the main pane). Interactive children opt out of dragging, and the org button shrinks to its content so the strip actually has drag area. - The operator's name hugs their avatar in the sidebar foot: buttons center text by UA default and the flexed name span inherited it; text-align: left ends that. --- src/renderer/src/i18n/zh.ts | 1 - src/renderer/src/pages/TeamPage.tsx | 3 - src/renderer/src/pages/WorkPage.tsx | 92 ++++++++++--------- .../src/pages/team/HiringPipeline.tsx | 76 --------------- src/renderer/src/shared/OrgSwitcher.tsx | 2 +- src/renderer/src/shared/SearchDialog.tsx | 8 +- src/renderer/src/shared/Shell.tsx | 2 +- src/renderer/src/styles.css | 34 ++++++- src/renderer/src/styles/components.css | 2 +- 9 files changed, 94 insertions(+), 126 deletions(-) delete mode 100644 src/renderer/src/pages/team/HiringPipeline.tsx diff --git a/src/renderer/src/i18n/zh.ts b/src/renderer/src/i18n/zh.ts index 0bdff6af..454f40e2 100644 --- a/src/renderer/src/i18n/zh.ts +++ b/src/renderer/src/i18n/zh.ts @@ -179,7 +179,6 @@ export const zh: Record = { "Hire agent": "招聘智能体", "Hires need your stamp; deliverables route through review before shipping.": "招聘需要你签字确认;交付物先经审核再发布。", "Hires require approval": "招聘需审批", - "Hiring pipeline": "招聘进展", "Home": "首页", "How autonomous should the CEO be?": "CEO 的自主程度?", "Humans only": "仅人类", diff --git a/src/renderer/src/pages/TeamPage.tsx b/src/renderer/src/pages/TeamPage.tsx index cc4c411c..382ef095 100644 --- a/src/renderer/src/pages/TeamPage.tsx +++ b/src/renderer/src/pages/TeamPage.tsx @@ -4,7 +4,6 @@ import { AvatarField } from "../shared/AvatarField"; import { TopbarActions } from "../shared/TopbarActions"; import { Dialog } from "../shared/Dialog"; import { OrgChartTree } from "./team/OrgChartTree"; -import { HiringPipeline } from "./team/HiringPipeline"; import type { AgentRecord, EmployeeRecord, ProviderRecord } from "../../../shared/domain"; import type { PageProps } from "./types"; import { activeOrgId, money } from "./types"; @@ -119,8 +118,6 @@ export function TeamPage({ snapshot, actions }: PageProps) { - a.orgId === orgId)} /> -
{t("Roster")}
task.id === detailTaskId) ?? null : null; return ( -
+
- {view === "board" && ( - setNewTaskOpen(true)} - onOpen={(task) => setDetailTaskId(task.id)} - /> - )} - {view === "list" && ( - setDetailTaskId(task.id)} - /> - )} - {view === "timeline" && } +
+ {view === "board" && ( + setNewTaskOpen(true)} + onOpen={(task) => setDetailTaskId(task.id)} + /> + )} + {view === "list" && ( +
+ setDetailTaskId(task.id)} + /> +
+ )} + {view === "timeline" && ( +
+ +
+ )} +
setNewGoalOpen(false)} /> setNewProjectOpen(false)} /> @@ -230,30 +238,32 @@ function BoardView({ onOpen: (task: TaskRecord) => void; }) { return ( -
+
{COLUMNS.map((column) => { const columnTasks = tasks.filter((task) => task.status === column.id); return ( -
-
+
+
{column.label} {columnTasks.length}
- {columnTasks.map((task) => ( - agent.id === task.assigneeAgentId) ?? null} - project={projects.find((project) => project.id === task.projectId) ?? null} - provider={providerFor(agents.find((agent) => agent.id === task.assigneeAgentId) ?? null, providers)} - latestRun={latestTaskRun(task.id, taskRuns)} - onAdvance={() => onAdvance(task)} - onRun={() => onRun(task)} - onOpen={() => onOpen(task)} - /> - ))} +
+ {columnTasks.map((task) => ( + agent.id === task.assigneeAgentId) ?? null} + project={projects.find((project) => project.id === task.projectId) ?? null} + provider={providerFor(agents.find((agent) => agent.id === task.assigneeAgentId) ?? null, providers)} + latestRun={latestTaskRun(task.id, taskRuns)} + onAdvance={() => onAdvance(task)} + onRun={() => onRun(task)} + onOpen={() => onOpen(task)} + /> + ))} +
); })} diff --git a/src/renderer/src/pages/team/HiringPipeline.tsx b/src/renderer/src/pages/team/HiringPipeline.tsx deleted file mode 100644 index 3bd2ad70..00000000 --- a/src/renderer/src/pages/team/HiringPipeline.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import { Avatar } from "../../shared/ui"; -import type { AgentRecord, ApprovalRecord } from "../../../../shared/domain"; -import { formatRelative } from "../types"; -import { t } from "../../i18n/i18n"; - -interface PipelineStage { - id: string; - label: string; - color: string; -} - -const STAGES: PipelineStage[] = [ - { id: "pending_approval", label: t("Pending approval"), color: "var(--amber)" }, - { id: "approved", label: t("Approved"), color: "var(--green)" }, - { id: "active", label: t("Active"), color: "var(--accent)" }, - { id: "rejected", label: t("Rejected"), color: "var(--red)" }, -]; - -interface PipelineEntry { - agent: AgentRecord; - stage: string; - approval: ApprovalRecord | null; -} - -function resolveStage(agent: AgentRecord, approvals: ApprovalRecord[]): PipelineEntry { - const hireApproval = approvals.find( - (a) => a.sourceEntityType === "agent" && a.sourceEntityId === agent.id && a.kind === "agent_hire", - ) ?? null; - - if (hireApproval?.state === "pending") return { agent, stage: "pending_approval", approval: hireApproval }; - if (hireApproval?.state === "declined") return { agent, stage: "rejected", approval: hireApproval }; - if (agent.lifecycle === "active") return { agent, stage: "active", approval: hireApproval }; - return { agent, stage: "approved", approval: hireApproval }; -} - -export function HiringPipeline({ agents, approvals }: { agents: AgentRecord[]; approvals: ApprovalRecord[] }) { - const entries = agents.map((a) => resolveStage(a, approvals)); - - return ( -
-
{t("Hiring pipeline")}
-
- {STAGES.map((stage) => { - const stageEntries = entries.filter((e) => e.stage === stage.id); - return ( -
-
- - {stage.label} - {stageEntries.length} -
-
- {stageEntries.map((entry) => ( -
-
- -
-
{entry.agent.name}
-
{entry.agent.role} · {entry.agent.department}
-
-
- {entry.approval && ( -
- {formatRelative(entry.approval.createdAt)} -
- )} -
- ))} -
-
- ); - })} -
-
- ); -} diff --git a/src/renderer/src/shared/OrgSwitcher.tsx b/src/renderer/src/shared/OrgSwitcher.tsx index 3215cd8e..50841867 100644 --- a/src/renderer/src/shared/OrgSwitcher.tsx +++ b/src/renderer/src/shared/OrgSwitcher.tsx @@ -51,7 +51,7 @@ export function OrgSwitcher({ snapshot, activeOrgId, onSelect, onNew, onDelete } }; return ( -
+