From 31850aefd125104c41447a5a0e8fdba1fba107a6 Mon Sep 17 00:00:00 2001 From: waleed Date: Fri, 1 May 2026 23:48:36 -0700 Subject: [PATCH 01/18] feat(files): resolve workspace file page URLs to raw content in markdown preview --- apps/sim/app/api/files/view/[id]/route.ts | 44 +++++++++++++++++++ .../components/file-viewer/preview-panel.tsx | 29 ++++++++---- .../sim/lib/api/contracts/storage-transfer.ts | 12 +++++ apps/sim/lib/uploads/server/metadata.ts | 18 ++++++++ 4 files changed, 95 insertions(+), 8 deletions(-) create mode 100644 apps/sim/app/api/files/view/[id]/route.ts diff --git a/apps/sim/app/api/files/view/[id]/route.ts b/apps/sim/app/api/files/view/[id]/route.ts new file mode 100644 index 0000000000..5fcd8086e5 --- /dev/null +++ b/apps/sim/app/api/files/view/[id]/route.ts @@ -0,0 +1,44 @@ +import { createLogger } from '@sim/logger' +import type { NextRequest } from 'next/server' +import { NextResponse } from 'next/server' +import { fileViewContract } from '@/lib/api/contracts/storage-transfer' +import { parseRequest } from '@/lib/api/server' +import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { USE_BLOB_STORAGE } from '@/lib/uploads/config' +import { getFileMetadataById } from '@/lib/uploads/server/metadata' +import { verifyFileAccess } from '@/app/api/files/authorization' + +const logger = createLogger('FilesViewAPI') + +export const GET = withRouteHandler( + async (request: NextRequest, context: { params: Promise<{ id: string }> }) => { + const parsed = await parseRequest(fileViewContract, request, await context.params) + if (!parsed.success) return parsed.response + + const { id } = parsed.data.params + + const authResult = await checkSessionOrInternalAuth(request, { requireWorkflowId: false }) + if (!authResult.success || !authResult.userId) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) + } + + const record = await getFileMetadataById(id) + if (!record) { + logger.warn('File not found by ID', { id }) + return NextResponse.json({ error: 'Not found' }, { status: 404 }) + } + + const hasAccess = await verifyFileAccess(record.key, authResult.userId) + if (!hasAccess) { + logger.warn('Unauthorized file view attempt', { id, userId: authResult.userId }) + return NextResponse.json({ error: 'Forbidden' }, { status: 403 }) + } + + const storagePrefix = USE_BLOB_STORAGE ? 'blob' : 's3' + const servePath = `/api/files/serve/${storagePrefix}/${encodeURIComponent(record.key)}` + logger.info('Redirecting file view to serve path', { id, servePath }) + + return NextResponse.redirect(new URL(servePath, request.url), { status: 302 }) + } +) diff --git a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/preview-panel.tsx b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/preview-panel.tsx index e98bb7622c..30de43f703 100644 --- a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/preview-panel.tsx +++ b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/preview-panel.tsx @@ -562,14 +562,17 @@ const STATIC_MARKDOWN_COMPONENTS = { ) }, hr: () =>
, - img: ({ src, alt }: React.ImgHTMLAttributes) => ( - {alt - ), + img: ({ src, alt }: React.ImgHTMLAttributes) => { + const resolvedSrc = resolveSimFileUrl(src) + return ( + {alt + ) + }, table: ({ children }: { children?: React.ReactNode }) => (
{children}
@@ -736,6 +739,16 @@ function AnchorRenderer({ href, children }: { href?: string; children?: React.Re ) } +const SIM_FILES_PAGE_PATTERN = + /(?:https?:\/\/[^/]+)?\/workspace\/[a-f0-9-]{36}\/files\/([a-f0-9-]{36})(?:[?#].*)?$/ + +function resolveSimFileUrl(src: string | undefined): string | undefined { + if (!src) return src + const match = SIM_FILES_PAGE_PATTERN.exec(src) + if (!match) return src + return `/api/files/view/${match[1]}` +} + const MARKDOWN_COMPONENTS = { ...STATIC_MARKDOWN_COMPONENTS, a: AnchorRenderer, diff --git a/apps/sim/lib/api/contracts/storage-transfer.ts b/apps/sim/lib/api/contracts/storage-transfer.ts index 03a88ad58c..8e70e8f26b 100644 --- a/apps/sim/lib/api/contracts/storage-transfer.ts +++ b/apps/sim/lib/api/contracts/storage-transfer.ts @@ -450,6 +450,10 @@ export const fileServeQuerySchema = z.object({ raw: z.string().nullish(), }) +export const fileViewParamsSchema = z.object({ + id: z.string().uuid('File ID must be a valid UUID'), +}) + export const boxUploadContract = defineRouteContract({ method: 'POST', path: '/api/tools/box/upload', @@ -696,6 +700,13 @@ export const fileServeContract = defineRouteContract({ response: { mode: 'binary' }, }) +export const fileViewContract = defineRouteContract({ + method: 'GET', + path: '/api/files/view/[id]', + params: fileViewParamsSchema, + response: { mode: 'binary' }, +}) + export type BoxUploadBody = ContractBodyInput export type BoxUploadResponse = ContractJsonResponse export type DropboxUploadBody = ContractBodyInput @@ -737,3 +748,4 @@ export type TokenBoundMultipartBody = z.output export type FileServeParams = ContractParamsInput export type FileServeQuery = ContractQueryInput +export type FileViewParams = ContractParamsInput diff --git a/apps/sim/lib/uploads/server/metadata.ts b/apps/sim/lib/uploads/server/metadata.ts index 38162a9398..7edc404946 100644 --- a/apps/sim/lib/uploads/server/metadata.ts +++ b/apps/sim/lib/uploads/server/metadata.ts @@ -140,6 +140,24 @@ export async function getFileMetadataByKey( return record ?? null } +/** + * Get file metadata by ID + */ +export async function getFileMetadataById( + id: string, + options?: { includeDeleted?: boolean } +): Promise { + const { includeDeleted = false } = options ?? {} + const conditions = [eq(workspaceFiles.id, id)] + if (!includeDeleted) conditions.push(isNull(workspaceFiles.deletedAt)) + const [record] = await db + .select() + .from(workspaceFiles) + .where(conditions.length > 1 ? and(...conditions) : conditions[0]) + .limit(1) + return record ?? null +} + /** * Get file metadata by context with optional workspaceId/userId filters */ From 46f0326ac3acba8e4d81e1f449f8a9e491f71fb8 Mon Sep 17 00:00:00 2001 From: waleed Date: Sat, 2 May 2026 00:00:19 -0700 Subject: [PATCH 02/18] refactor(files): replace regex URL matching with URL constructor in resolveSimFileUrl --- .../components/file-viewer/preview-panel.tsx | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/preview-panel.tsx b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/preview-panel.tsx index 30de43f703..f4060df7fa 100644 --- a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/preview-panel.tsx +++ b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/preview-panel.tsx @@ -423,6 +423,20 @@ const MermaidDiagram = memo(function MermaidDiagram({ return null }) +function resolveSimFileUrl(src: string | undefined): string | undefined { + if (!src) return src + try { + const { pathname } = new URL(src, 'http://placeholder') + const [, seg1, , seg3, fileId] = pathname.split('/') + if (seg1 === 'workspace' && seg3 === 'files' && fileId) { + return `/api/files/view/${fileId}` + } + } catch { + // not a parseable URL + } + return src +} + const STATIC_MARKDOWN_COMPONENTS = { pre: ({ children }: { children?: React.ReactNode }) => <>{children}, p: ({ children }: { children?: React.ReactNode }) => ( @@ -739,16 +753,6 @@ function AnchorRenderer({ href, children }: { href?: string; children?: React.Re ) } -const SIM_FILES_PAGE_PATTERN = - /(?:https?:\/\/[^/]+)?\/workspace\/[a-f0-9-]{36}\/files\/([a-f0-9-]{36})(?:[?#].*)?$/ - -function resolveSimFileUrl(src: string | undefined): string | undefined { - if (!src) return src - const match = SIM_FILES_PAGE_PATTERN.exec(src) - if (!match) return src - return `/api/files/view/${match[1]}` -} - const MARKDOWN_COMPONENTS = { ...STATIC_MARKDOWN_COMPONENTS, a: AnchorRenderer, From deb687b5be9242eb153b128332d1c0ab3ec89482 Mon Sep 17 00:00:00 2001 From: waleed Date: Sat, 2 May 2026 00:18:56 -0700 Subject: [PATCH 03/18] fix(files): render mermaid diagrams in markdown preview using design tokens Streamdown intercepts fenced code blocks before components.code is called, so the previous mermaid branch in the code renderer was dead code. Add a remarkMermaid plugin that transforms mermaid code nodes at the MDAST stage via data.hName/hProperties so remark-rehype emits a element that our MermaidDiagram component handles. This avoids Streamdown's built-in mermaid renderer which uses mismatched Tailwind semantic classes. --- .../components/file-viewer/preview-panel.tsx | 39 ++++++++++++++++--- 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/preview-panel.tsx b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/preview-panel.tsx index f4060df7fa..85a5e9f1b9 100644 --- a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/preview-panel.tsx +++ b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/preview-panel.tsx @@ -98,6 +98,33 @@ export const PreviewPanel = memo(function PreviewPanel({ const CALLOUT_TYPES = new Set(['NOTE', 'TIP', 'WARNING', 'IMPORTANT', 'CAUTION']) +function remarkMermaid() { + return (tree: { type: string; children?: unknown[] }) => { + function processNode(node: { + type: string + children?: unknown[] + lang?: string + value?: string + data?: Record + }) { + if (!node.children) return + for (const child of node.children) { + const c = child as typeof node + if (c.type === 'code' && c.lang === 'mermaid') { + c.data = { + hName: 'mermaid-diagram', + hProperties: { definition: c.value ?? '' }, + hChildren: [], + } + } else { + processNode(c) + } + } + } + processNode(tree) + } +} + function remarkCallouts() { return (tree: { type: string; children?: unknown[] }) => { function processNode(node: { type: string; children?: unknown[] }) { @@ -142,7 +169,7 @@ function remarkCallouts() { } } -const REMARK_PLUGINS = [remarkGfm, remarkBreaks, remarkCallouts] +const REMARK_PLUGINS = [remarkGfm, remarkBreaks, remarkMermaid, remarkCallouts] const REHYPE_PLUGINS = [rehypeSlug] /** @@ -439,6 +466,10 @@ function resolveSimFileUrl(src: string | undefined): string | undefined { const STATIC_MARKDOWN_COMPONENTS = { pre: ({ children }: { children?: React.ReactNode }) => <>{children}, + 'mermaid-diagram': ({ definition }: { definition?: string }) => { + const isStreaming = useContext(MermaidStreamingCtx) + return + }, p: ({ children }: { children?: React.ReactNode }) => (

{children} @@ -507,15 +538,10 @@ const STATIC_MARKDOWN_COMPONENTS = { ) }, code: ({ children, className }: { children?: React.ReactNode; className?: string }) => { - const isMarkdownStreaming = useContext(MermaidStreamingCtx) const langMatch = className?.match(/language-(\w+)/) const langRaw = langMatch?.[1] ?? '' const codeString = extractTextContent(children) - if (langRaw === 'mermaid') { - return - } - if (!codeString) { return ( @@ -849,6 +875,7 @@ const MarkdownPreview = memo(function MarkdownPreview({ remarkPlugins={REMARK_PLUGINS} rehypePlugins={REHYPE_PLUGINS} components={MARKDOWN_COMPONENTS} + allowedTags={{ 'mermaid-diagram': ['definition'] }} > {markdownContent} From ea7817c6d21754daeb852fdd8031588e9dda70a1 Mon Sep 17 00:00:00 2001 From: waleed Date: Sat, 2 May 2026 00:25:56 -0700 Subject: [PATCH 04/18] fix(trace): reduce default tree pane width from 360 to 280 --- .../components/log-details/components/trace-view/trace-view.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/sim/app/workspace/[workspaceId]/logs/components/log-details/components/trace-view/trace-view.tsx b/apps/sim/app/workspace/[workspaceId]/logs/components/log-details/components/trace-view/trace-view.tsx index c2baba8712..529285847e 100644 --- a/apps/sim/app/workspace/[workspaceId]/logs/components/log-details/components/trace-view/trace-view.tsx +++ b/apps/sim/app/workspace/[workspaceId]/logs/components/log-details/components/trace-view/trace-view.tsx @@ -45,7 +45,7 @@ import { } from '@/app/workspace/[workspaceId]/logs/components/log-details/utils' import { useCodeViewerFeatures } from '@/hooks/use-code-viewer' -const DEFAULT_TREE_PANE_WIDTH = 360 +const DEFAULT_TREE_PANE_WIDTH = 280 const MIN_TREE_PANE_WIDTH = 200 const MAX_TREE_PANE_WIDTH = 600 const INDENT_PX = 12 From 9e5dbef1501be0281c56faa698123c1fcf24e857 Mon Sep 17 00:00:00 2001 From: waleed Date: Sat, 2 May 2026 00:26:13 -0700 Subject: [PATCH 05/18] fix(files): guard resolveSimFileUrl against cross-origin URL hijacking --- .../files/components/file-viewer/preview-panel.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/preview-panel.tsx b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/preview-panel.tsx index 85a5e9f1b9..226ac06c37 100644 --- a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/preview-panel.tsx +++ b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/preview-panel.tsx @@ -453,8 +453,11 @@ const MermaidDiagram = memo(function MermaidDiagram({ function resolveSimFileUrl(src: string | undefined): string | undefined { if (!src) return src try { - const { pathname } = new URL(src, 'http://placeholder') - const [, seg1, , seg3, fileId] = pathname.split('/') + const parsed = new URL(src, 'http://placeholder') + const isRelative = parsed.origin === 'http://placeholder' + const isSameOrigin = typeof window !== 'undefined' && parsed.origin === window.location.origin + if (!isRelative && !isSameOrigin) return src + const [, seg1, , seg3, fileId] = parsed.pathname.split('/') if (seg1 === 'workspace' && seg3 === 'files' && fileId) { return `/api/files/view/${fileId}` } From 134e30d709fff569d0c23e74eeae19ced07da4f9 Mon Sep 17 00:00:00 2001 From: waleed Date: Sat, 2 May 2026 00:27:15 -0700 Subject: [PATCH 06/18] refactor(files): use getBrowserOrigin() instead of window.location.origin --- .../files/components/file-viewer/preview-panel.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/preview-panel.tsx b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/preview-panel.tsx index 226ac06c37..e0c73592bd 100644 --- a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/preview-panel.tsx +++ b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/preview-panel.tsx @@ -22,6 +22,7 @@ import 'prismjs/components/prism-sql' import 'prismjs/components/prism-python' import { cn } from '@/lib/core/utils/cn' import { extractTextContent } from '@/lib/core/utils/react-node-text' +import { getBrowserOrigin } from '@/lib/core/utils/urls' import { getFileExtension } from '@/lib/uploads/utils/file-utils' import { useAutoScroll } from '@/hooks/use-auto-scroll' import { DataTable } from './data-table' @@ -455,7 +456,7 @@ function resolveSimFileUrl(src: string | undefined): string | undefined { try { const parsed = new URL(src, 'http://placeholder') const isRelative = parsed.origin === 'http://placeholder' - const isSameOrigin = typeof window !== 'undefined' && parsed.origin === window.location.origin + const isSameOrigin = parsed.origin === getBrowserOrigin() if (!isRelative && !isSameOrigin) return src const [, seg1, , seg3, fileId] = parsed.pathname.split('/') if (seg1 === 'workspace' && seg3 === 'files' && fileId) { From a1f7037f3897c4d52abea9e175e83d910ebb2c1c Mon Sep 17 00:00:00 2001 From: waleed Date: Sat, 2 May 2026 00:28:03 -0700 Subject: [PATCH 07/18] fix(trace): reduce default tree pane width to 240 --- .../components/log-details/components/trace-view/trace-view.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/sim/app/workspace/[workspaceId]/logs/components/log-details/components/trace-view/trace-view.tsx b/apps/sim/app/workspace/[workspaceId]/logs/components/log-details/components/trace-view/trace-view.tsx index 529285847e..19f27a1ace 100644 --- a/apps/sim/app/workspace/[workspaceId]/logs/components/log-details/components/trace-view/trace-view.tsx +++ b/apps/sim/app/workspace/[workspaceId]/logs/components/log-details/components/trace-view/trace-view.tsx @@ -45,7 +45,7 @@ import { } from '@/app/workspace/[workspaceId]/logs/components/log-details/utils' import { useCodeViewerFeatures } from '@/hooks/use-code-viewer' -const DEFAULT_TREE_PANE_WIDTH = 280 +const DEFAULT_TREE_PANE_WIDTH = 240 const MIN_TREE_PANE_WIDTH = 200 const MAX_TREE_PANE_WIDTH = 600 const INDENT_PX = 12 From 701ad2b175424303f1ce08d4e911ddeb4d38bf01 Mon Sep 17 00:00:00 2001 From: waleed Date: Sat, 2 May 2026 00:33:22 -0700 Subject: [PATCH 08/18] fix(files): narrow img src type before passing to resolveSimFileUrl --- .../files/components/file-viewer/preview-panel.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/preview-panel.tsx b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/preview-panel.tsx index e0c73592bd..7bf844490a 100644 --- a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/preview-panel.tsx +++ b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/preview-panel.tsx @@ -607,7 +607,7 @@ const STATIC_MARKDOWN_COMPONENTS = { }, hr: () =>


, img: ({ src, alt }: React.ImgHTMLAttributes) => { - const resolvedSrc = resolveSimFileUrl(src) + const resolvedSrc = resolveSimFileUrl(typeof src === 'string' ? src : undefined) return ( Date: Sat, 2 May 2026 00:34:05 -0700 Subject: [PATCH 09/18] fix(files): use narrow props type for img renderer instead of runtime narrowing --- .../files/components/file-viewer/preview-panel.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/preview-panel.tsx b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/preview-panel.tsx index 7bf844490a..f944e7513a 100644 --- a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/preview-panel.tsx +++ b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/preview-panel.tsx @@ -606,8 +606,8 @@ const STATIC_MARKDOWN_COMPONENTS = { ) }, hr: () =>
, - img: ({ src, alt }: React.ImgHTMLAttributes) => { - const resolvedSrc = resolveSimFileUrl(typeof src === 'string' ? src : undefined) + img: ({ src, alt }: { src?: string; alt?: string }) => { + const resolvedSrc = resolveSimFileUrl(src) return ( Date: Sat, 2 May 2026 00:40:46 -0700 Subject: [PATCH 10/18] fix(files): accept full ImgHTMLAttributes for img renderer to satisfy Streamdown Components type --- .../files/components/file-viewer/preview-panel.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/preview-panel.tsx b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/preview-panel.tsx index f944e7513a..7bf844490a 100644 --- a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/preview-panel.tsx +++ b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/preview-panel.tsx @@ -606,8 +606,8 @@ const STATIC_MARKDOWN_COMPONENTS = { ) }, hr: () =>
, - img: ({ src, alt }: { src?: string; alt?: string }) => { - const resolvedSrc = resolveSimFileUrl(src) + img: ({ src, alt }: React.ImgHTMLAttributes) => { + const resolvedSrc = resolveSimFileUrl(typeof src === 'string' ? src : undefined) return ( Date: Sat, 2 May 2026 00:56:20 -0700 Subject: [PATCH 11/18] fix(files): treat null getBrowserOrigin as same-origin to prevent SSR hydration mismatch --- .../files/components/file-viewer/preview-panel.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/preview-panel.tsx b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/preview-panel.tsx index 7bf844490a..9f35f4bd30 100644 --- a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/preview-panel.tsx +++ b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/preview-panel.tsx @@ -456,7 +456,9 @@ function resolveSimFileUrl(src: string | undefined): string | undefined { try { const parsed = new URL(src, 'http://placeholder') const isRelative = parsed.origin === 'http://placeholder' - const isSameOrigin = parsed.origin === getBrowserOrigin() + const browserOrigin = getBrowserOrigin() + // null means SSR — treat as same-origin so server and client produce identical output + const isSameOrigin = browserOrigin === null || parsed.origin === browserOrigin if (!isRelative && !isSameOrigin) return src const [, seg1, , seg3, fileId] = parsed.pathname.split('/') if (seg1 === 'workspace' && seg3 === 'files' && fileId) { From 95b186d7911a55b7cf96b1a21d3a32158201a72d Mon Sep 17 00:00:00 2001 From: waleed Date: Sat, 2 May 2026 01:09:41 -0700 Subject: [PATCH 12/18] improvement(files): use centered aspect-ratio skeleton for mmd file preview --- .../files/components/file-viewer/preview-panel.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/preview-panel.tsx b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/preview-panel.tsx index 9f35f4bd30..497467c50a 100644 --- a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/preview-panel.tsx +++ b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/preview-panel.tsx @@ -10,7 +10,7 @@ import { Streamdown } from 'streamdown' import 'streamdown/styles.css' import { toError } from '@sim/utils/errors' import { generateShortId } from '@sim/utils/id' -import { Checkbox, CopyCodeButton, highlight, languages } from '@/components/emcn' +import { Checkbox, CopyCodeButton, highlight, languages, Skeleton } from '@/components/emcn' import '@/components/emcn/components/code/code.css' import 'prismjs/components/prism-bash' import 'prismjs/components/prism-css' @@ -446,6 +446,13 @@ const MermaidDiagram = memo(function MermaidDiagram({ } if (!trimmedDefinition || !svg || renderedDefinition !== trimmedDefinition) { + if (zoomable) { + return ( +
+ +
+ ) + } return } return null From 2d3993f2898fa25ad4c8d4dedb122b4e1571f801 Mon Sep 17 00:00:00 2001 From: waleed Date: Sat, 2 May 2026 01:13:39 -0700 Subject: [PATCH 13/18] improvement(files): diagram-shaped skeleton card for mmd file preview --- .../components/file-viewer/preview-panel.tsx | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/preview-panel.tsx b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/preview-panel.tsx index 497467c50a..6cc2e22bfe 100644 --- a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/preview-panel.tsx +++ b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/preview-panel.tsx @@ -448,8 +448,23 @@ const MermaidDiagram = memo(function MermaidDiagram({ if (!trimmedDefinition || !svg || renderedDefinition !== trimmedDefinition) { if (zoomable) { return ( -
- +
+
+
+ + +
+ + + +
+ + +
+
) } From 446d9e3aa61cf545f3063321a8723d7bd2eb8b8a Mon Sep 17 00:00:00 2001 From: waleed Date: Sat, 2 May 2026 01:27:55 -0700 Subject: [PATCH 14/18] fix(files): only rewrite relative workspace file URLs to avoid hydration mismatch --- .../files/components/file-viewer/preview-panel.tsx | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/preview-panel.tsx b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/preview-panel.tsx index 6cc2e22bfe..14d6a1655a 100644 --- a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/preview-panel.tsx +++ b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/preview-panel.tsx @@ -22,7 +22,6 @@ import 'prismjs/components/prism-sql' import 'prismjs/components/prism-python' import { cn } from '@/lib/core/utils/cn' import { extractTextContent } from '@/lib/core/utils/react-node-text' -import { getBrowserOrigin } from '@/lib/core/utils/urls' import { getFileExtension } from '@/lib/uploads/utils/file-utils' import { useAutoScroll } from '@/hooks/use-auto-scroll' import { DataTable } from './data-table' @@ -477,11 +476,7 @@ function resolveSimFileUrl(src: string | undefined): string | undefined { if (!src) return src try { const parsed = new URL(src, 'http://placeholder') - const isRelative = parsed.origin === 'http://placeholder' - const browserOrigin = getBrowserOrigin() - // null means SSR — treat as same-origin so server and client produce identical output - const isSameOrigin = browserOrigin === null || parsed.origin === browserOrigin - if (!isRelative && !isSameOrigin) return src + if (parsed.origin !== 'http://placeholder') return src const [, seg1, , seg3, fileId] = parsed.pathname.split('/') if (seg1 === 'workspace' && seg3 === 'files' && fileId) { return `/api/files/view/${fileId}` From 9cb28da008a38cf2c943d6d364ec0504b04b9336 Mon Sep 17 00:00:00 2001 From: waleed Date: Sat, 2 May 2026 01:37:24 -0700 Subject: [PATCH 15/18] fix(files): pass context not resolved params to parseRequest in view route --- apps/sim/app/api/files/view/[id]/route.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/sim/app/api/files/view/[id]/route.ts b/apps/sim/app/api/files/view/[id]/route.ts index 5fcd8086e5..681d00f443 100644 --- a/apps/sim/app/api/files/view/[id]/route.ts +++ b/apps/sim/app/api/files/view/[id]/route.ts @@ -13,7 +13,7 @@ const logger = createLogger('FilesViewAPI') export const GET = withRouteHandler( async (request: NextRequest, context: { params: Promise<{ id: string }> }) => { - const parsed = await parseRequest(fileViewContract, request, await context.params) + const parsed = await parseRequest(fileViewContract, request, context) if (!parsed.success) return parsed.response const { id } = parsed.data.params From 6642893df32145c5947ea88005c0cfd5da1f200a Mon Sep 17 00:00:00 2001 From: waleed Date: Sat, 2 May 2026 01:38:50 -0700 Subject: [PATCH 16/18] fix(files): simplify mmd file skeleton to match pptx card pattern --- .../components/file-viewer/preview-panel.tsx | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/preview-panel.tsx b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/preview-panel.tsx index 14d6a1655a..98e305634f 100644 --- a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/preview-panel.tsx +++ b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/preview-panel.tsx @@ -449,19 +449,19 @@ const MermaidDiagram = memo(function MermaidDiagram({ return (
-
- - -
- - - +
+
+ + + +
+
+ +
- -
From 380388c2b59fdb78f59744c4a7caa905bc6457e5 Mon Sep 17 00:00:00 2001 From: waleed Date: Sat, 2 May 2026 01:40:30 -0700 Subject: [PATCH 17/18] =?UTF-8?q?fix(files):=20match=20mmd=20skeleton=20to?= =?UTF-8?q?=20actual=20rendered=20layout=20=E2=80=94=20full-height=20zooma?= =?UTF-8?q?ble=20area?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/file-viewer/preview-panel.tsx | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/preview-panel.tsx b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/preview-panel.tsx index 98e305634f..be5e581418 100644 --- a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/preview-panel.tsx +++ b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/preview-panel.tsx @@ -447,23 +447,8 @@ const MermaidDiagram = memo(function MermaidDiagram({ if (!trimmedDefinition || !svg || renderedDefinition !== trimmedDefinition) { if (zoomable) { return ( -
-
-
-
- - - -
-
- - -
-
-
+
+
) } From 66207c43ff445e80a936efb2a4d7ac2ca25ffeb5 Mon Sep 17 00:00:00 2001 From: waleed Date: Sat, 2 May 2026 01:49:21 -0700 Subject: [PATCH 18/18] fix(sidebar): left-align folder lock icon next to name --- .../components/folder-item/folder-item.tsx | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/folder-item/folder-item.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/folder-item/folder-item.tsx index c3bb40f4d5..cf54344655 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/folder-item/folder-item.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/folder-item/folder-item.tsx @@ -545,18 +545,20 @@ export function FolderItem({ /> ) : (
- - {folder.name} - - {folder.locked && ( - - )} +
+ + {folder.name} + + {folder.locked && ( + + )} +