From 588b8ce270fbf13b19840df02bb935928822cc83 Mon Sep 17 00:00:00 2001 From: Waleed Date: Fri, 2 Oct 2026 01:16:25 -0700 Subject: [PATCH 1/6] improvement(audits): enforce console, helper, render-path, persist, and deployment-flag rules (#8559) * chore(lint): ban console in runtime code with Biome noConsole Runtime code logs through createLogger from @sim/logger. Scripts, CLIs, script-migrations, the logger itself, SDK examples, and tests keep console as their interface. Autofix is disabled so lint --unsafe never silently deletes a console call. * refactor(utils): replace inline toError, isRecordLike, omit, and truncate idioms with @sim/utils helpers * refactor(ui): lazy-init object refs and use size-* for equal height and width useRef(new X()) built a throwaway X on every render; the refs now lazy-init through ??= as sim-react-performance.md prescribes. Equal h-N w-N pairs become size-N per sim-styling.md. * improvement(audits): extend check:utils to the remaining written idioms Adds toError, isRecordLike, filterUndefined, omit, truncate, and escapeRegExp idioms from CLAUDE.md, plus render-path rules: ES2023 array methods in browser code, useRef(new X()), and h-N w-N pairs. * improvement(audits): require an explicit partialize on every zustand persist sim-stores.md requires persist to whitelist durable fields; check:zustand-v5 now fails on a persist with no partialize or one that spreads the whole state. canvas-mode was the one store without it. * improvement(audits): flag deployment-shape env-flags imports in client settings surfaces check:client-boundary now fails when a 'use client' module under the workspace, organization, or standalone settings surfaces imports isHosted, isBillingEnabled, isChatEnabled, or an enterprise feature flag from env-flags instead of reading the seeded deployment shape. * docs(agents): name the check that enforces the common-utilities list * docs(agents): scope the check:utils note to the forms it bans * perf(audits): gate backreference patterns in check:utils behind literal prefilters The h-N/w-N, toError, and truncate patterns backtrack from every word boundary; a cheap literal test per file keeps the scan at ~1s of CPU. * refactor(ui): lazy-init useRef containers the nested-generic pattern missed Allocate Map/Set ref containers once instead of on every render, and drop the redundant processedRemovalIds alias in the toast provider. * improvement(audits): close detector gaps in check:utils and the deployment-shape rule - check:utils: match useRef(new X()) with nested generics, honor utils-lint-allow above formatter-wrapped statements, drop h-screen/w-screen from the size-N rule, and skip server-only App Router files in the ES2023 rule - deployment-shape rule: cover stores/, hooks/, blocks/ and surface hooks, read namespace imports, derive the flag list from deployment-shape.ts, parse long import clauses whole, and allowlist the panel store's module-init isChatEnabled - zustand persist message names the hoisted-options escape - biome: allow console in *.integration.ts, *.spec.ts, and desktop e2e * improvement(audits): share the directive classifier and simplify check:utils - move leadingDirective/directiveOn into scripts/source-kind.ts; check:utils uses it instead of its own 'use client' regex, and multi-line block-comment headers now parse - replace the deployment-shape allowlist with a client-boundary-allow annotation on the panel store's isChatEnabled import - exempt all of packages/utils/src by prefix (drops the stale retry.test.ts entry) - build both truncate patterns from one shared fragment and prefilter - add literal prefilters to isRecordLike, fromEntries, and useRef patterns and memoize prefilter results per file - trim the deployment-shape rationale to a CLAUDE.md pointer * refactor: drop isRecordLike pass-through wrappers and return audioLevels directly useSpeechToText returns its stable, in-place-filled Float32Array instead of a nullable ref; MicButton and the composer, search, and user-input props follow. * improvement(audits): ban ES2023 array methods repo-wide, catch whole-state partialize, strip inline directive comments * improvement(audits): follow aliased persist imports, require strict !== for filterUndefined, state the .with scope --- .claude/rules/sim-components.md | 4 +- .claude/rules/sim-react-performance.md | 2 +- .cursor/rules/sim-components.mdc | 4 +- .cursor/rules/sim-react-performance.mdc | 2 +- CLAUDE.md | 4 +- apps/desktop/src/main/local-filesystem.ts | 5 +- apps/docs/app/api/search/route.ts | 5 +- apps/docs/app/llms-full.txt/route.ts | 5 +- apps/docs/app/llms.txt/route.ts | 5 +- apps/docs/components/ui/video-placeholder.tsx | 2 +- .../workflow-preview/block-preview.tsx | 2 +- .../workflow-preview/docs-container-node.tsx | 2 +- .../workflow-preview/workflow-preview.tsx | 4 +- apps/docs/package.json | 1 + .../chat/components/input/input.tsx | 4 +- .../hero-chat-loop/hero-chat-loop.tsx | 2 +- .../hero-platform-loop/hero-platform-loop.tsx | 4 +- .../hero-platform-loop/stage-block-card.tsx | 2 +- .../workflow-menu-preview.tsx | 9 +- .../platform-hero-visual.tsx | 2 +- .../build-agents-loop/build-agents-loop.tsx | 4 +- .../governance-loop/governance-loop.tsx | 4 +- .../shared/editor-loop/editor-loop.tsx | 4 +- .../solutions-card/solutions-card.tsx | 2 +- .../solutions-visual-frame.tsx | 4 +- .../components/solutions-page/constants.ts | 4 +- .../components/contact-form/contact-form.tsx | 2 +- .../components/demo-booking/demo-booking.tsx | 8 +- .../enterprise-home-stage.tsx | 2 +- .../enterprise-platform-loop.tsx | 4 +- .../feature-graphic-shell.tsx | 2 +- .../files/components/files-hero-loop.tsx | 4 +- .../integrations/(shell)/[slug]/page.tsx | 2 +- .../connector-sync-graphic.tsx | 2 +- .../components/knowledge-hero-loop.tsx | 4 +- .../logs/components/logs-hero-loop.tsx | 4 +- .../tables/components/tables-hero-loop.tsx | 6 +- .../workflow-canvas-graphic.tsx | 2 +- .../app/_shell/hydration-error-handler.tsx | 1 + apps/sim/app/api/mcp/oauth/start/route.ts | 13 +- .../components/search-input-bar.tsx | 2 +- .../components/composer/composer.test.tsx | 2 +- .../home/components/composer/composer.tsx | 2 +- .../app/o/[organizationId]/search/search.tsx | 2 +- .../files/[fileId]/view/file-viewer.tsx | 2 +- .../file-row-context-menu.tsx | 2 +- .../components/file-viewer/image-preview.tsx | 2 +- .../components/file-viewer/preview-panel.tsx | 4 +- .../agent-group/tool-permission-card.tsx | 3 +- .../browser-session/browser-session.test.ts | 4 +- .../browser-session/browser-session.tsx | 17 ++- .../components/mic-button/mic-button.tsx | 18 +-- .../components/send-button/send-button.tsx | 4 +- .../home/components/user-input/user-input.tsx | 4 +- .../preview/use-file-preview-controller.ts | 24 +-- .../hooks/stream/handle-resource-event.ts | 10 +- .../home/hooks/stream/stream-context.ts | 13 +- .../home/hooks/stream/stream-test-helpers.ts | 3 +- .../[workspaceId]/home/hooks/use-chat.ts | 115 +++++++------- .../[block]/integration-skills-section.tsx | 9 +- .../providers/global-commands-provider.tsx | 11 +- .../settings/components/general/general.tsx | 4 +- .../select-field/select-options-editor.tsx | 11 +- .../components/table-filter/table-filter.tsx | 11 +- .../table-grid/headers/column-header-menu.tsx | 6 +- .../workflow-sidebar/workflow-sidebar.tsx | 2 +- .../w/[workflowId]/components/chat/chat.tsx | 2 +- .../user-input/hooks/use-file-attachments.ts | 23 +-- .../components/general/general.tsx | 6 +- .../connection-blocks/connection-blocks.tsx | 9 +- .../condition-input/condition-input.tsx | 24 +-- .../input-mapping/input-mapping.tsx | 20 +-- .../sub-block/components/table/table.tsx | 30 ++-- .../components/tag-dropdown/tag-dropdown.tsx | 23 +-- .../panel/components/editor/editor.tsx | 2 +- .../w/[workflowId]/components/panel/panel.tsx | 7 +- .../hooks/use-dynamic-handle-refresh.ts | 4 +- .../[workspaceId]/w/[workflowId]/workflow.tsx | 51 ++++--- .../preview-editor/preview-editor.tsx | 10 +- .../w/components/sidebar/sidebar.tsx | 4 +- .../app/workspace/[workspaceId]/w/page.tsx | 4 +- .../workspace/providers/socket-provider.tsx | 57 +++---- apps/sim/blocks/blocks/google_slides.ts | 5 +- apps/sim/components/ui/progress.tsx | 2 +- apps/sim/connectors/airtable/airtable.ts | 3 +- apps/sim/executor/execution/block-executor.ts | 4 +- .../handlers/pi/cloud/authoring/backend.ts | 7 +- .../handlers/pi/cloud/babysit/backend.ts | 2 +- .../executor/handlers/pi/local/ssh-tools.ts | 3 +- apps/sim/executor/variables/resolver.ts | 2 +- apps/sim/hooks/mcp/use-mcp-oauth-popup.ts | 22 +-- apps/sim/hooks/queries/tables.ts | 5 +- apps/sim/hooks/use-speech-to-text.ts | 16 +- apps/sim/hooks/use-voice-input.test.tsx | 2 +- apps/sim/instrumentation-node.ts | 3 +- apps/sim/lib/internal/instagram/publishing.ts | 1 + .../agent-cli/engines/universal-grep.ts | 3 +- .../anthropic/structured-output-schema.ts | 3 +- apps/sim/providers/stream-pump.ts | 5 +- apps/sim/stores/canvas-mode/store.ts | 2 +- apps/sim/stores/panel/store.ts | 2 + apps/sim/stores/workflow-diff/store.ts | 9 +- apps/sim/tools/github/get_file_content.ts | 10 +- biome.json | 21 ++- bun.lock | 1 + .../emcn/src/components/checkbox/checkbox.tsx | 12 +- .../src/components/chip-input/chip-input.tsx | 2 +- .../dropdown-menu/dropdown-menu.tsx | 2 +- .../src/components/input-otp/input-otp.tsx | 2 +- .../emcn/src/components/popover/popover.tsx | 4 +- .../src/components/tag-input/tag-input.tsx | 2 +- packages/emcn/src/components/toast/toast.tsx | 23 ++- .../src/commands/protocol/workflow-run-get.ts | 16 +- packages/sim-cli/src/http/client.ts | 11 +- packages/testing/src/environment/index.ts | 1 + .../mocks/human-in-the-loop-manager.mock.ts | 13 +- .../mocks/providers-trace-enrichment.mock.ts | 5 +- .../workflow-block/workflow-block-view.tsx | 2 +- scripts/check-client-boundary-imports.ts | 124 +++++++++++---- scripts/check-utils-enforcement.ts | 144 +++++++++++++++--- scripts/check-zustand-v5-selectors.ts | 52 ++++++- scripts/source-kind.ts | 25 +++ 122 files changed, 786 insertions(+), 498 deletions(-) create mode 100644 scripts/source-kind.ts diff --git a/.claude/rules/sim-components.md b/.claude/rules/sim-components.md index 21a68a051e2..6c6179b2ef2 100644 --- a/.claude/rules/sim-components.md +++ b/.claude/rules/sim-components.md @@ -33,7 +33,7 @@ export function Component({ requiredProp, optionalProp = false }: ComponentProps When rendering or sorting a list of rows against a lookup collection (members, folders, tags), keep the per-row work O(1): - **Precompute a lookup `Map` once**, never `array.find(...)` per row. Build `const byId = useMemo(() => { const m = new Map(); for (const x of items ?? []) m.set(x.id, x); return m }, [items])` and read `byId.get(id)` in the sort comparator, `.map(...)`, and cell builders. A `.find` inside a sort comparator is O(n²·log n) — the worst offender. Depend memos on the derived `Map`, not the raw array. -- **Sort a copy with `[...array].sort(cmp)`**, never `toSorted` in client code — see `sim-react-performance.md` → "Never mutate a shared array in place". +- **Sort a copy with `[...array].sort(cmp)`**, never `toSorted` (`check:utils` bans it repo-wide) — see `sim-react-performance.md` → "Never mutate a shared array in place". - **Partition in a single pass** — when splitting one collection into several (`fileIds`/`folderIds`), do one `for…of` pushing into each bucket and return `{ a, b }` from a single `useMemo`, not two memos that each `map→filter→map` the same source twice. ## react-doctor (`bunx react-doctor`) — apply the wins, skip the false positives @@ -41,7 +41,7 @@ When rendering or sorting a list of rows against a lookup collection (members, f react-doctor diagnostics are hypotheses, not verdicts — confirm against the code before acting, and preserve behavior. Known repo-specific false positives to NOT "fix": - `no-barrel-import` — barrel imports are the repo convention (see sim-imports.md, "Barrel Exports"). Keep them. -- `js-tosorted-immutable` — won't-fix in `'use client'` code (see "List-render performance" above); apply it only in server-only modules. +- `js-tosorted-immutable` — won't-fix anywhere; `check:utils` bans the ES2023 array methods repo-wide. - `rerender-state-only-in-handlers` / "state set but never rendered" — a false positive when the `useState` is consumed by a `useEffect`/`useLayoutEffect` dependency (the effect must re-run on change). Only convert to a ref when nothing reads the value reactively. - `no-render-in-render` — a helper *called inline* (`{renderRow()}`) is reconciled by position and does **not** remount, so extracting it to a component is usually pure churn and can regress behavior (prop-drilling many closures, focus/scroll loss on the inner ``). Apply it only when the helper is genuinely a *component defined during render*, or when the move is mechanical (a stateless, ref-free helper whose closures become a small, explicit prop set). - `async-await-in-loop` on an upload/progress loop where sequential execution is intentional (per-item progress, server backpressure) — leave it. diff --git a/.claude/rules/sim-react-performance.md b/.claude/rules/sim-react-performance.md index a9bfeaa7f63..668e667a5d8 100644 --- a/.claude/rules/sim-react-performance.md +++ b/.claude/rules/sim-react-performance.md @@ -77,7 +77,7 @@ return items.sort(compare) return [...items].sort(compare) ``` -**Do NOT reach for `toSorted()` / `toReversed()` / `with()` / `toSpliced()` on client render paths.** They are ES2023 *runtime* methods — and a tsconfig `"lib": ["ES2023"]` only makes them **type-check**, it does not make them **run**. Next/SWC compiles syntax but does **not** polyfill prototype methods, and the default browserslist still includes browsers without them (`toSorted` landed in Safari 16 / iOS 16, so any device capped at iOS 15 throws `TypeError: x.toSorted is not a function` and crashes the page). The perf difference vs `[...arr].sort()` is negligible (both allocate one array), so the copy-then-sort form is the correct default everywhere client code runs. Only consider the immutable methods in Node-only code (server routes, scripts) on Node ≥20, where the runtime is known. +**Do NOT use `toSorted()` / `toReversed()` / `with()` / `toSpliced()`.** They are ES2023 *runtime* methods — and a tsconfig `"lib": ["ES2023"]` only makes them **type-check**, it does not make them **run**. Next/SWC compiles syntax but does **not** polyfill prototype methods, and the default browserslist still includes browsers without them (`toSorted` landed in Safari 16 / iOS 16, so any device capped at iOS 15 throws `TypeError: x.toSorted is not a function` and crashes the page). The perf difference vs `[...arr].sort()` is negligible (both allocate one array), so the copy-then-sort form is used everywhere: whether a module reaches the browser is not visible from its path. `check:utils` flags `toSorted`/`toReversed`/`toSpliced` repo-wide and `with` when called with a numeric index (an identifier index is indistinguishable from OpenTelemetry's `context.with`). ## Run independent awaits in parallel diff --git a/.cursor/rules/sim-components.mdc b/.cursor/rules/sim-components.mdc index dc49e5f8167..d592832a0c5 100644 --- a/.cursor/rules/sim-components.mdc +++ b/.cursor/rules/sim-components.mdc @@ -34,7 +34,7 @@ export function Component({ requiredProp, optionalProp = false }: ComponentProps When rendering or sorting a list of rows against a lookup collection (members, folders, tags), keep the per-row work O(1): - **Precompute a lookup `Map` once**, never `array.find(...)` per row. Build `const byId = useMemo(() => { const m = new Map(); for (const x of items ?? []) m.set(x.id, x); return m }, [items])` and read `byId.get(id)` in the sort comparator, `.map(...)`, and cell builders. A `.find` inside a sort comparator is O(n²·log n) — the worst offender. Depend memos on the derived `Map`, not the raw array. -- **Sort a copy with `[...array].sort(cmp)`**, never `toSorted` in client code — see `sim-react-performance.md` → "Never mutate a shared array in place". +- **Sort a copy with `[...array].sort(cmp)`**, never `toSorted` (`check:utils` bans it repo-wide) — see `sim-react-performance.md` → "Never mutate a shared array in place". - **Partition in a single pass** — when splitting one collection into several (`fileIds`/`folderIds`), do one `for…of` pushing into each bucket and return `{ a, b }` from a single `useMemo`, not two memos that each `map→filter→map` the same source twice. ## react-doctor (`bunx react-doctor`) — apply the wins, skip the false positives @@ -42,7 +42,7 @@ When rendering or sorting a list of rows against a lookup collection (members, f react-doctor diagnostics are hypotheses, not verdicts — confirm against the code before acting, and preserve behavior. Known repo-specific false positives to NOT "fix": - `no-barrel-import` — barrel imports are the repo convention (see sim-imports.md, "Barrel Exports"). Keep them. -- `js-tosorted-immutable` — won't-fix in `'use client'` code (see "List-render performance" above); apply it only in server-only modules. +- `js-tosorted-immutable` — won't-fix anywhere; `check:utils` bans the ES2023 array methods repo-wide. - `rerender-state-only-in-handlers` / "state set but never rendered" — a false positive when the `useState` is consumed by a `useEffect`/`useLayoutEffect` dependency (the effect must re-run on change). Only convert to a ref when nothing reads the value reactively. - `no-render-in-render` — a helper *called inline* (`{renderRow()}`) is reconciled by position and does **not** remount, so extracting it to a component is usually pure churn and can regress behavior (prop-drilling many closures, focus/scroll loss on the inner ``). Apply it only when the helper is genuinely a *component defined during render*, or when the move is mechanical (a stateless, ref-free helper whose closures become a small, explicit prop set). - `async-await-in-loop` on an upload/progress loop where sequential execution is intentional (per-item progress, server backpressure) — leave it. diff --git a/.cursor/rules/sim-react-performance.mdc b/.cursor/rules/sim-react-performance.mdc index f5f523f7e57..cf526216c5f 100644 --- a/.cursor/rules/sim-react-performance.mdc +++ b/.cursor/rules/sim-react-performance.mdc @@ -80,7 +80,7 @@ return items.sort(compare) return [...items].sort(compare) ``` -**Do NOT reach for `toSorted()` / `toReversed()` / `with()` / `toSpliced()` on client render paths.** They are ES2023 *runtime* methods — and a tsconfig `"lib": ["ES2023"]` only makes them **type-check**, it does not make them **run**. Next/SWC compiles syntax but does **not** polyfill prototype methods, and the default browserslist still includes browsers without them (`toSorted` landed in Safari 16 / iOS 16, so any device capped at iOS 15 throws `TypeError: x.toSorted is not a function` and crashes the page). The perf difference vs `[...arr].sort()` is negligible (both allocate one array), so the copy-then-sort form is the correct default everywhere client code runs. Only consider the immutable methods in Node-only code (server routes, scripts) on Node ≥20, where the runtime is known. +**Do NOT use `toSorted()` / `toReversed()` / `with()` / `toSpliced()`.** They are ES2023 *runtime* methods — and a tsconfig `"lib": ["ES2023"]` only makes them **type-check**, it does not make them **run**. Next/SWC compiles syntax but does **not** polyfill prototype methods, and the default browserslist still includes browsers without them (`toSorted` landed in Safari 16 / iOS 16, so any device capped at iOS 15 throws `TypeError: x.toSorted is not a function` and crashes the page). The perf difference vs `[...arr].sort()` is negligible (both allocate one array), so the copy-then-sort form is used everywhere: whether a module reaches the browser is not visible from its path. `check:utils` flags `toSorted`/`toReversed`/`toSpliced` repo-wide and `with` when called with a numeric index (an identifier index is indistinguishable from OpenTelemetry's `context.with`). ## Run independent awaits in parallel diff --git a/CLAUDE.md b/CLAUDE.md index 1b8c8272095..815c747b91c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -10,7 +10,7 @@ This file (also `AGENTS.md`) holds the repo-wide rules. Area detail lives in `.c - **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**: TSDoc for documentation. An inline `//` 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`, …). No `====` separators. - **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. -- **Common utilities**: use the shared helpers from `@sim/utils` instead of inline implementations: +- **Common utilities**: use the shared helpers from `@sim/utils` 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))` - `toError(e)` from `@sim/utils/errors` — normalize caught values to `Error`; never `e instanceof Error ? e : new Error(String(e))` - `getErrorMessage(e, fallback?)` from `@sim/utils/errors` — never `e instanceof Error ? e.message : 'fallback'` @@ -89,7 +89,7 @@ The `'use client'` server boundary, the app/worker runtime env split, and featur - **Imports**: absolute (`@/...`) only, never relative. A folder with 3+ exports gets an `index.ts` barrel; never re-export from a non-barrel file. `import type` for type-only imports. Order and lazy-loading through barrels: `.claude/rules/sim-imports.md`. - **TypeScript**: no `any` and no non-null `!` (use precise types or `unknown` with guards; `check:explicit-any` ratchets both); no export nothing imports (`check:unused-exports`); a props interface for every component; `as const` for constant objects/arrays; explicit ref types (`useRef(null)`). - **Unused bindings** fail lint (biome `noUnusedVariables`, `noUnusedFunctionParameters`): delete the dead variable, import, or parameter and update callers; write `catch {}` when the error is unused. Prefix `_` only for a parameter that must hold its position because a later one is used. `const { a, ...rest } = obj` to omit keys is allowed. The rules carry no autofix, so `bun run lint` will not rename anything for you. -- **Components**: `'use client'` only for hooks or browser APIs. Structure order, extraction thresholds, and list-render rules: `.claude/rules/sim-components.md`. Render-performance idioms (lazy-init refs, hoisting, `Map` pre-indexing, `[...arr].sort()` never `toSorted()` on client paths): `.claude/rules/sim-react-performance.md`. For effect/state/memo/callback anti-patterns use the `/you-might-not-need-*` skills and verify against the running UI. +- **Components**: `'use client'` only for hooks or browser APIs. Structure order, extraction thresholds, and list-render rules: `.claude/rules/sim-components.md`. Render-performance idioms (lazy-init refs, hoisting, `Map` pre-indexing, `[...arr].sort()`, never `toSorted()`): `.claude/rules/sim-react-performance.md`. For effect/state/memo/callback anti-patterns use the `/you-might-not-need-*` skills and verify against the running UI. - **State ownership**: React Query owns server data — never `useState` + `fetch`; shareable client view-state (tabs, filters, search, pagination, selected id) lives in the URL via `nuqs`; Zustand owns global client state; `useState` owns UI-only state. Hooks: `.claude/rules/sim-hooks.md`. Stores (`devtools`, `persist` only with an explicit `partialize` whitelist, workflow value invariants): `.claude/rules/sim-stores.md`. URL state: `.claude/rules/sim-url-state.md`. - **Utils**: inline a helper with one consumer; create `utils.ts` when 2+ files share it — in `lib/` (app-wide) or `feature/utils/` (feature-scoped). Check `lib/` before writing a new one. - **Lists and menus** mirror the order the user already reads elsewhere (toolbar, settings nav), encoded in one exported order constant (resource menus share `RESOURCE_MENU_ORDER`, a product order that does not mirror the sidebar); a separator marks only a change in what the action acts on (typically one, before the destructive action): `.claude/rules/sim-list-ordering.md`. diff --git a/apps/desktop/src/main/local-filesystem.ts b/apps/desktop/src/main/local-filesystem.ts index 46a6c30b9fb..bc3642c3ca7 100644 --- a/apps/desktop/src/main/local-filesystem.ts +++ b/apps/desktop/src/main/local-filesystem.ts @@ -19,7 +19,7 @@ import { } from '@sim/desktop-bridge/local-filesystem-limits' import { generateId } from '@sim/utils/id' import { isRecordLike } from '@sim/utils/object' -import { escapeRegExp } from '@sim/utils/string' +import { escapeRegExp, truncate } from '@sim/utils/string' import { app, dialog, shell } from 'electron' import micromatch from 'micromatch' import safeRegex from 'safe-regex2' @@ -1236,8 +1236,7 @@ export class LocalFilesystemService { matches.push({ uri: resultUri, line: request.lineNumbers === false ? 0 : contextIndex + 1, - text: - line.length > MAX_GREP_LINE_LENGTH ? `${line.slice(0, MAX_GREP_LINE_LENGTH)}…` : line, + text: truncate(line, MAX_GREP_LINE_LENGTH, '…'), }) if (matches.length >= maxResults) { truncated = true diff --git a/apps/docs/app/api/search/route.ts b/apps/docs/app/api/search/route.ts index 4bea9ead620..30915b87529 100644 --- a/apps/docs/app/api/search/route.ts +++ b/apps/docs/app/api/search/route.ts @@ -1,8 +1,11 @@ +import { createLogger } from '@sim/logger' import { sql } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { db, docsEmbeddings } from '@/lib/db' import { generateSearchEmbedding } from '@/lib/embeddings' +const logger = createLogger('DocsSearchAPI') + export const runtime = 'nodejs' export const revalidate = 0 @@ -196,7 +199,7 @@ export async function GET(request: NextRequest) { return NextResponse.json(searchResults) } catch (error) { - console.error('Semantic search error:', error) + logger.error('Semantic search error:', error) return NextResponse.json([]) } diff --git a/apps/docs/app/llms-full.txt/route.ts b/apps/docs/app/llms-full.txt/route.ts index 57bcd9045c5..244c7a62dc8 100644 --- a/apps/docs/app/llms-full.txt/route.ts +++ b/apps/docs/app/llms-full.txt/route.ts @@ -1,6 +1,9 @@ +import { createLogger } from '@sim/logger' import { getLLMText } from '@/lib/llms' import { source } from '@/lib/source' +const logger = createLogger('DocsLlmsFullText') + export const revalidate = false export async function GET() { @@ -18,7 +21,7 @@ export async function GET() { }, }) } catch (error) { - console.error('Error generating LLM full text:', error) + logger.error('Error generating LLM full text:', error) return new Response('Error generating full documentation text', { status: 500 }) } } diff --git a/apps/docs/app/llms.txt/route.ts b/apps/docs/app/llms.txt/route.ts index 0706a38595a..f43aeba1876 100644 --- a/apps/docs/app/llms.txt/route.ts +++ b/apps/docs/app/llms.txt/route.ts @@ -1,6 +1,9 @@ +import { createLogger } from '@sim/logger' import { source } from '@/lib/source' import { DOCS_BASE_URL } from '@/lib/urls' +const logger = createLogger('DocsLlmsManifest') + export const revalidate = false export async function GET() { @@ -70,7 +73,7 @@ See: https://llmstxt.org for specification` }, }) } catch (error) { - console.error('Error generating LLM manifest:', error) + logger.error('Error generating LLM manifest:', error) return new Response('Error generating documentation manifest', { status: 500 }) } } diff --git a/apps/docs/components/ui/video-placeholder.tsx b/apps/docs/components/ui/video-placeholder.tsx index e836b418b52..3490e13ca0f 100644 --- a/apps/docs/components/ui/video-placeholder.tsx +++ b/apps/docs/components/ui/video-placeholder.tsx @@ -113,7 +113,7 @@ export function VideoPlaceholder({ pendingSeek.current = null } }} - className='h-full w-full border-0' + className='size-full border-0' > diff --git a/apps/docs/components/workflow-preview/docs-container-node.tsx b/apps/docs/components/workflow-preview/docs-container-node.tsx index 20fbb118870..a81da4a995f 100644 --- a/apps/docs/components/workflow-preview/docs-container-node.tsx +++ b/apps/docs/components/workflow-preview/docs-container-node.tsx @@ -39,7 +39,7 @@ export const DocsContainerNode = memo(function DocsContainerNode({ } return ( -
+
@@ -329,7 +329,7 @@ export function WorkflowPreview({ className='fixed inset-0 m-auto h-[86dvh] max-h-none w-[92vw] max-w-none overflow-hidden rounded-xl border border-[var(--border)] bg-[var(--bg)] p-0 text-[var(--text-body)] backdrop:bg-black/70 backdrop:backdrop-blur-sm' > {expanded && ( -
+
{workflow.name} diff --git a/apps/docs/package.json b/apps/docs/package.json index ac0e88dcf59..fd4a4d1816a 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -20,6 +20,7 @@ "dependencies": { "@sim/db": "workspace:*", "@sim/emcn": "workspace:*", + "@sim/logger": "workspace:*", "@sim/utils": "workspace:*", "@sim/workflow-renderer": "workspace:*", "@xyflow/react": "12.11.3", diff --git a/apps/sim/app/(interfaces)/chat/components/input/input.tsx b/apps/sim/app/(interfaces)/chat/components/input/input.tsx index b2c6c8b48f1..9bd9c9b8b60 100644 --- a/apps/sim/app/(interfaces)/chat/components/input/input.tsx +++ b/apps/sim/app/(interfaces)/chat/components/input/input.tsx @@ -177,10 +177,10 @@ export const ChatInput: React.FC<{ {file.name} ) : ( -
+
{file.name.split('.').pop()} diff --git a/apps/sim/app/(landing)/components/hero/components/hero-chat-loop/hero-chat-loop.tsx b/apps/sim/app/(landing)/components/hero/components/hero-chat-loop/hero-chat-loop.tsx index c02f862bce5..8c828e34d86 100644 --- a/apps/sim/app/(landing)/components/hero/components/hero-chat-loop/hero-chat-loop.tsx +++ b/apps/sim/app/(landing)/components/hero/components/hero-chat-loop/hero-chat-loop.tsx @@ -179,7 +179,7 @@ export function HeroChatLoop({ return (
diff --git a/apps/sim/app/(landing)/components/hero/components/hero-platform-loop/hero-platform-loop.tsx b/apps/sim/app/(landing)/components/hero/components/hero-platform-loop/hero-platform-loop.tsx index 1fa4b426954..7f7e27cc6f8 100644 --- a/apps/sim/app/(landing)/components/hero/components/hero-platform-loop/hero-platform-loop.tsx +++ b/apps/sim/app/(landing)/components/hero/components/hero-platform-loop/hero-platform-loop.tsx @@ -266,7 +266,7 @@ export function HeroPlatformLoop() { >
diff --git a/apps/sim/app/(landing)/components/hero/components/hero-platform-loop/stage-block-card.tsx b/apps/sim/app/(landing)/components/hero/components/hero-platform-loop/stage-block-card.tsx index a679aef3881..7988b9fa5db 100644 --- a/apps/sim/app/(landing)/components/hero/components/hero-platform-loop/stage-block-card.tsx +++ b/apps/sim/app/(landing)/components/hero/components/hero-platform-loop/stage-block-card.tsx @@ -350,7 +350,7 @@ export function StageBlockCard({ if (onSelect) handleKeyboardActivation(event, () => onSelect(block.id)) }} className={cn( - 'relative h-full w-full rounded-2xl', + 'relative size-full rounded-2xl', onSelect && 'cursor-pointer focus-visible:outline-none' )} > diff --git a/apps/sim/app/(landing)/components/navbar/components/nav-menu-chip/components/nav-menu-preview/components/workflow-menu-preview/workflow-menu-preview.tsx b/apps/sim/app/(landing)/components/navbar/components/nav-menu-chip/components/nav-menu-preview/components/workflow-menu-preview/workflow-menu-preview.tsx index 699e664ff05..52c86909022 100644 --- a/apps/sim/app/(landing)/components/navbar/components/nav-menu-chip/components/nav-menu-preview/components/workflow-menu-preview/workflow-menu-preview.tsx +++ b/apps/sim/app/(landing)/components/navbar/components/nav-menu-chip/components/nav-menu-preview/components/workflow-menu-preview/workflow-menu-preview.tsx @@ -31,12 +31,13 @@ interface WorkflowMenuPreviewProps { /** A cropped production canvas with a clear center and progressively softened inner edges. */ export function WorkflowMenuPreview({ layout = 'menu', onReady }: WorkflowMenuPreviewProps) { - const readyBlocksRef = useRef(new Set()) + const readyBlocksRef = useRef | null>(null) + const readyBlocks = (readyBlocksRef.current ??= new Set()) const handleBlockReady = (blockId: string) => { - if (readyBlocksRef.current.has(blockId)) return - readyBlocksRef.current.add(blockId) - if (readyBlocksRef.current.size === BLOCKS.length) onReady?.() + if (readyBlocks.has(blockId)) return + readyBlocks.add(blockId) + if (readyBlocks.size === BLOCKS.length) onReady?.() } return ( diff --git a/apps/sim/app/(landing)/components/platform-hero-visual/platform-hero-visual.tsx b/apps/sim/app/(landing)/components/platform-hero-visual/platform-hero-visual.tsx index 46048f236f6..ab40396ead9 100644 --- a/apps/sim/app/(landing)/components/platform-hero-visual/platform-hero-visual.tsx +++ b/apps/sim/app/(landing)/components/platform-hero-visual/platform-hero-visual.tsx @@ -20,7 +20,7 @@ interface PlatformHeroVisualProps { */ export function PlatformHeroVisual({ children }: PlatformHeroVisualProps) { return ( -
+
@@ -112,7 +112,7 @@ export function BuildAgentsLoop() { stageOpen ? 'w-1/2 border-l' : 'w-0 border-l-0' )} > -
+
-
+
diff --git a/apps/sim/app/(landing)/components/shared/editor-loop/editor-loop.tsx b/apps/sim/app/(landing)/components/shared/editor-loop/editor-loop.tsx index 42aa3408516..332bf4653fb 100644 --- a/apps/sim/app/(landing)/components/shared/editor-loop/editor-loop.tsx +++ b/apps/sim/app/(landing)/components/shared/editor-loop/editor-loop.tsx @@ -98,10 +98,10 @@ export function EditorLoop({ content }: EditorLoopProps) { workflows={content.sidebarWorkflows} activeItem={content.activeItem} > -
+
diff --git a/apps/sim/app/(landing)/components/solutions-page/components/solutions-card-row/components/solutions-card/solutions-card.tsx b/apps/sim/app/(landing)/components/solutions-page/components/solutions-card-row/components/solutions-card/solutions-card.tsx index a74fe87d45a..32e3eb88e6a 100644 --- a/apps/sim/app/(landing)/components/solutions-page/components/solutions-card-row/components/solutions-card/solutions-card.tsx +++ b/apps/sim/app/(landing)/components/solutions-page/components/solutions-card-row/components/solutions-card/solutions-card.tsx @@ -107,7 +107,7 @@ export function SolutionsCard({ card, headingId, tabletSpan = false }: Solutions wide && 'sm:max-lg:mt-0 sm:max-lg:w-auto sm:max-lg:min-w-0' )} > -
{card.visual}
+
{card.visual}
diff --git a/apps/sim/app/(landing)/components/solutions-page/components/solutions-visual-frame/solutions-visual-frame.tsx b/apps/sim/app/(landing)/components/solutions-page/components/solutions-visual-frame/solutions-visual-frame.tsx index d5d9fce410f..0b7b6ecdb06 100644 --- a/apps/sim/app/(landing)/components/solutions-page/components/solutions-visual-frame/solutions-visual-frame.tsx +++ b/apps/sim/app/(landing)/components/solutions-page/components/solutions-visual-frame/solutions-visual-frame.tsx @@ -8,7 +8,7 @@ import { SOLUTIONS_VISUAL } from '@/app/(landing)/components/solutions-page/cons * hero-visual family: `--surface-2` fill, `--border-1` hairline, `rounded-lg`, * `overflow-hidden`) and, crucially, its dimensions: it reserves a full-width * 16:9 aspect ratio for the solutions hero visual. Because the size is reserved - * before paint and the node fills `h-full w-full` inside, a dropped-in node can + * before paint and the node fills `size-full` inside, a dropped-in node can * neither shift surrounding layout (CLS = 0) nor change the frame's own padding. * * The frame is decorative chrome around product visuals, so it is `aria-hidden`; @@ -29,7 +29,7 @@ export function SolutionsVisualFrame({ children }: SolutionsVisualFrameProps) { SOLUTIONS_VISUAL.heroAspect )} > -
{children}
+
{children}
) } diff --git a/apps/sim/app/(landing)/components/solutions-page/constants.ts b/apps/sim/app/(landing)/components/solutions-page/constants.ts index d99293824fb..6675801aeb0 100644 --- a/apps/sim/app/(landing)/components/solutions-page/constants.ts +++ b/apps/sim/app/(landing)/components/solutions-page/constants.ts @@ -74,7 +74,7 @@ export const SOLUTIONS_TEXT_MEASURE = { * Reserved fixed dimensions for the component-owned visual frames. A dropped-in * `ReactNode` renders into a frame of exactly these dimensions, so it can never * shift surrounding layout (CLS = 0) nor change its own frame padding. The node - * fills `h-full w-full` inside; it owns nothing about the frame. + * fills `size-full` inside; it owns nothing about the frame. */ export const SOLUTIONS_VISUAL = { /** Full-width hero visual aspect ratio - reserves height before paint. */ @@ -105,7 +105,7 @@ export const SOLUTIONS_VISUAL = { featureTileContainer: '[container-type:inline-size]', featureTileScale: '[--tile-scale:min(1,tan(atan2(100cqw,352px)))]', featureTileCanvas: - 'h-[calc(100%/var(--tile-scale,1))] w-[calc(100%/var(--tile-scale,1))] origin-top-left [scale:var(--tile-scale,1)]', + 'size-[calc(100%/var(--tile-scale,1))] origin-top-left [scale:var(--tile-scale,1)]', /** * Feature-tile minimum height - the `440px` design-space height, multiplied * by the tile's current scale so height tracks the zoomed content. The diff --git a/apps/sim/app/(landing)/contact/components/contact-form/contact-form.tsx b/apps/sim/app/(landing)/contact/components/contact-form/contact-form.tsx index 534fa557cdc..bf89ed90878 100644 --- a/apps/sim/app/(landing)/contact/components/contact-form/contact-form.tsx +++ b/apps/sim/app/(landing)/contact/components/contact-form/contact-form.tsx @@ -231,7 +231,7 @@ export function ContactForm() { >