Skip to content

Commit 5f56e46

Browse files
authored
v0.6.51: tables improvements, billing fixes, 404 pages, code hygiene
2 parents bc09865 + 5cf7e8d commit 5f56e46

File tree

847 files changed

+25738
-8197
lines changed

Some content is hidden

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

847 files changed

+25738
-8197
lines changed

.claude/rules/global.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Use TSDoc for documentation. No `====` separators. No non-TSDoc comments.
1010
Never update global styles. Keep all styling local to components.
1111

1212
## ID Generation
13-
Never use `crypto.randomUUID()`, `nanoid`, or the `uuid` package directly. Use the utilities from `@/lib/core/utils/uuid`:
13+
Never use `crypto.randomUUID()`, `nanoid`, or the `uuid` package directly. Use the utilities from `@sim/utils/id`:
1414

1515
- `generateId()` — UUID v4, use by default
1616
- `generateShortId(size?)` — short URL-safe ID (default 21 chars), for compact identifiers
@@ -24,14 +24,14 @@ import { v4 as uuidv4 } from 'uuid'
2424
const id = crypto.randomUUID()
2525

2626
// ✓ Good
27-
import { generateId, generateShortId } from '@/lib/core/utils/uuid'
27+
import { generateId, generateShortId } from '@sim/utils/id'
2828
const uuid = generateId()
2929
const shortId = generateShortId()
3030
const tiny = generateShortId(8)
3131
```
3232

3333
## Common Utilities
34-
Use shared helpers from `@/lib/core/utils/helpers` instead of writing inline implementations:
34+
Use shared helpers from `@sim/utils` instead of writing inline implementations:
3535

3636
- `sleep(ms)` — async delay. Never write `new Promise(resolve => setTimeout(resolve, ms))`
3737
- `toError(value)` — normalize unknown caught values to `Error`. Never write `e instanceof Error ? e : new Error(String(e))`
@@ -44,7 +44,8 @@ const msg = error instanceof Error ? error.message : String(error)
4444
const err = error instanceof Error ? error : new Error(String(error))
4545

4646
// ✓ Good
47-
import { sleep, toError } from '@/lib/core/utils/helpers'
47+
import { sleep } from '@sim/utils/helpers'
48+
import { toError } from '@sim/utils/errors'
4849
await sleep(1000)
4950
const msg = toError(error).message
5051
const err = toError(error)

.claude/rules/sim-testing.md

Lines changed: 81 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,12 @@ Use Vitest. Test files: `feature.ts` → `feature.test.ts`
1313
These modules are mocked globally — do NOT re-mock them in test files unless you need to override behavior:
1414

1515
- `@sim/db``databaseMock`
16+
- `@sim/db/schema``schemaMock`
1617
- `drizzle-orm``drizzleOrmMock`
1718
- `@sim/logger``loggerMock`
19+
- `@/lib/auth``authMock`
20+
- `@/lib/auth/hybrid``hybridAuthMock` (with default session-delegating behavior)
21+
- `@/lib/core/utils/request``requestUtilsMock`
1822
- `@/stores/console/store`, `@/stores/terminal`, `@/stores/execution/store`
1923
- `@/blocks/registry`
2024
- `@trigger.dev/sdk`
@@ -102,10 +106,6 @@ vi.mock('@/lib/workspaces/utils', () => ({
102106
}))
103107
```
104108

105-
### NEVER use `mockAuth()`, `mockConsoleLogger()`, or `setupCommonApiMocks()` from `@sim/testing`
106-
107-
These helpers internally use `vi.doMock()` which is slow. Use direct `vi.hoisted()` + `vi.mock()` instead.
108-
109109
### Mock heavy transitive dependencies
110110

111111
If a module under test imports `@/blocks` (200+ files), `@/tools/registry`, or other heavy modules, mock them:
@@ -135,83 +135,122 @@ await new Promise(r => setTimeout(r, 1))
135135
vi.useFakeTimers()
136136
```
137137

138-
## Mock Pattern Reference
138+
## Centralized Mocks (prefer over local declarations)
139+
140+
`@sim/testing` exports ready-to-use mock modules for common dependencies. Import and pass directly to `vi.mock()` — no `vi.hoisted()` boilerplate needed. Each paired `*MockFns` object exposes the underlying `vi.fn()`s for per-test overrides.
141+
142+
| Module mocked | Import | Factory form |
143+
|---|---|---|
144+
| `@/app/api/auth/oauth/utils` | `authOAuthUtilsMock`, `authOAuthUtilsMockFns` | `vi.mock('@/app/api/auth/oauth/utils', () => authOAuthUtilsMock)` |
145+
| `@/app/api/knowledge/utils` | `knowledgeApiUtilsMock`, `knowledgeApiUtilsMockFns` | `vi.mock('@/app/api/knowledge/utils', () => knowledgeApiUtilsMock)` |
146+
| `@/app/api/workflows/utils` | `workflowsApiUtilsMock`, `workflowsApiUtilsMockFns` | `vi.mock('@/app/api/workflows/utils', () => workflowsApiUtilsMock)` |
147+
| `@/lib/audit/log` | `auditMock`, `auditMockFns` | `vi.mock('@/lib/audit/log', () => auditMock)` |
148+
| `@/lib/auth` | `authMock`, `authMockFns` | `vi.mock('@/lib/auth', () => authMock)` |
149+
| `@/lib/auth/hybrid` | `hybridAuthMock`, `hybridAuthMockFns` | `vi.mock('@/lib/auth/hybrid', () => hybridAuthMock)` |
150+
| `@/lib/copilot/request/http` | `copilotHttpMock`, `copilotHttpMockFns` | `vi.mock('@/lib/copilot/request/http', () => copilotHttpMock)` |
151+
| `@/lib/core/config/env` | `envMock`, `createEnvMock(overrides)` | `vi.mock('@/lib/core/config/env', () => envMock)` |
152+
| `@/lib/core/config/feature-flags` | `featureFlagsMock` | `vi.mock('@/lib/core/config/feature-flags', () => featureFlagsMock)` |
153+
| `@/lib/core/config/redis` | `redisConfigMock`, `redisConfigMockFns` | `vi.mock('@/lib/core/config/redis', () => redisConfigMock)` |
154+
| `@/lib/core/security/encryption` | `encryptionMock`, `encryptionMockFns` | `vi.mock('@/lib/core/security/encryption', () => encryptionMock)` |
155+
| `@/lib/core/security/input-validation.server` | `inputValidationMock`, `inputValidationMockFns` | `vi.mock('@/lib/core/security/input-validation.server', () => inputValidationMock)` |
156+
| `@/lib/core/utils/request` | `requestUtilsMock`, `requestUtilsMockFns` | `vi.mock('@/lib/core/utils/request', () => requestUtilsMock)` |
157+
| `@/lib/core/utils/urls` | `urlsMock`, `urlsMockFns` | `vi.mock('@/lib/core/utils/urls', () => urlsMock)` |
158+
| `@/lib/execution/preprocessing` | `executionPreprocessingMock`, `executionPreprocessingMockFns` | `vi.mock('@/lib/execution/preprocessing', () => executionPreprocessingMock)` |
159+
| `@/lib/logs/execution/logging-session` | `loggingSessionMock`, `loggingSessionMockFns`, `LoggingSessionMock` | `vi.mock('@/lib/logs/execution/logging-session', () => loggingSessionMock)` |
160+
| `@/lib/workflows/orchestration` | `workflowsOrchestrationMock`, `workflowsOrchestrationMockFns` | `vi.mock('@/lib/workflows/orchestration', () => workflowsOrchestrationMock)` |
161+
| `@/lib/workflows/persistence/utils` | `workflowsPersistenceUtilsMock`, `workflowsPersistenceUtilsMockFns` | `vi.mock('@/lib/workflows/persistence/utils', () => workflowsPersistenceUtilsMock)` |
162+
| `@/lib/workflows/utils` | `workflowsUtilsMock`, `workflowsUtilsMockFns` | `vi.mock('@/lib/workflows/utils', () => workflowsUtilsMock)` |
163+
| `@/lib/workspaces/permissions/utils` | `permissionsMock`, `permissionsMockFns` | `vi.mock('@/lib/workspaces/permissions/utils', () => permissionsMock)` |
164+
| `@sim/db/schema` | `schemaMock` | `vi.mock('@sim/db/schema', () => schemaMock)` |
139165

140166
### Auth mocking (API routes)
141167

142168
```typescript
143-
const { mockGetSession } = vi.hoisted(() => ({
144-
mockGetSession: vi.fn(),
145-
}))
169+
import { authMock, authMockFns } from '@sim/testing'
170+
import { beforeEach, describe, expect, it, vi } from 'vitest'
146171

147-
vi.mock('@/lib/auth', () => ({
148-
auth: { api: { getSession: vi.fn() } },
149-
getSession: mockGetSession,
150-
}))
172+
vi.mock('@/lib/auth', () => authMock)
151173

152-
// In tests:
153-
mockGetSession.mockResolvedValue({ user: { id: 'user-1', email: 'test@example.com' } })
154-
mockGetSession.mockResolvedValue(null) // unauthenticated
174+
import { GET } from '@/app/api/my-route/route'
175+
176+
beforeEach(() => {
177+
vi.clearAllMocks()
178+
authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-1' } })
179+
})
155180
```
156181

182+
Only define a local `vi.mock('@/lib/auth', ...)` if the module under test consumes exports outside the centralized shape (e.g., `auth.api.verifyOneTimeToken`, `auth.api.resetPassword`).
183+
157184
### Hybrid auth mocking
158185

159186
```typescript
160-
const { mockCheckSessionOrInternalAuth } = vi.hoisted(() => ({
161-
mockCheckSessionOrInternalAuth: vi.fn(),
162-
}))
187+
import { hybridAuthMock, hybridAuthMockFns } from '@sim/testing'
163188

164-
vi.mock('@/lib/auth/hybrid', () => ({
165-
checkSessionOrInternalAuth: mockCheckSessionOrInternalAuth,
166-
}))
189+
vi.mock('@/lib/auth/hybrid', () => hybridAuthMock)
167190

168191
// In tests:
169-
mockCheckSessionOrInternalAuth.mockResolvedValue({
192+
hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValue({
170193
success: true, userId: 'user-1', authType: 'session',
171194
})
172195
```
173196

174197
### Database chain mocking
175198

199+
Use the centralized `dbChainMock` + `dbChainMockFns` helpers — no `vi.hoisted()` or chain-wiring boilerplate needed.
200+
176201
```typescript
177-
const { mockSelect, mockFrom, mockWhere } = vi.hoisted(() => ({
178-
mockSelect: vi.fn(),
179-
mockFrom: vi.fn(),
180-
mockWhere: vi.fn(),
181-
}))
202+
import { dbChainMock, dbChainMockFns, resetDbChainMock } from '@sim/testing'
182203

183-
vi.mock('@sim/db', () => ({
184-
db: { select: mockSelect },
185-
}))
204+
vi.mock('@sim/db', () => dbChainMock)
205+
// Spread for custom exports: vi.mock('@sim/db', () => ({ ...dbChainMock, myTable: {...} }))
186206

187207
beforeEach(() => {
188-
mockSelect.mockReturnValue({ from: mockFrom })
189-
mockFrom.mockReturnValue({ where: mockWhere })
190-
mockWhere.mockResolvedValue([{ id: '1', name: 'test' }])
208+
vi.clearAllMocks()
209+
resetDbChainMock() // only needed if tests use permanent (non-`Once`) overrides
210+
})
211+
212+
it('reads a row', async () => {
213+
dbChainMockFns.limit.mockResolvedValueOnce([{ id: '1', name: 'test' }])
214+
// exercise code that hits db.select().from().where().limit()
215+
expect(dbChainMockFns.where).toHaveBeenCalled()
191216
})
192217
```
193218

219+
**Default chains supported:**
220+
- `select()/selectDistinct()/selectDistinctOn() → from() → where()/innerJoin()/leftJoin() → where() → limit()/orderBy()/returning()/groupBy()`
221+
- `insert() → values() → returning()/onConflictDoUpdate()/onConflictDoNothing()`
222+
- `update() → set() → where() → limit()/orderBy()/returning()`
223+
- `delete() → where() → limit()/orderBy()/returning()`
224+
- `db.execute()` resolves `[]`
225+
- `db.transaction(cb)` calls cb with `dbChainMock.db`
226+
227+
All terminals default to `Promise.resolve([])`. Override per-test with `dbChainMockFns.<terminal>.mockResolvedValueOnce(...)`.
228+
229+
Use `resetDbChainMock()` in `beforeEach` only when tests replace wiring with `.mockReturnValue` / `.mockResolvedValue` (permanent). Tests using only `...Once` variants don't need it.
230+
194231
## @sim/testing Package
195232

196233
Always prefer over local test data.
197234

198235
| Category | Utilities |
199236
|----------|-----------|
200-
| **Mocks** | `loggerMock`, `databaseMock`, `drizzleOrmMock`, `setupGlobalFetchMock()` |
237+
| **Module mocks** | See "Centralized Mocks" table above |
238+
| **Logger helpers** | `loggerMock`, `createMockLogger()`, `getLoggerCalls()`, `clearLoggerMocks()` |
239+
| **Database helpers** | `databaseMock`, `drizzleOrmMock`, `createMockDb()`, `createMockSql()`, `createMockSqlOperators()` |
240+
| **Fetch helpers** | `setupGlobalFetchMock()`, `createMockFetch()`, `createMockResponse()`, `mockFetchError()` |
201241
| **Factories** | `createSession()`, `createWorkflowRecord()`, `createBlock()`, `createExecutionContext()` |
202242
| **Builders** | `WorkflowBuilder`, `ExecutionContextBuilder` |
203243
| **Assertions** | `expectWorkflowAccessGranted()`, `expectBlockExecuted()` |
204-
| **Requests** | `createMockRequest()`, `createEnvMock()` |
244+
| **Requests** | `createMockRequest()`, `createMockFormDataRequest()` |
205245

206246
## Rules Summary
207247

208248
1. `@vitest-environment node` unless DOM is required
209-
2. `vi.hoisted()` + `vi.mock()` + static imports — never `vi.resetModules()` + `vi.doMock()` + dynamic imports
210-
3. `vi.mock()` calls before importing mocked modules
211-
4. `@sim/testing` utilities over local mocks
249+
2. Prefer centralized mocks from `@sim/testing` (see table above) over local `vi.hoisted()` + `vi.mock()` boilerplate
250+
3. `vi.hoisted()` + `vi.mock()` + static imports — never `vi.resetModules()` + `vi.doMock()` + dynamic imports
251+
4. `vi.mock()` calls before importing mocked modules
212252
5. `beforeEach(() => vi.clearAllMocks())` to reset state — no redundant `afterEach`
213253
6. No `vi.importActual()` — mock everything explicitly
214-
7. No `mockAuth()`, `mockConsoleLogger()`, `setupCommonApiMocks()` — use direct mocks
215-
8. Mock heavy deps (`@/blocks`, `@/tools/registry`, `@/triggers`) in tests that don't need them
216-
9. Use absolute imports in test files
217-
10. Avoid real timers — use 1ms delays or `vi.useFakeTimers()`
254+
7. Mock heavy deps (`@/blocks`, `@/tools/registry`, `@/triggers`) in tests that don't need them
255+
8. Use absolute imports in test files
256+
9. Avoid real timers — use 1ms delays or `vi.useFakeTimers()`

.cursor/rules/global.mdc

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ Use TSDoc for documentation. No `====` separators. No non-TSDoc comments.
1717
Never update global styles. Keep all styling local to components.
1818

1919
## ID Generation
20-
Never use `crypto.randomUUID()`, `nanoid`, or the `uuid` package directly. Use the utilities from `@/lib/core/utils/uuid`:
20+
Never use `crypto.randomUUID()`, `nanoid`, or the `uuid` package directly. Use the utilities from `@sim/utils/id`:
2121

2222
- `generateId()` — UUID v4, use by default
2323
- `generateShortId(size?)` — short URL-safe ID (default 21 chars), for compact identifiers
@@ -31,14 +31,14 @@ import { v4 as uuidv4 } from 'uuid'
3131
const id = crypto.randomUUID()
3232

3333
// ✓ Good
34-
import { generateId, generateShortId } from '@/lib/core/utils/uuid'
34+
import { generateId, generateShortId } from '@sim/utils/id'
3535
const uuid = generateId()
3636
const shortId = generateShortId()
3737
const tiny = generateShortId(8)
3838
```
3939

4040
## Common Utilities
41-
Use shared helpers from `@/lib/core/utils/helpers` instead of writing inline implementations:
41+
Use shared helpers from `@sim/utils` instead of writing inline implementations:
4242

4343
- `sleep(ms)` — async delay. Never write `new Promise(resolve => setTimeout(resolve, ms))`
4444
- `toError(value)` — normalize unknown caught values to `Error`. Never write `e instanceof Error ? e : new Error(String(e))`
@@ -51,7 +51,8 @@ const msg = error instanceof Error ? error.message : String(error)
5151
const err = error instanceof Error ? error : new Error(String(error))
5252

5353
// ✓ Good
54-
import { sleep, toError } from '@/lib/core/utils/helpers'
54+
import { sleep } from '@sim/utils/helpers'
55+
import { toError } from '@sim/utils/errors'
5556
await sleep(1000)
5657
const msg = toError(error).message
5758
const err = toError(error)

0 commit comments

Comments
 (0)