From 9f435496062fdacbff98fb592780d5cc5ef609fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Tue, 21 Apr 2026 14:42:00 -0400 Subject: [PATCH] fix(docs): surface work-product open failures with a toast MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Clicking a Files & Deliverables row fired openWorkProduct and then discarded the result with \`void\`. When the main process could not resolve the target — archived work product, missing workspace root, path outside the managed root, or \`shell.openPath\` reporting no associated app for the file type — the renderer stayed silent and the row looked like a dead button. The user had no way to know why "clicking" did nothing. Await the result in WorkProductRow and route non-ok responses through addToast with tone="danger". The caller sees the underlying failure (OPEN_PATH_FORBIDDEN / OPEN_PATH_FAILED / WORK_PRODUCT_NOT_FOUND / the shell's own message) and can act on it. No backend change — the handler already returned structured errors; this just stops dropping them. Typecheck and build:verify clean. --- .../company-operations/DocumentCenter.tsx | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/renderer/components/company-operations/DocumentCenter.tsx b/src/renderer/components/company-operations/DocumentCenter.tsx index 979d9b3d..e5750f9f 100644 --- a/src/renderer/components/company-operations/DocumentCenter.tsx +++ b/src/renderer/components/company-operations/DocumentCenter.tsx @@ -3,6 +3,7 @@ import { FileText, FloppyDisk, NotePencil, Plus, Trash } from "../ui/icons"; import type { AgentRecord, DocumentRecord, GoalRecord, ProjectRecord, WorkProductRecord } from "@shared/types"; import { ActionButton, ConfirmDialog, EmptyState, InlineNotice, Input, ListFrame, ListRow, Modal, Select, StatusPill, TextArea } from "../ui"; import { useT } from "../../i18n"; +import { useAppStore } from "../../state/store"; import { documentTypeOptions, documentStatusOptions, splitTags, parseTags } from "./helpers"; import { SectionHeader } from "./SectionHeader"; import { NEW_ENTITY_ID } from "../../lib/constants"; @@ -66,6 +67,7 @@ function DocumentEditor({ companyId, agents, projects, goals, document, onSaved } function WorkProductRow({ product, companyId }: { product: WorkProductRecord; companyId: string }) { + const addToast = useAppStore((s) => s.addToast); const subtitle = [ product.type.replaceAll("_", " "), product.provider, @@ -76,9 +78,16 @@ function WorkProductRow({ product, companyId }: { product: WorkProductRecord; co // the main process can join it against the work product's workspace // root before handing off to the OS shell. Direct `openPath(externalId)` // used to flash a failure when the renderer tried to treat the relative - // path as absolute. + // path as absolute. Surface shell failures (no associated app, path + // outside managed root, workspace moved) as a toast so the click + // doesn't look like a dead button when the OS can't complete the open. const primaryAction = product.url || (product.provider === "filesystem" && product.externalId) - ? () => { void window.agentCompany.openWorkProduct({ companyId, id: product.id }); } + ? async () => { + const result = await window.agentCompany.openWorkProduct({ companyId, id: product.id }); + if (!result.ok) { + addToast(`Couldn't open ${product.title}: ${result.error.message}`, "danger"); + } + } : undefined; return (