From af831abd535d75a89943765a7b55e8587ecca5e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Fri, 9 Oct 2026 01:32:09 -0400 Subject: [PATCH] Q153: the approval button that reviews a request is called Review, and leads to where the request can change Co-Authored-By: Claude Opus 5.5 --- src/renderer/src/i18n/zh.ts | 3 +++ src/renderer/src/pages/ApprovalsPage.tsx | 16 +++++++++++----- tests/e2e/ceo-chat.spec.ts | 18 ++++++++++++++++++ 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/src/renderer/src/i18n/zh.ts b/src/renderer/src/i18n/zh.ts index 08bcd3c3..c0aae448 100644 --- a/src/renderer/src/i18n/zh.ts +++ b/src/renderer/src/i18n/zh.ts @@ -40,6 +40,9 @@ export const zh: Record = { "Reopen": "重新打开", "Back to backlog": "退回待办", "Delete this task?": "删除这个任务?", + "Review": "审阅", + "Edit the task": "编辑任务", + "Changing the task replaces this request with a fresh one.": "修改任务会用新的申请替换这一项。", "Delete task": "删除任务", "Nothing else is affected.": "不影响其他内容。", "{n} sessions and their action records are deleted.": "{n} 次会话及其操作记录将被删除。", diff --git a/src/renderer/src/pages/ApprovalsPage.tsx b/src/renderer/src/pages/ApprovalsPage.tsx index c0fd38b2..75c12088 100644 --- a/src/renderer/src/pages/ApprovalsPage.tsx +++ b/src/renderer/src/pages/ApprovalsPage.tsx @@ -116,7 +116,7 @@ export function ApprovalsPage({ snapshot, actions, navigate, focus }: PageProps) isBot={approval.requestedByType === "agent"} onApprove={() => decide(approval, "approved")} onDecline={() => decide(approval, "declined")} - onEdit={() => setEditingId(approval.id)} + onReview={() => setEditingId(approval.id)} /> ))} @@ -191,7 +191,12 @@ function ApprovalDetailDialog({ <> {t("Source")} {approval.sourceEntityType === "task" && approval.sourceEntityId - ? + ? ( + + + · {t("Changing the task replaces this request with a fresh one.")} + + ) : {humanizeKind(approval.sourceEntityType)}} )} @@ -254,14 +259,15 @@ function ApprovalRow({ isBot, onApprove, onDecline, - onEdit, + onReview, }: { approval: ApprovalRecord; requester: string; isBot: boolean; onApprove: () => void; onDecline: () => void; - onEdit: () => void; + /** Opens the request to read it in full and decide with notes; what is requested is changed at its source. */ + onReview: () => void; }) { const tone = RISK_TONES[approval.risk]; if (!tone) throw new Error("Approval risk tone is missing."); @@ -297,7 +303,7 @@ function ApprovalRow({
- +
diff --git a/tests/e2e/ceo-chat.spec.ts b/tests/e2e/ceo-chat.spec.ts index 9665c203..7aba6929 100644 --- a/tests/e2e/ceo-chat.spec.ts +++ b/tests/e2e/ceo-chat.spec.ts @@ -97,6 +97,24 @@ test("the pinned CEO conversation is the Messages front door", async () => { return [task.writingMode, task.requiresFilesystem]; }); expect(edited).toEqual(["technical", true]); + // A pending request opens for review: read it in full and decide with notes; nothing there pretends to edit it. + await taskDialog.getByRole("button", { name: "Close" }).click(); + await page.evaluate(async () => { + const api = (window as unknown as { agentCompany: { + loadSnapshot: () => Promise<{ ok: true; data: { activeOrgId: string } }>; + createApproval: (input: unknown) => Promise<{ ok: boolean }>; + } }).agentCompany; + const orgId = (await api.loadSnapshot()).data.activeOrgId; + await api.createApproval({ orgId, kind: "budget_increase", title: "Raise the ads budget", details: "From $200 to $500.", risk: "medium", requestedByType: "system", requestedById: null }); + }); + await page.getByRole("button", { name: "Approvals" }).click(); + await expect(page.getByRole("button", { name: "Edit", exact: true })).toHaveCount(0); + await page.getByRole("button", { name: "Review" }).first().click(); + const review = page.getByRole("dialog", { name: "Raise the ads budget" }); + await expect(review.getByRole("textbox", { name: "Decision notes" })).toBeVisible(); + await review.getByRole("button", { name: "Close" }).click(); + await page.getByRole("button", { name: "Work" }).click(); + await page.getByRole("button", { name: /Operationalize goal: Ship the FlowPilot MVP\./ }).first().click(); // Blocking says why on the task and stops it; work can also move backwards. await taskDialog.getByRole("button", { name: "Block", exact: true }).click(); await taskDialog.getByRole("textbox", { name: "Why is it blocked?" }).fill("Waiting for the legal review.");