Skip to content

Commit 7323249

Browse files
committed
fix(files): normalize previews and avoid oversized retries
1 parent 4fdc32b commit 7323249

9 files changed

Lines changed: 23 additions & 12 deletions

File tree

‎apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/file-category.ts‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { getFileExtension } from '@/lib/uploads/utils/file-utils'
2+
import { normalizeMimeType } from '@/lib/uploads/utils/mime'
23
import { SUPPORTED_CODE_EXTENSIONS } from '@/lib/uploads/utils/validation'
34

45
const TEXT_EDITABLE_MIME_TYPES = new Set([
@@ -132,7 +133,7 @@ export type FileCategory =
132133
| 'unsupported'
133134

134135
export function resolveFileCategory(mimeType: string | null, filename: string): FileCategory {
135-
mimeType = mimeType?.split(';', 1)[0].trim().toLowerCase() ?? null
136+
mimeType = normalizeMimeType(mimeType)
136137
if (mimeType && TEXT_EDITABLE_MIME_TYPES.has(mimeType)) return 'text-editable'
137138
if (mimeType && IFRAME_PREVIEWABLE_MIME_TYPES.has(mimeType)) return 'iframe-previewable'
138139
if (mimeType && IMAGE_PREVIEWABLE_MIME_TYPES.has(mimeType)) return 'image-previewable'
@@ -147,15 +148,15 @@ export function resolveFileCategory(mimeType: string | null, filename: string):
147148
const sourceName = filename.replace(/\.(example|template|sample|dist|default)$/i, '')
148149
const nameKey = getFileExtension(sourceName) || sourceName.toLowerCase()
149150
if (TEXT_EDITABLE_EXTENSIONS.has(nameKey)) return 'text-editable'
150-
if (/^\.?env(?: \(\d+\))*(?:\.[\w.-]+)?$/i.test(filename)) return 'text-editable'
151-
if (/^dockerfile\.[\w.-]+$/i.test(filename)) return 'text-editable'
152151
if (IFRAME_PREVIEWABLE_EXTENSIONS.has(ext)) return 'iframe-previewable'
153152
if (IMAGE_PREVIEWABLE_EXTENSIONS.has(ext)) return 'image-previewable'
154153
if (AUDIO_PREVIEWABLE_EXTENSIONS.has(ext)) return 'audio-previewable'
155154
if (VIDEO_PREVIEWABLE_EXTENSIONS.has(ext)) return 'video-previewable'
156155
if (DOCX_PREVIEWABLE_EXTENSIONS.has(ext)) return 'docx-previewable'
157156
if (PPTX_PREVIEWABLE_EXTENSIONS.has(ext)) return 'pptx-previewable'
158157
if (XLSX_PREVIEWABLE_EXTENSIONS.has(ext)) return 'xlsx-previewable'
158+
if (/^\.?env(?: \(\d+\))*(?:\.[\w.-]+)?$/i.test(filename)) return 'text-editable'
159+
if (/^dockerfile\.[\w.-]+$/i.test(filename)) return 'text-editable'
159160

160161
return 'unsupported'
161162
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { useTheme } from 'next-themes'
66
import '@sim/emcn/components/code/code.css'
77
import { CSV_PREVIEW_MAX_ROWS } from '@/lib/api/contracts/workspace-file-table'
88
import { getFileExtension } from '@/lib/uploads/utils/file-utils'
9+
import { normalizeMimeType } from '@/lib/uploads/utils/mime'
910
import {
1011
SIM_ARTIFACT_SHELL,
1112
SIM_ARTIFACT_STYLESHEET,
@@ -50,6 +51,7 @@ const PREVIEWABLE_EXTENSIONS: Record<string, PreviewType> = {
5051
export const RICH_PREVIEWABLE_EXTENSIONS = new Set(Object.keys(PREVIEWABLE_EXTENSIONS))
5152

5253
export function resolvePreviewType(mimeType: string | null, filename: string): PreviewType {
54+
mimeType = normalizeMimeType(mimeType)
5355
if (mimeType && PREVIEWABLE_MIME_TYPES[mimeType]) return PREVIEWABLE_MIME_TYPES[mimeType]
5456
const ext = getFileExtension(filename)
5557
return PREVIEWABLE_EXTENSIONS[ext] ?? null

‎apps/sim/hooks/queries/workspace-files.test.tsx‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,9 @@ describe('useWorkspaceFileContent while a superseded key is being re-resolved',
290290
return { success: true, files: [] }
291291
})
292292

293-
const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } })
293+
const queryClient = new QueryClient({
294+
defaultOptions: { queries: { retry: false, retryDelay: 0 } },
295+
})
294296
const root: Root = createRoot(document.createElement('div'))
295297
let result: WorkspaceFileContentResult | undefined
296298

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import {
2525
updateWorkspaceFileDimensionsContract,
2626
} from '@/lib/api/contracts/workspace-files'
2727
import { uploadWorkspaceFileSession } from '@/lib/uploads/client/session-upload'
28-
import { readFileText } from '@/lib/uploads/client/text-content'
28+
import { FileTextSizeError, readFileText } from '@/lib/uploads/client/text-content'
2929
import type { UploadProgressEvent } from '@/lib/uploads/client/types'
3030
import type { WorkspaceFileRecord } from '@/lib/uploads/contexts/workspace'
3131
import type { UserFile } from '@/executor/types'
@@ -337,6 +337,7 @@ export function useWorkspaceFileContent(
337337
},
338338
enabled: !!workspaceId && !!fileId && !!key,
339339
staleTime: WORKSPACE_FILE_CONTENT_STALE_TIME,
340+
retry: (failureCount, error) => failureCount < 1 && !(error instanceof FileTextSizeError),
340341
refetchOnWindowFocus: options?.refetchOnWindowFocus === false ? false : 'always',
341342
refetchInterval: options?.refetchInterval ?? false,
342343
})

‎apps/sim/lib/uploads/client/download.ts‎

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { requestRaw } from '@/lib/api/client/request'
33
import { downloadWorkspaceFileItemsContract } from '@/lib/api/contracts/workspace-file-folders'
44
import { exportWorkspaceFileSnapshotContract } from '@/lib/api/contracts/workspace-files'
55
import type { WorkspaceFileRecord } from '@/lib/uploads/contexts/workspace'
6+
import { isMarkdownFile } from '@/lib/uploads/utils/file-utils'
7+
import { normalizeMimeType } from '@/lib/uploads/utils/mime'
68

79
/** Action-time content from the mounted viewer, scoped so another file cannot consume it. */
810
export interface FileDownloadSource {
@@ -43,10 +45,7 @@ export async function triggerFileDownload(
4345
record: WorkspaceFileRecord,
4446
source?: FileDownloadSource | null
4547
): Promise<void> {
46-
const isMarkdown =
47-
record.type === 'text/markdown' ||
48-
record.type === 'text/x-markdown' ||
49-
/\.(?:md|markdown)$/i.test(record.name)
48+
const isMarkdown = isMarkdownFile(record) || normalizeMimeType(record.type) === 'text/x-markdown'
5049

5150
const content =
5251
isMarkdown &&

‎apps/sim/lib/uploads/client/text-content.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ export const MAX_TEXT_PREVIEW_BYTES = PASTE_LIMITS.TEXT_EDITOR_BYTES
55
export const TEXT_PREVIEW_SIZE_MESSAGE =
66
'This file is too large to preview. Download it to view its contents.'
77

8-
class FileTextSizeError extends Error {
8+
/** A deterministic preview limit failure that must not trigger a download retry. */
9+
export class FileTextSizeError extends Error {
910
constructor() {
1011
super(TEXT_PREVIEW_SIZE_MESSAGE)
1112
this.name = 'FileTextSizeError'

‎apps/sim/lib/uploads/utils/file-utils.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { Logger } from '@sim/logger'
22
import { omit } from '@sim/utils/object'
33
import type { StorageContext } from '@/lib/uploads'
4+
import { normalizeMimeType } from '@/lib/uploads/utils/mime'
45
import {
56
ACCEPTED_FILE_TYPES,
67
isAlphanumericExtension,
@@ -199,7 +200,7 @@ export function getFileExtension(filename: string): string {
199200
* merge) to exactly the files that can be open in that editor.
200201
*/
201202
export function isMarkdownFile(file: { type?: string | null; name: string }): boolean {
202-
if (file.type === 'text/markdown') return true
203+
if (normalizeMimeType(file.type) === 'text/markdown') return true
203204
const ext = getFileExtension(file.name)
204205
return ext === 'md' || ext === 'markdown'
205206
}

‎apps/sim/lib/uploads/utils/mime.ts‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/** Returns the lowercase media type without parameters for format matching. */
2+
export function normalizeMimeType(mimeType: string | null | undefined): string {
3+
return mimeType?.split(';', 1)[0].trim().toLowerCase() ?? ''
4+
}

‎packages/testing/src/mocks/file-utils.mock.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -507,7 +507,7 @@ export const fileUtilsMockFns = {
507507
mockCreateFileContentFromBase64: vi.fn(createFileContentFromBase64),
508508
mockGetFileExtension: vi.fn(getFileExtension),
509509
mockIsMarkdownFile: vi.fn((file: { type?: string | null; name: string }) => {
510-
if (file.type === 'text/markdown') return true
510+
if (file.type?.split(';', 1)[0].trim().toLowerCase() === 'text/markdown') return true
511511
const ext = getFileExtension(file.name)
512512
return ext === 'md' || ext === 'markdown'
513513
}),

0 commit comments

Comments
 (0)