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 },