Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
3 changes: 1 addition & 2 deletions .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -354,9 +354,8 @@ In addition, you will need to update the registries:
// apps/sim/blocks/blocks/pinecone.ts
import { PineconeIcon } from '@/components/icons'
import type { BlockConfig } from '@/blocks/types'
import type { PineconeResponse } from '@/tools/pinecone/types'

export const PineconeBlock: BlockConfig<PineconeResponse> = {
export const PineconeBlock: BlockConfig = {
type: 'pinecone',
name: 'Pinecone',
description: 'Use Pinecone vector database',
Expand Down
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ The `'use client'` server boundary, the app/worker runtime env split, and featur
- **Naming**: components PascalCase (`WorkflowList`); hooks `use*`; files kebab-case (`workflow-list.tsx`); constants SCREAMING_SNAKE_CASE; interfaces PascalCase with a suffix (`WorkflowListProps`); stores `stores/<feature>/store.ts`.
- **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` (use precise types or `unknown` with guards); 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.
- **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.
Expand Down
32 changes: 14 additions & 18 deletions apps/desktop/src/main/browser-agent/driver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1738,7 +1738,7 @@ function requireSnapshotForElementAction(): void {
)
}

function pageTargetForElement(contents: WebContents, elementId: number): PageExecutionTarget {
function pageTargetForElement(elementId: number): PageExecutionTarget {
requireSnapshotForElementAction()
const target = driverScopeState().snapshotTargets.get(elementId)
if (!target || ('isDestroyed' in target && target.isDestroyed())) {
Expand Down Expand Up @@ -2351,7 +2351,7 @@ async function captureSnapshot(
if (tab.view.webContents !== contents) {
throw new ToolError('The active tab changed before the snapshot started. Try again.')
}
if (elementId !== undefined && pageTargetForElement(contents, elementId) !== contents) {
if (elementId !== undefined && pageTargetForElement(elementId) !== contents) {
throw new ToolError(
'Scoped snapshots require a top-page element. Omit elementId to capture framed content.'
)
Expand Down Expand Up @@ -2745,7 +2745,7 @@ async function executeToolInner(
const contents = session.requireAutomationTab().view.webContents
const elementId = requireNum(params, 'elementId')
const paths = uploadPaths(params)
const target = pageTargetForElement(contents, elementId)
const target = pageTargetForElement(elementId)
assertCurrentExecution()
const frame = 'getURL' in target ? target.mainFrame : target
const expression = `(${String(resolveFileInputTarget)})(${elementId})`
Expand Down Expand Up @@ -2816,8 +2816,7 @@ async function executeToolInner(
}
const waitedTab = session.requireAutomationTab()
const contents = waitedTab.view.webContents
const elementTarget =
elementId === undefined ? undefined : pageTargetForElement(contents, elementId)
const elementTarget = elementId === undefined ? undefined : pageTargetForElement(elementId)
if (elementTarget && elementTarget !== contents) {
throw new ToolError(
'Element-state waits are limited to the top page. Use a text or URL condition for framed content.'
Expand Down Expand Up @@ -2946,7 +2945,7 @@ async function executeToolInner(
const contents = session.requireAutomationTab().view.webContents
const elementId = num(params, 'elementId')
if (elementId === undefined) return await readWholePageText(contents, executionDeadline)
const target = pageTargetForElement(contents, elementId)
const target = pageTargetForElement(elementId)
return unwrapPageResult(
await execInPage(target, readPageText, [elementId], false, executionDeadline)
)
Expand All @@ -2961,7 +2960,7 @@ async function executeToolInner(
const elementId = num(params, 'elementId')
let elementClip: Record<string, unknown> | undefined
if (elementId !== undefined) {
const target = pageTargetForElement(contents, elementId)
const target = pageTargetForElement(elementId)
if (target !== contents) {
throw new ToolError(
'Element screenshots are limited to the top page. Use browser_screenshot without elementId for framed content.'
Expand Down Expand Up @@ -3119,7 +3118,7 @@ async function executeToolInner(
const contents = clickedTab.view.webContents
const elementId = requireNum(params, 'elementId')
const click = pointerClick(params)
const target = pageTargetForElement(contents, elementId)
const target = pageTargetForElement(elementId)
const targetFrame = frameExecutionTarget(target, contents)
let trusted = false
let activation = 'synthetic-pointer'
Expand Down Expand Up @@ -3596,7 +3595,7 @@ async function executeToolInner(
let stoppedIndex = 0
let dispatchStarted = false
const readField = async (field: FormField) => {
const target = pageTargetForElement(contents, field.elementId)
const target = pageTargetForElement(field.elementId)
if (target !== contents)
throw new ToolError(
'Form batches require top-page fields; use individual tools for framed fields.'
Expand Down Expand Up @@ -3757,7 +3756,7 @@ async function executeToolInner(
if (typeof text !== 'string') throw new ToolError('Missing required parameter "text"')
const submit = params.submit === true
const contents = session.requireAutomationTab().view.webContents
const target = pageTargetForElement(contents, elementId)
const target = pageTargetForElement(elementId)
const targetFrame = frameExecutionTarget(target, contents)

// Native path: focus + select current content, then insert through the
Expand Down Expand Up @@ -4307,9 +4306,7 @@ async function executeToolInner(
const contents = session.requireAutomationTab().view.webContents
const elementId = num(params, 'elementId')
const target =
elementId !== undefined
? pageTargetForElement(contents, elementId)
: focusedPageTarget(contents)
elementId !== undefined ? pageTargetForElement(elementId) : focusedPageTarget(contents)
const targetFrame = frameExecutionTarget(target, contents)
assertCurrentExecution()
if (elementId !== undefined) assertElementActionCurrent(contents, elementId, target)
Expand Down Expand Up @@ -4355,7 +4352,7 @@ async function executeToolInner(
const selection = values === undefined ? requireStr(params, 'value') : (values as string[])
const contents = session.requireAutomationTab().view.webContents
const elementId = requireNum(params, 'elementId')
const target = pageTargetForElement(contents, elementId)
const target = pageTargetForElement(elementId)
const targetFrame = frameExecutionTarget(target, contents)
assertCurrentExecution()
assertElementActionCurrent(contents, elementId, target)
Expand Down Expand Up @@ -4434,8 +4431,7 @@ async function executeToolInner(
throw new ToolError('Missing required boolean parameter "checked"')
}
const checked = params.checked
const contents = session.requireAutomationTab().view.webContents
const target = pageTargetForElement(contents, elementId)
const target = pageTargetForElement(elementId)
const before = toRecord(
unwrapPageResult(
await execInPage(target, readCheckableElementState, [elementId], false, executionDeadline)
Expand Down Expand Up @@ -4562,7 +4558,7 @@ async function executeToolInner(
)
}
const elementId = requireNum(params, 'elementId')
const target = pageTargetForElement(contents, elementId)
const target = pageTargetForElement(elementId)
const targetFrame = frameExecutionTarget(target, contents)
let beforePage = await pageActionState(target, true, elementId)
let beforeElement = await activeElementState(target)
Expand Down Expand Up @@ -4939,7 +4935,7 @@ async function executeToolInner(
): Promise<{ x: number; y: number; element?: string }> => {
const elementId = num(params, `${which}ElementId`)
if (elementId !== undefined) {
const target = pageTargetForElement(contents, elementId)
const target = pageTargetForElement(elementId)
if (frameExecutionTarget(target, contents)) {
throw new ToolError(
`Dragging elements inside embedded frames is not supported. Use ${which}X/${which}Y viewport coordinates instead.`
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/main/browser-credentials/os-auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ async function promptForSecret(reason: string, action: string): Promise<boolean>
? await showShellDialog(parent, options)
: await showShellDialog(options)
return response === 1
} catch (error) {
} catch {
// Fail closed: if the confirmation cannot be shown, nothing is revealed.
logger.warn('Could not present the credential confirmation')
return false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,9 +208,7 @@ export default function ResumeExecutionPage({
const queuePosition = selectedDetail?.pausePoint.queuePosition
const resumeInputsRef = useRef<Record<string, string>>({})
const [resumeInput, setResumeInput] = useState('')
const [formValuesByContext, setFormValuesByContext] = useState<
Record<string, Record<string, string>>
>({})
const formValuesByContextRef = useRef<Record<string, Record<string, string>>>({})
const [formValues, setFormValues] = useState<Record<string, string>>({})
const [formErrors, setFormErrors] = useState<Record<string, string>>({})
const [loadingAction, setLoadingAction] = useState(false)
Expand Down Expand Up @@ -308,17 +306,6 @@ export default function ResumeExecutionPage({
[formatValueForInputField]
)

const formatStructureValue = useCallback((value: any): string => {
if (value === null || value === undefined) return '—'
if (typeof value === 'string') return value
if (typeof value === 'number' || typeof value === 'boolean') return String(value)
try {
return JSON.stringify(value, null, 2)
} catch {
return String(value)
}
}, [])

const parseFormValue = useCallback(
(field: NormalizedInputField, rawValue: string): { value: any; error?: string } => {
const value = rawValue ?? ''
Expand Down Expand Up @@ -358,11 +345,12 @@ export default function ResumeExecutionPage({
const handleFormFieldChange = useCallback(
(fieldName: string, newValue: string) => {
if (!selectedContextId) return
setFormValues((prev) => {
const updated = { ...prev, [fieldName]: newValue }
setFormValuesByContext((map) => ({ ...map, [selectedContextId]: updated }))
return updated
})
const updated = {
...formValuesByContextRef.current[selectedContextId],
[fieldName]: newValue,
}
formValuesByContextRef.current[selectedContextId] = updated
setFormValues(updated)
setFormErrors((prev) => {
if (!prev[fieldName]) return prev
const { [fieldName]: _, ...rest } = prev
Expand Down Expand Up @@ -516,12 +504,9 @@ export default function ResumeExecutionPage({
: undefined
if (operation === 'human' && fetchedInputFields.length > 0) {
const baseValues = buildInitialFormValues(fetchedInputFields, submission)
let mergedValues = baseValues
setFormValuesByContext((prev) => {
const existingValues = prev[detail.pausePoint.contextId]
if (existingValues) mergedValues = { ...baseValues, ...existingValues }
return { ...prev, [detail.pausePoint.contextId]: mergedValues }
})
const existingValues = formValuesByContextRef.current[detail.pausePoint.contextId]
const mergedValues = existingValues ? { ...baseValues, ...existingValues } : baseValues
formValuesByContextRef.current[detail.pausePoint.contextId] = mergedValues
setFormValues(mergedValues)
setFormErrors({})
if (resumeInputsRef.current[detail.pausePoint.contextId] !== undefined) {
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/app/api/auth/sso/register/route.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,7 @@ describe('POST /api/auth/sso/register', () => {

it('does not SSRF-validate userInfoEndpoint when skipUserInfoEndpoint is requested', async () => {
queueMembers([{ organizationId: 'org1', role: 'owner' }])
mockValidateUrlWithDNS.mockImplementation(async (url: string, label: string) => {
mockValidateUrlWithDNS.mockImplementation(async (_url: string, label: string) => {
if (label === 'OIDC userInfoEndpoint') {
return { isValid: false, error: 'resolves to a private IP address' }
}
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/app/api/chat/[identifier]/otp/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ async function deliverOtp(requestId: string, deploymentId: string, title: string
const otp = generateOTP()
await storeOTP('chat', deploymentId, email, otp)

const emailHtml = await renderOTPEmail(otp, email, 'email-verification', title)
const emailHtml = await renderOTPEmail(otp, 'email-verification', title)
const emailResult = await sendEmail({
to: email,
subject: getOtpSubject(title),
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/app/api/copilot/feedback/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ export const POST = withRouteHandler(async (req: NextRequest) => {
* GET /api/copilot/feedback
* Get feedback records for the authenticated user
*/
export const GET = withRouteHandler(async (req: NextRequest) => {
export const GET = withRouteHandler(async () => {
const tracker = createRequestTracker()

try {
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/app/api/environment/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ export const POST = withRouteHandler(async (req: NextRequest) => {
}
})

export const GET = withRouteHandler(async (request: Request) => {
export const GET = withRouteHandler(async () => {
const requestId = generateRequestId()

try {
Expand Down
20 changes: 10 additions & 10 deletions apps/sim/app/api/files/authorization.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ describe('verifyKBFileWriteAccess (binding-only delete authorization)', () => {

describe('public-context access (profile-pictures / og-images / workspace-logos)', () => {
function write(cloudKey: string, context: 'profile-pictures' | 'og-images' | 'workspace-logos') {
return verifyFileAccess(cloudKey, USER_ID, undefined, context, false, { requireWrite: true })
return verifyFileAccess(cloudKey, USER_ID, undefined, context, { requireWrite: true })
}

it('allows organization logo reads and denies generic deletes even for the uploader', async () => {
Expand All @@ -152,10 +152,10 @@ describe('public-context access (profile-pictures / og-images / workspace-logos)
true
)
await expect(
verifyFileAccess(key, USER_ID, undefined, 'organization-logos', false, { requireWrite: true })
verifyFileAccess(key, USER_ID, undefined, 'organization-logos', { requireWrite: true })
).resolves.toBe(false)
await expect(
verifyFileAccess(key, USER_ID, undefined, 'general', false, { requireWrite: true })
verifyFileAccess(key, USER_ID, undefined, 'general', { requireWrite: true })
).resolves.toBe(false)
expect(mockGetFileMetadata).not.toHaveBeenCalled()
expect(mockGetUserEntityPermissions).not.toHaveBeenCalled()
Expand Down Expand Up @@ -203,7 +203,7 @@ describe('workspace-scoped access (workspace files and mothership attachments)',
})

function read(cloudKey: string, context: 'workspace' | 'mothership') {
return verifyFileAccess(cloudKey, USER_ID, undefined, context, false)
return verifyFileAccess(cloudKey, USER_ID, undefined, context)
}

interface BoundRow {
Expand Down Expand Up @@ -315,7 +315,7 @@ describe('organization connector cache access', () => {
'denies the uploader a raw download even with a forged %s context',
async (context) => {
await expect(
verifyFileAccess(CLOUD_KEY, USER_ID, undefined, context, false, { knowledgeAccess: 'user' })
verifyFileAccess(CLOUD_KEY, USER_ID, undefined, context, { knowledgeAccess: 'user' })
).resolves.toBe(false)
expect(mockGetFileMetadata).not.toHaveBeenCalled()
expect(mockGetUserEntityPermissions).not.toHaveBeenCalled()
Expand All @@ -325,7 +325,7 @@ describe('organization connector cache access', () => {
it('denies system reads after the cache loses its active document reference', async () => {
dbChainMockFns.limit.mockResolvedValue([])
await expect(
verifyFileAccess(CLOUD_KEY, USER_ID, undefined, 'knowledge-base', false, {
verifyFileAccess(CLOUD_KEY, USER_ID, undefined, 'knowledge-base', {
knowledgeAccess: SYSTEM_ACCESS_SCOPE,
})
).resolves.toBe(false)
Expand All @@ -338,7 +338,7 @@ describe('organization connector cache access', () => {
deletedAt: null,
})
await expect(
verifyFileAccess(CLOUD_KEY, USER_ID, undefined, 'knowledge-base', false, {
verifyFileAccess(CLOUD_KEY, USER_ID, undefined, 'knowledge-base', {
knowledgeAccess: SYSTEM_ACCESS_SCOPE,
})
).resolves.toBe(false)
Expand All @@ -347,7 +347,7 @@ describe('organization connector cache access', () => {

it('does not let a raw download endpoint delete organization caches', async () => {
await expect(
verifyFileAccess(CLOUD_KEY, USER_ID, undefined, 'general', false, {
verifyFileAccess(CLOUD_KEY, USER_ID, undefined, 'general', {
requireWrite: true,
knowledgeAccess: SYSTEM_ACCESS_SCOPE,
})
Expand Down Expand Up @@ -378,7 +378,7 @@ describe('KB file live source authorization', () => {
queueTableRows(schemaMock.knowledgeConnector, [{ connectorId: 'confluence-source' }])
queueTableRows(schemaMock.document, allowed ? [{ id: 'doc-1' }] : [])
await expect(
verifyFileAccess(CLOUD_KEY, USER_ID, undefined, 'knowledge-base', false, {
verifyFileAccess(CLOUD_KEY, USER_ID, undefined, 'knowledge-base', {
knowledgeAccess: access,
})
).resolves.toBe(allowed)
Expand Down Expand Up @@ -415,7 +415,7 @@ describe('KB file live source authorization', () => {
mockGetUserEntityPermissions.mockResolvedValue(null)
const get = vi.fn()
await expect(
verifyFileAccess(CLOUD_KEY, USER_ID, undefined, 'knowledge-base', false, {
verifyFileAccess(CLOUD_KEY, USER_ID, undefined, 'knowledge-base', {
knowledgeAccess: { get, getForConnectors: vi.fn(), getForDocuments: vi.fn() },
})
).resolves.toBe(false)
Expand Down
Loading
Loading