Edit workspace files directly on web (#2270)
* feat(files): edit workspace files on web Keep source buffers synchronized with host file changes and require an explicit overwrite or reload when revisions diverge. * feat(panels): surface and protect modified tabs Expose tooltip and modification state through the generic panel boundary so tabs can show stable metadata and guard every close route consistently. * fix(tests): use portable fake timeout handle * fix(files): harden editor conflict handling Preserve modified panel state across tab eviction, use precise revisions for optimistic writes, coalesce concurrent file watchers, and localize the editor interface. * fix(files): close editor concurrency gaps Coalesce clean reloads, preserve subscriber identities and file permissions, suspend pending saves during close confirmation, and carry precise revisions through file reads. * test(files): expect read revision metadata
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
import {
|
||||
FileSubscribeRequestSchema,
|
||||
FileSubscribeResponseSchema,
|
||||
FileUnsubscribeRequestSchema,
|
||||
FileUpdateSchema,
|
||||
FileWriteRequestSchema,
|
||||
FileWriteResponseSchema,
|
||||
ServerInfoStatusPayloadSchema,
|
||||
} from "./messages.js";
|
||||
|
||||
describe("workspace file editing messages", () => {
|
||||
test("keeps the capability optional for older server info payloads", () => {
|
||||
expect(
|
||||
ServerInfoStatusPayloadSchema.parse({
|
||||
status: "server_info",
|
||||
serverId: "server-1",
|
||||
features: {},
|
||||
}).features?.workspaceFileEditing,
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
test("parses subscribe, unsubscribe, and version update messages", () => {
|
||||
expect(
|
||||
FileSubscribeRequestSchema.parse({
|
||||
type: "fs.file.subscribe.request",
|
||||
cwd: "/workspace",
|
||||
path: "file.ts",
|
||||
subscriptionId: "subscription-1",
|
||||
requestId: "request-1",
|
||||
}).subscriptionId,
|
||||
).toBe("subscription-1");
|
||||
expect(
|
||||
FileSubscribeResponseSchema.parse({
|
||||
type: "fs.file.subscribe.response",
|
||||
payload: {
|
||||
subscriptionId: "subscription-1",
|
||||
initial: {
|
||||
status: "ready",
|
||||
cwd: "/workspace",
|
||||
path: "file.ts",
|
||||
size: 12,
|
||||
modifiedAt: "2026-07-18T00:00:00.000Z",
|
||||
},
|
||||
requestId: "request-1",
|
||||
},
|
||||
}).payload.initial.status,
|
||||
).toBe("ready");
|
||||
expect(
|
||||
FileUnsubscribeRequestSchema.parse({
|
||||
type: "fs.file.unsubscribe.request",
|
||||
subscriptionId: "subscription-1",
|
||||
requestId: "request-2",
|
||||
}).subscriptionId,
|
||||
).toBe("subscription-1");
|
||||
expect(
|
||||
FileUpdateSchema.parse({
|
||||
type: "fs.file.update",
|
||||
payload: {
|
||||
subscriptionId: "subscription-1",
|
||||
version: { status: "missing", cwd: "/workspace", path: "file.ts" },
|
||||
},
|
||||
}).payload.version.status,
|
||||
).toBe("missing");
|
||||
});
|
||||
|
||||
test("parses optimistic write requests and conflict responses", () => {
|
||||
expect(
|
||||
FileWriteRequestSchema.parse({
|
||||
type: "fs.file.write.request",
|
||||
cwd: "/workspace",
|
||||
path: "file.ts",
|
||||
content: "const value = 1;\n",
|
||||
expectedModifiedAt: "2026-07-18T00:00:00.000Z",
|
||||
requestId: "request-1",
|
||||
}).content,
|
||||
).toBe("const value = 1;\n");
|
||||
expect(
|
||||
FileWriteResponseSchema.parse({
|
||||
type: "fs.file.write.response",
|
||||
payload: {
|
||||
result: {
|
||||
status: "conflict",
|
||||
version: {
|
||||
status: "ready",
|
||||
cwd: "/workspace",
|
||||
path: "file.ts",
|
||||
size: 20,
|
||||
modifiedAt: "2026-07-18T00:00:01.000Z",
|
||||
},
|
||||
},
|
||||
requestId: "request-1",
|
||||
},
|
||||
}).payload.result.status,
|
||||
).toBe("conflict");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user