Skip to content

Commit affb1d8

Browse files
committed
fix(files): reserve embedded-image space on direct file-view loads
An embedded image in a markdown file reshifted on every open: it loaded ~2.3s in with no reserved box and shoved everything below it down (CLS ~0.17). The intrinsic dimensions ARE stored server-side, but the image node view never read them at render. useWorkspaceImageDimensionsAdapter read the active files list via queryClient.getQueryData — non-reactively — so on a cold file-view load it returned null at first render and, because the adapter identity was stable, never re-checked when the list later resolved. Read the list via a reactive useWorkspaceFiles subscription instead: the adapter re-runs the image node view's memoized dimension read when the list resolves, so it reserves the box from the stored dimensions before the (slower) image download finishes. The query key is shared, so it dedupes with surrounding views. Gate it behind `enabled` (driven by the absence of a caller-supplied contentSource) so the public share page — which passes a share token as workspaceId — doesn't fire a 404. Verified in a CLS harness: dims present -> 0 shift; dims absent -> 0.20.
1 parent 9b80fdd commit affb1d8

2 files changed

Lines changed: 22 additions & 11 deletions

File tree

apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/file-viewer.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,10 @@ interface FileViewerProps {
131131

132132
export function FileViewer(props: FileViewerProps) {
133133
const { contentSource, workspaceId } = props
134-
const imageDimensions = useWorkspaceImageDimensionsAdapter(workspaceId)
134+
// A caller-supplied contentSource means the adapter is unused (and its `workspaceId` may be a share token).
135+
const imageDimensions = useWorkspaceImageDimensionsAdapter(workspaceId, {
136+
enabled: !contentSource,
137+
})
135138
const source = useMemo(
136139
() => contentSource ?? createWorkspaceFileContentSource(workspaceId, imageDimensions),
137140
[contentSource, workspaceId, imageDimensions]

apps/sim/hooks/queries/workspace-files.ts

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -142,20 +142,28 @@ export function useWorkspaceFiles(
142142
}
143143

144144
/**
145-
* Back the file content source's image-dimension capability with workspace file metadata. Reads intrinsic
146-
* dimensions synchronously from the already-loaded active file list (so a stored image reserves its box on
147-
* the first render), and persists the browser's measured dimensions when they're absent or disagree with
148-
* what's stored — an overwrite, so a stale value (left over after a content swap, or a non-EXIF-corrected
149-
* one) self-corrects rather than sticking. The write is fire-and-forget and de-duped (an exact-match cache
150-
* check plus mismatch-only reporting from the caller), so it never storms, never blocks render, and never
151-
* touches the collaborative document.
145+
* Back the file content source's image-dimension capability with workspace file metadata. Subscribes to
146+
* the active file list ({@link useWorkspaceFiles}) and reads each image's stored intrinsic dimensions from
147+
* it, so a stored image reserves its box before it downloads. A reactive read (not a one-shot
148+
* `getQueryData`), so it also works on a cold direct file-view load where the list isn't cached until after
149+
* the image first renders: the subscription re-runs the node view's dimension read once the list resolves.
150+
* Persists the browser's measured dimensions when they're absent or disagree with what's stored — an
151+
* overwrite, so a stale value (left over after a content swap, or a non-EXIF-corrected one) self-corrects
152+
* rather than sticking. The write is fire-and-forget and de-duped (an exact-match cache check plus
153+
* mismatch-only reporting from the caller), so it never storms, never blocks render, and never touches the
154+
* collaborative document. `options.enabled` turns the subscription off for callers that supply their own
155+
* content source (the public share page, whose `workspaceId` is a share token that would 404).
152156
*/
153-
export function useWorkspaceImageDimensionsAdapter(workspaceId: string): ImageDimensionsSource {
157+
export function useWorkspaceImageDimensionsAdapter(
158+
workspaceId: string,
159+
options?: { enabled?: boolean }
160+
): ImageDimensionsSource {
154161
const queryClient = useQueryClient()
162+
const { data: files } = useWorkspaceFiles(workspaceId, 'active', options)
155163
return useMemo<ImageDimensionsSource>(() => {
156164
const listKey = workspaceFilesKeys.list(workspaceId, 'active')
157165
const findRecord = (src: string | undefined): WorkspaceFileRecord | undefined =>
158-
findWorkspaceFileBySrc(queryClient.getQueryData<WorkspaceFileRecord[]>(listKey), src)
166+
findWorkspaceFileBySrc(files, src)
159167
return {
160168
getImageDimensions: (src) => {
161169
const record = findRecord(src)
@@ -194,7 +202,7 @@ export function useWorkspaceImageDimensionsAdapter(workspaceId: string): ImageDi
194202
.catch(() => {})
195203
},
196204
}
197-
}, [queryClient, workspaceId])
205+
}, [files, queryClient, workspaceId])
198206
}
199207

200208
/**

0 commit comments

Comments
 (0)