@tutti-os/workspace-file-preview 0.0.190 → 0.0.192

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/workspaceFilePreviewKinds.ts","../src/core/workspaceFilePreview.ts","../src/core/workspaceFilePreviewController.ts"],"sourcesContent":["/**\n * Preview kind taxonomy and classification.\n *\n * Ownership rules: packages/workspace/file-preview/CONTRACT.md\n */\n\nexport type WorkspaceFilePreviewEntryKind =\n | \"file\"\n | \"directory\"\n | \"folder\"\n | \"unknown\"\n | (string & {});\n\n/**\n * Flat preview routing kind. Built-in surface covers image / video / text;\n * text-degradable kinds fall back to built-in text when no host hook is\n * registered. Hook-only kinds (audio / pdf / Office) stay unsupported until a\n * host registers a renderer.\n */\nexport type WorkspaceFilePreviewKind =\n | \"directory\"\n | \"image\"\n | \"video\"\n | \"audio\"\n | \"text\"\n | \"code\"\n | \"markdown\"\n | \"json\"\n | \"csv\"\n | \"html\"\n | \"pdf\"\n | \"docx\"\n | \"xlsx\"\n | \"pptx\"\n | \"unsupported\";\n\n/** Coarse glyph / icon signal; must not be collapsed into previewKind. */\nexport type WorkspaceFileVisualKind =\n | \"binary\"\n | \"code\"\n | \"directory\"\n | \"document\"\n | \"image\"\n | \"markdown\"\n | \"video\";\n\n/** Built-in render modes the package surface can paint without host hooks. */\nexport type WorkspaceFileBuiltinRenderKind = \"image\" | \"text\" | \"video\";\n\nexport interface WorkspaceFilePreviewEntry {\n kind: WorkspaceFilePreviewEntryKind;\n mtimeMs?: number | null;\n name?: string;\n path: string;\n sizeBytes?: number | null;\n}\n\nexport interface WorkspaceFilePreviewTarget {\n previewKind: WorkspaceFilePreviewKind;\n mtimeMs?: number | null;\n name: string;\n path: string;\n sizeBytes?: number | null;\n}\n\nconst imageExtensions = new Set([\n \"avif\",\n \"gif\",\n \"jpeg\",\n \"jpg\",\n \"png\",\n \"svg\",\n \"webp\"\n]);\n\nconst videoExtensions = new Set([\n \"avi\",\n \"m2ts\",\n \"mkv\",\n \"mov\",\n \"mp4\",\n \"mpeg\",\n \"mpg\",\n \"mts\",\n \"webm\",\n \"wmv\"\n]);\n\n/** Browser-decodable video subset used for mime hints and open-with hosts. */\nexport const workspaceFileBrowserVideoExtensions = new Set([\"mp4\", \"webm\"]);\n\nconst audioExtensions = new Set([\n \"aac\",\n \"flac\",\n \"m4a\",\n \"mp3\",\n \"oga\",\n \"ogg\",\n \"opus\",\n \"wav\"\n]);\n\nconst markdownExtensions = new Set([\"md\", \"mdx\"]);\nconst jsonExtensions = new Set([\"json\", \"jsonc\"]);\nconst csvExtensions = new Set([\"csv\", \"tsv\"]);\nconst htmlExtensions = new Set([\"htm\", \"html\", \"shtml\", \"xhtml\"]);\nconst pdfExtensions = new Set([\"pdf\"]);\nconst docxExtensions = new Set([\"docx\"]);\nconst xlsxExtensions = new Set([\"xlsx\"]);\nconst pptxExtensions = new Set([\"pptx\"]);\n\nconst codeExtensions = new Set([\n \"bash\",\n \"c\",\n \"cc\",\n \"conf\",\n \"cpp\",\n \"cs\",\n \"css\",\n \"go\",\n \"h\",\n \"hpp\",\n \"ini\",\n \"java\",\n \"js\",\n \"jsx\",\n \"lua\",\n \"m\",\n \"mm\",\n \"php\",\n \"plist\",\n \"proto\",\n \"py\",\n \"rb\",\n \"rs\",\n \"sh\",\n \"sql\",\n \"swift\",\n \"toml\",\n \"ts\",\n \"tsx\",\n \"xml\",\n \"yaml\",\n \"yml\",\n \"zsh\"\n]);\n\nconst plainTextExtensions = new Set([\"env\", \"log\", \"txt\"]);\n\nconst textFileNames = new Set([\n \".gitignore\",\n \".npmrc\",\n \".nvmrc\",\n \"dockerfile\",\n \"makefile\",\n \"readme\"\n]);\n\n/** Document-like icons that are not themselves first-class preview kinds. */\nconst documentVisualExtensions = new Set([\n \"csv\",\n \"doc\",\n \"docx\",\n \"log\",\n \"pdf\",\n \"rtf\",\n \"txt\",\n \"xls\",\n \"xlsx\"\n]);\n\nconst textDegradablePreviewKinds = new Set<WorkspaceFilePreviewKind>([\n \"text\",\n \"code\",\n \"markdown\",\n \"json\",\n \"csv\",\n \"html\"\n]);\n\nexport function resolveWorkspaceFileExtension(pathOrName: string): string {\n const name = pathOrName.split(\"/\").pop()?.trim().toLowerCase() ?? \"\";\n const dotIndex = name.lastIndexOf(\".\");\n return dotIndex > 0 ? name.slice(dotIndex + 1) : \"\";\n}\n\nexport function resolveWorkspaceFilePreviewName(\n entry: Pick<WorkspaceFilePreviewEntry, \"name\" | \"path\">\n): string {\n return (\n entry.name?.trim() || entry.path.split(\"/\").pop()?.trim() || entry.path\n );\n}\n\nexport function isWorkspaceFileDirectoryKind(\n kind: WorkspaceFilePreviewEntryKind\n): boolean {\n return kind === \"directory\" || kind === \"folder\";\n}\n\nexport function isTextDegradablePreviewKind(\n kind: WorkspaceFilePreviewKind\n): boolean {\n return textDegradablePreviewKinds.has(kind);\n}\n\n/**\n * Maps a classified preview kind onto the built-in surface renderer, if any.\n * Text-degradable kinds resolve to built-in `text`.\n */\nexport function resolveWorkspaceFileBuiltinRenderKind(\n kind: WorkspaceFilePreviewKind\n): WorkspaceFileBuiltinRenderKind | null {\n if (kind === \"image\" || kind === \"video\") {\n return kind;\n }\n if (isTextDegradablePreviewKind(kind)) {\n return \"text\";\n }\n return null;\n}\n\nexport function resolveWorkspaceFileVisualKind(\n entry: Pick<WorkspaceFilePreviewEntry, \"kind\" | \"name\" | \"path\">\n): WorkspaceFileVisualKind {\n if (isWorkspaceFileDirectoryKind(entry.kind)) {\n return \"directory\";\n }\n\n const extension = resolveWorkspaceFileExtension(\n entry.path || entry.name || \"\"\n );\n if (imageExtensions.has(extension)) {\n return \"image\";\n }\n if (videoExtensions.has(extension)) {\n return \"video\";\n }\n if (markdownExtensions.has(extension)) {\n return \"markdown\";\n }\n if (codeExtensions.has(extension) || jsonExtensions.has(extension)) {\n return \"code\";\n }\n if (\n documentVisualExtensions.has(extension) ||\n htmlExtensions.has(extension) ||\n pptxExtensions.has(extension)\n ) {\n return \"document\";\n }\n return \"binary\";\n}\n\n/**\n * Classifies the flat previewKind for an entry.\n * Directory synonyms (`folder`) are normalized to `directory`.\n */\nexport function classifyWorkspaceFilePreviewKind(\n entry: Pick<WorkspaceFilePreviewEntry, \"kind\" | \"name\" | \"path\">\n): WorkspaceFilePreviewKind {\n if (isWorkspaceFileDirectoryKind(entry.kind)) {\n return \"directory\";\n }\n if (entry.kind !== \"file\") {\n return \"unsupported\";\n }\n\n const name = resolveWorkspaceFilePreviewName(entry);\n const extension = resolveWorkspaceFileExtension(name);\n const normalizedName = name.trim().toLowerCase();\n\n if (imageExtensions.has(extension)) {\n return \"image\";\n }\n if (videoExtensions.has(extension)) {\n return \"video\";\n }\n if (audioExtensions.has(extension)) {\n return \"audio\";\n }\n if (markdownExtensions.has(extension)) {\n return \"markdown\";\n }\n if (jsonExtensions.has(extension)) {\n return \"json\";\n }\n if (csvExtensions.has(extension)) {\n return \"csv\";\n }\n if (htmlExtensions.has(extension)) {\n return \"html\";\n }\n if (pdfExtensions.has(extension)) {\n return \"pdf\";\n }\n if (docxExtensions.has(extension)) {\n return \"docx\";\n }\n if (xlsxExtensions.has(extension)) {\n return \"xlsx\";\n }\n if (pptxExtensions.has(extension)) {\n return \"pptx\";\n }\n if (codeExtensions.has(extension)) {\n return \"code\";\n }\n if (plainTextExtensions.has(extension) || textFileNames.has(normalizedName)) {\n return \"text\";\n }\n\n return \"unsupported\";\n}\n\n/**\n * Returns a preview target when the entry can be presented with the built-in\n * resolve chain (host hook omitted). Hook-only kinds return null here; hosts\n * with renderers should call classify + hasHostRenderer themselves.\n */\nexport function resolveWorkspaceFilePreviewTarget(\n entry: WorkspaceFilePreviewEntry\n): WorkspaceFilePreviewTarget | null {\n const previewKind = classifyWorkspaceFilePreviewKind(entry);\n if (resolveWorkspaceFileBuiltinRenderKind(previewKind) === null) {\n return null;\n }\n\n const target: WorkspaceFilePreviewTarget = {\n previewKind,\n name: resolveWorkspaceFilePreviewName(entry),\n path: entry.path\n };\n if (entry.mtimeMs !== undefined) {\n target.mtimeMs = entry.mtimeMs;\n }\n if (entry.sizeBytes !== undefined) {\n target.sizeBytes = entry.sizeBytes;\n }\n return target;\n}\n\nexport function resolveWorkspaceImageMimeType(\n pathOrName: string\n): string | null {\n switch (resolveWorkspaceFileExtension(pathOrName)) {\n case \"avif\":\n return \"image/avif\";\n case \"gif\":\n return \"image/gif\";\n case \"jpeg\":\n case \"jpg\":\n return \"image/jpeg\";\n case \"png\":\n return \"image/png\";\n case \"svg\":\n return \"image/svg+xml\";\n case \"webp\":\n return \"image/webp\";\n default:\n return null;\n }\n}\n\nexport function resolveWorkspaceVideoMimeType(\n pathOrName: string\n): string | null {\n switch (resolveWorkspaceFileExtension(pathOrName)) {\n case \"mp4\":\n return \"video/mp4\";\n case \"webm\":\n return \"video/webm\";\n default:\n return null;\n }\n}\n\nexport function isWorkspaceFileBrowserHtmlExtension(\n extension: string\n): boolean {\n return htmlExtensions.has(extension);\n}\n\nexport function isWorkspaceFileImageExtension(extension: string): boolean {\n return imageExtensions.has(extension);\n}\n","/**\n * Preview readiness, byte decoding, and loaded-state construction.\n *\n * Ownership rules: packages/workspace/file-preview/CONTRACT.md\n */\n\nimport {\n classifyWorkspaceFilePreviewKind,\n isTextDegradablePreviewKind,\n resolveWorkspaceFileBuiltinRenderKind,\n resolveWorkspaceFilePreviewName,\n resolveWorkspaceFilePreviewTarget,\n resolveWorkspaceImageMimeType,\n resolveWorkspaceVideoMimeType,\n type WorkspaceFilePreviewEntry,\n type WorkspaceFilePreviewKind,\n type WorkspaceFilePreviewTarget\n} from \"./workspaceFilePreviewKinds.ts\";\n\nexport * from \"./workspaceFilePreviewKinds.ts\";\n\nexport type WorkspaceFilePreviewReadonlyReason =\n | \"binary\"\n | \"decode_failed\"\n | \"file_too_large\"\n | \"text_too_large\";\n\nexport type WorkspaceFilePreviewReadiness<\n TEntry extends WorkspaceFilePreviewEntry,\n TTarget extends WorkspaceFilePreviewTarget = WorkspaceFilePreviewTarget\n> =\n | { entry: TEntry; status: \"directory\" }\n | {\n entry: TEntry;\n maxSizeBytes: number;\n reason: Extract<\n WorkspaceFilePreviewReadonlyReason,\n \"file_too_large\" | \"text_too_large\"\n >;\n status: \"readonly\";\n }\n | {\n entry: TEntry;\n previewKind: WorkspaceFilePreviewKind;\n status: \"unsupported\";\n }\n | { entry: TEntry; status: \"ready\"; target: TTarget };\n\nexport type WorkspaceFilePreviewLoadedState<\n TEntry extends WorkspaceFilePreviewEntry,\n TTarget extends WorkspaceFilePreviewTarget\n> =\n | {\n content: string;\n entry: TTarget;\n previewKind: WorkspaceFilePreviewKind;\n status: \"text\";\n }\n | {\n bytes: Uint8Array<ArrayBuffer>;\n contentType: string;\n entry: TTarget;\n previewKind: \"image\";\n status: \"image\";\n }\n | {\n bytes: Uint8Array<ArrayBuffer>;\n contentType: string;\n entry: TTarget;\n previewKind: \"video\";\n status: \"video\";\n }\n | {\n bytes: Uint8Array<ArrayBuffer>;\n contentType: string | null;\n entry: TTarget;\n previewKind: WorkspaceFilePreviewKind;\n status: \"bytes\";\n }\n | {\n entry: TEntry;\n maxSizeBytes?: number;\n reason: WorkspaceFilePreviewReadonlyReason;\n status: \"readonly\";\n };\n\nexport interface ResolveWorkspaceFilePreviewReadinessOptions {\n /**\n * When true for a classified kind, readiness treats the entry as presentable\n * even if it is hook-only (pdf / audio / Office). Built-in presentable kinds\n * do not require this callback.\n */\n hasHostRenderer?: (kind: WorkspaceFilePreviewKind) => boolean;\n}\n\nexport const workspaceFileTextMaxBytes = 1024 * 1024;\nexport const workspaceFilePreviewMaxBytes = 20 * 1024 * 1024;\n\nexport function resolveWorkspaceFilePreviewReadiness<\n TEntry extends WorkspaceFilePreviewEntry\n>(\n entry: TEntry,\n options?: ResolveWorkspaceFilePreviewReadinessOptions\n): WorkspaceFilePreviewReadiness<TEntry> {\n const previewKind = classifyWorkspaceFilePreviewKind(entry);\n if (previewKind === \"directory\") {\n return {\n entry,\n status: \"directory\"\n };\n }\n\n const builtin = resolveWorkspaceFileBuiltinRenderKind(previewKind);\n const hostRenderable =\n options?.hasHostRenderer?.(previewKind) === true &&\n previewKind !== \"unsupported\";\n\n if (!builtin && !hostRenderable) {\n return {\n entry,\n previewKind,\n status: \"unsupported\"\n };\n }\n\n const target =\n resolveWorkspaceFilePreviewTarget(entry) ??\n createWorkspaceFilePreviewTarget(entry, previewKind);\n\n if (\n (builtin === \"text\" || isTextDegradablePreviewKind(previewKind)) &&\n isWorkspaceTextFileTooLarge(entry.sizeBytes)\n ) {\n return {\n entry,\n maxSizeBytes: workspaceFileTextMaxBytes,\n reason: \"text_too_large\",\n status: \"readonly\"\n };\n }\n\n if (isWorkspacePreviewFileTooLarge(entry.sizeBytes)) {\n return {\n entry,\n maxSizeBytes: workspaceFilePreviewMaxBytes,\n reason: \"file_too_large\",\n status: \"readonly\"\n };\n }\n\n return {\n entry,\n status: \"ready\",\n target\n };\n}\n\nexport function createWorkspaceFilePreviewLoadedState<\n TEntry extends WorkspaceFilePreviewEntry,\n TTarget extends WorkspaceFilePreviewTarget\n>(input: {\n bytes: Uint8Array | ArrayBuffer;\n contentType?: string | null;\n entry: TEntry;\n /**\n * When true, skip built-in text degradation and keep raw bytes for a host\n * renderer (hook-only kinds, or text-degradable kinds with a host hook).\n */\n preferHostBytes?: boolean;\n target: TTarget;\n}): WorkspaceFilePreviewLoadedState<TEntry, TTarget> {\n const previewKind = input.target.previewKind;\n const builtin = resolveWorkspaceFileBuiltinRenderKind(previewKind);\n\n if (builtin === \"image\") {\n return {\n bytes: copyWorkspaceFilePreviewBytes(input.bytes),\n contentType:\n input.contentType ??\n resolveWorkspaceImageMimeType(input.target.name) ??\n \"application/octet-stream\",\n entry: input.target,\n previewKind: \"image\",\n status: \"image\"\n };\n }\n if (builtin === \"video\") {\n return {\n bytes: copyWorkspaceFilePreviewBytes(input.bytes),\n contentType:\n input.contentType ??\n resolveWorkspaceVideoMimeType(input.target.name) ??\n \"application/octet-stream\",\n entry: input.target,\n previewKind: \"video\",\n status: \"video\"\n };\n }\n\n if (input.preferHostBytes || builtin === null) {\n return {\n bytes: copyWorkspaceFilePreviewBytes(input.bytes),\n contentType: input.contentType ?? null,\n entry: input.target,\n previewKind,\n status: \"bytes\"\n };\n }\n\n try {\n const content = decodeWorkspaceTextFile(input.bytes);\n if (looksLikeBinaryText(content)) {\n return {\n entry: input.entry,\n reason: \"binary\",\n status: \"readonly\"\n };\n }\n return {\n content,\n entry: input.target,\n previewKind,\n status: \"text\"\n };\n } catch {\n return {\n entry: input.entry,\n reason: \"decode_failed\",\n status: \"readonly\"\n };\n }\n}\n\nexport function isWorkspaceTextFileTooLarge(\n sizeBytes?: number | null\n): boolean {\n return (\n typeof sizeBytes === \"number\" &&\n Number.isFinite(sizeBytes) &&\n sizeBytes > workspaceFileTextMaxBytes\n );\n}\n\nexport function isWorkspacePreviewFileTooLarge(\n sizeBytes?: number | null\n): boolean {\n return (\n typeof sizeBytes === \"number\" &&\n Number.isFinite(sizeBytes) &&\n sizeBytes > workspaceFilePreviewMaxBytes\n );\n}\n\nexport function decodeWorkspaceTextFile(\n bytes: Uint8Array | ArrayBuffer\n): string {\n return new TextDecoder(\"utf-8\", { fatal: true }).decode(\n normalizeWorkspaceFilePreviewBytes(bytes)\n );\n}\n\nexport function normalizeWorkspaceFilePreviewBytes(\n value: Uint8Array | ArrayBuffer\n): Uint8Array {\n if (value instanceof Uint8Array) {\n return value;\n }\n return new Uint8Array(value);\n}\n\nexport function copyWorkspaceFilePreviewBytes(\n bytes: Uint8Array | ArrayBuffer\n): Uint8Array<ArrayBuffer> {\n const normalized = normalizeWorkspaceFilePreviewBytes(bytes);\n const buffer = new ArrayBuffer(normalized.byteLength);\n const copy = new Uint8Array(buffer);\n copy.set(normalized);\n return copy;\n}\n\nexport function looksLikeBinaryText(content: string): boolean {\n if (content.length === 0) {\n return false;\n }\n\n const sample = content.slice(0, 4096);\n if (sample.includes(\"\\u0000\")) {\n return true;\n }\n\n let suspiciousControlChars = 0;\n for (let index = 0; index < sample.length; index += 1) {\n const code = sample.charCodeAt(index);\n const isAllowedWhitespace = code === 9 || code === 10 || code === 13;\n const isControlChar = code < 32 || (code >= 127 && code <= 159);\n if (isControlChar && !isAllowedWhitespace) {\n suspiciousControlChars += 1;\n }\n }\n\n return suspiciousControlChars / sample.length > 0.12;\n}\n\nexport function formatWorkspacePreviewByteLimit(sizeBytes: number): string {\n if (sizeBytes < 1024) {\n return `${sizeBytes} B`;\n }\n\n const mebibytes = sizeBytes / (1024 * 1024);\n if (Number.isInteger(mebibytes)) {\n return `${mebibytes} MiB`;\n }\n return `${mebibytes.toFixed(1)} MiB`;\n}\n\nfunction createWorkspaceFilePreviewTarget(\n entry: WorkspaceFilePreviewEntry,\n previewKind: WorkspaceFilePreviewKind\n): WorkspaceFilePreviewTarget {\n const target: WorkspaceFilePreviewTarget = {\n previewKind,\n name: resolveWorkspaceFilePreviewName(entry),\n path: entry.path\n };\n if (entry.mtimeMs !== undefined) {\n target.mtimeMs = entry.mtimeMs;\n }\n if (entry.sizeBytes !== undefined) {\n target.sizeBytes = entry.sizeBytes;\n }\n return target;\n}\n","/**\n * Shared preview loading lifecycle.\n *\n * Ownership rules: packages/workspace/file-preview/CONTRACT.md\n */\n\nimport {\n createWorkspaceFilePreviewLoadedState,\n resolveWorkspaceFileBuiltinRenderKind,\n resolveWorkspaceFilePreviewReadiness,\n type WorkspaceFilePreviewEntry,\n type WorkspaceFilePreviewKind,\n type WorkspaceFilePreviewReadonlyReason,\n type WorkspaceFilePreviewTarget\n} from \"./workspaceFilePreview.ts\";\n\nexport type WorkspaceFilePreviewUnsupportedReason =\n | \"file_type\"\n | \"reader_unavailable\";\n\nexport type WorkspaceFilePreviewControllerState<TEntry> =\n | { status: \"empty\" }\n | { entry: TEntry; status: \"directory\" }\n | {\n entry: TEntry;\n previewKind: WorkspaceFilePreviewKind;\n status: \"loading\";\n }\n | {\n content: string;\n entry: TEntry;\n previewKind: WorkspaceFilePreviewKind;\n previewSizeBytes: number;\n status: \"text\";\n }\n | {\n entry: TEntry;\n objectUrl: string;\n previewKind: \"image\";\n previewSizeBytes: number;\n status: \"image\";\n }\n | {\n entry: TEntry;\n objectUrl: string;\n previewKind: \"video\";\n previewSizeBytes: number;\n status: \"video\";\n }\n | {\n bytes: Uint8Array<ArrayBuffer>;\n contentType: string | null;\n entry: TEntry;\n previewKind: WorkspaceFilePreviewKind;\n previewSizeBytes: number;\n status: \"bytes\";\n }\n | {\n entry: TEntry;\n maxSizeBytes?: number;\n previewKind?: WorkspaceFilePreviewKind;\n previewSizeBytes?: number;\n reason: WorkspaceFilePreviewReadonlyReason;\n status: \"readonly\";\n }\n | {\n entry: TEntry;\n previewKind?: WorkspaceFilePreviewKind;\n reason: WorkspaceFilePreviewUnsupportedReason;\n status: \"unsupported\";\n }\n | {\n entry: TEntry;\n error: unknown;\n previewKind?: WorkspaceFilePreviewKind;\n status: \"error\";\n };\n\nexport interface WorkspaceFilePreviewReadInput<TEntry> {\n entry: TEntry;\n signal: AbortSignal;\n target: WorkspaceFilePreviewTarget;\n}\n\n/**\n * Host read result. Bare Uint8Array / ArrayBuffer are intentionally not\n * accepted; wrap bytes in this object shape.\n */\nexport interface WorkspaceFilePreviewReadResult {\n bytes: Uint8Array | ArrayBuffer;\n contentType?: string | null;\n /** Optional host override of the classified preview kind. */\n kind?: WorkspaceFilePreviewKind;\n}\n\nexport interface WorkspaceFilePreviewObjectUrlFactory {\n create(bytes: Uint8Array<ArrayBuffer>, contentType: string): string;\n revoke(objectUrl: string): void;\n}\n\nexport interface CreateWorkspaceFilePreviewControllerInput<TEntry> {\n /**\n * Optional source capability gate. `false` blocks every non-directory read;\n * `true` also lets the reader classify locally unsupported files. When\n * omitted, the controller relies on local file classification.\n */\n canReadEntry?: (entry: TEntry) => boolean;\n getEntryKey?: (entry: TEntry) => string;\n /**\n * Host renderer registry probe used by readiness / load planning. Returning\n * true for a kind keeps hook-only kinds presentable and can prefer raw bytes\n * for text-degradable kinds that the host wants to render itself.\n */\n hasHostRenderer?: (kind: WorkspaceFilePreviewKind) => boolean;\n objectUrls?: WorkspaceFilePreviewObjectUrlFactory;\n read?: (\n input: WorkspaceFilePreviewReadInput<TEntry>\n ) => Promise<WorkspaceFilePreviewReadResult | null>;\n toPreviewEntry: (entry: TEntry) => WorkspaceFilePreviewEntry;\n}\n\nexport interface WorkspaceFilePreviewController<TEntry> {\n dispose(): void;\n getSnapshot(): WorkspaceFilePreviewControllerState<TEntry>;\n reload(): Promise<void>;\n setEntry(entry: TEntry | null): Promise<void>;\n subscribe(listener: () => void): () => void;\n}\n\nexport function createWorkspaceFilePreviewController<TEntry>(\n input: CreateWorkspaceFilePreviewControllerInput<TEntry>\n): WorkspaceFilePreviewController<TEntry> {\n return new WorkspaceFilePreviewControllerImpl(input);\n}\n\nclass WorkspaceFilePreviewControllerImpl<\n TEntry\n> implements WorkspaceFilePreviewController<TEntry> {\n private activeEntry: TEntry | null = null;\n private activeEntryKey: string | null = null;\n private abortController: AbortController | null = null;\n private disposed = false;\n private generation = 0;\n private loadPromise: Promise<void> = Promise.resolve();\n private objectUrl: string | null = null;\n private readonly listeners = new Set<() => void>();\n private readonly input: CreateWorkspaceFilePreviewControllerInput<TEntry>;\n private readonly objectUrls: WorkspaceFilePreviewObjectUrlFactory;\n private state: WorkspaceFilePreviewControllerState<TEntry> = {\n status: \"empty\"\n };\n\n constructor(input: CreateWorkspaceFilePreviewControllerInput<TEntry>) {\n this.input = input;\n this.objectUrls = input.objectUrls ?? browserWorkspaceFilePreviewObjectUrls;\n }\n\n dispose(): void {\n if (this.disposed) {\n return;\n }\n this.disposed = true;\n this.cancelLoad();\n this.revokeObjectUrl();\n this.listeners.clear();\n }\n\n getSnapshot(): WorkspaceFilePreviewControllerState<TEntry> {\n return this.state;\n }\n\n reload(): Promise<void> {\n if (this.disposed || !this.activeEntry) {\n return Promise.resolve();\n }\n return this.start(this.activeEntry);\n }\n\n setEntry(entry: TEntry | null): Promise<void> {\n if (this.disposed) {\n return Promise.resolve();\n }\n\n const nextKey = entry ? this.resolveEntryKey(entry) : null;\n if (nextKey === this.activeEntryKey) {\n return this.loadPromise;\n }\n\n this.activeEntry = entry;\n this.activeEntryKey = nextKey;\n if (!entry) {\n this.cancelLoad();\n this.revokeObjectUrl();\n this.updateState({ status: \"empty\" });\n this.loadPromise = Promise.resolve();\n return this.loadPromise;\n }\n return this.start(entry);\n }\n\n subscribe(listener: () => void): () => void {\n if (this.disposed) {\n return () => {};\n }\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n private cancelLoad(): void {\n this.generation += 1;\n this.abortController?.abort();\n this.abortController = null;\n }\n\n private isStale(generation: number): boolean {\n return this.disposed || generation !== this.generation;\n }\n\n private async load(\n entry: TEntry,\n previewEntry: WorkspaceFilePreviewEntry,\n target: WorkspaceFilePreviewTarget,\n generation: number,\n abortController: AbortController\n ): Promise<void> {\n try {\n const result = await this.input.read?.({\n entry,\n signal: abortController.signal,\n target\n });\n if (this.isStale(generation)) {\n return;\n }\n if (!result) {\n this.updateState({\n entry,\n previewKind: target.previewKind,\n reason: \"file_type\",\n status: \"unsupported\"\n });\n return;\n }\n\n const resolvedKind = result.kind ?? target.previewKind;\n const resolvedTarget: WorkspaceFilePreviewTarget = {\n ...target,\n previewKind: resolvedKind\n };\n // Hook-only kinds keep raw bytes for the host renderer. Text-degradable\n // kinds still decode to text; the surface may then prefer a host hook.\n const preferHostBytes =\n resolveWorkspaceFileBuiltinRenderKind(resolvedKind) === null;\n\n const loaded = createWorkspaceFilePreviewLoadedState({\n bytes: result.bytes,\n contentType: result.contentType,\n entry: previewEntry,\n preferHostBytes,\n target: resolvedTarget\n });\n if (this.isStale(generation)) {\n return;\n }\n\n if (loaded.status === \"image\") {\n const previewSizeBytes = loaded.bytes.byteLength;\n const objectUrl = this.objectUrls.create(\n loaded.bytes,\n loaded.contentType\n );\n if (this.isStale(generation)) {\n this.objectUrls.revoke(objectUrl);\n return;\n }\n this.objectUrl = objectUrl;\n this.updateState({\n entry,\n objectUrl,\n previewKind: \"image\",\n previewSizeBytes,\n status: \"image\"\n });\n return;\n }\n if (loaded.status === \"video\") {\n const previewSizeBytes = loaded.bytes.byteLength;\n const objectUrl = this.objectUrls.create(\n loaded.bytes,\n loaded.contentType\n );\n if (this.isStale(generation)) {\n this.objectUrls.revoke(objectUrl);\n return;\n }\n this.objectUrl = objectUrl;\n this.updateState({\n entry,\n objectUrl,\n previewKind: \"video\",\n previewSizeBytes,\n status: \"video\"\n });\n return;\n }\n if (loaded.status === \"text\") {\n this.updateState({\n content: loaded.content,\n entry,\n previewKind: loaded.previewKind,\n previewSizeBytes: result.bytes.byteLength,\n status: \"text\"\n });\n return;\n }\n if (loaded.status === \"bytes\") {\n this.updateState({\n bytes: loaded.bytes,\n contentType: loaded.contentType,\n entry,\n previewKind: loaded.previewKind,\n previewSizeBytes: loaded.bytes.byteLength,\n status: \"bytes\"\n });\n return;\n }\n this.updateState({\n entry,\n previewKind: target.previewKind,\n previewSizeBytes: result.bytes.byteLength,\n reason: loaded.reason,\n ...(loaded.maxSizeBytes === undefined\n ? {}\n : { maxSizeBytes: loaded.maxSizeBytes }),\n status: \"readonly\"\n });\n } catch (error) {\n if (this.isStale(generation)) {\n return;\n }\n this.updateState({\n entry,\n error,\n previewKind: target.previewKind,\n status: \"error\"\n });\n } finally {\n if (!this.isStale(generation)) {\n this.abortController = null;\n }\n }\n }\n\n private resolveEntryKey(entry: TEntry): string {\n if (this.input.getEntryKey) {\n return this.input.getEntryKey(entry);\n }\n const previewEntry = this.input.toPreviewEntry(entry);\n return [\n previewEntry.kind,\n previewEntry.path,\n previewEntry.name ?? \"\",\n previewEntry.sizeBytes ?? \"\",\n previewEntry.mtimeMs ?? \"\"\n ].join(\"\\0\");\n }\n\n private revokeObjectUrl(): void {\n if (!this.objectUrl) {\n return;\n }\n this.objectUrls.revoke(this.objectUrl);\n this.objectUrl = null;\n }\n\n private start(entry: TEntry): Promise<void> {\n this.cancelLoad();\n this.revokeObjectUrl();\n\n const previewEntry = this.input.toPreviewEntry(entry);\n const readiness = resolveWorkspaceFilePreviewReadiness(previewEntry, {\n hasHostRenderer: this.input.hasHostRenderer\n });\n\n if (readiness.status === \"directory\") {\n this.updateState({ entry, status: \"directory\" });\n this.loadPromise = Promise.resolve();\n return this.loadPromise;\n }\n\n const canRead = this.input.canReadEntry?.(entry);\n if (canRead === false) {\n this.updateState({\n entry,\n reason: \"reader_unavailable\",\n status: \"unsupported\"\n });\n this.loadPromise = Promise.resolve();\n return this.loadPromise;\n }\n\n if (readiness.status === \"unsupported\") {\n if (canRead !== true) {\n this.updateState({\n entry,\n previewKind: readiness.previewKind,\n reason: \"file_type\",\n status: \"unsupported\"\n });\n this.loadPromise = Promise.resolve();\n return this.loadPromise;\n }\n if (!this.input.read) {\n this.updateState({\n entry,\n previewKind: readiness.previewKind,\n reason: \"reader_unavailable\",\n status: \"unsupported\"\n });\n this.loadPromise = Promise.resolve();\n return this.loadPromise;\n }\n // Source claimed it can classify/read locally unsupported files as text.\n const target: WorkspaceFilePreviewTarget = {\n previewKind: \"text\",\n name:\n previewEntry.name ??\n previewEntry.path.split(\"/\").pop() ??\n previewEntry.path,\n path: previewEntry.path,\n ...(previewEntry.mtimeMs === undefined\n ? {}\n : { mtimeMs: previewEntry.mtimeMs }),\n ...(previewEntry.sizeBytes === undefined\n ? {}\n : { sizeBytes: previewEntry.sizeBytes })\n };\n return this.beginLoad(entry, previewEntry, target);\n }\n\n if (readiness.status === \"readonly\") {\n this.updateState({\n entry,\n maxSizeBytes: readiness.maxSizeBytes,\n reason: readiness.reason,\n status: \"readonly\"\n });\n this.loadPromise = Promise.resolve();\n return this.loadPromise;\n }\n\n if (!this.input.read) {\n this.updateState({\n entry,\n previewKind: readiness.target.previewKind,\n reason: \"reader_unavailable\",\n status: \"unsupported\"\n });\n this.loadPromise = Promise.resolve();\n return this.loadPromise;\n }\n\n return this.beginLoad(entry, previewEntry, readiness.target);\n }\n\n private beginLoad(\n entry: TEntry,\n previewEntry: WorkspaceFilePreviewEntry,\n target: WorkspaceFilePreviewTarget\n ): Promise<void> {\n const abortController = new AbortController();\n this.abortController = abortController;\n const generation = this.generation;\n this.updateState({\n entry,\n previewKind: target.previewKind,\n status: \"loading\"\n });\n this.loadPromise = this.load(\n entry,\n previewEntry,\n target,\n generation,\n abortController\n );\n return this.loadPromise;\n }\n\n private updateState(\n state: WorkspaceFilePreviewControllerState<TEntry>\n ): void {\n if (this.disposed) {\n return;\n }\n this.state = state;\n for (const listener of this.listeners) {\n listener();\n }\n }\n}\n\nconst browserWorkspaceFilePreviewObjectUrls: WorkspaceFilePreviewObjectUrlFactory =\n {\n create(bytes, contentType) {\n return URL.createObjectURL(new Blob([bytes], { type: contentType }));\n },\n revoke(objectUrl) {\n URL.revokeObjectURL(objectUrl);\n }\n };\n"],"mappings":"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","names":[]}
@@ -1,23 +1,70 @@
1
+ /**
2
+ * Preview kind taxonomy and classification.
3
+ *
4
+ * Ownership rules: packages/workspace/file-preview/CONTRACT.md
5
+ */
1
6
  type WorkspaceFilePreviewEntryKind = "file" | "directory" | "folder" | "unknown" | (string & {});
2
- type WorkspaceFilePreviewKind = "image" | "text" | "video";
7
+ /**
8
+ * Flat preview routing kind. Built-in surface covers image / video / text;
9
+ * text-degradable kinds fall back to built-in text when no host hook is
10
+ * registered. Hook-only kinds (audio / pdf / Office) stay unsupported until a
11
+ * host registers a renderer.
12
+ */
13
+ type WorkspaceFilePreviewKind = "directory" | "image" | "video" | "audio" | "text" | "code" | "markdown" | "json" | "csv" | "html" | "pdf" | "docx" | "xlsx" | "pptx" | "unsupported";
14
+ /** Coarse glyph / icon signal; must not be collapsed into previewKind. */
3
15
  type WorkspaceFileVisualKind = "binary" | "code" | "directory" | "document" | "image" | "markdown" | "video";
16
+ /** Built-in render modes the package surface can paint without host hooks. */
17
+ type WorkspaceFileBuiltinRenderKind = "image" | "text" | "video";
4
18
  interface WorkspaceFilePreviewEntry {
5
- displayName?: string;
6
19
  kind: WorkspaceFilePreviewEntryKind;
7
20
  mtimeMs?: number | null;
8
21
  name?: string;
9
22
  path: string;
10
23
  sizeBytes?: number | null;
11
24
  }
12
- interface WorkspaceFilePreviewActivationTarget {
13
- fileKind: WorkspaceFilePreviewKind;
25
+ interface WorkspaceFilePreviewTarget {
26
+ previewKind: WorkspaceFilePreviewKind;
14
27
  mtimeMs?: number | null;
15
28
  name: string;
16
29
  path: string;
17
30
  sizeBytes?: number | null;
18
31
  }
32
+ /** Browser-decodable video subset used for mime hints and open-with hosts. */
33
+ declare const workspaceFileBrowserVideoExtensions: Set<string>;
34
+ declare function resolveWorkspaceFileExtension(pathOrName: string): string;
35
+ declare function resolveWorkspaceFilePreviewName(entry: Pick<WorkspaceFilePreviewEntry, "name" | "path">): string;
36
+ declare function isWorkspaceFileDirectoryKind(kind: WorkspaceFilePreviewEntryKind): boolean;
37
+ declare function isTextDegradablePreviewKind(kind: WorkspaceFilePreviewKind): boolean;
38
+ /**
39
+ * Maps a classified preview kind onto the built-in surface renderer, if any.
40
+ * Text-degradable kinds resolve to built-in `text`.
41
+ */
42
+ declare function resolveWorkspaceFileBuiltinRenderKind(kind: WorkspaceFilePreviewKind): WorkspaceFileBuiltinRenderKind | null;
43
+ declare function resolveWorkspaceFileVisualKind(entry: Pick<WorkspaceFilePreviewEntry, "kind" | "name" | "path">): WorkspaceFileVisualKind;
44
+ /**
45
+ * Classifies the flat previewKind for an entry.
46
+ * Directory synonyms (`folder`) are normalized to `directory`.
47
+ */
48
+ declare function classifyWorkspaceFilePreviewKind(entry: Pick<WorkspaceFilePreviewEntry, "kind" | "name" | "path">): WorkspaceFilePreviewKind;
49
+ /**
50
+ * Returns a preview target when the entry can be presented with the built-in
51
+ * resolve chain (host hook omitted). Hook-only kinds return null here; hosts
52
+ * with renderers should call classify + hasHostRenderer themselves.
53
+ */
54
+ declare function resolveWorkspaceFilePreviewTarget(entry: WorkspaceFilePreviewEntry): WorkspaceFilePreviewTarget | null;
55
+ declare function resolveWorkspaceImageMimeType(pathOrName: string): string | null;
56
+ declare function resolveWorkspaceVideoMimeType(pathOrName: string): string | null;
57
+ declare function isWorkspaceFileBrowserHtmlExtension(extension: string): boolean;
58
+ declare function isWorkspaceFileImageExtension(extension: string): boolean;
59
+
60
+ /**
61
+ * Preview readiness, byte decoding, and loaded-state construction.
62
+ *
63
+ * Ownership rules: packages/workspace/file-preview/CONTRACT.md
64
+ */
65
+
19
66
  type WorkspaceFilePreviewReadonlyReason = "binary" | "decode_failed" | "file_too_large" | "text_too_large";
20
- type WorkspaceFilePreviewReadiness<TEntry extends WorkspaceFilePreviewEntry, TTarget extends WorkspaceFilePreviewActivationTarget = WorkspaceFilePreviewActivationTarget> = {
67
+ type WorkspaceFilePreviewReadiness<TEntry extends WorkspaceFilePreviewEntry, TTarget extends WorkspaceFilePreviewTarget = WorkspaceFilePreviewTarget> = {
21
68
  entry: TEntry;
22
69
  status: "directory";
23
70
  } | {
@@ -27,55 +74,64 @@ type WorkspaceFilePreviewReadiness<TEntry extends WorkspaceFilePreviewEntry, TTa
27
74
  status: "readonly";
28
75
  } | {
29
76
  entry: TEntry;
77
+ previewKind: WorkspaceFilePreviewKind;
30
78
  status: "unsupported";
31
79
  } | {
32
80
  entry: TEntry;
33
81
  status: "ready";
34
82
  target: TTarget;
35
83
  };
36
- type WorkspaceFilePreviewLoadedState<TEntry extends WorkspaceFilePreviewEntry, TTarget extends WorkspaceFilePreviewActivationTarget> = {
84
+ type WorkspaceFilePreviewLoadedState<TEntry extends WorkspaceFilePreviewEntry, TTarget extends WorkspaceFilePreviewTarget> = {
37
85
  content: string;
38
86
  entry: TTarget;
87
+ previewKind: WorkspaceFilePreviewKind;
39
88
  status: "text";
40
89
  } | {
41
90
  bytes: Uint8Array<ArrayBuffer>;
42
91
  contentType: string;
43
92
  entry: TTarget;
93
+ previewKind: "image";
44
94
  status: "image";
45
95
  } | {
46
96
  bytes: Uint8Array<ArrayBuffer>;
47
97
  contentType: string;
48
98
  entry: TTarget;
99
+ previewKind: "video";
49
100
  status: "video";
101
+ } | {
102
+ bytes: Uint8Array<ArrayBuffer>;
103
+ contentType: string | null;
104
+ entry: TTarget;
105
+ previewKind: WorkspaceFilePreviewKind;
106
+ status: "bytes";
50
107
  } | {
51
108
  entry: TEntry;
52
109
  maxSizeBytes?: number;
53
110
  reason: WorkspaceFilePreviewReadonlyReason;
54
111
  status: "readonly";
55
112
  };
56
- /**
57
- * Extensions where macOS Launch Services may register video handlers even when
58
- * the workspace file is source code (UTI / uniform type collisions).
59
- */
60
- declare const workspaceFileVideoHandlerCollisionExtensions: Set<string>;
113
+ interface ResolveWorkspaceFilePreviewReadinessOptions {
114
+ /**
115
+ * When true for a classified kind, readiness treats the entry as presentable
116
+ * even if it is hook-only (pdf / audio / Office). Built-in presentable kinds
117
+ * do not require this callback.
118
+ */
119
+ hasHostRenderer?: (kind: WorkspaceFilePreviewKind) => boolean;
120
+ }
61
121
  declare const workspaceFileTextMaxBytes: number;
62
122
  declare const workspaceFilePreviewMaxBytes: number;
63
- declare function resolveWorkspaceFileVisualKind(entry: Pick<WorkspaceFilePreviewEntry, "kind" | "name" | "path">): WorkspaceFileVisualKind;
64
- declare function resolveWorkspaceFileExtension(pathOrName: string): string;
65
- declare function isWorkspaceFileBrowserOpenable(entry: Pick<WorkspaceFilePreviewEntry, "kind" | "name" | "path">): boolean;
66
- declare function shouldFilterVideoPlayersForOpenWith(entry: Pick<WorkspaceFilePreviewEntry, "kind" | "name" | "path">): boolean;
67
- declare function resolveWorkspaceImageMimeType(pathOrName: string): string | null;
68
- declare function resolveWorkspaceVideoMimeType(pathOrName: string): string | null;
69
- declare function classifyWorkspaceFilePreviewKind(entry: Pick<WorkspaceFilePreviewEntry, "displayName" | "kind" | "name" | "path">): WorkspaceFilePreviewKind | null;
70
- declare function resolveWorkspaceFileActivationTarget(entry: WorkspaceFilePreviewEntry): WorkspaceFilePreviewActivationTarget | null;
71
- declare function resolveWorkspaceFilePreviewReadiness<TEntry extends WorkspaceFilePreviewEntry>(entry: TEntry): WorkspaceFilePreviewReadiness<TEntry>;
72
- declare function createWorkspaceFilePreviewLoadedState<TEntry extends WorkspaceFilePreviewEntry, TTarget extends WorkspaceFilePreviewActivationTarget>(input: {
123
+ declare function resolveWorkspaceFilePreviewReadiness<TEntry extends WorkspaceFilePreviewEntry>(entry: TEntry, options?: ResolveWorkspaceFilePreviewReadinessOptions): WorkspaceFilePreviewReadiness<TEntry>;
124
+ declare function createWorkspaceFilePreviewLoadedState<TEntry extends WorkspaceFilePreviewEntry, TTarget extends WorkspaceFilePreviewTarget>(input: {
73
125
  bytes: Uint8Array | ArrayBuffer;
74
126
  contentType?: string | null;
75
127
  entry: TEntry;
128
+ /**
129
+ * When true, skip built-in text degradation and keep raw bytes for a host
130
+ * renderer (hook-only kinds, or text-degradable kinds with a host hook).
131
+ */
132
+ preferHostBytes?: boolean;
76
133
  target: TTarget;
77
134
  }): WorkspaceFilePreviewLoadedState<TEntry, TTarget>;
78
- declare function resolveWorkspaceFilePreviewName(entry: Pick<WorkspaceFilePreviewEntry, "displayName" | "name" | "path">): string;
79
135
  declare function isWorkspaceTextFileTooLarge(sizeBytes?: number | null): boolean;
80
136
  declare function isWorkspacePreviewFileTooLarge(sizeBytes?: number | null): boolean;
81
137
  declare function decodeWorkspaceTextFile(bytes: Uint8Array | ArrayBuffer): string;
@@ -84,6 +140,12 @@ declare function copyWorkspaceFilePreviewBytes(bytes: Uint8Array | ArrayBuffer):
84
140
  declare function looksLikeBinaryText(content: string): boolean;
85
141
  declare function formatWorkspacePreviewByteLimit(sizeBytes: number): string;
86
142
 
143
+ /**
144
+ * Shared preview loading lifecycle.
145
+ *
146
+ * Ownership rules: packages/workspace/file-preview/CONTRACT.md
147
+ */
148
+
87
149
  type WorkspaceFilePreviewUnsupportedReason = "file_type" | "reader_unavailable";
88
150
  type WorkspaceFilePreviewControllerState<TEntry> = {
89
151
  status: "empty";
@@ -92,48 +154,66 @@ type WorkspaceFilePreviewControllerState<TEntry> = {
92
154
  status: "directory";
93
155
  } | {
94
156
  entry: TEntry;
157
+ previewKind: WorkspaceFilePreviewKind;
95
158
  status: "loading";
96
159
  } | {
97
160
  content: string;
98
161
  entry: TEntry;
162
+ previewKind: WorkspaceFilePreviewKind;
99
163
  previewSizeBytes: number;
100
164
  status: "text";
101
165
  } | {
102
166
  entry: TEntry;
103
167
  objectUrl: string;
168
+ previewKind: "image";
104
169
  previewSizeBytes: number;
105
170
  status: "image";
106
171
  } | {
107
172
  entry: TEntry;
108
173
  objectUrl: string;
174
+ previewKind: "video";
109
175
  previewSizeBytes: number;
110
176
  status: "video";
177
+ } | {
178
+ bytes: Uint8Array<ArrayBuffer>;
179
+ contentType: string | null;
180
+ entry: TEntry;
181
+ previewKind: WorkspaceFilePreviewKind;
182
+ previewSizeBytes: number;
183
+ status: "bytes";
111
184
  } | {
112
185
  entry: TEntry;
113
186
  maxSizeBytes?: number;
187
+ previewKind?: WorkspaceFilePreviewKind;
114
188
  previewSizeBytes?: number;
115
189
  reason: WorkspaceFilePreviewReadonlyReason;
116
190
  status: "readonly";
117
191
  } | {
118
192
  entry: TEntry;
193
+ previewKind?: WorkspaceFilePreviewKind;
119
194
  reason: WorkspaceFilePreviewUnsupportedReason;
120
195
  status: "unsupported";
121
196
  } | {
122
197
  entry: TEntry;
123
198
  error: unknown;
199
+ previewKind?: WorkspaceFilePreviewKind;
124
200
  status: "error";
125
201
  };
126
202
  interface WorkspaceFilePreviewReadInput<TEntry> {
127
203
  entry: TEntry;
128
204
  signal: AbortSignal;
129
- target: WorkspaceFilePreviewActivationTarget;
205
+ target: WorkspaceFilePreviewTarget;
130
206
  }
207
+ /**
208
+ * Host read result. Bare Uint8Array / ArrayBuffer are intentionally not
209
+ * accepted; wrap bytes in this object shape.
210
+ */
131
211
  interface WorkspaceFilePreviewReadResult {
132
212
  bytes: Uint8Array | ArrayBuffer;
133
213
  contentType?: string | null;
214
+ /** Optional host override of the classified preview kind. */
134
215
  kind?: WorkspaceFilePreviewKind;
135
216
  }
136
- type WorkspaceFilePreviewReadOutput = WorkspaceFilePreviewReadResult | Uint8Array | ArrayBuffer;
137
217
  interface WorkspaceFilePreviewObjectUrlFactory {
138
218
  create(bytes: Uint8Array<ArrayBuffer>, contentType: string): string;
139
219
  revoke(objectUrl: string): void;
@@ -146,8 +226,14 @@ interface CreateWorkspaceFilePreviewControllerInput<TEntry> {
146
226
  */
147
227
  canReadEntry?: (entry: TEntry) => boolean;
148
228
  getEntryKey?: (entry: TEntry) => string;
229
+ /**
230
+ * Host renderer registry probe used by readiness / load planning. Returning
231
+ * true for a kind keeps hook-only kinds presentable and can prefer raw bytes
232
+ * for text-degradable kinds that the host wants to render itself.
233
+ */
234
+ hasHostRenderer?: (kind: WorkspaceFilePreviewKind) => boolean;
149
235
  objectUrls?: WorkspaceFilePreviewObjectUrlFactory;
150
- read?: (input: WorkspaceFilePreviewReadInput<TEntry>) => Promise<WorkspaceFilePreviewReadOutput | null>;
236
+ read?: (input: WorkspaceFilePreviewReadInput<TEntry>) => Promise<WorkspaceFilePreviewReadResult | null>;
151
237
  toPreviewEntry: (entry: TEntry) => WorkspaceFilePreviewEntry;
152
238
  }
153
239
  interface WorkspaceFilePreviewController<TEntry> {
@@ -159,4 +245,4 @@ interface WorkspaceFilePreviewController<TEntry> {
159
245
  }
160
246
  declare function createWorkspaceFilePreviewController<TEntry>(input: CreateWorkspaceFilePreviewControllerInput<TEntry>): WorkspaceFilePreviewController<TEntry>;
161
247
 
162
- export { type CreateWorkspaceFilePreviewControllerInput, type WorkspaceFilePreviewActivationTarget, type WorkspaceFilePreviewController, type WorkspaceFilePreviewControllerState, type WorkspaceFilePreviewEntry, type WorkspaceFilePreviewEntryKind, type WorkspaceFilePreviewKind, type WorkspaceFilePreviewLoadedState, type WorkspaceFilePreviewObjectUrlFactory, type WorkspaceFilePreviewReadInput, type WorkspaceFilePreviewReadOutput, type WorkspaceFilePreviewReadResult, type WorkspaceFilePreviewReadiness, type WorkspaceFilePreviewReadonlyReason, type WorkspaceFilePreviewUnsupportedReason, type WorkspaceFileVisualKind, classifyWorkspaceFilePreviewKind, copyWorkspaceFilePreviewBytes, createWorkspaceFilePreviewController, createWorkspaceFilePreviewLoadedState, decodeWorkspaceTextFile, formatWorkspacePreviewByteLimit, isWorkspaceFileBrowserOpenable, isWorkspacePreviewFileTooLarge, isWorkspaceTextFileTooLarge, looksLikeBinaryText, normalizeWorkspaceFilePreviewBytes, resolveWorkspaceFileActivationTarget, resolveWorkspaceFileExtension, resolveWorkspaceFilePreviewName, resolveWorkspaceFilePreviewReadiness, resolveWorkspaceFileVisualKind, resolveWorkspaceImageMimeType, resolveWorkspaceVideoMimeType, shouldFilterVideoPlayersForOpenWith, workspaceFilePreviewMaxBytes, workspaceFileTextMaxBytes, workspaceFileVideoHandlerCollisionExtensions };
248
+ export { type CreateWorkspaceFilePreviewControllerInput, type ResolveWorkspaceFilePreviewReadinessOptions, type WorkspaceFileBuiltinRenderKind, type WorkspaceFilePreviewController, type WorkspaceFilePreviewControllerState, type WorkspaceFilePreviewEntry, type WorkspaceFilePreviewEntryKind, type WorkspaceFilePreviewKind, type WorkspaceFilePreviewLoadedState, type WorkspaceFilePreviewObjectUrlFactory, type WorkspaceFilePreviewReadInput, type WorkspaceFilePreviewReadResult, type WorkspaceFilePreviewReadiness, type WorkspaceFilePreviewReadonlyReason, type WorkspaceFilePreviewTarget, type WorkspaceFilePreviewUnsupportedReason, type WorkspaceFileVisualKind, classifyWorkspaceFilePreviewKind, copyWorkspaceFilePreviewBytes, createWorkspaceFilePreviewController, createWorkspaceFilePreviewLoadedState, decodeWorkspaceTextFile, formatWorkspacePreviewByteLimit, isTextDegradablePreviewKind, isWorkspaceFileBrowserHtmlExtension, isWorkspaceFileDirectoryKind, isWorkspaceFileImageExtension, isWorkspacePreviewFileTooLarge, isWorkspaceTextFileTooLarge, looksLikeBinaryText, normalizeWorkspaceFilePreviewBytes, resolveWorkspaceFileBuiltinRenderKind, resolveWorkspaceFileExtension, resolveWorkspaceFilePreviewName, resolveWorkspaceFilePreviewReadiness, resolveWorkspaceFilePreviewTarget, resolveWorkspaceFileVisualKind, resolveWorkspaceImageMimeType, resolveWorkspaceVideoMimeType, workspaceFileBrowserVideoExtensions, workspaceFilePreviewMaxBytes, workspaceFileTextMaxBytes };
@@ -5,23 +5,26 @@ import {
5
5
  createWorkspaceFilePreviewLoadedState,
6
6
  decodeWorkspaceTextFile,
7
7
  formatWorkspacePreviewByteLimit,
8
- isWorkspaceFileBrowserOpenable,
8
+ isTextDegradablePreviewKind,
9
+ isWorkspaceFileBrowserHtmlExtension,
10
+ isWorkspaceFileDirectoryKind,
11
+ isWorkspaceFileImageExtension,
9
12
  isWorkspacePreviewFileTooLarge,
10
13
  isWorkspaceTextFileTooLarge,
11
14
  looksLikeBinaryText,
12
15
  normalizeWorkspaceFilePreviewBytes,
13
- resolveWorkspaceFileActivationTarget,
16
+ resolveWorkspaceFileBuiltinRenderKind,
14
17
  resolveWorkspaceFileExtension,
15
18
  resolveWorkspaceFilePreviewName,
16
19
  resolveWorkspaceFilePreviewReadiness,
20
+ resolveWorkspaceFilePreviewTarget,
17
21
  resolveWorkspaceFileVisualKind,
18
22
  resolveWorkspaceImageMimeType,
19
23
  resolveWorkspaceVideoMimeType,
20
- shouldFilterVideoPlayersForOpenWith,
24
+ workspaceFileBrowserVideoExtensions,
21
25
  workspaceFilePreviewMaxBytes,
22
- workspaceFileTextMaxBytes,
23
- workspaceFileVideoHandlerCollisionExtensions
24
- } from "../chunk-SUQYO7VX.js";
26
+ workspaceFileTextMaxBytes
27
+ } from "../chunk-SDSS6OA5.js";
25
28
  export {
26
29
  classifyWorkspaceFilePreviewKind,
27
30
  copyWorkspaceFilePreviewBytes,
@@ -29,21 +32,24 @@ export {
29
32
  createWorkspaceFilePreviewLoadedState,
30
33
  decodeWorkspaceTextFile,
31
34
  formatWorkspacePreviewByteLimit,
32
- isWorkspaceFileBrowserOpenable,
35
+ isTextDegradablePreviewKind,
36
+ isWorkspaceFileBrowserHtmlExtension,
37
+ isWorkspaceFileDirectoryKind,
38
+ isWorkspaceFileImageExtension,
33
39
  isWorkspacePreviewFileTooLarge,
34
40
  isWorkspaceTextFileTooLarge,
35
41
  looksLikeBinaryText,
36
42
  normalizeWorkspaceFilePreviewBytes,
37
- resolveWorkspaceFileActivationTarget,
43
+ resolveWorkspaceFileBuiltinRenderKind,
38
44
  resolveWorkspaceFileExtension,
39
45
  resolveWorkspaceFilePreviewName,
40
46
  resolveWorkspaceFilePreviewReadiness,
47
+ resolveWorkspaceFilePreviewTarget,
41
48
  resolveWorkspaceFileVisualKind,
42
49
  resolveWorkspaceImageMimeType,
43
50
  resolveWorkspaceVideoMimeType,
44
- shouldFilterVideoPlayersForOpenWith,
51
+ workspaceFileBrowserVideoExtensions,
45
52
  workspaceFilePreviewMaxBytes,
46
- workspaceFileTextMaxBytes,
47
- workspaceFileVideoHandlerCollisionExtensions
53
+ workspaceFileTextMaxBytes
48
54
  };
49
55
  //# sourceMappingURL=index.js.map
package/dist/index.d.ts CHANGED
@@ -1 +1 @@
1
- export { CreateWorkspaceFilePreviewControllerInput, WorkspaceFilePreviewActivationTarget, WorkspaceFilePreviewController, WorkspaceFilePreviewControllerState, WorkspaceFilePreviewEntry, WorkspaceFilePreviewEntryKind, WorkspaceFilePreviewKind, WorkspaceFilePreviewLoadedState, WorkspaceFilePreviewObjectUrlFactory, WorkspaceFilePreviewReadInput, WorkspaceFilePreviewReadOutput, WorkspaceFilePreviewReadResult, WorkspaceFilePreviewReadiness, WorkspaceFilePreviewReadonlyReason, WorkspaceFilePreviewUnsupportedReason, WorkspaceFileVisualKind, classifyWorkspaceFilePreviewKind, copyWorkspaceFilePreviewBytes, createWorkspaceFilePreviewController, createWorkspaceFilePreviewLoadedState, decodeWorkspaceTextFile, formatWorkspacePreviewByteLimit, isWorkspaceFileBrowserOpenable, isWorkspacePreviewFileTooLarge, isWorkspaceTextFileTooLarge, looksLikeBinaryText, normalizeWorkspaceFilePreviewBytes, resolveWorkspaceFileActivationTarget, resolveWorkspaceFileExtension, resolveWorkspaceFilePreviewName, resolveWorkspaceFilePreviewReadiness, resolveWorkspaceFileVisualKind, resolveWorkspaceImageMimeType, resolveWorkspaceVideoMimeType, shouldFilterVideoPlayersForOpenWith, workspaceFilePreviewMaxBytes, workspaceFileTextMaxBytes, workspaceFileVideoHandlerCollisionExtensions } from './core/index.js';
1
+ export { CreateWorkspaceFilePreviewControllerInput, ResolveWorkspaceFilePreviewReadinessOptions, WorkspaceFileBuiltinRenderKind, WorkspaceFilePreviewController, WorkspaceFilePreviewControllerState, WorkspaceFilePreviewEntry, WorkspaceFilePreviewEntryKind, WorkspaceFilePreviewKind, WorkspaceFilePreviewLoadedState, WorkspaceFilePreviewObjectUrlFactory, WorkspaceFilePreviewReadInput, WorkspaceFilePreviewReadResult, WorkspaceFilePreviewReadiness, WorkspaceFilePreviewReadonlyReason, WorkspaceFilePreviewTarget, WorkspaceFilePreviewUnsupportedReason, WorkspaceFileVisualKind, classifyWorkspaceFilePreviewKind, copyWorkspaceFilePreviewBytes, createWorkspaceFilePreviewController, createWorkspaceFilePreviewLoadedState, decodeWorkspaceTextFile, formatWorkspacePreviewByteLimit, isTextDegradablePreviewKind, isWorkspaceFileBrowserHtmlExtension, isWorkspaceFileDirectoryKind, isWorkspaceFileImageExtension, isWorkspacePreviewFileTooLarge, isWorkspaceTextFileTooLarge, looksLikeBinaryText, normalizeWorkspaceFilePreviewBytes, resolveWorkspaceFileBuiltinRenderKind, resolveWorkspaceFileExtension, resolveWorkspaceFilePreviewName, resolveWorkspaceFilePreviewReadiness, resolveWorkspaceFilePreviewTarget, resolveWorkspaceFileVisualKind, resolveWorkspaceImageMimeType, resolveWorkspaceVideoMimeType, workspaceFileBrowserVideoExtensions, workspaceFilePreviewMaxBytes, workspaceFileTextMaxBytes } from './core/index.js';
package/dist/index.js CHANGED
@@ -5,23 +5,26 @@ import {
5
5
  createWorkspaceFilePreviewLoadedState,
6
6
  decodeWorkspaceTextFile,
7
7
  formatWorkspacePreviewByteLimit,
8
- isWorkspaceFileBrowserOpenable,
8
+ isTextDegradablePreviewKind,
9
+ isWorkspaceFileBrowserHtmlExtension,
10
+ isWorkspaceFileDirectoryKind,
11
+ isWorkspaceFileImageExtension,
9
12
  isWorkspacePreviewFileTooLarge,
10
13
  isWorkspaceTextFileTooLarge,
11
14
  looksLikeBinaryText,
12
15
  normalizeWorkspaceFilePreviewBytes,
13
- resolveWorkspaceFileActivationTarget,
16
+ resolveWorkspaceFileBuiltinRenderKind,
14
17
  resolveWorkspaceFileExtension,
15
18
  resolveWorkspaceFilePreviewName,
16
19
  resolveWorkspaceFilePreviewReadiness,
20
+ resolveWorkspaceFilePreviewTarget,
17
21
  resolveWorkspaceFileVisualKind,
18
22
  resolveWorkspaceImageMimeType,
19
23
  resolveWorkspaceVideoMimeType,
20
- shouldFilterVideoPlayersForOpenWith,
24
+ workspaceFileBrowserVideoExtensions,
21
25
  workspaceFilePreviewMaxBytes,
22
- workspaceFileTextMaxBytes,
23
- workspaceFileVideoHandlerCollisionExtensions
24
- } from "./chunk-SUQYO7VX.js";
26
+ workspaceFileTextMaxBytes
27
+ } from "./chunk-SDSS6OA5.js";
25
28
  export {
26
29
  classifyWorkspaceFilePreviewKind,
27
30
  copyWorkspaceFilePreviewBytes,
@@ -29,21 +32,24 @@ export {
29
32
  createWorkspaceFilePreviewLoadedState,
30
33
  decodeWorkspaceTextFile,
31
34
  formatWorkspacePreviewByteLimit,
32
- isWorkspaceFileBrowserOpenable,
35
+ isTextDegradablePreviewKind,
36
+ isWorkspaceFileBrowserHtmlExtension,
37
+ isWorkspaceFileDirectoryKind,
38
+ isWorkspaceFileImageExtension,
33
39
  isWorkspacePreviewFileTooLarge,
34
40
  isWorkspaceTextFileTooLarge,
35
41
  looksLikeBinaryText,
36
42
  normalizeWorkspaceFilePreviewBytes,
37
- resolveWorkspaceFileActivationTarget,
43
+ resolveWorkspaceFileBuiltinRenderKind,
38
44
  resolveWorkspaceFileExtension,
39
45
  resolveWorkspaceFilePreviewName,
40
46
  resolveWorkspaceFilePreviewReadiness,
47
+ resolveWorkspaceFilePreviewTarget,
41
48
  resolveWorkspaceFileVisualKind,
42
49
  resolveWorkspaceImageMimeType,
43
50
  resolveWorkspaceVideoMimeType,
44
- shouldFilterVideoPlayersForOpenWith,
51
+ workspaceFileBrowserVideoExtensions,
45
52
  workspaceFilePreviewMaxBytes,
46
- workspaceFileTextMaxBytes,
47
- workspaceFileVideoHandlerCollisionExtensions
53
+ workspaceFileTextMaxBytes
48
54
  };
49
55
  //# sourceMappingURL=index.js.map
@@ -1,5 +1,15 @@
1
+ import { WorkspaceFilePreviewKind, WorkspaceFilePreviewReadonlyReason, WorkspaceFilePreviewUnsupportedReason, WorkspaceFilePreviewControllerState } from '../core/index.js';
1
2
  import { ReactNode, ReactElement } from 'react';
2
3
 
4
+ /**
5
+ * Thin built-in preview surface (image / video / text) plus host renderer hooks.
6
+ *
7
+ * Resolve order: host renderer for previewKind → built-in text degradation →
8
+ * unsupported/fallback messaging already present in state.
9
+ *
10
+ * Ownership rules: packages/workspace/file-preview/CONTRACT.md
11
+ */
12
+
3
13
  type WorkspaceFilePreviewSurfaceState<TEntry> = {
4
14
  status: "empty";
5
15
  } | {
@@ -7,43 +17,96 @@ type WorkspaceFilePreviewSurfaceState<TEntry> = {
7
17
  status: "directory";
8
18
  } | {
9
19
  entry: TEntry;
20
+ previewKind?: WorkspaceFilePreviewKind;
10
21
  status: "loading";
11
22
  } | {
12
23
  content: string;
13
24
  entry: TEntry;
25
+ previewKind: WorkspaceFilePreviewKind;
14
26
  status: "text";
15
27
  } | {
16
28
  entry: TEntry;
17
29
  objectUrl: string;
30
+ previewKind: "image";
18
31
  status: "image";
19
32
  } | {
20
33
  entry: TEntry;
21
34
  objectUrl: string;
35
+ previewKind: "video";
22
36
  status: "video";
37
+ } | {
38
+ bytes: Uint8Array<ArrayBuffer>;
39
+ contentType: string | null;
40
+ entry: TEntry;
41
+ previewKind: WorkspaceFilePreviewKind;
42
+ status: "bytes";
23
43
  } | {
24
44
  entry: TEntry;
25
45
  message: string;
46
+ previewKind?: WorkspaceFilePreviewKind;
26
47
  status: "readonly";
27
48
  } | {
28
49
  entry: TEntry;
29
50
  message: string;
51
+ previewKind?: WorkspaceFilePreviewKind;
30
52
  status: "unsupported";
31
53
  } | {
32
54
  entry: TEntry;
33
55
  message: string;
56
+ previewKind?: WorkspaceFilePreviewKind;
34
57
  status: "error";
35
58
  };
36
59
  type WorkspaceFilePreviewSurfaceVariant = "canvas" | "compact" | "detail";
60
+ interface WorkspaceFilePreviewHostRendererProps<TEntry> {
61
+ entry: TEntry;
62
+ previewKind: WorkspaceFilePreviewKind;
63
+ variant: WorkspaceFilePreviewSurfaceVariant;
64
+ /** Present when the controller decoded text (including text-degradable kinds). */
65
+ content?: string;
66
+ /** Present for hook-only kinds loaded as raw bytes. */
67
+ bytes?: Uint8Array<ArrayBuffer>;
68
+ contentType?: string | null;
69
+ /** Present for built-in media kinds when a host still overrides rendering. */
70
+ objectUrl?: string;
71
+ }
72
+ type WorkspaceFilePreviewHostRenderer<TEntry> = (props: WorkspaceFilePreviewHostRendererProps<TEntry>) => ReactNode;
73
+ /**
74
+ * Host renderer registry keyed by previewKind. `variant` is passed as props;
75
+ * optional (kind, variant) specificity may be added later.
76
+ */
77
+ type WorkspaceFilePreviewHostRenderers<TEntry> = Partial<Record<WorkspaceFilePreviewKind, WorkspaceFilePreviewHostRenderer<TEntry>>>;
37
78
  interface WorkspaceFilePreviewSurfaceProps<TEntry> {
38
79
  directoryMessage: string;
39
80
  emptyMessage: string;
40
81
  imageAlt: (entry: TEntry) => string;
41
82
  loadingIndicator: ReactNode;
42
83
  loadingMessage: string;
84
+ /**
85
+ * Optional host renderers registered by previewKind. When present for the
86
+ * current kind, they win over built-in image / video / text shells.
87
+ */
88
+ renderers?: WorkspaceFilePreviewHostRenderers<TEntry>;
43
89
  renderIcon: (entry: TEntry) => ReactNode;
44
90
  state: WorkspaceFilePreviewSurfaceState<TEntry>;
45
91
  variant: WorkspaceFilePreviewSurfaceVariant;
46
92
  }
47
- declare function WorkspaceFilePreviewSurface<TEntry>({ directoryMessage, emptyMessage, imageAlt, loadingIndicator, loadingMessage, renderIcon, state, variant }: WorkspaceFilePreviewSurfaceProps<TEntry>): ReactElement;
93
+ declare function WorkspaceFilePreviewSurface<TEntry>({ directoryMessage, emptyMessage, imageAlt, loadingIndicator, loadingMessage, renderers, renderIcon, state, variant }: WorkspaceFilePreviewSurfaceProps<TEntry>): ReactElement;
94
+
95
+ /**
96
+ * Projects controller state into surface state with host-provided copy.
97
+ *
98
+ * Ownership rules: packages/workspace/file-preview/CONTRACT.md
99
+ */
100
+
101
+ interface WorkspaceFilePreviewSurfaceCopy {
102
+ errorMessage: (error: unknown) => string;
103
+ readonlyMessage: (reason: WorkspaceFilePreviewReadonlyReason, maxSizeBytes?: number) => string;
104
+ unsupportedMessage: (reason: WorkspaceFilePreviewUnsupportedReason, previewKind?: WorkspaceFilePreviewKind) => string;
105
+ }
106
+ /**
107
+ * Shared projection from machine-readable controller state to the React
108
+ * surface state. Controllers remain the source of truth; hosts inject copy.
109
+ */
110
+ declare function toSurfaceState<TEntry>(state: WorkspaceFilePreviewControllerState<TEntry>, copy: WorkspaceFilePreviewSurfaceCopy): WorkspaceFilePreviewSurfaceState<TEntry>;
48
111
 
49
- export { WorkspaceFilePreviewSurface, type WorkspaceFilePreviewSurfaceProps, type WorkspaceFilePreviewSurfaceState, type WorkspaceFilePreviewSurfaceVariant };
112
+ export { type WorkspaceFilePreviewHostRenderer, type WorkspaceFilePreviewHostRendererProps, type WorkspaceFilePreviewHostRenderers, WorkspaceFilePreviewSurface, type WorkspaceFilePreviewSurfaceCopy, type WorkspaceFilePreviewSurfaceProps, type WorkspaceFilePreviewSurfaceState, type WorkspaceFilePreviewSurfaceVariant, toSurfaceState };