diff --git a/server/src/__tests__/native-finalization-migration.test.ts b/server/src/__tests__/native-finalization-migration.test.ts index 326a3d47328..d150eca54ce 100644 --- a/server/src/__tests__/native-finalization-migration.test.ts +++ b/server/src/__tests__/native-finalization-migration.test.ts @@ -14,7 +14,7 @@ describe("P6-18 / MIG-01..04 native finalization migration", () => { it("repairs only later duplicates and preserves legacy event bytes and cursors", async () => { const temporary = await startEmbeddedPostgresTestDatabase("paperclip-native-migration-"); const migration = await readFile( - new URL("../../../packages/db/src/migrations/0224_flippant_goliath.sql", import.meta.url), + new URL("../../../packages/db/src/migrations/0227_sharp_lilandra.sql", import.meta.url), "utf8", ); const migrationHash = createHash("sha256").update(migration).digest("hex"); @@ -23,7 +23,7 @@ describe("P6-18 / MIG-01..04 native finalization migration", () => { const companyId = "10000000-0000-4000-8000-000000000001"; const agentId = "10000000-0000-4000-8000-000000000002"; const runId = "10000000-0000-4000-8000-000000000003"; - // Reconstruct the actual pre-0224 shape rather than extracting selected + // Reconstruct the actual pre-0227 shape rather than extracting selected // repair statements from the migration under test. await rawDb.execute(sql.raw(` DROP TABLE IF EXISTS status_decision_effects, status_decisions, work_assessments, diff --git a/ui/src/adapters/paperclip-runner/index.ts b/ui/src/adapters/paperclip-runner/index.ts index 17588681254..9cf35df9312 100644 --- a/ui/src/adapters/paperclip-runner/index.ts +++ b/ui/src/adapters/paperclip-runner/index.ts @@ -494,7 +494,7 @@ function runtimeRequestEntry( function parseQuestionSet(value: unknown): PaperclipQuestionSet | null { const input = record(value); if (input.schema !== "paperclip.question_set.v1" || !Array.isArray(input.questions) || input.questions.length === 0) return null; - const questions = input.questions.map(record).slice(0, 64).map((question, questionIndex) => { + const questions = input.questions.map(record).slice(0, 64).map((question, questionIndex): PaperclipQuestion => { const answerMode: PaperclipQuestion["answerMode"] = question.answerMode === "single_select" || question.answerMode === "multi_select" ? question.answerMode : "text"; const options = (Array.isArray(question.options) ? question.options : []).map(record).slice(0, 128).map((option, optionIndex) => ({ id: text(option.id, `option-${optionIndex + 1}`).slice(0, 160), diff --git a/ui/src/components/issue-properties/IssuePropertiesArtifactsTab.tsx b/ui/src/components/issue-properties/IssuePropertiesArtifactsTab.tsx index 67009a092e2..9e4a9c04676 100644 --- a/ui/src/components/issue-properties/IssuePropertiesArtifactsTab.tsx +++ b/ui/src/components/issue-properties/IssuePropertiesArtifactsTab.tsx @@ -149,14 +149,12 @@ function MarkdownWorkProductRow({ metadata, reviewDoc, openRequestId, - onOpen, }: { issueId: string; workProduct: IssueWorkProduct; metadata: AttachmentArtifactWorkProductMetadata; reviewDoc: IssueDocument | undefined; openRequestId?: number; - onOpen?: () => void; }) { const [expanded, setExpanded] = useState(false); const [annotationPanelOpen, setAnnotationPanelOpen] = useState(false); @@ -195,22 +193,6 @@ function MarkdownWorkProductRow({ if (openRequestId === undefined || !expanded) return; headerRef.current?.scrollIntoView({ behavior: "smooth", block: "center" }); }, [expanded, openRequestId]); - if (onOpen) { - return ( - - ); - } // Deep links land before the user clicks, so the deep-link expansion has to // request materialization the same way a manual expand does. useEffect(() => { @@ -338,10 +320,12 @@ function DocumentRow({ issueId, doc, openRequestId, + onOpen, }: { issueId: string; doc: IssueDocument; openRequestId?: number; + onOpen?: () => void; }) { const [expanded, setExpanded] = useState(false); const [annotationPanelOpen, setAnnotationPanelOpen] = useState(false); @@ -356,6 +340,22 @@ function DocumentRow({ if (openRequestId === undefined || !expanded) return; headerRef.current?.scrollIntoView({ behavior: "smooth", block: "center" }); }, [expanded, openRequestId]); + if (onOpen) { + return ( + + ); + } return (
diff --git a/ui/src/components/issue-properties/IssuePropertiesDocumentAnnotations.test.tsx b/ui/src/components/issue-properties/IssuePropertiesDocumentAnnotations.test.tsx index 25b3235499a..38bfee19bea 100644 --- a/ui/src/components/issue-properties/IssuePropertiesDocumentAnnotations.test.tsx +++ b/ui/src/components/issue-properties/IssuePropertiesDocumentAnnotations.test.tsx @@ -31,15 +31,20 @@ const issueDocument: IssueDocument = { const issue = { id: "issue-1", identifier: "PAP-522", workMode: "standard" } as Issue; vi.mock("@tanstack/react-query", () => ({ useQuery: () => ({ data: [] }) })); -vi.mock("@/hooks/useIssuePlanDocument", () => ({ useIssuePlanDocument: () => ({ data: null, isLoading: false }) })); +vi.mock("@/hooks/useIssuePlanDocument", () => ({ + useIssuePlanDocument: () => ({ data: { ...issueDocument, key: "plan" }, isLoading: false }), +})); vi.mock("@/hooks/useIssueDocuments", () => ({ useIssueDocuments: () => ({ data: [issueDocument] }) })); vi.mock("@/lib/router", () => ({ useLocation: () => ({ hash: "" }) })); vi.mock("@/components/IssuePlanDecompositionsSection", () => ({ IssuePlanDecompositionsSection: () => null })); vi.mock("@/components/MarkdownBody", () => ({ MarkdownBody: ({ children }: { children: string }) =>
{children}
})); vi.mock("@/components/IssueDocumentAnnotations", () => ({ DocumentAnnotationsCountChip: ({ docKey }: { docKey: string }) => , - IssueDocumentAnnotations: ({ doc, children }: { doc: IssueDocument; children: React.ReactNode }) => ( -
{children}
+ IssueDocumentAnnotations: ({ doc, children }: { + doc: Pick; + children: React.ReactNode; + }) => ( +
{children}
), })); @@ -59,8 +64,8 @@ describe("issue properties document annotation mounting", () => { it("uses the issue document key on the Plan tab", async () => { const root = createRoot(container); await act(async () => root.render()); - expect(container.querySelector('[data-testid="annotation-surface-qa-evidence"]')?.getAttribute("data-document-id")) - .toBe("document-1"); + expect(container.querySelector('[data-testid="annotation-surface-plan"]')?.getAttribute("data-revision-id")) + .toBe("revision-1"); await act(async () => root.unmount()); }); @@ -71,8 +76,8 @@ describe("issue properties document annotation mounting", () => { expect(container.querySelector('[data-testid="annotation-surface-qa-evidence"]')).toBeNull(); const expand = container.querySelector('button[aria-expanded="false"]') as HTMLButtonElement; await act(async () => expand.click()); - expect(container.querySelector('[data-testid="annotation-surface-qa-evidence"]')?.getAttribute("data-document-id")) - .toBe("document-1"); + expect(container.querySelector('[data-testid="annotation-surface-qa-evidence"]')?.getAttribute("data-revision-id")) + .toBe("revision-1"); await act(async () => root.unmount()); }); diff --git a/ui/src/components/task-side-panel/TaskSidePanel.tsx b/ui/src/components/task-side-panel/TaskSidePanel.tsx index 6dca700aa16..f2aecf247f5 100644 --- a/ui/src/components/task-side-panel/TaskSidePanel.tsx +++ b/ui/src/components/task-side-panel/TaskSidePanel.tsx @@ -249,7 +249,13 @@ export function TaskSidePanel({ markInteracted(); const tab = taskPanelWorkspaceFileTab(ref); controller.openTab(tab); - viewer?.open(ref, { fromBrowse: true }); + viewer?.open({ + ...ref, + projectId: ref.projectId ?? null, + workspaceId: ref.workspaceId ?? null, + line: ref.line ?? null, + column: ref.column ?? null, + }, { fromBrowse: true }); } const documentByKey = useMemo(() => new Map(documents.map((document) => [document.key, document])), [documents]); diff --git a/ui/src/lib/task-side-panel-state.ts b/ui/src/lib/task-side-panel-state.ts index 38c67588281..0a6f448e31b 100644 --- a/ui/src/lib/task-side-panel-state.ts +++ b/ui/src/lib/task-side-panel-state.ts @@ -59,7 +59,8 @@ function positiveInteger(value: unknown): number | null | undefined { function parsePayload(value: unknown): TaskSidePanelTabPayload | null { const input = record(value); - const kind = input?.kind; + if (!input) return null; + const kind = input.kind; if (kind === "properties") return { kind }; if (kind === "artifacts") return { kind }; if (kind === "issue-document") { diff --git a/ui/src/pages/IssueDetail.test.tsx b/ui/src/pages/IssueDetail.test.tsx index 01c883254b1..92654a3ced3 100644 --- a/ui/src/pages/IssueDetail.test.tsx +++ b/ui/src/pages/IssueDetail.test.tsx @@ -8,6 +8,7 @@ import { NavigationType } from "react-router-dom"; import { flushSync } from "react-dom"; import { createRoot, type Root } from "react-dom/client"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { TooltipProvider } from "@/components/ui/tooltip"; import { canBoardManageRuntime, canBoardResolveRecoveryAction, @@ -493,7 +494,7 @@ vi.mock("@/components/ui/sheet", () => ({ className?: string; "data-testid"?: string; }) => ( -
+
{children}
@@ -533,6 +534,18 @@ async function act(callback: () => void | Promise) { await result; } +function createTooltipRoot(container: Element): Root { + const root = createRoot(container); + return { + render(children) { + root.render({children}); + }, + unmount() { + root.unmount(); + }, + }; +} + function createDeferred() { let resolve!: (value: T) => void; const promise = new Promise((innerResolve) => { @@ -1052,7 +1065,7 @@ describe("IssueDetail", () => { mockSidebarState.isMobile = false; container = document.createElement("div"); document.body.appendChild(container); - root = createRoot(container); + root = createTooltipRoot(container); queryClient = new QueryClient({ defaultOptions: { queries: { retry: false },