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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .claude/rules/sim-resource-views.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ paths:

# 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.
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 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.**

Expand All @@ -30,14 +30,14 @@ Enforced by `bun run check:resources` (strict CI gate: `bun run check:resources:

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.
`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` (`table`, `knowledge`, `log`) 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/<unit>/ # '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.
A resource kind with no canonical view yet (`table`, `knowledge`, `log`) 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

Expand Down
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -416,15 +416,15 @@ Principles when building or migrating shared UI:

## 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/<unit>/`, mounted by every consumer: the workspace route page, the mothership panel, the public share page.
A **resource** is a thing a workspace holds that can also be shared — a file, a table, a knowledge base, a log. A resource with a canonical view has exactly ONE, in `apps/sim/components/resources/<unit>/`, 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<K> | ShareSource<K>`, 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.
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`) 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`.

Expand Down
2 changes: 1 addition & 1 deletion apps/sim/app/_shell/desktop-title-bar-surfaces.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const logoShell = read('../(landing)/components/logo-shell/logo-shell.tsx')
const pageHeaderBar = read('../../components/page-header-bar.ts')
const settingsHeader = read('../../components/settings/settings-header.tsx')
const resourceHeader = read(
'../workspace/[workspaceId]/components/resource/components/resource-header/resource-header.tsx'
'../../components/resource/components/resource-header/resource-header.tsx'
)
const mothershipView = read(
'../workspace/[workspaceId]/home/components/mothership-view/mothership-view.tsx'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type {
BreadcrumbEditing,
BreadcrumbItem,
DropdownOption,
} from '@/app/workspace/[workspaceId]/components/resource/components/resource-header'
} from '@/components/resource/components/resource-header'
import type { WorkflowFolder } from '@/stores/folders/types'

export interface FolderBreadcrumbItemsOptions {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import { Folder } from '@sim/emcn/icons'
import type { ResourceCell, ResourceRow } from '@/components/resource/resource'
import { folderRowId } from '@/app/workspace/[workspaceId]/components/folders/folder-row-id'
import type {
ResourceCell,
ResourceRow,
} from '@/app/workspace/[workspaceId]/components/resource/resource'
import type { WorkflowFolder } from '@/stores/folders/types'

const FOLDER_ICON = <Folder className='size-[14px]' />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
'use client'

import { type DragEvent, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import type { RowDragDropConfig } from '@/components/resource/resource'
import { parseFolderedRowId } from '@/app/workspace/[workspaceId]/components/folders/folder-row-id'
import type { RowDragDropConfig } from '@/app/workspace/[workspaceId]/components/resource/resource'

/**
* Private drag payload, namespaced so a drag started on another Sim surface (or an external
Expand Down
78 changes: 39 additions & 39 deletions apps/sim/app/workspace/[workspaceId]/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,47 +1,47 @@
export { ConversationListItem } from './conversation-list-item'
export type { ErrorBoundaryProps, ErrorStateProps } from './error'
export { ErrorShell, ErrorState } from './error'
export { InlineRenameInput } from './inline-rename-input'
export { IntegrationTabsHeader } from './integration-tabs-header'
export { MessageActions } from './message-actions'
export { FloatingOverflowText } from './resource/components/floating-overflow-text'
/**
* `Resource` and `InlineRenameInput` are re-exported from `@/components/**`:
* they moved out of this tree because the canonical resource views under
* `components/resources/**` mount them too, and a shared unit may not import
* `@/app/workspace/[workspaceId]/**`. The workspace tree keeps reaching them
* through this barrel, which stays its single aggregated entry point.
*/
export { InlineRenameInput } from '@/components/inline-rename-input'
export {
type BreadcrumbEditing,
type BreadcrumbItem,
type ChromeActionSpec,
type ColumnOption,
type DropdownOption,
EMPTY_CELL_PLACEHOLDER,
type FilterConfig,
type FilterTag,
FloatingOverflowText,
type MemberFilterOption,
memberFilterOptions,
ownerCell,
} from './resource/components/owner-cell'
export {
type ChromeActionSpec,
type PaginationConfig,
Resource,
type ResourceAction,
type ResourceCell,
type ResourceCellEditing,
ResourceChromeFallback,
} from './resource/components/resource-chrome-fallback'
export type {
BreadcrumbEditing,
BreadcrumbItem,
DropdownOption,
ResourceAction,
} from './resource/components/resource-header'
export type {
ColumnOption,
FilterConfig,
FilterTag,
SearchConfig,
SearchTag,
SortConfig,
} from './resource/components/resource-options'
export { SortDropdown } from './resource/components/resource-options'
export { timeCell } from './resource/components/time-cell'
export type {
PaginationConfig,
ResourceCell,
ResourceCellEditing,
ResourceColumn,
ResourceRow,
ResourceTableHandle,
RowDragDropConfig,
SelectableConfig,
} from './resource/resource'
export { EMPTY_CELL_PLACEHOLDER, Resource } from './resource/resource'
export { useBackgroundContextMenu } from './resource/use-background-context-menu'
type ResourceColumn,
type ResourceRow,
type ResourceTableHandle,
type RowDragDropConfig,
type SearchConfig,
type SearchTag,
type SelectableConfig,
type SortConfig,
SortDropdown,
timeCell,
useBackgroundContextMenu,
} from '@/components/resource'
export { ConversationListItem } from './conversation-list-item'
export type { ErrorBoundaryProps, ErrorStateProps } from './error'
export { ErrorShell, ErrorState } from './error'
export { IntegrationTabsHeader } from './integration-tabs-header'
export { MessageActions } from './message-actions'
export { ResourceTile } from './resource-tile'
export { ShareModal, type ShareModalProps } from './share-modal'
export { SkillTile } from './skill-tile'
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
Tooltip,
} from '@sim/emcn'
import { Folder, Plus } from '@sim/emcn/icons'
import { formatDate } from '@/components/resources/log-view'
import { isBrowserAgentAvailable } from '@/lib/browser-agent/transport'
import {
BROWSER_SESSION_RESOURCE_ID,
Expand All @@ -36,7 +37,6 @@ import type {
MothershipResource,
MothershipResourceType,
} from '@/app/workspace/[workspaceId]/home/types'
import { formatDate } from '@/app/workspace/[workspaceId]/logs/utils'
import { listIntegrations } from '@/blocks/integration-matcher'
import { useFolders } from '@/hooks/queries/folders'
import { useKnowledgeBasesQuery } from '@/hooks/queries/kb/knowledge'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export { AddResourceDropdown, useAvailableResources } from './add-resource-dropdown'
export { ResourceActions, ResourceContent } from './resource-content'
export { ResourceContent, ResourceTabActions } from './resource-content'
export type { ResourceTypeConfig } from './resource-registry'
export {
getResourceConfig,
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { ResourceActions, ResourceContent } from './resource-content'
export { ResourceContent, ResourceTabActions } from './resource-content'
Loading