Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
304cf71
improvement(cmdk): refactor search modal to use cmdk + fix icon SVG I…
waleedlatif1 Jan 28, 2026
6814f33
fix(helm): move rotationPolicy under privateKey for cert-manager comp…
waleedlatif1 Jan 28, 2026
01e0723
fix(loops): fix loops on empty collection (#3049)
Sg312 Jan 28, 2026
2c2b485
fix(workflow): update container dimensions on keyboard movement (#3043)
waleedlatif1 Jan 28, 2026
72a2f79
improvement(search-modal): add quick navigation items and fix cmdk va…
waleedlatif1 Jan 28, 2026
655fe4f
feat(executor): run from/until block (#3029)
Sg312 Jan 28, 2026
78410ee
improvement(inputs): sanitize trigger inputs better (#3047)
icecrasher321 Jan 28, 2026
c00f05c
fix(tests): use UTC methods for timezone-independent schedule asserti…
waleedlatif1 Jan 28, 2026
8b24047
feat(description): add deployment version descriptions (#3048)
waleedlatif1 Jan 28, 2026
5c02d46
feat(terminal): structured output (#3026)
emir-karabeg Jan 28, 2026
57f0837
fix(child-workflow-error-spans): pass trace-spans accurately in block…
icecrasher321 Jan 28, 2026
12d529d
fix: terminal spacing, subflow disabled in preview (#3055)
emir-karabeg Jan 28, 2026
0c0f19c
fix(icons): update strokeWidth of action bar items to match, update r…
waleedlatif1 Jan 29, 2026
9e40342
fix(snapshot): consolidate to use hasWorkflowChanges check (#3051)
icecrasher321 Jan 29, 2026
1bc476f
fix(copilot): panning on workflow (#3057)
emir-karabeg Jan 29, 2026
06d7ce7
feat(timeout): add API block timeout configuration (#3053)
waleedlatif1 Jan 29, 2026
1469e9c
feat(youtube): add captions, trending, and video categories tools wit…
waleedlatif1 Jan 29, 2026
20bb7cd
improvement(preview): include current workflow badge in breadcrumb in…
emir-karabeg Jan 29, 2026
e0f1e66
feat(child-workflows): nested execution snapshots (#3059)
icecrasher321 Jan 29, 2026
0d8d9fb
fix(type): logs workspace delivery (#3063)
icecrasher321 Jan 29, 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
385 changes: 220 additions & 165 deletions apps/docs/components/icons.tsx

Large diffs are not rendered by default.

19 changes: 17 additions & 2 deletions apps/docs/content/docs/en/quick-reference/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -280,14 +280,24 @@ A quick lookup for everyday actions in the Sim workflow editor. For keyboard sho
<td>Click clear button in Chat panel</td>
<td><ActionImage src="/static/quick-reference/clear-chat.png" alt="Clear chat history" /></td>
</tr>
<tr>
<td>Run from block</td>
<td>Hover block → Click play button, or right-click → **Run from block**</td>
<td><ActionImage src="/static/quick-reference/run-from-block.png" alt="Run from block" /></td>
</tr>
<tr>
<td>Run until block</td>
<td>Right-click block → **Run until block**</td>
<td><ActionImage src="/static/quick-reference/run-until-block.png" alt="Run until block" /></td>
</tr>
<tr>
<td>View execution logs</td>
<td>Open terminal panel at bottom, or `Mod+L`</td>
<td><ActionImage src="/static/quick-reference/terminal.png" alt="Execution logs terminal" /></td>
</tr>
<tr>
<td>Filter logs by block or status</td>
<td>Click block filter in terminal or right-click log entry → **Filter by Block** or **Filter by Status**</td>
<td>Filter logs</td>
<td>Click filter icon in terminal Filter by block or status</td>
<td><ActionImage src="/static/quick-reference/filter-block.png" alt="Filter logs by block" /></td>
</tr>
<tr>
Expand Down Expand Up @@ -335,6 +345,11 @@ A quick lookup for everyday actions in the Sim workflow editor. For keyboard sho
<td>Access previous versions in Deploy tab → **Promote to live**</td>
<td><ActionImage src="/static/quick-reference/promote-deployment.png" alt="Promote deployment to live" /></td>
</tr>
<tr>
<td>Add version description</td>
<td>Deploy tab → Click description icon → Add or generate description</td>
<td><ActionVideo src="quick-reference/deployment-description.mp4" alt="Add deployment version description" /></td>
</tr>
<tr>
<td>Copy API endpoint</td>
<td>Deploy tab → API → Copy API cURL</td>
Expand Down
281 changes: 196 additions & 85 deletions apps/docs/content/docs/en/tools/youtube.mdx

Large diffs are not rendered by default.

Binary file modified apps/docs/public/static/introduction.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified apps/docs/public/static/quick-reference/filter-block.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified apps/docs/public/static/quick-reference/search-everything.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion apps/sim/app/_styles/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
--panel-width: 320px; /* PANEL_WIDTH.DEFAULT */
--toolbar-triggers-height: 300px; /* TOOLBAR_TRIGGERS_HEIGHT.DEFAULT */
--editor-connections-height: 172px; /* EDITOR_CONNECTIONS_HEIGHT.DEFAULT */
--terminal-height: 155px; /* TERMINAL_HEIGHT.DEFAULT */
--terminal-height: 206px; /* TERMINAL_HEIGHT.DEFAULT */
}

.sidebar-container {
Expand Down
28 changes: 15 additions & 13 deletions apps/sim/app/api/logs/[id]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export async function GET(_request: NextRequest, { params }: { params: Promise<{
deploymentVersionName: workflowDeploymentVersion.name,
})
.from(workflowExecutionLogs)
.innerJoin(workflow, eq(workflowExecutionLogs.workflowId, workflow.id))
.leftJoin(workflow, eq(workflowExecutionLogs.workflowId, workflow.id))
.leftJoin(
workflowDeploymentVersion,
eq(workflowDeploymentVersion.id, workflowExecutionLogs.deploymentVersionId)
Expand All @@ -65,7 +65,7 @@ export async function GET(_request: NextRequest, { params }: { params: Promise<{
permissions,
and(
eq(permissions.entityType, 'workspace'),
eq(permissions.entityId, workflow.workspaceId),
eq(permissions.entityId, workflowExecutionLogs.workspaceId),
eq(permissions.userId, userId)
)
)
Expand All @@ -77,17 +77,19 @@ export async function GET(_request: NextRequest, { params }: { params: Promise<{
return NextResponse.json({ error: 'Not found' }, { status: 404 })
}

const workflowSummary = {
id: log.workflowId,
name: log.workflowName,
description: log.workflowDescription,
color: log.workflowColor,
folderId: log.workflowFolderId,
userId: log.workflowUserId,
workspaceId: log.workflowWorkspaceId,
createdAt: log.workflowCreatedAt,
updatedAt: log.workflowUpdatedAt,
}
const workflowSummary = log.workflowId
? {
id: log.workflowId,
name: log.workflowName,
description: log.workflowDescription,
color: log.workflowColor,
folderId: log.workflowFolderId,
userId: log.workflowUserId,
workspaceId: log.workflowWorkspaceId,
createdAt: log.workflowCreatedAt,
updatedAt: log.workflowUpdatedAt,
}
: null

const response = {
id: log.id,
Expand Down
24 changes: 12 additions & 12 deletions apps/sim/app/api/logs/cleanup/route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { db } from '@sim/db'
import { subscription, user, workflow, workflowExecutionLogs } from '@sim/db/schema'
import { subscription, user, workflowExecutionLogs, workspace } from '@sim/db/schema'
import { createLogger } from '@sim/logger'
import { and, eq, inArray, lt, sql } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
Expand Down Expand Up @@ -40,17 +40,17 @@ export async function GET(request: NextRequest) {

const freeUserIds = freeUsers.map((u) => u.userId)

const workflowsQuery = await db
.select({ id: workflow.id })
.from(workflow)
.where(inArray(workflow.userId, freeUserIds))
const workspacesQuery = await db
.select({ id: workspace.id })
.from(workspace)
.where(inArray(workspace.billedAccountUserId, freeUserIds))

if (workflowsQuery.length === 0) {
logger.info('No workflows found for free users')
return NextResponse.json({ message: 'No workflows found for cleanup' })
if (workspacesQuery.length === 0) {
logger.info('No workspaces found for free users')
return NextResponse.json({ message: 'No workspaces found for cleanup' })
}

const workflowIds = workflowsQuery.map((w) => w.id)
const workspaceIds = workspacesQuery.map((w) => w.id)

const results = {
enhancedLogs: {
Expand All @@ -77,7 +77,7 @@ export async function GET(request: NextRequest) {
let batchesProcessed = 0
let hasMoreLogs = true

logger.info(`Starting enhanced logs cleanup for ${workflowIds.length} workflows`)
logger.info(`Starting enhanced logs cleanup for ${workspaceIds.length} workspaces`)

while (hasMoreLogs && batchesProcessed < MAX_BATCHES) {
const oldEnhancedLogs = await db
Expand All @@ -99,7 +99,7 @@ export async function GET(request: NextRequest) {
.from(workflowExecutionLogs)
.where(
and(
inArray(workflowExecutionLogs.workflowId, workflowIds),
inArray(workflowExecutionLogs.workspaceId, workspaceIds),
lt(workflowExecutionLogs.createdAt, retentionDate)
)
)
Expand Down Expand Up @@ -127,7 +127,7 @@ export async function GET(request: NextRequest) {
customKey: enhancedLogKey,
metadata: {
logId: String(log.id),
workflowId: String(log.workflowId),
workflowId: String(log.workflowId ?? ''),
executionId: String(log.executionId),
logType: 'enhanced',
archivedAt: new Date().toISOString(),
Expand Down
40 changes: 37 additions & 3 deletions apps/sim/app/api/logs/execution/[executionId]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ import {
workflowExecutionSnapshots,
} from '@sim/db/schema'
import { createLogger } from '@sim/logger'
import { and, eq } from 'drizzle-orm'
import { and, eq, inArray } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { checkHybridAuth } from '@/lib/auth/hybrid'
import { generateRequestId } from '@/lib/core/utils/request'
import type { TraceSpan, WorkflowExecutionLog } from '@/lib/logs/types'

const logger = createLogger('LogsByExecutionIdAPI')

Expand Down Expand Up @@ -48,14 +49,15 @@ export async function GET(
endedAt: workflowExecutionLogs.endedAt,
totalDurationMs: workflowExecutionLogs.totalDurationMs,
cost: workflowExecutionLogs.cost,
executionData: workflowExecutionLogs.executionData,
})
.from(workflowExecutionLogs)
.innerJoin(workflow, eq(workflowExecutionLogs.workflowId, workflow.id))
.leftJoin(workflow, eq(workflowExecutionLogs.workflowId, workflow.id))
.innerJoin(
permissions,
and(
eq(permissions.entityType, 'workspace'),
eq(permissions.entityId, workflow.workspaceId),
eq(permissions.entityId, workflowExecutionLogs.workspaceId),
eq(permissions.userId, authenticatedUserId)
)
)
Expand All @@ -78,10 +80,42 @@ export async function GET(
return NextResponse.json({ error: 'Workflow state snapshot not found' }, { status: 404 })
}

const executionData = workflowLog.executionData as WorkflowExecutionLog['executionData']
const traceSpans = (executionData?.traceSpans as TraceSpan[]) || []
const childSnapshotIds = new Set<string>()
const collectSnapshotIds = (spans: TraceSpan[]) => {
spans.forEach((span) => {
const snapshotId = span.childWorkflowSnapshotId
if (typeof snapshotId === 'string') {
childSnapshotIds.add(snapshotId)
}
if (span.children?.length) {
collectSnapshotIds(span.children)
}
})
}
if (traceSpans.length > 0) {
collectSnapshotIds(traceSpans)
}

const childWorkflowSnapshots =
childSnapshotIds.size > 0
? await db
.select()
.from(workflowExecutionSnapshots)
.where(inArray(workflowExecutionSnapshots.id, Array.from(childSnapshotIds)))
: []

const childSnapshotMap = childWorkflowSnapshots.reduce<Record<string, unknown>>((acc, snap) => {
acc[snap.id] = snap.stateData
return acc
}, {})

const response = {
executionId,
workflowId: workflowLog.workflowId,
workflowState: snapshot.stateData,
childWorkflowSnapshots: childSnapshotMap,
executionMetadata: {
trigger: workflowLog.trigger,
startedAt: workflowLog.startedAt.toISOString(),
Expand Down
6 changes: 3 additions & 3 deletions apps/sim/app/api/logs/export/route.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { db } from '@sim/db'
import { permissions, workflow, workflowExecutionLogs } from '@sim/db/schema'
import { createLogger } from '@sim/logger'
import { and, desc, eq } from 'drizzle-orm'
import { and, desc, eq, sql } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { getSession } from '@/lib/auth'
import { buildFilterConditions, LogFilterParamsSchema } from '@/lib/logs/filters'
Expand Down Expand Up @@ -41,7 +41,7 @@ export async function GET(request: NextRequest) {
totalDurationMs: workflowExecutionLogs.totalDurationMs,
cost: workflowExecutionLogs.cost,
executionData: workflowExecutionLogs.executionData,
workflowName: workflow.name,
workflowName: sql<string>`COALESCE(${workflow.name}, 'Deleted Workflow')`,
}

const workspaceCondition = eq(workflowExecutionLogs.workspaceId, params.workspaceId)
Expand Down Expand Up @@ -74,7 +74,7 @@ export async function GET(request: NextRequest) {
const rows = await db
.select(selectColumns)
.from(workflowExecutionLogs)
.innerJoin(workflow, eq(workflowExecutionLogs.workflowId, workflow.id))
.leftJoin(workflow, eq(workflowExecutionLogs.workflowId, workflow.id))
.innerJoin(
permissions,
and(
Expand Down
28 changes: 15 additions & 13 deletions apps/sim/app/api/logs/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ export async function GET(request: NextRequest) {
workflowDeploymentVersion,
eq(workflowDeploymentVersion.id, workflowExecutionLogs.deploymentVersionId)
)
.innerJoin(workflow, eq(workflowExecutionLogs.workflowId, workflow.id))
.leftJoin(workflow, eq(workflowExecutionLogs.workflowId, workflow.id))
.innerJoin(
permissions,
and(
Expand Down Expand Up @@ -190,7 +190,7 @@ export async function GET(request: NextRequest) {
pausedExecutions,
eq(pausedExecutions.executionId, workflowExecutionLogs.executionId)
)
.innerJoin(workflow, eq(workflowExecutionLogs.workflowId, workflow.id))
.leftJoin(workflow, eq(workflowExecutionLogs.workflowId, workflow.id))
.innerJoin(
permissions,
and(
Expand Down Expand Up @@ -314,17 +314,19 @@ export async function GET(request: NextRequest) {
} catch {}
}

const workflowSummary = {
id: log.workflowId,
name: log.workflowName,
description: log.workflowDescription,
color: log.workflowColor,
folderId: log.workflowFolderId,
userId: log.workflowUserId,
workspaceId: log.workflowWorkspaceId,
createdAt: log.workflowCreatedAt,
updatedAt: log.workflowUpdatedAt,
}
const workflowSummary = log.workflowId
? {
id: log.workflowId,
name: log.workflowName,
description: log.workflowDescription,
color: log.workflowColor,
folderId: log.workflowFolderId,
userId: log.workflowUserId,
workspaceId: log.workflowWorkspaceId,
createdAt: log.workflowCreatedAt,
updatedAt: log.workflowUpdatedAt,
}
: null

return {
id: log.id,
Expand Down
14 changes: 9 additions & 5 deletions apps/sim/app/api/logs/stats/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export async function GET(request: NextRequest) {
maxTime: sql<string>`MAX(${workflowExecutionLogs.startedAt})`,
})
.from(workflowExecutionLogs)
.innerJoin(workflow, eq(workflowExecutionLogs.workflowId, workflow.id))
.leftJoin(workflow, eq(workflowExecutionLogs.workflowId, workflow.id))
.innerJoin(
permissions,
and(
Expand Down Expand Up @@ -103,8 +103,8 @@ export async function GET(request: NextRequest) {

const statsQuery = await db
.select({
workflowId: workflowExecutionLogs.workflowId,
workflowName: workflow.name,
workflowId: sql<string>`COALESCE(${workflowExecutionLogs.workflowId}, 'deleted')`,
workflowName: sql<string>`COALESCE(${workflow.name}, 'Deleted Workflow')`,
segmentIndex:
sql<number>`FLOOR(EXTRACT(EPOCH FROM (${workflowExecutionLogs.startedAt} - ${startTimeIso}::timestamp)) * 1000 / ${segmentMs})`.as(
'segment_index'
Expand All @@ -120,7 +120,7 @@ export async function GET(request: NextRequest) {
),
})
.from(workflowExecutionLogs)
.innerJoin(workflow, eq(workflowExecutionLogs.workflowId, workflow.id))
.leftJoin(workflow, eq(workflowExecutionLogs.workflowId, workflow.id))
.innerJoin(
permissions,
and(
Expand All @@ -130,7 +130,11 @@ export async function GET(request: NextRequest) {
)
)
.where(whereCondition)
.groupBy(workflowExecutionLogs.workflowId, workflow.name, sql`segment_index`)
.groupBy(
sql`COALESCE(${workflowExecutionLogs.workflowId}, 'deleted')`,
sql`COALESCE(${workflow.name}, 'Deleted Workflow')`,
sql`segment_index`
)

const workflowMap = new Map<
string,
Expand Down
8 changes: 4 additions & 4 deletions apps/sim/app/api/schedules/[id]/route.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@ describe('Schedule PUT API (Reactivate)', () => {
expect(nextRunAt).toBeGreaterThan(beforeCall)
expect(nextRunAt).toBeLessThanOrEqual(afterCall + 5 * 60 * 1000 + 1000)
// Should align with 5-minute intervals (minute divisible by 5)
expect(new Date(nextRunAt).getMinutes() % 5).toBe(0)
expect(new Date(nextRunAt).getUTCMinutes() % 5).toBe(0)
})

it('calculates nextRunAt from daily cron expression', async () => {
Expand Down Expand Up @@ -572,7 +572,7 @@ describe('Schedule PUT API (Reactivate)', () => {
expect(nextRunAt.getTime()).toBeGreaterThan(beforeCall)
expect(nextRunAt.getTime()).toBeLessThanOrEqual(beforeCall + 10 * 60 * 1000 + 1000)
// Should align with 10-minute intervals
expect(nextRunAt.getMinutes() % 10).toBe(0)
expect(nextRunAt.getUTCMinutes() % 10).toBe(0)
})

it('handles hourly schedules with timezone correctly', async () => {
Expand All @@ -598,8 +598,8 @@ describe('Schedule PUT API (Reactivate)', () => {

// Should be a future date at minute 15
expect(nextRunAt.getTime()).toBeGreaterThan(beforeCall)
expect(nextRunAt.getMinutes()).toBe(15)
expect(nextRunAt.getSeconds()).toBe(0)
expect(nextRunAt.getUTCMinutes()).toBe(15)
expect(nextRunAt.getUTCSeconds()).toBe(0)
})

it('handles custom cron expressions with complex patterns and timezone', async () => {
Expand Down
Loading
Loading