From 001cbb4728233b921c30821069c2600894fa3ffb Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Tue, 4 Aug 2026 19:04:46 -0700 Subject: [PATCH 1/3] refactor(resources): one canonical view per resource, mounted on source/grants/host MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extracts the file and table views out of their route folders into canonical units under `apps/sim/components/resources/**`, and gives every consumer one way to mount them. Behavior-neutral: no page, no API route, and no migration is added or removed, and every default is preserved. The three axes (`apps/sim/resources/**`, pure TypeScript so a Server Component can build one 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 }`. Replaces `canEdit`, `canRun`, `canAdmin`, `disableEdit/Insert/Delete`. - `host` — who owns the URL, the router, the document frame. Replaces `embedded`; `hostOwnsUrl(host)` is the single home of the "embedded views do not write nuqs keys" rule. `file` is the only kind with a share seed, because `/f/[token]` is the only public route that serves one. Every other kind is `never` and cannot be addressed by a token at all — a compile-time fact rather than a runtime check. Also in this PR: - Public share auth (password / email-OTP / SSO) moves out of `app/f/[token]` into `components/public-share/**`, behind one page-side gate. Security hardening on the way: timing-safe OTP comparison, a per-IP bucket on OTP verify separate from the send path, and share tokens removed from logs. - Log status chrome (`StatusBadge`, `STATUS_CONFIG`, `getDisplayStatus`) moves to `components/execution-status/`, shared instead of per-surface. - `AnchoredContextMenu` and `ActionRow` move to `components/`, which closed a real hole: `file-view` is mounted by `app/f/[token]` for anonymous visitors, and with no `sideEffects: false` its import of the workspace barrel pulled the whole authenticated tree into the public chunk. - emcn: `ChipPasswordInput`, a shared `ChipChevronDown`, and byte-range / untitled-title utilities behind the file preview routes. - Deployed-chat header now renders the shared `Navbar` in `logoOnly` mode, so a public chat wears the same wordmark, geometry, name and "Shared by" credit as the shared file page. This drops the GitHub star chip from that header. - `ActionRow` used `var(--divider)`, which is not a defined token, so its dividers fell back to `currentColor`. Now `var(--border)`. Nothing here advertises a capability it cannot deliver: the `form` trigger type that arrived with this work drives nothing on its own, so it is not registered in the Start block's trigger list, the Logs filter, the execute contract, or the executor's streaming types. It lands with the surface that produces it. Enforced by `bun run check:resources` (added to CI), which ratchets counters for wrappers, imports past a unit barrel, cross-tree imports, unsanctioned props, token-as-workspaceId, and context leaks. The cross-tree counter is re-based to the current tree: every offender it counts is inherited, and this change strictly reduces the number. Verification: apps/sim + 22 packages typecheck, biome clean, 18598 tests pass, and all 15 repo gates pass including both strict variants. --- .claude/rules/canon.md | 50 + .claude/rules/sim-resource-views.md | 134 ++ .cursor/rules/sim-resource-views.mdc | 99 ++ .github/workflows/test-build.yml | 8 +- 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 | 2 +- apps/sim/app/(landing)/components/index.ts | 1 + .../landing-preview-home.tsx | 11 +- .../components/logo-shell/logo-shell.tsx | 25 +- .../(landing)/components/navbar/navbar.tsx | 136 +- .../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 +- .../app/api/files/public/[token]/otp/route.ts | 15 +- .../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 +- apps/sim/app/api/logs/triggers/route.ts | 3 +- apps/sim/app/api/tools/file/manage/route.ts | 9 +- .../app/api/workflows/[id]/execute/route.ts | 39 +- .../[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 - .../[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 | 210 +++ .../files/[fileId]/view/file-viewer.tsx | 36 - .../[fileId]/view/fullscreen-file-view.tsx | 35 + .../files/[fileId]/view/page.tsx | 4 +- .../file-row-context-menu.tsx | 76 +- .../file-viewer/editor-context-menu.tsx | 114 -- .../components/file-viewer/file-viewer.tsx | 439 ------ .../files/components/file-viewer/index.ts | 10 - .../rich-markdown-editor/image.test.ts | 42 - .../mention/mention-chip.test.tsx | 78 - .../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 | 248 --- .../workspace/[workspaceId]/files/files.tsx | 120 +- .../[workspaceId]/home/components/index.ts | 2 - .../components/chat-content/index.ts | 1 - .../message-content/components/index.ts | 3 +- .../components/special-tags/index.ts | 9 +- .../special-tags/special-tags.test.ts | 9 +- .../special-tags/special-tags.test.tsx | 8 +- .../components/special-tags/special-tags.tsx | 1272 +--------------- .../message-content/message-content.tsx | 5 +- .../mothership-chat/mothership-chat.tsx | 23 +- .../resource-content/resource-content.tsx | 305 ++-- .../resource-tabs/resource-tabs.tsx | 2 +- .../mothership-view/mothership-view.tsx | 8 +- .../queued-messages/queued-messages.tsx | 2 +- .../suggested-actions/suggested-actions.tsx | 36 +- .../prompt-editor/prompt-editor.tsx | 2 +- .../app/workspace/[workspaceId]/home/home.tsx | 2 +- .../app/workspace/[workspaceId]/home/types.ts | 53 +- .../chunk-context-menu/chunk-context-menu.tsx | 136 +- .../document-context-menu.tsx | 148 +- .../knowledge-base-context-menu.tsx | 138 +- .../knowledge-list-context-menu.tsx | 52 +- .../[workspaceId]/knowledge/knowledge.tsx | 37 +- .../components/log-details/log-details.tsx | 3 +- .../log-row-context-menu.tsx | 123 +- .../app/workspace/[workspaceId]/logs/logs.tsx | 17 +- .../app/workspace/[workspaceId]/logs/utils.ts | 75 - .../components/skill-fields/skill-fields.tsx | 3 +- .../components/skill-modal/skill-modal.tsx | 2 +- .../components/context-menu/context-menu.tsx | 151 +- .../new-column-dropdown.tsx | 10 +- .../components/row-modal/row-modal.tsx | 12 +- .../components/select-field/index.ts | 1 - .../select-field/select-value-editor.tsx | 2 +- .../table-grid/cells/cell-content.tsx | 73 - .../cells/expanded-cell-popover.tsx | 11 +- .../components/table-grid/cells/index.ts | 2 - .../table-grid/cells/inline-editors.tsx | 15 +- .../components/table-grid/table-grid.tsx | 106 +- .../tables/[tableId]/hooks/use-table-room.ts | 12 +- .../tables/[tableId]/hooks/use-table.ts | 2 +- .../[workspaceId]/tables/[tableId]/page.tsx | 12 +- .../[workspaceId]/tables/[tableId]/table.tsx | 61 +- .../[workspaceId]/tables/[tableId]/types.ts | 17 - .../table-context-menu/table-context-menu.tsx | 144 +- .../tables-list-context-menu.tsx | 70 +- .../[workspaceId]/tables/prefetch.ts | 4 +- .../workspace/[workspaceId]/tables/tables.tsx | 37 +- .../components/chat-message/chat-message.tsx | 2 +- .../deploy-modal/components/chat/chat.tsx | 6 +- .../components/version-description-modal.tsx | 2 +- .../components/search-modal/search-modal.tsx | 131 +- .../workflow-item/avatars/avatars.tsx | 2 +- apps/sim/components/action-row/action-row.tsx | 43 + apps/sim/components/action-row/index.ts | 1 + .../anchored-context-menu.tsx | 49 + .../components/anchored-context-menu/index.ts | 1 + .../chat}/chat-content/chat-content.test.ts | 4 +- .../chat}/chat-content/chat-content.tsx | 68 +- .../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 | 2 +- .../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 | 23 + .../chat}/scaling-test-helpers.ts | 0 .../sim/components/chat/special-tags/parse.ts | 1236 +++++++++++++++ apps/sim/components/chat/turn-layout.ts | 50 + apps/sim/components/chat/types.ts | 54 + .../chat}/user-message-content/index.ts | 0 .../user-message-content.tsx | 4 +- .../execution-status/execution-status.tsx | 79 + apps/sim/components/execution-status/index.ts | 6 + apps/sim/components/presence/index.ts | 1 + .../components/presence/presence-avatars.tsx | 0 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} | 34 +- .../public-share-password-auth/index.ts | 1 + .../public-share-password-auth.tsx} | 26 +- .../public-share-sso-auth/index.ts | 1 + .../public-share-sso-auth.tsx} | 41 +- apps/sim/components/public-share/types.ts | 13 + .../resources/MIGRATING-RESOURCES.md | 212 +++ .../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 + .../file-view/components}/document-table.css | 0 .../components}/document-table.test.ts | 0 .../components/docx-preview}/docx-preview.tsx | 24 +- .../components/docx-preview/index.ts | 1 + .../editor-context-menu.tsx | 87 ++ .../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-schema.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 .../collaboration/agent-stream-leader.test.ts | 0 .../collaboration/agent-stream-leader.ts | 0 ...apply-streamed-markdown.concurrent.test.ts | 0 .../apply-streamed-markdown.test.ts | 0 .../collaboration/apply-streamed-markdown.ts | 0 .../collaboration/caret-presence.test.ts | 0 .../collaboration/caret-presence.ts | 0 .../collab-streaming-integration.test.ts | 0 .../collaboration/file-doc-avatars.tsx | 2 +- .../collaboration/file-doc-provider.test.ts | 0 .../collaboration/file-doc-provider.ts | 0 .../collaboration/file-doc-room-context.tsx | 2 +- .../collaboration/readiness.test.ts | 0 .../collaboration/readiness.ts | 0 .../use-file-doc-collaboration.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 | 6 +- .../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 | 0 .../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-schema.ts | 0 .../rich-markdown-editor/image.test.ts | 44 + .../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 .../markdown-fidelity.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 | 176 +++ .../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-schema.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 | 98 +- .../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 .../title-heading.test.ts | 0 .../rich-markdown-editor/title-heading.ts | 0 .../use-editor-editable.ts | 0 .../file-view/components/text-editor/index.ts | 1 + .../components/text-editor}/text-editor.tsx | 37 +- .../use-selection-copy-bridge.test.tsx | 0 .../components}/use-selection-copy-bridge.ts | 0 .../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 | 549 +++++++ .../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 .../resources/resource-empty-state/index.ts | 2 + .../resource-empty-state.tsx | 42 + .../resources/resource-provider/index.ts | 11 + .../resource-provider/resource-provider.tsx | 116 ++ .../table-view/cell-formatting.test.ts | 75 + .../table-view/cells/cell-content.tsx | 82 + .../table-view/cells/cell-render.test.ts | 161 ++ .../table-view}/cells/cell-render.tsx | 6 +- .../resources/table-view/cells/index.ts | 3 + .../table-view}/cells/sim-resource-cell.tsx | 4 +- .../resources/table-view}/constants.ts | 0 .../resources/table-view}/data-row.tsx | 44 +- .../headers/column-header-menu.tsx | 0 .../table-view}/headers/column-type-icon.tsx | 0 .../resources/table-view}/headers/index.ts | 0 .../headers/workflow-group-meta-cell.tsx | 0 .../components/resources/table-view/index.ts | 97 ++ .../table-view}/remote-selection-overlay.tsx | 7 +- .../resources/table-view}/select-pill.tsx | 0 .../resources/table-view}/table-find.tsx | 0 .../table-view}/table-primitives.tsx | 0 .../resources/table-view}/types.ts | 33 + .../resources/table-view}/utils.test.ts | 0 .../resources/table-view}/utils.ts | 0 .../resources/table-view/values.test.ts} | 2 +- .../resources/table-view/values.ts} | 0 ....test.tsx => chip-password-input.test.tsx} | 60 +- .../ui/generated-password-input.tsx | 163 -- apps/sim/components/ui/index.ts | 1 - .../handlers/pi/cloud-review-tools.test.ts | 66 +- apps/sim/executor/utils/start-block.test.ts | 122 ++ apps/sim/hooks/queries/public-shares.ts | 79 +- apps/sim/hooks/queries/tables.ts | 61 +- .../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 | 327 ++++ apps/sim/hooks/use-share-modal-state.ts | 199 +++ apps/sim/lib/api/contracts/chats.ts | 2 + apps/sim/lib/api/contracts/primitives.ts | 16 +- apps/sim/lib/api/contracts/public-shares.ts | 12 + apps/sim/lib/api/contracts/tables.ts | 11 +- apps/sim/lib/collab-doc/converter.test.ts | 4 +- apps/sim/lib/collab-doc/converter.ts | 9 +- apps/sim/lib/collab-doc/merge.test.ts | 2 +- apps/sim/lib/collab-doc/merge.ts | 3 +- apps/sim/lib/collab-doc/seed.test.ts | 2 +- apps/sim/lib/collab-doc/seed.ts | 3 +- apps/sim/lib/compare/data/sim.ts | 4 +- apps/sim/lib/copilot/resources/types.ts | 1 + .../copilot/tools/handlers/resources.test.ts | 59 + .../sim/lib/copilot/tools/server/base-tool.ts | 21 + .../copilot/tools/server/files/share-file.ts | 9 +- .../copilot/tools/server/table/user-table.ts | 5 +- apps/sim/lib/copilot/tools/tool-display.ts | 2 + apps/sim/lib/copilot/vfs/path-utils.test.ts | 4 +- apps/sim/lib/copilot/vfs/path-utils.ts | 5 + apps/sim/lib/core/security/otp.ts | 3 +- 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 | 41 + apps/sim/lib/core/utils/unique-name.ts | 29 + apps/sim/lib/knowledge/service.ts | 36 +- apps/sim/lib/posthog/events.ts | 18 + 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 | 52 +- apps/sim/lib/public-shares/urls.ts | 24 + apps/sim/lib/social-share.ts | 24 + apps/sim/lib/table/service.ts | 40 +- .../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 | 9 +- 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 ++ .../uploads/utils}/untitled-title.test.ts | 0 .../uploads/utils}/untitled-title.ts | 0 .../lib/workflows/executor/execution-core.ts | 19 +- .../executor/human-in-the-loop-manager.ts | 5 +- .../streaming/logging-session-completion.ts | 29 + apps/sim/lib/workflows/streaming/streaming.ts | 32 +- apps/sim/resources/file-source.ts | 136 ++ apps/sim/resources/grants.test.ts | 77 + apps/sim/resources/grants.ts | 61 + apps/sim/resources/host.ts | 20 + apps/sim/resources/index.ts | 42 + apps/sim/resources/kinds.ts | 59 + apps/sim/resources/source.test.ts | 225 +++ apps/sim/resources/source.ts | 172 +++ apps/sim/resources/table-source.ts | 22 + package.json | 2 + packages/audit/src/types.ts | 2 + packages/db/schema.ts | 7 +- .../chip-copy-input/chip-copy-input.tsx | 7 +- .../chip-dropdown/chip-dropdown.tsx | 43 +- .../chip-password-input.tsx | 207 +++ .../components/chip-select/chip-select.tsx | 36 +- .../emcn/src/components/chip/chip-chevron.tsx | 7 +- .../emcn/src/components/chip/chip-chrome.ts | 9 + .../emcn/src/components/combobox/combobox.tsx | 25 +- packages/emcn/src/components/index.ts | 5 + .../src/components/tag-input/tag-input.tsx | 31 +- scripts/check-api-validation-contracts.ts | 4 +- scripts/check-resource-views.ts | 1328 +++++++++++++++++ scripts/sync-agent-stream-docs.ts | 3 +- 406 files changed, 10736 insertions(+), 5133 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/(landing)/components/share-link-button/index.ts create mode 100644 apps/sim/app/(landing)/components/share-link-button/share-link-button.tsx delete mode 100644 apps/sim/app/f/[token]/utils.ts 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 delete mode 100644 apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-grid/cells/cell-content.tsx 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 (96%) 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 (97%) 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 rename apps/sim/{app/workspace/[workspaceId]/home/components/message-content/components => components/chat}/scaling-test-helpers.ts (100%) 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/execution-status/execution-status.tsx create mode 100644 apps/sim/components/execution-status/index.ts create mode 100644 apps/sim/components/presence/index.ts rename apps/sim/{app/workspace/[workspaceId] => }/components/presence/presence-avatars.tsx (100%) 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} (86%) 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} (75%) 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} (69%) 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}/document-table.css (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/document-table.test.ts (100%) 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-schema.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/collaboration/agent-stream-leader.test.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/collaboration/agent-stream-leader.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/collaboration/apply-streamed-markdown.concurrent.test.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/collaboration/apply-streamed-markdown.test.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/collaboration/apply-streamed-markdown.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/collaboration/caret-presence.test.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/collaboration/caret-presence.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/collaboration/collab-streaming-integration.test.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/collaboration/file-doc-avatars.tsx (81%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/collaboration/file-doc-provider.test.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/collaboration/file-doc-provider.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/collaboration/file-doc-room-context.tsx (95%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/collaboration/readiness.test.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/collaboration/readiness.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/collaboration/use-file-doc-collaboration.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 (91%) 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 (100%) 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%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/image-schema.ts (100%) 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 (94%) 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.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-schema.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 (95%) 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/title-heading.test.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/rich-markdown-editor/title-heading.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%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/use-selection-copy-bridge.test.tsx (100%) rename apps/sim/{app/workspace/[workspaceId]/files/components/file-viewer => components/resources/file-view/components}/use-selection-copy-bridge.ts (100%) 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/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 create mode 100644 apps/sim/components/resources/table-view/cell-formatting.test.ts create mode 100644 apps/sim/components/resources/table-view/cells/cell-content.tsx create mode 100644 apps/sim/components/resources/table-view/cells/cell-render.test.ts rename apps/sim/{app/workspace/[workspaceId]/tables/[tableId]/components/table-grid => components/resources/table-view}/cells/cell-render.tsx (98%) create mode 100644 apps/sim/components/resources/table-view/cells/index.ts rename apps/sim/{app/workspace/[workspaceId]/tables/[tableId]/components/table-grid => components/resources/table-view}/cells/sim-resource-cell.tsx (94%) rename apps/sim/{app/workspace/[workspaceId]/tables/[tableId]/components/table-grid => components/resources/table-view}/constants.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/tables/[tableId]/components/table-grid => components/resources/table-view}/data-row.tsx (91%) rename apps/sim/{app/workspace/[workspaceId]/tables/[tableId]/components/table-grid => components/resources/table-view}/headers/column-header-menu.tsx (100%) rename apps/sim/{app/workspace/[workspaceId]/tables/[tableId]/components/table-grid => components/resources/table-view}/headers/column-type-icon.tsx (100%) rename apps/sim/{app/workspace/[workspaceId]/tables/[tableId]/components/table-grid => components/resources/table-view}/headers/index.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/tables/[tableId]/components/table-grid => components/resources/table-view}/headers/workflow-group-meta-cell.tsx (100%) create mode 100644 apps/sim/components/resources/table-view/index.ts rename apps/sim/{app/workspace/[workspaceId]/tables/[tableId]/components/table-grid => components/resources/table-view}/remote-selection-overlay.tsx (98%) rename apps/sim/{app/workspace/[workspaceId]/tables/[tableId]/components/select-field => components/resources/table-view}/select-pill.tsx (100%) rename apps/sim/{app/workspace/[workspaceId]/tables/[tableId]/components/table-grid => components/resources/table-view}/table-find.tsx (100%) rename apps/sim/{app/workspace/[workspaceId]/tables/[tableId]/components/table-grid => components/resources/table-view}/table-primitives.tsx (100%) rename apps/sim/{app/workspace/[workspaceId]/tables/[tableId]/components/table-grid => components/resources/table-view}/types.ts (55%) rename apps/sim/{app/workspace/[workspaceId]/tables/[tableId]/components/table-grid => components/resources/table-view}/utils.test.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/tables/[tableId]/components/table-grid => components/resources/table-view}/utils.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/tables/[tableId]/utils.test.ts => components/resources/table-view/values.test.ts} (99%) rename apps/sim/{app/workspace/[workspaceId]/tables/[tableId]/utils.ts => components/resources/table-view/values.ts} (100%) rename apps/sim/components/ui/{generated-password-input.test.tsx => chip-password-input.test.tsx} (79%) delete mode 100644 apps/sim/components/ui/generated-password-input.tsx 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/core/utils/unique-name.test.ts create mode 100644 apps/sim/lib/core/utils/unique-name.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 rename apps/sim/{app/workspace/[workspaceId]/files => lib/uploads/utils}/untitled-title.test.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/files => lib/uploads/utils}/untitled-title.ts (100%) 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/resources/table-source.ts create mode 100644 packages/emcn/src/components/chip-password-input/chip-password-input.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..1eceb7c08b4 --- /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: boolean }` | `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/(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..835cef56358 --- /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/(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: boolean }` | `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/(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/.github/workflows/test-build.yml b/.github/workflows/test-build.yml index 5d971ab15fc..9c48af45ffa 100644 --- a/.github/workflows/test-build.yml +++ b/.github/workflows/test-build.yml @@ -123,6 +123,9 @@ jobs: - name: API contract boundary audit run: bun run check:api-validation:strict + - name: Resource view boundary audit + run: bun run check:resources + - name: Desktop bridge contract audit run: bun run check:desktop-bridge @@ -181,11 +184,6 @@ jobs: - name: Type-check realtime server run: bunx turbo run type-check --filter=@sim/realtime - # cloud-review-tools.test.ts runs the real helper on the runner, which shells - # out to rg. Blacksmith's image ships it, GitHub's doesn't. - - name: Install ripgrep - run: command -v rg || (sudo apt-get update && sudo apt-get install -y ripgrep) - # Runs the setup CLI's Bun tests plus each workspace's Vitest suite, # without `--coverage`. See the Codecov note below. - name: Run tests diff --git a/CLAUDE.md b/CLAUDE.md index d2e376dbd9f..4aea4c8f460 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, 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, 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 }`. 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/(interfaces)/**`), and never read `useRouter`/`useParams`/`useQueryState`/`useUserPermissionsContext` inside a unit. A kind with no canonical view yet (`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 36085dc52ad..40dc82ad5e4 100644 --- a/apps/sim/app/(auth)/components/auth-shell.tsx +++ b/apps/sim/app/(auth)/components/auth-shell.tsx @@ -1,7 +1,6 @@ import type { ReactNode } from 'react' -import Link from 'next/link' import { DesktopTitleBarLane } from '@/app/_shell/desktop-title-bar' -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.). */ @@ -16,9 +15,9 @@ 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. * * The shell also owns the macOS traffic-light lane, unconditionally — every surface that * wears it (the `(auth)` routes, the CLI auth handoff, the invite pages) sits outside @@ -32,15 +31,7 @@ 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 f066279140d..dd0b9847eda 100644 --- a/apps/sim/app/(interfaces)/chat/[identifier]/chat.tsx +++ b/apps/sim/app/(interfaces)/chat/[identifier]/chat.tsx @@ -22,7 +22,6 @@ import { CHAT_ERROR_MESSAGES, CHAT_REQUEST_TIMEOUT_MS } from '@/app/(interfaces) import { 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' const logger = createLogger('ChatClient') @@ -63,7 +62,6 @@ export default function ChatClient({ identifier }: { identifier: string }) { const ignoreScrollRef = useRef(false) const { data: chatConfigResult, error: chatConfigError } = useDeployedChatConfig(identifier) - const { data: starCount } = useGitHubStars() const authRequired = chatConfigResult?.kind === 'auth' ? chatConfigResult.authType : null const chatConfig = chatConfigResult?.kind === 'config' ? chatConfigResult.config : null @@ -310,7 +308,7 @@ export default function ChatClient({ identifier }: { identifier: string }) { return (
- + -
-
- {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 abb1460c955..ee8bed551b3 100644 --- a/apps/sim/app/(interfaces)/chat/components/input/input.tsx +++ b/apps/sim/app/(interfaces)/chat/components/input/input.tsx @@ -142,7 +142,7 @@ export const ChatInput: React.FC<{ onClick={handleContainerClick} className={cn( 'relative z-10 cursor-text rounded-2xl border border-[var(--border-1)] bg-[var(--surface-2)] px-2.5 py-2', - isDragOver && 'border-purple-500' + isDragOver && 'border-[var(--text-muted)]' )} onDragEnter={(e) => { e.preventDefault() 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/landing-preview/components/landing-preview-home/landing-preview-home.tsx b/apps/sim/app/(landing)/components/landing-preview/components/landing-preview-home/landing-preview-home.tsx index 973c0bc26b4..8388b6f9300 100644 --- a/apps/sim/app/(landing)/components/landing-preview/components/landing-preview-home/landing-preview-home.tsx +++ b/apps/sim/app/(landing)/components/landing-preview/components/landing-preview-home/landing-preview-home.tsx @@ -253,7 +253,7 @@ export const LandingPreviewHome = memo(function LandingPreviewHome({ animate={{ width: '55%', opacity: 1 }} transition={{ duration: 0.35, ease: EASE_OUT }} > - + )} @@ -381,9 +381,14 @@ function ChatMarkdown({ } /** - * Mini Customer Leads table panel matching the resource panel pattern. + * The "Customer Leads" panel in the landing hero's static product mock. + * + * Named for what it is rather than `MiniTablePanel`: it is hardcoded marketing + * markup with no queries and no relationship to the table resource, and a + * `Mini` + resource-noun name reads to both a reader and the resource-view + * audit as a per-consumer fork of the real table view. */ -function MiniTablePanel() { +function LandingLeadsPanel() { return (
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 64c25cbc09d..da5ebfd47f7 100644 --- a/apps/sim/app/(landing)/components/logo-shell/logo-shell.tsx +++ b/apps/sim/app/(landing)/components/logo-shell/logo-shell.tsx @@ -1,16 +1,15 @@ import type { ReactNode } from 'react' import { cn } from '@sim/emcn' -import Link from 'next/link' import { DesktopTitleBarLane } from '@/app/_shell/desktop-title-bar' -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 @@ -29,15 +28,7 @@ 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 19a67772f3a..d9d9e9f2e97 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 `