Skip to content

Commit 8ec18fe

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-identifies when the list arrives, re-running the image node view's memoized dimension read 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 8ec18fe

2 files changed

Lines changed: 25 additions & 4 deletions

File tree

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

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

132132
export function FileViewer(props: FileViewerProps) {
133133
const { contentSource, workspaceId } = props
134-
const imageDimensions = useWorkspaceImageDimensionsAdapter(workspaceId)
134+
// Only fetch/track dimensions when this adapter actually backs the source. A caller that supplies its
135+
// own `contentSource` (the public share page) doesn't use it — and there `workspaceId` is a share
136+
// token, so fetching the workspace files list would 404.
137+
const imageDimensions = useWorkspaceImageDimensionsAdapter(workspaceId, {
138+
enabled: !contentSource,
139+
})
135140
const source = useMemo(
136141
() => contentSource ?? createWorkspaceFileContentSource(workspaceId, imageDimensions),
137142
[contentSource, workspaceId, imageDimensions]

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

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -150,12 +150,28 @@ export function useWorkspaceFiles(
150150
* check plus mismatch-only reporting from the caller), so it never storms, never blocks render, and never
151151
* touches the collaborative document.
152152
*/
153-
export function useWorkspaceImageDimensionsAdapter(workspaceId: string): ImageDimensionsSource {
153+
export function useWorkspaceImageDimensionsAdapter(
154+
workspaceId: string,
155+
options?: { enabled?: boolean }
156+
): ImageDimensionsSource {
154157
const queryClient = useQueryClient()
158+
// Read dimensions from a REACTIVE `useWorkspaceFiles` subscription rather than a one-shot
159+
// `queryClient.getQueryData`. On a cold file-view load the list may not be cached when the image first
160+
// renders; a non-reactive read would return undefined then and never recover, because the adapter's
161+
// identity wouldn't change when the list later resolved. Subscribing re-identifies this adapter when
162+
// the list arrives, which re-runs the image node view's memoized dimension read so it reserves the box
163+
// from stored dimensions BEFORE the (slower) image download finishes — instead of measuring on load
164+
// and reflowing on open. The query key is shared, so where a surrounding view already loads the list
165+
// this adds no fetch; otherwise it seeds it.
166+
//
167+
// `enabled` gates it off for callers that don't use this adapter as their source (the public share page
168+
// passes a token-scoped `contentSource`, and its `workspaceId` is actually a share token — fetching
169+
// `/api/workspaces/<token>/files` there would 404).
170+
const { data: files } = useWorkspaceFiles(workspaceId, 'active', options)
155171
return useMemo<ImageDimensionsSource>(() => {
156172
const listKey = workspaceFilesKeys.list(workspaceId, 'active')
157173
const findRecord = (src: string | undefined): WorkspaceFileRecord | undefined =>
158-
findWorkspaceFileBySrc(queryClient.getQueryData<WorkspaceFileRecord[]>(listKey), src)
174+
findWorkspaceFileBySrc(files, src)
159175
return {
160176
getImageDimensions: (src) => {
161177
const record = findRecord(src)
@@ -194,7 +210,7 @@ export function useWorkspaceImageDimensionsAdapter(workspaceId: string): ImageDi
194210
.catch(() => {})
195211
},
196212
}
197-
}, [queryClient, workspaceId])
213+
}, [files, queryClient, workspaceId])
198214
}
199215

200216
/**

0 commit comments

Comments
 (0)