Skip to content

Commit 48d90c0

Browse files
refactor(dashboards): drop file discovery for a content-type exclusion
With one dashboard per workspace, the listed/unlisted discovery column, its migration, legacy-writer trigger, and the upload backfill script are no longer needed. File listings, search, and workflow file pickers exclude the dashboard content type instead, and the one-dashboard index is now migration 0392.
1 parent c714d45 commit 48d90c0

28 files changed

Lines changed: 56 additions & 29850 deletions

‎apps/sim/app/api/files/uploads/finalizers.ts‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import {
2222
import { getWorkspaceFileSize, type StorageContext } from '@/lib/uploads/shared/types'
2323
import { UploadSessionError, type UploadSessionRecord } from '@/lib/uploads/upload-session/service'
2424
import { readWorkspaceFileUploadProvenance } from '@/lib/uploads/upload-session/workspace-file-provenance'
25-
import { defaultFileDiscovery } from '@/lib/workspace-files/discovery'
2625
import { toV2File } from '@/app/api/v2/files/utils'
2726

2827
export interface UploadActor {
@@ -377,7 +376,6 @@ async function insertOrLoadFileMetadata(
377376
userId: input.userId,
378377
workspaceId: input.workspaceId,
379378
context: input.context,
380-
discovery: defaultFileDiscovery(input.context),
381379
originalName: input.originalName,
382380
displayName: input.originalName,
383381
contentType: input.contentType,

‎apps/sim/ee/workspace-forking/application/content-outbox.ts‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { db } from '@sim/db'
2-
import { fileDiscoveryEnum, workspaceOperationReceipt } from '@sim/db/schema'
2+
import { workspaceOperationReceipt } from '@sim/db/schema'
33
import { and, eq } from 'drizzle-orm'
44
import { z } from 'zod'
55
import { OrchestrationError } from '@/lib/core/orchestration/types'
@@ -107,7 +107,6 @@ const contentPayloadSchema = z
107107
'logs',
108108
'workspace-logos',
109109
]),
110-
discovery: z.enum(fileDiscoveryEnum.enumValues).optional(),
111110
fileName: z.string().max(4096),
112111
contentType: z.string().max(1024),
113112
size: z.number().min(0),

‎apps/sim/ee/workspace-forking/lib/copy/copy-files.ts‎

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import {
2222
} from '@/lib/uploads/core/storage-service'
2323
import { getWorkspaceFileSize, type StorageContext } from '@/lib/uploads/shared/types'
2424
import { MAX_FILE_SIZE } from '@/lib/uploads/utils/validation'
25-
import type { FileDiscovery } from '@/lib/workspace-files/discovery'
2625
import { activeWorkspaceFileConditions } from '@/lib/workspace-files/query-scope'
2726
import { resolveForkFolderMapping } from '@/ee/workspace-forking/lib/copy/copy-workflows'
2827
import {
@@ -50,8 +49,6 @@ function isMarkdownBlob(task: Pick<BlobCopyTask, 'contentType' | 'fileName'>): b
5049
export interface BlobCopyTask {
5150
/** Added after persisted fork jobs shipped; absence marks only copied-file provenance unknown. */
5251
sourceFileId?: string
53-
/** Jobs queued before discovery existed contain only listed workspace files. */
54-
discovery?: FileDiscovery
5552
/** Added after persisted fork jobs shipped; absence marks only copied-file provenance unknown. */
5653
sourceContentUpdatedAtMs?: number
5754
sourceKey: string
@@ -238,7 +235,6 @@ export async function planForkFileCopies(params: {
238235
sourceKey: meta.key,
239236
targetKey,
240237
context: meta.context as StorageContext,
241-
discovery: meta.discovery,
242238
fileName: meta.originalName,
243239
contentType: meta.contentType,
244240
size: getWorkspaceFileSize(meta),
@@ -373,7 +369,6 @@ export async function executeForkFileBlobCopies(
373369
workspaceId: task.workspaceId,
374370
folderId: task.targetFolderId ?? null,
375371
context: task.context,
376-
discovery: task.discovery ?? 'listed',
377372
chatId: null,
378373
originalName: targetOriginalName,
379374
displayName: targetDisplayName,
@@ -422,7 +417,6 @@ export async function executeForkFileBlobCopies(
422417
userId: task.userId,
423418
folderId: task.targetFolderId ?? null,
424419
context: task.context,
425-
discovery: task.discovery ?? 'listed',
426420
chatId: null,
427421
originalName: targetOriginalName,
428422
displayName: targetDisplayName,

‎apps/sim/lib/dashboards/README.md‎

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Table-backed dashboards
22

3-
Each workspace has at most one dashboard, a separate resource with its own sidebar page, resource tab, and Mothership `dashboards get` / `dashboards set` commands. Sim builds it: the page shows an empty state until the first save, and there is no create, rename, move, or folder operation. A partial unique index (migration 0393) keeps one live dashboard per workspace; lifting the limit means dropping it. Storage reuses workspace files with MIME type `text/x-sim-dashboard`; the backing `.dashboard` suffix is hidden from display names. The built-in **create-dashboard** skill documents the syntax without example dashboards or prescribed layouts. Sharing is deferred.
3+
Each workspace has at most one dashboard, a separate resource with its own sidebar page, resource tab, and Mothership `dashboards get` / `dashboards set` commands. Sim builds it: the page shows an empty state until the first save, and there is no create, rename, move, or folder operation. A partial unique index (migration 0392) keeps one live dashboard per workspace; lifting the limit means dropping it. Storage reuses workspace files with MIME type `text/x-sim-dashboard`; the backing `.dashboard` suffix is hidden from display names. The built-in **create-dashboard** skill documents the syntax without example dashboards or prescribed layouts. Sharing is deferred.
44

55
The implementation has three boundaries:
66

@@ -78,19 +78,10 @@ before enabling the flag. Sharing and a tool for capturing the user's displayed
7878
data are deferred.
7979

8080

81-
## File discovery
81+
## Storage
8282

83-
`workspace_files.discovery` separates listing/search membership (`listed` or `unlisted`)
84-
from storage and ownership (`context`). Files listings, Mothership file discovery, resource
85-
pickers, and content search apply this rule in SQL before pagination. Explicit-reference
86-
reads retain their existing authorization; unlisted is not a permission boundary.
87-
88-
The workspace dashboard is an unlisted workspace file at the root. Only the dashboard use case
89-
may write its content type, and the resource picker requests it separately from Files.
90-
Versions, billing, cleanup, and complete workspace copies still include unlisted resources.
91-
92-
Migration 0392 adds the defaulted discovery column and a temporary bridge for old upload
93-
writers during rollout. Script migration 0028 backfills non-workspace uploads in id-keyed
94-
pages of 1,000, including archived uploads, without changing content revisions or ownership.
95-
It contains no dashboard backfill: dashboards have not shipped. Remove the bridge in a
96-
later migration after all discovery-aware writers are deployed.
83+
The workspace dashboard is a workspace file at the root with content type
84+
`text/x-sim-dashboard`. Files listings, file search, and workflow file pickers exclude that
85+
content type, and only the dashboard use case may write it, so the backing file never appears
86+
or changes as an ordinary file. Versions, billing, cleanup, and complete workspace copies
87+
still include it.

‎apps/sim/lib/dashboards/application/dashboards.ts‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ export function dashboardRecord(file: WorkspaceFileRecord) {
4646
/** The unique index guarantees at most one live dashboard per workspace. */
4747
async function findWorkspaceDashboard(workspaceId: string): Promise<WorkspaceFileRecord | null> {
4848
const { files } = await queryWorkspaceFiles(workspaceId, {
49-
discovery: 'unlisted',
5049
contentType: DASHBOARD_CONTENT_TYPE,
5150
sortBy: 'name',
5251
sortOrder: 'asc',
@@ -105,7 +104,6 @@ export const saveWorkspaceDashboard = defineAuthorizedWorkspaceFileUseCase({
105104
{
106105
dashboard: true,
107106
exactName: true,
108-
discovery: 'unlisted',
109107
secretProvenance: EXACT_EMPTY_WORKSPACE_FILE_SECRET_PROVENANCE,
110108
notifyWorkspaceChange: false,
111109
}

‎apps/sim/lib/folders/config.ts‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,13 @@ import {
88
workflowSchedule,
99
workspaceFiles,
1010
} from '@sim/db/schema'
11-
import { and, eq, type SQL } from 'drizzle-orm'
11+
import { eq, type SQL } from 'drizzle-orm'
1212
import type { PgColumn, PgTable } from 'drizzle-orm/pg-core'
1313
import type { FolderResourceType } from '@/lib/api/contracts/folders'
1414
import {
1515
FOLDER_RESOURCE_LABELS,
1616
FOLDER_RESOURCE_SUPPORTS_LOCKING,
1717
} from '@/lib/folders/resource-traits'
18-
import { fileDiscoveryCondition } from '@/lib/workspace-files/discovery'
1918

2019
/**
2120
* Counts of cascaded resources returned by a folder delete/restore, keyed per resource
@@ -511,7 +510,7 @@ export const FOLDER_RESOURCES: Record<FolderResourceType, FolderResourceConfig>
511510
* `workspace_files` also stores copilot/chat/execution artifacts and profile pictures;
512511
* only files surfaced on the Files page live in folders.
513512
*/
514-
scope: and(eq(workspaceFiles.context, 'workspace'), fileDiscoveryCondition()),
513+
scope: eq(workspaceFiles.context, 'workspace'),
515514
},
516515
knowledge_base: {
517516
resourceType: 'knowledge_base',

‎apps/sim/lib/uploads/contexts/workspace/workspace-file-manager.ts‎

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { DASHBOARD_CONTENT_TYPE } from '@/lib/dashboards/resource'
2-
import { type FileDiscovery, fileDiscoveryCondition } from '@/lib/workspace-files/discovery'
32
/**
43
* Workspace file storage system
54
* Files uploaded at workspace level persist indefinitely and are accessible across all workflows
@@ -103,6 +102,7 @@ import { getWorkspaceFileSize, MAX_WORKSPACE_FILE_SIZE } from '@/lib/uploads/sha
103102
import { isMarkdownFile } from '@/lib/uploads/utils/file-utils'
104103
import type { ServableFile } from '@/lib/uploads/utils/file-utils.server'
105104
import { SIM_PAGE_CONTENT_TYPE } from '@/lib/workspace-files/page-compile'
105+
import { workspaceFileContentTypeCondition } from '@/lib/workspace-files/query-scope'
106106
import {
107107
MAX_SIM_PAGE_UPLOAD_SNIFF_BYTES,
108108
restoreSimPageSourceBuffer,
@@ -207,7 +207,6 @@ export interface ActiveWorkspaceContext {
207207
}
208208

209209
interface ListWorkspaceFilesOptions {
210-
discovery?: FileDiscovery
211210
contentType?: string
212211

213212
scope?: WorkspaceFileScope
@@ -277,7 +276,6 @@ const MAX_NUMBERED_COPY_SUFFIX = 20
277276
const MAX_UPLOAD_UNIQUE_RETRIES = 8
278277

279278
interface WorkspaceFileMetadataInsert {
280-
discovery?: FileDiscovery
281279
id: string
282280
key: string
283281
userId: string
@@ -444,7 +442,6 @@ export async function uploadWorkspaceFile(
444442
folderId?: string | null
445443
folderPath?: string
446444
exactName?: boolean
447-
discovery?: FileDiscovery
448445
/** Only the workspace dashboard use case writes dashboard content; it lives at the root. */
449446
dashboard?: true
450447
secretProvenance?: WorkspaceFileSecretProvenance
@@ -554,7 +551,6 @@ export async function uploadWorkspaceFile(
554551
originalName: uniqueName,
555552
contentType: effectiveContentType,
556553
size: effectiveBuffer.length,
557-
discovery: options?.discovery ?? 'listed',
558554
})
559555
if (!inserted) {
560556
throw new FileConflictError(uniqueName)
@@ -1067,7 +1063,6 @@ export async function trackChatUpload(
10671063
chatId,
10681064
messageId: messageId ?? null,
10691065
context: 'mothership',
1070-
discovery: 'unlisted',
10711066
displayName: candidate,
10721067
})
10731068
.where(
@@ -1115,7 +1110,6 @@ export async function trackChatUpload(
11151110
userId,
11161111
workspaceId,
11171112
context: 'mothership',
1118-
discovery: 'unlisted',
11191113
chatId,
11201114
messageId: messageId ?? null,
11211115
originalName: fileName,
@@ -1400,8 +1394,7 @@ export async function listWorkspaceFiles(
14001394
.where(
14011395
and(
14021396
workspaceFileScopeCondition(workspaceId, scope),
1403-
fileDiscoveryCondition(options?.discovery),
1404-
options?.contentType ? eq(workspaceFiles.contentType, options.contentType) : undefined
1397+
workspaceFileContentTypeCondition(options?.contentType)
14051398
)
14061399
)
14071400
.orderBy(workspaceFiles.uploadedAt)
@@ -1437,7 +1430,6 @@ const WORKSPACE_FILE_SORTS = {
14371430
} satisfies Record<V2FileSortBy, readonly KeysetKey<WorkspaceFileRecord>[]>
14381431

14391432
export interface QueryWorkspaceFilesOptions {
1440-
discovery?: FileDiscovery
14411433
contentType?: string
14421434

14431435
scope?: WorkspaceFileScope
@@ -1529,8 +1521,7 @@ export async function queryWorkspaceFiles(
15291521

15301522
const conditions = [
15311523
workspaceFileScopeCondition(workspaceId, scope),
1532-
fileDiscoveryCondition(options.discovery),
1533-
options.contentType ? eq(workspaceFiles.contentType, options.contentType) : undefined,
1524+
workspaceFileContentTypeCondition(options.contentType),
15341525
workspaceFileFolderCondition(folderId),
15351526
workspaceFileFolderScopeCondition(folderScope),
15361527
searchFilter(workspaceFiles.originalName, search),

‎apps/sim/lib/uploads/server/metadata.ts‎

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import {
1010
type StorageContext,
1111
} from '@/lib/uploads/shared/types'
1212
import { inferContextFromKey } from '@/lib/uploads/utils/file-utils'
13-
import { defaultFileDiscovery } from '@/lib/workspace-files/discovery'
1413

1514
const logger = createLogger('FileMetadata')
1615

@@ -158,10 +157,6 @@ async function insertFileMetadataWithExecutor(
158157
organizationId: organizationId || null,
159158
folderId: folderId ?? null,
160159
context,
161-
discovery:
162-
existingDeleted.context === context
163-
? existingDeleted.discovery
164-
: defaultFileDiscovery(context),
165160
originalName,
166161
displayName: originalName,
167162
contentType,
@@ -193,7 +188,6 @@ async function insertFileMetadataWithExecutor(
193188
organizationId: organizationId || null,
194189
folderId: folderId ?? null,
195190
context,
196-
discovery: defaultFileDiscovery(context),
197191
originalName,
198192
displayName: originalName,
199193
contentType,
@@ -250,7 +244,6 @@ async function insertImmutableFileMetadataWithExecutor(
250244
organizationId: organizationId || null,
251245
folderId: folderId ?? null,
252246
context,
253-
discovery: defaultFileDiscovery(context),
254247
originalName,
255248
displayName: originalName,
256249
contentType,
@@ -333,7 +326,6 @@ export async function insertFileMetadataMany(
333326
organizationId: row.organizationId || null,
334327
folderId: row.folderId ?? null,
335328
context: row.context,
336-
discovery: defaultFileDiscovery(row.context),
337329
originalName: row.originalName,
338330
displayName: row.originalName,
339331
contentType: row.contentType,

‎apps/sim/lib/workflows/references/resources.ts‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,10 @@ import { MAX_FOLDERS_PER_WORKSPACE } from '@/lib/folders/constants'
2424
import { parseFolderPath, ROOT_FOLDER_PATH } from '@/lib/folders/paths'
2525
import { loadActiveFolderPathIndex } from '@/lib/folders/queries'
2626
import type { ForkMcpServerMeta, ForkRemapKind } from '@/lib/workflows/references/remap-references'
27-
import { fileDiscoveryCondition } from '@/lib/workspace-files/discovery'
28-
import { activeWorkspaceFileConditions } from '@/lib/workspace-files/query-scope'
27+
import {
28+
activeWorkspaceFileConditions,
29+
workspaceFileContentTypeCondition,
30+
} from '@/lib/workspace-files/query-scope'
2931

3032
export interface ForkResourceCandidate {
3133
id: string
@@ -241,7 +243,7 @@ const fileCandidatesQuery = (executor: DbOrTx, workspaceId: string, keys?: strin
241243
.where(
242244
and(
243245
...activeWorkspaceFileConditions([workspaceId]),
244-
keys ? undefined : fileDiscoveryCondition(),
246+
keys ? undefined : workspaceFileContentTypeCondition(),
245247
keys ? inArray(workspaceFiles.key, keys) : undefined
246248
)
247249
)
@@ -302,7 +304,7 @@ const fileCandidatesWithFolderQuery = (
302304
.where(
303305
and(
304306
...activeWorkspaceFileConditions([workspaceId]),
305-
keys ? undefined : fileDiscoveryCondition(),
307+
keys ? undefined : workspaceFileContentTypeCondition(),
306308
page?.after ? gt(workspaceFiles.id, page.after) : undefined,
307309
keys ? inArray(workspaceFiles.key, keys) : undefined
308310
)

0 commit comments

Comments
 (0)