From 694f4a5895a95fde52ede808cad955820a358786 Mon Sep 17 00:00:00 2001 From: Waleed Date: Wed, 8 Apr 2026 13:25:15 -0700 Subject: [PATCH 01/11] fix: merge subblock values in auto-layout to prevent losing router context (#4055) Auto-layout was reading from getWorkflowState() without merging subblock store values, then persisting stale subblock data to the database. This caused runtime-edited values (e.g. router_v2 context) to be overwritten with their initial/empty values whenever auto-layout was triggered. --- .../w/[workflowId]/utils/auto-layout-utils.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/auto-layout-utils.ts b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/auto-layout-utils.ts index 69a15ec7d67..e9a203ef728 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/auto-layout-utils.ts +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/auto-layout-utils.ts @@ -4,6 +4,7 @@ import { DEFAULT_LAYOUT_PADDING, DEFAULT_VERTICAL_SPACING, } from '@/lib/workflows/autolayout/constants' +import { mergeSubblockState } from '@/stores/workflows/utils' import { useWorkflowStore } from '@/stores/workflows/workflow/store' const logger = createLogger('AutoLayoutUtils') @@ -109,10 +110,12 @@ export async function applyAutoLayoutAndUpdateStore( return { success: false, error: errorMessage } } - // Update workflow store immediately with new positions + const layoutedBlocks = result.data?.layoutedBlocks || blocks + const mergedBlocks = mergeSubblockState(layoutedBlocks, workflowId) + const newWorkflowState = { ...workflowStore.getWorkflowState(), - blocks: result.data?.layoutedBlocks || blocks, + blocks: mergedBlocks, lastSaved: Date.now(), } @@ -167,9 +170,10 @@ export async function applyAutoLayoutAndUpdateStore( }) // Revert the store changes since database save failed + const revertBlocks = mergeSubblockState(blocks, workflowId) useWorkflowStore.getState().replaceWorkflowState({ ...workflowStore.getWorkflowState(), - blocks, + blocks: revertBlocks, lastSaved: workflowStore.lastSaved, }) From 91ce55e547668158dc2cff2ab144220ea37d5085 Mon Sep 17 00:00:00 2001 From: Waleed Date: Wed, 8 Apr 2026 14:07:31 -0700 Subject: [PATCH 02/11] fix(whitelabeling): eliminate logo flash by fetching org settings server-side (#4057) * fix(whitelabeling): eliminate logo flash by fetching org settings server-side * improvement(whitelabeling): add SVG support for logo and wordmark uploads * skelly in workspace header * remove dead code * fix(whitelabeling): hydration error, SVG support, skeleton shimmer, dead code removal * fix(whitelabeling): blob preview dep cycle and missing color fallback * fix(whitelabeling): use brand-accent as color fallback when workspace color is undefined * chore(whitelabeling): inline hasOrgBrand --- .../app/workspace/[workspaceId]/layout.tsx | 20 ++-- .../[workspaceId]/settings/[section]/page.tsx | 1 + .../settings/[section]/settings.tsx | 2 +- .../settings/components/general/general.tsx | 2 +- .../hooks/use-profile-picture-upload.ts | 16 ++- .../workspace-header/workspace-header.tsx | 55 ++++++----- .../emcn/components/tooltip/tooltip.tsx | 45 ++++++--- .../components/branding-provider.tsx | 98 ++++--------------- .../components/whitelabeling-settings.tsx | 8 +- .../ee/whitelabeling/org-branding-utils.ts | 16 +-- apps/sim/ee/whitelabeling/org-branding.ts | 12 +-- apps/sim/lib/uploads/utils/file-utils.ts | 2 +- 12 files changed, 112 insertions(+), 165 deletions(-) diff --git a/apps/sim/app/workspace/[workspaceId]/layout.tsx b/apps/sim/app/workspace/[workspaceId]/layout.tsx index e1f9d815bb3..83f1630942e 100644 --- a/apps/sim/app/workspace/[workspaceId]/layout.tsx +++ b/apps/sim/app/workspace/[workspaceId]/layout.tsx @@ -1,5 +1,5 @@ -import { cookies } from 'next/headers' import { ToastProvider } from '@/components/emcn' +import { getSession } from '@/lib/auth' import { NavTour } from '@/app/workspace/[workspaceId]/components/product-tour' import { ImpersonationBanner } from '@/app/workspace/[workspaceId]/impersonation-banner' import { GlobalCommandsProvider } from '@/app/workspace/[workspaceId]/providers/global-commands-provider' @@ -8,22 +8,16 @@ import { SettingsLoader } from '@/app/workspace/[workspaceId]/providers/settings import { WorkspacePermissionsProvider } from '@/app/workspace/[workspaceId]/providers/workspace-permissions-provider' import { WorkspaceScopeSync } from '@/app/workspace/[workspaceId]/providers/workspace-scope-sync' import { Sidebar } from '@/app/workspace/[workspaceId]/w/components/sidebar/sidebar' -import { - BRAND_COOKIE_NAME, - type BrandCache, - BrandingProvider, -} from '@/ee/whitelabeling/components/branding-provider' +import { BrandingProvider } from '@/ee/whitelabeling/components/branding-provider' +import { getOrgWhitelabelSettings } from '@/ee/whitelabeling/org-branding' export default async function WorkspaceLayout({ children }: { children: React.ReactNode }) { - const cookieStore = await cookies() - let initialCache: BrandCache | null = null - try { - const raw = cookieStore.get(BRAND_COOKIE_NAME)?.value - if (raw) initialCache = JSON.parse(decodeURIComponent(raw)) - } catch {} + const session = await getSession() + const orgId = session?.session?.activeOrganizationId + const initialOrgSettings = orgId ? await getOrgWhitelabelSettings(orgId) : null return ( - + diff --git a/apps/sim/app/workspace/[workspaceId]/settings/[section]/page.tsx b/apps/sim/app/workspace/[workspaceId]/settings/[section]/page.tsx index 36c1f97867a..ca48abef01b 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/[section]/page.tsx +++ b/apps/sim/app/workspace/[workspaceId]/settings/[section]/page.tsx @@ -16,6 +16,7 @@ const SECTION_TITLES: Record = { subscription: 'Subscription', team: 'Team', sso: 'Single Sign-On', + whitelabeling: 'Whitelabeling', copilot: 'Copilot Keys', mcp: 'MCP Tools', 'custom-tools': 'Custom Tools', diff --git a/apps/sim/app/workspace/[workspaceId]/settings/[section]/settings.tsx b/apps/sim/app/workspace/[workspaceId]/settings/[section]/settings.tsx index 93d1f537be8..4642fc9e843 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/[section]/settings.tsx +++ b/apps/sim/app/workspace/[workspaceId]/settings/[section]/settings.tsx @@ -161,7 +161,7 @@ const WhitelabelingSettings = dynamic( import('@/ee/whitelabeling/components/whitelabeling-settings').then( (m) => m.WhitelabelingSettings ), - { loading: () => } + { loading: () => , ssr: false } ) interface SettingsPageProps { diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/general/general.tsx b/apps/sim/app/workspace/[workspaceId]/settings/components/general/general.tsx index 2c3f437224e..b55b588605e 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/components/general/general.tsx +++ b/apps/sim/app/workspace/[workspaceId]/settings/components/general/general.tsx @@ -387,7 +387,7 @@ export function General() { diff --git a/apps/sim/app/workspace/[workspaceId]/settings/hooks/use-profile-picture-upload.ts b/apps/sim/app/workspace/[workspaceId]/settings/hooks/use-profile-picture-upload.ts index 074285d49fa..04078386354 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/hooks/use-profile-picture-upload.ts +++ b/apps/sim/app/workspace/[workspaceId]/settings/hooks/use-profile-picture-upload.ts @@ -3,7 +3,7 @@ import { createLogger } from '@sim/logger' const logger = createLogger('ProfilePictureUpload') const MAX_FILE_SIZE = 5 * 1024 * 1024 // 5MB -const ACCEPTED_IMAGE_TYPES = ['image/png', 'image/jpeg', 'image/jpg'] +const ACCEPTED_IMAGE_TYPES = ['image/png', 'image/jpeg', 'image/jpg', 'image/svg+xml'] interface UseProfilePictureUploadProps { onUpload?: (url: string | null) => void @@ -27,21 +27,19 @@ export function useProfilePictureUpload({ const [isUploading, setIsUploading] = useState(false) useEffect(() => { - if (currentImage !== previewUrl) { - if (previewRef.current && previewRef.current !== currentImage) { - URL.revokeObjectURL(previewRef.current) - previewRef.current = null - } - setPreviewUrl(currentImage || null) + if (previewRef.current && previewRef.current !== currentImage) { + URL.revokeObjectURL(previewRef.current) + previewRef.current = null } - }, [currentImage, previewUrl]) + setPreviewUrl(currentImage || null) + }, [currentImage]) const validateFile = useCallback((file: File): string | null => { if (file.size > MAX_FILE_SIZE) { return `File "${file.name}" is too large. Maximum size is 5MB.` } if (!ACCEPTED_IMAGE_TYPES.includes(file.type)) { - return `File "${file.name}" is not a supported image format. Please use PNG or JPEG.` + return `File "${file.name}" is not a supported image format. Please use PNG, JPEG, or SVG.` } return null }, []) diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/workspace-header.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/workspace-header.tsx index df36aa26164..cd2337ee4e2 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/workspace-header.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/workspace-header.tsx @@ -17,6 +17,7 @@ import { ModalFooter, ModalHeader, Plus, + Skeleton, UserPlus, } from '@/components/emcn' import { getDisplayPlanName, isFree } from '@/lib/billing/plan-helpers' @@ -356,14 +357,16 @@ export function WorkspaceHeader({ } }} > -
- {workspaceInitial} -
+ {activeWorkspaceFull ? ( +
+ {workspaceInitial} +
+ ) : ( + + )} {!isCollapsed && ( <> @@ -400,14 +403,18 @@ export function WorkspaceHeader({ ) : ( <>
-
- {workspaceInitial} -
+ {activeWorkspaceFull ? ( +
+ {workspaceInitial} +
+ ) : ( + + )}
{activeWorkspace?.name || 'Loading...'} @@ -580,12 +587,16 @@ export function WorkspaceHeader({ title={activeWorkspace?.name || 'Loading...'} disabled > -
- {workspaceInitial} -
+ {activeWorkspaceFull ? ( +
+ {workspaceInitial} +
+ ) : ( + + )} {!isCollapsed && ( <> diff --git a/apps/sim/components/emcn/components/tooltip/tooltip.tsx b/apps/sim/components/emcn/components/tooltip/tooltip.tsx index fb4608c9584..e8a35718d89 100644 --- a/apps/sim/components/emcn/components/tooltip/tooltip.tsx +++ b/apps/sim/components/emcn/components/tooltip/tooltip.tsx @@ -42,20 +42,20 @@ const Trigger = TooltipPrimitive.Trigger const Content = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef ->(({ className, sideOffset = 6, ...props }, ref) => ( +>(({ className, sideOffset = 6, children, ...props }, ref) => ( - {props.children} + {children} @@ -120,22 +120,35 @@ const VIDEO_EXTENSIONS = ['.mp4', '.webm', '.ogg', '.mov'] as const const Preview = ({ src, alt = '', width = 240, height, loop = true, className }: PreviewProps) => { const pathname = src.toLowerCase().split('?')[0].split('#')[0] const isVideo = VIDEO_EXTENSIONS.some((ext) => pathname.endsWith(ext)) + const [isReady, setIsReady] = React.useState(!isVideo) return ( -
+
{isVideo ? ( -