diff --git a/apps/sim/app/(landing)/components/mothership/components/iso-marks/iso-build-illustration.tsx b/apps/sim/app/(landing)/components/mothership/components/iso-marks/iso-build-illustration.tsx index 296f0a01318..00f921aaca9 100644 --- a/apps/sim/app/(landing)/components/mothership/components/iso-marks/iso-build-illustration.tsx +++ b/apps/sim/app/(landing)/components/mothership/components/iso-marks/iso-build-illustration.tsx @@ -5,7 +5,7 @@ import { ISO_FILL_LOW, ISO_FILL_MID, ISO_STROKE, -} from '@/app/(landing)/components/mothership/components/iso-marks/iso-illustration-style' +} from '@/components/iso/iso-illustration-style' export interface IsoBuildIllustrationProps { size?: number diff --git a/apps/sim/app/(landing)/components/mothership/components/iso-marks/iso-ingest-illustration.tsx b/apps/sim/app/(landing)/components/mothership/components/iso-marks/iso-ingest-illustration.tsx index 9411b4b3fa9..980bb792339 100644 --- a/apps/sim/app/(landing)/components/mothership/components/iso-marks/iso-ingest-illustration.tsx +++ b/apps/sim/app/(landing)/components/mothership/components/iso-marks/iso-ingest-illustration.tsx @@ -9,7 +9,7 @@ import { ISO_FILL_PULSE_LOW, ISO_FILL_PULSE_MID, ISO_STROKE, -} from '@/app/(landing)/components/mothership/components/iso-marks/iso-illustration-style' +} from '@/components/iso/iso-illustration-style' export interface IsoIngestIllustrationProps { size?: number diff --git a/apps/sim/app/(landing)/components/mothership/components/iso-marks/iso-integrate-illustration.tsx b/apps/sim/app/(landing)/components/mothership/components/iso-marks/iso-integrate-illustration.tsx index 955fca0f61e..4981feb35b0 100644 --- a/apps/sim/app/(landing)/components/mothership/components/iso-marks/iso-integrate-illustration.tsx +++ b/apps/sim/app/(landing)/components/mothership/components/iso-marks/iso-integrate-illustration.tsx @@ -5,7 +5,7 @@ import { ISO_FILL_LOW, ISO_FILL_MID, ISO_STROKE, -} from '@/app/(landing)/components/mothership/components/iso-marks/iso-illustration-style' +} from '@/components/iso/iso-illustration-style' export interface IsoIntegrateIllustrationProps { size?: number diff --git a/apps/sim/app/(landing)/components/mothership/components/iso-marks/iso-monitor-illustration.tsx b/apps/sim/app/(landing)/components/mothership/components/iso-marks/iso-monitor-illustration.tsx index dd7643d14d3..b70bab7a7b1 100644 --- a/apps/sim/app/(landing)/components/mothership/components/iso-marks/iso-monitor-illustration.tsx +++ b/apps/sim/app/(landing)/components/mothership/components/iso-marks/iso-monitor-illustration.tsx @@ -5,7 +5,7 @@ import { ISO_FILL_LOW, ISO_FILL_MID, ISO_STROKE, -} from '@/app/(landing)/components/mothership/components/iso-marks/iso-illustration-style' +} from '@/components/iso/iso-illustration-style' export interface IsoMonitorIllustrationProps { size?: number diff --git a/apps/sim/app/workspace/[workspaceId]/components/resource/components/resource-empty-state/docs-link.tsx b/apps/sim/app/workspace/[workspaceId]/components/resource/components/resource-empty-state/docs-link.tsx new file mode 100644 index 00000000000..f0d47606e7f --- /dev/null +++ b/apps/sim/app/workspace/[workspaceId]/components/resource/components/resource-empty-state/docs-link.tsx @@ -0,0 +1,21 @@ +import { ChipLink } from '@sim/emcn' +import { BookOpen } from '@sim/emcn/icons' + +interface EmptyStateDocsLinkProps { + href: string +} + +/** The docs chip every resource empty state carries, so the four stay identical. */ +export function EmptyStateDocsLink({ href }: EmptyStateDocsLinkProps) { + return ( + + Docs + + ) +} diff --git a/apps/sim/app/workspace/[workspaceId]/components/resource/components/resource-empty-state/files-empty-state.tsx b/apps/sim/app/workspace/[workspaceId]/components/resource/components/resource-empty-state/files-empty-state.tsx new file mode 100644 index 00000000000..09a5f802c6c --- /dev/null +++ b/apps/sim/app/workspace/[workspaceId]/components/resource/components/resource-empty-state/files-empty-state.tsx @@ -0,0 +1,103 @@ +import { Chip, cn } from '@sim/emcn' +import { Upload } from '@sim/emcn/icons' +import { EmptyState } from '@/components/empty-state/empty-state' +import { EmptyStateDocsLink } from '@/app/workspace/[workspaceId]/components/resource/components/resource-empty-state/docs-link' + +const FILES_DOCS_URL = 'https://docs.sim.ai/files' + +/** + * Hairline contours, matching the tables grid's 1px `--border-1` rules and the + * knowledge mark's thinned strokes — the graphics sit one nav item apart, so a + * heavier outline here would read as a different illustration system. + * + * Fills stay near the top of the surface ramp for the same reason: the border + * draws the folder and the fill only has to separate one layer from the next. A + * solid mid-grey body made this the heaviest thing on the page. + */ +const HAIRLINE = { + stroke: 'var(--border-1)', + strokeWidth: 1.1, + strokeLinejoin: 'round' as const, +} as const + +const FOLDER_BACK = [ + 'M 22 34', + 'H 66', + 'Q 72 34 75.5 38', + 'L 82.5 46', + 'Q 86 50 92 50', + 'H 178', + 'Q 188 50 188 60', + 'V 140', + 'Q 188 150 178 150', + 'H 22', + 'Q 12 150 12 140', + 'V 44', + 'Q 12 34 22 34', + 'Z', +].join(' ') + +/** + * Dissolves upward, so the folder rises out of the page while its front panel + * stays crisp where the copy begins. Running it the other way — the direction the + * logs feed fades — ate the panel's base and left the tab hanging. + */ +const FOLDER_FADE = + '[-webkit-mask-image:linear-gradient(to_top,#000_58%,transparent_100%)] [mask-image:linear-gradient(to_top,#000_58%,transparent_100%)]' + +/** + * A folder held open with sheets standing proud of its front panel. + * + * Depth is carried by the surface ramp rather than by shadow: the back panel is the + * darkest tier, the sheets the lightest, the front panel between them. Shadows + * would need separate light and dark recipes; the ramp inverts on its own. + * + * Every outer corner shares the same 10-unit radius so the silhouette reads as a + * single drawn shape — mixing radii makes the corners fight each other at this size. + */ +function FilesGraphic() { + return ( + + ) +} + +interface FilesEmptyStateProps { + /** Opens the file picker — the same action the header's upload chip runs. */ + onUpload: () => void + /** Mirrors the header chip's disabled state: no edit rights, or an upload in flight. */ + uploadDisabled?: boolean +} + +/** Empty state for the files list when the workspace has none. */ +export function FilesEmptyState({ onUpload, uploadDisabled = false }: FilesEmptyStateProps) { + return ( + } + title='Files' + description='Upload files to share them across your team and every agent.' + action={ + <> + + Upload + + + + } + /> + ) +} diff --git a/apps/sim/app/workspace/[workspaceId]/components/resource/components/resource-empty-state/index.ts b/apps/sim/app/workspace/[workspaceId]/components/resource/components/resource-empty-state/index.ts new file mode 100644 index 00000000000..7b34c26e6b5 --- /dev/null +++ b/apps/sim/app/workspace/[workspaceId]/components/resource/components/resource-empty-state/index.ts @@ -0,0 +1,4 @@ +export { FilesEmptyState } from '@/app/workspace/[workspaceId]/components/resource/components/resource-empty-state/files-empty-state' +export { KnowledgeEmptyState } from '@/app/workspace/[workspaceId]/components/resource/components/resource-empty-state/knowledge-empty-state' +export { LogsEmptyState } from '@/app/workspace/[workspaceId]/components/resource/components/resource-empty-state/logs-empty-state' +export { TablesEmptyState } from '@/app/workspace/[workspaceId]/components/resource/components/resource-empty-state/tables-empty-state' diff --git a/apps/sim/app/workspace/[workspaceId]/components/resource/components/resource-empty-state/knowledge-empty-state.tsx b/apps/sim/app/workspace/[workspaceId]/components/resource/components/resource-empty-state/knowledge-empty-state.tsx new file mode 100644 index 00000000000..dcfdc7a3664 --- /dev/null +++ b/apps/sim/app/workspace/[workspaceId]/components/resource/components/resource-empty-state/knowledge-empty-state.tsx @@ -0,0 +1,36 @@ +import { Chip } from '@sim/emcn' +import { Plus } from '@sim/emcn/icons' +import { EmptyState } from '@/components/empty-state/empty-state' +import { EmptyStateDocsLink } from '@/app/workspace/[workspaceId]/components/resource/components/resource-empty-state/docs-link' +import { KnowledgeIsoMark } from '@/app/workspace/[workspaceId]/components/resource/components/resource-empty-state/knowledge-iso' + +const KNOWLEDGE_DOCS_URL = 'https://docs.sim.ai/knowledgebase' + +interface KnowledgeEmptyStateProps { + /** Opens the create-base modal — the same action the header's primary chip runs. */ + onCreate: () => void + /** Mirrors the header chip's disabled state: no edit rights on the workspace. */ + createDisabled?: boolean +} + +/** Empty state for the knowledge bases list when the workspace has none. */ +export function KnowledgeEmptyState({ + onCreate, + createDisabled = false, +}: KnowledgeEmptyStateProps) { + return ( + } + title='Knowledge bases' + description='Upload documents to give your agents a memory they can search.' + action={ + <> + + New base + + + + } + /> + ) +} diff --git a/apps/sim/app/workspace/[workspaceId]/components/resource/components/resource-empty-state/knowledge-iso.tsx b/apps/sim/app/workspace/[workspaceId]/components/resource/components/resource-empty-state/knowledge-iso.tsx new file mode 100644 index 00000000000..37cd2b50a9f --- /dev/null +++ b/apps/sim/app/workspace/[workspaceId]/components/resource/components/resource-empty-state/knowledge-iso.tsx @@ -0,0 +1,231 @@ +import { cn } from '@sim/emcn' +import { + createIsoLineProps, + ISO_FILL_HIGH, + ISO_FILL_LOW, + ISO_FILL_MID, + ISO_FILL_PULSE_LOW, + ISO_STROKE as ISO_STROKE_BASE, +} from '@/components/iso/iso-illustration-style' + +const COS_30 = Math.cos(Math.PI / 6) + +type Point = readonly [number, number] + +/** Standard isometric projection: +x right-and-down, +y left-and-down, +z up. */ +function project(x: number, y: number, z: number): Point { + return [(x - y) * COS_30, (x + y) * 0.5 - z] +} + +function toPath(points: Point[]): string { + return `${points.map(([x, y], i) => `${i === 0 ? 'M' : 'L'} ${x.toFixed(2)} ${y.toFixed(2)}`).join(' ')} Z` +} + +interface Box { + x: number + y: number + z: number + w: number + d: number + h: number +} + +/** The three faces an isometric viewer can see, brightest on top. */ +function boxFaces(box: Box) { + const { x, y, z, w, d, h } = box + const x1 = x + w + const y1 = y + d + const z1 = z + h + return { + top: [project(x, y, z1), project(x1, y, z1), project(x1, y1, z1), project(x, y1, z1)], + right: [project(x1, y, z1), project(x1, y1, z1), project(x1, y1, z), project(x1, y, z)], + left: [project(x, y1, z1), project(x1, y1, z1), project(x1, y1, z), project(x, y1, z)], + } +} + +/** + * Thin along y rather than x, so the cover is the face at max y — the one the + * projection turns to the left. Stacking along y then walks the set left-and-down + * toward the viewer, so drawing in offset order paints back to front. + */ +const SLABS: Box[] = [0, 90, 180].map((offset) => ({ + x: 0, + y: offset, + z: 0, + w: 208, + d: 46, + h: 236, +})) + +/** + * Lighter and thinner than the landing marks draw them. + * + * Those marks are the focal art of their section; here the mark sits beside a + * ruled grid and a skeleton feed whose lines are 1px of `--border-1`. Carrying + * the landing's full-weight contour made the volumes read as ink next to those, + * so the shared stroke is mixed toward `--border-1` and thinned to land near a + * hairline once the mark is scaled to empty-state size. Only the width diverges + * from the shared recipe; the fills are imported so a change to the iso ramp + * reaches this mark too. + */ +const ISO_STROKE = `color-mix(in srgb, ${ISO_STROKE_BASE} 55%, var(--border-1))` +/** Darker than any outer face — the bore's wall turns away from the light. */ +const ISO_FILL_BORE = ISO_FILL_PULSE_LOW +const KNOWLEDGE_STROKE_WIDTH = 1.9 + +/** Matches the other three resource graphics, so the set centres as one collection. */ +const MARK_HEIGHT = 148 + +/** + * Maps flat artwork into the plane of the front volume's cover. + * + * The cover is the face at max y, spanned by the volume's width going across and + * its height going up. Those two edges are its basis vectors in projected space, + * and a matrix built from them lets a plain `` be authored in face + * coordinates — the projection skews it into the right ellipse. Local units are + * world units measured on the face, so a circle stays circular *on the cover* + * instead of being stretched by the face's aspect. + */ +const COVER = SLABS[SLABS.length - 1] + +const COVER_PLANE = (() => { + const [originX, originY] = project(COVER.x, COVER.y + COVER.d, COVER.z) + return `matrix(${COS_30.toFixed(4)} 0.5 0 1 ${originX.toFixed(3)} ${(originY - COVER.h).toFixed(3)})` +})() + +const BORE_RADIUS = 62 +const BORE_CX = COVER.w / 2 +const BORE_CY = COVER.h / 2 + +/** + * The far mouth of the bore, in the same cover-plane coordinates. + * + * Boring straight back through the volume is a world-space step of `-d` along y. + * Solving the cover-plane matrix for the local offset that produces that step + * gives `(+d, -d)` — so the far mouth sits up and left of the near one by exactly + * the volume's thickness, and the sliver of near-mouth it fails to cover is the + * wall you see down the hole. + */ +const FAR_CX = BORE_CX + COVER.d +const FAR_CY = BORE_CY - COVER.d + +const BORE_MASK_ID = 'knowledge-iso-bore-mask' +const BORE_CLIP_ID = 'knowledge-iso-bore-clip' + +const ALL_POINTS: Point[] = SLABS.flatMap((box) => Object.values(boxFaces(box)).flat()) + +/** `SLABS` is a module constant and the projection is pure, so every face path is fixed. */ +const SLAB_PATHS = SLABS.map((box) => { + const faces = boxFaces(box) + return { + key: `${box.x}-${box.y}`, + left: toPath(faces.left), + right: toPath(faces.right), + top: toPath(faces.top), + } +}) + +const PADDING = 14 +const MIN_X = Math.min(...ALL_POINTS.map(([x]) => x)) - PADDING +const MAX_X = Math.max(...ALL_POINTS.map(([x]) => x)) + PADDING +const MIN_Y = Math.min(...ALL_POINTS.map(([, y]) => y)) - PADDING +const MAX_Y = Math.max(...ALL_POINTS.map(([, y]) => y)) + PADDING +const VIEW_BOX = `${MIN_X.toFixed(2)} ${MIN_Y.toFixed(2)} ${(MAX_X - MIN_X).toFixed(2)} ${(MAX_Y - MIN_Y).toFixed(2)}` + +/** + * The tables grid's corner fade, run along the other diagonal. + * + * There it dissolves toward the bottom-right, because a grid keeps its meaning + * cropped. Here the set recedes up and to the right, and the front volume carries + * the bore — so the fade is anchored at the bottom-left and eats into the back of + * the set instead, reading as more volumes behind rather than dissolving the one + * detail worth looking at. + */ +const STACK_FADE = + '[-webkit-mask-image:linear-gradient(to_right,#000_56%,transparent_100%),linear-gradient(to_bottom,transparent_0%,#000_40%)] [mask-image:linear-gradient(to_right,#000_56%,transparent_100%),linear-gradient(to_bottom,transparent_0%,#000_40%)] [-webkit-mask-composite:source-in] [mask-composite:intersect]' + +/** Shared iso contour recipe at this mark's own weight; spread onto both paths and circles. */ +const LINE_PROPS = createIsoLineProps( + undefined, + ISO_STROKE, + KNOWLEDGE_STROKE_WIDTH +) + +/** + * Down the hole: the near mouth is floored with the wall tone, then the far mouth + * is painted over it in the cover tone of the volume standing behind — looking + * through a bore in the front volume lands on that volume's face, not on the page. + * Both are clipped to the near mouth so the bore never paints outside its opening. + */ +function BoreInterior() { + return ( + + + + + + ) +} + +/** + * Isometric knowledge-base mark on the landing page's iso-illustration recipe. + * + * Geometry is authored in a large unit space so the shared stroke constant lands + * as a hairline once the mark is scaled to empty-state size — as it does on the + * landing marks, which draw 3.2 into a ~526-unit viewBox. + */ +export function KnowledgeIsoMark() { + const width = MARK_HEIGHT * ((MAX_X - MIN_X) / (MAX_Y - MIN_Y)) + return ( + + ) +} diff --git a/apps/sim/app/workspace/[workspaceId]/components/resource/components/resource-empty-state/logs-empty-state.tsx b/apps/sim/app/workspace/[workspaceId]/components/resource/components/resource-empty-state/logs-empty-state.tsx new file mode 100644 index 00000000000..6ea0d5ba336 --- /dev/null +++ b/apps/sim/app/workspace/[workspaceId]/components/resource/components/resource-empty-state/logs-empty-state.tsx @@ -0,0 +1,76 @@ +import { cn } from '@sim/emcn' +import { EmptyState } from '@/components/empty-state/empty-state' +import { EmptyStateDocsLink } from '@/app/workspace/[workspaceId]/components/resource/components/resource-empty-state/docs-link' + +const LOGS_DOCS_URL = 'https://docs.sim.ai/logs-debugging' + +/** Skeleton ink — see the `INK` note in `tables-empty-state.tsx` for why not the surface ramp. */ +const INK = { + title: 'color-mix(in srgb, var(--text-secondary) 32%, transparent)', + detail: 'color-mix(in srgb, var(--text-secondary) 15%, transparent)', +} as const + +interface ActivityRow { + stamp: string + title: number + detail: number +} + +const ROWS: ActivityRow[] = [ + { stamp: 'Now', title: 72, detail: 112 }, + { stamp: '12 min ago', title: 62, detail: 124 }, + { stamp: '1h ago', title: 76, detail: 100 }, + { stamp: 'Jul 8', title: 56, detail: 108 }, +] + +/** + * Vertical falloff so the feed dissolves into the page instead of ending on a hard + * last row — the list is a repeating structure, so cropping it costs nothing. + */ +const FEED_FADE = + '[-webkit-mask-image:linear-gradient(to_bottom,#000_44%,transparent_100%)] [mask-image:linear-gradient(to_bottom,#000_44%,transparent_100%)]' + +/** Four rows, sized to the ~148px the other resource graphics occupy so the frame centres the set alike. */ +function LogsGraphic() { + return ( + + ) +} + +/** Empty state for the logs list when the workspace has no runs yet. */ +export function LogsEmptyState() { + return ( + } + title='Logs' + description='Every workflow execution lands here, traced block by block.' + action={} + /> + ) +} diff --git a/apps/sim/app/workspace/[workspaceId]/components/resource/components/resource-empty-state/tables-empty-state.tsx b/apps/sim/app/workspace/[workspaceId]/components/resource/components/resource-empty-state/tables-empty-state.tsx new file mode 100644 index 00000000000..5ca521d2e99 --- /dev/null +++ b/apps/sim/app/workspace/[workspaceId]/components/resource/components/resource-empty-state/tables-empty-state.tsx @@ -0,0 +1,128 @@ +import { Chip, cn } from '@sim/emcn' +import { Plus } from '@sim/emcn/icons' +import { EmptyState } from '@/components/empty-state/empty-state' +import { EmptyStateDocsLink } from '@/app/workspace/[workspaceId]/components/resource/components/resource-empty-state/docs-link' + +/** + * Neutral ink at two strengths. + * + * `--surface-4`/`--surface-5` are near-white in light mode (#f5f5f5/#f3f3f3), so + * skeleton geometry built on them dissolves against the page. Mixing + * `--text-secondary` into transparent gives a real mid-grey that inverts with the + * theme — the idiom the workflow editor's vignette uses for the one bar it needs + * you to actually see. + */ +const INK = { + header: 'color-mix(in srgb, var(--text-secondary) 30%, transparent)', + cell: 'color-mix(in srgb, var(--text-secondary) 15%, transparent)', + /** + * Mixed into `--bg` rather than `transparent`: a translucent ring lets the + * grid rules underneath show through its own stroke. Opaque, and darker than + * the ink it surrounds so it reads as chrome rather than more content. + */ + selection: 'color-mix(in srgb, var(--text-secondary) 46%, var(--bg))', +} as const + +/** + * Crisp at the top-left, dissolving through the bottom-right — the same + * two-gradient intersect the landing page's workflow vignette uses, so the grid + * blends into the page rather than sitting on it as a card. + */ +const CORNER_FADE = + '[-webkit-mask-image:linear-gradient(to_right,#000_62%,transparent_100%),linear-gradient(to_bottom,#000_56%,transparent_100%)] [mask-image:linear-gradient(to_right,#000_62%,transparent_100%),linear-gradient(to_bottom,#000_56%,transparent_100%)] [-webkit-mask-composite:source-in] [mask-composite:intersect]' + +const COLUMN_TEMPLATE = '88px 72px 72px 72px' + +const HEADER_WIDTHS = [36, 28, 26, 28] as const + +const CELL_WIDTHS = [ + [54, 34, 26, 38], + [44, 30, 34, 30], + [60, 38, 22, 40], + [40, 26, 30, 34], + [50, 32, 28, 36], +] as const + +/** + * The one cell held in an edit ring, placed in the top-left quadrant the corner + * fade leaves fully opaque — a selection dissolving mid-stroke would read as a + * rendering fault rather than a detail. + */ +const SELECTED_CELL = { row: 1, column: 0 } as const + +/** Skeleton grid with one cell held in an edit ring, running off two edges. */ +function TablesGraphic() { + return ( + + ) +} + +const TABLES_DOCS_URL = 'https://docs.sim.ai/tables' + +interface TablesEmptyStateProps { + /** Creates a table — the same action the header's primary chip runs. */ + onCreate: () => void + /** Mirrors the header chip's disabled state: no edit rights, or a create already in flight. */ + createDisabled?: boolean +} + +/** Empty state for the tables list when the workspace has none. */ +export function TablesEmptyState({ onCreate, createDisabled = false }: TablesEmptyStateProps) { + return ( + } + title='Tables' + description='Create a table to store structured data your agents can read and write.' + action={ + <> + + New table + + + + } + /> + ) +} diff --git a/apps/sim/app/workspace/[workspaceId]/components/resource/is-resource-list-empty.ts b/apps/sim/app/workspace/[workspaceId]/components/resource/is-resource-list-empty.ts new file mode 100644 index 00000000000..c9b8ece3273 --- /dev/null +++ b/apps/sim/app/workspace/[workspaceId]/components/resource/is-resource-list-empty.ts @@ -0,0 +1,65 @@ +interface ResourceListEmptyInput { + rowCount: number + /** The list query's first load. No rows yet means "not arrived", not "none exist". */ + isLoading: boolean + /** The query is serving the previous key's rows while the new key refetches. */ + isPlaceholderData: boolean + /** A failed load also leaves the list empty. */ + error: unknown + /** + * The search term the rows are actually filtered by — the debounced value, never the + * instant URL one. + */ + search: string + filterCount: number + /** The open folder, or `null` at the root. Omit for lists without folder navigation. */ + folderId?: string | null + /** + * Whether the folder tree has resolved. Folder rows share the list with resource rows, + * so a workspace holding only folders looks empty until they arrive. Omit for lists + * without folder navigation. + */ + foldersResolved?: boolean +} + +/** + * Whether a resource list holds nothing, as opposed to merely showing nothing. + * + * A zero-data graphic invites someone to create their first item, so it may only + * appear when that is the true answer. Every other way `rows` empties out is a + * different message and gets gated here: + * + * - A search or filter that matched nothing, or an empty subfolder — the copy would + * be wrong. The search must be the debounced value the rows are filtered by; + * reading the instant URL value flashes the graphic for one debounce window after + * a search is cleared. + * - The list still arriving. Server prefetches are allowed to seed nothing, and the + * files list deliberately seeds nothing above 300 rows — so without this the + * emptiest-looking screen is shown to the fullest workspaces. + * - The query serving the previous key's rows. Filters are part of the query key and + * every list keeps previous data, so `isLoading` is false across a filter change. + * - A failed load, which is not an invitation to create anything. + * - The folder tree still resolving, for the same reason as the rows themselves: a + * workspace whose only contents are folders reads as empty until they land. + */ +export function isResourceListEmpty({ + rowCount, + isLoading, + isPlaceholderData, + error, + search, + filterCount, + folderId = null, + foldersResolved = true, +}: ResourceListEmptyInput): boolean { + return ( + rowCount === 0 && + !isLoading && + !isPlaceholderData && + !error && + foldersResolved && + folderId === null && + !search.trim() && + filterCount === 0 + ) +} diff --git a/apps/sim/app/workspace/[workspaceId]/components/resource/resource.tsx b/apps/sim/app/workspace/[workspaceId]/components/resource/resource.tsx index 31ca47ae1bc..0742f9a93cc 100644 --- a/apps/sim/app/workspace/[workspaceId]/components/resource/resource.tsx +++ b/apps/sim/app/workspace/[workspaceId]/components/resource/resource.tsx @@ -225,6 +225,16 @@ interface ResourceTableProps { * chrome and positioning; it never alters the table's rendering. */ overlay?: ReactNode + /** + * Sanctioned empty slot. Rendered below the column headers when `rows` is + * empty, filling the otherwise blank scroll area. It never replaces the table + * region or the headers — the chrome guarantee holds — so a consumer can show + * a zero-data graphic without the list losing its structure. + * + * The table owns the growth box the slot is centred in, so any node centres — + * the slot does not have to carry its own `flex-1` to sit in the middle. + */ + emptyState?: ReactNode } /** @@ -263,10 +273,13 @@ const ResourceTable = memo(function ResourceTable({ isLoadingMore, pagination, overlay, + emptyState, }: ResourceTableProps) { const scrollRef = useRef(null) const loadMoreRef = useRef(null) + const showEmptyState = rows.length === 0 && Boolean(emptyState) + const [contextMenuRowId, setContextMenuRowId] = useState(null) const wrappedOnRowContextMenu = useCallback( @@ -358,7 +371,10 @@ const ResourceTable = memo(function ResourceTable({
+ {showEmptyState ?
{emptyState}
: null} {hasMore && (
{isLoadingMore && ( diff --git a/apps/sim/app/workspace/[workspaceId]/files/files.tsx b/apps/sim/app/workspace/[workspaceId]/files/files.tsx index f2fce25c67a..b655e6f2d25 100644 --- a/apps/sim/app/workspace/[workspaceId]/files/files.tsx +++ b/apps/sim/app/workspace/[workspaceId]/files/files.tsx @@ -85,6 +85,8 @@ import { useFolderRowDragDrop, } from '@/app/workspace/[workspaceId]/components/folders' import { ResourceActionBar } from '@/app/workspace/[workspaceId]/components/resource/components/action-bar' +import { FilesEmptyState } from '@/app/workspace/[workspaceId]/components/resource/components/resource-empty-state' +import { isResourceListEmpty } from '@/app/workspace/[workspaceId]/components/resource/is-resource-list-empty' import { DeleteConfirmModal } from '@/app/workspace/[workspaceId]/files/components/delete-confirm-modal' import { FileRowContextMenu } from '@/app/workspace/[workspaceId]/files/components/file-row-context-menu' import type { PreviewMode } from '@/app/workspace/[workspaceId]/files/components/file-viewer' @@ -263,8 +265,19 @@ export function Files() { } }, [permissionConfig.hideFilesTab, router, workspaceId]) - const { data: files = EMPTY_WORKSPACE_FILES, isLoading, error } = useWorkspaceFiles(workspaceId) - const { data: folders = EMPTY_WORKSPACE_FILE_FOLDERS } = useWorkspaceFileFolders(workspaceId) + const { + data: files = EMPTY_WORKSPACE_FILES, + isLoading, + isPlaceholderData, + error, + } = useWorkspaceFiles(workspaceId) + const { + data: folders = EMPTY_WORKSPACE_FILE_FOLDERS, + isSuccess: foldersLoaded, + isPlaceholderData: foldersArePlaceholder, + } = useWorkspaceFileFolders(workspaceId) + /** Matches `FolderNavigation.foldersResolved`, which the other resource pages read. */ + const foldersResolved = foldersLoaded && !foldersArePlaceholder const { data: members } = useWorkspaceMembersQuery(workspaceId) const pinnedFileIds = usePinnedIds(workspaceId, 'file') // Folders pin under their own resource type, so their pinned set is a separate query. @@ -1804,6 +1817,17 @@ export function Files() { return tags }, [typeFilter, sizeFilter, uploadedByFilter, membersById]) + const showEmptyState = isResourceListEmpty({ + rowCount: rows.length, + isLoading, + isPlaceholderData, + error, + search: debouncedSearchTerm, + filterCount: filterTags.length, + folderId: currentFolderId, + foldersResolved, + }) + if (fileIdFromRoute && !selectedFile && isLoading) { return ( @@ -1904,6 +1928,14 @@ export function Files() { filter={filterConfig} /> + ) : undefined + } columns={COLUMNS} rows={rows} selectable={selectableConfig} diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/knowledge.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/knowledge.tsx index 207f8b9599e..32fbed78a31 100644 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/knowledge.tsx +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/knowledge.tsx @@ -54,6 +54,8 @@ import { useFolderRowDragDrop, } from '@/app/workspace/[workspaceId]/components/folders' import { ResourceActionBar } from '@/app/workspace/[workspaceId]/components/resource/components/action-bar' +import { KnowledgeEmptyState } from '@/app/workspace/[workspaceId]/components/resource/components/resource-empty-state' +import { isResourceListEmpty } from '@/app/workspace/[workspaceId]/components/resource/is-resource-list-empty' import { BaseTagsModal } from '@/app/workspace/[workspaceId]/knowledge/[id]/components' import { CreateBaseModal, @@ -191,7 +193,7 @@ export function Knowledge() { } }, [permissionConfig.hideKnowledgeBaseTab, router, workspaceId]) - const { knowledgeBases, error } = useKnowledgeBasesList(workspaceId) + const { knowledgeBases, isLoading, isPlaceholderData, error } = useKnowledgeBasesList(workspaceId) const { data: members } = useWorkspaceMembersQuery(workspaceId) /** * Indexed once: `ownerCell` resolves a member per row, so passing the raw array makes the @@ -1338,6 +1340,17 @@ export function Knowledge() { return tags }, [connectorFilter, contentFilter, ownerFilter, members]) + const showEmptyState = isResourceListEmpty({ + rowCount: rows.length, + isLoading, + isPlaceholderData, + error, + search: debouncedSearchQuery, + filterCount: filterTags.length, + folderId: currentFolderId, + foldersResolved, + }) + return ( <> @@ -1355,6 +1368,11 @@ export function Knowledge() { filter={filterConfig} /> + ) : undefined + } columns={COLUMNS} rows={rows} selectable={canEdit ? selectableConfig : undefined} diff --git a/apps/sim/app/workspace/[workspaceId]/logs/logs.tsx b/apps/sim/app/workspace/[workspaceId]/logs/logs.tsx index 17cc80be70c..da75a34a8da 100644 --- a/apps/sim/app/workspace/[workspaceId]/logs/logs.tsx +++ b/apps/sim/app/workspace/[workspaceId]/logs/logs.tsx @@ -58,6 +58,8 @@ import type { SortConfig, } from '@/app/workspace/[workspaceId]/components' import { Resource, type ResourceTableHandle } from '@/app/workspace/[workspaceId]/components' +import { LogsEmptyState } from '@/app/workspace/[workspaceId]/components/resource/components/resource-empty-state' +import { isResourceListEmpty } from '@/app/workspace/[workspaceId]/components/resource/is-resource-list-empty' import { useLogFilters } from '@/app/workspace/[workspaceId]/logs/hooks/use-log-filters' import { useSearchState } from '@/app/workspace/[workspaceId]/logs/hooks/use-search-state' import { @@ -900,6 +902,16 @@ export default function Logs() { setTimeRange, ]) + /** Logs has no folder navigation, so the graphic means "nothing has ever run here". */ + const showEmptyState = isResourceListEmpty({ + rowCount: rows.length, + isLoading: logsQuery.isLoading, + isPlaceholderData: logsQuery.isPlaceholderData, + error: logsQuery.error, + search: debouncedSearchQuery, + filterCount: filterTags.length, + }) + const workflowsData = useMemo( () => Object.values(allWorkflows).map((w) => ({ @@ -1191,6 +1203,7 @@ export default function Logs() { ) : ( : undefined} virtualized columns={LOG_COLUMNS} rows={rows} diff --git a/apps/sim/app/workspace/[workspaceId]/tables/tables.tsx b/apps/sim/app/workspace/[workspaceId]/tables/tables.tsx index dc5e89a8742..a7c309907b5 100644 --- a/apps/sim/app/workspace/[workspaceId]/tables/tables.tsx +++ b/apps/sim/app/workspace/[workspaceId]/tables/tables.tsx @@ -53,6 +53,8 @@ import { useFolderRowDragDrop, } from '@/app/workspace/[workspaceId]/components/folders' import { ResourceActionBar } from '@/app/workspace/[workspaceId]/components/resource/components/action-bar' +import { TablesEmptyState } from '@/app/workspace/[workspaceId]/components/resource/components/resource-empty-state' +import { isResourceListEmpty } from '@/app/workspace/[workspaceId]/components/resource/is-resource-list-empty' import { useRegisterGlobalCommands } from '@/app/workspace/[workspaceId]/providers/global-commands-provider' import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/providers/workspace-permissions-provider' import { @@ -135,7 +137,12 @@ export function Tables() { // mutation service) invalidates the list so this view refetches without waiting for staleness. useWorkspaceTablesRoom(workspaceId) - const { data: tables = EMPTY_TABLES, error } = useTablesList(workspaceId) + const { + data: tables = EMPTY_TABLES, + isLoading, + isPlaceholderData, + error, + } = useTablesList(workspaceId) const { data: members } = useWorkspaceMembersQuery(workspaceId) const pinnedTableIds = usePinnedIds(workspaceId, 'table') // Folder pins live in their own `resourceType` namespace, so a page listing @@ -731,6 +738,17 @@ export function Tables() { return tags }, [rowCountFilter, ownerFilter, membersById, setRowCountFilter, setOwnerFilter]) + const showEmptyState = isResourceListEmpty({ + rowCount: rows.length, + isLoading, + isPlaceholderData, + error, + search: debouncedSearchTerm, + filterCount: filterTags.length, + folderId: currentFolderId, + foldersResolved, + }) + const handleContentContextMenu = useCallback( (e: React.MouseEvent) => { const target = e.target as HTMLElement @@ -1267,6 +1285,14 @@ export function Tables() { filter={filterConfig} /> + ) : undefined + } columns={COLUMNS} rows={rows} selectable={canEdit ? selectableConfig : undefined} diff --git a/apps/sim/components/empty-state/empty-state.tsx b/apps/sim/components/empty-state/empty-state.tsx new file mode 100644 index 00000000000..538f740714e --- /dev/null +++ b/apps/sim/components/empty-state/empty-state.tsx @@ -0,0 +1,27 @@ +import type { ReactNode } from 'react' + +interface EmptyStateProps { + title: string + description: string + graphic?: ReactNode + action?: ReactNode +} + +/** + * Shared platform empty-state frame with a visual, concise guidance, and an optional action. + * + * The frame owns the action row's layout so every empty state's chips sit identically — + * callers pass the chips themselves, not a wrapper. + */ +export function EmptyState({ title, description, graphic, action }: EmptyStateProps) { + return ( +
+ {graphic ?
{graphic}
: null} +

{title}

+

+ {description} +

+ {action ?
{action}
: null} +
+ ) +} diff --git a/apps/sim/app/(landing)/components/mothership/components/iso-marks/iso-illustration-style.ts b/apps/sim/components/iso/iso-illustration-style.ts similarity index 68% rename from apps/sim/app/(landing)/components/mothership/components/iso-marks/iso-illustration-style.ts rename to apps/sim/components/iso/iso-illustration-style.ts index bbb7a06986d..a97971a06b0 100644 --- a/apps/sim/app/(landing)/components/mothership/components/iso-marks/iso-illustration-style.ts +++ b/apps/sim/components/iso/iso-illustration-style.ts @@ -15,15 +15,23 @@ export const ISO_FILL_PROPS = { pointerEvents: 'none', } satisfies SVGProps -export function createIsoLineProps(className: string, stroke: string): SVGProps { +/** + * Contour props for an iso face. `strokeWidth` defaults to the shared weight; a mark + * drawn at a different scale passes its own so the recipe stays single-sourced. + */ +export function createIsoLineProps( + className: string | undefined, + stroke: string, + strokeWidth: number = ISO_LINE_STROKE_WIDTH +): SVGProps { return { - className, + ...(className ? { className } : {}), fill: 'none', pathLength: 1, pointerEvents: 'none', opacity: 1, stroke, - strokeWidth: ISO_LINE_STROKE_WIDTH, + strokeWidth, strokeLinecap: 'round', strokeLinejoin: 'round', } diff --git a/scripts/check-tool-registry-boundary.baseline.json b/scripts/check-tool-registry-boundary.baseline.json index ef913731a9c..a93c3db63a8 100644 --- a/scripts/check-tool-registry-boundary.baseline.json +++ b/scripts/check-tool-registry-boundary.baseline.json @@ -10,49 +10,49 @@ "gateways": {} }, "app/workspace/[workspaceId]/chat/[chatId]/page.tsx": { - "modules": 2919, + "modules": 2959, "gateways": { - "apps/sim/app/workspace/[workspaceId]/home/home.tsx": 1327, - "apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/mothership-view.tsx": 982, - "apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/index.ts": 847, - "apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/index.ts": 844, - "apps/sim/triggers/registry.ts": 446, - "apps/sim/blocks/registry.ts": 309, - "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx": 295, - "apps/sim/lib/auth/index.ts": 204 + "apps/sim/app/workspace/[workspaceId]/home/home.tsx": 1346, + "apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/mothership-view.tsx": 1000, + "apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/index.ts": 865, + "apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/index.ts": 862, + "apps/sim/triggers/registry.ts": 448, + "apps/sim/blocks/registry.ts": 312, + "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx": 304, + "apps/sim/lib/auth/index.ts": 208 } }, "app/workspace/[workspaceId]/files/[fileId]/page.tsx": { - "modules": 1931, + "modules": 1968, "gateways": { - "apps/sim/triggers/registry.ts": 446, - "apps/sim/blocks/registry.ts": 337, - "apps/sim/app/workspace/[workspaceId]/files/files.tsx": 280, - "apps/sim/lib/auth/index.ts": 210, - "apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/index.ts": 143, - "apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/file-viewer.tsx": 129, - "apps/sim/lib/api/contracts/index.ts": 107, - "apps/sim/lib/webhooks/providers/index.ts": 100 + "apps/sim/triggers/registry.ts": 448, + "apps/sim/blocks/registry.ts": 335, + "apps/sim/app/workspace/[workspaceId]/files/files.tsx": 293, + "apps/sim/lib/auth/index.ts": 214, + "apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/index.ts": 144, + "apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/file-viewer.tsx": 130, + "apps/sim/lib/api/contracts/index.ts": 108, + "apps/sim/lib/webhooks/providers/index.ts": 102 } }, "app/workspace/[workspaceId]/files/[fileId]/view/page.tsx": { - "modules": 59, + "modules": 58, "gateways": { - "apps/sim/app/workspace/[workspaceId]/files/[fileId]/view/file-viewer.tsx": 58, - "apps/sim/hooks/queries/workspace-files.ts": 55 + "apps/sim/app/workspace/[workspaceId]/files/[fileId]/view/file-viewer.tsx": 57, + "apps/sim/hooks/queries/workspace-files.ts": 54 } }, "app/workspace/[workspaceId]/files/page.tsx": { - "modules": 1931, + "modules": 1968, "gateways": { - "apps/sim/triggers/registry.ts": 446, - "apps/sim/blocks/registry.ts": 337, - "apps/sim/app/workspace/[workspaceId]/files/files.tsx": 282, - "apps/sim/lib/auth/index.ts": 210, - "apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/index.ts": 143, - "apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/file-viewer.tsx": 129, - "apps/sim/lib/api/contracts/index.ts": 107, - "apps/sim/lib/webhooks/providers/index.ts": 100 + "apps/sim/triggers/registry.ts": 448, + "apps/sim/blocks/registry.ts": 335, + "apps/sim/app/workspace/[workspaceId]/files/files.tsx": 295, + "apps/sim/lib/auth/index.ts": 214, + "apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/index.ts": 144, + "apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/file-viewer.tsx": 130, + "apps/sim/lib/api/contracts/index.ts": 108, + "apps/sim/lib/webhooks/providers/index.ts": 102 } }, "app/workspace/[workspaceId]/home/layout.tsx": { @@ -60,25 +60,25 @@ "gateways": {} }, "app/workspace/[workspaceId]/home/page.tsx": { - "modules": 2919, + "modules": 2959, "gateways": { - "apps/sim/app/workspace/[workspaceId]/home/home.tsx": 1327, - "apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/mothership-view.tsx": 982, - "apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/index.ts": 847, - "apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/index.ts": 844, - "apps/sim/triggers/registry.ts": 446, - "apps/sim/blocks/registry.ts": 309, - "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx": 295, - "apps/sim/lib/auth/index.ts": 204 + "apps/sim/app/workspace/[workspaceId]/home/home.tsx": 1346, + "apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/mothership-view.tsx": 1000, + "apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/index.ts": 865, + "apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/index.ts": 862, + "apps/sim/triggers/registry.ts": 448, + "apps/sim/blocks/registry.ts": 312, + "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx": 304, + "apps/sim/lib/auth/index.ts": 208 } }, "app/workspace/[workspaceId]/integrations/[block]/page.tsx": { - "modules": 1284, + "modules": 1300, "gateways": { - "apps/sim/app/workspace/[workspaceId]/integrations/[block]/integration-block-detail.tsx": 1259, - "apps/sim/blocks/registry.ts": 935, - "apps/sim/triggers/index.ts": 482, - "apps/sim/lib/api/contracts/index.ts": 127, + "apps/sim/app/workspace/[workspaceId]/integrations/[block]/integration-block-detail.tsx": 1273, + "apps/sim/blocks/registry.ts": 919, + "apps/sim/triggers/index.ts": 484, + "apps/sim/lib/api/contracts/index.ts": 128, "apps/sim/stores/workflows/registry/store.ts": 67, "apps/sim/hooks/queries/deployments.ts": 61, "apps/sim/lib/api/contracts/tools/index.ts": 60, @@ -86,11 +86,11 @@ } }, "app/workspace/[workspaceId]/integrations/connected/[credentialId]/page.tsx": { - "modules": 1266, + "modules": 1283, "gateways": { - "apps/sim/app/workspace/[workspaceId]/integrations/connected/[credentialId]/connected-credential-detail.tsx": 1265, - "apps/sim/triggers/registry.ts": 481, - "apps/sim/blocks/registry.ts": 339, + "apps/sim/app/workspace/[workspaceId]/integrations/connected/[credentialId]/connected-credential-detail.tsx": 1282, + "apps/sim/triggers/registry.ts": 483, + "apps/sim/blocks/registry.ts": 342, "apps/sim/lib/api/contracts/index.ts": 134, "apps/sim/stores/workflows/registry/store.ts": 64, "apps/sim/hooks/queries/deployments.ts": 61, @@ -99,12 +99,12 @@ } }, "app/workspace/[workspaceId]/integrations/page.tsx": { - "modules": 1269, + "modules": 1287, "gateways": { - "apps/sim/app/workspace/[workspaceId]/integrations/integrations.tsx": 993, - "apps/sim/blocks/registry.ts": 936, - "apps/sim/triggers/index.ts": 482, - "apps/sim/lib/api/contracts/index.ts": 129, + "apps/sim/app/workspace/[workspaceId]/integrations/integrations.tsx": 1007, + "apps/sim/blocks/registry.ts": 920, + "apps/sim/triggers/index.ts": 484, + "apps/sim/lib/api/contracts/index.ts": 130, "apps/sim/stores/workflows/registry/store.ts": 67, "apps/sim/hooks/queries/deployments.ts": 61, "apps/sim/lib/api/contracts/tools/index.ts": 60, @@ -112,68 +112,68 @@ } }, "app/workspace/[workspaceId]/knowledge/[id]/[documentId]/page.tsx": { - "modules": 1482, + "modules": 1501, "gateways": { - "apps/sim/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/document.tsx": 1203, - "apps/sim/triggers/registry.ts": 481, - "apps/sim/blocks/registry.ts": 330, - "apps/sim/blocks/registry-maps.ts": 327, - "apps/sim/lib/api/contracts/index.ts": 119, - "apps/sim/connectors/registry.ts": 62, + "apps/sim/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/document.tsx": 1218, + "apps/sim/triggers/registry.ts": 483, + "apps/sim/blocks/registry.ts": 338, + "apps/sim/blocks/registry-maps.ts": 335, + "apps/sim/lib/api/contracts/index.ts": 120, + "apps/sim/connectors/registry.ts": 61, "apps/sim/lib/api/contracts/tools/index.ts": 60, - "apps/sim/app/workspace/[workspaceId]/components/index.ts": 52 + "apps/sim/app/workspace/[workspaceId]/components/index.ts": 55 } }, "app/workspace/[workspaceId]/knowledge/[id]/page.tsx": { - "modules": 1483, + "modules": 1504, "gateways": { - "apps/sim/app/workspace/[workspaceId]/knowledge/[id]/base.tsx": 1204, - "apps/sim/triggers/registry.ts": 481, - "apps/sim/blocks/registry.ts": 330, - "apps/sim/blocks/registry-maps.ts": 327, - "apps/sim/lib/api/contracts/index.ts": 119, - "apps/sim/connectors/registry.ts": 62, + "apps/sim/app/workspace/[workspaceId]/knowledge/[id]/base.tsx": 1221, + "apps/sim/triggers/registry.ts": 483, + "apps/sim/blocks/registry.ts": 338, + "apps/sim/blocks/registry-maps.ts": 335, + "apps/sim/lib/api/contracts/index.ts": 120, + "apps/sim/connectors/registry.ts": 61, "apps/sim/lib/api/contracts/tools/index.ts": 60, - "apps/sim/app/workspace/[workspaceId]/components/index.ts": 52 + "apps/sim/app/workspace/[workspaceId]/components/index.ts": 55 } }, "app/workspace/[workspaceId]/knowledge/page.tsx": { - "modules": 2138, + "modules": 2177, "gateways": { - "apps/sim/triggers/registry.ts": 446, - "apps/sim/blocks/registry.ts": 325, + "apps/sim/triggers/registry.ts": 448, + "apps/sim/blocks/registry.ts": 333, "apps/sim/app/workspace/[workspaceId]/knowledge/prefetch.ts": 273, "apps/sim/lib/knowledge/application/knowledge-bases.ts": 218, - "apps/sim/app/workspace/[workspaceId]/knowledge/knowledge.tsx": 168, - "apps/sim/lib/auth/index.ts": 158, - "apps/sim/lib/knowledge/orchestration/index.ts": 141, - "apps/sim/lib/knowledge/orchestration/connectors.ts": 136 + "apps/sim/app/workspace/[workspaceId]/knowledge/knowledge.tsx": 182, + "apps/sim/lib/auth/index.ts": 163, + "apps/sim/lib/knowledge/orchestration/index.ts": 138, + "apps/sim/lib/knowledge/orchestration/connectors.ts": 133 } }, "app/workspace/[workspaceId]/layout.tsx": { - "modules": 1974, + "modules": 2003, "gateways": { - "apps/sim/triggers/registry.ts": 446, - "apps/sim/blocks/registry.ts": 324, - "apps/sim/lib/auth/index.ts": 272, - "apps/sim/app/workspace/[workspaceId]/components/workspace-chrome/index.ts": 270, - "apps/sim/app/workspace/[workspaceId]/w/components/sidebar/sidebar.tsx": 262, - "apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/index.ts": 165, - "apps/sim/lib/api/contracts/index.ts": 109, - "apps/sim/lib/webhooks/providers/index.ts": 100 + "apps/sim/triggers/registry.ts": 448, + "apps/sim/blocks/registry.ts": 332, + "apps/sim/app/workspace/[workspaceId]/components/workspace-chrome/index.ts": 279, + "apps/sim/lib/auth/index.ts": 278, + "apps/sim/app/workspace/[workspaceId]/w/components/sidebar/sidebar.tsx": 271, + "apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/index.ts": 176, + "apps/sim/lib/api/contracts/index.ts": 110, + "apps/sim/lib/webhooks/providers/index.ts": 102 } }, "app/workspace/[workspaceId]/logs/page.tsx": { - "modules": 1711, + "modules": 1737, "gateways": { - "apps/sim/app/workspace/[workspaceId]/logs/logs.tsx": 1434, - "apps/sim/triggers/registry.ts": 481, - "apps/sim/app/workspace/[workspaceId]/logs/components/index.ts": 419, - "apps/sim/app/workspace/[workspaceId]/logs/components/log-details/components/execution-snapshot/index.ts": 363, - "apps/sim/blocks/registry.ts": 326, - "apps/sim/app/workspace/[workspaceId]/w/components/preview/components/preview-editor/index.ts": 319, - "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/index.ts": 282, - "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block.tsx": 252 + "apps/sim/app/workspace/[workspaceId]/logs/logs.tsx": 1456, + "apps/sim/triggers/registry.ts": 483, + "apps/sim/app/workspace/[workspaceId]/logs/components/index.ts": 421, + "apps/sim/app/workspace/[workspaceId]/logs/components/log-details/components/execution-snapshot/index.ts": 365, + "apps/sim/blocks/registry.ts": 329, + "apps/sim/app/workspace/[workspaceId]/w/components/preview/components/preview-editor/index.ts": 321, + "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/index.ts": 286, + "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block.tsx": 255 } }, "app/workspace/[workspaceId]/page.tsx": { @@ -185,16 +185,16 @@ "gateways": {} }, "app/workspace/[workspaceId]/settings/[section]/page.tsx": { - "modules": 2003, + "modules": 2039, "gateways": { - "apps/sim/triggers/registry.ts": 446, - "apps/sim/app/workspace/[workspaceId]/settings/[section]/settings.tsx": 420, - "apps/sim/blocks/registry.ts": 325, - "apps/sim/lib/auth/index.ts": 282, - "apps/sim/lib/api/contracts/index.ts": 105, - "apps/sim/lib/webhooks/providers/index.ts": 100, + "apps/sim/triggers/registry.ts": 448, + "apps/sim/app/workspace/[workspaceId]/settings/[section]/settings.tsx": 434, + "apps/sim/blocks/registry.ts": 328, + "apps/sim/lib/auth/index.ts": 288, + "apps/sim/lib/api/contracts/index.ts": 107, + "apps/sim/lib/webhooks/providers/index.ts": 102, "apps/sim/lib/api/contracts/tools/index.ts": 59, - "apps/sim/lib/workflows/lifecycle.ts": 48 + "apps/sim/lib/uploads/utils/file-utils.server.ts": 42 } }, "app/workspace/[workspaceId]/settings/billing/credit-usage/layout.tsx": { @@ -202,16 +202,16 @@ "gateways": {} }, "app/workspace/[workspaceId]/settings/billing/credit-usage/page.tsx": { - "modules": 1588, + "modules": 1610, "gateways": { - "apps/sim/lib/auth/index.ts": 1455, - "apps/sim/triggers/index.ts": 447, - "apps/sim/blocks/registry.ts": 339, - "apps/sim/blocks/registry-maps.ts": 336, - "apps/sim/lib/api/contracts/index.ts": 122, - "apps/sim/lib/webhooks/providers/index.ts": 100, - "apps/sim/stores/workflows/registry/store.ts": 64, - "apps/sim/lib/api/contracts/tools/index.ts": 60 + "apps/sim/lib/auth/index.ts": 1475, + "apps/sim/triggers/index.ts": 449, + "apps/sim/blocks/registry.ts": 342, + "apps/sim/blocks/registry-maps.ts": 339, + "apps/sim/lib/api/contracts/index.ts": 123, + "apps/sim/lib/webhooks/providers/index.ts": 102, + "apps/sim/stores/workflows/registry/store.ts": 68, + "apps/sim/hooks/queries/deployments.ts": 62 } }, "app/workspace/[workspaceId]/settings/layout.tsx": { @@ -223,159 +223,159 @@ "gateways": {} }, "app/workspace/[workspaceId]/settings/secrets/[credentialId]/page.tsx": { - "modules": 1297, + "modules": 1317, "gateways": { - "apps/sim/app/workspace/[workspaceId]/settings/secrets/[credentialId]/secret-detail.tsx": 1296, - "apps/sim/app/workspace/[workspaceId]/components/credential-detail/index.ts": 1005, - "apps/sim/components/permissions/index.ts": 992, - "apps/sim/components/permissions/add-people-modal.tsx": 983, - "apps/sim/app/workspace/[workspaceId]/providers/workspace-permissions-provider.tsx": 981, - "apps/sim/triggers/registry.ts": 481, - "apps/sim/blocks/registry.ts": 343, - "apps/sim/blocks/registry-maps.ts": 340 + "apps/sim/triggers/registry.ts": 483, + "apps/sim/blocks/registry.ts": 344, + "apps/sim/blocks/registry-maps.ts": 341, + "apps/sim/lib/api/contracts/index.ts": 135, + "apps/sim/stores/workflows/registry/store.ts": 64, + "apps/sim/app/workspace/[workspaceId]/settings/secrets/[credentialId]/secret-detail.tsx": 63, + "apps/sim/hooks/queries/deployments.ts": 61, + "apps/sim/lib/api/contracts/tools/index.ts": 60 } }, "app/workspace/[workspaceId]/skills/[skillId]/page.tsx": { - "modules": 1388, + "modules": 1402, "gateways": { - "apps/sim/app/workspace/[workspaceId]/skills/[skillId]/skill-detail.tsx": 1387, - "apps/sim/triggers/registry.ts": 481, - "apps/sim/blocks/registry.ts": 342, - "apps/sim/blocks/registry-maps.ts": 340, - "apps/sim/lib/api/contracts/index.ts": 125, + "apps/sim/app/workspace/[workspaceId]/skills/[skillId]/skill-detail.tsx": 1401, + "apps/sim/triggers/registry.ts": 483, + "apps/sim/blocks/registry.ts": 343, + "apps/sim/blocks/registry-maps.ts": 341, + "apps/sim/lib/api/contracts/index.ts": 126, "apps/sim/app/workspace/[workspaceId]/skills/components/skill-fields/index.ts": 89, "apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/rich-markdown-field.tsx": 86, "apps/sim/lib/api/contracts/tools/index.ts": 60 } }, "app/workspace/[workspaceId]/skills/new/page.tsx": { - "modules": 1386, + "modules": 1400, "gateways": { - "apps/sim/app/workspace/[workspaceId]/skills/new/skill-create.tsx": 1385, - "apps/sim/triggers/registry.ts": 481, - "apps/sim/blocks/registry.ts": 342, - "apps/sim/blocks/registry-maps.ts": 340, - "apps/sim/lib/api/contracts/index.ts": 125, + "apps/sim/app/workspace/[workspaceId]/skills/new/skill-create.tsx": 1399, + "apps/sim/triggers/registry.ts": 483, + "apps/sim/blocks/registry.ts": 343, + "apps/sim/blocks/registry-maps.ts": 341, + "apps/sim/lib/api/contracts/index.ts": 126, "apps/sim/app/workspace/[workspaceId]/skills/components/skill-fields/index.ts": 89, "apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/rich-markdown-field.tsx": 86, "apps/sim/lib/api/contracts/tools/index.ts": 60 } }, "app/workspace/[workspaceId]/skills/page.tsx": { - "modules": 1250, + "modules": 1268, "gateways": { - "apps/sim/app/workspace/[workspaceId]/skills/skills.tsx": 974, - "apps/sim/app/workspace/[workspaceId]/integrations/components/showcase-with-explore/index.ts": 962, - "apps/sim/blocks/registry.ts": 950, - "apps/sim/blocks/registry-maps.ts": 948, - "apps/sim/triggers/index.ts": 482, - "apps/sim/lib/api/contracts/index.ts": 134, - "apps/sim/stores/workflows/registry/store.ts": 68, - "apps/sim/hooks/queries/deployments.ts": 62 + "apps/sim/app/workspace/[workspaceId]/skills/skills.tsx": 988, + "apps/sim/app/workspace/[workspaceId]/integrations/components/showcase-with-explore/index.ts": 976, + "apps/sim/blocks/registry.ts": 964, + "apps/sim/blocks/registry-maps.ts": 962, + "apps/sim/triggers/index.ts": 484, + "apps/sim/lib/api/contracts/index.ts": 135, + "apps/sim/stores/workflows/registry/store.ts": 74, + "apps/sim/hooks/queries/deployments.ts": 68 } }, "app/workspace/[workspaceId]/tables/[tableId]/page.tsx": { - "modules": 2204, + "modules": 2236, "gateways": { - "apps/sim/app/workspace/[workspaceId]/tables/[tableId]/table.tsx": 577, - "apps/sim/triggers/registry.ts": 446, - "apps/sim/app/workspace/[workspaceId]/w/components/preview/index.ts": 331, - "apps/sim/blocks/registry.ts": 309, - "apps/sim/lib/auth/index.ts": 303, - "apps/sim/app/workspace/[workspaceId]/w/components/preview/components/preview-editor/index.ts": 289, - "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/index.ts": 261, - "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block.tsx": 232 + "apps/sim/app/workspace/[workspaceId]/tables/[tableId]/table.tsx": 584, + "apps/sim/triggers/registry.ts": 448, + "apps/sim/app/workspace/[workspaceId]/w/components/preview/index.ts": 335, + "apps/sim/blocks/registry.ts": 312, + "apps/sim/lib/auth/index.ts": 309, + "apps/sim/app/workspace/[workspaceId]/w/components/preview/components/preview-editor/index.ts": 293, + "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/index.ts": 265, + "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block.tsx": 235 } }, "app/workspace/[workspaceId]/tables/page.tsx": { - "modules": 1788, + "modules": 1827, "gateways": { - "apps/sim/triggers/registry.ts": 446, - "apps/sim/blocks/registry.ts": 336, - "apps/sim/lib/auth/index.ts": 293, - "apps/sim/app/workspace/[workspaceId]/tables/tables.tsx": 126, - "apps/sim/lib/api/contracts/index.ts": 111, - "apps/sim/lib/webhooks/providers/index.ts": 100, + "apps/sim/triggers/registry.ts": 448, + "apps/sim/blocks/registry.ts": 334, + "apps/sim/lib/auth/index.ts": 299, + "apps/sim/app/workspace/[workspaceId]/tables/tables.tsx": 140, + "apps/sim/lib/api/contracts/index.ts": 112, + "apps/sim/lib/webhooks/providers/index.ts": 102, "apps/sim/lib/api/contracts/tools/index.ts": 60, - "apps/sim/app/workspace/[workspaceId]/components/index.ts": 50 + "apps/sim/app/workspace/[workspaceId]/components/index.ts": 53 } }, "app/workspace/[workspaceId]/upgrade/page.tsx": { - "modules": 267, + "modules": 268, "gateways": { - "apps/sim/app/workspace/[workspaceId]/upgrade/upgrade.tsx": 260, - "apps/sim/app/workspace/[workspaceId]/upgrade/hooks/index.ts": 213, - "apps/sim/lib/billing/client/upgrade.ts": 205, - "apps/sim/hooks/queries/organization.ts": 201, - "apps/sim/hooks/queries/workspace.ts": 193, - "apps/sim/lib/api/contracts/index.ts": 191, + "apps/sim/app/workspace/[workspaceId]/upgrade/upgrade.tsx": 261, + "apps/sim/app/workspace/[workspaceId]/upgrade/hooks/index.ts": 214, + "apps/sim/lib/billing/client/upgrade.ts": 206, + "apps/sim/hooks/queries/organization.ts": 202, + "apps/sim/hooks/queries/workspace.ts": 194, + "apps/sim/lib/api/contracts/index.ts": 192, "apps/sim/lib/api/contracts/tools/index.ts": 61, "apps/sim/lib/api/contracts/v1/index.ts": 38 } }, "app/workspace/[workspaceId]/w/[workflowId]/layout.tsx": { - "modules": 2160, + "modules": 2187, "gateways": { - "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/error/index.tsx": 2159, - "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/index.ts": 540, - "apps/sim/triggers/registry.ts": 481, - "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/index.ts": 458, - "apps/sim/blocks/registry.ts": 326, - "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/index.ts": 285, - "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/index.ts": 141, - "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block.tsx": 134 + "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/error/index.tsx": 2186, + "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/index.ts": 545, + "apps/sim/triggers/registry.ts": 483, + "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/index.ts": 462, + "apps/sim/blocks/registry.ts": 329, + "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/index.ts": 289, + "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/index.ts": 145, + "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block.tsx": 138 } }, "app/workspace/[workspaceId]/w/[workflowId]/page.tsx": { - "modules": 2187, + "modules": 2214, "gateways": { - "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx": 2186, - "apps/sim/triggers/registry.ts": 481, - "apps/sim/blocks/registry.ts": 326, - "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/index.ts": 305, - "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/index.ts": 267, - "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/index.ts": 224, - "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/index.ts": 140, - "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block.tsx": 133 + "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx": 2213, + "apps/sim/triggers/registry.ts": 483, + "apps/sim/blocks/registry.ts": 329, + "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/index.ts": 308, + "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/index.ts": 270, + "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/index.ts": 227, + "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/index.ts": 143, + "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block.tsx": 136 } }, "app/workspace/[workspaceId]/w/page.tsx": { - "modules": 2160, + "modules": 2187, "gateways": { - "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/index.ts": 904, - "apps/sim/triggers/registry.ts": 481, - "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/index.ts": 458, - "apps/sim/blocks/registry.ts": 326, - "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/index.ts": 285, - "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/index.ts": 141, - "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block.tsx": 134, - "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/error/index.tsx": 129 + "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/index.ts": 922, + "apps/sim/triggers/registry.ts": 483, + "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/index.ts": 462, + "apps/sim/blocks/registry.ts": 329, + "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/index.ts": 289, + "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/index.ts": 145, + "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/error/index.tsx": 139, + "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block.tsx": 138 } }, "app/workspace/layout.tsx": { - "modules": 1208, + "modules": 1219, "gateways": { - "apps/sim/app/workspace/providers/socket-provider.tsx": 1198, - "apps/sim/triggers/registry.ts": 481, - "apps/sim/blocks/registry.ts": 343, - "apps/sim/blocks/registry-maps.ts": 340, - "apps/sim/lib/api/contracts/index.ts": 138, - "apps/sim/stores/workflows/registry/store.ts": 65, - "apps/sim/hooks/queries/deployments.ts": 62, - "apps/sim/lib/api/contracts/tools/index.ts": 60 + "apps/sim/app/workspace/providers/socket-provider.tsx": 1209, + "apps/sim/triggers/registry.ts": 483, + "apps/sim/blocks/registry.ts": 344, + "apps/sim/blocks/registry-maps.ts": 341, + "apps/sim/lib/api/contracts/index.ts": 139, + "apps/sim/stores/workflows/registry/store.ts": 67, + "apps/sim/hooks/queries/deployments.ts": 64, + "apps/sim/lib/workflows/comparison/compare.ts": 61 } }, "app/workspace/page.tsx": { - "modules": 1202, + "modules": 1213, "gateways": { - "apps/sim/lib/auth/stale-session-recovery.ts": 971, - "apps/sim/triggers/index.ts": 482, - "apps/sim/blocks/registry.ts": 343, - "apps/sim/blocks/registry-maps.ts": 340, - "apps/sim/lib/api/contracts/index.ts": 137, - "apps/sim/stores/workflows/registry/store.ts": 64, - "apps/sim/lib/api/contracts/tools/index.ts": 60, - "apps/sim/hooks/queries/deployments.ts": 58 + "apps/sim/lib/auth/stale-session-recovery.ts": 981, + "apps/sim/triggers/index.ts": 484, + "apps/sim/blocks/registry.ts": 344, + "apps/sim/blocks/registry-maps.ts": 341, + "apps/sim/lib/api/contracts/index.ts": 138, + "apps/sim/stores/workflows/registry/store.ts": 66, + "apps/sim/hooks/queries/deployments.ts": 60, + "apps/sim/lib/api/contracts/tools/index.ts": 60 } } }