diff --git a/apps/daemon/src/import-export-routes.ts b/apps/daemon/src/import-export-routes.ts
index fbd06bbff..fe8203f71 100644
--- a/apps/daemon/src/import-export-routes.ts
+++ b/apps/daemon/src/import-export-routes.ts
@@ -716,14 +716,16 @@ function buildProjectExportManifestResponse({
const addManifestRef = (
ref: unknown,
reason: string,
- options: { preferProjectRoot?: boolean } = {},
+ options: { allowProjectRootFallback?: boolean; preferProjectRoot?: boolean } = {},
) => {
+ const ownerRelative = normalizeManifestProjectRef(ref, file.name);
+ const projectRoot = normalizeManifestProjectRootRef(ref);
const candidates = options.preferProjectRoot
- ? [
- normalizeManifestProjectRootRef(ref),
- normalizeManifestProjectRef(ref, file.name),
- ]
- : [normalizeManifestProjectRef(ref, file.name)];
+ ? [projectRoot, ownerRelative]
+ : [
+ ownerRelative,
+ ...(options.allowProjectRootFallback ? [projectRoot] : []),
+ ];
const normalized = candidates.find((candidate) => candidate && filesByName.has(candidate));
if (!normalized) return;
if (normalized === file.name) return;
@@ -737,7 +739,7 @@ function buildProjectExportManifestResponse({
}
if (Array.isArray(manifest.supportingFiles)) {
for (const ref of manifest.supportingFiles) {
- addManifestRef(ref, 'artifact-supporting-file');
+ addManifestRef(ref, 'artifact-supporting-file', { allowProjectRootFallback: true });
}
}
diff --git a/apps/daemon/tests/export-manifest-route.test.ts b/apps/daemon/tests/export-manifest-route.test.ts
index fe79e7d1d..c56bafc2a 100644
--- a/apps/daemon/tests/export-manifest-route.test.ts
+++ b/apps/daemon/tests/export-manifest-route.test.ts
@@ -284,6 +284,58 @@ describe('project export manifest route', () => {
});
});
+ it('keeps artifact entry refs current when a referenced file moves out of the wrapper directory', async () => {
+ const projectId = await createProject({ kind: 'prototype' });
+ await writeFile(projectId, {
+ name: 'index.html',
+ content: 'fallback',
+ });
+ await writeFile(projectId, {
+ name: 'preview/reviewed.html',
+ content: 'reviewed',
+ });
+ await writeFile(projectId, {
+ name: 'preview/wrapper.html',
+ content: '',
+ artifactManifest: {
+ version: 1,
+ kind: 'html',
+ title: 'Review wrapper',
+ entry: 'reviewed.html',
+ renderer: 'html',
+ status: 'complete',
+ exports: ['html'],
+ primary: 'reviewed.html',
+ supportingFiles: ['reviewed.html'],
+ },
+ });
+
+ await renameFile(projectId, 'preview/reviewed.html', 'reviewed.html');
+
+ const response = await fetch(`${baseUrl}/api/projects/${projectId}/export/manifest`);
+ expect(response.ok).toBe(true);
+ const body = await response.json() as {
+ entryFile: string;
+ files: Array<{ name: string; role: string; reasons: string[] }>;
+ artifacts: Array<{ file: string; supportingFiles: string[] }>;
+ };
+
+ expect(body.entryFile).toBe('reviewed.html');
+ expect(body.files.find((file) => file.name === 'reviewed.html')).toMatchObject({
+ role: 'entry',
+ reasons: expect.arrayContaining([
+ 'artifact-entry',
+ 'artifact-primary',
+ 'artifact-supporting-file',
+ 'project-entry-file',
+ ]),
+ });
+ expect(body.artifacts.find((artifact) => artifact.file === 'preview/wrapper.html'))
+ .toMatchObject({
+ supportingFiles: ['reviewed.html'],
+ });
+ });
+
it('rejects invalid project ids before listing files', async () => {
const response = await fetch(`${baseUrl}/api/projects/bad:id/export/manifest`);
expect(response.status).toBe(400);