Skip to content

Commit e70844f

Browse files
Merge remote-tracking branch 'origin/staging' into fix/infinite-scroll-sidebar
2 parents e396954 + c246f5c commit e70844f

File tree

892 files changed

+54993
-9464
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

892 files changed

+54993
-9464
lines changed

.claude/commands/you-might-not-need-a-callback.md

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,34 @@ User arguments: $ARGUMENTS
1616
Read before analyzing:
1717
1. https://react.dev/reference/react/useCallback — official docs on when useCallback is actually needed
1818

19+
## The one rule that matters
20+
21+
`useCallback` is only useful when **something observes the reference**. Ask: does anything care if this function gets a new identity on re-render?
22+
23+
Observers that care about reference stability:
24+
- A `useEffect` that lists the function in its deps array
25+
- A `useMemo` that lists the function in its deps array
26+
- Another `useCallback` that lists the function in its deps array
27+
- A child component wrapped in `React.memo` that receives the function as a prop
28+
29+
If none of those apply — if the function is only called inline, or passed to a non-memoized child, or assigned to a native element event — the reference is unobserved and `useCallback` adds overhead with zero benefit.
30+
1931
## Anti-patterns to detect
2032

21-
1. **useCallback on functions not passed as props or deps**: No benefit if only called within the same component.
22-
2. **useCallback with deps that change every render**: Memoization is wasted.
23-
3. **useCallback on handlers passed to native elements**: `<button onClick={fn}>` doesn't benefit from stable references.
24-
4. **useCallback wrapping functions that return new objects/arrays**: Memoization at the wrong level.
25-
5. **useCallback with empty deps when deps are needed**: Stale closures.
26-
6. **Pairing useCallback + React.memo unnecessarily**: Only optimize when you've measured a problem.
27-
7. **useCallback in hooks that don't need stable references**: Not every hook return needs memoization.
33+
1. **No observer tracks the reference**: The function is only called inline in the same component, or passed to a non-memoized child, or used as a native element handler (`<button onClick={fn}>`). Nothing re-runs or bails out based on reference identity. Remove `useCallback`.
34+
2. **useCallback with deps that change every render**: If a dep is a plain object/array created inline, or state that changes on every interaction, memoization buys nothing — the function gets a new identity anyway.
35+
3. **useCallback on handlers passed only to native elements**: `<button onClick={fn}>` — React never does reference equality on native element props. No benefit.
36+
4. **useCallback wrapping functions that return new objects/arrays**: Stable function identity, unstable return value — memoization is at the wrong level. Use `useMemo` on the return value instead, or restructure.
37+
5. **useCallback with empty deps when deps are needed**: Stale closure — reads initial values forever. This is a correctness bug, not just a performance issue.
38+
6. **Pairing useCallback + React.memo on trivially cheap renders**: If the child renders in < 1ms and re-renders rarely, the memo infrastructure costs more than it saves.
39+
40+
## Patterns that ARE correct — do not flag
2841

29-
Note: This codebase uses a ref pattern for stable callbacks (`useRef` + empty deps). That pattern is correct — don't flag it.
42+
- `useCallback` whose result is in a `useEffect` dep array — prevents the effect from re-running on every render
43+
- `useCallback` whose result is in a `useMemo` dep array — prevents the memo from recomputing on every render
44+
- `useCallback` whose result is a dep of another `useCallback` — stabilises a callback chain
45+
- `useCallback` passed to a `React.memo`-wrapped child — the whole point of the pattern
46+
- This codebase's ref pattern: `useRef` + callback with empty deps that reads the ref inside — correct, do not flag
3047

3148
## Steps
3249

.claude/rules/global.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,25 @@ const shortId = generateShortId()
3030
const tiny = generateShortId(8)
3131
```
3232

33+
## Common Utilities
34+
Use shared helpers from `@/lib/core/utils/helpers` instead of writing inline implementations:
35+
36+
- `sleep(ms)` — async delay. Never write `new Promise(resolve => setTimeout(resolve, ms))`
37+
- `toError(value)` — normalize unknown caught values to `Error`. Never write `e instanceof Error ? e : new Error(String(e))`
38+
- `toError(value).message` — get error message safely. Never write `e instanceof Error ? e.message : String(e)`
39+
40+
```typescript
41+
// ✗ Bad
42+
await new Promise(resolve => setTimeout(resolve, 1000))
43+
const msg = error instanceof Error ? error.message : String(error)
44+
const err = error instanceof Error ? error : new Error(String(error))
45+
46+
// ✓ Good
47+
import { sleep, toError } from '@/lib/core/utils/helpers'
48+
await sleep(1000)
49+
const msg = toError(error).message
50+
const err = toError(error)
51+
```
52+
3353
## Package Manager
3454
Use `bun` and `bunx`, not `npm` and `npx`.

.cursor/rules/global.mdc

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,5 +37,25 @@ const shortId = generateShortId()
3737
const tiny = generateShortId(8)
3838
```
3939

40+
## Common Utilities
41+
Use shared helpers from `@/lib/core/utils/helpers` instead of writing inline implementations:
42+
43+
- `sleep(ms)` — async delay. Never write `new Promise(resolve => setTimeout(resolve, ms))`
44+
- `toError(value)` — normalize unknown caught values to `Error`. Never write `e instanceof Error ? e : new Error(String(e))`
45+
- `toError(value).message` — get error message safely. Never write `e instanceof Error ? e.message : String(e)`
46+
47+
```typescript
48+
// ✗ Bad
49+
await new Promise(resolve => setTimeout(resolve, 1000))
50+
const msg = error instanceof Error ? error.message : String(error)
51+
const err = error instanceof Error ? error : new Error(String(error))
52+
53+
// ✓ Good
54+
import { sleep, toError } from '@/lib/core/utils/helpers'
55+
await sleep(1000)
56+
const msg = toError(error).message
57+
const err = toError(error)
58+
```
59+
4060
## Package Manager
4161
Use `bun` and `bunx`, not `npm` and `npx`.

.cursor/rules/sim-sandbox.mdc

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
---
2+
description: Isolated-vm sandbox worker security policy. Hard rules for anything that lives in the worker child process that runs user code.
3+
globs: ["apps/sim/lib/execution/isolated-vm-worker.cjs", "apps/sim/lib/execution/isolated-vm.ts", "apps/sim/lib/execution/sandbox/**", "apps/sim/sandbox-tasks/**"]
4+
---
5+
6+
# Sim Sandbox — Worker Security Policy
7+
8+
The isolated-vm worker child process at
9+
`apps/sim/lib/execution/isolated-vm-worker.cjs` runs untrusted user code inside
10+
V8 isolates. The process itself is a trust boundary. Everything in this rule is
11+
about what must **never** live in that process.
12+
13+
## Hard rules
14+
15+
1. **No app credentials in the worker process**. The worker must not hold, load,
16+
or receive via IPC: database URLs, Redis URLs, AWS keys, Stripe keys,
17+
session-signing keys, encryption keys, OAuth client secrets, internal API
18+
secrets, or any LLM / email / search provider API keys. If you catch yourself
19+
`require`'ing `@/lib/auth`, `@sim/db`, `@/lib/uploads/core/storage-service`,
20+
or anything that imports `env` directly inside the worker, stop and use a
21+
host-side broker instead.
22+
23+
2. **Host-side brokers own all credentialed work**. The worker can only access
24+
resources through `ivm.Reference` / `ivm.Callback` bridges back to the host
25+
process. Today the only broker is `workspaceFileBroker`
26+
(`apps/sim/lib/execution/sandbox/brokers/workspace-file.ts`); adding a new
27+
one requires co-reviewing this file.
28+
29+
3. **Host-side brokers must scope every resource access to a single tenant**.
30+
The `SandboxBrokerContext` always carries `workspaceId`. Any new broker that
31+
accesses storage, DB, or an external API must use `ctx.workspaceId` to scope
32+
the lookup — never accept a raw path, key, or URL from isolate code without
33+
validation.
34+
35+
4. **Nothing that runs in the isolate is trusted, even if we wrote it**. The
36+
task `bootstrap` and `finalize` strings in `apps/sim/sandbox-tasks/` execute
37+
inside the isolate. They must treat `globalThis` as adversarial — no pulling
38+
values from it that might have been mutated by user code. The hardening
39+
script in `executeTask` undefines dangerous globals before user code runs.
40+
41+
## Why
42+
43+
A V8 JIT bug (Chrome ships these roughly monthly) gives an attacker a native
44+
code primitive inside the process that owns whatever that process can reach.
45+
If the worker only holds `isolated-vm` + a single narrow workspace-file broker,
46+
a V8 escape leaks one tenant's files. If the worker holds a Stripe key or a DB
47+
connection, a V8 escape leaks the service.
48+
49+
The original `doc-worker.cjs` vulnerability (CVE-class, 225 production secrets
50+
leaked via `/proc/1/environ`) was the forcing function for this architecture.
51+
Keep the blast radius small.
52+
53+
## Checklist for changes to `isolated-vm-worker.cjs`
54+
55+
Before landing any change that adds a new `require(...)` or `process.send(...)`
56+
payload or `ivm.Reference` wrapper in the worker:
57+
58+
- [ ] Does it load a credential, key, connection string, or secret? If yes,
59+
move it host-side and expose as a broker.
60+
- [ ] Does it import from `@/lib/auth`, `@sim/db`, `@/lib/uploads/core/*`,
61+
`@/lib/core/config/env`, or any module that reads `process.env` of the
62+
main app? If yes, same — move host-side.
63+
- [ ] Does it expose a resource that's workspace-scoped without taking a
64+
`workspaceId`? If yes, re-scope.
65+
- [ ] Did you update the broker limits (`IVM_MAX_BROKER_ARGS_JSON_CHARS`,
66+
`IVM_MAX_BROKER_RESULT_JSON_CHARS`, `IVM_MAX_BROKERS_PER_EXECUTION`) if
67+
the new broker can emit large payloads or fire frequently?
68+
69+
## What the worker *may* hold
70+
71+
- `isolated-vm` module
72+
- Node built-ins: `node:fs` (only for reading the checked-in bundle `.cjs`
73+
files) and `node:path`
74+
- The three prebuilt library bundles under
75+
`apps/sim/lib/execution/sandbox/bundles/*.cjs`
76+
- IPC message handlers for `execute`, `cancel`, `fetchResponse`,
77+
`brokerResponse`
78+
79+
The worker deliberately has **no host-side logger**. All errors and
80+
diagnostics flow through IPC back to the host, which has `@sim/logger`. Do
81+
not add `createLogger` or console-based logging to the worker — it would
82+
require pulling the main app's config / env, which is exactly what this
83+
rule is preventing.
84+
85+
Anything else is suspect.

CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ You are a professional software engineer. All code must follow best practices: a
88
- **Comments**: Use TSDoc for documentation. No `====` separators. No non-TSDoc comments
99
- **Styling**: Never update global styles. Keep all styling local to components
1010
- **ID Generation**: Never use `crypto.randomUUID()`, `nanoid`, or `uuid` package. Use `generateId()` (UUID v4) or `generateShortId()` (compact) from `@/lib/core/utils/uuid`
11+
- **Common Utilities**: Use shared helpers from `@/lib/core/utils/helpers` instead of inline implementations. `sleep(ms)` for delays, `toError(e)` to normalize caught values.
1112
- **Package Manager**: Use `bun` and `bunx`, not `npm` and `npx`
1213

1314
## Architecture

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,13 +142,15 @@ See the [environment variables reference](https://docs.sim.ai/self-hosting/envir
142142
- **Database**: PostgreSQL with [Drizzle ORM](https://orm.drizzle.team)
143143
- **Authentication**: [Better Auth](https://better-auth.com)
144144
- **UI**: [Shadcn](https://ui.shadcn.com/), [Tailwind CSS](https://tailwindcss.com)
145-
- **State Management**: [Zustand](https://zustand-demo.pmnd.rs/)
145+
- **Streaming Markdown**: [Streamdown](https://github.com/vercel/streamdown)
146+
- **State Management**: [Zustand](https://zustand-demo.pmnd.rs/), [TanStack Query](https://tanstack.com/query)
146147
- **Flow Editor**: [ReactFlow](https://reactflow.dev/)
147148
- **Docs**: [Fumadocs](https://fumadocs.vercel.app/)
148149
- **Monorepo**: [Turborepo](https://turborepo.org/)
149150
- **Realtime**: [Socket.io](https://socket.io/)
150151
- **Background Jobs**: [Trigger.dev](https://trigger.dev/)
151152
- **Remote Code Execution**: [E2B](https://www.e2b.dev/)
153+
- **Isolated Code Execution**: [isolated-vm](https://github.com/laverdet/isolated-vm)
152154

153155
## Contributing
154156

apps/docs/app/[lang]/[[...slug]]/page.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@ import { ResponseSection } from '@/components/ui/response-section'
1717
import { i18n } from '@/lib/i18n'
1818
import { getApiSpecContent, openapi } from '@/lib/openapi'
1919
import { type PageData, source } from '@/lib/source'
20+
import { DOCS_BASE_URL } from '@/lib/urls'
2021

2122
const SUPPORTED_LANGUAGES: Set<string> = new Set(i18n.languages)
22-
const BASE_URL = 'https://docs.sim.ai'
23+
const BASE_URL = DOCS_BASE_URL
2324

2425
const OG_LOCALE_MAP: Record<string, string> = {
2526
en: 'en_US',

apps/docs/app/[lang]/layout.tsx

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { defineI18nUI } from 'fumadocs-ui/i18n'
33
import { DocsLayout } from 'fumadocs-ui/layouts/docs'
44
import { RootProvider } from 'fumadocs-ui/provider/next'
55
import { Geist_Mono, Inter } from 'next/font/google'
6-
import Script from 'next/script'
76
import {
87
SidebarFolder,
98
SidebarItem,
@@ -13,6 +12,7 @@ import { Navbar } from '@/components/navbar/navbar'
1312
import { SimLogoFull } from '@/components/ui/sim-logo'
1413
import { i18n } from '@/lib/i18n'
1514
import { source } from '@/lib/source'
15+
import { DOCS_BASE_URL } from '@/lib/urls'
1616
import '../global.css'
1717

1818
const inter = Inter({
@@ -67,22 +67,22 @@ export default async function Layout({ children, params }: LayoutProps) {
6767
name: 'Sim Documentation',
6868
description:
6969
'Documentation for Sim — the open-source AI workspace where teams build, deploy, and manage AI agents. Connect 1,000+ integrations and every major LLM.',
70-
url: 'https://docs.sim.ai',
70+
url: DOCS_BASE_URL,
7171
publisher: {
7272
'@type': 'Organization',
7373
name: 'Sim',
7474
url: 'https://sim.ai',
7575
logo: {
7676
'@type': 'ImageObject',
77-
url: 'https://docs.sim.ai/static/logo.png',
77+
url: `${DOCS_BASE_URL}/static/logo.png`,
7878
},
7979
},
8080
inLanguage: lang,
8181
potentialAction: {
8282
'@type': 'SearchAction',
8383
target: {
8484
'@type': 'EntryPoint',
85-
urlTemplate: 'https://docs.sim.ai/api/search?q={search_term_string}',
85+
urlTemplate: `${DOCS_BASE_URL}/api/search?q={search_term_string}`,
8686
},
8787
'query-input': 'required name=search_term_string',
8888
},
@@ -101,7 +101,6 @@ export default async function Layout({ children, params }: LayoutProps) {
101101
/>
102102
</head>
103103
<body className='flex min-h-screen flex-col font-sans'>
104-
<Script src='https://assets.onedollarstats.com/stonks.js' strategy='lazyOnload' />
105104
<RootProvider i18n={provider(lang)}>
106105
<Navbar />
107106
<DocsLayout

apps/docs/app/[lang]/not-found.tsx

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { DocsBody, DocsPage } from 'fumadocs-ui/page'
1+
import { DocsPage } from 'fumadocs-ui/page'
2+
import Link from 'next/link'
23

34
export const metadata = {
45
title: 'Page Not Found',
@@ -7,17 +8,21 @@ export const metadata = {
78
export default function NotFound() {
89
return (
910
<DocsPage>
10-
<DocsBody>
11-
<div className='flex min-h-[60vh] flex-col items-center justify-center text-center'>
12-
<h1 className='mb-4 bg-gradient-to-b from-[#47d991] to-[#33c482] bg-clip-text font-bold text-8xl text-transparent'>
13-
404
14-
</h1>
15-
<h2 className='mb-2 font-semibold text-2xl text-foreground'>Page Not Found</h2>
16-
<p className='text-muted-foreground'>
17-
The page you're looking for doesn't exist or has been moved.
18-
</p>
19-
</div>
20-
</DocsBody>
11+
<div className='flex min-h-[70vh] flex-col items-center justify-center gap-4 text-center'>
12+
<h1 className='bg-gradient-to-b from-[#47d991] to-[#33c482] bg-clip-text font-bold text-8xl text-transparent'>
13+
404
14+
</h1>
15+
<h2 className='font-semibold text-2xl text-foreground'>Page Not Found</h2>
16+
<p className='text-muted-foreground'>
17+
The page you're looking for doesn't exist or has been moved.
18+
</p>
19+
<Link
20+
href='/'
21+
className='ml-1 flex items-center rounded-[8px] bg-[#33c482] px-2.5 py-1.5 text-[13px] text-white transition-colors duration-200 hover:bg-[#2DAC72]'
22+
>
23+
Go home
24+
</Link>
25+
</div>
2126
</DocsPage>
2227
)
2328
}

apps/docs/app/layout.tsx

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { ReactNode } from 'react'
22
import type { Viewport } from 'next'
3+
import { DOCS_BASE_URL } from '@/lib/urls'
34

45
export default function RootLayout({ children }: { children: ReactNode }) {
56
return children
@@ -12,7 +13,7 @@ export const viewport: Viewport = {
1213
}
1314

1415
export const metadata = {
15-
metadataBase: new URL('https://docs.sim.ai'),
16+
metadataBase: new URL(DOCS_BASE_URL),
1617
title: {
1718
default: 'Sim Documentation — The AI Workspace for Teams',
1819
template: '%s | Sim Docs',
@@ -61,14 +62,14 @@ export const metadata = {
6162
type: 'website',
6263
locale: 'en_US',
6364
alternateLocale: ['es_ES', 'fr_FR', 'de_DE', 'ja_JP', 'zh_CN'],
64-
url: 'https://docs.sim.ai',
65+
url: DOCS_BASE_URL,
6566
siteName: 'Sim Documentation',
6667
title: 'Sim Documentation — The AI Workspace for Teams',
6768
description:
6869
'Documentation for Sim — the open-source AI workspace where teams build, deploy, and manage AI agents. Connect 1,000+ integrations and every major LLM.',
6970
images: [
7071
{
71-
url: 'https://docs.sim.ai/api/og?title=Sim%20Documentation',
72+
url: `${DOCS_BASE_URL}/api/og?title=Sim%20Documentation`,
7273
width: 1200,
7374
height: 630,
7475
alt: 'Sim Documentation',
@@ -82,7 +83,7 @@ export const metadata = {
8283
'Documentation for Sim — the open-source AI workspace where teams build, deploy, and manage AI agents. Connect 1,000+ integrations and every major LLM.',
8384
creator: '@simdotai',
8485
site: '@simdotai',
85-
images: ['https://docs.sim.ai/api/og?title=Sim%20Documentation'],
86+
images: [`${DOCS_BASE_URL}/api/og?title=Sim%20Documentation`],
8687
},
8788
robots: {
8889
index: true,
@@ -96,15 +97,15 @@ export const metadata = {
9697
},
9798
},
9899
alternates: {
99-
canonical: 'https://docs.sim.ai',
100+
canonical: DOCS_BASE_URL,
100101
languages: {
101-
'x-default': 'https://docs.sim.ai',
102-
en: 'https://docs.sim.ai',
103-
es: 'https://docs.sim.ai/es',
104-
fr: 'https://docs.sim.ai/fr',
105-
de: 'https://docs.sim.ai/de',
106-
ja: 'https://docs.sim.ai/ja',
107-
zh: 'https://docs.sim.ai/zh',
102+
'x-default': DOCS_BASE_URL,
103+
en: DOCS_BASE_URL,
104+
es: `${DOCS_BASE_URL}/es`,
105+
fr: `${DOCS_BASE_URL}/fr`,
106+
de: `${DOCS_BASE_URL}/de`,
107+
ja: `${DOCS_BASE_URL}/ja`,
108+
zh: `${DOCS_BASE_URL}/zh`,
108109
},
109110
},
110111
}

0 commit comments

Comments
 (0)