fix(studio): enforce optimistic file concurrency (#2156)

* fix(studio): enforce optimistic file concurrency

* fix(studio): harden conditional file writes

* fix(studio): honor explicit file preconditions

* test(producer): allow zero-ms encode timing
This commit is contained in:
James Russo
2026-07-15 18:07:04 -04:00
committed by GitHub
parent 35e623b4f3
commit 2417293dab
34 changed files with 838 additions and 72 deletions
@@ -34,7 +34,7 @@ interface RenderedDomEditCommits {
interface RenderDomEditCommitsOptions {
importedFontAssets?: ImportedFontAsset[];
writeProjectFile?: (path: string, content: string) => Promise<void>;
writeProjectFile?: (path: string, content: string, expectedContent?: string) => Promise<void>;
}
type FetchHandler = (
@@ -1050,6 +1050,46 @@ describe("useDomEditCommits style persist handling", () => {
}
});
it("uses the patched server content as the custom-font write precondition", async () => {
const patchedContent =
'<!doctype html><html><head></head><body><div data-hf-id="hf-card">Card</div></body></html>';
stubPatchFetch(
{ ok: true, changed: true, matched: true, content: patchedContent },
patchedContent,
);
const { iframe, element } = createPreviewElement();
const selection = createSelection(element, {
textFields: [textField({ key: "self", value: "Card", source: "self", tagName: "div" })],
});
const writeProjectFile = vi.fn(async () => {});
const rendered = renderDomEditCommits(selection, iframe, { writeProjectFile });
try {
await act(async () => {
await rendered.hook.commitDomTextFields(
selection,
[textField({ key: "self", value: "Card", source: "self", tagName: "div" })],
{
importedFont: {
family: "Imported",
path: "fonts/Imported.woff2",
url: "/api/projects/p1/preview/fonts/Imported.woff2",
},
},
);
});
expect(writeProjectFile).toHaveBeenCalledWith(
"index.html",
expect.stringContaining("@font-face"),
patchedContent,
);
expect(rendered.showToast).not.toHaveBeenCalled();
} finally {
rendered.cleanup();
}
});
it("keeps a rejected patch request (HTTP error) to one toast", async () => {
const { rendered, cleanup } = renderStyleCommitWithFetch(async (input) => {
const url = requestUrl(input);