From d047d0eae39565517bdc07cebaefcc874954c324 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Fri, 9 Oct 2026 19:05:13 -0700 Subject: [PATCH 1/5] chore(audits): drop dead ESLint directives and the redundant check:dead-code script - Remove all 89 `eslint-disable` comments. Nothing runs ESLint (no eslint dependency or config in any workspace; Next 16 `next build` has no lint step), so they suppressed nothing. Reasons they carried are kept as plain `//` whys. - `check:comment-hygiene` now rejects `eslint-disable`/`eslint-enable` comments so they cannot return. - Remove `check:dead-code`: `check:unused-exports` already runs knip with the same config and a superset of its issue types, and `check:audits` skipped the alias. - Ignore unused exports in the generated `apps/docs/components/icons.tsx` (a verbatim copy of the sim icon set, whose export surface is ratcheted at the source) and shrink the unused-exports baseline by its 66 entries. --- .../you-might-not-need-a-comment/SKILL.md | 2 +- CLAUDE.md | 2 +- .../[executionId]/resume-page-client.tsx | 240 +++++++++--------- .../components/file-viewer/csv-import.ts | 1 - .../terminal-session/terminal-session.tsx | 1 - .../resource-tabs/resource-tabs.tsx | 3 - .../prompt-editor/prompt-editor.tsx | 2 +- .../home/components/user-input/user-input.tsx | 2 +- .../app/workspace/[workspaceId]/home/home.tsx | 1 - .../[workspaceId]/knowledge/knowledge.tsx | 14 +- .../components/log-details/log-details.tsx | 2 +- .../app/workspace/[workspaceId]/logs/logs.tsx | 29 +-- .../providers/global-commands-provider.tsx | 1 - .../settings/components/mcp/mcp.tsx | 2 +- .../workspace/[workspaceId]/tables/tables.tsx | 10 +- .../keyboard-navigation-handler.tsx | 1 - .../hooks/use-toolbar-item-interactions.ts | 1 - .../workflow-block/hooks/use-webhook-info.ts | 1 - .../hooks/use-block-dimensions.ts | 1 - .../hooks/use-workflow-execution.ts | 1 - .../sidebar/hooks/use-workspace-management.ts | 4 - apps/sim/connectors/asana/asana.ts | 1 - apps/sim/lib/internal/file/parser.test.ts | 1 - .../permission-groups/request-scope.server.ts | 1 - apps/sim/lib/posthog/server.ts | 1 - .../pptx-renderer/renderer/chart-renderer.ts | 13 - .../pptx-renderer/renderer/render-context.ts | 2 - .../pptx-renderer/renderer/shape-renderer.ts | 12 +- .../pptx-renderer/renderer/text-renderer.ts | 12 - apps/sim/lib/table/snapshot-cache.test.ts | 1 - .../lib/workflows/persistence/utils.test.ts | 1 - .../serializer/index.edge-case-blocks.test.ts | 3 - apps/sim/tools/gmail/utils.ts | 1 - apps/sim/tools/http/request.test.ts | 1 - knip.jsonc | 13 +- package.json | 3 +- packages/logger/src/request-context.ts | 1 - .../testing/src/builders/workflow.builder.ts | 2 - .../testing/src/factories/block.factory.ts | 2 - .../testing/src/factories/edge.factory.ts | 2 - .../testing/src/factories/workflow.factory.ts | 2 - packages/testing/src/mocks/blocks.mock.ts | 2 - packages/testing/src/types/index.ts | 2 - scripts/check-comment-hygiene.test.ts | 25 ++ scripts/check-comment-hygiene.ts | 40 ++- scripts/check-unused-exports.baseline.json | 66 ----- scripts/check-unused-exports.ts | 15 +- scripts/run-audits.ts | 1 - 48 files changed, 218 insertions(+), 329 deletions(-) diff --git a/.agents/skills/you-might-not-need-a-comment/SKILL.md b/.agents/skills/you-might-not-need-a-comment/SKILL.md index 46b7f559b2d..90ce7e547b5 100644 --- a/.agents/skills/you-might-not-need-a-comment/SKILL.md +++ b/.agents/skills/you-might-not-need-a-comment/SKILL.md @@ -33,7 +33,7 @@ This codebase's convention: **TSDoc for documentation; an inline `//` only for a - A `//` comment that explains a **non-obvious why**: a workaround for an upstream bug, an ordering constraint, a perf reason, a spec/edge-case the code can't self-document (`// first-match wins — matches the old find() semantics`). - Existing TSDoc `/** ... */` blocks on declarations — leave them (only tighten if verbose). - `// boundary-raw-fetch:`, `// double-cast-allowed:`, `// boundary-raw-json:`, `// untyped-response:`, `-- migration-safe:` (SQL migrations), `// rq-lint-allow:`, `// client-boundary-allow:` and any other `: ` annotation a script under `scripts/` greps for, in line-comment or block-comment form (e.g. the `/** svg-path-precision-exception: ... */` directive on icon paths) — these are load-bearing, never touch them. -- `// biome-ignore`, `// eslint-disable`, `// @ts-expect-error` and other tooling directives. +- `// biome-ignore`, `// @ts-expect-error` and other tooling directives. (Nothing runs ESLint, so `eslint-disable` is dead and `check:comment-hygiene` rejects it.) - `// TODO` / `// FIXME` that point at real, still-open work. ## Bias diff --git a/CLAUDE.md b/CLAUDE.md index 0b70bd052b8..673496b7d3a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,7 +6,7 @@ This file (also `AGENTS.md`) holds the repo-wide rules. Area detail lives in `.c - **Package manager**: `bun` and `bunx`, never `npm` and `npx`. - **Logging**: `createLogger` from `@sim/logger`; `logger.info` / `logger.warn` / `logger.error`, never `console.log`. Inside `withRouteHandler` the logger already carries the request ID — no manual `withMetadata({ requestId })`. -- **Comments**: name things so the code explains itself. TSDoc documents exported APIs and non-obvious modules. An inline `//` is only for a terse, non-obvious *why*, or for a script-enforced `// : ` annotation (`boundary-raw-fetch`, `double-cast-allowed`, `boundary-raw-json`, `untyped-response`, `rq-lint-allow`, `client-boundary-allow`, `utils-lint-allow`, …). History belongs in the commit message. No `====` separators or commented-out code (`check:comment-hygiene` enforces this). The `/you-might-not-need-a-comment` skill applies this to a diff. +- **Comments**: name things so the code explains itself. TSDoc documents exported APIs and non-obvious modules. An inline `//` is only for a terse, non-obvious *why*, or for a script-enforced `// : ` annotation (`boundary-raw-fetch`, `double-cast-allowed`, `boundary-raw-json`, `untyped-response`, `rq-lint-allow`, `client-boundary-allow`, `utils-lint-allow`, …). History belongs in the commit message. No `====` separators, commented-out code, or `eslint-disable` directives (nothing runs ESLint; `check:comment-hygiene` enforces all three). The `/you-might-not-need-a-comment` skill applies this to a diff. - **ID generation**: `generateId()` (UUID v4, the default) or `generateShortId(size?)` (URL-safe, 21 chars by default) from `@sim/utils/id` — never `crypto.randomUUID()`, `nanoid`, or `uuid`. Both use `crypto.getRandomValues()`, so they also work in non-secure (HTTP) browsers. For other randomness, `@sim/utils/random` (`randomInt`, `randomFloat`, `randomItem`, `generateRandomBytes`, `generateRandomHex`) — never `Math.random()` or `crypto.randomBytes()`. - **Common utilities**: use the shared helpers from the `@sim/utils/` subpaths instead of inline implementations (`check:utils` bans most of the inline forms below): - `sleep(ms)` from `@sim/utils/helpers` — never `new Promise(resolve => setTimeout(resolve, ms))` diff --git a/apps/sim/app/(interfaces)/resume/[workflowId]/[executionId]/resume-page-client.tsx b/apps/sim/app/(interfaces)/resume/[workflowId]/[executionId]/resume-page-client.tsx index 5a88444c95a..f5af1bcfcb6 100644 --- a/apps/sim/app/(interfaces)/resume/[workflowId]/[executionId]/resume-page-client.tsx +++ b/apps/sim/app/(interfaces)/resume/[workflowId]/[executionId]/resume-page-client.tsx @@ -548,137 +548,133 @@ export default function ResumeExecutionPage({ } }, [refetchExecutionDetail, selectedContextId]) - const handleResume = useCallback( - async () => { - if (!selectedContextId || !selectedDetail) { - setError('No pause point is selected. Refresh and try again.') - return - } - setLoadingAction(true) - setError(null) - setMessage(null) - let resumePayload: any - try { - if (isHumanMode && hasInputFormat) { - const errors: Record = {} - const submission: Record = {} - for (const field of inputFormatFields) { - const rawValue = formValues[field.name] ?? '' - const hasValue = - field.type === 'boolean' - ? rawValue === 'true' || rawValue === 'false' - : rawValue.trim().length > 0 && rawValue !== '__unset__' - if (!hasValue || rawValue === '__unset__') { - if (field.required) errors[field.name] = 'This field is required.' - continue - } - const { value, error: parseError } = parseFormValue(field, rawValue) - if (parseError) { - errors[field.name] = parseError - continue - } - if (value !== undefined) submission[field.name] = value + const handleResume = useCallback(async () => { + if (!selectedContextId || !selectedDetail) { + setError('No pause point is selected. Refresh and try again.') + return + } + setLoadingAction(true) + setError(null) + setMessage(null) + let resumePayload: any + try { + if (isHumanMode && hasInputFormat) { + const errors: Record = {} + const submission: Record = {} + for (const field of inputFormatFields) { + const rawValue = formValues[field.name] ?? '' + const hasValue = + field.type === 'boolean' + ? rawValue === 'true' || rawValue === 'false' + : rawValue.trim().length > 0 && rawValue !== '__unset__' + if (!hasValue || rawValue === '__unset__') { + if (field.required) errors[field.name] = 'This field is required.' + continue + } + const { value, error: parseError } = parseFormValue(field, rawValue) + if (parseError) { + errors[field.name] = parseError + continue } - if (Object.keys(errors).length > 0) { - setFormErrors(errors) - setError('Fix the highlighted fields before resuming.') + if (value !== undefined) submission[field.name] = value + } + if (Object.keys(errors).length > 0) { + setFormErrors(errors) + setError('Fix the highlighted fields before resuming.') + setLoadingAction(false) + return + } + setFormErrors({}) + resumePayload = { submission } + } else { + let parsedInput: any + if (resumeInput && resumeInput.trim().length > 0) { + try { + parsedInput = JSON.parse(resumeInput) + } catch { + setError('Resume input must be valid JSON.') setLoadingAction(false) return } - setFormErrors({}) - resumePayload = { submission } - } else { - let parsedInput: any - if (resumeInput && resumeInput.trim().length > 0) { - try { - parsedInput = JSON.parse(resumeInput) - } catch { - setError('Resume input must be valid JSON.') - setLoadingAction(false) - return - } - } - resumePayload = parsedInput } - } catch (err: any) { - setError(err?.message || 'Failed to prepare resume payload.') - setLoadingAction(false) + resumePayload = parsedInput + } + } catch (err: any) { + setError(err?.message || 'Failed to prepare resume payload.') + setLoadingAction(false) + return + } + try { + const { ok, payload } = await resumeMutation.mutateAsync({ + workflowId, + executionId, + contextId: selectedContextId, + input: resumePayload, + }) + if (!ok) { + setError(payload.error || 'Failed to resume execution.') return } - try { - const { ok, payload } = await resumeMutation.mutateAsync({ - workflowId, - executionId, - contextId: selectedContextId, - input: resumePayload, - }) - if (!ok) { - setError(payload.error || 'Failed to resume execution.') - return - } - const nextStatus = payload.status === 'queued' ? 'queued' : 'resuming' - const nextQueuePosition = payload.queuePosition ?? null - const fallbackContextId = - executionDetail?.pausePoints.find( - (point) => point.contextId !== selectedContextId && point.resumeStatus === 'paused' - )?.contextId ?? null - queryClient.setQueryData( - resumeKeys.execution(workflowId, executionId), - (prev) => { - if (!prev) return prev - return { - ...prev, - pausePoints: prev.pausePoints.map((point) => - point.contextId === selectedContextId - ? { ...point, resumeStatus: nextStatus, queuePosition: nextQueuePosition } - : point - ), - } + const nextStatus = payload.status === 'queued' ? 'queued' : 'resuming' + const nextQueuePosition = payload.queuePosition ?? null + const fallbackContextId = + executionDetail?.pausePoints.find( + (point) => point.contextId !== selectedContextId && point.resumeStatus === 'paused' + )?.contextId ?? null + queryClient.setQueryData( + resumeKeys.execution(workflowId, executionId), + (prev) => { + if (!prev) return prev + return { + ...prev, + pausePoints: prev.pausePoints.map((point) => + point.contextId === selectedContextId + ? { ...point, resumeStatus: nextStatus, queuePosition: nextQueuePosition } + : point + ), } - ) - queryClient.setQueryData( - resumeKeys.context(workflowId, executionId, selectedContextId), - (prev) => { - if (!prev || prev.pausePoint.contextId !== selectedContextId) return prev - return { - ...prev, - pausePoint: { - ...prev.pausePoint, - resumeStatus: nextStatus, - queuePosition: nextQueuePosition, - }, - } + } + ) + queryClient.setQueryData( + resumeKeys.context(workflowId, executionId, selectedContextId), + (prev) => { + if (!prev || prev.pausePoint.contextId !== selectedContextId) return prev + return { + ...prev, + pausePoint: { + ...prev.pausePoint, + resumeStatus: nextStatus, + queuePosition: nextQueuePosition, + }, } - ) - setSelectedContextIdOverride((override) => { - const currentContextId = override === undefined ? (defaultContextId ?? null) : override - return currentContextId !== selectedContextId ? override : fallbackContextId - }) - setMessage( - payload.status === 'queued' ? 'Resume request queued.' : 'Resume started successfully.' - ) - } catch (err: any) { - setError(err.message || 'Unexpected error while resuming execution.') - } finally { - setLoadingAction(false) - } - }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [ - workflowId, - executionId, - selectedContextId, - isHumanMode, - hasInputFormat, - inputFormatFields, - formValues, - parseFormValue, - resumeInput, - selectedDetail, - executionDetail, - queryClient, - ] - ) + } + ) + setSelectedContextIdOverride((override) => { + const currentContextId = override === undefined ? (defaultContextId ?? null) : override + return currentContextId !== selectedContextId ? override : fallbackContextId + }) + setMessage( + payload.status === 'queued' ? 'Resume request queued.' : 'Resume started successfully.' + ) + } catch (err: any) { + setError(err.message || 'Unexpected error while resuming execution.') + } finally { + setLoadingAction(false) + } + }, [ + workflowId, + executionId, + selectedContextId, + isHumanMode, + hasInputFormat, + inputFormatFields, + formValues, + parseFormValue, + resumeInput, + selectedDetail, + executionDetail, + queryClient, + ]) const isFormComplete = useMemo(() => { if (!isHumanMode || !hasInputFormat) return true diff --git a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/csv-import.ts b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/csv-import.ts index efd99ab7b40..0bb082d20fe 100644 --- a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/csv-import.ts +++ b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/csv-import.ts @@ -61,7 +61,6 @@ export function useCsvTruncationImport( } ) // importFile.mutate and router are stable references - // eslint-disable-next-line react-hooks/exhaustive-deps }, [workspaceId, file.id, file.key, file.name]) // Surface the cap as a warning toast with an import action, once per file. diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/components/terminal-session/terminal-session.tsx b/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/components/terminal-session/terminal-session.tsx index e37b6bf091c..2d7e2ccae20 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/components/terminal-session/terminal-session.tsx +++ b/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/components/terminal-session/terminal-session.tsx @@ -442,7 +442,6 @@ const TerminalView = memo(function TerminalView({ } // Theme is applied by the effect below so the terminal is never torn down // (and its buffer never lost) for a repaint. - // eslint-disable-next-line react-hooks/exhaustive-deps }, [terminalId, scopeId]) // Runs after the effect above, which is what installs these. diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-tabs/resource-tabs.tsx b/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-tabs/resource-tabs.tsx index 99cbbc3bf53..e2cb65ed72d 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-tabs/resource-tabs.tsx +++ b/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-tabs/resource-tabs.tsx @@ -363,7 +363,6 @@ export function ResourceTabs({ // The chat owner handles optimistic state and its single ordered persistence queue. onAddResource(resource) }, - // eslint-disable-next-line react-hooks/exhaustive-deps [desktopScopeId, onAddResource, selectResource] ) @@ -477,7 +476,6 @@ export function ResourceTabs({ anchorIdRef.current = null } }, - // eslint-disable-next-line react-hooks/exhaustive-deps [ activeId, chatId, @@ -574,7 +572,6 @@ export function ResourceTabs({ ).catch(() => toast.error('Could not reorder that terminal. Please try again.')) } }, - // eslint-disable-next-line react-hooks/exhaustive-deps [chatId, desktopScopeId, resources, onReorderResources] ) diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/user-input/components/prompt-editor/prompt-editor.tsx b/apps/sim/app/workspace/[workspaceId]/home/components/user-input/components/prompt-editor/prompt-editor.tsx index 63ab704b60f..eb06e0b0973 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/components/user-input/components/prompt-editor/prompt-editor.tsx +++ b/apps/sim/app/workspace/[workspaceId]/home/components/user-input/components/prompt-editor/prompt-editor.tsx @@ -86,7 +86,7 @@ export function PromptEditor({ useEffect(() => { if (autoFocus && !readOnly) editor.focusAtEnd() - // eslint-disable-next-line react-hooks/exhaustive-deps -- mount-only focus + // mount-only focus }, []) /** diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/user-input/user-input.tsx b/apps/sim/app/workspace/[workspaceId]/home/components/user-input/user-input.tsx index 639435293a8..26ee174eade 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/components/user-input/user-input.tsx +++ b/apps/sim/app/workspace/[workspaceId]/home/components/user-input/user-input.tsx @@ -207,7 +207,7 @@ const UserInputImpl = forwardRef(function UserI textarea.setSelectionRange(caretText.length, caretText.length) } } - }, []) // eslint-disable-line react-hooks/exhaustive-deps -- intentional mount-only restore + }, []) // intentional mount-only restore const isFirstSaveRef = useRef(true) const draftSaveTimerRef = useRef(null) diff --git a/apps/sim/app/workspace/[workspaceId]/home/home.tsx b/apps/sim/app/workspace/[workspaceId]/home/home.tsx index 947b0d31f12..02cc1039762 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/home.tsx +++ b/apps/sim/app/workspace/[workspaceId]/home/home.tsx @@ -298,7 +298,6 @@ function HomeContent({ chatId, userName, userId }: HomeProps) { // listing it would re-run this drain on every render. Omitted deliberately to // keep it one-shot — and harmless either way, since `consume` clears the entry // atomically and any re-run would find nothing. - // eslint-disable-next-line react-hooks/exhaustive-deps -- see above }, [chatId, workspaceId, prepareResourceViewForAgentTurn, sendMessage]) function resolveResourceFromContext( diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/knowledge.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/knowledge.tsx index bc26441338b..d4d09e15962 100644 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/knowledge.tsx +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/knowledge.tsx @@ -525,7 +525,7 @@ function KnowledgeContent() { await deleteKnowledgeBase.mutateAsync({ knowledgeBaseId: id }) logger.info(`Knowledge base deleted: ${id}`) }, - // eslint-disable-next-line react-hooks/exhaustive-deps -- mutation objects are unstable; mutateAsync is stable in v5 + // mutation objects are unstable; mutateAsync is stable in v5 [knowledgeBases, userPermissions.canEdit, userPermissions.canAdmin] ) @@ -933,7 +933,6 @@ function KnowledgeContent() { logger.error('Failed to create folder', createError) toast.error(getErrorMessage(createError, 'Failed to create folder')) } - // eslint-disable-next-line react-hooks/exhaustive-deps }, [workspaceId]) useRegisterGlobalCommands(() => [ @@ -987,7 +986,6 @@ function KnowledgeContent() { logger.error('Failed to delete folder', deleteError) toast.error(getErrorMessage(deleteError, 'Failed to delete folder')) } - // eslint-disable-next-line react-hooks/exhaustive-deps }, [workspaceId, openFolder, canDeleteFolder]) const descendantsByFolderId = useMemo(() => buildDescendantIndex(folders), [folders]) @@ -998,7 +996,7 @@ function KnowledgeContent() { const mutation = pinnedBaseIds.has(kb.id) ? unpinItem : pinItem mutation.mutate({ workspaceId, resourceType: 'knowledge_base', resourceId: kb.id }) closeRowContextMenu() - // eslint-disable-next-line react-hooks/exhaustive-deps -- mutation objects are unstable; mutate is stable in v5 + // mutation objects are unstable; mutate is stable in v5 }, [workspaceId, pinnedBaseIds, closeRowContextMenu]) const handleToggleFolderPin = useCallback(() => { @@ -1007,7 +1005,7 @@ function KnowledgeContent() { const mutation = pinnedFolderIds.has(folder.id) ? unpinItem : pinItem mutation.mutate({ workspaceId, resourceType: 'folder', resourceId: folder.id }) closeFolderContextMenu() - // eslint-disable-next-line react-hooks/exhaustive-deps -- mutation objects are unstable; mutate is stable in v5 + // mutation objects are unstable; mutate is stable in v5 }, [workspaceId, pinnedFolderIds, closeFolderContextMenu]) /** Move targets for the folder under the cursor: itself and its subtree are unreachable. */ @@ -1045,7 +1043,7 @@ function KnowledgeContent() { toast.error(getErrorMessage(moveError, 'Failed to move folder')) } }, - // eslint-disable-next-line react-hooks/exhaustive-deps -- mutation objects are unstable; mutateAsync is stable in v5 + // mutation objects are unstable; mutateAsync is stable in v5 [workspaceId] ) @@ -1111,7 +1109,7 @@ function KnowledgeContent() { } ) }, - // eslint-disable-next-line react-hooks/exhaustive-deps -- mutation objects are unstable; mutate is stable in v5 + // mutation objects are unstable; mutate is stable in v5 [clearSelection] ) @@ -1157,7 +1155,7 @@ function KnowledgeContent() { // The mutation toasts the request failure itself; the modal stays open to allow a retry. logger.error('Failed to delete selected items', deleteError) } - // eslint-disable-next-line react-hooks/exhaustive-deps -- mutation objects are unstable; mutateAsync is stable in v5 + // mutation objects are unstable; mutateAsync is stable in v5 }, [selectedKnowledgeBaseIds, selectedFolderIds, clearSelection, canDeleteSelection]) /** diff --git a/apps/sim/app/workspace/[workspaceId]/logs/components/log-details/log-details.tsx b/apps/sim/app/workspace/[workspaceId]/logs/components/log-details/log-details.tsx index 2bc06ab2b9a..725d6111468 100644 --- a/apps/sim/app/workspace/[workspaceId]/logs/components/log-details/log-details.tsx +++ b/apps/sim/app/workspace/[workspaceId]/logs/components/log-details/log-details.tsx @@ -345,7 +345,7 @@ export function LogDetailsContent({ log, onActiveTabChange }: LogDetailsContentP if (scrollAreaRef.current) { scrollAreaRef.current.scrollTop = 0 } - // eslint-disable-next-line react-hooks/exhaustive-deps -- stable nuqs setter; reset tab when switching logs + // stable nuqs setter; reset tab when switching logs }, [log.id]) const isLikelyExecution = !!log.executionId && log.trigger !== 'mothership' diff --git a/apps/sim/app/workspace/[workspaceId]/logs/logs.tsx b/apps/sim/app/workspace/[workspaceId]/logs/logs.tsx index 18cf3eca63d..d47cdbb9878 100644 --- a/apps/sim/app/workspace/[workspaceId]/logs/logs.tsx +++ b/apps/sim/app/workspace/[workspaceId]/logs/logs.tsx @@ -604,25 +604,20 @@ export default function Logs() { } catch (error) { toast.error(getErrorMessage(error, 'Failed to stop run')) } - // eslint-disable-next-line react-hooks/exhaustive-deps }, [contextMenuLog, userPermissions.canEdit]) - const retryLog = useCallback( - async (log: WorkflowLogRow | null) => { - const workflowId = log?.workflow?.id || log?.workflowId - const executionId = log?.executionId - if (!workflowId || !executionId) return - - try { - await retryExecution.mutateAsync({ workflowId, executionId }) - toast.success('Retry started') - } catch { - toast.error('Failed to retry execution') - } - }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [] - ) + const retryLog = useCallback(async (log: WorkflowLogRow | null) => { + const workflowId = log?.workflow?.id || log?.workflowId + const executionId = log?.executionId + if (!workflowId || !executionId) return + + try { + await retryExecution.mutateAsync({ workflowId, executionId }) + toast.success('Retry started') + } catch { + toast.error('Failed to retry execution') + } + }, []) const handleRetryExecution = useCallback(() => { retryLog(contextMenuLog) diff --git a/apps/sim/app/workspace/[workspaceId]/providers/global-commands-provider.tsx b/apps/sim/app/workspace/[workspaceId]/providers/global-commands-provider.tsx index deca164e3d1..b811c330674 100644 --- a/apps/sim/app/workspace/[workspaceId]/providers/global-commands-provider.tsx +++ b/apps/sim/app/workspace/[workspaceId]/providers/global-commands-provider.tsx @@ -230,6 +230,5 @@ export function useRegisterGlobalCommands(commands: GlobalCommand[] | (() => Glo })) const unregister = ctx.register(wrappedCommands) return unregister - // eslint-disable-next-line react-hooks/exhaustive-deps }, []) } diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/mcp/mcp.tsx b/apps/sim/app/workspace/[workspaceId]/settings/components/mcp/mcp.tsx index 3394e9e8bdf..eefe088e261 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/components/mcp/mcp.tsx +++ b/apps/sim/app/workspace/[workspaceId]/settings/components/mcp/mcp.tsx @@ -346,7 +346,7 @@ export function MCP() { if (!refreshServerMutation.isSuccess && !refreshServerMutation.isError) return const timeout = window.setTimeout(() => refreshServerMutation.reset(), 3000) return () => window.clearTimeout(timeout) - // eslint-disable-next-line react-hooks/exhaustive-deps -- mutation object is unstable; status flags are the triggers + // mutation object is unstable; status flags are the triggers }, [refreshServerMutation.isSuccess, refreshServerMutation.isError]) const editingServer = editingServerId diff --git a/apps/sim/app/workspace/[workspaceId]/tables/tables.tsx b/apps/sim/app/workspace/[workspaceId]/tables/tables.tsx index 199bfd9591d..01dab7d7975 100644 --- a/apps/sim/app/workspace/[workspaceId]/tables/tables.tsx +++ b/apps/sim/app/workspace/[workspaceId]/tables/tables.tsx @@ -939,7 +939,7 @@ function TablesContent() { moveTable.mutate({ tableId: activeTable.id, folderId }) closeRowContextMenu() }, - // eslint-disable-next-line react-hooks/exhaustive-deps -- mutation objects are unstable; mutate is stable in v5 + // mutation objects are unstable; mutate is stable in v5 [activeTable, closeRowContextMenu] ) @@ -954,7 +954,7 @@ function TablesContent() { } ) }, - // eslint-disable-next-line react-hooks/exhaustive-deps -- mutation objects are unstable; mutate is stable in v5 + // mutation objects are unstable; mutate is stable in v5 [workspaceId] ) @@ -992,7 +992,7 @@ function TablesContent() { } ) }, - // eslint-disable-next-line react-hooks/exhaustive-deps -- mutation objects are unstable; mutate is stable in v5 + // mutation objects are unstable; mutate is stable in v5 [clearSelection] ) @@ -1035,7 +1035,7 @@ function TablesContent() { // The mutation toasts the request failure itself; the modal stays open to allow a retry. logger.error('Failed to delete selected items:', err) } - // eslint-disable-next-line react-hooks/exhaustive-deps -- mutation objects are unstable; mutateAsync is stable in v5 + // mutation objects are unstable; mutateAsync is stable in v5 }, [selectedTableIds, selectedFolderIds, clearSelection]) /** @@ -1126,7 +1126,7 @@ function TablesContent() { const mutation = pinned ? unpinItem : pinItem mutation.mutate({ workspaceId, resourceType: target.resourceType, resourceId: target.id }) closeRowContextMenu() - // eslint-disable-next-line react-hooks/exhaustive-deps -- mutation objects are unstable; mutate is stable in v5 + // mutation objects are unstable; mutate is stable in v5 }, [ workspaceId, contextMenuKind, diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tag-dropdown/components/keyboard-navigation-handler.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tag-dropdown/components/keyboard-navigation-handler.tsx index 73e17424c91..274b4e07890 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tag-dropdown/components/keyboard-navigation-handler.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tag-dropdown/components/keyboard-navigation-handler.tsx @@ -188,7 +188,6 @@ export const KeyboardNavigationHandler: React.FC if (!visible || visibleIndices.length === 0) return setSelectedIndex(visibleIndices[0]) - // eslint-disable-next-line react-hooks/exhaustive-deps }, [visible, isInFolder, currentFolder, nestedPathLength]) const handleKeyboardEvent = useEffectEvent((e: KeyboardEvent) => { diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/toolbar/hooks/use-toolbar-item-interactions.ts b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/toolbar/hooks/use-toolbar-item-interactions.ts index c873513563f..3e085de68ad 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/toolbar/hooks/use-toolbar-item-interactions.ts +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/toolbar/hooks/use-toolbar-item-interactions.ts @@ -65,7 +65,6 @@ export function useToolbarItemInteractions({ dragPreviewRef.current = preview // Force browser to render the element by triggering reflow - // eslint-disable-next-line @typescript-eslint/no-unused-expressions preview.offsetHeight // Set the custom drag image with offset to center it on cursor diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/hooks/use-webhook-info.ts b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/hooks/use-webhook-info.ts index 332fea24821..efe315c72c2 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/hooks/use-webhook-info.ts +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/hooks/use-webhook-info.ts @@ -81,7 +81,6 @@ export function useWebhookInfo(blockId: string, workflowId: string): UseWebhookI logger.error('Error reactivating webhook:', error) } }, - // eslint-disable-next-line react-hooks/exhaustive-deps [workflowId, blockId] ) diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-block-dimensions.ts b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-block-dimensions.ts index c2a53a15f53..37c86255195 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-block-dimensions.ts +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-block-dimensions.ts @@ -40,6 +40,5 @@ export function useBlockDimensions({ updateBlockLayoutMetrics(blockId, dimensions) updateNodeInternals(blockId) } - // eslint-disable-next-line react-hooks/exhaustive-deps }, [blockId, updateBlockLayoutMetrics, updateNodeInternals, ...dependencies]) } diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution.ts b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution.ts index e2cd89e19e3..c794cad4af9 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution.ts +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution.ts @@ -2720,7 +2720,6 @@ export function useWorkflowExecution() { releaseReconnectOwnership() } } - // eslint-disable-next-line react-hooks/exhaustive-deps }, [activeWorkflowId, hasHydrated, reconnectAttemptNonce]) return { diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/hooks/use-workspace-management.ts b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/hooks/use-workspace-management.ts index 5c4f296dd66..7fb992912d6 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/hooks/use-workspace-management.ts +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/hooks/use-workspace-management.ts @@ -126,7 +126,6 @@ export function useWorkspaceManagement({ return false } }, - // eslint-disable-next-line react-hooks/exhaustive-deps [] ) @@ -167,7 +166,6 @@ export function useWorkspaceManagement({ throw error } }, - // eslint-disable-next-line react-hooks/exhaustive-deps [switchWorkspace] ) @@ -194,7 +192,6 @@ export function useWorkspaceManagement({ logger.error('Error deleting workspace:', error) } }, - // eslint-disable-next-line react-hooks/exhaustive-deps [switchWorkspace] ) @@ -228,7 +225,6 @@ export function useWorkspaceManagement({ throw error } }, - // eslint-disable-next-line react-hooks/exhaustive-deps [switchWorkspace, sessionUserId] ) diff --git a/apps/sim/connectors/asana/asana.ts b/apps/sim/connectors/asana/asana.ts index 83fdf9e2a9c..3751dd22509 100644 --- a/apps/sim/connectors/asana/asana.ts +++ b/apps/sim/connectors/asana/asana.ts @@ -298,7 +298,6 @@ async function listWorkspaceProjects( const projects: AsanaProject[] = [] let offset: string | undefined - // eslint-disable-next-line no-constant-condition while (true) { const result = await asanaGet<{ data: AsanaProject[]; next_page: { offset: string } | null }>( accessToken, diff --git a/apps/sim/lib/internal/file/parser.test.ts b/apps/sim/lib/internal/file/parser.test.ts index b1f72c57cee..5f52dcaac5b 100644 --- a/apps/sim/lib/internal/file/parser.test.ts +++ b/apps/sim/lib/internal/file/parser.test.ts @@ -51,7 +51,6 @@ const { storageConfig, mockGetBlobContainerClient, } = vi.hoisted(() => { - // eslint-disable-next-line @typescript-eslint/no-require-imports const actualPath = require('path') as typeof import('path') return { mockVerifyFileAccess: vi.fn().mockResolvedValue(true), diff --git a/apps/sim/lib/permission-groups/request-scope.server.ts b/apps/sim/lib/permission-groups/request-scope.server.ts index c972449dd5f..6f683451e0a 100644 --- a/apps/sim/lib/permission-groups/request-scope.server.ts +++ b/apps/sim/lib/permission-groups/request-scope.server.ts @@ -34,7 +34,6 @@ interface Storage { let storage: Storage if (typeof globalThis.process !== 'undefined' && globalThis.process.versions?.node) { - // eslint-disable-next-line @typescript-eslint/no-require-imports const { AsyncLocalStorage } = require('node:async_hooks') as typeof import('node:async_hooks') storage = new AsyncLocalStorage() } else { diff --git a/apps/sim/lib/posthog/server.ts b/apps/sim/lib/posthog/server.ts index a9cb0603907..e53539e184f 100644 --- a/apps/sim/lib/posthog/server.ts +++ b/apps/sim/lib/posthog/server.ts @@ -23,7 +23,6 @@ function getClient(): PostHog | null { return null } - // eslint-disable-next-line @typescript-eslint/no-require-imports const { PostHog } = require('posthog-node') as typeof import('posthog-node') _client = new PostHog(key, { host: 'https://us.i.posthog.com', diff --git a/apps/sim/lib/pptx-renderer/renderer/chart-renderer.ts b/apps/sim/lib/pptx-renderer/renderer/chart-renderer.ts index ccce38fd396..6b96bfb2c61 100644 --- a/apps/sim/lib/pptx-renderer/renderer/chart-renderer.ts +++ b/apps/sim/lib/pptx-renderer/renderer/chart-renderer.ts @@ -1331,12 +1331,10 @@ function buildBarChartOption( cfg?.showVal ? { show: true, - // eslint-disable-next-line @typescript-eslint/no-explicit-any position: mapBarLabelPosition(cfg.position, isStacked) as any, fontSize: cfg.fontSize ?? 9, ...(cfg.color ? { color: cfg.color } : {}), ...(cfg.bold === true ? { fontWeight: 'bold' } : {}), - // eslint-disable-next-line @typescript-eslint/no-explicit-any formatter: (params: any) => { const rawVal = params?.value const val = @@ -1368,7 +1366,6 @@ function buildBarChartOption( return { value: v, label: buildLabel(merged), - // eslint-disable-next-line @typescript-eslint/no-explicit-any } as any }) @@ -1617,7 +1614,6 @@ function buildPieChartOption( const explosions = firstSer ? parseExplosion(firstSer, firstSeries.categories.length) : undefined const pieData = firstSeries.categories.map((cat, i) => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any const item: any = { name: cat || `Item ${i + 1}`, value: firstSeries.values[i] ?? 0, @@ -1635,11 +1631,9 @@ function buildPieChartOption( // Build label formatter based on data label config; show value and percent when requested const fc = firstSeries.formatCode - // eslint-disable-next-line @typescript-eslint/no-explicit-any let labelFormatter: string | ((params: any) => string) = '{b}: {c} ({d}%)' if (sharedLabels) { if (sharedLabels.showVal && fc && fc.includes('%')) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any labelFormatter = (params: any) => { const parts: string[] = [] if (sharedLabels!.showCatName) parts.push(params.name) @@ -2164,7 +2158,6 @@ function buildStockChartOption( seriesArr[1].values[i] ?? 0, seriesArr[2].values[i] ?? 0, ]), - // eslint-disable-next-line @typescript-eslint/no-explicit-any renderItem: (_params: any, api: any) => { const xValue = api.value(0) const high = api.value(1) @@ -2488,7 +2481,6 @@ function buildChartPalette(chartXml: SafeXmlNode, ctx: RenderContext): string[] * font size was set on that element (i.e., value matches our hardcoded defaults). */ function applyDefaultFontSizes(option: echarts.EChartsOption, defaultFs: number): void { - // eslint-disable-next-line @typescript-eslint/no-explicit-any const opt = option as any // Title: our defaults are 12 or 14 — replace with the chart-space default @@ -2537,7 +2529,6 @@ function applyDefaultFontSizes(option: echarts.EChartsOption, defaultFs: number) } function applyDefaultFontFamily(option: echarts.EChartsOption, fontFamily: string): void { - // eslint-disable-next-line @typescript-eslint/no-explicit-any const opt = option as any if (opt.title?.textStyle && !opt.title.textStyle.fontFamily) { @@ -2574,7 +2565,6 @@ function applyLegendGridMargins( chartNode: SafeXmlNode, defaultFs: number | undefined ): void { - // eslint-disable-next-line @typescript-eslint/no-explicit-any const opt = option as any if (!opt.grid || !opt.legend) return if (opt.legend.show === false) return @@ -2628,7 +2618,6 @@ function applyLegendGridMargins( * This post-processes the ECharts option to set axis max when not explicitly provided. */ function applyNiceAxisRange(option: echarts.EChartsOption): void { - // eslint-disable-next-line @typescript-eslint/no-explicit-any const opt = option as any // Only applies to cartesian charts with axes @@ -3152,9 +3141,7 @@ export function parseChartXml(chartXml: SafeXmlNode, ctx: RenderContext): ParseC } if (plotAreaBg && option.grid) { // Apply plot area background via grid (for cartesian charts) - // eslint-disable-next-line @typescript-eslint/no-explicit-any ;(option.grid as any).backgroundColor = plotAreaBg - // eslint-disable-next-line @typescript-eslint/no-explicit-any ;(option.grid as any).show = true } diff --git a/apps/sim/lib/pptx-renderer/renderer/render-context.ts b/apps/sim/lib/pptx-renderer/renderer/render-context.ts index 16ec2131e64..c9c4abf2557 100644 --- a/apps/sim/lib/pptx-renderer/renderer/render-context.ts +++ b/apps/sim/lib/pptx-renderer/renderer/render-context.ts @@ -43,7 +43,6 @@ export function createRenderContext( const layout: LayoutData = presentation.layouts.get(layoutPath) || { placeholders: [], - // eslint-disable-next-line @typescript-eslint/no-explicit-any spTree: {} as any, rels: new Map(), showMasterSp: true, @@ -53,7 +52,6 @@ export function createRenderContext( colorMap: new Map(), textStyles: {}, placeholders: [], - // eslint-disable-next-line @typescript-eslint/no-explicit-any spTree: {} as any, rels: new Map(), } diff --git a/apps/sim/lib/pptx-renderer/renderer/shape-renderer.ts b/apps/sim/lib/pptx-renderer/renderer/shape-renderer.ts index 9bc86dbc35e..0e257b8c7b0 100644 --- a/apps/sim/lib/pptx-renderer/renderer/shape-renderer.ts +++ b/apps/sim/lib/pptx-renderer/renderer/shape-renderer.ts @@ -246,7 +246,6 @@ function createArrowMarker( default: return null } - // eslint-disable-next-line @typescript-eslint/no-explicit-any ;(marker as any)._markerId = id return marker @@ -650,7 +649,6 @@ export function renderShape(node: ShapeNodeData, ctx: RenderContext): HTMLElemen // Mark path as no-fill; the blend group handles it. // Tag the blend group so we can insert it before the main path later. path.setAttribute('fill', 'none') - // eslint-disable-next-line @typescript-eslint/no-explicit-any ;(path as any).__rectBlendGroup = blendGroup } else if (gradientFillData.type === 'radial') { const radialGrad = document.createElementNS(svgNs, 'radialGradient') @@ -855,7 +853,6 @@ export function renderShape(node: ShapeNodeData, ctx: RenderContext): HTMLElemen ) if (marker) { defs.appendChild(marker) - // eslint-disable-next-line @typescript-eslint/no-explicit-any path.setAttribute('marker-start', `url(#${(marker as any)._markerId})`) } } @@ -870,20 +867,14 @@ export function renderShape(node: ShapeNodeData, ctx: RenderContext): HTMLElemen ) if (marker) { defs.appendChild(marker) - // eslint-disable-next-line @typescript-eslint/no-explicit-any path.setAttribute('marker-end', `url(#${(marker as any)._markerId})`) } } } // Insert rect blend group (two linear gradients + lighten) before the main path - // eslint-disable-next-line @typescript-eslint/no-explicit-any if ((path as any).__rectBlendGroup) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - svg.appendChild((path as any).__rectBlendGroup)( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - path as any - ).__rectBlendGroup = undefined + svg.appendChild((path as any).__rectBlendGroup)(path as any).__rectBlendGroup = undefined } svg.appendChild(path) @@ -1442,7 +1433,6 @@ export function renderShape(node: ShapeNodeData, ctx: RenderContext): HTMLElemen const mask = `linear-gradient(to bottom, rgba(255,255,255,${stA.toFixed(3)}) ${stPos.toFixed(1)}%, rgba(255,255,255,${endA.toFixed(3)}) ${endPos.toFixed(1)}%)` const reflectValue = `below ${dist.toFixed(1)}px ${mask}` wrapper.style.setProperty('-webkit-box-reflect', reflectValue) - // eslint-disable-next-line @typescript-eslint/no-explicit-any ;(wrapper.style as any).webkitBoxReflect = reflectValue } } diff --git a/apps/sim/lib/pptx-renderer/renderer/text-renderer.ts b/apps/sim/lib/pptx-renderer/renderer/text-renderer.ts index 84f60b30292..1c0b3887e30 100644 --- a/apps/sim/lib/pptx-renderer/renderer/text-renderer.ts +++ b/apps/sim/lib/pptx-renderer/renderer/text-renderer.ts @@ -812,9 +812,7 @@ export function renderTextBody( // Gradient text fill: use background-clip to paint text with gradient if (runStyle.textGradientCss) { element.style.background = runStyle.textGradientCss - // eslint-disable-next-line @typescript-eslint/no-explicit-any ;(element.style as any).webkitBackgroundClip = 'text' - // eslint-disable-next-line @typescript-eslint/no-explicit-any ;(element.style as any).backgroundClip = 'text' element.style.color = 'transparent' } @@ -826,36 +824,26 @@ export function renderTextBody( // Ghost text: no fill + gradient outline → show outline fading via mask const outlineColor = '#ffffff' // base stroke color (gradient applied via mask) element.style.color = 'transparent' - // eslint-disable-next-line @typescript-eslint/no-explicit-any ;(element.style as any).webkitTextStrokeWidth = `${strokeW}px` - // eslint-disable-next-line @typescript-eslint/no-explicit-any ;(element.style as any).webkitTextStrokeColor = outlineColor - // eslint-disable-next-line @typescript-eslint/no-explicit-any ;(element.style as any).paintOrder = 'stroke fill' // Use mask-image to apply the gradient fade to the entire text element const maskGrad = runStyle.textOutlineGradientCss element.style.maskImage = maskGrad - // eslint-disable-next-line @typescript-eslint/no-explicit-any ;(element.style as any).webkitMaskImage = maskGrad } else if (runStyle.textNoFill && runStyle.textOutlineColor) { // Ghost text with solid outline element.style.color = 'transparent' - // eslint-disable-next-line @typescript-eslint/no-explicit-any ;(element.style as any).webkitTextStrokeWidth = `${strokeW}px` - // eslint-disable-next-line @typescript-eslint/no-explicit-any ;(element.style as any).webkitTextStrokeColor = runStyle.textOutlineColor - // eslint-disable-next-line @typescript-eslint/no-explicit-any ;(element.style as any).paintOrder = 'stroke fill' } else if (runStyle.textNoFill) { // noFill with no outline — invisible text (but keep space) element.style.color = 'transparent' } else if (runStyle.textOutlineColor) { // Outline with normal fill - // eslint-disable-next-line @typescript-eslint/no-explicit-any ;(element.style as any).webkitTextStrokeWidth = `${strokeW}px` - // eslint-disable-next-line @typescript-eslint/no-explicit-any ;(element.style as any).webkitTextStrokeColor = runStyle.textOutlineColor - // eslint-disable-next-line @typescript-eslint/no-explicit-any ;(element.style as any).paintOrder = 'stroke fill' } } diff --git a/apps/sim/lib/table/snapshot-cache.test.ts b/apps/sim/lib/table/snapshot-cache.test.ts index 8da48f06654..a17fe30935f 100644 --- a/apps/sim/lib/table/snapshot-cache.test.ts +++ b/apps/sim/lib/table/snapshot-cache.test.ts @@ -29,7 +29,6 @@ const table = { }, ], }, - // eslint-disable-next-line @typescript-eslint/no-explicit-any } as any let lastHandle: { diff --git a/apps/sim/lib/workflows/persistence/utils.test.ts b/apps/sim/lib/workflows/persistence/utils.test.ts index 255c0141ea1..9ac1eb02bb0 100644 --- a/apps/sim/lib/workflows/persistence/utils.test.ts +++ b/apps/sim/lib/workflows/persistence/utils.test.ts @@ -49,7 +49,6 @@ function asAppBlocks(blocks: T): Record { * These tests intentionally use old SubBlockTypes (textarea, select, messages-input, input) * to verify the migration logic converts them to new types. */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any function legacySubBlocks(subBlocks: Record): any { return subBlocks } diff --git a/apps/sim/serializer/index.edge-case-blocks.test.ts b/apps/sim/serializer/index.edge-case-blocks.test.ts index fd7eff39dc1..f7d208a6d7d 100644 --- a/apps/sim/serializer/index.edge-case-blocks.test.ts +++ b/apps/sim/serializer/index.edge-case-blocks.test.ts @@ -14,7 +14,6 @@ import type { BlockState } from '@/stores/workflows/workflow/types' * Hoisted mock setup - vi.mock is hoisted, so we need to hoist the config too. */ const { mockBlockConfigs, createMockGetBlock } = vi.hoisted(() => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any const mockBlockConfigs: Record = { starter: { name: 'Starter', @@ -39,7 +38,6 @@ const { mockBlockConfigs, createMockGetBlock } = vi.hoisted(() => { tools: { access: ['anthropic_chat', 'openai_chat'], config: { - // eslint-disable-next-line @typescript-eslint/no-explicit-any tool: (params: Record) => { const model = params.model || 'gpt-4o' if (model.includes('claude')) return 'anthropic' @@ -214,7 +212,6 @@ const { mockBlockConfigs, createMockGetBlock } = vi.hoisted(() => { }, } - // eslint-disable-next-line @typescript-eslint/no-explicit-any const createMockGetBlock = (extraConfigs: Record = {}) => { const configs = { ...mockBlockConfigs, ...extraConfigs } return (type: string) => configs[type] || null diff --git a/apps/sim/tools/gmail/utils.ts b/apps/sim/tools/gmail/utils.ts index 6fa514107fc..d988b6655ed 100644 --- a/apps/sim/tools/gmail/utils.ts +++ b/apps/sim/tools/gmail/utils.ts @@ -321,7 +321,6 @@ function generateBoundary(): string { * @see https://github.com/googleapis/google-api-nodejs-client/blob/main/samples/gmail/send.js */ export function encodeRfc2047(value: string): string { - // eslint-disable-next-line no-control-regex if (/^[\x00-\x7F]*$/.test(value)) { return value } diff --git a/apps/sim/tools/http/request.test.ts b/apps/sim/tools/http/request.test.ts index d574617f930..959fc4b3bd2 100644 --- a/apps/sim/tools/http/request.test.ts +++ b/apps/sim/tools/http/request.test.ts @@ -15,7 +15,6 @@ import { processUrl } from '@/tools/http/utils' process.env.VITEST = 'true' describe('HTTP Request Tool', () => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any let tester: ToolTester beforeEach(() => { diff --git a/knip.jsonc b/knip.jsonc index ad3883359dc..aa34c749d71 100644 --- a/knip.jsonc +++ b/knip.jsonc @@ -1,8 +1,8 @@ { "$schema": "https://unpkg.com/knip@6/schema.json", - // Plain `knip` (check:dead-code) gates reachability and dependency ownership. - // Unused exports, types, and duplicates are ratcheted by check:unused-exports, - // which reuses this config in the same knip pass. + // Plain `knip` reports reachability and dependency ownership. check:unused-exports runs + // knip with this config, gates these at zero, and ratchets unused exports, types, and + // duplicates in the same pass. "include": ["files", "dependencies", "unlisted", "unresolved"], // On by default: every workspace's `exports` map serves only this monorepo, so an entry export // no workspace imports is dead. The workspaces below turn it off because their entries are @@ -93,7 +93,12 @@ // ensure-pty-prebuilds.ts assembles these package names for universal builds. "ignoreDependencies": ["@lydell/node-pty-darwin-arm64", "@lydell/node-pty-darwin-x64"] }, - "apps/docs": { "includeEntryExports": false }, + "apps/docs": { + "includeEntryExports": false, + // scripts/generate-docs.ts copies this verbatim from apps/sim/components/icons.tsx, whose + // export surface is already ratcheted there; docs consumes only the icons it maps. + "ignoreIssues": { "components/icons.tsx": ["exports", "types", "duplicates"] } + }, "apps/pii": { "includeEntryExports": false }, "packages/cli": { "includeEntryExports": false }, "packages/db": { diff --git a/package.json b/package.json index 8584ebaf6b3..4cac3722380 100644 --- a/package.json +++ b/package.json @@ -130,8 +130,7 @@ "type-check": "turbo run type-check", "release": "bun run scripts/create-single-release.ts", "test:scripts": "vitest run --config scripts/vitest.config.ts", - "check:agent-cli-boundary": "bun run scripts/check-agent-cli-boundary.ts", - "check:dead-code": "bun run node_modules/knip/bin/knip.js" + "check:agent-cli-boundary": "bun run scripts/check-agent-cli-boundary.ts" }, "overrides": { "react": "19.2.4", diff --git a/packages/logger/src/request-context.ts b/packages/logger/src/request-context.ts index 918ab3460a4..04cd36734da 100644 --- a/packages/logger/src/request-context.ts +++ b/packages/logger/src/request-context.ts @@ -57,7 +57,6 @@ let storage: Storage if (typeof globalThis.process !== 'undefined' && globalThis.process.versions?.node) { // Node.js — use real AsyncLocalStorage - // eslint-disable-next-line @typescript-eslint/no-require-imports const { AsyncLocalStorage } = require('node:async_hooks') as typeof import('node:async_hooks') storage = new AsyncLocalStorage() } else { diff --git a/packages/testing/src/builders/workflow.builder.ts b/packages/testing/src/builders/workflow.builder.ts index 39075e46957..e8604067194 100644 --- a/packages/testing/src/builders/workflow.builder.ts +++ b/packages/testing/src/builders/workflow.builder.ts @@ -7,8 +7,6 @@ import { } from '../factories/block.factory' import type { Position } from '../types' -/* eslint-disable @typescript-eslint/no-explicit-any */ - /** * Fluent builder for creating complex workflow states. * diff --git a/packages/testing/src/factories/block.factory.ts b/packages/testing/src/factories/block.factory.ts index 8959a8ac4df..9a285fc769e 100644 --- a/packages/testing/src/factories/block.factory.ts +++ b/packages/testing/src/factories/block.factory.ts @@ -1,8 +1,6 @@ import { generateRandomString } from '@sim/utils/random' import type { BlockData, BlockOutput, Position } from '../types' -/* eslint-disable @typescript-eslint/no-explicit-any */ - /** * Options for creating a mock block. * All fields are optional - sensible defaults are provided. diff --git a/packages/testing/src/factories/edge.factory.ts b/packages/testing/src/factories/edge.factory.ts index c8fbd2a4292..b46214b96d5 100644 --- a/packages/testing/src/factories/edge.factory.ts +++ b/packages/testing/src/factories/edge.factory.ts @@ -1,5 +1,3 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ - import { generateRandomString } from '@sim/utils/random' /** diff --git a/packages/testing/src/factories/workflow.factory.ts b/packages/testing/src/factories/workflow.factory.ts index cec65a1da8f..bb670f50ba2 100644 --- a/packages/testing/src/factories/workflow.factory.ts +++ b/packages/testing/src/factories/workflow.factory.ts @@ -1,5 +1,3 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ - /** * Options for creating a mock workflow state. * Uses `any` for complex types to avoid conflicts with app types. diff --git a/packages/testing/src/mocks/blocks.mock.ts b/packages/testing/src/mocks/blocks.mock.ts index e9d5be0d9d0..62662b2ec00 100644 --- a/packages/testing/src/mocks/blocks.mock.ts +++ b/packages/testing/src/mocks/blocks.mock.ts @@ -12,8 +12,6 @@ * ``` */ -/* eslint-disable @typescript-eslint/no-explicit-any */ - import { vi } from 'vitest' /** diff --git a/packages/testing/src/types/index.ts b/packages/testing/src/types/index.ts index 6922126b6f3..0a9af48fba9 100644 --- a/packages/testing/src/types/index.ts +++ b/packages/testing/src/types/index.ts @@ -8,8 +8,6 @@ * Tests themselves provide type safety through their actual usage of app types. */ -/* eslint-disable @typescript-eslint/no-explicit-any */ - export interface Position { x: number y: number diff --git a/scripts/check-comment-hygiene.test.ts b/scripts/check-comment-hygiene.test.ts index 906bbb0d985..6a1108916b9 100644 --- a/scripts/check-comment-hygiene.test.ts +++ b/scripts/check-comment-hygiene.test.ts @@ -98,3 +98,28 @@ describe('commented-out-code', () => { expect(rules('// return ;', 'view.tsx')).toEqual(['commented-out-code@1']) }) }) + +describe('eslint-directive', () => { + it('flags own-line, trailing, and block ESLint directives', () => { + const source = [ + '// eslint-disable-next-line react-hooks/exhaustive-deps', + 'useEffect(() => {}, []) // eslint-disable-line react-hooks/exhaustive-deps', + '/* eslint-disable @typescript-eslint/no-explicit-any */', + '/* eslint-enable */', + ].join('\n') + expect(rules(source)).toEqual([ + 'eslint-directive@1', + 'eslint-directive@2', + 'eslint-directive@3', + 'eslint-directive@4', + ]) + }) + + it('ignores ESLint mentioned in prose and strings', () => { + const source = [ + '// Ported from a file that used an eslint-disable comment', + "const rule = '// eslint-disable-next-line'", + ].join('\n') + expect(rules(source)).toEqual([]) + }) +}) diff --git a/scripts/check-comment-hygiene.ts b/scripts/check-comment-hygiene.ts index e51e4e0b468..02624128228 100644 --- a/scripts/check-comment-hygiene.ts +++ b/scripts/check-comment-hygiene.ts @@ -1,6 +1,6 @@ #!/usr/bin/env bun /** - * Fails on the two comment patterns that are never documentation: + * Fails on the three comment patterns that are never documentation: * * - `banner`: a separator comment — `// ====`, `// --- Section ---`, `// ─── Title ───`, * `/* ------ v2 ------ *\/`. The repo convention is "no separators": structure lives in the @@ -8,10 +8,13 @@ * - `commented-out-code`: a run of `//` lines whose text parses as TypeScript. Git is the * history; dead code left in a comment rots, misleads readers and agents into thinking it * is a live option, and survives every refactor of the code around it. + * - `eslint-directive`: an `eslint-disable` / `eslint-enable` comment. The repo lints with Biome + * and nothing runs ESLint, so the directive suppresses nothing; it only misleads a reader into + * thinking a rule is enforced and deliberately waived here. Biome's form is `// biome-ignore`. * - * Both are detected conservatively so a hit is almost always real. Comments are read from the - * Babel token stream, so `//` inside strings and template literals is never inspected. A - * commented-out run only counts when it parses with no recovery errors, contains code + * Banners and commented-out code are detected conservatively so a hit is almost always real. + * Comments are read from the Babel token stream, so `//` inside strings and template literals is + * never inspected. A commented-out run only counts when it parses with no recovery errors, contains code * punctuation, and is not a lone label (`firstRow: bold`), literal, or `a = b` gloss; prose * lines inside a comment group split it into separate runs. Change-history phrasing * ("previously", "no longer", "used to") was measured and left out: most hits describe live @@ -20,8 +23,8 @@ * A legitimate exception (a code sample that must stay a line comment) takes * `// comment-hygiene-allow: ` anywhere in the same comment group. * - * The tree was swept clean when this landed (236 banners, 9 commented-out blocks), so there is - * no baseline: every hit fails. + * The tree was swept clean when each rule landed (236 banners, 9 commented-out blocks, 89 + * ESLint directives), so there is no baseline: every hit fails. * * Run: `bun run check:comment-hygiene` */ @@ -38,7 +41,7 @@ const ALLOW = 'comment-hygiene-allow:' /** Vendored or generated sources whose comments are not ours to edit. */ const EXCLUDED = [/\.d\.ts$/, /(^|\/)bundles\//, /(^|\/)node_modules\//] -export type Rule = 'banner' | 'commented-out-code' +export type Rule = 'banner' | 'commented-out-code' | 'eslint-directive' export interface Violation { rule: Rule @@ -49,6 +52,9 @@ export interface Violation { /** Comment text that opens with a run of separator characters. */ const BANNER = /^(?:={3,}|-{3,}|─{3,}|━{3,}|\*{3,}|~{3,})/ +/** Comment text that is an ESLint suppression directive. */ +const ESLINT_DIRECTIVE = /^eslint-(?:disable|enable)\b/ + /** At least one token that ordinary prose does not contain. */ const CODE_PUNCTUATION = /[;{}]|=>|\b(?:const|let|return|await|import|export)\s|\w\.\w+\(/ @@ -154,13 +160,14 @@ function firstCodeSpan(run: LineComment[], jsx: boolean): LineComment | undefine } /** - * A superset of every hit: a separator right after a comment opener, or a `//` line holding a - * {@link CODE_PUNCTUATION} token. Files without one skip the parse, which dominates the run. + * A superset of every hit: a separator or ESLint directive right after a comment opener, or a + * `//` line holding a {@link CODE_PUNCTUATION} token. Files without one skip the parse, which + * dominates the run. */ const MAY_VIOLATE = - /\/[/*][*\s]*(?:={3}|-{3}|─{3}|━{3}|\*{3}|~{3})|\/\/[^\n]*(?:[;{}]|=>|\b(?:const|let|return|await|import|export)\b|\w\.\w+\()/ + /\/[/*][*\s]*(?:={3}|-{3}|─{3}|━{3}|\*{3}|~{3}|eslint-(?:disable|enable))|\/\/[^\n]*(?:[;{}]|=>|\b(?:const|let|return|await|import|export)\b|\w\.\w+\()/ -/** Every banner and commented-out-code hit in one source file. */ +/** Every comment-hygiene hit in one source file. */ export function findViolations(file: string, source: string): Violation[] { if (!MAY_VIOLATE.test(source)) return [] const jsx = /\.[jt]sx$/.test(file) @@ -183,6 +190,12 @@ export function findViolations(file: string, source: string): Violation[] { for (const comment of comments) { const line = comment.loc?.start.line ?? 0 + const directive = comment.value.replace(/^\*+/, '').trim() + if (ESLINT_DIRECTIVE.test(directive)) { + violations.push({ rule: 'eslint-directive', line, text: directive }) + previous = undefined + continue + } if (comment.type === 'CommentBlock') { const singleLine = comment.loc?.start.line === comment.loc?.end.line const text = comment.value.replace(/^\*+/, '').trim() @@ -253,6 +266,9 @@ const FIX: Record = { 'commented-out-code': 'delete the commented-out code — git keeps the history. If it is a deliberate code sample, ' + 'put it in a TSDoc `@example` or add `// comment-hygiene-allow: ` to the group', + 'eslint-directive': + 'delete it — nothing runs ESLint. Keep any reason it carried as a plain `// ...` why; ' + + 'to waive a Biome rule, use `// biome-ignore : `', } function main(): void { @@ -270,7 +286,7 @@ function main(): void { } if (hits.length === 0) { - console.log('✓ comment hygiene (no banners or commented-out code)') + console.log('✓ comment hygiene (no banners, commented-out code, or ESLint directives)') return } console.error(hits.join('\n')) diff --git a/scripts/check-unused-exports.baseline.json b/scripts/check-unused-exports.baseline.json index c16098ee35e..13f0e994e34 100644 --- a/scripts/check-unused-exports.baseline.json +++ b/scripts/check-unused-exports.baseline.json @@ -57,72 +57,6 @@ "apps/desktop/src/main/window.ts#fitBoundsToWorkArea", "apps/desktop/src/main/window.ts#sanitizeBounds", "apps/desktop/src/main/windows.ts#registerPopupContents", - "apps/docs/components/icons.tsx#AgentSkillsIcon", - "apps/docs/components/icons.tsx#AnthropicIcon", - "apps/docs/components/icons.tsx#AtlassianIcon", - "apps/docs/components/icons.tsx#BasetenIcon", - "apps/docs/components/icons.tsx#BedrockIcon", - "apps/docs/components/icons.tsx#BigQueryIcon", - "apps/docs/components/icons.tsx#CalendarIcon", - "apps/docs/components/icons.tsx#CerebrasIcon", - "apps/docs/components/icons.tsx#CohereIcon", - "apps/docs/components/icons.tsx#CrewAIIcon", - "apps/docs/components/icons.tsx#DeepseekIcon", - "apps/docs/components/icons.tsx#DustIcon", - "apps/docs/components/icons.tsx#FalIcon", - "apps/docs/components/icons.tsx#FireworksIcon", - "apps/docs/components/icons.tsx#FlowiseIcon", - "apps/docs/components/icons.tsx#GeminiIcon", - "apps/docs/components/icons.tsx#GithubOutlineIcon", - "apps/docs/components/icons.tsx#GitlabIcon", - "apps/docs/components/icons.tsx#GoogleChatIcon", - "apps/docs/components/icons.tsx#GroqIcon", - "apps/docs/components/icons.tsx#GumloopIcon", - "apps/docs/components/icons.tsx#InputIcon", - "apps/docs/components/icons.tsx#InstagramIcon", - "apps/docs/components/icons.tsx#KieIcon", - "apps/docs/components/icons.tsx#KimiIcon", - "apps/docs/components/icons.tsx#LangChainIcon", - "apps/docs/components/icons.tsx#LangflowIcon", - "apps/docs/components/icons.tsx#LitellmIcon", - "apps/docs/components/icons.tsx#LucidIcon", - "apps/docs/components/icons.tsx#MailIcon", - "apps/docs/components/icons.tsx#MakeIcon", - "apps/docs/components/icons.tsx#MessagesIcon", - "apps/docs/components/icons.tsx#MetaIcon", - "apps/docs/components/icons.tsx#MicrosoftCopilotIcon", - "apps/docs/components/icons.tsx#MicrosoftIcon", - "apps/docs/components/icons.tsx#N8nIcon", - "apps/docs/components/icons.tsx#NotificationsIcon", - "apps/docs/components/icons.tsx#NvidiaIcon", - "apps/docs/components/icons.tsx#OllamaIcon", - "apps/docs/components/icons.tsx#OpenClawIcon", - "apps/docs/components/icons.tsx#OpenRouterIcon", - "apps/docs/components/icons.tsx#PipedreamIcon", - "apps/docs/components/icons.tsx#RetoolIcon", - "apps/docs/components/icons.tsx#SMSIcon", - "apps/docs/components/icons.tsx#SOC2BadgeIcon", - "apps/docs/components/icons.tsx#SSHIcon", - "apps/docs/components/icons.tsx#SakanaIcon", - "apps/docs/components/icons.tsx#ScheduleIcon", - "apps/docs/components/icons.tsx#ServerIcon", - "apps/docs/components/icons.tsx#SignalIcon", - "apps/docs/components/icons.tsx#SimAutoIcon", - "apps/docs/components/icons.tsx#SimDeploymentsIcon", - "apps/docs/components/icons.tsx#SlackMonoIcon", - "apps/docs/components/icons.tsx#SpotifyIcon", - "apps/docs/components/icons.tsx#SshTerminalIcon", - "apps/docs/components/icons.tsx#StackAIIcon", - "apps/docs/components/icons.tsx#TinesIcon", - "apps/docs/components/icons.tsx#TogetherIcon", - "apps/docs/components/icons.tsx#TypeSafeIcon", - "apps/docs/components/icons.tsx#VellumIcon", - "apps/docs/components/icons.tsx#VertexIcon", - "apps/docs/components/icons.tsx#VllmIcon", - "apps/docs/components/icons.tsx#WorkatoIcon", - "apps/docs/components/icons.tsx#ZaiIcon", - "apps/docs/components/icons.tsx#ZapierIcon", - "apps/docs/components/icons.tsx#xAIIcon", "apps/docs/components/workflow-preview/index.ts#PreviewBlock", "apps/docs/components/workflow-preview/index.ts#PreviewTool", "apps/docs/components/workflow-preview/index.ts#PreviewWorkflow", diff --git a/scripts/check-unused-exports.ts b/scripts/check-unused-exports.ts index bed43a8dd9d..fc53bbbfa9d 100644 --- a/scripts/check-unused-exports.ts +++ b/scripts/check-unused-exports.ts @@ -3,11 +3,11 @@ * Runs knip once for the whole dead-code audit: zero tolerance for unreachable files and * dependency drift, and a shrink-only ratchet for unused exports. * - * `knip.jsonc` scopes plain `knip` (`check:dead-code`) to files, dependencies, unlisted, and - * unresolved, because thousands of pre-existing unused exports would otherwise drown the report. - * That left exports unguarded: an agent could export a helper nothing imports, or keep a dead - * export alive after deleting its last caller, and nothing objected. This script adds knip's - * `exports`, `types`, and `duplicates` issues on top of the same pass: + * `knip.jsonc` scopes plain `knip` to files, dependencies, unlisted, and unresolved, because + * thousands of pre-existing unused exports would otherwise drown the report. That left exports + * unguarded: an agent could export a helper nothing imports, or keep a dead export alive after + * deleting its last caller, and nothing objected. This script adds knip's `exports`, `types`, + * and `duplicates` issues on top of the same pass: * * - Every other issue knip reports must be empty: `files`, `unlisted`, `unresolved`, and every * dependency type the `dependencies` include expands to (`devDependencies`, @@ -23,8 +23,7 @@ * files entries. An export whose only consumer knip cannot see (a path-based `import()`, or a * helper an audit names as the replacement) carries a `@public` TSDoc tag saying why. * - * Knip is slow, so `run-audits.ts` runs this script and skips `check:dead-code`, which stays - * available as the human-readable report. + * Knip is slow, so this is the only audit that runs it; `check:audits` runs this script. * * Run: `bun run check:unused-exports` */ @@ -116,7 +115,7 @@ if (strict.length) { console.error( '\nDelete unreachable files, declare or remove dependencies, and fix unresolved imports. ' + 'If knip cannot see a real entry point, add it to knip.jsonc with a comment saying why. ' + - 'Details: bun run check:dead-code' + 'Details: bun run node_modules/knip/bin/knip.js' ) } diff --git a/scripts/run-audits.ts b/scripts/run-audits.ts index b06890355cd..828d62831be 100644 --- a/scripts/run-audits.ts +++ b/scripts/run-audits.ts @@ -18,7 +18,6 @@ const EXCLUDED: Record = { 'check:audits': 'this runner', 'check:migrations': 'diffs against a git base ref (origin/staging by default)', 'check:api-validation': 'superseded by the :strict variant, which this runner does run', - 'check:dead-code': 'check:unused-exports gates the same issues in its single knip pass', } /** From b4577a9ee9b432c9565f62da00c787eaafbffb15 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Fri, 9 Oct 2026 19:06:56 -0700 Subject: [PATCH 2/5] fix(pptx): stop calling the appended rect blend group as a function --- apps/sim/lib/pptx-renderer/renderer/shape-renderer.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/sim/lib/pptx-renderer/renderer/shape-renderer.ts b/apps/sim/lib/pptx-renderer/renderer/shape-renderer.ts index 0e257b8c7b0..f25e4967c40 100644 --- a/apps/sim/lib/pptx-renderer/renderer/shape-renderer.ts +++ b/apps/sim/lib/pptx-renderer/renderer/shape-renderer.ts @@ -874,7 +874,8 @@ export function renderShape(node: ShapeNodeData, ctx: RenderContext): HTMLElemen // Insert rect blend group (two linear gradients + lighten) before the main path if ((path as any).__rectBlendGroup) { - svg.appendChild((path as any).__rectBlendGroup)(path as any).__rectBlendGroup = undefined + svg.appendChild((path as any).__rectBlendGroup) + ;(path as any).__rectBlendGroup = undefined } svg.appendChild(path) From ab852a23b38d29f1599dec9cdceae3f2e143af9c Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Fri, 9 Oct 2026 19:11:20 -0700 Subject: [PATCH 3/5] docs(skills): name both ESLint directives the comment audit rejects --- .agents/skills/you-might-not-need-a-comment/SKILL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.agents/skills/you-might-not-need-a-comment/SKILL.md b/.agents/skills/you-might-not-need-a-comment/SKILL.md index 90ce7e547b5..db3645bf022 100644 --- a/.agents/skills/you-might-not-need-a-comment/SKILL.md +++ b/.agents/skills/you-might-not-need-a-comment/SKILL.md @@ -33,7 +33,7 @@ This codebase's convention: **TSDoc for documentation; an inline `//` only for a - A `//` comment that explains a **non-obvious why**: a workaround for an upstream bug, an ordering constraint, a perf reason, a spec/edge-case the code can't self-document (`// first-match wins — matches the old find() semantics`). - Existing TSDoc `/** ... */` blocks on declarations — leave them (only tighten if verbose). - `// boundary-raw-fetch:`, `// double-cast-allowed:`, `// boundary-raw-json:`, `// untyped-response:`, `-- migration-safe:` (SQL migrations), `// rq-lint-allow:`, `// client-boundary-allow:` and any other `: ` annotation a script under `scripts/` greps for, in line-comment or block-comment form (e.g. the `/** svg-path-precision-exception: ... */` directive on icon paths) — these are load-bearing, never touch them. -- `// biome-ignore`, `// @ts-expect-error` and other tooling directives. (Nothing runs ESLint, so `eslint-disable` is dead and `check:comment-hygiene` rejects it.) +- `// biome-ignore`, `// @ts-expect-error` and other tooling directives. (Nothing runs ESLint, so `eslint-disable`/`eslint-enable` directives are dead; delete them — `check:comment-hygiene` rejects both.) - `// TODO` / `// FIXME` that point at real, still-open work. ## Bias From a97380df80ce7fa311cd732d60258219e2afcc4e Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Fri, 9 Oct 2026 19:11:38 -0700 Subject: [PATCH 4/5] docs: name both ESLint directives in the comment rule --- CLAUDE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index 673496b7d3a..d416a2a6463 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,7 +6,7 @@ This file (also `AGENTS.md`) holds the repo-wide rules. Area detail lives in `.c - **Package manager**: `bun` and `bunx`, never `npm` and `npx`. - **Logging**: `createLogger` from `@sim/logger`; `logger.info` / `logger.warn` / `logger.error`, never `console.log`. Inside `withRouteHandler` the logger already carries the request ID — no manual `withMetadata({ requestId })`. -- **Comments**: name things so the code explains itself. TSDoc documents exported APIs and non-obvious modules. An inline `//` is only for a terse, non-obvious *why*, or for a script-enforced `// : ` annotation (`boundary-raw-fetch`, `double-cast-allowed`, `boundary-raw-json`, `untyped-response`, `rq-lint-allow`, `client-boundary-allow`, `utils-lint-allow`, …). History belongs in the commit message. No `====` separators, commented-out code, or `eslint-disable` directives (nothing runs ESLint; `check:comment-hygiene` enforces all three). The `/you-might-not-need-a-comment` skill applies this to a diff. +- **Comments**: name things so the code explains itself. TSDoc documents exported APIs and non-obvious modules. An inline `//` is only for a terse, non-obvious *why*, or for a script-enforced `// : ` annotation (`boundary-raw-fetch`, `double-cast-allowed`, `boundary-raw-json`, `untyped-response`, `rq-lint-allow`, `client-boundary-allow`, `utils-lint-allow`, …). History belongs in the commit message. No `====` separators, commented-out code, or ESLint directives (`eslint-disable`/`eslint-enable`; nothing runs ESLint; `check:comment-hygiene` enforces all three). The `/you-might-not-need-a-comment` skill applies this to a diff. - **ID generation**: `generateId()` (UUID v4, the default) or `generateShortId(size?)` (URL-safe, 21 chars by default) from `@sim/utils/id` — never `crypto.randomUUID()`, `nanoid`, or `uuid`. Both use `crypto.getRandomValues()`, so they also work in non-secure (HTTP) browsers. For other randomness, `@sim/utils/random` (`randomInt`, `randomFloat`, `randomItem`, `generateRandomBytes`, `generateRandomHex`) — never `Math.random()` or `crypto.randomBytes()`. - **Common utilities**: use the shared helpers from the `@sim/utils/` subpaths instead of inline implementations (`check:utils` bans most of the inline forms below): - `sleep(ms)` from `@sim/utils/helpers` — never `new Promise(resolve => setTimeout(resolve, ms))` From 1b24113b174f55e93caaf1742f1092b1f1ed7a55 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Fri, 9 Oct 2026 19:21:16 -0700 Subject: [PATCH 5/5] test(pptx): cover the rect gradient blend-group path --- .../renderer/shape-renderer.test.ts | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 apps/sim/lib/pptx-renderer/renderer/shape-renderer.test.ts diff --git a/apps/sim/lib/pptx-renderer/renderer/shape-renderer.test.ts b/apps/sim/lib/pptx-renderer/renderer/shape-renderer.test.ts new file mode 100644 index 00000000000..c2f84f23480 --- /dev/null +++ b/apps/sim/lib/pptx-renderer/renderer/shape-renderer.test.ts @@ -0,0 +1,94 @@ +/** + * @vitest-environment jsdom + */ +import { describe, expect, it } from 'vitest' +import { parseShapeNode } from '@/lib/pptx-renderer/model/nodes/shape-node' +import { parseXml } from '@/lib/pptx-renderer/parser/xml-parser' +import type { RenderContext } from '@/lib/pptx-renderer/renderer/render-context' +import { renderShape } from '@/lib/pptx-renderer/renderer/shape-renderer' + +const EMPTY_NODE = parseXml( + '' +) + +function createContext(): RenderContext { + const slide = { + index: 0, + nodes: [], + layoutIndex: '', + rels: new Map(), + slidePath: 'ppt/slides/slide1.xml', + showMasterSp: true, + } + + return { + presentation: { + width: 960, + height: 540, + slides: [slide], + layouts: new Map(), + masters: new Map(), + themes: new Map(), + slideToLayout: new Map(), + layoutToMaster: new Map(), + masterToTheme: new Map(), + media: new Map(), + charts: new Map(), + isWps: false, + }, + slide, + theme: { + colorScheme: new Map(), + majorFont: { latin: 'Calibri', ea: '', cs: '' }, + minorFont: { latin: 'Calibri', ea: '', cs: '' }, + fillStyles: [], + lineStyles: [], + effectStyles: [], + }, + master: { + colorMap: new Map(), + textStyles: {}, + placeholders: [], + spTree: EMPTY_NODE, + rels: new Map(), + }, + layout: { + placeholders: [], + spTree: EMPTY_NODE, + rels: new Map(), + showMasterSp: true, + }, + mediaUrlCache: new Map(), + colorCache: new Map(), + } +} + +describe('renderShape', () => { + it('inserts the rect gradient blend group before the main path', () => { + const node = parseShapeNode( + parseXml(` + + + + + + + + + + + + + + + `) + ) + + const svg = renderShape(node, createContext()).querySelector('svg') + + const blendGroup = svg?.querySelector(':scope > g[clip-path]') + expect(blendGroup?.nextElementSibling?.localName).toBe('path') + expect(blendGroup?.nextElementSibling?.getAttribute('fill')).toBe('none') + }) +})