diff --git a/frontend/editor/src/core/components/fileEditor/FileEditor.tsx b/frontend/editor/src/core/components/fileEditor/FileEditor.tsx index 3ddd5ca40..ed8944fb0 100644 --- a/frontend/editor/src/core/components/fileEditor/FileEditor.tsx +++ b/frontend/editor/src/core/components/fileEditor/FileEditor.tsx @@ -17,7 +17,7 @@ import AddFileCard from "@app/components/fileEditor/AddFileCard"; import FilePickerModal from "@app/components/shared/FilePickerModal"; import { FileId, StirlingFile } from "@app/types/fileContext"; import { alert } from "@app/components/toast"; -import { downloadFile } from "@app/services/downloadService"; +import { downloadFileWithPolicy as downloadFile } from "@app/services/exportWithPolicy"; import { useToolWorkflow } from "@app/contexts/ToolWorkflowContext"; interface FileEditorProps { @@ -256,6 +256,7 @@ const FileEditor = ({ data: file, filename: file.name, localPath: record.localFilePath, + fileId, }); console.log("[FileEditor] Download complete, checking dirty state:", { localFilePath: record.localFilePath, diff --git a/frontend/editor/src/core/components/fileEditor/FileEditorThumbnail.tsx b/frontend/editor/src/core/components/fileEditor/FileEditorThumbnail.tsx index 0b78d4e26..632d9ea00 100644 --- a/frontend/editor/src/core/components/fileEditor/FileEditorThumbnail.tsx +++ b/frontend/editor/src/core/components/fileEditor/FileEditorThumbnail.tsx @@ -36,7 +36,7 @@ import ToolChain from "@app/components/shared/ToolChain"; import HoverActionMenu, { HoverAction, } from "@app/components/shared/HoverActionMenu"; -import { downloadFile } from "@app/services/downloadService"; +import { downloadFileWithPolicy as downloadFile } from "@app/services/exportWithPolicy"; import { PrivateContent } from "@app/components/shared/PrivateContent"; import UploadToServerModal from "@app/components/shared/UploadToServerModal"; import ShareFileModal from "@app/components/shared/ShareFileModal"; @@ -248,6 +248,7 @@ const FileEditorThumbnail = ({ data: fileToSave, filename: file.name, localPath: file.localFilePath, + fileId: file.id, }); if (!result.cancelled && result.savedPath) { fileActions.updateStirlingFileStub(file.id, { diff --git a/frontend/editor/src/core/components/pageEditor/FileThumbnail.tsx b/frontend/editor/src/core/components/pageEditor/FileThumbnail.tsx index 0356aa6c6..93ff3c2ab 100644 --- a/frontend/editor/src/core/components/pageEditor/FileThumbnail.tsx +++ b/frontend/editor/src/core/components/pageEditor/FileThumbnail.tsx @@ -23,7 +23,7 @@ import { FileId } from "@app/types/file"; import { PrivateContent } from "@app/components/shared/PrivateContent"; import { useFileActionTerminology } from "@app/hooks/useFileActionTerminology"; import { useFileActionIcons } from "@app/hooks/useFileActionIcons"; -import { downloadFile } from "@app/services/downloadService"; +import { downloadFileWithPolicy as downloadFile } from "@app/services/exportWithPolicy"; interface FileItem { id: FileId; @@ -98,6 +98,7 @@ const FileThumbnail = ({ void downloadFile({ data: maybeFile, filename: maybeFile.name || file.name || "download", + fileId: file.id, }); return; } diff --git a/frontend/editor/src/core/components/shared/FileSidebarFileItem.css b/frontend/editor/src/core/components/shared/FileSidebarFileItem.css index 81ca24ae0..02e6de971 100644 --- a/frontend/editor/src/core/components/shared/FileSidebarFileItem.css +++ b/frontend/editor/src/core/components/shared/FileSidebarFileItem.css @@ -36,6 +36,32 @@ background-color: rgba(59, 130, 246, 0.06); } +/* A policy-enforced file glows in that policy's accent colour so it's obvious + the file has had a policy applied: it pulses a few times to catch the eye and + then fades out (no lingering glow). */ +.file-sidebar-file-item.policy-enforced { + animation: policy-glow-fade 4.5s ease-in-out forwards; +} +@keyframes policy-glow-fade { + 0%, + 24%, + 48% { + box-shadow: + inset 0 0 0 1px color-mix(in srgb, var(--policy-glow) 30%, transparent), + 0 0 6px -2px var(--policy-glow); + } + 12%, + 36%, + 60% { + box-shadow: + inset 0 0 0 1px color-mix(in srgb, var(--policy-glow) 75%, transparent), + 0 0 16px 0 var(--policy-glow); + } + 100% { + box-shadow: 0 0 0 0 transparent; + } +} + .file-sidebar-file-item.selected { background-color: rgba(59, 130, 246, 0.12); } diff --git a/frontend/editor/src/core/components/shared/FileSidebarFileItem.tsx b/frontend/editor/src/core/components/shared/FileSidebarFileItem.tsx index 1b2f566f1..6c5ecaba3 100644 --- a/frontend/editor/src/core/components/shared/FileSidebarFileItem.tsx +++ b/frontend/editor/src/core/components/shared/FileSidebarFileItem.tsx @@ -132,6 +132,9 @@ export interface FileItemPolicyRef { name: string; /** CSS colour for the badge (matches the policy's accent). */ accentColor: string; + /** True only just after the policy was applied — drives the one-off glow, so + * it doesn't replay on every reload of an already-enforced file. */ + recent: boolean; } export interface FileItemProps { @@ -201,6 +204,9 @@ export function FileItem({ const handleMouseLeave = useCallback(() => setHoverRect(null), []); + // A just-applied policy (recent run) drives the one-off row glow. + const recentPolicy = policies.find((p) => p.recent); + // Reactive: tooltip appears as soon as both hover rect and thumbnail are ready const thumbPos = hoverRect && resolvedThumbnail @@ -214,7 +220,14 @@ export function FileItem({ <>
{category.desc}
-Output & retries