mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-25 17:45:53 +01:00
fix: reconcile Runner D UI contracts
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
ebee3b3b15
commit
1016e82d90
7 files changed
+57
-32
No files matched your search
@@ -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,
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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 (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onOpen}
|
||||
className={cn(ROW_CLASS, "w-full text-left hover:bg-accent/50")}
|
||||
>
|
||||
<FileText className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
||||
<span className="min-w-0 flex-1 truncate">{documentDisplayTitle(doc)}</span>
|
||||
<span className="shrink-0 text-(length:--text-micro) text-muted-foreground">
|
||||
{`Rev ${doc.latestRevisionNumber ?? 1}`}
|
||||
</span>
|
||||
<ChevronRight className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
||||
</button>
|
||||
);
|
||||
}
|
||||
// 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 (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onOpen}
|
||||
className={cn(ROW_CLASS, "w-full text-left hover:bg-accent/50")}
|
||||
>
|
||||
<FileText className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
||||
<span className="min-w-0 flex-1 truncate">{documentDisplayTitle(doc)}</span>
|
||||
<span className="shrink-0 text-(length:--text-micro) text-muted-foreground">
|
||||
{`Rev ${doc.latestRevisionNumber ?? 1}`}
|
||||
</span>
|
||||
<ChevronRight className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
||||
</button>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="rounded-md border border-border bg-card/50">
|
||||
<div ref={headerRef} className="flex items-center hover:bg-accent/50">
|
||||
|
||||
@@ -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 }) => <div>{children}</div> }));
|
||||
vi.mock("@/components/IssueDocumentAnnotations", () => ({
|
||||
DocumentAnnotationsCountChip: ({ docKey }: { docKey: string }) => <span data-testid={`annotation-count-${docKey}`} />,
|
||||
IssueDocumentAnnotations: ({ doc, children }: { doc: IssueDocument; children: React.ReactNode }) => (
|
||||
<div data-testid={`annotation-surface-${doc.key}`} data-document-id={doc.id}>{children}</div>
|
||||
IssueDocumentAnnotations: ({ doc, children }: {
|
||||
doc: Pick<IssueDocument, "key" | "latestRevisionId">;
|
||||
children: React.ReactNode;
|
||||
}) => (
|
||||
<div data-testid={`annotation-surface-${doc.key}`} data-revision-id={doc.latestRevisionId}>{children}</div>
|
||||
),
|
||||
}));
|
||||
|
||||
@@ -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(<IssuePropertiesPlansTab issue={issue} />));
|
||||
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());
|
||||
});
|
||||
|
||||
|
||||
@@ -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]);
|
||||
|
||||
@@ -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") {
|
||||
|
||||
@@ -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;
|
||||
}) => (
|
||||
<div className={className} data-testid={testId}>
|
||||
<div data-slot="sheet-content" className={className} data-testid={testId}>
|
||||
{children}
|
||||
<button type="button" data-slot="sheet-close">Close</button>
|
||||
</div>
|
||||
@@ -533,6 +534,18 @@ async function act(callback: () => void | Promise<void>) {
|
||||
await result;
|
||||
}
|
||||
|
||||
function createTooltipRoot(container: Element): Root {
|
||||
const root = createRoot(container);
|
||||
return {
|
||||
render(children) {
|
||||
root.render(<TooltipProvider>{children}</TooltipProvider>);
|
||||
},
|
||||
unmount() {
|
||||
root.unmount();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createDeferred<T>() {
|
||||
let resolve!: (value: T) => void;
|
||||
const promise = new Promise<T>((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 },
|
||||
|
||||
Reference in new issue
Block a user