diff --git a/apps/docs/app/global.css b/apps/docs/app/global.css
index 95eebe1e8..70ec578bf 100644
--- a/apps/docs/app/global.css
+++ b/apps/docs/app/global.css
@@ -59,12 +59,6 @@ body {
--content-gap: 1.75rem;
}
-/* Remove custom layout variable overrides to fallback to fumadocs defaults */
-
-/* ============================================
- Navbar Light Mode Styling
- ============================================ */
-
/* Light mode navbar and search styling */
:root:not(.dark) nav {
background-color: hsla(0, 0%, 96%, 0.85) !important;
@@ -88,10 +82,6 @@ body {
-webkit-backdrop-filter: blur(25px) saturate(180%) brightness(0.6) !important;
}
-/* ============================================
- Custom Sidebar Styling (Turborepo-inspired)
- ============================================ */
-
/* Floating sidebar appearance - remove background */
[data-sidebar-container],
#nd-sidebar {
@@ -468,10 +458,6 @@ aside[data-sidebar],
writing-mode: horizontal-tb !important;
}
-/* ============================================
- Code Block Styling (Improved)
- ============================================ */
-
/* Apply Geist Mono to code elements */
code,
pre,
@@ -532,10 +518,6 @@ pre code .line {
color: var(--color-fd-primary);
}
-/* ============================================
- TOC (Table of Contents) Styling
- ============================================ */
-
/* Remove the thin border-left on nested TOC items (keeps main indicator only) */
#nd-toc a[style*="padding-inline-start"] {
border-left: none !important;
@@ -554,10 +536,6 @@ main article,
padding-bottom: 4rem;
}
-/* ============================================
- Center and Constrain Main Content Width
- ============================================ */
-
/* Main content area - center and constrain like turborepo/raindrop */
/* Note: --sidebar-offset and --toc-offset are now applied at #nd-docs-layout level */
main[data-main] {
diff --git a/apps/sim/app/(auth)/components/oauth-provider-checker.tsx b/apps/sim/app/(auth)/components/oauth-provider-checker.tsx
index c7eba7af1..6a7177e4f 100644
--- a/apps/sim/app/(auth)/components/oauth-provider-checker.tsx
+++ b/apps/sim/app/(auth)/components/oauth-provider-checker.tsx
@@ -1,5 +1,3 @@
-'use server'
-
import { env } from '@/lib/core/config/env'
import { isProd } from '@/lib/core/config/feature-flags'
diff --git a/apps/sim/app/(landing)/components/hero/components/landing-canvas/landing-block/landing-node.tsx b/apps/sim/app/(landing)/components/hero/components/landing-canvas/landing-block/landing-node.tsx
index 0d92b9672..2f13fd7ff 100644
--- a/apps/sim/app/(landing)/components/hero/components/landing-canvas/landing-block/landing-node.tsx
+++ b/apps/sim/app/(landing)/components/hero/components/landing-canvas/landing-block/landing-node.tsx
@@ -85,7 +85,7 @@ export const LandingNode = React.memo(function LandingNode({ data }: { data: Lan
transform: isAnimated ? 'translateY(0) scale(1)' : 'translateY(8px) scale(0.98)',
transition:
'opacity 0.6s cubic-bezier(0.22, 1, 0.36, 1), transform 0.6s cubic-bezier(0.22, 1, 0.36, 1)',
- willChange: 'transform, opacity',
+ willChange: isAnimated ? 'auto' : 'transform, opacity',
}}
>
diff --git a/apps/sim/app/(landing)/components/hero/components/landing-canvas/landing-edge/landing-edge.tsx b/apps/sim/app/(landing)/components/hero/components/landing-canvas/landing-edge/landing-edge.tsx
index 65c48f33e..aa43a60bf 100644
--- a/apps/sim/app/(landing)/components/hero/components/landing-canvas/landing-edge/landing-edge.tsx
+++ b/apps/sim/app/(landing)/components/hero/components/landing-canvas/landing-edge/landing-edge.tsx
@@ -67,7 +67,6 @@ export const LandingEdge = React.memo(function LandingEdge(props: EdgeProps) {
strokeLinejoin: 'round',
pointerEvents: 'none',
animation: `landing-edge-dash-${id} 1s linear infinite`,
- willChange: 'stroke-dashoffset',
...style,
}}
/>
diff --git a/apps/sim/app/_styles/globals.css b/apps/sim/app/_styles/globals.css
index 96e8981e3..f7e15a76c 100644
--- a/apps/sim/app/_styles/globals.css
+++ b/apps/sim/app/_styles/globals.css
@@ -754,3 +754,100 @@ input[type="search"]::-ms-clear {
text-decoration: none !important;
color: inherit !important;
}
+
+/**
+ * Respect user's prefers-reduced-motion setting (WCAG 2.3.3)
+ * Disables animations and transitions for users who prefer reduced motion.
+ */
+@media (prefers-reduced-motion: reduce) {
+ *,
+ *::before,
+ *::after {
+ animation-duration: 0.01ms !important;
+ animation-iteration-count: 1 !important;
+ transition-duration: 0.01ms !important;
+ scroll-behavior: auto !important;
+ }
+}
+
+/* WandPromptBar status indicator */
+@keyframes smoke-pulse {
+ 0%,
+ 100% {
+ transform: scale(0.8);
+ opacity: 0.4;
+ }
+ 50% {
+ transform: scale(1.1);
+ opacity: 0.8;
+ }
+}
+
+.status-indicator {
+ position: relative;
+ width: 12px;
+ height: 12px;
+ border-radius: 50%;
+ overflow: hidden;
+ background-color: hsl(var(--muted-foreground) / 0.5);
+ transition: background-color 0.3s ease;
+}
+
+.status-indicator.streaming {
+ background-color: transparent;
+}
+
+.status-indicator.streaming::before {
+ content: "";
+ position: absolute;
+ inset: 0;
+ border-radius: 50%;
+ background: radial-gradient(
+ circle,
+ hsl(var(--primary) / 0.9) 0%,
+ hsl(var(--primary) / 0.4) 60%,
+ transparent 80%
+ );
+ animation: smoke-pulse 1.8s ease-in-out infinite;
+ opacity: 0.9;
+}
+
+.dark .status-indicator.streaming::before {
+ background: #6b7280;
+ opacity: 0.9;
+ animation: smoke-pulse 1.8s ease-in-out infinite;
+}
+
+/* MessageContainer loading dot */
+@keyframes growShrink {
+ 0%,
+ 100% {
+ transform: scale(0.9);
+ }
+ 50% {
+ transform: scale(1.1);
+ }
+}
+
+.loading-dot {
+ animation: growShrink 1.5s infinite ease-in-out;
+}
+
+/* Subflow node z-index and drag-over styles */
+.workflow-container .react-flow__node-subflowNode {
+ z-index: -1 !important;
+}
+
+.workflow-container .react-flow__node-subflowNode:has([data-subflow-selected="true"]) {
+ z-index: 10 !important;
+}
+
+.loop-node-drag-over,
+.parallel-node-drag-over {
+ box-shadow: 0 0 0 1.75px var(--brand-secondary) !important;
+ border-radius: 8px !important;
+}
+
+.react-flow__node[data-parent-node-id] .react-flow__handle {
+ z-index: 30;
+}
diff --git a/apps/sim/app/chat/components/message-container/message-container.tsx b/apps/sim/app/chat/components/message-container/message-container.tsx
index 8695878e9..df7d5e751 100644
--- a/apps/sim/app/chat/components/message-container/message-container.tsx
+++ b/apps/sim/app/chat/components/message-container/message-container.tsx
@@ -30,21 +30,6 @@ export const ChatMessageContainer = memo(function ChatMessageContainer({
}: ChatMessageContainerProps) {
return (
-
-
{/* Scrollable Messages Area */}
('idle')
const [isInitialized, setIsInitialized] = useState(false)
const [isMuted, setIsMuted] = useState(false)
- const [audioLevels, setAudioLevels] = useState
(new Array(200).fill(0))
+ const [audioLevels, setAudioLevels] = useState(() => new Array(200).fill(0))
const [permissionStatus, setPermissionStatus] = useState<'prompt' | 'granted' | 'denied'>(
'prompt'
)
diff --git a/apps/sim/app/workspace/[workspaceId]/files/[fileId]/view/page.tsx b/apps/sim/app/workspace/[workspaceId]/files/[fileId]/view/page.tsx
index e82182edb..1f78f7e68 100644
--- a/apps/sim/app/workspace/[workspaceId]/files/[fileId]/view/page.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/files/[fileId]/view/page.tsx
@@ -1,4 +1,4 @@
-import { redirect } from 'next/navigation'
+import { redirect, unstable_rethrow } from 'next/navigation'
import { getSession } from '@/lib/auth'
import { getWorkspaceFile } from '@/lib/uploads/contexts/workspace'
import { verifyWorkspaceMembership } from '@/app/api/workflows/utils'
@@ -14,24 +14,27 @@ interface FileViewerPageProps {
export default async function FileViewerPage({ params }: FileViewerPageProps) {
const { workspaceId, fileId } = await params
- try {
- const session = await getSession()
- if (!session?.user?.id) {
- redirect('/')
- }
+ const session = await getSession()
+ if (!session?.user?.id) {
+ redirect('/')
+ }
- const hasPermission = await verifyWorkspaceMembership(session.user.id, workspaceId)
- if (!hasPermission) {
- redirect(`/workspace/${workspaceId}`)
- }
-
- const fileRecord = await getWorkspaceFile(workspaceId, fileId)
- if (!fileRecord) {
- redirect(`/workspace/${workspaceId}`)
- }
-
- return
- } catch (error) {
+ const hasPermission = await verifyWorkspaceMembership(session.user.id, workspaceId)
+ if (!hasPermission) {
redirect(`/workspace/${workspaceId}`)
}
+
+ let fileRecord: Awaited>
+ try {
+ fileRecord = await getWorkspaceFile(workspaceId, fileId)
+ } catch (error) {
+ unstable_rethrow(error)
+ redirect(`/workspace/${workspaceId}`)
+ }
+
+ if (!fileRecord) {
+ redirect(`/workspace/${workspaceId}`)
+ }
+
+ return
}
diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/combobox/combobox.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/combobox/combobox.tsx
index 32036c21d..f20da325f 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/combobox/combobox.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/combobox/combobox.tsx
@@ -1,5 +1,5 @@
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'
-import { isEqual } from 'lodash'
+import isEqual from 'lodash/isEqual'
import { useReactFlow } from 'reactflow'
import { useStoreWithEqualityFn } from 'zustand/traditional'
import { Combobox, type ComboboxOption } from '@/components/emcn/components'
diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/dropdown/dropdown.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/dropdown/dropdown.tsx
index d8d3ec00e..355463819 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/dropdown/dropdown.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/dropdown/dropdown.tsx
@@ -1,5 +1,5 @@
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'
-import { isEqual } from 'lodash'
+import isEqual from 'lodash/isEqual'
import { useStoreWithEqualityFn } from 'zustand/traditional'
import { Badge } from '@/components/emcn'
import { Combobox, type ComboboxOption } from '@/components/emcn/components'
diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/messages-input/messages-input.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/messages-input/messages-input.tsx
index b5cd52600..69ed53a5c 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/messages-input/messages-input.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/messages-input/messages-input.tsx
@@ -7,7 +7,7 @@ import {
useRef,
useState,
} from 'react'
-import { isEqual } from 'lodash'
+import isEqual from 'lodash/isEqual'
import { ChevronDown, ChevronsUpDown, ChevronUp, Plus } from 'lucide-react'
import { Button, Popover, PopoverContent, PopoverItem, PopoverTrigger } from '@/components/emcn'
import { Trash } from '@/components/emcn/icons/trash'
diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block.tsx
index 180b8bb12..35a90e159 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block.tsx
@@ -1,5 +1,5 @@
import { type JSX, type MouseEvent, memo, useCallback, useRef, useState } from 'react'
-import { isEqual } from 'lodash'
+import isEqual from 'lodash/isEqual'
import { AlertTriangle, ArrowLeftRight, ArrowUp, Check, Clipboard } from 'lucide-react'
import { Button, Input, Label, Tooltip } from '@/components/emcn/components'
import { cn } from '@/lib/core/utils/cn'
diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/editor.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/editor.tsx
index 9f1905c83..6d7829c17 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/editor.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/editor.tsx
@@ -1,7 +1,7 @@
'use client'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
-import { isEqual } from 'lodash'
+import isEqual from 'lodash/isEqual'
import {
BookOpen,
Check,
diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/subflows/subflow-node.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/subflows/subflow-node.tsx
index 96c85791f..6205818cd 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/subflows/subflow-node.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/subflows/subflow-node.tsx
@@ -10,40 +10,6 @@ import { ActionBar } from '@/app/workspace/[workspaceId]/w/[workflowId]/componen
import { useCurrentWorkflow } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks'
import { usePanelEditorStore } from '@/stores/panel'
-/**
- * Global styles for subflow nodes (loop and parallel containers).
- * Includes animations for drag-over states and hover effects.
- *
- * @returns Style component with global CSS
- */
-const SubflowNodeStyles: React.FC = () => {
- return (
-
- )
-}
-
/**
* Data structure for subflow nodes (loop and parallel containers)
*/
@@ -151,133 +117,130 @@ export const SubflowNodeComponent = memo(({ data, id, selected }: NodeProps
-
-
+
+
setCurrentBlockId(id)}
+ className={cn(
+ 'workflow-drag-handle relative cursor-grab select-none rounded-[8px] border border-[var(--border-1)] [&:active]:cursor-grabbing',
+ 'transition-block-bg transition-ring',
+ 'z-[20]'
+ )}
+ style={{
+ width: data.width || 500,
+ height: data.height || 300,
+ position: 'relative',
+ overflow: 'visible',
+ pointerEvents: isPreview ? 'none' : 'all',
+ }}
+ data-node-id={id}
+ data-type='subflowNode'
+ data-nesting-level={nestingLevel}
+ data-subflow-selected={isFocused || isSelected || isPreviewSelected}
+ >
+ {!isPreview && (
+
+ )}
+
+ {/* Header Section */}
setCurrentBlockId(id)}
className={cn(
- 'workflow-drag-handle relative cursor-grab select-none rounded-[8px] border border-[var(--border-1)] [&:active]:cursor-grabbing',
- 'transition-block-bg transition-ring',
- 'z-[20]'
+ 'flex items-center justify-between rounded-t-[8px] border-[var(--border)] border-b bg-[var(--surface-2)] py-[8px] pr-[12px] pl-[8px]'
)}
- style={{
- width: data.width || 500,
- height: data.height || 300,
- position: 'relative',
- overflow: 'visible',
- pointerEvents: isPreview ? 'none' : 'all',
- }}
- data-node-id={id}
- data-type='subflowNode'
- data-nesting-level={nestingLevel}
- data-subflow-selected={isFocused || isSelected || isPreviewSelected}
>
- {!isPreview && (
-
- )}
-
- {/* Header Section */}
-
-
-
-
-
-
- {blockName}
-
-
-
- {!isEnabled && disabled}
- {isLocked && locked}
-
-
-
- {!isPreview && (
+
- )}
-
-
- {/* Subflow Start */}
-
- Start
-
-
+
+
+ {blockName}
+
+
+
+ {!isEnabled && disabled}
+ {isLocked && locked}
-
- {/* Input handle on left middle */}
-
-
- {/* Output handle on right middle */}
-
-
- {hasRing && (
-
- )}
+
+ {!isPreview && (
+
+ )}
+
+
+ {/* Subflow Start */}
+
+ Start
+
+
+
+
+
+ {/* Input handle on left middle */}
+
+
+ {/* Output handle on right middle */}
+
+
+ {hasRing && (
+
+ )}
- >
+
)
})
diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/wand-prompt-bar/wand-prompt-bar.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/wand-prompt-bar/wand-prompt-bar.tsx
index a15022ea5..e39cbbb9c 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/wand-prompt-bar/wand-prompt-bar.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/wand-prompt-bar/wand-prompt-bar.tsx
@@ -134,57 +134,6 @@ export function WandPromptBar({
)}
-
-
)
}
diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx
index c0f89e2b3..434ea73cc 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx
@@ -1,6 +1,6 @@
import { memo, useCallback, useEffect, useMemo, useRef } from 'react'
import { createLogger } from '@sim/logger'
-import { isEqual } from 'lodash'
+import isEqual from 'lodash/isEqual'
import { useParams } from 'next/navigation'
import { Handle, type NodeProps, Position, useUpdateNodeInternals } from 'reactflow'
import { useStoreWithEqualityFn } from 'zustand/traditional'
diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/help-modal/help-modal.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/help-modal/help-modal.tsx
index aefb340b4..9c558baa5 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/help-modal/help-modal.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/help-modal/help-modal.tsx
@@ -514,6 +514,7 @@ export function HelpModal({ open, onOpenChange, workflowId, workspaceId }: HelpM
alt={`Preview ${index + 1}`}
fill
unoptimized
+ sizes='(max-width: 768px) 100vw, 50vw'
className='object-contain'
/>