Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
9dc14f0
chore(lint): ban console in runtime code with Biome noConsole
waleedlatif1 Oct 2, 2026
1dfa35d
refactor(utils): replace inline toError, isRecordLike, omit, and trun…
waleedlatif1 Oct 2, 2026
9dc6b41
refactor(ui): lazy-init object refs and use size-* for equal height a…
waleedlatif1 Oct 2, 2026
3ae2fa1
improvement(audits): extend check:utils to the remaining written idioms
waleedlatif1 Oct 2, 2026
951f895
improvement(audits): require an explicit partialize on every zustand …
waleedlatif1 Oct 2, 2026
055410f
improvement(audits): flag deployment-shape env-flags imports in clien…
waleedlatif1 Oct 2, 2026
a372a61
docs(agents): name the check that enforces the common-utilities list
waleedlatif1 Oct 2, 2026
8165d0d
docs(agents): scope the check:utils note to the forms it bans
waleedlatif1 Oct 2, 2026
c9c629a
perf(audits): gate backreference patterns in check:utils behind liter…
waleedlatif1 Oct 2, 2026
4ebbf7a
refactor(ui): lazy-init useRef containers the nested-generic pattern …
waleedlatif1 Oct 2, 2026
fb900db
improvement(audits): close detector gaps in check:utils and the deplo…
waleedlatif1 Oct 2, 2026
52f8b4d
improvement(audits): share the directive classifier and simplify chec…
waleedlatif1 Oct 2, 2026
ca7d92a
refactor: drop isRecordLike pass-through wrappers and return audioLev…
waleedlatif1 Oct 2, 2026
f6c27c8
improvement(audits): ban ES2023 array methods repo-wide, catch whole-…
waleedlatif1 Oct 2, 2026
db24cc9
improvement(audits): follow aliased persist imports, require strict !…
waleedlatif1 Oct 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .claude/rules/sim-components.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,15 @@ 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<string, T>(); 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

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 `<input>`). 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.
Expand Down
2 changes: 1 addition & 1 deletion .claude/rules/sim-react-performance.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 2 additions & 2 deletions .cursor/rules/sim-components.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,15 @@ 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<string, T>(); 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

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 `<input>`). 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.
Expand Down
2 changes: 1 addition & 1 deletion .cursor/rules/sim-react-performance.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `// <tag>: <reason>` 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'`
Expand Down Expand Up @@ -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<HTMLDivElement>(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`.
Expand Down
5 changes: 2 additions & 3 deletions apps/desktop/src/main/local-filesystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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
Expand Down
5 changes: 4 additions & 1 deletion apps/docs/app/api/search/route.ts
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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([])
}
Expand Down
5 changes: 4 additions & 1 deletion apps/docs/app/llms-full.txt/route.ts
Original file line number Diff line number Diff line change
@@ -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() {
Expand All @@ -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 })
}
}
5 changes: 4 additions & 1 deletion apps/docs/app/llms.txt/route.ts
Original file line number Diff line number Diff line change
@@ -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() {
Expand Down Expand Up @@ -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 })
}
}
2 changes: 1 addition & 1 deletion apps/docs/components/ui/video-placeholder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ export function VideoPlaceholder({
pendingSeek.current = null
}
}}
className='h-full w-full border-0'
className='size-full border-0'
>
<track
kind='captions'
Expand Down
2 changes: 1 addition & 1 deletion apps/docs/components/workflow-preview/block-preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export function BlockPreview({ type }: BlockPreviewProps) {
panOnDrag={false}
panOnScroll={false}
preventScrolling={false}
className='h-full w-full [--text-muted:var(--text-secondary)]! [--xy-background-color:var(--bg)]'
className='size-full [--text-muted:var(--text-secondary)]! [--xy-background-color:var(--bg)]'
/>
</ReactFlowProvider>
</LazyMotion>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export const DocsContainerNode = memo(function DocsContainerNode({
}

return (
<div className={cn('h-full w-full', data.isDimmed && DIMMED_PREVIEW_CLASS)}>
<div className={cn('size-full', data.isDimmed && DIMMED_PREVIEW_CLASS)}>
<SubflowNodeView
id={id}
data={subflowData}
Expand Down
4 changes: 2 additions & 2 deletions apps/docs/components/workflow-preview/workflow-preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ function PreviewFlow({
autoPanOnNodeDrag={false}
proOptions={PRO_OPTIONS}
minZoom={0.1}
className='h-full w-full [--text-muted:var(--text-secondary)]! [--xy-background-color:var(--bg)]'
className='size-full [--text-muted:var(--text-secondary)]! [--xy-background-color:var(--bg)]'
>
<FitViewAfterInit options={interactive ? LIGHTBOX_FIT_VIEW_OPTIONS : FIT_VIEW_OPTIONS} />
</ReactFlow>
Expand Down Expand Up @@ -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 && (
<div className='relative flex h-full w-full flex-col sm:flex-row'>
<div className='relative flex size-full flex-col sm:flex-row'>
<div className='relative min-h-0 min-w-0 flex-1'>
<div className='pointer-events-none absolute top-0 right-0 left-0 z-10 flex items-center justify-between px-4 py-3'>
<span className='text-[var(--text-muted)] text-small'>{workflow.name}</span>
Expand Down
1 change: 1 addition & 0 deletions apps/docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
4 changes: 2 additions & 2 deletions apps/sim/app/(interfaces)/chat/components/input/input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -177,10 +177,10 @@ export const ChatInput: React.FC<{
<img
src={file.dataUrl}
alt={file.name}
className='h-full w-full object-cover'
className='size-full object-cover'
/>
) : (
<div className='flex h-full w-full flex-col items-center justify-center gap-0.5 text-[var(--text-muted)]'>
<div className='flex size-full flex-col items-center justify-center gap-0.5 text-[var(--text-muted)]'>
<Paperclip className='size-[18px]' />
<span className='max-w-[48px] truncate px-[2px] text-[9px]'>
{file.name.split('.').pop()}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ export function HeroChatLoop({

return (
<div
className='relative flex h-full w-full flex-col bg-[var(--bg)]'
className='relative flex size-full flex-col bg-[var(--bg)]'
data-chat-view={welcome ? 'welcome' : 'conversation'}
data-chat-phase={phase}
>
Expand Down
Loading
Loading