From f9d201a7ff013df30ce23302266f0828d9656db8 Mon Sep 17 00:00:00 2001 From: Emir Karabeg Date: Sun, 19 Jul 2026 15:45:39 -0700 Subject: [PATCH 01/17] interfaces --- .claude/rules/canon.md | 50 + .claude/rules/sim-resource-views.md | 134 ++ .cursor/rules/sim-resource-views.mdc | 99 ++ CLAUDE.md | 17 +- apps/sim/AGENTS.md | 8 + apps/sim/app/(auth)/components/auth-shell.tsx | 19 +- .../(interfaces)/chat/[identifier]/chat.tsx | 4 +- .../chat/components/header/header.tsx | 86 +- .../chat/components/input/input.tsx | 103 +- apps/sim/app/(interfaces)/chat/constants.ts | 3 + .../chat/hooks/use-chat-streaming.ts | 104 +- .../(interfaces)/chat/utils/attachments.ts | 47 + apps/sim/app/(landing)/components/index.ts | 1 + .../components/logo-shell/logo-shell.tsx | 25 +- .../(landing)/components/navbar/navbar.tsx | 138 +- .../components/share-button/share-button.tsx | 7 +- .../components/share-link-button/index.ts | 1 + .../share-link-button/share-link-button.tsx | 71 + .../app/_shell/providers/theme-provider.tsx | 1 + apps/sim/app/api/chat/[identifier]/route.ts | 42 +- .../public/[token]/content/route.test.ts | 52 +- .../api/files/public/[token]/content/route.ts | 90 +- .../files/public/[token]/inline/route.test.ts | 65 +- .../api/files/public/[token]/inline/route.ts | 16 +- .../api/files/public/[token]/route.test.ts | 22 +- .../sim/app/api/files/public/[token]/route.ts | 10 +- .../app/api/files/serve/[...path]/route.ts | 81 +- apps/sim/app/api/files/utils.test.ts | 122 +- apps/sim/app/api/files/utils.ts | 101 +- .../modules/[moduleId]/submit/route.test.ts | 390 +++++ .../modules/[moduleId]/submit/route.ts | 200 +++ .../[interfaceId]/restore/route.test.ts | 267 ++++ .../interfaces/[interfaceId]/restore/route.ts | 65 + .../interfaces/[interfaceId]/route.test.ts | 533 +++++++ .../app/api/interfaces/[interfaceId]/route.ts | 220 +++ .../[interfaceId]/share/route.test.ts | 340 +++++ .../interfaces/[interfaceId]/share/route.ts | 166 +++ .../modules/[moduleId]/chat/route.test.ts | 365 +++++ .../[token]/modules/[moduleId]/chat/route.ts | 223 +++ .../[moduleId]/chat/stream-dialect.test.ts | 506 +++++++ .../[moduleId]/file/content/route.test.ts | 304 ++++ .../modules/[moduleId]/file/content/route.ts | 186 +++ .../[moduleId]/file/inline/route.test.ts | 252 ++++ .../modules/[moduleId]/file/inline/route.ts | 136 ++ .../modules/[moduleId]/submit/route.test.ts | 322 ++++ .../modules/[moduleId]/submit/route.ts | 202 +++ .../[moduleId]/table/rows/route.test.ts | 306 ++++ .../modules/[moduleId]/table/rows/route.ts | 144 ++ .../[token]/modules/[moduleId]/utils.ts | 59 + .../public/[token]/otp/route.test.ts | 222 +++ .../interfaces/public/[token]/otp/route.ts | 216 +++ .../interfaces/public/[token]/route.test.ts | 164 ++ .../api/interfaces/public/[token]/route.ts | 88 ++ .../interfaces/public/[token]/sso/route.ts | 76 + apps/sim/app/api/interfaces/route.test.ts | 268 ++++ apps/sim/app/api/interfaces/route.ts | 109 ++ apps/sim/app/api/interfaces/utils.ts | 121 ++ apps/sim/app/api/logs/triggers/route.ts | 5 +- apps/sim/app/api/tools/file/manage/route.ts | 9 +- .../app/api/workflows/[id]/execute/route.ts | 41 +- .../[id]/files/[fileId]/share/route.test.ts | 40 +- .../[id]/files/[fileId]/share/route.ts | 7 +- apps/sim/app/f/[token]/opengraph-image.tsx | 2 +- apps/sim/app/f/[token]/page.tsx | 56 +- apps/sim/app/f/[token]/public-file-view.tsx | 143 +- apps/sim/app/f/[token]/utils.ts | 9 - apps/sim/app/i/[token]/components/index.ts | 1 + .../components/public-interface-view/index.ts | 1 + .../public-interface-view.tsx | 72 + apps/sim/app/i/[token]/opengraph-image.tsx | 37 + apps/sim/app/i/[token]/page.tsx | 245 +++ .../[workspaceId]/components/index.ts | 8 +- .../message-actions/message-actions.tsx | 8 +- .../resource/components/owner-cell/index.ts | 2 +- .../components/owner-cell/owner-cell.tsx | 22 +- .../resource/use-background-context-menu.ts | 31 + .../components/share-modal/index.ts | 1 + .../components/share-modal/share-modal.tsx | 224 +++ .../files/[fileId]/view/file-viewer.tsx | 34 - .../[fileId]/view/fullscreen-file-view.tsx | 33 + .../files/[fileId]/view/page.tsx | 4 +- .../file-row-context-menu.tsx | 76 +- .../file-viewer/editor-context-menu.tsx | 103 -- .../components/file-viewer/file-viewer.tsx | 421 ------ .../files/components/file-viewer/index.ts | 10 - .../rich-markdown-editor/image.test.ts | 42 - .../mention/mention-chip.test.tsx | 81 - .../mention/mention-chip.tsx | 69 - .../mention/sim-link.test.ts | 22 - .../rich-markdown-editor/mention/sim-link.ts | 38 - .../files-list-context-menu.tsx | 20 +- .../files/components/share-modal/index.ts | 1 - .../components/share-modal/share-modal.tsx | 271 ---- .../workspace/[workspaceId]/files/files.tsx | 123 +- .../[workspaceId]/home/components/index.ts | 2 - .../components/chat-content/index.ts | 1 - .../message-content/components/index.ts | 3 +- .../components/special-tags/index.ts | 1 - .../components/special-tags/special-tags.tsx | 551 +------ .../message-content/message-content.tsx | 5 +- .../home/components/message-content/utils.ts | 4 +- .../mothership-chat/mothership-chat.tsx | 31 +- .../add-resource-dropdown.tsx | 11 + .../resource-content/resource-content.tsx | 363 +++-- .../resource-registry/resource-registry.tsx | 15 + .../resource-tabs/resource-tabs.tsx | 2 +- .../mothership-view/mothership-view.tsx | 8 +- .../queued-messages/queued-messages.tsx | 2 +- .../suggested-actions/suggested-actions.tsx | 36 +- .../components/chip-clipboard-codec.ts | 3 + .../user-input/components/constants.ts | 1 + .../prompt-editor/prompt-editor.tsx | 2 +- .../prompt-editor/use-prompt-editor.test.tsx | 1 + .../app/workspace/[workspaceId]/home/home.tsx | 2 +- .../[workspaceId]/home/hooks/use-chat.ts | 2 + .../app/workspace/[workspaceId]/home/types.ts | 36 +- .../components/inspector-field/index.ts | 2 + .../inspector-field/inspector-field.tsx | 127 ++ .../chat-module-fields/chat-module-fields.tsx | 219 +++ .../components/chat-module-fields/index.ts | 1 + .../form-field-row/form-field-row.test.tsx | 77 + .../form-field-row/form-field-row.tsx | 373 +++++ .../components/form-field-row/index.ts | 2 + .../form-module-fields/form-module-fields.tsx | 193 +++ .../components/form-module-fields/index.ts | 2 + .../components/resource-picker-field/index.ts | 5 + .../resource-picker-field.test.tsx | 69 + .../resource-picker-field.tsx | 80 + .../components/table-module-fields/index.ts | 1 + .../table-module-fields.tsx | 42 + .../components/module-inspector/index.ts | 1 + .../module-inspector/module-inspector.tsx | 262 ++++ .../interfaces/[interfaceId]/error.tsx | 30 + .../hooks/use-interface-editor-state.ts | 86 ++ .../hooks/use-interface-layout.test.tsx | 479 ++++++ .../hooks/use-interface-layout.ts | 262 ++++ .../interfaces/[interfaceId]/interface.tsx | 231 +++ .../interfaces/[interfaceId]/loading.tsx | 29 + .../interfaces/[interfaceId]/page.tsx | 31 + .../interfaces/[interfaceId]/search-params.ts | 28 + .../interfaces/components/index.ts | 2 + .../interface-context-menu/index.ts | 1 + .../interface-context-menu.tsx | 58 + .../interfaces-list-context-menu/index.ts | 1 + .../interfaces-list-context-menu.tsx | 33 + .../[workspaceId]/interfaces/error.tsx | 15 + .../[workspaceId]/interfaces/interfaces.tsx | 477 ++++++ .../[workspaceId]/interfaces/loading.tsx | 33 + .../[workspaceId]/interfaces/page.tsx | 36 + .../[workspaceId]/interfaces/prefetch.ts | 29 + .../[workspaceId]/interfaces/search-params.ts | 46 + .../chunk-context-menu/chunk-context-menu.tsx | 136 +- .../document-context-menu.tsx | 148 +- .../knowledge-base-context-menu.tsx | 116 +- .../knowledge-list-context-menu.tsx | 40 +- .../[workspaceId]/knowledge/knowledge.tsx | 37 +- .../log-row-context-menu.tsx | 123 +- .../app/workspace/[workspaceId]/logs/utils.ts | 1 + .../scheduled-tasks/scheduled-tasks.tsx | 16 +- .../recently-deleted/recently-deleted.tsx | 25 + .../recently-deleted/search-params.ts | 1 + .../components/skill-modal/skill-modal.tsx | 2 +- .../components/context-menu/context-menu.tsx | 126 +- .../new-column-dropdown.tsx | 14 +- .../table-grid/cells/sim-resource-cell.tsx | 2 +- .../table-context-menu/table-context-menu.tsx | 119 +- .../tables-list-context-menu.tsx | 58 +- .../[workspaceId]/tables/prefetch.ts | 4 +- .../workspace/[workspaceId]/tables/tables.tsx | 43 +- .../components/chat-message/chat-message.tsx | 2 +- .../deploy-modal/components/chat/chat.tsx | 6 +- .../components/version-description-modal.tsx | 2 +- .../search-modal/components/index.ts | 1 + .../components/search-groups/index.ts | 1 + .../search-groups/search-groups.tsx | 3 +- .../components/search-modal/search-modal.tsx | 149 +- .../sidebar/components/search-modal/utils.ts | 1 + .../w/components/sidebar/sidebar.tsx | 20 + apps/sim/blocks/blocks/start_trigger.ts | 2 +- apps/sim/components/action-row/action-row.tsx | 43 + apps/sim/components/action-row/index.ts | 1 + .../anchored-context-menu.tsx | 55 + .../components/anchored-context-menu/index.ts | 1 + .../chat}/chat-content/chat-content.test.ts | 2 +- .../chat}/chat-content/chat-content.tsx | 70 +- .../chat}/chat-content/chat-sanitize.ts | 0 .../chat}/chat-content/external-link.tsx | 0 .../sim/components/chat/chat-content/index.ts | 2 + .../chat-context-kind-registry.tsx | 7 +- .../chat}/chat-context-kind-registry/index.ts | 0 .../chat-copy-button/chat-copy-button.tsx | 65 + .../components/chat/chat-copy-button/index.ts | 1 + .../chat-message-attachments.tsx | 2 +- .../chat}/chat-message-attachments/index.ts | 0 .../context-mention-icon.tsx | 4 +- .../chat}/context-mention-icon/index.ts | 0 apps/sim/components/chat/index.ts | 16 + .../chat/pending-tag-indicator/index.ts | 1 + .../pending-tag-indicator.tsx | 18 + .../sim/components/chat/special-tags/parse.ts | 482 ++++++ apps/sim/components/chat/turn-layout.ts | 50 + apps/sim/components/chat/types.ts | 39 + .../chat}/user-message-content/index.ts | 0 .../user-message-content.tsx | 4 +- apps/sim/components/public-share/index.ts | 5 + .../public-share-auth-shell/index.ts | 1 + .../public-share-auth-shell.tsx} | 8 +- .../public-share-email-auth/index.ts | 1 + .../public-share-email-auth.tsx} | 56 +- .../public-share-password-auth/index.ts | 1 + .../public-share-password-auth.tsx} | 41 +- .../public-share-sso-auth/index.ts | 1 + .../public-share-sso-auth.tsx} | 65 +- apps/sim/components/public-share/types.ts | 12 + .../resources/MIGRATING-RESOURCES.md | 179 +++ .../csv-table-preview}/csv-table-preview.tsx | 23 +- .../components/csv-table-preview/index.ts | 1 + .../components/data-table}/data-table.tsx | 0 .../file-view/components/data-table/index.ts | 1 + .../components/docx-preview}/docx-preview.tsx | 24 +- .../components/docx-preview/index.ts | 1 + .../editor-context-menu.tsx | 76 + .../components/editor-context-menu/index.ts | 1 + .../image-preview}/image-preview.tsx | 14 +- .../components/image-preview/index.ts | 1 + .../components/mermaid-diagram/index.ts | 1 + .../mermaid-diagram}/mermaid-diagram.test.ts | 0 .../mermaid-diagram}/mermaid-diagram.tsx | Bin 7062 -> 7178 bytes .../file-view/components/pdf-viewer/index.ts | 1 + .../components/pdf-viewer}/pdf-viewer.tsx | 6 +- .../components/pptx-preview/index.ts | 1 + .../components/pptx-preview}/pptx-preview.tsx | 18 +- .../components/pptx-sandbox-host/index.ts | 1 + .../pptx-sandbox-host}/pptx-sandbox-host.tsx | 4 +- .../components/preview-panel/index.ts | 1 + .../preview-panel}/preview-panel.tsx | 29 +- .../components/preview-shared/index.ts | 1 + .../preview-shared}/preview-shared.tsx | 0 .../components/preview-toolbar/index.ts | 1 + .../preview-toolbar}/preview-toolbar.tsx | 0 .../rich-markdown-editor/block-mover.ts | 0 .../rich-markdown-editor/code-block.tsx | 5 +- .../code-highlight.test.ts | 0 .../rich-markdown-editor/code-highlight.ts | 0 .../code-languages.test.ts | 0 .../detect-language.test.ts | 0 .../rich-markdown-editor/detect-language.ts | 0 .../dirty-on-open.test.ts | 0 .../rich-markdown-editor/dirty-signal.test.ts | 0 .../rich-markdown-editor/editor-extensions.ts | 8 +- .../rich-markdown-editor/embed/embed-dom.ts | 0 .../embed/link-embed.test.ts | 2 +- .../rich-markdown-editor/embed/link-embed.ts | 0 .../rich-markdown-editor/extensions.ts | 4 +- .../heading-anchors.test.ts | 0 .../rich-markdown-editor/heading-anchors.ts | 0 .../rich-markdown-editor/highlight.ts | 0 .../rich-markdown-editor/image-paste.test.ts | 31 +- .../rich-markdown-editor/image-paste.ts | 2 +- .../rich-markdown-editor/image.test.ts | 56 + .../rich-markdown-editor/image.tsx | 8 +- .../rich-markdown-editor/keymap.test.ts | 2 +- .../rich-markdown-editor/keymap.ts | 2 +- .../link-input-rule.test.ts | 0 .../rich-markdown-editor/link-input-rule.ts | 0 .../link-mark-precedence.test.ts | 0 .../rich-markdown-editor/markdown-fidelity.ts | 0 .../markdown-parse.test.ts | 0 .../rich-markdown-editor/markdown-parse.ts | 0 .../markdown-paste.test.ts | 0 .../rich-markdown-editor/markdown-paste.ts | 0 .../rich-markdown-editor/mention/index.ts | 0 .../mention/mention-chip.test.tsx | 181 +++ .../mention/mention-chip.tsx | 131 ++ .../mention/mention-icon.test.ts | 0 .../mention/mention-icon.ts | 0 .../mention/mention-list.test.tsx | 2 +- .../mention/mention-list.tsx | 4 +- .../mention/mention-node.test.ts | 5 +- .../mention/mention-node.ts | 0 .../mention/mention-store.ts | 0 .../rich-markdown-editor/mention/mention.ts | 2 +- .../mention/sim-link.test.ts | 36 + .../rich-markdown-editor/mention/sim-link.ts | 44 + .../rich-markdown-editor/mention/types.ts | 0 .../mention/use-editor-mentions.ts | 0 .../mention/use-markdown-mentions.ts | 0 .../menus/bubble-menu.tsx | 0 .../menus/link-editing.tsx | 2 +- .../menus/link-hover-card.tsx | 2 +- .../menus/suggestion-list.tsx | 0 .../menus/suggestion-menu-chrome.ts | 0 .../menus/suggestion-popup.ts | 0 .../menus/table-menu.test.ts | 2 +- .../rich-markdown-editor/menus/table-menu.tsx | 0 .../menus/toolbar-button.tsx | 0 .../menus/use-suggestion-keyboard.ts | 0 .../rich-markdown-editor/normalize-content.ts | 0 .../raw-markdown-snippet-view.test.ts | 0 .../raw-markdown-snippet.test.ts | 0 .../raw-markdown-snippet.tsx | 0 .../rich-markdown-editor.css | 0 .../rich-markdown-editor.tsx | 87 +- .../rich-markdown-field.tsx | 4 +- .../round-trip-safety.test.ts | 0 .../rich-markdown-editor/round-trip-safety.ts | 0 .../rich-markdown-editor/round-trip.test.ts | 0 .../slash-command/commands.test.ts | 0 .../slash-command/commands.ts | 0 .../slash-command/slash-command-list.tsx | 4 +- .../slash-command/slash-command.ts | 2 +- .../stream-settle-selection.test.ts | 0 .../use-editor-editable.ts | 0 .../file-view/components/text-editor/index.ts | 1 + .../components/text-editor}/text-editor.tsx | 35 +- .../components/xlsx-preview/index.ts | 1 + .../components/xlsx-preview}/xlsx-preview.tsx | 22 +- .../components/zoomable-preview/index.ts | 1 + .../zoomable-preview}/zoomable-preview.tsx | 4 +- .../resources/file-view/file-view.tsx | 526 +++++++ .../resources/file-view/hooks}/csv-import.ts | 8 +- .../hooks}/use-doc-preview-binary.test.ts | 0 .../hooks}/use-doc-preview-binary.ts | 10 +- .../hooks}/use-editable-file-content.test.tsx | 18 +- .../hooks}/use-editable-file-content.ts | 26 +- .../components/resources/file-view/index.ts | 16 + .../file-view/utils}/file-category.test.ts | 0 .../file-view/utils}/file-category.ts | 0 .../file-view/utils}/preview-wheel-zoom.ts | 0 .../utils}/text-editor-state.test.ts | 0 .../file-view/utils}/text-editor-state.ts | 0 .../components/interface-cell/index.ts | 2 + .../interface-cell/interface-cell.tsx | 268 ++++ .../components/interface-canvas/index.ts | 2 + .../interface-canvas.test.tsx | 206 +++ .../interface-canvas/interface-canvas.tsx | 179 +++ .../components/interface-pane/index.ts | 2 + .../interface-pane/interface-pane.tsx | 46 + .../interface-preview-grid/index.ts | 2 + .../interface-preview-grid.tsx | 72 + .../components/module-chooser/index.ts | 2 + .../module-chooser/module-chooser.tsx | 47 + .../components/chat-module/chat-module.tsx | 208 +++ .../components/chat-turn/chat-turn.tsx | 116 ++ .../chat-module/components/chat-turn/index.ts | 1 + .../hooks/use-interface-chat.test.tsx | 342 +++++ .../chat-module/hooks/use-interface-chat.ts | 449 ++++++ .../components/chat-module/index.ts | 2 + .../components/file-module/file-module.tsx | 119 ++ .../components/file-module/index.ts | 2 + .../form-field-control/form-field-control.tsx | 258 ++++ .../components/form-field-control/index.ts | 2 + .../form-module/form-module.test.tsx | 879 +++++++++++ .../components/form-module/form-module.tsx | 375 +++++ .../hooks/use-module-form-submit.ts | 74 + .../components/form-module/index.ts | 2 + .../module-resource-picker/index.ts | 2 + .../module-resource-picker.tsx | 141 ++ .../components/table-cell-value/index.ts | 2 + .../table-cell-value/table-cell-value.tsx | 151 ++ .../table-module/hooks/use-module-table.ts | 133 ++ .../components/table-module/index.ts | 2 + .../components/table-module/table-module.tsx | 163 ++ .../components/module-renderer/index.ts | 2 + .../module-renderer/module-renderer.test.tsx | 788 ++++++++++ .../module-renderer/module-renderer.tsx | 75 + .../resources/interface-view/index.ts | 2 + .../interface-view/interface-scope.ts | 13 + .../interface-view/interface-view.tsx | 96 ++ .../resources/interface-view/module-chrome.ts | 25 + .../resources/resource-empty-state/index.ts | 2 + .../resource-empty-state.tsx | 42 + .../resources/resource-provider/index.ts | 11 + .../resource-provider/resource-provider.tsx | 116 ++ .../ui/generated-password-input.tsx | 118 -- apps/sim/components/ui/index.ts | 1 - .../components/group-detail.tsx | 56 +- .../access-control/utils/permission-check.ts | 90 +- apps/sim/executor/utils/start-block.test.ts | 122 ++ apps/sim/hooks/queries/interfaces.ts | 368 +++++ .../hooks/queries/public-interface-shares.ts | 59 + apps/sim/hooks/queries/public-interfaces.ts | 124 ++ apps/sim/hooks/queries/public-shares.ts | 97 +- apps/sim/hooks/queries/tables.ts | 74 +- .../sim/hooks/queries/utils/interface-keys.ts | 24 + .../queries/utils/mutation-error-toast.ts | 24 + .../hooks/queries/workspace-files.test.tsx | 5 +- apps/sim/hooks/queries/workspace-files.ts | 25 +- apps/sim/hooks/use-drag-reorder.ts | 105 ++ apps/sim/hooks/use-execution-stream.ts | 47 +- apps/sim/hooks/use-file-content-source.tsx | 101 -- apps/sim/hooks/use-share-modal-state.test.tsx | 338 +++++ apps/sim/hooks/use-share-modal-state.ts | 223 +++ apps/sim/lib/api/contracts/chats.ts | 2 + apps/sim/lib/api/contracts/interfaces.ts | 254 ++++ .../lib/api/contracts/permission-groups.ts | 2 + apps/sim/lib/api/contracts/primitives.ts | 15 + .../lib/api/contracts/public-interfaces.ts | 222 +++ apps/sim/lib/api/contracts/public-shares.ts | 172 ++- apps/sim/lib/api/contracts/tables.ts | 10 +- apps/sim/lib/api/contracts/workflows.ts | 1 + apps/sim/lib/compare/data/sim.ts | 4 +- apps/sim/lib/copilot/chat/process-contents.ts | 35 + .../copilot/chat/workspace-context.test.ts | 38 + .../sim/lib/copilot/chat/workspace-context.ts | 48 +- .../lib/copilot/generated/tool-catalog-v1.ts | 148 +- .../lib/copilot/generated/tool-schemas-v1.ts | 127 +- .../lib/copilot/generated/vfs-snapshot-v1.ts | 10 + apps/sim/lib/copilot/resources/extraction.ts | 36 + apps/sim/lib/copilot/resources/types.ts | 3 + .../copilot/tools/handlers/resources.test.ts | 53 +- .../lib/copilot/tools/handlers/resources.ts | 10 + .../tools/handlers/restore-resource.ts | 10 +- .../sim/lib/copilot/tools/server/base-tool.ts | 21 + .../server/interfaces/user-interface.test.ts | 877 +++++++++++ .../tools/server/interfaces/user-interface.ts | 825 ++++++++++ apps/sim/lib/copilot/tools/server/router.ts | 7 + .../copilot/tools/server/table/user-table.ts | 5 +- .../lib/copilot/tools/tool-display.test.ts | 26 + apps/sim/lib/copilot/tools/tool-display.ts | 39 + apps/sim/lib/copilot/vfs/path-utils.test.ts | 4 +- apps/sim/lib/copilot/vfs/path-utils.ts | 5 + apps/sim/lib/copilot/vfs/serializers.test.ts | 77 + apps/sim/lib/copilot/vfs/serializers.ts | 24 + apps/sim/lib/copilot/vfs/workspace-vfs.ts | 43 + apps/sim/lib/core/security/deployment.ts | 2 +- apps/sim/lib/core/security/otp.ts | 9 +- apps/sim/lib/core/utils/chat-outputs.ts | 72 + apps/sim/lib/core/utils/response-format.ts | 21 + apps/sim/lib/core/utils/restore-name.ts | 39 + apps/sim/lib/core/utils/unique-name.test.ts | 44 + apps/sim/lib/core/utils/unique-name.ts | 29 + apps/sim/lib/interfaces/constants.ts | 167 +++ apps/sim/lib/interfaces/form-config.ts | 98 ++ .../lib/interfaces/form-submission.test.ts | 173 +++ apps/sim/lib/interfaces/form-submission.ts | 109 ++ apps/sim/lib/interfaces/geometry.test.ts | 447 ++++++ apps/sim/lib/interfaces/geometry.ts | 291 ++++ apps/sim/lib/interfaces/index.ts | 22 + apps/sim/lib/interfaces/module-meta.ts | 43 + apps/sim/lib/interfaces/orchestration.ts | 57 + apps/sim/lib/interfaces/schema.ts | 356 +++++ apps/sim/lib/interfaces/service.test.ts | 556 +++++++ apps/sim/lib/interfaces/service.ts | 573 +++++++ apps/sim/lib/interfaces/types.ts | 155 ++ apps/sim/lib/interfaces/validation.test.ts | 652 ++++++++ apps/sim/lib/interfaces/validation.ts | 232 +++ apps/sim/lib/knowledge/service.ts | 36 +- apps/sim/lib/logs/get-trigger-options.ts | 1 + apps/sim/lib/permission-groups/types.ts | 28 +- apps/sim/lib/posthog/events.ts | 19 + .../public-shares/interface-access.test.ts | 433 ++++++ .../sim/lib/public-shares/interface-access.ts | 159 ++ apps/sim/lib/public-shares/provenance.ts | 18 + apps/sim/lib/public-shares/rate-limit.ts | 105 +- apps/sim/lib/public-shares/share-gate.ts | 65 + apps/sim/lib/public-shares/share-manager.ts | 133 +- apps/sim/lib/public-shares/urls.ts | 25 + .../orchestration/restore-resource.ts | 19 + apps/sim/lib/social-share.ts | 24 + apps/sim/lib/table/service.ts | 38 +- .../workspace/workspace-file-manager.ts | 28 +- apps/sim/lib/uploads/core/storage-service.ts | 42 +- apps/sim/lib/uploads/providers/blob/client.ts | 8 +- apps/sim/lib/uploads/providers/gcs/client.ts | 36 +- apps/sim/lib/uploads/providers/s3/client.ts | 15 +- apps/sim/lib/uploads/utils/byte-range.test.ts | 108 ++ apps/sim/lib/uploads/utils/byte-range.ts | 101 ++ .../workflows/executor/execute-workflow.ts | 2 +- .../workflows/executor/execution-core.test.ts | 58 + .../lib/workflows/executor/execution-core.ts | 22 +- .../executor/human-in-the-loop-manager.ts | 5 +- .../streaming/execution-event-stream.ts | 393 +++++ .../streaming/logging-session-completion.ts | 28 + apps/sim/lib/workflows/streaming/streaming.ts | 34 +- apps/sim/next.config.ts | 2 +- apps/sim/package.json | 6 +- apps/sim/providers/utils.ts | 2 +- apps/sim/resources/file-source.ts | 132 ++ apps/sim/resources/grants.test.ts | 92 ++ apps/sim/resources/grants.ts | 63 + apps/sim/resources/host.ts | 20 + apps/sim/resources/index.ts | 43 + apps/sim/resources/kinds.ts | 91 ++ apps/sim/resources/source.test.ts | 243 +++ apps/sim/resources/source.ts | 175 +++ apps/sim/stores/logs/filters/types.ts | 1 + apps/sim/stores/modals/search/types.ts | 1 + apps/sim/stores/panel/types.ts | 1 + apps/sim/tools/merge-params.ts | 113 ++ apps/sim/tools/params.test.ts | 2 +- apps/sim/tools/params.ts | 108 +- bun.lock | 43 +- bunfig.toml | 20 +- package.json | 6 +- packages/audit/src/types.ts | 9 + packages/db/schema.ts | 49 +- packages/emcn/package.json | 2 +- .../chip-dropdown/chip-dropdown.tsx | 24 + .../chip-password-input.tsx | 137 ++ .../components/chip-select/chip-select.tsx | 23 + .../emcn/src/components/chip/chip-chevron.tsx | 7 +- packages/emcn/src/components/chip/chip.tsx | 54 +- .../emcn/src/components/combobox/combobox.tsx | 23 + packages/emcn/src/components/index.ts | 4 + .../src/components/tag-input/tag-input.tsx | 31 +- packages/emcn/src/icons/bubble-chat.tsx | 28 + packages/emcn/src/icons/form-input.tsx | 27 + packages/emcn/src/icons/grip-vertical.tsx | 26 + packages/emcn/src/icons/index.ts | 4 + packages/emcn/src/icons/panels.tsx | 27 + packages/testing/src/mocks/audit.mock.ts | 7 + packages/testing/src/mocks/schema.mock.ts | 11 + scripts/check-api-validation-contracts.ts | 4 +- scripts/check-resource-views.ts | 1323 +++++++++++++++++ 515 files changed, 34228 insertions(+), 4122 deletions(-) create mode 100644 .claude/rules/canon.md create mode 100644 .claude/rules/sim-resource-views.md create mode 100644 .cursor/rules/sim-resource-views.mdc create mode 100644 apps/sim/app/(interfaces)/chat/utils/attachments.ts create mode 100644 apps/sim/app/(landing)/components/share-link-button/index.ts create mode 100644 apps/sim/app/(landing)/components/share-link-button/share-link-button.tsx create mode 100644 apps/sim/app/api/interfaces/[interfaceId]/modules/[moduleId]/submit/route.test.ts create mode 100644 apps/sim/app/api/interfaces/[interfaceId]/modules/[moduleId]/submit/route.ts create mode 100644 apps/sim/app/api/interfaces/[interfaceId]/restore/route.test.ts create mode 100644 apps/sim/app/api/interfaces/[interfaceId]/restore/route.ts create mode 100644 apps/sim/app/api/interfaces/[interfaceId]/route.test.ts create mode 100644 apps/sim/app/api/interfaces/[interfaceId]/route.ts create mode 100644 apps/sim/app/api/interfaces/[interfaceId]/share/route.test.ts create mode 100644 apps/sim/app/api/interfaces/[interfaceId]/share/route.ts create mode 100644 apps/sim/app/api/interfaces/public/[token]/modules/[moduleId]/chat/route.test.ts create mode 100644 apps/sim/app/api/interfaces/public/[token]/modules/[moduleId]/chat/route.ts create mode 100644 apps/sim/app/api/interfaces/public/[token]/modules/[moduleId]/chat/stream-dialect.test.ts create mode 100644 apps/sim/app/api/interfaces/public/[token]/modules/[moduleId]/file/content/route.test.ts create mode 100644 apps/sim/app/api/interfaces/public/[token]/modules/[moduleId]/file/content/route.ts create mode 100644 apps/sim/app/api/interfaces/public/[token]/modules/[moduleId]/file/inline/route.test.ts create mode 100644 apps/sim/app/api/interfaces/public/[token]/modules/[moduleId]/file/inline/route.ts create mode 100644 apps/sim/app/api/interfaces/public/[token]/modules/[moduleId]/submit/route.test.ts create mode 100644 apps/sim/app/api/interfaces/public/[token]/modules/[moduleId]/submit/route.ts create mode 100644 apps/sim/app/api/interfaces/public/[token]/modules/[moduleId]/table/rows/route.test.ts create mode 100644 apps/sim/app/api/interfaces/public/[token]/modules/[moduleId]/table/rows/route.ts create mode 100644 apps/sim/app/api/interfaces/public/[token]/modules/[moduleId]/utils.ts create mode 100644 apps/sim/app/api/interfaces/public/[token]/otp/route.test.ts create mode 100644 apps/sim/app/api/interfaces/public/[token]/otp/route.ts create mode 100644 apps/sim/app/api/interfaces/public/[token]/route.test.ts create mode 100644 apps/sim/app/api/interfaces/public/[token]/route.ts create mode 100644 apps/sim/app/api/interfaces/public/[token]/sso/route.ts create mode 100644 apps/sim/app/api/interfaces/route.test.ts create mode 100644 apps/sim/app/api/interfaces/route.ts create mode 100644 apps/sim/app/api/interfaces/utils.ts delete mode 100644 apps/sim/app/f/[token]/utils.ts create mode 100644 apps/sim/app/i/[token]/components/index.ts create mode 100644 apps/sim/app/i/[token]/components/public-interface-view/index.ts create mode 100644 apps/sim/app/i/[token]/components/public-interface-view/public-interface-view.tsx create mode 100644 apps/sim/app/i/[token]/opengraph-image.tsx create mode 100644 apps/sim/app/i/[token]/page.tsx create mode 100644 apps/sim/app/workspace/[workspaceId]/components/resource/use-background-context-menu.ts create mode 100644 apps/sim/app/workspace/[workspaceId]/components/share-modal/index.ts create mode 100644 apps/sim/app/workspace/[workspaceId]/components/share-modal/share-modal.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/files/[fileId]/view/file-viewer.tsx create mode 100644 apps/sim/app/workspace/[workspaceId]/files/[fileId]/view/fullscreen-file-view.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/editor-context-menu.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/file-viewer.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/index.ts delete mode 100644 apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/image.test.ts delete mode 100644 apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/mention/mention-chip.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/mention/mention-chip.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/mention/sim-link.test.ts delete mode 100644 apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/mention/sim-link.ts delete mode 100644 apps/sim/app/workspace/[workspaceId]/files/components/share-modal/index.ts delete mode 100644 apps/sim/app/workspace/[workspaceId]/files/components/share-modal/share-modal.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/chat-content/index.ts create mode 100644 apps/sim/app/workspace/[workspaceId]/interfaces/[interfaceId]/components/inspector-field/index.ts create mode 100644 apps/sim/app/workspace/[workspaceId]/interfaces/[interfaceId]/components/inspector-field/inspector-field.tsx create mode 100644 apps/sim/app/workspace/[workspaceId]/interfaces/[interfaceId]/components/module-inspector/components/chat-module-fields/chat-module-fields.tsx create mode 100644 apps/sim/app/workspace/[workspaceId]/interfaces/[interfaceId]/components/module-inspector/components/chat-module-fields/index.ts create mode 100644 apps/sim/app/workspace/[workspaceId]/interfaces/[interfaceId]/components/module-inspector/components/form-module-fields/components/form-field-row/form-field-row.test.tsx create mode 100644 apps/sim/app/workspace/[workspaceId]/interfaces/[interfaceId]/components/module-inspector/components/form-module-fields/components/form-field-row/form-field-row.tsx create mode 100644 apps/sim/app/workspace/[workspaceId]/interfaces/[interfaceId]/components/module-inspector/components/form-module-fields/components/form-field-row/index.ts create mode 100644 apps/sim/app/workspace/[workspaceId]/interfaces/[interfaceId]/components/module-inspector/components/form-module-fields/form-module-fields.tsx create mode 100644 apps/sim/app/workspace/[workspaceId]/interfaces/[interfaceId]/components/module-inspector/components/form-module-fields/index.ts create mode 100644 apps/sim/app/workspace/[workspaceId]/interfaces/[interfaceId]/components/module-inspector/components/resource-picker-field/index.ts create mode 100644 apps/sim/app/workspace/[workspaceId]/interfaces/[interfaceId]/components/module-inspector/components/resource-picker-field/resource-picker-field.test.tsx create mode 100644 apps/sim/app/workspace/[workspaceId]/interfaces/[interfaceId]/components/module-inspector/components/resource-picker-field/resource-picker-field.tsx create mode 100644 apps/sim/app/workspace/[workspaceId]/interfaces/[interfaceId]/components/module-inspector/components/table-module-fields/index.ts create mode 100644 apps/sim/app/workspace/[workspaceId]/interfaces/[interfaceId]/components/module-inspector/components/table-module-fields/table-module-fields.tsx create mode 100644 apps/sim/app/workspace/[workspaceId]/interfaces/[interfaceId]/components/module-inspector/index.ts create mode 100644 apps/sim/app/workspace/[workspaceId]/interfaces/[interfaceId]/components/module-inspector/module-inspector.tsx create mode 100644 apps/sim/app/workspace/[workspaceId]/interfaces/[interfaceId]/error.tsx create mode 100644 apps/sim/app/workspace/[workspaceId]/interfaces/[interfaceId]/hooks/use-interface-editor-state.ts create mode 100644 apps/sim/app/workspace/[workspaceId]/interfaces/[interfaceId]/hooks/use-interface-layout.test.tsx create mode 100644 apps/sim/app/workspace/[workspaceId]/interfaces/[interfaceId]/hooks/use-interface-layout.ts create mode 100644 apps/sim/app/workspace/[workspaceId]/interfaces/[interfaceId]/interface.tsx create mode 100644 apps/sim/app/workspace/[workspaceId]/interfaces/[interfaceId]/loading.tsx create mode 100644 apps/sim/app/workspace/[workspaceId]/interfaces/[interfaceId]/page.tsx create mode 100644 apps/sim/app/workspace/[workspaceId]/interfaces/[interfaceId]/search-params.ts create mode 100644 apps/sim/app/workspace/[workspaceId]/interfaces/components/index.ts create mode 100644 apps/sim/app/workspace/[workspaceId]/interfaces/components/interface-context-menu/index.ts create mode 100644 apps/sim/app/workspace/[workspaceId]/interfaces/components/interface-context-menu/interface-context-menu.tsx create mode 100644 apps/sim/app/workspace/[workspaceId]/interfaces/components/interfaces-list-context-menu/index.ts create mode 100644 apps/sim/app/workspace/[workspaceId]/interfaces/components/interfaces-list-context-menu/interfaces-list-context-menu.tsx create mode 100644 apps/sim/app/workspace/[workspaceId]/interfaces/error.tsx create mode 100644 apps/sim/app/workspace/[workspaceId]/interfaces/interfaces.tsx create mode 100644 apps/sim/app/workspace/[workspaceId]/interfaces/loading.tsx create mode 100644 apps/sim/app/workspace/[workspaceId]/interfaces/page.tsx create mode 100644 apps/sim/app/workspace/[workspaceId]/interfaces/prefetch.ts create mode 100644 apps/sim/app/workspace/[workspaceId]/interfaces/search-params.ts create mode 100644 apps/sim/components/action-row/action-row.tsx create mode 100644 apps/sim/components/action-row/index.ts create mode 100644 apps/sim/components/anchored-context-menu/anchored-context-menu.tsx create mode 100644 apps/sim/components/anchored-context-menu/index.ts rename apps/sim/{app/workspace/[workspaceId]/home/components/message-content/components => components/chat}/chat-content/chat-content.test.ts (89%) rename apps/sim/{app/workspace/[workspaceId]/home/components/message-content/components => components/chat}/chat-content/chat-content.tsx (91%) rename apps/sim/{app/workspace/[workspaceId]/home/components/message-content/components => components/chat}/chat-content/chat-sanitize.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/home/components/message-content/components => components/chat}/chat-content/external-link.tsx (100%) create mode 100644 apps/sim/components/chat/chat-content/index.ts rename apps/sim/{app/workspace/[workspaceId]/home/components => components/chat}/chat-context-kind-registry/chat-context-kind-registry.tsx (94%) rename apps/sim/{app/workspace/[workspaceId]/home/components => components/chat}/chat-context-kind-registry/index.ts (100%) create mode 100644 apps/sim/components/chat/chat-copy-button/chat-copy-button.tsx create mode 100644 apps/sim/components/chat/chat-copy-button/index.ts rename apps/sim/{app/workspace/[workspaceId]/home/components => components/chat}/chat-message-attachments/chat-message-attachments.tsx (96%) rename apps/sim/{app/workspace/[workspaceId]/home/components => components/chat}/chat-message-attachments/index.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/home/components => components/chat}/context-mention-icon/context-mention-icon.tsx (70%) rename apps/sim/{app/workspace/[workspaceId]/home/components => components/chat}/context-mention-icon/index.ts (100%) create mode 100644 apps/sim/components/chat/index.ts create mode 100644 apps/sim/components/chat/pending-tag-indicator/index.ts create mode 100644 apps/sim/components/chat/pending-tag-indicator/pending-tag-indicator.tsx create mode 100644 apps/sim/components/chat/special-tags/parse.ts create mode 100644 apps/sim/components/chat/turn-layout.ts create mode 100644 apps/sim/components/chat/types.ts rename apps/sim/{app/workspace/[workspaceId]/home/components => components/chat}/user-message-content/index.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/home/components => components/chat}/user-message-content/user-message-content.tsx (96%) create mode 100644 apps/sim/components/public-share/index.ts create mode 100644 apps/sim/components/public-share/public-share-auth-shell/index.ts rename apps/sim/{app/f/[token]/public-file-auth-shell.tsx => components/public-share/public-share-auth-shell/public-share-auth-shell.tsx} (81%) create mode 100644 apps/sim/components/public-share/public-share-email-auth/index.ts rename apps/sim/{app/f/[token]/public-file-email-auth.tsx => components/public-share/public-share-email-auth/public-share-email-auth.tsx} (75%) create mode 100644 apps/sim/components/public-share/public-share-password-auth/index.ts rename apps/sim/{app/f/[token]/public-file-auth.tsx => components/public-share/public-share-password-auth/public-share-password-auth.tsx} (63%) create mode 100644 apps/sim/components/public-share/public-share-sso-auth/index.ts rename apps/sim/{app/f/[token]/public-file-sso-auth.tsx => components/public-share/public-share-sso-auth/public-share-sso-auth.tsx} (57%) create mode 100644 apps/sim/components/public-share/types.ts create mode 100644 apps/sim/components/resources/MIGRATING-RESOURCES.md rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components/csv-table-preview}/csv-table-preview.tsx (65%) create mode 100644 apps/sim/components/resources/file-view/components/csv-table-preview/index.ts rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components/data-table}/data-table.tsx (100%) create mode 100644 apps/sim/components/resources/file-view/components/data-table/index.ts rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components/docx-preview}/docx-preview.tsx (94%) create mode 100644 apps/sim/components/resources/file-view/components/docx-preview/index.ts create mode 100644 apps/sim/components/resources/file-view/components/editor-context-menu/editor-context-menu.tsx create mode 100644 apps/sim/components/resources/file-view/components/editor-context-menu/index.ts rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components/image-preview}/image-preview.tsx (65%) create mode 100644 apps/sim/components/resources/file-view/components/image-preview/index.ts create mode 100644 apps/sim/components/resources/file-view/components/mermaid-diagram/index.ts rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components/mermaid-diagram}/mermaid-diagram.test.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components/mermaid-diagram}/mermaid-diagram.tsx (96%) create mode 100644 apps/sim/components/resources/file-view/components/pdf-viewer/index.ts rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components/pdf-viewer}/pdf-viewer.tsx (96%) create mode 100644 apps/sim/components/resources/file-view/components/pptx-preview/index.ts rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components/pptx-preview}/pptx-preview.tsx (74%) create mode 100644 apps/sim/components/resources/file-view/components/pptx-sandbox-host/index.ts rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components/pptx-sandbox-host}/pptx-sandbox-host.tsx (96%) create mode 100644 apps/sim/components/resources/file-view/components/preview-panel/index.ts rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components/preview-panel}/preview-panel.tsx (90%) create mode 100644 apps/sim/components/resources/file-view/components/preview-shared/index.ts rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components/preview-shared}/preview-shared.tsx (100%) create mode 100644 apps/sim/components/resources/file-view/components/preview-toolbar/index.ts rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components/preview-toolbar}/preview-toolbar.tsx (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/block-mover.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/code-block.tsx (98%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/code-highlight.test.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/code-highlight.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/code-languages.test.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/detect-language.test.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/detect-language.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/dirty-on-open.test.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/dirty-signal.test.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/editor-extensions.ts (80%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/embed/embed-dom.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/embed/link-embed.test.ts (94%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/embed/link-embed.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/extensions.ts (96%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/heading-anchors.test.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/heading-anchors.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/highlight.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/image-paste.test.ts (95%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/image-paste.ts (99%) create mode 100644 apps/sim/components/resources/file-view/components/rich-markdown-editor/image.test.ts rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/image.tsx (96%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/keymap.test.ts (99%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/keymap.ts (99%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/link-input-rule.test.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/link-input-rule.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/link-mark-precedence.test.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/markdown-fidelity.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/markdown-parse.test.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/markdown-parse.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/markdown-paste.test.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/markdown-paste.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/mention/index.ts (100%) create mode 100644 apps/sim/components/resources/file-view/components/rich-markdown-editor/mention/mention-chip.test.tsx create mode 100644 apps/sim/components/resources/file-view/components/rich-markdown-editor/mention/mention-chip.tsx rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/mention/mention-icon.test.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/mention/mention-icon.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/mention/mention-list.test.tsx (97%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/mention/mention-list.tsx (94%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/mention/mention-node.test.ts (92%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/mention/mention-node.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/mention/mention-store.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/mention/mention.ts (96%) create mode 100644 apps/sim/components/resources/file-view/components/rich-markdown-editor/mention/sim-link.test.ts create mode 100644 apps/sim/components/resources/file-view/components/rich-markdown-editor/mention/sim-link.ts rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/mention/types.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/mention/use-editor-mentions.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/mention/use-markdown-mentions.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/menus/bubble-menu.tsx (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/menus/link-editing.tsx (93%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/menus/link-hover-card.tsx (98%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/menus/suggestion-list.tsx (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/menus/suggestion-menu-chrome.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/menus/suggestion-popup.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/menus/table-menu.test.ts (97%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/menus/table-menu.tsx (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/menus/toolbar-button.tsx (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/menus/use-suggestion-keyboard.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/normalize-content.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/raw-markdown-snippet-view.test.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/raw-markdown-snippet.test.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/raw-markdown-snippet.tsx (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/rich-markdown-editor.css (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/rich-markdown-editor.tsx (91%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/rich-markdown-field.tsx (97%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/round-trip-safety.test.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/round-trip-safety.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/round-trip.test.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/slash-command/commands.test.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/slash-command/commands.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/slash-command/slash-command-list.tsx (91%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/slash-command/slash-command.ts (94%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/stream-settle-selection.test.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/use-editor-editable.ts (100%) create mode 100644 apps/sim/components/resources/file-view/components/text-editor/index.ts rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components/text-editor}/text-editor.tsx (96%) create mode 100644 apps/sim/components/resources/file-view/components/xlsx-preview/index.ts rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components/xlsx-preview}/xlsx-preview.tsx (87%) create mode 100644 apps/sim/components/resources/file-view/components/zoomable-preview/index.ts rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components/zoomable-preview}/zoomable-preview.tsx (97%) create mode 100644 apps/sim/components/resources/file-view/file-view.tsx rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/hooks}/csv-import.ts (91%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/hooks}/use-doc-preview-binary.test.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/hooks}/use-doc-preview-binary.ts (92%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/hooks}/use-editable-file-content.test.tsx (91%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/hooks}/use-editable-file-content.ts (95%) create mode 100644 apps/sim/components/resources/file-view/index.ts rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/utils}/file-category.test.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/utils}/file-category.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/utils}/preview-wheel-zoom.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/utils}/text-editor-state.test.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/utils}/text-editor-state.ts (100%) create mode 100644 apps/sim/components/resources/interface-view/components/interface-canvas/components/interface-cell/index.ts create mode 100644 apps/sim/components/resources/interface-view/components/interface-canvas/components/interface-cell/interface-cell.tsx create mode 100644 apps/sim/components/resources/interface-view/components/interface-canvas/index.ts create mode 100644 apps/sim/components/resources/interface-view/components/interface-canvas/interface-canvas.test.tsx create mode 100644 apps/sim/components/resources/interface-view/components/interface-canvas/interface-canvas.tsx create mode 100644 apps/sim/components/resources/interface-view/components/interface-preview-grid/components/interface-pane/index.ts create mode 100644 apps/sim/components/resources/interface-view/components/interface-preview-grid/components/interface-pane/interface-pane.tsx create mode 100644 apps/sim/components/resources/interface-view/components/interface-preview-grid/index.ts create mode 100644 apps/sim/components/resources/interface-view/components/interface-preview-grid/interface-preview-grid.tsx create mode 100644 apps/sim/components/resources/interface-view/components/module-chooser/index.ts create mode 100644 apps/sim/components/resources/interface-view/components/module-chooser/module-chooser.tsx create mode 100644 apps/sim/components/resources/interface-view/components/module-renderer/components/chat-module/chat-module.tsx create mode 100644 apps/sim/components/resources/interface-view/components/module-renderer/components/chat-module/components/chat-turn/chat-turn.tsx create mode 100644 apps/sim/components/resources/interface-view/components/module-renderer/components/chat-module/components/chat-turn/index.ts create mode 100644 apps/sim/components/resources/interface-view/components/module-renderer/components/chat-module/hooks/use-interface-chat.test.tsx create mode 100644 apps/sim/components/resources/interface-view/components/module-renderer/components/chat-module/hooks/use-interface-chat.ts create mode 100644 apps/sim/components/resources/interface-view/components/module-renderer/components/chat-module/index.ts create mode 100644 apps/sim/components/resources/interface-view/components/module-renderer/components/file-module/file-module.tsx create mode 100644 apps/sim/components/resources/interface-view/components/module-renderer/components/file-module/index.ts create mode 100644 apps/sim/components/resources/interface-view/components/module-renderer/components/form-module/components/form-field-control/form-field-control.tsx create mode 100644 apps/sim/components/resources/interface-view/components/module-renderer/components/form-module/components/form-field-control/index.ts create mode 100644 apps/sim/components/resources/interface-view/components/module-renderer/components/form-module/form-module.test.tsx create mode 100644 apps/sim/components/resources/interface-view/components/module-renderer/components/form-module/form-module.tsx create mode 100644 apps/sim/components/resources/interface-view/components/module-renderer/components/form-module/hooks/use-module-form-submit.ts create mode 100644 apps/sim/components/resources/interface-view/components/module-renderer/components/form-module/index.ts create mode 100644 apps/sim/components/resources/interface-view/components/module-renderer/components/module-resource-picker/index.ts create mode 100644 apps/sim/components/resources/interface-view/components/module-renderer/components/module-resource-picker/module-resource-picker.tsx create mode 100644 apps/sim/components/resources/interface-view/components/module-renderer/components/table-module/components/table-cell-value/index.ts create mode 100644 apps/sim/components/resources/interface-view/components/module-renderer/components/table-module/components/table-cell-value/table-cell-value.tsx create mode 100644 apps/sim/components/resources/interface-view/components/module-renderer/components/table-module/hooks/use-module-table.ts create mode 100644 apps/sim/components/resources/interface-view/components/module-renderer/components/table-module/index.ts create mode 100644 apps/sim/components/resources/interface-view/components/module-renderer/components/table-module/table-module.tsx create mode 100644 apps/sim/components/resources/interface-view/components/module-renderer/index.ts create mode 100644 apps/sim/components/resources/interface-view/components/module-renderer/module-renderer.test.tsx create mode 100644 apps/sim/components/resources/interface-view/components/module-renderer/module-renderer.tsx create mode 100644 apps/sim/components/resources/interface-view/index.ts create mode 100644 apps/sim/components/resources/interface-view/interface-scope.ts create mode 100644 apps/sim/components/resources/interface-view/interface-view.tsx create mode 100644 apps/sim/components/resources/interface-view/module-chrome.ts create mode 100644 apps/sim/components/resources/resource-empty-state/index.ts create mode 100644 apps/sim/components/resources/resource-empty-state/resource-empty-state.tsx create mode 100644 apps/sim/components/resources/resource-provider/index.ts create mode 100644 apps/sim/components/resources/resource-provider/resource-provider.tsx delete mode 100644 apps/sim/components/ui/generated-password-input.tsx create mode 100644 apps/sim/hooks/queries/interfaces.ts create mode 100644 apps/sim/hooks/queries/public-interface-shares.ts create mode 100644 apps/sim/hooks/queries/public-interfaces.ts create mode 100644 apps/sim/hooks/queries/utils/interface-keys.ts create mode 100644 apps/sim/hooks/queries/utils/mutation-error-toast.ts create mode 100644 apps/sim/hooks/use-drag-reorder.ts delete mode 100644 apps/sim/hooks/use-file-content-source.tsx create mode 100644 apps/sim/hooks/use-share-modal-state.test.tsx create mode 100644 apps/sim/hooks/use-share-modal-state.ts create mode 100644 apps/sim/lib/api/contracts/interfaces.ts create mode 100644 apps/sim/lib/api/contracts/public-interfaces.ts create mode 100644 apps/sim/lib/copilot/tools/server/interfaces/user-interface.test.ts create mode 100644 apps/sim/lib/copilot/tools/server/interfaces/user-interface.ts create mode 100644 apps/sim/lib/core/utils/chat-outputs.ts create mode 100644 apps/sim/lib/core/utils/unique-name.test.ts create mode 100644 apps/sim/lib/core/utils/unique-name.ts create mode 100644 apps/sim/lib/interfaces/constants.ts create mode 100644 apps/sim/lib/interfaces/form-config.ts create mode 100644 apps/sim/lib/interfaces/form-submission.test.ts create mode 100644 apps/sim/lib/interfaces/form-submission.ts create mode 100644 apps/sim/lib/interfaces/geometry.test.ts create mode 100644 apps/sim/lib/interfaces/geometry.ts create mode 100644 apps/sim/lib/interfaces/index.ts create mode 100644 apps/sim/lib/interfaces/module-meta.ts create mode 100644 apps/sim/lib/interfaces/orchestration.ts create mode 100644 apps/sim/lib/interfaces/schema.ts create mode 100644 apps/sim/lib/interfaces/service.test.ts create mode 100644 apps/sim/lib/interfaces/service.ts create mode 100644 apps/sim/lib/interfaces/types.ts create mode 100644 apps/sim/lib/interfaces/validation.test.ts create mode 100644 apps/sim/lib/interfaces/validation.ts create mode 100644 apps/sim/lib/public-shares/interface-access.test.ts create mode 100644 apps/sim/lib/public-shares/interface-access.ts create mode 100644 apps/sim/lib/public-shares/provenance.ts create mode 100644 apps/sim/lib/public-shares/share-gate.ts create mode 100644 apps/sim/lib/public-shares/urls.ts create mode 100644 apps/sim/lib/social-share.ts create mode 100644 apps/sim/lib/uploads/utils/byte-range.test.ts create mode 100644 apps/sim/lib/uploads/utils/byte-range.ts create mode 100644 apps/sim/lib/workflows/streaming/execution-event-stream.ts create mode 100644 apps/sim/lib/workflows/streaming/logging-session-completion.ts create mode 100644 apps/sim/resources/file-source.ts create mode 100644 apps/sim/resources/grants.test.ts create mode 100644 apps/sim/resources/grants.ts create mode 100644 apps/sim/resources/host.ts create mode 100644 apps/sim/resources/index.ts create mode 100644 apps/sim/resources/kinds.ts create mode 100644 apps/sim/resources/source.test.ts create mode 100644 apps/sim/resources/source.ts create mode 100644 apps/sim/tools/merge-params.ts create mode 100644 packages/emcn/src/components/chip-password-input/chip-password-input.tsx create mode 100644 packages/emcn/src/icons/bubble-chat.tsx create mode 100644 packages/emcn/src/icons/form-input.tsx create mode 100644 packages/emcn/src/icons/grip-vertical.tsx create mode 100644 packages/emcn/src/icons/panels.tsx create mode 100644 scripts/check-resource-views.ts diff --git a/.claude/rules/canon.md b/.claude/rules/canon.md new file mode 100644 index 00000000000..d7b9de3de64 --- /dev/null +++ b/.claude/rules/canon.md @@ -0,0 +1,50 @@ +# Canon — The 7 Canonical Concerns + +The non-negotiable conventions every change is measured against. This file is the index — the full rules live in the referenced files. When a canon rule and any other guidance conflict, canon wins. Known gaps are flagged so we close them deliberately instead of re-discovering them. + +## 1. Next.js best practices + +Server Components by default; `'use client'` only on the smallest leaf. `page.tsx` owns metadata. `next/image` with `priority` on the LCP element; `next/dynamic` for below-fold. Client refs are never called server-side (enforced by `scripts/check-client-boundary-imports.ts`). + +- Full rules: `apps/sim/app/(landing)/CLAUDE.md` (landing), `.claude/rules/sim-architecture.md` (server boundary) +- **Gap**: no general app-router conventions rule — when to add `error.tsx` / `loading.tsx` / `not-found.tsx`, `generateMetadata` vs static `metadata`, ISR/`revalidate`, Suspense/streaming. Landing doc covers landing only. + +## 2. SEO / GEO (landing only) + +One `

` (Hero), strict heading hierarchy, `
`, server-rendered navbar, JSON-LD, answer-first H2s, atomic extractable blocks, entity consistency ("Sim", never "the platform"), sr-only summaries, concrete numbers. Copy follows `.claude/rules/constitution.md`. + +- Full rules: `.claude/rules/landing-seo-geo.md`, `apps/sim/app/(landing)/CLAUDE.md` +- **Gap**: docs reference a single `structured-data.tsx` but code is split into `site-structured-data/`, `home-structured-data/`, `json-ld/`. `sitemap.ts` / `robots.ts` / `manifest.ts` conventions (app root) are undocumented. + +## 3. Feature file structure + +Every feature dir: `feature.tsx` + `page.tsx` (+ `error.tsx`, `loading.tsx`, `search-params.ts` where applicable) + `utils/` (only for 2+ consumers — single-consumer helpers stay in `feature.tsx`) + `hooks/` + `components/`. Every component lives in its own kebab-case folder holding `.tsx` + `index.ts` barrel; children nest under that folder's own `components/`, recursively. Never a bare `.tsx` flat inside a `components/` directory. Reference implementation: `apps/sim/app/workspace/[workspaceId]/scheduled-tasks/`. + +- Full rules: `apps/sim/app/(landing)/CLAUDE.md` "Structure", `.claude/rules/sim-architecture.md` +- **Gap**: the recursion + barrel rule is only fully written in the landing CLAUDE.md; `sim-architecture.md` shows a flatter sketch and omits `search-params.ts` / `error.tsx` / `loading.tsx` co-location. + +## 4. EMCN components only (platform) + +No custom buttons/inputs/menus — always the `@sim/emcn` chip-family equivalent. Components own their chrome; consumers pass props, never chrome via `className` (layout/sizing only). + +- Full rules: `.claude/rules/emcn-components.md` (authoring), `.claude/rules/sim-styling.md` (consumer) +- **Gap**: stale paths — EMCN moved to `packages/emcn/` but `emcn-components.md` frontmatter still scopes `apps/sim/components/emcn/**`, and the landing CLAUDE.md still says import from `@/components/emcn`. + +## 5. No ad-hoc animations or colors + +Colors come from tokens in `apps/sim/app/_styles/globals.css` / `tailwind.config.ts` — never raw hex in components. Animations: prefer CSS, respect `prefers-reduced-motion`, no new keyframes outside the Tailwind config / scoped `.module.css`. Never touch global styles. + +- Full rules: `.claude/rules/sim-styling.md` (tokens), `apps/sim/app/(landing)/CLAUDE.md` (motion) +- **Gap**: the positive rule ("declare custom keyframes/tokens HERE and nowhere else") is unwritten — only the prohibition exists. + +## 6. State placement (useState / Zustand / React Query / URL) + +One four-way decision: React Query = all server state · nuqs URL params = shareable view-state · Zustand = high-frequency, ephemeral, or socket-synced state · useState = purely local UI. Never `useState` + `fetch`; never store-synced-with-effects for view-state. + +- Full rules: `.claude/rules/sim-url-state.md` (the canonical 4-way table), `.claude/rules/sim-queries.md`, `.claude/rules/sim-stores.md` + +## 7. Meta — authoring skills, rules, and CLAUDE.md files + +How we write the docs themselves: CLAUDE.md stays a lean index; detailed conventions go in `.claude/rules/*.md` with `paths:` frontmatter globs so they load only when matching files are touched; repeatable multi-step procedures become skills (`.claude/skills/` or `.claude/commands/`); one-off preferences go in memory, not the repo. + +- **Gap**: no authoring guide exists — rule/skill conventions are learned by imitating existing files. Needs a short `.claude/rules/meta-authoring.md`. diff --git a/.claude/rules/sim-resource-views.md b/.claude/rules/sim-resource-views.md new file mode 100644 index 00000000000..d5217531ec0 --- /dev/null +++ b/.claude/rules/sim-resource-views.md @@ -0,0 +1,134 @@ +--- +paths: + - "apps/sim/resources/**/*.ts" + - "apps/sim/components/resources/**/*.ts" + - "apps/sim/components/resources/**/*.tsx" + - "apps/sim/app/workspace/[workspaceId]/**/*.tsx" + - "apps/sim/app/f/**/*.tsx" + - "apps/sim/app/i/**/*.tsx" + - "apps/sim/app/(interfaces)/**/*.tsx" + - "apps/sim/hooks/queries/workspace-files.ts" +--- + +# Resource Views + +A **resource** is a thing a workspace holds that can also be shared: a file, a table, an interface, a knowledge base, a log, a scheduled task. A resource with a canonical view has **exactly one**, and every consumer mounts that one — the workspace route page, the mothership panel, an interface module, and the public share page. + +**One view per resource. Consumers construct the axes and mount it. They never wrap it.** + +Enforced by `bun run check:resources` (strict CI gate: `bun run check:resources:strict`), which is `scripts/check-resource-views.ts`. + +## The three axes + +`apps/sim/resources/**` is pure TypeScript — no React, no `'use client'` — because `app/i/[token]/page.tsx` builds a share source during SSR. + +| Axis | Type | Replaces | +| --- | --- | --- | +| `source` | `WorkspaceSource \| ShareSource`, discriminated on `via` | `workspaceId`, `token`, `contentSource`, `isPublic`, `isShared` | +| `grants` | `{ write: boolean; run: 'none'\|'deployed'\|'draft' }` | `canEdit`, `canRun`, `canAdmin`, `canDelete`, `disableEdit/Insert/Delete` | +| `host` | `'page' \| 'panel' \| 'public'` | `embedded`, `isEmbedded`, `compact`, `minimal` | + +There is no fourth axis. Agent streaming is **one optional prop on `FileView`** (`streaming?: FileViewStreaming`), because only files stream. + +`ShareSource` declares `workspaceId?: never` and `resourceId?: never`, and `WorkspaceSource` declares `token?: never` and `seed?: never`. A share source **cannot** carry a workspace id — that is a compile error, not a convention. A kind whose seed is typed `never` (`knowledge`, `log`, `schedule`) structurally cannot construct a share source at all: "no public surface" is a compile-time fact. + +``` +apps/sim/resources/ # kinds.ts · source.ts · grants.ts · host.ts — pure TS +apps/sim/components/resources// # 'use client' — THE view, one per resource +``` + +A resource kind with no canonical view yet (`table`, `knowledge`, `log`, `schedule`) is simply **absent** from `CANONICAL_UNITS` in the check. That is the correct state for an unmigrated kind. Do not add a flag, a shim, or a placeholder entry for it. + +## Consume: construct the axes, then mount + +That is the whole job. Same component, same props; only the constructed values differ. + +```typescript +// app/f/[token]/public-file-view.tsx — anonymous share +const source = useMemo( + () => shareSource({ kind: 'file', token, grantId: token, seed: { name, type, size, version } }), + [token, name, type, size, version] +) +return +``` + +```typescript +// .../mothership-view/.../resource-content.tsx — panel, same view +const source = useMemo( + () => workspaceSource({ kind: 'file', workspaceId, resourceId: file.id }), + [workspaceId, file.id] +) +const grants = useMemo(() => grantsFromPermissions(permissions), [permissions]) +return +``` + +- Import from the **unit barrel** (`@/components/resources/file-view`), never a file inside it. +- Copy that differs between workspace and share belongs on the **source** (`source.unavailableCopy`), not in the view. A share must never say "workspace" — that is what stops the view becoming an existence oracle. +- Links belong on the source too (`source.hrefFor(link)`), which returns `null` in share scope so nobody hand-builds `/workspace/${token}/…`. +- `host` decides chrome and URL ownership. `hostOwnsUrl(host)` is the single place the "embedded views do not write nuqs keys" rule lives. + +## Never do this + +**Never wrap a view.** A component whose body is a canonical view with its own props forwarded in is a wrapper. `check:resources` fails on the first one (`wrapperMounts` is at `0`). + +```typescript +// ✗ Bad — adds a name, a file, and an import hop; adds no behavior. +export function EmbeddedFilePanel({ source, grants, host }: EmbeddedFilePanelProps) { + return +} + +// ✓ Good — the consumer constructs the axes and mounts the view itself. +const source = workspaceSource({ kind: 'file', workspaceId, resourceId }) +return +``` + +**Never add a fourth spelling.** If the view cannot express what you need, change `source` / `grants` / `host` — one place, every consumer — or collapse the need into an existing optional object (`streaming`, `editing`). Do not add a loose prop. + +```typescript +// ✗ Bad — three axes, spelled four wrong ways. + + +// ✓ Good + +``` + +**Never reimplement.** If a view has no seam for what you need, **add the seam**. A hand-rolled mini-table loses booleans, JSON, dates, links, resource chips, pinned columns and windowing — every one of which the real view already handles. + +**Never reach past the barrel.** + +```typescript +// ✗ Bad — binds you to the unit's private layout +import { resolveFileCategory } from '@/components/resources/file-view/file-category' + +// ✓ Good +import { resolveFileCategory } from '@/components/resources/file-view' +``` + +The one sanctioned exception is a `lazy()` code-split point, where routing through the barrel silently re-attaches the split chunk (`apps/sim` has no `sideEffects: false`). Those go in `INTERNAL_IMPORT_ALLOWLIST` in the check, keyed by importer **and** specifier. + +**Never import the workspace route tree from an anonymous surface.** `app/f/**`, `app/i/**`, `app/(interfaces)/**`, `app/(shared)/**` and public API routes may not import `@/app/workspace/[workspaceId]/**`. Shared units live in `apps/sim/components/resources/**`. Nesting under a `[workspaceId]` segment is exactly why `workspaceId: string` once read as natural on a component anonymous visitors mounted with a **share token**. + +**Never read route or permission context inside a unit.** No `useRouter`, `useParams`, `useSearchParams`, `usePathname`, `useQueryState(s)`, or `useUserPermissionsContext` under `apps/sim/components/resources/**`. Addressing is `source`, navigation targets are `source.hrefFor(link)`, capability is `grants`, URL ownership is `host`. A component that falls back to `useParams()` can only ever exist once per page. + +**Never put `'use client'` in `apps/sim/resources/**`.** Next rewrites every export of a `'use client'` module into a client reference in the server bundle, so the Server Component that builds a share source would throw at runtime. + +## Escape hatch + +Four annotations, reason mandatory, on the line directly above the offending mount / import / attribute (up to three preceding comment lines of extra context are tolerated): + +```typescript +// boundary-resource-wrapper: +// boundary-resource-internal: +// boundary-resource-tree: +// boundary-resource-prop: +``` + +An annotation with an empty reason is still a finding **and** trips `annotationsMissingReason`. Whole-file exceptions go through `INTERNAL_IMPORT_ALLOWLIST` / `CROSS_TREE_ALLOWLIST` in `scripts/check-resource-views.ts`, not per-line annotations. + +## Checklist before you add a component near a resource + +1. Does a canonical view already exist for this kind? Mount it. +2. Does it exist but lack a seam? Add the seam in the unit and thread it — do not fork the UI. +3. Is your new component only forwarding props into a view? Delete it; mount the view at the call site. +4. Are you about to write `embedded`, `canEdit`, `canRun`, `isPublic`, `token` or `workspaceId` on a view? Map it to `source` / `grants` / `host`. +5. Run `bun run check:resources`. The success metric is **consumers per view going up and component count going down**. diff --git a/.cursor/rules/sim-resource-views.mdc b/.cursor/rules/sim-resource-views.mdc new file mode 100644 index 00000000000..2695b178c56 --- /dev/null +++ b/.cursor/rules/sim-resource-views.mdc @@ -0,0 +1,99 @@ +--- +description: Resource views — one view per resource, many consumers, no wrappers +globs: ["apps/sim/resources/**/*.ts", "apps/sim/components/resources/**/*.ts", "apps/sim/components/resources/**/*.tsx", "apps/sim/app/workspace/[workspaceId]/**/*.tsx", "apps/sim/app/f/**/*.tsx", "apps/sim/app/i/**/*.tsx", "apps/sim/app/(interfaces)/**/*.tsx"] +--- +# Resource Views + +A **resource** is a thing a workspace holds that can also be shared: a file, a table, an interface, a knowledge base, a log, a scheduled task. A resource with a canonical view has **exactly one**, and every consumer mounts that one — the workspace page, the mothership panel, an interface module, the public share page. + +**One view per resource. Consumers construct the axes and mount it. They never wrap it.** + +Enforced by `bun run check:resources`; strict gate `bun run check:resources:strict`. + +## Three axes, nothing else + +| Axis | Type | Replaces | +| --- | --- | --- | +| `source` | `WorkspaceSource \| ShareSource`, discriminated on `via` | `workspaceId`, `token`, `contentSource`, `isPublic` | +| `grants` | `{ write: boolean; run: 'none'\|'deployed'\|'draft' }` | `canEdit`, `canRun`, `canAdmin`, `disableEdit/Insert/Delete` | +| `host` | `'page' \| 'panel' \| 'public'` | `embedded`, `isEmbedded`, `compact`, `minimal` | + +There is no fourth axis. Agent streaming is one optional prop on `FileView` (`streaming?`), because only files stream. + +`ShareSource` declares `workspaceId?: never`; `WorkspaceSource` declares `token?: never`. A share source cannot carry a workspace id — compile error, not convention. + +``` +apps/sim/resources/ # kinds/source/grants/host — pure TS, NO 'use client' +apps/sim/components/resources// # 'use client' — THE view, one per resource +``` + +A kind with no canonical view yet (`table`, `knowledge`, `log`, `schedule`) is simply absent from the check's `CANONICAL_UNITS`. Do not add a flag, shim, or placeholder for it. + +## Consume: construct, then mount + +```typescript +// Anonymous share +const source = shareSource({ kind: 'file', token, grantId: token, seed }) +return + +// Panel — same view, same props +const source = workspaceSource({ kind: 'file', workspaceId, resourceId: file.id }) +return +``` + +Import from the unit barrel (`@/components/resources/file-view`), never a file inside it. Scope-dependent copy lives on `source.unavailableCopy`; links on `source.hrefFor(link)` (which returns `null` in share scope). `hostOwnsUrl(host)` is the one place the "embedded views do not write nuqs keys" rule lives. + +## Never + +```typescript +// ✗ Wrapper — adds a name, a file, an import hop; adds no behavior. Fails check:resources. +export function EmbeddedFilePanel({ source, grants, host }: EmbeddedFilePanelProps) { + return +} + +// ✓ The consumer constructs the axes and mounts the view itself +const source = workspaceSource({ kind: 'file', workspaceId, resourceId }) +return +``` + +```typescript +// ✗ A fourth spelling for capability / chrome / address + + +// ✓ + +``` + +```typescript +// ✗ Reaching past the barrel +import { resolveFileCategory } from '@/components/resources/file-view/file-category' + +// ✓ +import { resolveFileCategory } from '@/components/resources/file-view' +``` + +- **Never reimplement** a resource's UI because the view has no seam — add the seam. A hand-rolled mini-table loses booleans, JSON, dates, links, chips, pinned columns and windowing. +- **Never import `@/app/workspace/[workspaceId]/**`** from `app/f/**`, `app/i/**`, `app/(interfaces)/**`, `app/(shared)/**`, or a public API route. Shared units live in `apps/sim/components/resources/**`. +- **Never** use `useRouter` / `useParams` / `useSearchParams` / `usePathname` / `useQueryState(s)` / `useUserPermissionsContext` inside `apps/sim/components/resources/**`. Addressing is `source`, navigation is `source.hrefFor`, capability is `grants`, URL ownership is `host`. +- **Never** put `'use client'` in `apps/sim/resources/**` — a Server Component builds a share source from it during SSR. + +## Escape hatch + +Reason mandatory, on the line directly above the offending mount / import / attribute: + +```typescript +// boundary-resource-wrapper: +// boundary-resource-internal: +// boundary-resource-tree: +// boundary-resource-prop: +``` + +An empty reason is still a finding. Whole-file exceptions go in `INTERNAL_IMPORT_ALLOWLIST` / `CROSS_TREE_ALLOWLIST` in `scripts/check-resource-views.ts`. The only sanctioned deep import is a `lazy()` split point, where the barrel would re-attach the split chunk. + +## Before adding a component near a resource + +1. Does a canonical view exist for this kind? Mount it. +2. Exists but lacks a seam? Add the seam in the unit — do not fork. +3. Only forwarding props into a view? Delete it; mount the view at the call site. +4. About to write `embedded` / `canEdit` / `canRun` / `isPublic` / `token` / `workspaceId` on a view? Map it to `source` / `grants` / `host`. +5. Run `bun run check:resources`. Success is consumers-per-view up, component count down. diff --git a/CLAUDE.md b/CLAUDE.md index a0632e40ad7..02510f550ae 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,7 +4,7 @@ You are a professional software engineer. All code must follow best practices: a ## Global Standards -- **Linting / Audit**: `bun run check:api-validation` must pass on PRs. Do not introduce route-local boundary Zod schemas, direct route Zod imports, or ad-hoc client wire types — see "API Contracts" and "API Route Pattern" below +- **Linting / Audit**: `bun run check:api-validation` and `bun run check:resources` must pass on PRs. Do not introduce route-local boundary Zod schemas, direct route Zod imports, ad-hoc client wire types, or wrappers over a canonical resource view — see "API Contracts", "API Route Pattern", and "Resource Views" below - **Logging**: Import `createLogger` from `@sim/logger`. Use `logger.info`, `logger.warn`, `logger.error` instead of `console.log`. Inside API routes wrapped with `withRouteHandler`, loggers automatically include the request ID — no manual `withMetadata({ requestId })` needed - **API Route Handlers**: All API route handlers (`GET`, `POST`, `PUT`, `DELETE`, `PATCH`) must be wrapped with `withRouteHandler` from `@/lib/core/utils/with-route-handler`. This provides request ID tracking, automatic error logging for 4xx/5xx responses, and unhandled error catching. See "API Route Pattern" section below - **Comments**: Use TSDoc for documentation. No `====` separators. No non-TSDoc comments @@ -41,6 +41,7 @@ apps/ │ ├── hooks/ # Shared hooks (queries/, selectors/) │ ├── lib/ # App-wide utilities │ ├── providers/ # LLM provider integrations +│ ├── resources/ # Resource axes (source/grants/host) — pure TS, server-importable │ ├── stores/ # Zustand stores │ ├── tools/ # Tool definitions │ └── triggers/ # Trigger definitions @@ -413,6 +414,20 @@ Principles when building or migrating shared UI: - Align consumers to the canonical defaults — normal weight, `--text-body` text, `--text-icon` icons. - Verify referenced CSS vars exist — an undefined var silently falls back to `currentColor` (black-bug). +## Resource Views + +A **resource** is a thing a workspace holds that can also be shared — a file, a table, an interface, a knowledge base, a log, a scheduled task. A resource with a canonical view has exactly ONE, in `apps/sim/components/resources//`, mounted by every consumer: the workspace route page, the mothership panel, an interface module, the public share page. + +Views are mounted against exactly **three axes**, defined in `apps/sim/resources/**` (pure TypeScript — no React, no `'use client'`, because a Server Component builds a share source during SSR): + +- `source` — where the data comes from and by what address: `WorkspaceSource | ShareSource`, discriminated on `via`. Replaces `workspaceId`, `token`, `contentSource`, `isPublic`. `ShareSource` declares `workspaceId?: never`, so a share token can no longer be laundered through a workspace-shaped slot. +- `grants` — what this viewer may do: `{ write, run: 'none'|'deployed'|'draft' }`. Replaces `canEdit`, `canRun`, `canAdmin`, `disableEdit/Insert/Delete`. +- `host` — who owns the URL, the router, the document frame: `'page' | 'panel' | 'public'`. Replaces `embedded`. `hostOwnsUrl(host)` is the one place the "embedded views do not write nuqs keys" rule lives. + +There is no fourth axis; agent streaming is one optional prop on `FileView`. Consumers CONSTRUCT the axes and MOUNT the view — never wrap it in a passthrough, never reach past its barrel, never reimplement its UI because it lacks a seam (add the seam), never import `@/app/workspace/[workspaceId]/**` from an anonymous surface (`app/f/**`, `app/i/**`, `app/(interfaces)/**`), and never read `useRouter`/`useParams`/`useQueryState`/`useUserPermissionsContext` inside a unit. A kind with no canonical view yet (`table`, `knowledge`, `log`, `schedule`) is simply absent from the check's `CANONICAL_UNITS` — no flag, shim, or placeholder. + +Enforced by `bun run check:resources` (strict: `check:resources:strict`), which ratchets counters for wrappers, imports past a barrel, cross-tree imports, unsanctioned props, token-as-`workspaceId`, and context leaks. Escape hatches — reason mandatory, on the line directly above: `// boundary-resource-wrapper:`, `// boundary-resource-internal:`, `// boundary-resource-tree:`, `// boundary-resource-prop:`. Full rules in `.claude/rules/sim-resource-views.md`. + ## Testing Use Vitest. Test files: `feature.ts` → `feature.test.ts`. See `.cursor/rules/sim-testing.mdc` for full details. diff --git a/apps/sim/AGENTS.md b/apps/sim/AGENTS.md index 6c52c2df02d..10227490ebd 100644 --- a/apps/sim/AGENTS.md +++ b/apps/sim/AGENTS.md @@ -229,3 +229,11 @@ export function useEntityList(workspaceId?: string) { - **Check existing sources** before duplicating (`lib/` has many utilities) - **Location**: `lib/` (app-wide) → `feature/utils/` (feature-scoped) → inline (single-use) + + +# This is NOT the Next.js you know + +This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` (resolved from this file's directory; in monorepos the `next` package may not be visible from the repo root) before writing any code. Heed deprecation notices. + +This block is written and re-added by `next dev` — verify at `node_modules/next/dist/server/lib/generate-agent-files.js`. Removing it from a diff only re-creates the uncommitted change; committing it with your work keeps the tree clean. + diff --git a/apps/sim/app/(auth)/components/auth-shell.tsx b/apps/sim/app/(auth)/components/auth-shell.tsx index 91f4d324d6c..15536e9ffe1 100644 --- a/apps/sim/app/(auth)/components/auth-shell.tsx +++ b/apps/sim/app/(auth)/components/auth-shell.tsx @@ -1,6 +1,5 @@ import type { ReactNode } from 'react' -import Link from 'next/link' -import { LogoMark, SimWordmark } from '@/app/(landing)/components/navbar/components' +import { Navbar } from '@/app/(landing)/components/navbar' interface AuthShellProps { /** Centered content column (the form, status copy, etc.). */ @@ -15,22 +14,14 @@ interface AuthShellProps { * * Mirrors the landing chrome: it pins the `light` token layer (so the platform's * light-mode `var(--*)` tokens resolve regardless of the visitor's theme), uses - * the canvas/`--text-primary` surface, and renders a logo-only header that reuses - * the landing {@link LogoMark} + {@link SimWordmark} at the same nav gutters. The - * single content column is centered and capped for a calm single-form layout. + * the canvas/`--text-primary` surface, and renders the shared {@link Navbar} in + * `logoOnly` mode — the same wordmark, geometry, and hover as the landing bar. + * The single content column is centered and capped for a calm single-form layout. */ export function AuthShell({ children, footer }: AuthShellProps) { return (
-
- -
+
{children}
diff --git a/apps/sim/app/(interfaces)/chat/[identifier]/chat.tsx b/apps/sim/app/(interfaces)/chat/[identifier]/chat.tsx index 49f71d79272..7b7dc0e38c4 100644 --- a/apps/sim/app/(interfaces)/chat/[identifier]/chat.tsx +++ b/apps/sim/app/(interfaces)/chat/[identifier]/chat.tsx @@ -19,7 +19,6 @@ import { CHAT_ERROR_MESSAGES, CHAT_REQUEST_TIMEOUT_MS } from '@/app/(interfaces) import { useAudioStreaming, useChatStreaming } from '@/app/(interfaces)/chat/hooks' import SSOAuth from '@/ee/sso/components/sso-auth' import { useDeployedChatConfig } from '@/hooks/queries/chats' -import { useGitHubStars } from '@/hooks/queries/github-stars' import { useVoiceSettings } from '@/hooks/queries/voice-settings' const logger = createLogger('ChatClient') @@ -102,7 +101,6 @@ export default function ChatClient({ identifier }: { identifier: string }) { const { data: chatConfigResult, error: chatConfigError } = useDeployedChatConfig(identifier) const { data: voiceSettings } = useVoiceSettings() - const { data: starCount } = useGitHubStars() const sttAvailable = voiceSettings?.sttAvailable === true const authRequired = chatConfigResult?.kind === 'auth' ? chatConfigResult.authType : null @@ -428,7 +426,7 @@ export default function ChatClient({ identifier }: { identifier: string }) { return (
{/* Header component */} - + {/* Message Container component */} -
-
- {customImage && ( - {`${chatConfig?.title - )} -

- {chatConfig?.customizations?.headerText || chatConfig?.title || 'Chat'} -

-
-
- - {!brand.logoUrl && ( -
- - - {/* Only show Sim logo if no custom branding is set */} - - - - -
- )} - + } + nameIcon={ + customImage ? ( + {`${chatConfig?.title + ) : undefined + } + /> ) } diff --git a/apps/sim/app/(interfaces)/chat/components/input/input.tsx b/apps/sim/app/(interfaces)/chat/components/input/input.tsx index 63efde5b042..eb193ec3e73 100644 --- a/apps/sim/app/(interfaces)/chat/components/input/input.tsx +++ b/apps/sim/app/(interfaces)/chat/components/input/input.tsx @@ -13,7 +13,11 @@ const logger = createLogger('ChatInput') const MAX_TEXTAREA_HEIGHT = 200 -interface AttachedFile { +/** Today's deployed-chat budget — unchanged defaults for every existing caller. */ +const DEFAULT_MAX_FILE_BYTES = 10 * 1024 * 1024 +const DEFAULT_MAX_FILES = 15 + +export interface AttachedFile { id: string name: string size: number @@ -29,6 +33,30 @@ export const ChatInput: React.FC<{ onVoiceStart?: () => void voiceOnly?: boolean sttAvailable?: boolean + /** Inert composer — the interface canvas renders one in edit mode, where nothing runs. */ + disabled?: boolean + /** + * Whether the composer pins itself to the bottom of the viewport, which is + * what a full-page chat wants. `false` lets it sit inside a bounded container + * — an interface module's pane — as an ordinary block. + */ + docked?: boolean + /** Placeholder override; defaults to the deployed chat's own prompt. */ + placeholder?: string + /** + * Whether the attach control is offered. Off for surfaces whose run endpoint + * takes no inbound files, so the composer never presents an affordance that + * would silently drop what the user attached. + */ + allowAttachments?: boolean + /** + * Per-file and per-turn caps. Attachments travel inline as base64, so the + * ceiling is really the receiving route's body limit — a surface posting to + * a smaller one lowers these rather than letting the user attach something + * the request will reject. + */ + maxFileSizeBytes?: number + maxFiles?: number }> = ({ onSubmit, isStreaming = false, @@ -36,6 +64,12 @@ export const ChatInput: React.FC<{ onVoiceStart, voiceOnly = false, sttAvailable = false, + disabled = false, + docked = true, + placeholder, + allowAttachments = true, + maxFileSizeBytes = DEFAULT_MAX_FILE_BYTES, + maxFiles = DEFAULT_MAX_FILES, }) => { const fileInputRef = useRef(null) const textareaRef = useRef(null) @@ -53,11 +87,10 @@ export const ChatInput: React.FC<{ }, [inputValue]) const handleFileSelect = async (selectedFiles: FileList | null) => { - if (!selectedFiles) return + if (!selectedFiles || !attachmentsEnabled) return const newFiles: AttachedFile[] = [] - const maxSize = 10 * 1024 * 1024 - const maxFiles = 15 + const maxSize = maxFileSizeBytes for (let i = 0; i < selectedFiles.length; i++) { if (attachedFiles.length + newFiles.length >= maxFiles) break @@ -139,7 +172,12 @@ export const ChatInput: React.FC<{ textareaRef.current?.focus() }, []) - const canSubmit = (inputValue.trim().length > 0 || attachedFiles.length > 0) && !isStreaming + /** One flag for every attach path — button, drag-drop, and the send gate. */ + const attachmentsEnabled = allowAttachments && !disabled + const canSubmit = + (inputValue.trim().length > 0 || (attachmentsEnabled && attachedFiles.length > 0)) && + !isStreaming && + !disabled if (voiceOnly) { return ( @@ -168,8 +206,14 @@ export const ChatInput: React.FC<{ return ( -
-
+
+
{/* Error Messages */} {uploadErrors.length > 0 && (
@@ -197,12 +241,12 @@ export const ChatInput: React.FC<{ onDragEnter={(e) => { e.preventDefault() e.stopPropagation() - if (!isStreaming) setDragCounter((prev) => prev + 1) + if (!isStreaming && attachmentsEnabled) setDragCounter((prev) => prev + 1) }} onDragOver={(e) => { e.preventDefault() e.stopPropagation() - if (!isStreaming) e.dataTransfer.dropEffect = 'copy' + if (!isStreaming && attachmentsEnabled) e.dataTransfer.dropEffect = 'copy' }} onDragLeave={(e) => { e.preventDefault() @@ -213,7 +257,7 @@ export const ChatInput: React.FC<{ e.preventDefault() e.stopPropagation() setDragCounter(0) - if (!isStreaming) handleFileSelect(e.dataTransfer.files) + if (!isStreaming && attachmentsEnabled) handleFileSelect(e.dataTransfer.files) }} > {/* File thumbnails */} @@ -263,8 +307,11 @@ export const ChatInput: React.FC<{ value={inputValue} onChange={(e) => setInputValue(e.target.value)} onKeyDown={handleKeyDown} - placeholder={isDragOver ? 'Drop files here...' : 'Enter a message...'} + placeholder={ + isDragOver ? 'Drop files here...' : (placeholder ?? 'Enter a message...') + } rows={1} + disabled={disabled} className='m-0 h-auto min-h-[24px] w-full resize-none overflow-y-auto overflow-x-hidden border-0 bg-transparent p-1 text-[15px] text-[var(--text-primary)] leading-[24px] caret-[var(--text-primary)] outline-none [-ms-overflow-style:none] [scrollbar-width:none] placeholder:text-[var(--text-muted)] focus-visible:ring-0 focus-visible:ring-offset-0 [&::-webkit-scrollbar]:hidden' /> @@ -272,21 +319,25 @@ export const ChatInput: React.FC<{
{/* Left: attach */}
- - - - - -

Attach files

-
-
+ {allowAttachments && ( + + + + + +

Attach files

+
+
+ )} , path: string): unknown { + if (!path.includes('.')) return undefined + return path.split('.').reduce((current, segment) => { + if (current && typeof current === 'object' && segment in current) { + return (current as Record)[segment] + } + return undefined + }, output) +} + function extractFilesFromData( data: any, files: ChatFile[] = [], @@ -22,15 +44,7 @@ function extractFilesFromData( if (isUserFileWithMetadata(data)) { if (!seenIds.has(data.id)) { seenIds.add(data.id) - files.push({ - id: data.id, - name: data.name, - url: data.url, - key: data.key, - size: data.size, - type: data.type, - context: data.context, - }) + files.push(toChatFileMetadata(data)) } return files } @@ -87,8 +101,7 @@ export function useChatStreaming() { if (lastMessage && lastMessage.type === 'assistant') { const content = latestContent || lastMessage.content - const updatedContent = - content + (content ? '\n\n_Response stopped by user._' : '_Response stopped by user._') + const updatedContent = content ? `${content}\n\n${CHAT_STOPPED_NOTE}` : CHAT_STOPPED_NOTE return [ ...prev.slice(0, -1), @@ -239,74 +252,15 @@ export function useChatStreaming() { const formattedOutputs: string[] = [] let extractedFiles: ChatFile[] = [] - const formatValue = (value: any): string | null => { - if (value === null || value === undefined) { - return null - } - - if (isUserFileWithMetadata(value)) { - return null - } - - if (Array.isArray(value) && value.length === 0) { - return null - } - - if (typeof value === 'string') { - return value - } - - if (typeof value === 'object') { - try { - return `\`\`\`json\n${JSON.stringify(value, null, 2)}\n\`\`\`` - } catch { - return String(value) - } - } - - return String(value) - } - - const getOutputValue = (blockOutputs: Record, path?: string) => { - if (!path || path === 'content') { - if (blockOutputs.content !== undefined) return blockOutputs.content - if (blockOutputs.result !== undefined) return blockOutputs.result - return blockOutputs - } - - if (blockOutputs[path] !== undefined) { - return blockOutputs[path] - } - - if (path.includes('.')) { - return path.split('.').reduce((current, segment) => { - if (current && typeof current === 'object' && segment in current) { - return current[segment] - } - return undefined - }, blockOutputs) - } - - return undefined - } - if (outputConfigs?.length && finalData.output) { for (const config of outputConfigs) { const blockOutputs = finalData.output[config.blockId] if (!blockOutputs) continue - const value = getOutputValue(blockOutputs, config.path) + const value = resolveChatOutputValue(blockOutputs, config.path, resolveDotPath) if (isUserFileWithMetadata(value)) { - extractedFiles.push({ - id: value.id, - name: value.name, - url: value.url, - key: value.key, - size: value.size, - type: value.type, - context: value.context, - }) + extractedFiles.push(toChatFileMetadata(value)) continue } @@ -316,7 +270,7 @@ export function useChatStreaming() { continue } - const formatted = formatValue(value) + const formatted = formatChatOutputValue(value) if (formatted) { formattedOutputs.push(formatted) } @@ -344,7 +298,7 @@ export function useChatStreaming() { } } else if (finalData.success && finalData.output) { const fallbackOutput = Object.values(finalData.output) - .map((block) => formatValue(block)?.trim()) + .map((block) => formatChatOutputValue(block)?.trim()) .filter(Boolean)[0] if (fallbackOutput) { finalContent = fallbackOutput diff --git a/apps/sim/app/(interfaces)/chat/utils/attachments.ts b/apps/sim/app/(interfaces)/chat/utils/attachments.ts new file mode 100644 index 00000000000..568722ed346 --- /dev/null +++ b/apps/sim/app/(interfaces)/chat/utils/attachments.ts @@ -0,0 +1,47 @@ +import type { AttachedFile } from '@/app/(interfaces)/chat/components/input/input' + +/** + * One attachment as the chat wire format carries it: metadata plus the file + * inlined as a base64 data URL. Matches `deployedChatFileSchema` + * (`@/lib/api/contracts/chats`), which is what the server decodes and uploads. + */ +export interface ChatFilePayload { + name: string + size: number + type: string + data: string +} + +/** Reads a `File` into a `data:;base64,…` URL. */ +function fileToBase64(file: File): Promise { + return new Promise((resolve, reject) => { + const reader = new FileReader() + reader.onload = () => resolve(reader.result as string) + reader.onerror = reject + reader.readAsDataURL(file) + }) +} + +/** + * Turns the composer's attachments into the wire payload both chat surfaces + * send — the deployed chat and an interface's chat module. + * + * The `dataUrl` fallback is load-bearing rather than an optimisation: the + * composer only pre-reads *images*, so every other attachment arrives with + * `dataUrl: undefined` and must be read here. Skipping it produces an empty + * `data`, which the server drops with only a warning — the file would vanish + * silently instead of failing. + */ +export async function toChatFilePayloads( + files: readonly AttachedFile[] | undefined +): Promise { + if (!files || files.length === 0) return [] + return Promise.all( + files.map(async (file) => ({ + name: file.name, + size: file.size, + type: file.type, + data: file.dataUrl || (await fileToBase64(file.file)), + })) + ) +} diff --git a/apps/sim/app/(landing)/components/index.ts b/apps/sim/app/(landing)/components/index.ts index 4181286a3a5..8e15831460a 100644 --- a/apps/sim/app/(landing)/components/index.ts +++ b/apps/sim/app/(landing)/components/index.ts @@ -21,6 +21,7 @@ export { Navbar } from './navbar' export { PlatformHeroVisual } from './platform-hero-visual' export { ProductDemo } from './product-demo' export { ShareButton } from './share-button' +export { ShareLinkButton } from './share-link-button' export { SiteStructuredData } from './site-structured-data' export type { SolutionsPageConfig } from './solutions-page' export { SolutionsPage } from './solutions-page' diff --git a/apps/sim/app/(landing)/components/logo-shell/logo-shell.tsx b/apps/sim/app/(landing)/components/logo-shell/logo-shell.tsx index 545d2599da0..6f488ca6f2b 100644 --- a/apps/sim/app/(landing)/components/logo-shell/logo-shell.tsx +++ b/apps/sim/app/(landing)/components/logo-shell/logo-shell.tsx @@ -1,15 +1,14 @@ import type { ReactNode } from 'react' import { cn } from '@sim/emcn' -import Link from 'next/link' -import { LogoMark, SimWordmark } from '@/app/(landing)/components/navbar/components' +import { Navbar } from '@/app/(landing)/components/navbar' /** - * The canonical light, logo-only page frame - a Sim wordmark linking home, no - * marketing menus, on the platform's light tokens (the `light` class pins - * light mode regardless of visitor theme). It is the shared base for every - * surface that wants minimal chrome: the global 404, and the `(interfaces)` - * group (which adds a support footer). The `(auth)` group uses its own - * `AuthShell` with the same look. + * The canonical light, logo-only page frame - the shared {@link Navbar} in + * `logoOnly` mode (Sim wordmark linking home, no marketing menus) on the + * platform's light tokens (the `light` class pins light mode regardless of + * visitor theme). It is the shared base for every surface that wants minimal + * chrome: the global 404, and the `(interfaces)` group (which adds a support + * footer). The `(auth)` group uses its own `AuthShell` with the same navbar. * * Children decide their own layout: pass `center` for a single centered column * (404 message, simple gates); omit it for full-width content (the live chat @@ -27,15 +26,7 @@ interface LogoShellProps { export function LogoShell({ children, center = false, footer }: LogoShellProps) { return (
-
- -
+
diff --git a/apps/sim/app/(landing)/components/navbar/navbar.tsx b/apps/sim/app/(landing)/components/navbar/navbar.tsx index 2cf7458cac6..9a9110c8d8b 100644 --- a/apps/sim/app/(landing)/components/navbar/navbar.tsx +++ b/apps/sim/app/(landing)/components/navbar/navbar.tsx @@ -1,4 +1,5 @@ -import { ChipLink } from '@sim/emcn' +import type { ReactNode } from 'react' +import { ChipLink, chipContentLabelClass, chipGeometryClass, cn } from '@sim/emcn' import Link from 'next/link' import { GitHubChip, @@ -12,7 +13,11 @@ import { import { DEMO_HREF, SIGNUP_HREF } from '@/app/(landing)/constants' /** - * Landing navbar. + * The single navbar for every public surface — the landing marketing bar and, + * via {@link logoOnly}, the minimal header every non-marketing surface wears + * (auth, 404, shared file / interface). One component so the brand mark, geometry, + * and hover behaviour can never drift between surfaces; the only differences are + * what is presented (mega-menus vs a static resource name) and whether CTAs show. * * Sticky `