fix: persist public file shares across daemon restarts (#7002)

* fix: persist public file shares across restarts

* fix(daemon): redact unpersisted public snapshots

Generated-By: looper 0.11.8 (runner=fixer, agent=codex)

* fix(web): expose public share recovery action

Generated-By: looper 0.11.8 (runner=fixer, agent=codex)
This commit is contained in:
Ray Xi
2026-08-18 15:49:39 +00:00
committed by GitHub
parent 42faa84d0c
commit aa5bb5d67a
13 changed files with 839 additions and 30 deletions
@@ -5520,6 +5520,68 @@ describe('FileViewer SVG artifacts', () => {
);
});
it('exposes Stop sharing when publication persistence and compensation both fail', async () => {
const context = teamWorkspaceContext();
const publicUrl =
'https://hub.example.test/api/v1/public/snapshots/manual-revoke-slug/files/index.html';
const unpublishBodies: unknown[] = [];
vi.stubGlobal(
'fetch',
vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => {
const url = typeof input === 'string' ? input : input.toString();
if (url.includes('/api/workspace/context')) {
return new Response(JSON.stringify({ context }), { status: 200 });
}
if (url.includes('publish-public')) {
const method = (init?.method ?? 'GET').toUpperCase();
if (method === 'GET') {
return new Response(JSON.stringify({ publication: null }), { status: 200 });
}
if (method === 'DELETE') {
unpublishBodies.push(JSON.parse(String(init?.body)));
return new Response(
JSON.stringify({ ok: true, slug: 'manual-revoke-slug', fileName: 'index.html' }),
{ status: 200 },
);
}
return new Response(
JSON.stringify({
error: {
code: 'PUBLIC_FILE_MANUAL_REVOKE_REQUIRED',
message: `The public link remains active at ${publicUrl}.`,
data: {
projectId: 'project-1',
url: publicUrl,
slug: 'manual-revoke-slug',
fileName: 'index.html',
},
},
}),
{ status: 502 },
);
}
return new Response(JSON.stringify({ deployments: [] }), { status: 200 });
}),
);
renderWithProjectWorkspace(
<FileViewer projectId="project-1" projectKind="prototype" file={publicPublishFile()}
liveHtml="<html><body><h1>Hello</h1></body></html>"
/>,
context,
);
fireEvent.click(screen.getByRole('button', { name: /share/i }));
fireEvent.click(await screen.findByRole('menuitem', { name: /Get a share link/i }));
expect(await screen.findByText(publicUrl)).toBeTruthy();
const stopSharing = screen.getByRole('button', { name: /Stop sharing/i });
fireEvent.click(stopSharing);
await waitFor(() => expect(unpublishBodies).toEqual([{ slug: 'manual-revoke-slug' }]));
expect(await screen.findByRole('menuitem', { name: /Get a share link/i })).toBeTruthy();
});
// Reading the help must never publish. The publish row's trailing "?" carries
// the reach + single-file limitation copy, i.e. exactly what a user wants to
// read BEFORE committing — but it used to be nested inside the same