improvement(lint): fix react-doctor errors and warnings (#3232)

* improvement(lint): fix react-doctor errors and warnings

* remove separators
This commit is contained in:
Waleed
2026-02-17 11:40:47 -08:00
committed by GitHub
parent 48715ff013
commit 299cc26694
23 changed files with 275 additions and 289 deletions

View File

@@ -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] {

View File

@@ -1,5 +1,3 @@
'use server'
import { env } from '@/lib/core/config/env'
import { isProd } from '@/lib/core/config/feature-flags'

View File

@@ -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',
}}
>
<LandingBlock icon={data.icon} color={data.color} name={data.name} tags={data.tags} />

View File

@@ -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,
}}
/>

View File

@@ -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;
}

View File

@@ -30,21 +30,6 @@ export const ChatMessageContainer = memo(function ChatMessageContainer({
}: ChatMessageContainerProps) {
return (
<div className='relative flex flex-1 flex-col overflow-hidden bg-white'>
<style jsx>{`
@keyframes growShrink {
0%,
100% {
transform: scale(0.9);
}
50% {
transform: scale(1.1);
}
}
.loading-dot {
animation: growShrink 1.5s infinite ease-in-out;
}
`}</style>
{/* Scrollable Messages Area */}
<div
ref={messagesContainerRef}

View File

@@ -71,7 +71,7 @@ export function VoiceInterface({
const [state, setState] = useState<'idle' | 'listening' | 'agent_speaking'>('idle')
const [isInitialized, setIsInitialized] = useState(false)
const [isMuted, setIsMuted] = useState(false)
const [audioLevels, setAudioLevels] = useState<number[]>(new Array(200).fill(0))
const [audioLevels, setAudioLevels] = useState<number[]>(() => new Array(200).fill(0))
const [permissionStatus, setPermissionStatus] = useState<'prompt' | 'granted' | 'denied'>(
'prompt'
)

View File

@@ -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 <FileViewer file={fileRecord} />
} catch (error) {
const hasPermission = await verifyWorkspaceMembership(session.user.id, workspaceId)
if (!hasPermission) {
redirect(`/workspace/${workspaceId}`)
}
let fileRecord: Awaited<ReturnType<typeof getWorkspaceFile>>
try {
fileRecord = await getWorkspaceFile(workspaceId, fileId)
} catch (error) {
unstable_rethrow(error)
redirect(`/workspace/${workspaceId}`)
}
if (!fileRecord) {
redirect(`/workspace/${workspaceId}`)
}
return <FileViewer file={fileRecord} />
}

View File

@@ -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'

View File

@@ -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'

View File

@@ -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'

View File

@@ -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'

View File

@@ -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,

View File

@@ -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 (
<style jsx global>{`
/* Z-index management for subflow nodes - default behind blocks */
.workflow-container .react-flow__node-subflowNode {
z-index: -1 !important;
}
/* Selected subflows appear above other subflows but below blocks (z-21) */
.workflow-container .react-flow__node-subflowNode:has([data-subflow-selected='true']) {
z-index: 10 !important;
}
/* Drag-over states */
.loop-node-drag-over,
.parallel-node-drag-over {
box-shadow: 0 0 0 1.75px var(--brand-secondary) !important;
border-radius: 8px !important;
}
/* Handle z-index for nested nodes */
.react-flow__node[data-parent-node-id] .react-flow__handle {
z-index: 30;
}
`}</style>
)
}
/**
* Data structure for subflow nodes (loop and parallel containers)
*/
@@ -151,133 +117,130 @@ export const SubflowNodeComponent = memo(({ data, id, selected }: NodeProps<Subf
)
return (
<>
<SubflowNodeStyles />
<div className='group relative'>
<div className='group relative'>
<div
ref={blockRef}
onClick={() => 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 && (
<ActionBar blockId={id} blockType={data.kind} disabled={!userPermissions.canEdit} />
)}
{/* Header Section */}
<div
ref={blockRef}
onClick={() => 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 && (
<ActionBar blockId={id} blockType={data.kind} disabled={!userPermissions.canEdit} />
)}
{/* Header Section */}
<div
className={cn(
'flex items-center justify-between rounded-t-[8px] border-[var(--border)] border-b bg-[var(--surface-2)] py-[8px] pr-[12px] pl-[8px]'
)}
>
<div className='flex min-w-0 flex-1 items-center gap-[10px]'>
<div
className='flex h-[24px] w-[24px] flex-shrink-0 items-center justify-center rounded-[6px]'
style={{ backgroundColor: isEnabled ? blockIconBg : 'gray' }}
>
<BlockIcon className='h-[16px] w-[16px] text-white' />
</div>
<span
className={cn(
'truncate font-medium text-[16px]',
!isEnabled && 'text-[var(--text-muted)]'
)}
title={blockName}
>
{blockName}
</span>
</div>
<div className='flex items-center gap-1'>
{!isEnabled && <Badge variant='gray-secondary'>disabled</Badge>}
{isLocked && <Badge variant='gray-secondary'>locked</Badge>}
</div>
</div>
{!isPreview && (
<div className='flex min-w-0 flex-1 items-center gap-[10px]'>
<div
className='absolute right-[8px] bottom-[8px] z-20 flex h-[32px] w-[32px] cursor-se-resize items-center justify-center text-muted-foreground'
style={{ pointerEvents: 'auto' }}
/>
)}
<div
className='h-[calc(100%-50px)] pt-[16px] pr-[80px] pb-[16px] pl-[16px]'
data-dragarea='true'
style={{
position: 'relative',
pointerEvents: isPreview ? 'none' : 'auto',
}}
>
{/* Subflow Start */}
<div
className='absolute top-[16px] left-[16px] flex items-center justify-center rounded-[8px] border border-[var(--border-1)] bg-[var(--surface-2)] px-[12px] py-[6px]'
style={{ pointerEvents: isPreview ? 'none' : 'auto' }}
data-parent-id={id}
data-node-role={`${data.kind}-start`}
data-extent='parent'
className='flex h-[24px] w-[24px] flex-shrink-0 items-center justify-center rounded-[6px]'
style={{ backgroundColor: isEnabled ? blockIconBg : 'gray' }}
>
<span className='font-medium text-[14px] text-[var(--text-primary)]'>Start</span>
<Handle
type='source'
position={Position.Right}
id={startHandleId}
className={getHandleClasses('right')}
style={{
top: '50%',
transform: 'translateY(-50%)',
pointerEvents: 'auto',
}}
data-parent-id={id}
/>
<BlockIcon className='h-[16px] w-[16px] text-white' />
</div>
<span
className={cn(
'truncate font-medium text-[16px]',
!isEnabled && 'text-[var(--text-muted)]'
)}
title={blockName}
>
{blockName}
</span>
</div>
<div className='flex items-center gap-1'>
{!isEnabled && <Badge variant='gray-secondary'>disabled</Badge>}
{isLocked && <Badge variant='gray-secondary'>locked</Badge>}
</div>
{/* Input handle on left middle */}
<Handle
type='target'
position={Position.Left}
className={getHandleClasses('left')}
style={{
...getHandleStyle(),
pointerEvents: 'auto',
}}
/>
{/* Output handle on right middle */}
<Handle
type='source'
position={Position.Right}
className={getHandleClasses('right')}
style={{
...getHandleStyle(),
pointerEvents: 'auto',
}}
id={endHandleId}
/>
{hasRing && (
<div
className={cn('pointer-events-none absolute inset-0 z-40 rounded-[8px]', ringStyles)}
/>
)}
</div>
{!isPreview && (
<div
className='absolute right-[8px] bottom-[8px] z-20 flex h-[32px] w-[32px] cursor-se-resize items-center justify-center text-muted-foreground'
style={{ pointerEvents: 'auto' }}
/>
)}
<div
className='h-[calc(100%-50px)] pt-[16px] pr-[80px] pb-[16px] pl-[16px]'
data-dragarea='true'
style={{
position: 'relative',
pointerEvents: isPreview ? 'none' : 'auto',
}}
>
{/* Subflow Start */}
<div
className='absolute top-[16px] left-[16px] flex items-center justify-center rounded-[8px] border border-[var(--border-1)] bg-[var(--surface-2)] px-[12px] py-[6px]'
style={{ pointerEvents: isPreview ? 'none' : 'auto' }}
data-parent-id={id}
data-node-role={`${data.kind}-start`}
data-extent='parent'
>
<span className='font-medium text-[14px] text-[var(--text-primary)]'>Start</span>
<Handle
type='source'
position={Position.Right}
id={startHandleId}
className={getHandleClasses('right')}
style={{
top: '50%',
transform: 'translateY(-50%)',
pointerEvents: 'auto',
}}
data-parent-id={id}
/>
</div>
</div>
{/* Input handle on left middle */}
<Handle
type='target'
position={Position.Left}
className={getHandleClasses('left')}
style={{
...getHandleStyle(),
pointerEvents: 'auto',
}}
/>
{/* Output handle on right middle */}
<Handle
type='source'
position={Position.Right}
className={getHandleClasses('right')}
style={{
...getHandleStyle(),
pointerEvents: 'auto',
}}
id={endHandleId}
/>
{hasRing && (
<div
className={cn('pointer-events-none absolute inset-0 z-40 rounded-[8px]', ringStyles)}
/>
)}
</div>
</>
</div>
)
})

View File

@@ -134,57 +134,6 @@ export function WandPromptBar({
</Button>
)}
</div>
<style jsx global>{`
@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;
}
`}</style>
</div>
)
}

View File

@@ -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'

View File

@@ -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'
/>
<button

View File

@@ -165,12 +165,16 @@ export function CancelSubscription({ subscription, subscriptionData }: CancelSub
logger.info('Subscription restored successfully', result)
}
await queryClient.invalidateQueries({ queryKey: subscriptionKeys.all })
if (activeOrgId) {
await queryClient.invalidateQueries({ queryKey: organizationKeys.detail(activeOrgId) })
await queryClient.invalidateQueries({ queryKey: organizationKeys.billing(activeOrgId) })
await queryClient.invalidateQueries({ queryKey: organizationKeys.lists() })
}
await Promise.all([
queryClient.invalidateQueries({ queryKey: subscriptionKeys.all }),
...(activeOrgId
? [
queryClient.invalidateQueries({ queryKey: organizationKeys.detail(activeOrgId) }),
queryClient.invalidateQueries({ queryKey: organizationKeys.billing(activeOrgId) }),
queryClient.invalidateQueries({ queryKey: organizationKeys.lists() }),
]
: []),
])
setIsDialogOpen(false)
} catch (err) {

View File

@@ -37,7 +37,7 @@ export const UsageLimit = forwardRef<UsageLimitRef, UsageLimitProps>(
},
ref
) => {
const [inputValue, setInputValue] = useState(currentLimit.toString())
const [inputValue, setInputValue] = useState(() => currentLimit.toString())
const [hasError, setHasError] = useState(false)
const [errorType, setErrorType] = useState<'general' | 'belowUsage' | null>(null)
const [isEditing, setIsEditing] = useState(false)

View File

@@ -9,6 +9,7 @@ import {
type ReactNode,
useCallback,
useEffect,
useId,
useMemo,
useRef,
useState,
@@ -170,6 +171,7 @@ const Combobox = memo(
},
ref
) => {
const listboxId = useId()
const [open, setOpen] = useState(false)
const [highlightedIndex, setHighlightedIndex] = useState(-1)
const [searchQuery, setSearchQuery] = useState('')
@@ -513,6 +515,7 @@ const Combobox = memo(
role='combobox'
aria-expanded={open}
aria-haspopup='listbox'
aria-controls={listboxId}
aria-disabled={disabled}
tabIndex={disabled ? -1 : 0}
className={cn(
@@ -616,7 +619,7 @@ const Combobox = memo(
}
}}
>
<div ref={dropdownRef} role='listbox'>
<div ref={dropdownRef} role='listbox' id={listboxId}>
{isLoading ? (
<div className='flex items-center justify-center py-[14px]'>
<Loader2 className='h-[16px] w-[16px] animate-spin text-[var(--text-muted)]' />

View File

@@ -27,12 +27,14 @@ const Alert = React.forwardRef<
Alert.displayName = 'Alert'
const AlertTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>(
({ className, ...props }, ref) => (
({ className, children, ...props }, ref) => (
<h5
ref={ref}
className={cn('mb-1 font-medium leading-none tracking-tight', className)}
{...props}
/>
>
{children}
</h5>
)
)
AlertTitle.displayName = 'AlertTitle'

View File

@@ -16,26 +16,32 @@ export const mdxComponents: MDXRemoteProps['components'] = {
unoptimized
/>
),
h2: (props: any) => (
h2: ({ children, className, ...props }: any) => (
<h2
{...props}
style={{ fontSize: '30px', marginTop: '3rem', marginBottom: '1.5rem' }}
className={clsx('font-medium text-black leading-tight', props.className)}
/>
className={clsx('font-medium text-black leading-tight', className)}
>
{children}
</h2>
),
h3: (props: any) => (
h3: ({ children, className, ...props }: any) => (
<h3
{...props}
style={{ fontSize: '24px', marginTop: '1.5rem', marginBottom: '0.75rem' }}
className={clsx('font-medium leading-tight', props.className)}
/>
className={clsx('font-medium leading-tight', className)}
>
{children}
</h3>
),
h4: (props: any) => (
h4: ({ children, className, ...props }: any) => (
<h4
{...props}
style={{ fontSize: '19px', marginTop: '1.5rem', marginBottom: '0.75rem' }}
className={clsx('font-medium leading-tight', props.className)}
/>
className={clsx('font-medium leading-tight', className)}
>
{children}
</h4>
),
p: (props: any) => (
<p

View File

@@ -107,5 +107,3 @@ if (typeof process !== 'undefined') {
logger.info(`S3 copilot bucket: ${env.S3_COPILOT_BUCKET_NAME}`)
}
}
export default ensureUploadsDirectory