From 17c99f6e409e517631783aca1bc024be04420c42 Mon Sep 17 00:00:00 2001 From: Camiel van Schoonhoven Date: Thu, 5 Nov 2026 11:53:47 -0700 Subject: [PATCH] feat(documents): edit a document in place and save it to the project A document opened in the Tangent workarea or selected on the project page is now a Monaco editor with Save and Discard, not a read-only viewer. Save writes the body back to the resource's payload (Cmd/Ctrl+S too), keeping any other payload keys. The draft lives in the component rather than the query cache, so while nothing has been typed the editor follows a save made elsewhere (e.g. by an agent), and once something has, that save is announced with a choice to overwrite or discard into it. The document workarea kind now stays mounted while inactive so an unsaved draft survives a tab switch, and a successful update seeds the resource's query cache so the editor does not flash the old body before the refetch. On the project page the editor is keyed by the resource, so selecting another document opens a fresh editor instead of carrying the previous document's draft over, where Save would have written it into the wrong row. Co-Authored-By: Claude Opus 5.5 --- .../Project/DocumentEditor.test.tsx | 242 ++++++++++++++++++ src/components/Project/DocumentEditor.tsx | 140 ++++++++++ .../Project/ProjectResourcePreview.test.tsx | 51 +++- .../Project/ProjectResourcePreview.tsx | 27 +- .../Tangent/workarea/documentKind.test.tsx | 62 +++-- .../pages/Tangent/workarea/documentKind.tsx | 47 +--- src/services/projects/useProjectResources.ts | 6 +- 7 files changed, 489 insertions(+), 86 deletions(-) create mode 100644 src/components/Project/DocumentEditor.test.tsx create mode 100644 src/components/Project/DocumentEditor.tsx diff --git a/src/components/Project/DocumentEditor.test.tsx b/src/components/Project/DocumentEditor.test.tsx new file mode 100644 index 0000000000..b17c3f1a61 --- /dev/null +++ b/src/components/Project/DocumentEditor.test.tsx @@ -0,0 +1,242 @@ +import { act, fireEvent, render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { afterEach, describe, expect, it, vi } from "vitest"; + +import type { ProjectResource } from "@/services/projects/types"; +import { useUpdateProjectResource } from "@/services/projects/useProjectResources"; + +import { documentBody, DocumentEditor } from "./DocumentEditor"; + +vi.mock("@/services/projects/useProjectResources", () => ({ + useUpdateProjectResource: vi.fn(), +})); + +vi.mock("@/components/shared/CodeViewer/CodeEditor", () => ({ + default: ({ + value, + language, + onChange, + options, + }: { + value: string; + language: string; + onChange: (value: string) => void; + options: { ariaLabel: string }; + }) => ( +