@ai-matrx/media 0.10.0 → 0.10.2

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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/files/engine/redux/thunks.ts"],"sourcesContent":["/**\n * features/files/redux/thunks.ts\n *\n * Async orchestration for cloud files: reads via supabase-js (RLS), writes via\n * the REST API client. Every mutation follows the pattern:\n *\n * 1. Snapshot current state for rollback.\n * 2. Dispatch optimistic reducer immediately.\n * 3. Register the requestId in the ledger (for realtime echo dedup).\n * 4. Call the REST API.\n * 5. On success: markSaved, release ledger entry.\n * 6. On error: dispatch rollback, set error, release ledger entry, rethrow.\n */\n\n\"use client\";\n\nimport { createAsyncThunk } from \"@reduxjs/toolkit\";\nimport type { ThunkDispatch, UnknownAction } from \"@reduxjs/toolkit\";\nimport type { CloudFilesState } from \"../types\";\n\n// Minimal local types — avoids importing from store.ts (which imports this\n// module via rootReducer → middleware chain), breaking the type-level cycle.\ntype StateWithCloudFiles = { cloudFiles: CloudFilesState };\ntype AppDispatch = ThunkDispatch<StateWithCloudFiles, unknown, UnknownAction>;\nimport { supabase } from \"../host/supabase\";\nimport { filesDb, FILE_VERSIONS_TABLE_COLUMNS } from \"../filesDb\";\nimport { pgErrorToError } from \"@ai-matrx/data\";\n\nimport * as Files from \"../api/files\";\nimport {\n claimUpload,\n uploadDedupKey,\n type UploadClaim,\n} from \"../upload/uploadDedupGuard\";\nimport * as Folders from \"../api/folders\";\nimport * as Permissions from \"../api/permissions\";\nimport * as Versions from \"../api/versions\";\nimport {\n createShareLink as createCanonicalShareLink,\n listShareLinks as listCanonicalShareLinks,\n revokeShareLink as revokeCanonicalShareLink,\n} from \"../host/share-links\";\nimport {\n restoreFileDirect,\n restoreFolderDirect,\n softDeleteFileDirect,\n softDeleteFolderDirect,\n} from \"../api/direct\";\nimport { fileHandler } from \"../handler/handler\";\nimport { newRequestId } from \"../host/python-client\";\nimport { extractErrorMessage } from \"@ai-matrx/data/net\";\nimport {\n apiFileRecordToCloudFile,\n dbRowToCloudFilePermission,\n dbRowToCloudFileVersion,\n dbRowToCloudFolder,\n canonicalShareLinkToCloudShareLink,\n parseCloudTreeRows,\n rpcPermissionRowToCloudFilePermission,\n} from \"./converters\";\nimport {\n areCloudFileFieldsLoaded,\n FILE_DB_RECORD_FIELDS,\n fileHintToCloudFilePartial,\n needsOnlyRenderFields,\n type CloudFileHydrationField,\n} from \"./file-hydration\";\nimport {\n registerRequest,\n releaseRequest,\n beginResourceOp,\n isLatestResourceOp,\n hasInFlight,\n} from \"./request-ledger\";\nimport { toast } from \"../host/toast\";\nimport { buildTreeState } from \"./tree-utils\";\nimport {\n isUserVisibleFilePath,\n isUserVisibleFolderPath,\n} from \"../utils/user-visible\";\nimport {\n hasMatchingFileTreeSession,\n runFileTreeSessionOperation,\n} from \"./file-tree-auth-boundary\";\nimport {\n isFileTreeLoadDeferred,\n runWithFileTreeLoadTimeout,\n} from \"./file-tree-timeout\";\nimport {\n invalidate as invalidateBlobCache,\n replaceWithSavedBytes,\n} from \"../hooks/blob-cache\";\nimport { invalidateOfficeExtraction } from \"../hooks/office-extraction-cache\";\nimport {\n addFilePendingRequest,\n attachChildToFolder,\n clearUpload,\n detachChildFromFolder,\n markFileSaved,\n markFolderFullyLoaded,\n removeFile,\n removeFilePendingRequest,\n removeFolder,\n removePermissionForResource,\n removeShareLink,\n replaceTree,\n rollbackFileOptimisticUpdate,\n setFileError,\n setFileField,\n setFileLoading,\n setTreeStatus,\n trackUploadStart,\n updateUploadProgress,\n updateUploadStatus,\n upsertFile,\n upsertFiles,\n upsertFolder,\n upsertFolders,\n upsertPermissionsForResource,\n upsertShareLinksForResource,\n upsertVersionsForFile,\n} from \"./slice\";\nimport { getFileFromState } from \"./selectors\";\n\nimport type {\n BulkDeleteFilesArg,\n BulkMoveFilesArg,\n BulkMoveFoldersArg,\n BulkResponse,\n CloudFile,\n CloudFileFieldSnapshot,\n CloudFileRecord,\n CloudFolderRecord,\n CloudFilePermission,\n CloudFileVersion,\n CloudFolder,\n CloudShareLink,\n CreateShareLinkArg,\n RevokeShareLinkArg,\n DeleteFileArg,\n GranteeType,\n GrantPermissionArg,\n MoveFileArg,\n RenameFileArg,\n ResourceType,\n RestoreVersionArg,\n SaveFileNewVersionArg,\n SaveFileNewVersionResult,\n RevokePermissionArg,\n UpdateFileMetadataArg,\n UploadFilesArg,\n FileIdentityHint,\n} from \"../types\";\n\nimport { getClaimsUser } from \"../host/supabase\";\ntype ThunkApi = { dispatch: AppDispatch; state: StateWithCloudFiles };\n\n// ---------------------------------------------------------------------------\n// Reads\n// ---------------------------------------------------------------------------\n\nexport interface EnsureCloudFileFieldsArg {\n fileId: string;\n fields?: readonly CloudFileHydrationField[];\n hint?: FileIdentityHint;\n}\n\nexport type EnsureCloudFileFieldsOutcome = \"ready\" | \"missing\";\n\nconst fileFieldHydrationInFlight = new Map<\n string,\n Promise<(Partial<CloudFile> & Pick<CloudFile, \"id\">) | null>\n>();\n\nasync function fetchCloudFileFields(\n fileId: string,\n fields: readonly CloudFileHydrationField[],\n): Promise<(Partial<CloudFile> & Pick<CloudFile, \"id\">) | null> {\n const key = `${fileId}:${[...fields].sort().join(\",\")}`;\n const existing = fileFieldHydrationInFlight.get(key);\n if (existing) return existing;\n\n const request = (async () => {\n // A direct PostgREST exact-id read still compiles the entire files.files\n // RLS access expression. On a PDF surface many file chips hydrate at\n // once, so planning alone can cross its statement deadline before the\n // primary-key lookup begins. The server's `/files/{id}` boundary checks\n // the same canonical access rule once and reads by primary key without\n // browser RLS planning. Keep its URL/thumbnail work off this metadata\n // path; consumers that need it use the regular getFile endpoint.\n const { data } = await Files.getFileMetadata(fileId);\n const record = apiFileRecordToCloudFile(data);\n if (record.deletedAt) return null;\n\n if (needsOnlyRenderFields(fields)) {\n return {\n id: record.id,\n fileName: record.fileName,\n mimeType: record.mimeType,\n fileSize: record.fileSize,\n visibility: record.visibility,\n };\n }\n return record;\n })().finally(() => fileFieldHydrationInFlight.delete(key));\n\n fileFieldHydrationInFlight.set(key, request);\n return request;\n}\n\n/**\n * Ensure a durable file id has the requested canonical fields in Redux.\n * Known hints merge first, so the network reads only what is actually absent.\n */\nexport const ensureCloudFileFields = createAsyncThunk<\n EnsureCloudFileFieldsOutcome,\n EnsureCloudFileFieldsArg,\n ThunkApi\n>(\n \"cloudFiles/ensureCloudFileFields\",\n async (\n { fileId, fields = FILE_DB_RECORD_FIELDS, hint },\n { dispatch, getState },\n ) => {\n dispatch(upsertFile(fileHintToCloudFilePartial(fileId, hint)));\n\n if (\n areCloudFileFieldsLoaded(getFileFromState(getState(), fileId), fields)\n ) {\n return \"ready\";\n }\n\n dispatch(setFileLoading({ id: fileId, loading: true }));\n dispatch(setFileError({ id: fileId, error: null }));\n try {\n const partial = await fetchCloudFileFields(fileId, fields);\n if (!partial) return \"missing\";\n dispatch(upsertFile(partial));\n return \"ready\";\n } catch (error) {\n const normalized = pgErrorToError(error);\n dispatch(setFileError({ id: fileId, error: normalized.message }));\n throw normalized;\n } finally {\n dispatch(setFileLoading({ id: fileId, loading: false }));\n }\n },\n);\n\n/**\n * Loads the full tree for the current user via the get_user_file_tree\n * RPC. Normalizes into filesById / foldersById / tree.\n */\n// `get_user_file_tree` server-side cap (`p_limit := LEAST(GREATEST(p_limit, 1), 5000);`).\n// Passing anything > 5000 silently clamps. The FE pages via `p_offset`\n// to recover the full tree for power users (users with >5k files).\nconst TREE_PAGE_SIZE = 5000;\n\n// Sanity cap on the pagination loop — prevents an infinite loop if the\n// RPC ever returns a full page on the page that should be the last.\n// 20 * 5000 = 100,000 rows, well above any realistic user.\nconst TREE_MAX_PAGES = 20;\n\n/**\n * Shared promise for an in-progress full-tree load, so concurrent mounts\n * collapse onto one `get_user_file_tree` RPC. Session-local, not Redux state.\n */\nlet _treeInFlight: Promise<void> | null = null;\nlet _treeInFlightUserId: string | null = null;\n\nexport const loadUserFileTree = createAsyncThunk<\n void,\n { userId: string },\n ThunkApi\n>(\"cloudFiles/loadUserFileTree\", async ({ userId }, { dispatch, getState }) => {\n // Redux is server-seeded before client effects run, so during signed-out\n // hydration or session teardown it can briefly retain a user UUID while\n // supabase-js is already anonymous. Isolate that race at this canonical\n // producer boundary instead of sending an authenticated-only RPC as anon.\n if (!(await hasMatchingFileTreeSession(userId))) return;\n\n // In-flight dedupe. Several surfaces hydrate the tree on mount\n // (CloudFilesRealtimeProvider + every useCloudTree consumer), and their\n // effects all run in the same commit, so a status check in the component\n // can't see a sibling's fetch. Sharing the promise collapses them to one\n // RPC while keeping `.unwrap()` working for every caller (unlike a\n // `condition` guard, which rejects the losers).\n if (_treeInFlight && _treeInFlightUserId === userId) return _treeInFlight;\n\n const run = (async () => {\n dispatch(setTreeStatus({ status: \"loading\" }));\n\n // RPC contract (migration 014, 2026-05-17): identity-locked to\n // `auth.uid()`, returns owner OR explicit-grant rows only (no public\n // leak), excludes `parent_file_id IS NOT NULL` + `system-files/%`\n // paths. So we can consume the response raw — no FE-side ownership\n // or system-path filtering needed. See common-docs systems/files/file-service/WIRE_CONTRACT.md\n // (2026-05-17 \"Phase 1d.5\" entry).\n //\n // Pagination: server caps p_limit at 5000. We loop on `p_offset`\n // until a partial page comes back. Sequential pages — Postgres\n // handles them fast and parallelism would just contend for the\n // same connection.\n try {\n // Bounded per attempt; a timeout in a hidden tab waits for the person to\n // return and retries instead of failing (see file-tree-timeout.ts).\n const rows = await runWithFileTreeLoadTimeout(async (signal) => {\n const rows: ReturnType<typeof parseCloudTreeRows> = [];\n for (let page = 0; page < TREE_MAX_PAGES; page += 1) {\n const { data, error } = await runFileTreeSessionOperation(() =>\n supabase\n .rpc(\"get_user_file_tree\", {\n p_user_id: userId,\n p_limit: TREE_PAGE_SIZE,\n p_offset: page * TREE_PAGE_SIZE,\n p_include_folders: true,\n p_include_deleted: false,\n })\n .abortSignal(signal),\n );\n\n if (error) {\n // An aborted page is the timeout's call, not a server failure:\n // the runner decides (retry on return, or TimeoutError).\n if (!signal.aborted) {\n dispatch(\n setTreeStatus({ status: \"error\", error: error.message }),\n );\n }\n throw error;\n }\n\n const pageRows = parseCloudTreeRows(data);\n rows.push(...pageRows);\n if (pageRows.length < TREE_PAGE_SIZE) break;\n }\n return rows;\n });\n\n // Every field pushed below is always set from the non-optional\n // `CloudTreeRow` columns — narrowed here so the tree-spine reconstruction\n // (below) doesn't need a non-null assertion on `f.id` or a nullish-default\n // fallback on `f.ownerId`, which would otherwise silently paper over a\n // real parse gap.\n type PartialCloudFileWithId = Partial<CloudFile> &\n Pick<\n CloudFile,\n | \"id\"\n | \"ownerId\"\n | \"filePath\"\n | \"fileName\"\n | \"visibility\"\n | \"currentVersion\"\n | \"createdAt\"\n | \"updatedAt\"\n | \"deletedAt\"\n >;\n type PartialCloudFolderWithId = Partial<CloudFolder> &\n Pick<\n CloudFolder,\n | \"id\"\n | \"ownerId\"\n | \"folderPath\"\n | \"folderName\"\n | \"visibility\"\n | \"createdAt\"\n | \"updatedAt\"\n | \"deletedAt\"\n >;\n const files: PartialCloudFileWithId[] = [];\n const folders: PartialCloudFolderWithId[] = [];\n for (const row of rows) {\n if (row.kind === \"file\") {\n // No client-side visibility filter: `get_user_file_tree` already\n // applied `files.is_user_visible_path`. A second rule here is the\n // D16 disagreement this cutover deleted.\n files.push({\n id: row.id,\n ownerId: row.owner_id,\n filePath: row.file_path,\n fileName: row.file_name,\n parentFolderId: row.parent_folder_id,\n mimeType: row.mime_type,\n // Phase 0 rename: `file_size` → `size_bytes`. The\n // `CloudTreeFileRow` shape and Supabase row both carry the new\n // name now. See docs/PYTHON_UPDATES.md §3.\n fileSize: row.size_bytes,\n visibility: row.visibility,\n currentVersion: row.current_version,\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n deletedAt: row.deleted_at,\n originDeviceId: row.origin_device_id,\n metadata: row.metadata,\n });\n } else {\n folders.push({\n id: row.id,\n ownerId: row.owner_id,\n folderPath: row.folder_path,\n folderName: row.folder_name,\n parentId: row.parent_id,\n visibility: row.visibility,\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n deletedAt: row.deleted_at,\n metadata: row.metadata,\n });\n }\n }\n\n dispatch(upsertFiles(files));\n dispatch(upsertFolders(folders));\n\n // Drop rows the DATABASE's predicate rejects that slipped into the\n // store through realtime before the mirror was applied. FastFire rows\n // are user-visible now and are deliberately NOT purged.\n const prior = getState().cloudFiles;\n for (const [id, rec] of Object.entries(prior.filesById)) {\n if (rec && !isUserVisibleFilePath(rec.filePath)) {\n dispatch(removeFile({ id }));\n }\n }\n for (const [id, rec] of Object.entries(prior.foldersById)) {\n if (\n rec &&\n rec.source.kind === \"real\" &&\n !isUserVisibleFolderPath(rec.folderPath)\n ) {\n dispatch(removeFolder({ id }));\n }\n }\n\n // Build tree spine directly from the just-parsed rows.\n // the normalized slice back out, and avoids any race with batched dispatch.\n const fileIds = files.map((f) => f.id).filter(Boolean);\n const folderIds = folders.map((f) => f.id).filter(Boolean);\n const tree = buildTreeState({\n fileIds,\n folderIds,\n filesById: Object.fromEntries(\n files.map((f) => [\n f.id,\n {\n id: f.id,\n ownerId: f.ownerId,\n filePath: f.filePath,\n fileName: f.fileName,\n mimeType: f.mimeType ?? null,\n fileSize: f.fileSize ?? null,\n checksum: null,\n visibility: f.visibility,\n currentVersion: f.currentVersion,\n parentFolderId: f.parentFolderId ?? null,\n metadata: f.metadata ?? {},\n createdAt: f.createdAt,\n updatedAt: f.updatedAt,\n deletedAt: f.deletedAt,\n // Tree-spine reconstruction is internal — the computed URL fields\n // and the Phase-1b backend thumbnail_url are only populated when\n // records arrive via the REST API. Default all to null here; the\n // file grid (`MediaThumbnail`) falls through to `useFileAsset` →\n // `Asset.variants[\"thumbnail_url\"]` when these are absent.\n publicUrl: null,\n url: null,\n cdnUrl: null,\n downloadUrl: null,\n thumbnailUrl: null,\n source: { kind: \"real\" },\n _dirty: false,\n _dirtyFields: {},\n _fieldHistory: {},\n _loadedFields: {},\n _loading: false,\n _error: null,\n _pendingRequestIds: [],\n },\n ]),\n ),\n foldersById: Object.fromEntries(\n folders.map((f) => [\n f.id,\n {\n id: f.id,\n ownerId: f.ownerId,\n folderPath: f.folderPath,\n folderName: f.folderName,\n parentId: f.parentId ?? null,\n visibility: f.visibility,\n metadata: f.metadata ?? {},\n createdAt: f.createdAt,\n updatedAt: f.updatedAt,\n deletedAt: f.deletedAt,\n // Tree-spine reconstruction is internal — public_url is only\n // populated when records arrive via the API. Default to null\n // here; surfaces that need a CDN URL fetch via useFileSrc.\n publicUrl: null,\n source: { kind: \"real\" },\n _dirty: false,\n _dirtyFields: {},\n _fieldHistory: {},\n _loadedFields: {},\n _loading: false,\n _error: null,\n _pendingRequestIds: [],\n },\n ]),\n ),\n });\n dispatch(\n replaceTree({\n rootFolderIds: tree.rootFolderIds,\n rootFileIds: tree.rootFileIds,\n childrenByFolderId: tree.childrenByFolderId,\n // This RPC returns the complete discoverable tree, not one lazy level.\n // Mark every real folder so navigation never re-downloads up to 100k\n // rows merely because the user opened a different folder.\n fullyLoadedFolderIds: Object.fromEntries(\n folderIds.map((id) => [id, true] as const),\n ),\n }),\n );\n } catch (error) {\n // A load deferred by a hidden tab is not a failure: back to idle, and\n // the next consumer reloads. A visible timeout arrives as TimeoutError.\n if (isFileTreeLoadDeferred(error)) {\n dispatch(setTreeStatus({ status: \"idle\" }));\n throw error;\n }\n const message =\n error instanceof Error\n ? error.message\n : \"Could not load your file library.\";\n dispatch(setTreeStatus({ status: \"error\", error: message }));\n throw error;\n }\n })();\n\n _treeInFlight = run;\n _treeInFlightUserId = userId;\n try {\n await run;\n } finally {\n if (_treeInFlight === run) {\n _treeInFlight = null;\n _treeInFlightUserId = null;\n }\n }\n});\n\n/**\n * Re-run the tree load. Fired by the realtime middleware when the subscription\n * reconnects after an outage — guarantees we don't miss events.\n */\nexport const reconcileTree = createAsyncThunk<\n void,\n { userId: string },\n ThunkApi\n>(\"cloudFiles/reconcileTree\", async ({ userId }, { dispatch }) => {\n await dispatch(loadUserFileTree({ userId })).unwrap();\n});\n\nexport const loadFolderContents = createAsyncThunk<\n void,\n { folderId: string },\n ThunkApi\n>(\"cloudFiles/loadFolderContents\", async ({ folderId }, { dispatch }) => {\n // Virtual folders use their adapter's `list()` via `loadVirtualChildren`\n // — `cld_*` tables don't know about them and would 22P02 on the synthetic id.\n if (folderId.startsWith(\"vfs:\")) return;\n // RLS intentionally permits contextual reads for files conveyed through a\n // shared conversation, so a raw table enumeration here leaks those rows into\n // the global Files UI. Refresh through the discoverability-gated tree RPC —\n // one canonical list contract for roots and nested folders alike.\n const { data, error } = await getClaimsUser(supabase);\n if (error) throw error;\n if (!data.user?.id)\n throw new Error(\"Cannot load folder contents without a user\");\n await dispatch(loadUserFileTree({ userId: data.user.id })).unwrap();\n dispatch(markFolderFullyLoaded({ folderId }));\n});\n\n// Virtual ids look like `vfs:<adapter>:<vid>` and can't be queried against\n// the real `cld_*` tables — Postgres rejects them with `22P02 invalid input\n// syntax for type uuid`. The version / permission / share-link surfaces\n// aren't mapped to virtual sources in v1, so these thunks short-circuit\n// for synthetic ids. Consumers (FileVersionsList, useSharing) also render\n// a \"not supported here\" empty state so the UX is clear, not silent.\nfunction isVirtualResourceId(id: string): boolean {\n return id.startsWith(\"vfs:\");\n}\n\nexport const loadFileVersions = createAsyncThunk<\n void,\n { fileId: string },\n ThunkApi\n>(\"cloudFiles/loadFileVersions\", async ({ fileId }, { dispatch }) => {\n if (isVirtualResourceId(fileId)) return;\n const { data, error } = await filesDb(supabase)\n .from(\"file_versions\")\n .select(FILE_VERSIONS_TABLE_COLUMNS)\n .eq(\"file_id\", fileId)\n .order(\"version_number\", { ascending: false });\n if (error) throw pgErrorToError(error);\n const versions: CloudFileVersion[] = (data ?? []).map(\n dbRowToCloudFileVersion,\n );\n dispatch(upsertVersionsForFile({ fileId, versions }));\n});\n\nexport const loadPermissions = createAsyncThunk<\n void,\n { resourceId: string },\n ThunkApi\n>(\"cloudFiles/loadPermissions\", async ({ resourceId }, { dispatch }) => {\n if (isVirtualResourceId(resourceId)) return;\n // Canonical grant store is `public.permissions` (resource_type='file'),\n // NOT the legacy cld_ file-permission duplicate. RLS returns the rows the\n // caller is allowed to see (own grants / grants to them / org / public).\n const { data, error } = await supabase\n .schema(\"iam\")\n .from(\"permissions\")\n .select(\"*\")\n .eq(\"resource_type\", \"file\")\n .eq(\"resource_id\", resourceId);\n if (error) throw pgErrorToError(error);\n const permissions: CloudFilePermission[] = (data ?? []).map(\n dbRowToCloudFilePermission,\n );\n dispatch(upsertPermissionsForResource({ resourceId, permissions }));\n});\n\nexport const loadShareLinks = createAsyncThunk<\n void,\n { resourceId: string; resourceType?: ResourceType },\n ThunkApi\n>(\n \"cloudFiles/loadShareLinks\",\n async ({ resourceId, resourceType = \"file\" }, { dispatch }) => {\n if (isVirtualResourceId(resourceId)) return;\n // Canonical owner-gated list over platform.share_links.\n const links = await listCanonicalShareLinks(resourceType, resourceId);\n const shareLinks: CloudShareLink[] = links\n .filter((l) => l.isActive)\n .map((l) =>\n canonicalShareLinkToCloudShareLink(l, resourceType, resourceId),\n );\n dispatch(upsertShareLinksForResource({ resourceId, shareLinks }));\n },\n);\n\n// ---------------------------------------------------------------------------\n// Writes — folders\n// ---------------------------------------------------------------------------\n//\n// Folder-CRUD endpoints (`POST /folders`, `PATCH /folders/{id}`,\n// `DELETE /folders/{id}`) shipped 2026-04-26. These thunks now hit\n// the REST surface; legacy direct supabase-js writes were retired.\n\n/**\n * Create a folder via the Python REST contract. We hit `POST /folders` with\n * the explicit `{ folder_name, parent_id }` form because callers already\n * resolve the parent id from the tree. The path-style form (`folder_path`)\n * is preferred for upload-time auto-create, which `uploadFiles` handles.\n *\n * Architecturally important: this thunk no longer touches `cld_folders` from\n * the browser. Folder writes were the single biggest source of RLS recursion\n * regressions; routing them through the backend (which uses SECURITY DEFINER\n * helpers) makes the path uniform with file uploads.\n */\nexport const createFolder = createAsyncThunk<\n string,\n import(\"../types\").CreateFolderArg,\n ThunkApi\n>(\"cloudFiles/createFolder\", async (arg, { dispatch, getState }) => {\n const folderName = arg.folderName.trim();\n if (!folderName) {\n throw new Error(\"Folder name cannot be empty.\");\n }\n if (/[/\\\\]/.test(folderName)) {\n throw new Error(\"Folder names cannot contain '/' or '\\\\'.\");\n }\n // Reject NUL + ASCII control characters. These can be paste-injected\n // and confuse downstream object stores or any layer that materialises\n // the path on disk. Mirrors the rule in `validateRenameInput`.\n if (/[\\x00-\\x1f\\x7f]/.test(folderName)) {\n throw new Error(\"Folder names cannot contain control characters.\");\n }\n if (folderName.endsWith(\".\")) {\n throw new Error(\"Folder names cannot end with '.'.\");\n }\n // Sibling collision check — case-insensitive. Without this the thunk\n // happily issues `POST /folders` with a duplicate name and the backend\n // either returns a confusing UNIQUE-constraint error or (worse) silently\n // creates a sibling that's indistinguishable from the existing one.\n {\n const state = getState().cloudFiles;\n const lowered = folderName.toLowerCase();\n const collision = Object.values(state.foldersById).some(\n (f) =>\n !!f &&\n !f.deletedAt &&\n (f.parentId ?? null) === (arg.parentId ?? null) &&\n f.folderName.toLowerCase() === lowered,\n );\n if (collision) {\n throw new Error(\n `A folder named '${folderName}' already exists in this location.`,\n );\n }\n }\n\n const requestId = newRequestId();\n registerRequest({\n requestId,\n kind: \"folder-create\",\n resourceId: null,\n resourceType: \"folder\",\n });\n\n // Always use path-style: the backend rejects `{folder_name, parent_id}`\n // with `validation_error` (both at root with parent_id=null AND nested\n // with a parent uuid). Path-style creation is idempotent and creates\n // any missing intermediate segments. For nested creation we resolve the\n // parent's path from local state and append the new segment.\n let folderPath = folderName;\n if (arg.parentId !== null) {\n const parent = getState().cloudFiles.foldersById[arg.parentId];\n if (!parent) {\n // The parent isn't in this view's slice — a stale tree, not a missing\n // folder. Say what the user can do; the id stays out of the sentence\n // and in the rejected-thunk capture.\n throw new Error(\n \"We couldn't create the folder here — this view is out of date. Refresh and try again.\",\n );\n }\n folderPath = `${parent.folderPath}/${folderName}`;\n }\n const body = {\n folder_path: folderPath,\n visibility: arg.visibility ?? \"personal\",\n metadata: arg.metadata ?? null,\n };\n\n try {\n const { data: row } = await Folders.createFolder(body, { requestId });\n\n const folder = dbRowToCloudFolder(row);\n dispatch(upsertFolder(folder));\n dispatch(\n attachChildToFolder({\n parentFolderId: folder.parentId,\n kind: \"folder\",\n id: folder.id,\n }),\n );\n return folder.id;\n } catch (err) {\n if (process.env.NODE_ENV !== \"production\") {\n const e = err as {\n status?: number | null;\n code?: string;\n detail?: string;\n userMessage?: string;\n details?: unknown;\n };\n // Surface the full backend response so we can fix the contract drift\n // rather than show a generic \"request failed\" toast.\n console.error(\"[createFolder] backend error\", {\n status: e.status ?? null,\n code: e.code,\n detail: e.detail,\n userMessage: e.userMessage,\n details: e.details,\n sentBody: body,\n });\n }\n throw err;\n } finally {\n releaseRequest(requestId);\n }\n});\n\n/**\n * Update folder properties — rename, move (`parentId`), change visibility,\n * patch metadata. Optimistic: applies the patch locally before the REST call\n * and rolls back on failure.\n */\nexport const updateFolder = createAsyncThunk<\n void,\n import(\"../types\").UpdateFolderArg,\n ThunkApi\n>(\"cloudFiles/updateFolder\", async (arg, { dispatch, getState }) => {\n const state = getState();\n const folder = state.cloudFiles.foldersById[arg.folderId];\n if (!folder)\n throw new Error(\n \"We couldn't update that folder — this view is out of date. Refresh and try again.\",\n );\n\n const requestId = newRequestId();\n registerRequest({\n requestId,\n kind: \"folder-update\",\n resourceId: arg.folderId,\n resourceType: \"folder\",\n });\n\n // Optimistic: apply the patch locally.\n const patch = arg.patch;\n const optimistic: CloudFolder = {\n ...folder,\n ...(patch.folderName !== undefined ? { folderName: patch.folderName } : {}),\n ...(patch.parentId !== undefined ? { parentId: patch.parentId } : {}),\n ...(patch.visibility !== undefined ? { visibility: patch.visibility } : {}),\n ...(patch.metadata !== undefined ? { metadata: patch.metadata } : {}),\n };\n dispatch(upsertFolder(optimistic));\n\n // Track move in tree state — detach from old parent, attach under new.\n if (patch.parentId !== undefined && patch.parentId !== folder.parentId) {\n dispatch(\n detachChildFromFolder({\n parentFolderId: folder.parentId,\n kind: \"folder\",\n id: folder.id,\n }),\n );\n dispatch(\n attachChildToFolder({\n parentFolderId: patch.parentId,\n kind: \"folder\",\n id: folder.id,\n }),\n );\n }\n\n // The backend PATCH /folders honors folder_path / visibility / metadata ONLY;\n // it silently ignores folder_name / parent_id (same as POST /folders, which\n // rejects them). Rename AND move must therefore be expressed as the TARGET\n // folder_path — the server renames, reparents, and cascades descendants from\n // it. Sending folder_name/parent_id no-op'd server-side (rename/move reverted\n // on refresh). Compute the target path from the new name + new parent's path.\n let targetFolderPath: string | undefined;\n if (patch.folderName !== undefined || patch.parentId !== undefined) {\n const newName = patch.folderName ?? folder.folderName;\n const newParentId =\n patch.parentId !== undefined ? patch.parentId : folder.parentId;\n if (newParentId) {\n const newParent = state.cloudFiles.foldersById[newParentId];\n if (!newParent) {\n throw new Error(\n \"We couldn't move the folder there — this view is out of date. Refresh and try again.\",\n );\n }\n targetFolderPath = `${newParent.folderPath}/${newName}`;\n } else {\n targetFolderPath = newName;\n }\n }\n\n try {\n const { data: row } = await Folders.patchFolder(\n arg.folderId,\n {\n folder_path: targetFolderPath,\n visibility: patch.visibility,\n metadata: patch.metadata ?? null,\n },\n { requestId },\n );\n dispatch(upsertFolder(dbRowToCloudFolder(row)));\n } catch (err) {\n // Roll back to the pre-edit folder state and tree links (strip frozen\n // runtime fields — re-upserting a store record crashes the merge).\n dispatch(upsertFolder(toCloudFolderPartial(folder)));\n if (patch.parentId !== undefined && patch.parentId !== folder.parentId) {\n dispatch(\n detachChildFromFolder({\n parentFolderId: patch.parentId,\n kind: \"folder\",\n id: folder.id,\n }),\n );\n dispatch(\n attachChildToFolder({\n parentFolderId: folder.parentId,\n kind: \"folder\",\n id: folder.id,\n }),\n );\n }\n throw err;\n } finally {\n releaseRequest(requestId);\n }\n});\n\nexport const deleteFolder = createAsyncThunk<\n void,\n import(\"../types\").DeleteFolderArg,\n ThunkApi\n>(\"cloudFiles/deleteFolder\", async (arg, { dispatch, getState }) => {\n const state = getState();\n const folder = state.cloudFiles.foldersById[arg.folderId];\n if (!folder) return;\n\n const requestId = newRequestId();\n registerRequest({\n requestId,\n kind: \"folder-delete\",\n resourceId: arg.folderId,\n resourceType: \"folder\",\n });\n\n try {\n // Soft delete (Trash) cascades the subtree in SQL (soft_delete_folder) →\n // direct. There is no hard delete.\n await softDeleteFolderDirect(arg.folderId);\n dispatch(removeFolder({ id: arg.folderId }));\n } finally {\n releaseRequest(requestId);\n }\n});\n\n/**\n * Ensure every segment of `folderPath` exists; create any that don't.\n * Returns the leaf folder's id. Used for convention-based uploads like\n * \"save all pasted images under /Images\".\n */\nexport const ensureFolderPath = createAsyncThunk<\n string,\n import(\"../types\").EnsureFolderPathArg,\n ThunkApi\n>(\"cloudFiles/ensureFolderPath\", async (arg, { dispatch, getState }) => {\n const normalizedPath = arg.folderPath\n .split(\"/\")\n .map((s) => s.trim())\n .filter(Boolean)\n .join(\"/\");\n if (!normalizedPath) {\n throw new Error(\"folderPath cannot be empty.\");\n }\n\n // Realtime/tree state is the zero-request fast path.\n const existing = Object.values(getState().cloudFiles.foldersById).find(\n (folder) => folder.folderPath === normalizedPath && !folder.deletedAt,\n );\n if (existing) {\n return existing.id;\n }\n\n // The backend owns path traversal and race handling. One atomic request\n // replaces the obsolete per-segment files.folders PostgREST reads.\n const requestId = newRequestId();\n registerRequest({\n requestId,\n kind: \"folder-create\",\n resourceId: null,\n resourceType: \"folder\",\n });\n try {\n const { data: row } = await Folders.ensureFolderPath(\n normalizedPath,\n arg.visibility ?? \"personal\",\n { requestId },\n );\n const folder = dbRowToCloudFolder(row);\n dispatch(upsertFolder(folder));\n dispatch(\n attachChildToFolder({\n parentFolderId: folder.parentId,\n kind: \"folder\",\n id: folder.id,\n }),\n );\n return folder.id;\n } finally {\n releaseRequest(requestId);\n }\n});\n\n// ---------------------------------------------------------------------------\n// Writes — uploads (multi-file with progress)\n// ---------------------------------------------------------------------------\n\n/**\n * Say, on the screen the person is actually looking at, that an upload failed —\n * in the SERVER'S words.\n *\n * 🚨 THE 2026-09-19 SILENT FAILURE (`common-docs/systems/knowledge/ingestion/projects/acquisition-frontier/\n * own-files/VERIFICATION.md` §15/§16). A 21-file drop on a fresh Rulebook fired\n * 21 uploads and every one came back `400 matrx-files: this write carries no\n * organization`. Every sentence was captured correctly — into\n * `failed[].error` and into the upload slice — and then shown NOWHERE, because:\n *\n * - `mutation-toast-middleware` deliberately skips `uploadFiles`, on the\n * reasoning that \"uploads surface per-file errors inline in the\n * UploadProgressList\". That is true on the Files surface and FALSE\n * everywhere else — the Rulebook sources panel never renders that list.\n * - the capture toolbar that started the drop collapses N failures into\n * \"Failed to upload N files\", dropping every reason.\n *\n * So the person saw a card that still read \"Nothing attached yet\" and no reason\n * anywhere. An upload that failed must never be indistinguishable from an\n * upload nobody started.\n *\n * This announces from the THUNK, which every caller shares, so no surface can\n * forget. Reasons are deduped (21 identical 400s are one sentence, not 21\n * toasts), and the toast carries a stable id so the surrounding UploadProgressList\n * or capture toolbar cannot stack a second copy of the same news.\n */\nexport function announceUploadFailures(\n failed: ReadonlyArray<{ name: string; error: string }>,\n): void {\n if (failed.length === 0) return;\n const reasons = Array.from(\n new Set(failed.map((f) => f.error).filter((e) => e && e.trim().length > 0)),\n );\n const title =\n failed.length === 1\n ? `Couldn't upload ${failed[0].name}`\n : `Couldn't upload ${failed.length} files`;\n toast.error(title, {\n // The server's own sentence, never a sentence of ours. When several\n // distinct things went wrong, each one gets said.\n description:\n reasons.length > 0\n ? reasons.join(\" · \")\n : \"The server refused the upload and gave no reason.\",\n id: `upload-failed:${failed.map((f) => f.name).join(\"|\")}`,\n duration: 12000,\n });\n}\n\nexport const uploadFiles = createAsyncThunk<\n { uploaded: string[]; failed: Array<{ name: string; error: string }> },\n UploadFilesArg,\n ThunkApi\n>(\"cloudFiles/uploadFiles\", async (arg, { dispatch, getState }) => {\n const concurrency = Math.max(1, arg.concurrency ?? 3);\n const uploaded: string[] = [];\n // Track REAL error per file (not just filename). Without this, every\n // upload failure surfaces to callers as the file's name rather than the\n // backend's actual error code/message — which is what made the Phase\n // 11 migration look \"broken\" when really we just couldn't see why.\n const failed: Array<{ name: string; error: string }> = [];\n\n // Resolve logical path prefix.\n //\n // Order of preference:\n // 1. `folderPath` arg — passed directly (the Python backend\n // auto-creates the hierarchy server-side; the browser never has to\n // query `cld_folders` via supabase-js, which avoids the\n // well-known RLS recursion bug on the legacy file-permission table).\n // 2. `parentFolderId` — look up the folder in slice state (works when\n // the folder is already loaded from the tree RPC or realtime).\n // 3. Empty prefix — file lands at root.\n const state = getState();\n let prefix = \"\";\n if (arg.folderPath) {\n prefix = `${arg.folderPath.replace(/^\\/+|\\/+$/g, \"\")}/`;\n } else if (arg.parentFolderId) {\n const parentFolder = state.cloudFiles.foldersById[arg.parentFolderId];\n if (parentFolder) {\n prefix = `${parentFolder.folderPath}/`;\n }\n }\n\n // ─── Collision handling ───────────────────────────────────────────────\n //\n // Build a case-insensitive set of file names already present in the\n // destination. Used by `uniqueName` below to auto-rename uploads that\n // collide (\"report.pdf\" → \"report (1).pdf\"). Without this, the backend\n // either silently overwrites or version-bumps the existing file —\n // neither matches user expectations from Drive / Dropbox where a\n // duplicate upload becomes \"report (1).pdf\" by default.\n //\n // We only consider files in the SAME parent folder. The lookup is by\n // path-prefix because virtual folders may not have stable ids in\n // local state at upload time (e.g. `folderPath` arg with no\n // pre-loaded folder row).\n const existingNames = (() => {\n const out = new Set<string>();\n const targetPrefix = prefix; // captured outside; matches \"<path>/<name>\"\n for (const f of Object.values(state.cloudFiles.filesById)) {\n if (!f) continue;\n if (f.deletedAt) continue;\n // Match by storage path under the prefix. `f.filePath` is the\n // backend's canonical path including the file name.\n if (!f.filePath) continue;\n const normalized = f.filePath.replace(/^\\/+/, \"\");\n if (!normalized.startsWith(targetPrefix)) continue;\n const tail = normalized.slice(targetPrefix.length);\n // Skip files nested deeper than this folder.\n if (tail.includes(\"/\")) continue;\n if (tail) out.add(tail.toLowerCase());\n }\n return out;\n })();\n\n /**\n * Pick the next non-colliding name, mutating the queue:\n * \"report.pdf\" → \"report.pdf\" (no collision)\n * \"report.pdf\" → \"report (1).pdf\" (one collision)\n * \"report.pdf\" → \"report (2).pdf\" (two collisions)\n * \".env\" → \".env (1)\" (hidden file: suffix only)\n *\n * Reserves the chosen name in `existingNames` so back-to-back uploads\n * of the same source file (drop the same File twice) don't collide\n * with each other.\n */\n function uniqueName(originalName: string): string {\n const lowered = originalName.toLowerCase();\n if (!existingNames.has(lowered)) {\n existingNames.add(lowered);\n return originalName;\n }\n const dot = originalName.lastIndexOf(\".\");\n const isHidden = originalName.startsWith(\".\");\n const stem =\n dot > 0 && !isHidden ? originalName.slice(0, dot) : originalName;\n const ext = dot > 0 && !isHidden ? originalName.slice(dot) : \"\";\n for (let i = 1; i < 1000; i++) {\n const candidate = `${stem} (${i})${ext}`;\n if (!existingNames.has(candidate.toLowerCase())) {\n existingNames.add(candidate.toLowerCase());\n return candidate;\n }\n }\n // 1000 collisions — fall back to original name and let the backend\n // version-bump. Practically never reached.\n return originalName;\n }\n\n // ─── Per-file overrides from the duplicate-upload dialog ────────────\n //\n // When a user picks \"Overwrite\" on a duplicate, the dialog passes the\n // existing file's exact name back as a `filenameOverrides[index]`\n // entry. We honour the override verbatim — the auto \" (1)\" rename\n // would defeat the point. When they pick \"Skip\", the dialog adds the\n // index to `skipIndices` and we drop that file from the queue.\n const overrides = arg.filenameOverrides ?? {};\n const skipSet = new Set(arg.skipIndices ?? []);\n const forceNewCopySet = new Set(arg.forceNewCopyIndices ?? []);\n\n // Reserve override target names in `existingNames` BEFORE the loop\n // runs, so two overrides into the same path can't both win and so\n // a file with no override doesn't accidentally collide-rename onto\n // an override target.\n for (const [, name] of Object.entries(overrides)) {\n if (typeof name === \"string\") existingNames.add(name.toLowerCase());\n }\n\n // Build the queue as [originalIndex, file] pairs so workers can\n // look up overrides by index regardless of dequeue order.\n const queue: Array<{ index: number; file: File }> = arg.files\n .map((file, index) => ({ index, file }))\n .filter(({ index }) => !skipSet.has(index));\n\n async function worker(): Promise<void> {\n while (queue.length) {\n const next = queue.shift();\n if (!next) return;\n const { file, index } = next;\n const overrideName = overrides[index];\n const forcedCopy = forceNewCopySet.has(index);\n\n // Dedup guard (VERIFICATION.md §12, 2026-09-18): a caller-level retry\n // after a transient failure (dev-server connection reset, etc.) can\n // re-dispatch `uploadFiles` for a file whose FIRST attempt already\n // landed — the client has no proof it didn't, and the backend's own\n // `X-Idempotency-Key` only replays a 412, never a success (see\n // `features/files/upload/uploadDedupGuard.ts` for the full trace).\n // Only the plain path participates: an explicit \"Overwrite\" override\n // or \"force new copy\" means the caller WANTS a fresh write.\n const dedupKey =\n !overrideName && !forcedCopy\n ? uploadDedupKey(prefix, file.name, file.size)\n : null;\n let dedupClaim: UploadClaim | null = null;\n if (dedupKey) {\n const claim = claimUpload(dedupKey);\n if (claim.shared) {\n try {\n const outcome = await claim.promise;\n // Reuse the earlier attempt's outcome — no bytes sent again.\n uploaded.push(outcome.fileId);\n continue;\n } catch {\n // The earlier attempt genuinely failed, and the guard already\n // cleared its slot — fall through and upload for real below.\n }\n } else {\n dedupClaim = claim;\n }\n }\n\n const requestId = newRequestId();\n // Override (from duplicate dialog \"Overwrite\") wins. Otherwise\n // the auto \" (1)\" rename runs. We track the display name\n // separately from the original file.name so progress + telemetry\n // continue to use what the user dragged in.\n const targetName =\n typeof overrideName === \"string\" && overrideName\n ? overrideName\n : uniqueName(file.name);\n dispatch(\n trackUploadStart({\n requestId,\n fileName: targetName,\n fileSize: file.size,\n parentFolderId: arg.parentFolderId ?? null,\n folderPath: arg.folderPath ?? null,\n }),\n );\n registerRequest({\n requestId,\n kind: \"upload\",\n resourceId: null,\n resourceType: \"file\",\n });\n try {\n const { data } = await Files.uploadFileWithProgress(\n {\n file,\n filePath: `${prefix}${targetName}`,\n visibility: arg.visibility ?? \"personal\",\n shareWith: arg.shareWith,\n shareLevel: arg.shareLevel,\n changeSummary: arg.changeSummary,\n metadata: arg.metadata,\n options: arg.options,\n ...(forceNewCopySet.has(index)\n ? {\n intent: \"force_new_copy\" as const,\n reason:\n \"User selected Make a copy in the duplicate upload dialog\",\n }\n : {}),\n },\n (ev) =>\n dispatch(\n updateUploadProgress({\n requestId,\n bytesUploaded: ev.loaded,\n }),\n ),\n {\n requestId,\n // Idempotency key — same value across automatic retries of the\n // same intended upload. The backend stores it in\n // `metadata._idempotency_key` so retries don't create duplicate\n // version rows. We reuse `requestId` because it's generated\n // once per intended upload, before any retry.\n idempotencyKey: requestId,\n },\n );\n // Upsert file into slice from response.\n dispatch(\n upsertFile(\n apiFileRecordToCloudFile({\n id: data.file_id,\n owner_id: \"\",\n file_path: data.file_path,\n file_name: data.file_path.split(\"/\").pop() ?? targetName,\n mime_type: file.type || null,\n // Phase 0 rename — see docs/PYTHON_UPDATES.md §3.\n size_bytes: data.size_bytes,\n checksum: data.checksum,\n visibility: arg.visibility ?? \"personal\",\n current_version: data.version_number,\n parent_folder_id: arg.parentFolderId ?? null,\n metadata: arg.metadata ?? {},\n // P1-7: stamp the optimistic row with the current time instead of\n // null. The upload just happened, so \"now\" is accurate for the\n // recents filter / \"sort by modified\" until the realtime echo\n // delivers the authoritative server timestamp. Null sorted the\n // freshly-uploaded file to the wrong end of the list.\n created_at: new Date().toISOString(),\n updated_at: new Date().toISOString(),\n deleted_at: null,\n }),\n ),\n );\n // If this upload replaced an existing file (same logical path,\n // backend bumped current_version), the cached blob for the\n // original version is now stale. Invalidate so the next preview\n // open re-fetches the latest bytes.\n invalidateBlobCache(data.file_id);\n invalidateOfficeExtraction(data.file_id);\n dispatch(\n attachChildToFolder({\n parentFolderId: arg.parentFolderId ?? null,\n kind: \"file\",\n id: data.file_id,\n }),\n );\n dispatch(\n updateUploadStatus({\n requestId,\n status: \"success\",\n fileId: data.file_id,\n }),\n );\n uploaded.push(data.file_id);\n dedupClaim?.settle(\n Promise.resolve({ fileId: data.file_id, filePath: data.file_path }),\n );\n } catch (err) {\n const message = extractErrorMessage(err);\n dispatch(\n updateUploadStatus({\n requestId,\n status: \"error\",\n error: message,\n }),\n );\n failed.push({ name: file.name, error: message });\n dedupClaim?.settle(Promise.reject(err));\n } finally {\n releaseRequest(requestId);\n }\n }\n }\n\n await Promise.all(\n Array.from({ length: Math.min(concurrency, arg.files.length) }, () =>\n worker(),\n ),\n );\n\n announceUploadFailures(failed);\n return { uploaded, failed };\n});\n\nexport const clearUploadEntry = createAsyncThunk<\n void,\n { requestId: string },\n ThunkApi\n>(\"cloudFiles/clearUpload\", async ({ requestId }, { dispatch }) => {\n dispatch(clearUpload({ requestId }));\n});\n\n// ---------------------------------------------------------------------------\n// Writes — optimistic metadata updates\n// ---------------------------------------------------------------------------\n\nexport const renameFile = createAsyncThunk<void, RenameFileArg, ThunkApi>(\n \"cloudFiles/rename\",\n async ({ fileId, newName }, { dispatch, getState }) => {\n const record = getFileFromState(getState(), fileId);\n if (!record)\n throw new Error(\n \"We couldn't find that file in this view. Refresh and try again.\",\n );\n\n const requestId = newRequestId();\n // Per-resource op sequence — guards against an older response clobbering a\n // newer optimistic state (rename A→B→C out of order, or a double-click).\n const seq = beginResourceOp(fileId);\n const snapshot: CloudFileFieldSnapshot = {\n fileName: record.fileName,\n filePath: record.filePath,\n };\n\n // Compute new file path (replace final segment).\n const pathParts = record.filePath.split(\"/\");\n pathParts[pathParts.length - 1] = newName;\n const newPath = pathParts.join(\"/\");\n\n dispatch(setFileField({ id: fileId, field: \"fileName\", value: newName }));\n dispatch(setFileField({ id: fileId, field: \"filePath\", value: newPath }));\n dispatch(addFilePendingRequest({ id: fileId, requestId }));\n registerRequest({\n requestId,\n kind: \"rename\",\n resourceId: fileId,\n resourceType: \"file\",\n });\n\n try {\n // Use the dedicated rename endpoint — it accepts a full new path,\n // auto-creates parent folders if any segment is missing, and lets\n // the backend handle the file_path / storage-location update atomically.\n const { data } = await Files.renameFile(\n fileId,\n { new_path: newPath },\n { requestId },\n );\n // Only apply if no newer op for this file has begun since (out-of-order\n // guard); otherwise the newer op owns the state.\n if (isLatestResourceOp(fileId, seq)) {\n dispatch(upsertFile(apiFileRecordToCloudFile(data)));\n dispatch(markFileSaved({ id: fileId }));\n }\n } catch (err) {\n // Don't roll back if a newer op has superseded this one.\n if (isLatestResourceOp(fileId, seq)) {\n dispatch(rollbackFileOptimisticUpdate({ id: fileId, snapshot }));\n const msg = extractErrorMessage(err);\n dispatch(setFileError({ id: fileId, error: msg }));\n }\n throw err;\n } finally {\n dispatch(removeFilePendingRequest({ id: fileId, requestId }));\n releaseRequest(requestId);\n }\n },\n);\n\nexport const moveFile = createAsyncThunk<void, MoveFileArg, ThunkApi>(\n \"cloudFiles/move\",\n async ({ fileId, newParentFolderId }, { dispatch, getState }) => {\n const record = getFileFromState(getState(), fileId);\n if (!record)\n throw new Error(\n \"We couldn't find that file in this view. Refresh and try again.\",\n );\n const oldParentId = record.parentFolderId;\n const requestId = newRequestId();\n const seq = beginResourceOp(fileId);\n const snapshot: CloudFileFieldSnapshot = {\n parentFolderId: oldParentId,\n };\n\n dispatch(\n setFileField({\n id: fileId,\n field: \"parentFolderId\",\n value: newParentFolderId,\n }),\n );\n dispatch(\n detachChildFromFolder({\n parentFolderId: oldParentId,\n kind: \"file\",\n id: fileId,\n }),\n );\n dispatch(\n attachChildToFolder({\n parentFolderId: newParentFolderId,\n kind: \"file\",\n id: fileId,\n }),\n );\n dispatch(addFilePendingRequest({ id: fileId, requestId }));\n registerRequest({\n requestId,\n kind: \"move\",\n resourceId: fileId,\n resourceType: \"file\",\n });\n\n try {\n // Move = rename to a new full logical path. The backend's rename\n // endpoint auto-creates missing parent folders, so we just compute\n // `<targetFolderPath>/<filename>` (or `<filename>` for root) and\n // hand it over.\n const targetFolder =\n newParentFolderId === null\n ? null\n : (getState().cloudFiles.foldersById[newParentFolderId] ?? null);\n const targetPrefix = targetFolder ? `${targetFolder.folderPath}/` : \"\";\n const newPath = `${targetPrefix}${record.fileName}`;\n\n const { data } = await Files.renameFile(\n fileId,\n { new_path: newPath },\n { requestId },\n );\n if (isLatestResourceOp(fileId, seq)) {\n dispatch(upsertFile(apiFileRecordToCloudFile(data)));\n dispatch(markFileSaved({ id: fileId }));\n }\n } catch (err) {\n if (isLatestResourceOp(fileId, seq)) {\n // Rollback tree membership + field.\n dispatch(\n detachChildFromFolder({\n parentFolderId: newParentFolderId,\n kind: \"file\",\n id: fileId,\n }),\n );\n dispatch(\n attachChildToFolder({\n parentFolderId: oldParentId,\n kind: \"file\",\n id: fileId,\n }),\n );\n dispatch(rollbackFileOptimisticUpdate({ id: fileId, snapshot }));\n const msg = extractErrorMessage(err);\n dispatch(setFileError({ id: fileId, error: msg }));\n }\n throw err;\n } finally {\n dispatch(removeFilePendingRequest({ id: fileId, requestId }));\n releaseRequest(requestId);\n }\n },\n);\n\nexport const updateFileMetadata = createAsyncThunk<\n void,\n UpdateFileMetadataArg,\n ThunkApi\n>(\n \"cloudFiles/updateMetadata\",\n async ({ fileId, patch }, { dispatch, getState }) => {\n const record = getFileFromState(getState(), fileId);\n if (!record)\n throw new Error(\n \"We couldn't find that file in this view. Refresh and try again.\",\n );\n\n const requestId = newRequestId();\n const seq = beginResourceOp(fileId);\n const snapshot: CloudFileFieldSnapshot = {\n visibility: record.visibility,\n metadata: record.metadata,\n };\n\n if (patch.visibility !== undefined) {\n dispatch(\n setFileField({\n id: fileId,\n field: \"visibility\",\n value: patch.visibility,\n }),\n );\n }\n if (patch.metadata !== undefined) {\n dispatch(\n setFileField({\n id: fileId,\n field: \"metadata\",\n value: patch.metadata,\n }),\n );\n }\n dispatch(addFilePendingRequest({ id: fileId, requestId }));\n registerRequest({\n requestId,\n kind: \"update\",\n resourceId: fileId,\n resourceType: \"file\",\n });\n\n try {\n const { data } = await Files.patchFile(\n fileId,\n {\n visibility: patch.visibility,\n metadata: patch.metadata,\n },\n { requestId },\n );\n if (isLatestResourceOp(fileId, seq)) {\n dispatch(upsertFile(apiFileRecordToCloudFile(data)));\n dispatch(markFileSaved({ id: fileId }));\n }\n } catch (err) {\n if (isLatestResourceOp(fileId, seq)) {\n dispatch(rollbackFileOptimisticUpdate({ id: fileId, snapshot }));\n const msg = extractErrorMessage(err);\n dispatch(setFileError({ id: fileId, error: msg }));\n }\n throw err;\n } finally {\n dispatch(removeFilePendingRequest({ id: fileId, requestId }));\n releaseRequest(requestId);\n }\n },\n);\n\n// ---------------------------------------------------------------------------\n// Writes — delete\n// ---------------------------------------------------------------------------\n\nexport const deleteFile = createAsyncThunk<void, DeleteFileArg, ThunkApi>(\n \"cloudFiles/delete\",\n async ({ fileId }, { dispatch, getState }) => {\n const record = getFileFromState(getState(), fileId);\n if (!record) return; // nothing to do\n const parentFolderId = record.parentFolderId;\n const requestId = newRequestId();\n\n // Optimistic remove.\n dispatch(removeFile({ id: fileId }));\n dispatch(\n detachChildFromFolder({\n parentFolderId,\n kind: \"file\",\n id: fileId,\n }),\n );\n // Drop the cached blob bytes — the file is gone, no point holding\n // memory for something the user can't open anymore (and if the\n // delete is rolled back on error, the next open will re-fetch).\n invalidateBlobCache(fileId);\n invalidateOfficeExtraction(fileId);\n registerRequest({\n requestId,\n kind: \"delete\",\n resourceId: fileId,\n resourceType: \"file\",\n });\n\n try {\n // Soft delete (Trash) is a pure metadata write → direct (canonical).\n // There is no hard delete; the file stays restorable from Trash.\n await softDeleteFileDirect(fileId);\n } catch (err) {\n // Rollback — reinsert the record and reattach to its parent.\n dispatch(upsertFile(toCloudFilePartial(record)));\n dispatch(\n attachChildToFolder({\n parentFolderId,\n kind: \"file\",\n id: fileId,\n }),\n );\n throw err;\n } finally {\n releaseRequest(requestId);\n }\n },\n);\n\n// ---------------------------------------------------------------------------\n// Trash — hydrate, restore (delete means archive: no purge from the client)\n// ---------------------------------------------------------------------------\n\n/**\n * Hydrate the trash section. The main tree load runs with\n * `p_include_deleted: false` and every delete/echo REMOVES rows from the\n * store, so soft-deleted records never reach Redux on their own — this thunk\n * pages the same RPC with deleted rows included and upserts ONLY the trashed\n * ones (`deletedAt` set). It never touches the folder tree spine, so trashed\n * rows can't leak into live folder listings; the section filters in\n * PageShell/row-data pick them up by `deletedAt`.\n */\n\n/**\n * A store record carries immer-frozen runtime bookkeeping (`_loadedFields`,\n * `_dirtyFields`, ...). Re-dispatching it into `upsertFile` on the INSERT path\n * would adopt those frozen objects into the new draft and crash the next\n * merge (\"object is not extensible\") — strip to domain fields first. Used by\n * every optimistic-rollback site.\n */\nfunction toCloudFilePartial(record: CloudFileRecord): Partial<CloudFile> {\n const {\n _dirty,\n _dirtyFields,\n _fieldHistory,\n _loadedFields,\n _loading,\n _error,\n _pendingRequestIds,\n ...domain\n } = record;\n return domain;\n}\n\n/** Folder twin of `toCloudFilePartial` — same frozen-runtime-fields hazard. */\nfunction toCloudFolderPartial(record: CloudFolderRecord): Partial<CloudFolder> {\n const {\n _dirty,\n _dirtyFields,\n _fieldHistory,\n _loadedFields,\n _loading,\n _error,\n _pendingRequestIds,\n ...domain\n } = record;\n return domain;\n}\n\nexport const loadTrash = createAsyncThunk<void, { userId: string }, ThunkApi>(\n \"cloudFiles/loadTrash\",\n async ({ userId }, { dispatch }) => {\n const rows: ReturnType<typeof parseCloudTreeRows> = [];\n for (let page = 0; page < TREE_MAX_PAGES; page += 1) {\n const { data, error } = await supabase.rpc(\"get_user_file_tree\", {\n p_user_id: userId,\n p_limit: TREE_PAGE_SIZE,\n p_offset: page * TREE_PAGE_SIZE,\n p_include_folders: true,\n p_include_deleted: true,\n });\n if (error) throw error;\n const pageRows = parseCloudTreeRows(data);\n rows.push(...pageRows);\n if (pageRows.length < TREE_PAGE_SIZE) break;\n }\n\n const files: Partial<CloudFile>[] = [];\n const folders: Partial<CloudFolder>[] = [];\n for (const row of rows) {\n if (!row.deleted_at) continue;\n if (row.kind === \"file\") {\n // Already filtered by the RPC's predicate — see above.\n files.push({\n id: row.id,\n ownerId: row.owner_id,\n filePath: row.file_path,\n fileName: row.file_name,\n parentFolderId: row.parent_folder_id,\n mimeType: row.mime_type,\n fileSize: row.size_bytes,\n visibility: row.visibility,\n currentVersion: row.current_version,\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n deletedAt: row.deleted_at,\n originDeviceId: row.origin_device_id,\n });\n } else {\n folders.push({\n id: row.id,\n ownerId: row.owner_id,\n folderPath: row.folder_path,\n folderName: row.folder_name,\n parentId: row.parent_id,\n visibility: row.visibility,\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n deletedAt: row.deleted_at,\n });\n }\n }\n dispatch(upsertFiles(files));\n dispatch(upsertFolders(folders));\n },\n);\n\n/** Restore a trashed file: flip `deletedAt` off and reattach to its folder. */\nexport const restoreFile = createAsyncThunk<void, { fileId: string }, ThunkApi>(\n \"cloudFiles/restoreFile\",\n async ({ fileId }, { dispatch, getState }) => {\n const record = getFileFromState(getState(), fileId);\n if (!record?.deletedAt) return;\n const requestId = newRequestId();\n\n // Optimistic: back to live, back under its parent.\n dispatch(upsertFile({ ...toCloudFilePartial(record), deletedAt: null }));\n dispatch(\n attachChildToFolder({\n parentFolderId: record.parentFolderId,\n kind: \"file\",\n id: fileId,\n }),\n );\n registerRequest({\n requestId,\n kind: \"file-restore\",\n resourceId: fileId,\n resourceType: \"file\",\n });\n try {\n await restoreFileDirect(fileId);\n } catch (err) {\n dispatch(upsertFile(toCloudFilePartial(record))); // rollback to trashed (clone — state objects are frozen)\n dispatch(\n detachChildFromFolder({\n parentFolderId: record.parentFolderId,\n kind: \"file\",\n id: fileId,\n }),\n );\n throw err;\n } finally {\n releaseRequest(requestId);\n }\n },\n);\n\n/**\n * Restore a trashed folder + its subtree. The RPC un-deletes descendants in\n * SQL; rather than mirroring that cascade optimistically we reconcile by\n * reloading the live tree afterwards (folder restore is rare — correctness\n * over cleverness).\n */\nexport const restoreFolder = createAsyncThunk<\n void,\n { folderId: string },\n ThunkApi\n>(\"cloudFiles/restoreFolder\", async ({ folderId }, { dispatch, getState }) => {\n const folder = getState().cloudFiles.foldersById[folderId];\n if (!folder?.deletedAt) return;\n await restoreFolderDirect(folderId);\n dispatch(upsertFolder({ ...toCloudFolderPartial(folder), deletedAt: null }));\n const userId = folder.ownerId;\n await dispatch(loadUserFileTree({ userId }));\n await dispatch(loadTrash({ userId }));\n});\n\n// ---------------------------------------------------------------------------\n// Writes — versions\n// ---------------------------------------------------------------------------\n\nexport const restoreVersion = createAsyncThunk<\n void,\n RestoreVersionArg,\n ThunkApi\n>(\n \"cloudFiles/restoreVersion\",\n async ({ fileId, versionNumber }, { dispatch }) => {\n if (isVirtualResourceId(fileId)) return;\n dispatch(setFileLoading({ id: fileId, loading: true }));\n const requestId = newRequestId();\n registerRequest({\n requestId,\n kind: \"restore-version\",\n resourceId: fileId,\n resourceType: \"file\",\n });\n try {\n const { data } = await Versions.restoreVersion(fileId, versionNumber, {\n requestId,\n });\n dispatch(upsertFile(apiFileRecordToCloudFile(data)));\n // The current bytes just changed — drop any cached blob so the\n // next preview reads the restored version, not the in-memory\n // copy of the version that was active before restore.\n invalidateBlobCache(fileId);\n invalidateOfficeExtraction(fileId);\n // Reload version list to pick up the new synthetic version row.\n await dispatch(loadFileVersions({ fileId })).unwrap();\n } finally {\n dispatch(setFileLoading({ id: fileId, loading: false }));\n releaseRequest(requestId);\n }\n },\n);\n\n/**\n * THE edit-save path: write new content as the next version of the SAME file.\n *\n * Every editor that saves a file's text goes through here — never `uploadFiles`.\n * `uploadFiles` is the NEW-file path: it auto-renames a colliding name to\n * \"name (1).ext\" (Drive semantics for a dropped duplicate), so an editor that\n * re-uploaded its own file created a second file and left the original at\n * version 1 while the UI said \"Saved\" (2026-09-30).\n *\n * ONE path for everyone: `POST /files/{id}/versions` writes by file ID, so the\n * owner and an editor holding an edit grant on someone else's file save the\n * same way (the server checks the caller's write right, keeps the file's owner\n * and path, and records the caller + `changeSummary` on the version). The\n * answer is still REFUSED if it is not that same row (a different `file_id` or\n * `is_new: true`), so a save can never silently become a copy.\n */\nexport const saveFileNewVersion = createAsyncThunk<\n SaveFileNewVersionResult,\n SaveFileNewVersionArg,\n ThunkApi\n>(\n \"cloudFiles/saveFileNewVersion\",\n async ({ fileId, content, changeSummary }, { dispatch, getState }) => {\n if (isVirtualResourceId(fileId)) {\n throw new Error(\n \"saveFileNewVersion is for stored files; virtual files save through writeAny.\",\n );\n }\n let record: CloudFile | undefined = getState().cloudFiles.filesById[fileId];\n // A row hydrated for rendering only may not carry its name/mime — read the\n // file's own metadata rather than guessing.\n if (!record?.fileName) {\n const { data } = await Files.getFileMetadata(fileId);\n const fresh = apiFileRecordToCloudFile(data);\n dispatch(upsertFile(fresh));\n record = fresh;\n }\n const mimeType = record.mimeType ?? \"text/plain\";\n const body =\n typeof content === \"string\"\n ? new Blob([content], { type: mimeType })\n : content;\n const upload = new File([body], record.fileName, { type: mimeType });\n\n dispatch(setFileLoading({ id: fileId, loading: true }));\n const requestId = newRequestId();\n registerRequest({\n requestId,\n kind: \"upload\",\n resourceId: fileId,\n resourceType: \"file\",\n });\n try {\n const reqOpts = { requestId, idempotencyKey: requestId };\n const summary = changeSummary ?? \"Edited in place\";\n const { data } = await Files.uploadNewVersion(\n fileId,\n { file: upload, changeSummary: summary },\n reqOpts,\n );\n if (data.file_id !== fileId || data.is_new) {\n throw new Error(\n `Saving \"${record.fileName}\" did not update it — the files service wrote ` +\n `a separate file (${data.file_path}, id ${data.file_id}) instead of ` +\n `version ${record.currentVersion + 1}. Your original is unchanged; ` +\n `remove the extra file from the folder.`,\n );\n }\n dispatch(\n upsertFile({\n id: fileId,\n currentVersion: data.version_number,\n fileSize: data.size_bytes,\n checksum: data.checksum,\n updatedAt: new Date().toISOString(),\n }),\n );\n // The current bytes are the ones just uploaded — cache them as the new\n // version so every viewer shows it without downloading them back.\n replaceWithSavedBytes(fileId, body, data.version_number);\n invalidateOfficeExtraction(fileId);\n // Refresh the history without holding the save on it — the save has\n // landed, and \"Saving…\" must not wait on a list read.\n void dispatch(loadFileVersions({ fileId }))\n .unwrap()\n .catch((err: unknown) => {\n // The save landed; only the history refresh failed. Say so.\n console.error(\n \"[saveFileNewVersion] saved, but the version list did not refresh:\",\n err,\n );\n });\n return { fileId, versionNumber: data.version_number };\n } finally {\n dispatch(setFileLoading({ id: fileId, loading: false }));\n releaseRequest(requestId);\n }\n },\n);\n\n// ---------------------------------------------------------------------------\n// Writes — permissions\n// ---------------------------------------------------------------------------\n\n/**\n * The by-grantee REST grant/revoke endpoints (`GrantPermissionRequest`,\n * `?grantee_type=`) accept only `user | group`. PUBLIC access is not a\n * by-grantee grant — it is toggled on the resource's visibility. Reaching the\n * grant/revoke thunks with `granteeType: \"public\"` is a programming error, so\n * fail loudly (repo principle) rather than silently coercing it to a bogus\n * \"user\"/\"group\" call against an empty/row-id grantee.\n */\nfunction requireByGranteeType(\n granteeType: GranteeType | undefined,\n): \"user\" | \"group\" {\n if (granteeType === \"public\") {\n throw new Error(\n \"Public access is toggled via the resource's visibility, not the by-grantee grant/revoke path.\",\n );\n }\n return granteeType ?? \"user\";\n}\n\nexport const grantPermission = createAsyncThunk<\n void,\n GrantPermissionArg,\n ThunkApi\n>(\"cloudFiles/grantPermission\", async (arg, { dispatch }) => {\n if (isVirtualResourceId(arg.resourceId)) {\n throw new Error(\"Sharing isn't available for this source yet.\");\n }\n const requestId = newRequestId();\n registerRequest({\n requestId,\n kind: \"grant-permission\",\n resourceId: arg.resourceId,\n resourceType: arg.resourceType,\n });\n try {\n const body = {\n grantee_id: arg.granteeId,\n level: arg.level,\n grantee_type: requireByGranteeType(arg.granteeType),\n expires_at: arg.expiresAt ?? null,\n };\n const { data } =\n arg.resourceType === \"folder\"\n ? await Permissions.grantFolderPermission(arg.resourceId, body, {\n requestId,\n })\n : await Permissions.grantFilePermission(arg.resourceId, body, {\n requestId,\n });\n dispatch(\n upsertPermissionsForResource({\n resourceId: arg.resourceId,\n permissions: [rpcPermissionRowToCloudFilePermission(data)],\n }),\n );\n // P1-6: the returned row already makes the UI correct. Reconcile the full\n // list in the BACKGROUND (don't await) so the dialog doesn't stall on a\n // second round-trip after every grant.\n void dispatch(loadPermissions({ resourceId: arg.resourceId }));\n } finally {\n releaseRequest(requestId);\n }\n});\n\nexport const revokePermission = createAsyncThunk<\n void,\n RevokePermissionArg,\n ThunkApi\n>(\"cloudFiles/revokePermission\", async (arg, { dispatch }) => {\n if (isVirtualResourceId(arg.resourceId)) {\n throw new Error(\"Sharing isn't available for this source yet.\");\n }\n const requestId = newRequestId();\n registerRequest({\n requestId,\n kind: \"revoke-permission\",\n resourceId: arg.resourceId,\n resourceType: arg.resourceType,\n });\n try {\n const granteeType = requireByGranteeType(arg.granteeType);\n if (arg.resourceType === \"folder\") {\n await Permissions.revokeFolderPermission(\n arg.resourceId,\n arg.granteeId,\n { granteeType },\n { requestId },\n );\n } else {\n await Permissions.revokeFilePermission(\n arg.resourceId,\n arg.granteeId,\n { granteeType },\n { requestId },\n );\n }\n dispatch(\n removePermissionForResource({\n resourceId: arg.resourceId,\n granteeId: arg.granteeId,\n granteeType,\n }),\n );\n } finally {\n releaseRequest(requestId);\n }\n});\n\n// ---------------------------------------------------------------------------\n// Writes — share links\n// ---------------------------------------------------------------------------\n\nexport const createShareLink = createAsyncThunk<\n CloudShareLink,\n CreateShareLinkArg,\n ThunkApi\n>(\"cloudFiles/createShareLink\", async (arg, { dispatch, getState }) => {\n if (isVirtualResourceId(arg.resourceId)) {\n throw new Error(\"Share links aren't available for this source yet.\");\n }\n // P1-1: a second click while the first create is in flight would mint a\n // second token for the same resource. Suppress the duplicate submit.\n if (hasInFlight(arg.resourceId, \"create-share-link\")) {\n throw new Error(\"A share link is already being created for this item.\");\n }\n const requestId = newRequestId();\n registerRequest({\n requestId,\n kind: \"create-share-link\",\n resourceId: arg.resourceId,\n resourceType: arg.resourceType,\n });\n try {\n // Canonical owner-gated mint over platform.share_links.\n const created = await createCanonicalShareLink({\n resourceType: arg.resourceType,\n resourceId: arg.resourceId,\n permissionLevel: arg.permissionLevel,\n expiresAt: arg.expiresAt ?? null,\n maxUses: arg.maxUses ?? null,\n });\n if (!created.success || !created.token) {\n throw new Error(created.error ?? \"Failed to create share link\");\n }\n // Reload the full list — the create RPC returns only { token, id }.\n await dispatch(\n loadShareLinks({\n resourceId: arg.resourceId,\n resourceType: arg.resourceType,\n }),\n ).unwrap();\n const link = (\n getState().cloudFiles.shareLinksByResourceId[arg.resourceId] ?? []\n ).find((l) => l.shareToken === created.token);\n if (!link) {\n throw new Error(\"Share link was created but could not be loaded back\");\n }\n return link;\n } finally {\n releaseRequest(requestId);\n }\n});\n\nexport const revokeShareLink = createAsyncThunk<\n void,\n RevokeShareLinkArg,\n ThunkApi\n>(\"cloudFiles/revokeShareLink\", async ({ linkId }, { dispatch }) => {\n const requestId = newRequestId();\n registerRequest({\n requestId,\n kind: \"revoke-share-link\",\n resourceId: null,\n resourceType: null,\n });\n try {\n const result = await revokeCanonicalShareLink(linkId);\n if (!result.success) {\n throw new Error(result.error ?? \"Failed to revoke share link\");\n }\n dispatch(removeShareLink({ linkId }));\n } finally {\n releaseRequest(requestId);\n }\n});\n\n// ---------------------------------------------------------------------------\n// Utility — getFileUrl (no slice state change).\n//\n// Routes through the universal handler and returns the durable renderable\n// URL (permanent CDN for public files, the durable authenticated download\n// route otherwise). Durable URLs never expire.\n// ---------------------------------------------------------------------------\n\nexport const getFileUrl = createAsyncThunk<\n { url: string },\n { fileId: string },\n ThunkApi\n>(\"cloudFiles/getFileUrl\", async ({ fileId }) => {\n if (isVirtualResourceId(fileId)) {\n // Virtual files don't have stored bytes — there's no byte URL. Callers\n // already hide the buttons that surface URLs (Download, Copy link,\n // Open in new tab) for virtual rows; this guards the API path for any\n // remaining callers.\n throw new Error(\"Byte URLs aren't available for virtual sources\");\n }\n const url = await fileHandler\n .use({ kind: \"file_id\", fileId })\n .as({ kind: \"html_src\" });\n if (!url) {\n throw new Error(`Could not resolve a URL for file ${fileId}`);\n }\n return { url };\n});\n\n// ---------------------------------------------------------------------------\n// Bulk operations (Python P-7)\n// ---------------------------------------------------------------------------\n//\n// Each bulk thunk applies the optimistic local change up front, then makes a\n// single REST round-trip. The backend returns a per-item succeeded/failed\n// envelope; we re-apply the failed entries by re-upserting their pre-change\n// state from a snapshot taken before the optimistic update.\n\n/**\n * Move many files to Trash (soft delete) in one round-trip.\n *\n * Returns the standard `BulkResponse` envelope so the caller can decide\n * how to surface partial failures (toast vs. row-level error chips).\n * Successful ids are removed from the local store immediately for\n * snappy UI; failed ids are restored from the pre-change snapshot.\n */\n/**\n * P1-5: bulk ops resolve successfully even when some items fail (per-item\n * results in the envelope), so the rejected-thunk toast middleware never sees\n * them. Surface partial failures here so the user isn't left thinking every\n * item succeeded. Full success stays silent (the rows visibly changed).\n */\nfunction toastBulkPartialFailure(result: BulkResponse, verb: string): void {\n if (result.failed > 0) {\n const total = result.succeeded + result.failed;\n toast.error(`Couldn't ${verb} ${result.failed} of ${total} items`, {\n description:\n result.succeeded > 0\n ? `${result.succeeded} succeeded. The rest were restored.`\n : \"All items were restored.\",\n });\n }\n}\n\nexport const bulkDeleteFiles = createAsyncThunk<\n BulkResponse,\n BulkDeleteFilesArg,\n ThunkApi\n>(\"cloudFiles/bulkDeleteFiles\", async (arg, { dispatch, getState }) => {\n if (arg.fileIds.length === 0) {\n return { results: [], succeeded: 0, failed: 0 };\n }\n\n // Snapshot pre-change records for rollback on per-item failures.\n const state = getState();\n const snapshots = new Map<string, CloudFile>();\n for (const id of arg.fileIds) {\n const file = state.cloudFiles.filesById[id];\n if (file) snapshots.set(id, file);\n }\n\n const requestId = newRequestId();\n registerRequest({\n requestId,\n kind: \"bulk-delete-files\",\n resourceId: null,\n resourceType: \"file\",\n });\n\n // Optimistic: remove all targets from the local store and drop their\n // cached blobs (deletion makes the cache entry useless and keeping\n // bytes around for soft-deleted items wastes the LRU budget).\n for (const id of arg.fileIds) {\n dispatch(removeFile({ id }));\n invalidateBlobCache(id);\n invalidateOfficeExtraction(id);\n }\n\n try {\n const { data } = await Files.bulkDeleteFiles(\n { file_ids: arg.fileIds },\n { requestId },\n );\n const result: BulkResponse = data ?? {\n results: arg.fileIds.map((id) => ({ id, ok: true, error: null })),\n succeeded: arg.fileIds.length,\n failed: 0,\n };\n\n // Roll back any items the backend reports as failed.\n for (const r of result.results) {\n if (r.ok) continue;\n const snap = snapshots.get(r.id);\n if (snap) dispatch(upsertFile(snap));\n }\n toastBulkPartialFailure(result, \"delete\");\n return result;\n } catch (err) {\n // Whole-call failure — restore everything we removed.\n for (const snap of snapshots.values()) dispatch(upsertFile(snap));\n throw err;\n } finally {\n releaseRequest(requestId);\n }\n});\n\n/**\n * Move many files to a new parent folder in one round-trip. Pass `null` to\n * move to root.\n */\nexport const bulkMoveFiles = createAsyncThunk<\n BulkResponse,\n BulkMoveFilesArg,\n ThunkApi\n>(\"cloudFiles/bulkMoveFiles\", async (arg, { dispatch, getState }) => {\n if (arg.fileIds.length === 0) {\n return { results: [], succeeded: 0, failed: 0 };\n }\n\n const state = getState();\n const snapshots = new Map<string, CloudFile>();\n for (const id of arg.fileIds) {\n const file = state.cloudFiles.filesById[id];\n if (file) snapshots.set(id, file);\n }\n\n const requestId = newRequestId();\n registerRequest({\n requestId,\n kind: \"bulk-move-files\",\n resourceId: null,\n resourceType: \"file\",\n });\n\n // Optimistic: re-parent + retract from old parent / attach to new in tree.\n for (const [id, file] of snapshots) {\n if (file.parentFolderId === arg.newParentFolderId) continue;\n dispatch(\n detachChildFromFolder({\n parentFolderId: file.parentFolderId,\n kind: \"file\",\n id,\n }),\n );\n dispatch(\n attachChildToFolder({\n parentFolderId: arg.newParentFolderId,\n kind: \"file\",\n id,\n }),\n );\n dispatch(upsertFile({ ...file, parentFolderId: arg.newParentFolderId }));\n }\n\n try {\n const { data } = await Files.bulkMoveFiles(\n {\n file_ids: arg.fileIds,\n new_parent_folder_id: arg.newParentFolderId,\n },\n { requestId },\n );\n\n // Roll back per-item failures.\n for (const r of data.results) {\n if (r.ok) continue;\n const snap = snapshots.get(r.id);\n if (!snap || snap.parentFolderId === arg.newParentFolderId) continue;\n dispatch(\n detachChildFromFolder({\n parentFolderId: arg.newParentFolderId,\n kind: \"file\",\n id: snap.id,\n }),\n );\n dispatch(\n attachChildToFolder({\n parentFolderId: snap.parentFolderId,\n kind: \"file\",\n id: snap.id,\n }),\n );\n dispatch(upsertFile(snap));\n }\n toastBulkPartialFailure(data, \"move\");\n return data;\n } catch (err) {\n // Whole-call failure — restore everything.\n for (const snap of snapshots.values()) {\n if (snap.parentFolderId === arg.newParentFolderId) continue;\n dispatch(\n detachChildFromFolder({\n parentFolderId: arg.newParentFolderId,\n kind: \"file\",\n id: snap.id,\n }),\n );\n dispatch(\n attachChildToFolder({\n parentFolderId: snap.parentFolderId,\n kind: \"file\",\n id: snap.id,\n }),\n );\n dispatch(upsertFile(snap));\n }\n throw err;\n } finally {\n releaseRequest(requestId);\n }\n});\n\n/**\n * Move many folders to a new parent in one round-trip. Pass `null` to move\n * to root. The backend cascades `folder_path` updates to descendants.\n */\nexport const bulkMoveFolders = createAsyncThunk<\n BulkResponse,\n BulkMoveFoldersArg,\n ThunkApi\n>(\"cloudFiles/bulkMoveFolders\", async (arg, { dispatch, getState }) => {\n if (arg.folderIds.length === 0) {\n return { results: [], succeeded: 0, failed: 0 };\n }\n\n const state = getState();\n const snapshots = new Map<string, CloudFolder>();\n for (const id of arg.folderIds) {\n const folder = state.cloudFiles.foldersById[id];\n if (folder) snapshots.set(id, folder);\n }\n\n const requestId = newRequestId();\n registerRequest({\n requestId,\n kind: \"bulk-move-folders\",\n resourceId: null,\n resourceType: \"folder\",\n });\n\n // Optimistic: rewire each folder under the new parent.\n for (const [id, folder] of snapshots) {\n if (folder.parentId === arg.newParentId) continue;\n dispatch(\n detachChildFromFolder({\n parentFolderId: folder.parentId,\n kind: \"folder\",\n id,\n }),\n );\n dispatch(\n attachChildToFolder({\n parentFolderId: arg.newParentId,\n kind: \"folder\",\n id,\n }),\n );\n dispatch(upsertFolder({ ...folder, parentId: arg.newParentId }));\n }\n\n try {\n const { data } = await Folders.bulkMoveFolders(\n { folder_ids: arg.folderIds, new_parent_id: arg.newParentId },\n { requestId },\n );\n\n // Roll back per-item failures.\n for (const r of data.results) {\n if (r.ok) continue;\n const snap = snapshots.get(r.id);\n if (!snap || snap.parentId === arg.newParentId) continue;\n dispatch(\n detachChildFromFolder({\n parentFolderId: arg.newParentId,\n kind: \"folder\",\n id: snap.id,\n }),\n );\n dispatch(\n attachChildToFolder({\n parentFolderId: snap.parentId,\n kind: \"folder\",\n id: snap.id,\n }),\n );\n dispatch(upsertFolder(snap));\n }\n toastBulkPartialFailure(data, \"move\");\n return data;\n } catch (err) {\n for (const snap of snapshots.values()) {\n if (snap.parentId === arg.newParentId) continue;\n dispatch(\n detachChildFromFolder({\n parentFolderId: arg.newParentId,\n kind: \"folder\",\n id: snap.id,\n }),\n );\n dispatch(\n attachChildToFolder({\n parentFolderId: snap.parentId,\n kind: \"folder\",\n id: snap.id,\n }),\n );\n dispatch(upsertFolder(snap));\n }\n throw err;\n } finally {\n releaseRequest(requestId);\n }\n});\n"],"mappings":"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","names":["rows"]}
1
+ {"version":3,"sources":["../../../../src/files/engine/redux/thunks.ts"],"sourcesContent":["/**\n * features/files/redux/thunks.ts\n *\n * Async orchestration for cloud files: reads via supabase-js (RLS), writes via\n * the REST API client. Every mutation follows the pattern:\n *\n * 1. Snapshot current state for rollback.\n * 2. Dispatch optimistic reducer immediately.\n * 3. Register the requestId in the ledger (for realtime echo dedup).\n * 4. Call the REST API.\n * 5. On success: markSaved, release ledger entry.\n * 6. On error: dispatch rollback, set error, release ledger entry, rethrow.\n */\n\n\"use client\";\n\nimport { createAsyncThunk } from \"@reduxjs/toolkit\";\nimport type { ThunkDispatch, UnknownAction } from \"@reduxjs/toolkit\";\nimport type { CloudFilesState } from \"../types\";\n\n// Minimal local types — avoids importing from store.ts (which imports this\n// module via rootReducer → middleware chain), breaking the type-level cycle.\ntype StateWithCloudFiles = { cloudFiles: CloudFilesState };\ntype AppDispatch = ThunkDispatch<StateWithCloudFiles, unknown, UnknownAction>;\nimport { supabase } from \"../host/supabase\";\nimport { filesDb, FILE_VERSIONS_TABLE_COLUMNS } from \"../filesDb\";\nimport { pgErrorToError } from \"@ai-matrx/data\";\n\nimport * as Files from \"../api/files\";\nimport {\n claimUpload,\n uploadDedupKey,\n type UploadClaim,\n} from \"../upload/uploadDedupGuard\";\nimport * as Folders from \"../api/folders\";\nimport * as Permissions from \"../api/permissions\";\nimport * as Versions from \"../api/versions\";\nimport {\n createShareLink as createCanonicalShareLink,\n listShareLinks as listCanonicalShareLinks,\n revokeShareLink as revokeCanonicalShareLink,\n} from \"../host/share-links\";\nimport {\n restoreFileDirect,\n restoreFolderDirect,\n softDeleteFileDirect,\n softDeleteFolderDirect,\n} from \"../api/direct\";\nimport { fileHandler } from \"../handler/handler\";\nimport { newRequestId } from \"../host/python-client\";\nimport { extractErrorMessage } from \"@ai-matrx/data/net\";\nimport {\n apiFileRecordToCloudFile,\n dbRowToCloudFilePermission,\n dbRowToCloudFileVersion,\n dbRowToCloudFolder,\n canonicalShareLinkToCloudShareLink,\n parseCloudTreeRows,\n rpcPermissionRowToCloudFilePermission,\n} from \"./converters\";\nimport {\n areCloudFileFieldsLoaded,\n FILE_DB_RECORD_FIELDS,\n fileHintToCloudFilePartial,\n needsOnlyRenderFields,\n type CloudFileHydrationField,\n} from \"./file-hydration\";\nimport {\n registerRequest,\n releaseRequest,\n beginResourceOp,\n isLatestResourceOp,\n hasInFlight,\n} from \"./request-ledger\";\nimport { toast } from \"../host/toast\";\nimport { buildTreeState } from \"./tree-utils\";\nimport {\n isUserVisibleFilePath,\n isUserVisibleFolderPath,\n} from \"../utils/user-visible\";\nimport {\n hasMatchingFileTreeSession,\n runFileTreeSessionOperation,\n} from \"./file-tree-auth-boundary\";\nimport {\n isFileTreeLoadDeferred,\n runWithFileTreeLoadTimeout,\n} from \"./file-tree-timeout\";\nimport {\n invalidate as invalidateBlobCache,\n replaceWithSavedBytes,\n} from \"../hooks/blob-cache\";\nimport { invalidateOfficeExtraction } from \"../hooks/office-extraction-cache\";\nimport {\n addFilePendingRequest,\n attachChildToFolder,\n clearUpload,\n detachChildFromFolder,\n markFileSaved,\n markFolderFullyLoaded,\n removeFile,\n removeFilePendingRequest,\n removeFolder,\n removePermissionForResource,\n removeShareLink,\n replaceTree,\n rollbackFileOptimisticUpdate,\n setFileError,\n setFileField,\n setFileLoading,\n setTreeStatus,\n trackUploadStart,\n updateUploadProgress,\n updateUploadStatus,\n upsertFile,\n upsertFiles,\n upsertFolder,\n upsertFolders,\n upsertPermissionsForResource,\n upsertShareLinksForResource,\n upsertVersionsForFile,\n} from \"./slice\";\nimport { getFileFromState } from \"./selectors\";\n\nimport type {\n BulkDeleteFilesArg,\n BulkMoveFilesArg,\n BulkMoveFoldersArg,\n BulkResponse,\n CloudFile,\n CloudFileFieldSnapshot,\n CloudFileRecord,\n CloudFolderRecord,\n CloudFilePermission,\n CloudFileVersion,\n CloudFolder,\n CloudShareLink,\n CreateShareLinkArg,\n RevokeShareLinkArg,\n DeleteFileArg,\n GranteeType,\n GrantPermissionArg,\n MoveFileArg,\n RenameFileArg,\n ResourceType,\n RestoreVersionArg,\n SaveFileNewVersionArg,\n SaveFileNewVersionResult,\n RevokePermissionArg,\n UpdateFileMetadataArg,\n UploadFilesArg,\n FileIdentityHint,\n} from \"../types\";\n\nimport { getClaimsUser } from \"../host/supabase\";\ntype ThunkApi = { dispatch: AppDispatch; state: StateWithCloudFiles };\n\n// ---------------------------------------------------------------------------\n// Reads\n// ---------------------------------------------------------------------------\n\nexport interface EnsureCloudFileFieldsArg {\n fileId: string;\n fields?: readonly CloudFileHydrationField[];\n hint?: FileIdentityHint;\n}\n\nexport type EnsureCloudFileFieldsOutcome = \"ready\" | \"missing\";\n\nconst fileFieldHydrationInFlight = new Map<\n string,\n Promise<(Partial<CloudFile> & Pick<CloudFile, \"id\">) | null>\n>();\n\nasync function fetchCloudFileFields(\n fileId: string,\n fields: readonly CloudFileHydrationField[],\n): Promise<(Partial<CloudFile> & Pick<CloudFile, \"id\">) | null> {\n const key = `${fileId}:${[...fields].sort().join(\",\")}`;\n const existing = fileFieldHydrationInFlight.get(key);\n if (existing) return existing;\n\n const request = (async () => {\n // A direct PostgREST exact-id read still compiles the entire files.files\n // RLS access expression. On a PDF surface many file chips hydrate at\n // once, so planning alone can cross its statement deadline before the\n // primary-key lookup begins. The server's `/files/{id}` boundary checks\n // the same canonical access rule once and reads by primary key without\n // browser RLS planning. Keep its URL/thumbnail work off this metadata\n // path; consumers that need it use the regular getFile endpoint.\n const { data } = await Files.getFileMetadata(fileId);\n const record = apiFileRecordToCloudFile(data);\n if (record.deletedAt) return null;\n\n if (needsOnlyRenderFields(fields)) {\n return {\n id: record.id,\n fileName: record.fileName,\n mimeType: record.mimeType,\n fileSize: record.fileSize,\n visibility: record.visibility,\n };\n }\n return record;\n })().finally(() => fileFieldHydrationInFlight.delete(key));\n\n fileFieldHydrationInFlight.set(key, request);\n return request;\n}\n\n/**\n * Ensure a durable file id has the requested canonical fields in Redux.\n * Known hints merge first, so the network reads only what is actually absent.\n */\nexport const ensureCloudFileFields = createAsyncThunk<\n EnsureCloudFileFieldsOutcome,\n EnsureCloudFileFieldsArg,\n ThunkApi\n>(\n \"cloudFiles/ensureCloudFileFields\",\n async (\n { fileId, fields = FILE_DB_RECORD_FIELDS, hint },\n { dispatch, getState },\n ) => {\n dispatch(upsertFile(fileHintToCloudFilePartial(fileId, hint)));\n\n if (\n areCloudFileFieldsLoaded(getFileFromState(getState(), fileId), fields)\n ) {\n return \"ready\";\n }\n\n dispatch(setFileLoading({ id: fileId, loading: true }));\n dispatch(setFileError({ id: fileId, error: null }));\n try {\n const partial = await fetchCloudFileFields(fileId, fields);\n if (!partial) return \"missing\";\n dispatch(upsertFile(partial));\n return \"ready\";\n } catch (error) {\n const normalized = pgErrorToError(error);\n dispatch(setFileError({ id: fileId, error: normalized.message }));\n throw normalized;\n } finally {\n dispatch(setFileLoading({ id: fileId, loading: false }));\n }\n },\n);\n\n/**\n * Loads the full tree for the current user via the get_user_file_tree\n * RPC. Normalizes into filesById / foldersById / tree.\n */\n// `get_user_file_tree` server-side cap (`p_limit := LEAST(GREATEST(p_limit, 1), 5000);`).\n// Passing anything > 5000 silently clamps. The FE pages via `p_offset`\n// to recover the full tree for power users (users with >5k files).\nconst TREE_PAGE_SIZE = 5000;\n\n// Sanity cap on the pagination loop — prevents an infinite loop if the\n// RPC ever returns a full page on the page that should be the last.\n// 20 * 5000 = 100,000 rows, well above any realistic user.\nconst TREE_MAX_PAGES = 20;\n\n/**\n * Shared promise for an in-progress full-tree load, so concurrent mounts\n * collapse onto one `get_user_file_tree` RPC. Session-local, not Redux state.\n */\nlet _treeInFlight: Promise<void> | null = null;\nlet _treeInFlightUserId: string | null = null;\n\nexport const loadUserFileTree = createAsyncThunk<\n void,\n { userId: string },\n ThunkApi\n>(\"cloudFiles/loadUserFileTree\", async ({ userId }, { dispatch, getState }) => {\n // Redux is server-seeded before client effects run, so during signed-out\n // hydration or session teardown it can briefly retain a user UUID while\n // supabase-js is already anonymous. Isolate that race at this canonical\n // producer boundary instead of sending an authenticated-only RPC as anon.\n if (!(await hasMatchingFileTreeSession(userId))) return;\n\n // In-flight dedupe. Several surfaces hydrate the tree on mount\n // (CloudFilesRealtimeProvider + every useCloudTree consumer), and their\n // effects all run in the same commit, so a status check in the component\n // can't see a sibling's fetch. Sharing the promise collapses them to one\n // RPC while keeping `.unwrap()` working for every caller (unlike a\n // `condition` guard, which rejects the losers).\n if (_treeInFlight && _treeInFlightUserId === userId) return _treeInFlight;\n\n const run = (async () => {\n dispatch(setTreeStatus({ status: \"loading\" }));\n\n // RPC contract (migration 014, 2026-05-17): identity-locked to\n // `auth.uid()`, returns owner OR explicit-grant rows only (no public\n // leak), excludes `parent_file_id IS NOT NULL` + `system-files/%`\n // paths. So we can consume the response raw — no FE-side ownership\n // or system-path filtering needed. See common-docs systems/files/file-service/WIRE_CONTRACT.md\n // (2026-05-17 \"Phase 1d.5\" entry).\n //\n // Pagination: server caps p_limit at 5000. We loop on `p_offset`\n // until a partial page comes back. Sequential pages — Postgres\n // handles them fast and parallelism would just contend for the\n // same connection.\n try {\n // Bounded per attempt; a timeout in a hidden tab waits for the person to\n // return and retries instead of failing (see file-tree-timeout.ts).\n const rows = await runWithFileTreeLoadTimeout(async (signal) => {\n const rows: ReturnType<typeof parseCloudTreeRows> = [];\n for (let page = 0; page < TREE_MAX_PAGES; page += 1) {\n const { data, error } = await runFileTreeSessionOperation(() =>\n supabase\n .rpc(\"get_user_file_tree\", {\n p_user_id: userId,\n p_limit: TREE_PAGE_SIZE,\n p_offset: page * TREE_PAGE_SIZE,\n p_include_folders: true,\n p_include_deleted: false,\n })\n .abortSignal(signal),\n );\n\n if (error) {\n // An aborted page is the timeout's call, not a server failure:\n // the runner decides (retry on return, or TimeoutError).\n if (!signal.aborted) {\n dispatch(\n setTreeStatus({ status: \"error\", error: error.message }),\n );\n }\n throw error;\n }\n\n const pageRows = parseCloudTreeRows(data);\n rows.push(...pageRows);\n if (pageRows.length < TREE_PAGE_SIZE) break;\n }\n return rows;\n });\n\n // Every field pushed below is always set from the non-optional\n // `CloudTreeRow` columns — narrowed here so the tree-spine reconstruction\n // (below) doesn't need a non-null assertion on `f.id` or a nullish-default\n // fallback on `f.ownerId`, which would otherwise silently paper over a\n // real parse gap.\n type PartialCloudFileWithId = Partial<CloudFile> &\n Pick<\n CloudFile,\n | \"id\"\n | \"ownerId\"\n | \"filePath\"\n | \"fileName\"\n | \"visibility\"\n | \"currentVersion\"\n | \"createdAt\"\n | \"updatedAt\"\n | \"deletedAt\"\n >;\n type PartialCloudFolderWithId = Partial<CloudFolder> &\n Pick<\n CloudFolder,\n | \"id\"\n | \"ownerId\"\n | \"folderPath\"\n | \"folderName\"\n | \"visibility\"\n | \"createdAt\"\n | \"updatedAt\"\n | \"deletedAt\"\n >;\n const files: PartialCloudFileWithId[] = [];\n const folders: PartialCloudFolderWithId[] = [];\n for (const row of rows) {\n if (row.kind === \"file\") {\n // No client-side visibility filter: `get_user_file_tree` already\n // applied `files.is_user_visible_path`. A second rule here is the\n // D16 disagreement this cutover deleted.\n files.push({\n id: row.id,\n ownerId: row.owner_id,\n filePath: row.file_path,\n fileName: row.file_name,\n parentFolderId: row.parent_folder_id,\n mimeType: row.mime_type,\n // Phase 0 rename: `file_size` → `size_bytes`. The\n // `CloudTreeFileRow` shape and Supabase row both carry the new\n // name now. See docs/PYTHON_UPDATES.md §3.\n fileSize: row.size_bytes,\n visibility: row.visibility,\n currentVersion: row.current_version,\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n deletedAt: row.deleted_at,\n originDeviceId: row.origin_device_id,\n metadata: row.metadata,\n });\n } else {\n folders.push({\n id: row.id,\n ownerId: row.owner_id,\n folderPath: row.folder_path,\n folderName: row.folder_name,\n parentId: row.parent_id,\n visibility: row.visibility,\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n deletedAt: row.deleted_at,\n metadata: row.metadata,\n });\n }\n }\n\n dispatch(upsertFiles(files));\n dispatch(upsertFolders(folders));\n\n // Drop rows the DATABASE's predicate rejects that slipped into the\n // store through realtime before the mirror was applied. FastFire rows\n // are user-visible now and are deliberately NOT purged.\n const prior = getState().cloudFiles;\n for (const [id, rec] of Object.entries(prior.filesById)) {\n if (rec && !isUserVisibleFilePath(rec.filePath)) {\n dispatch(removeFile({ id }));\n }\n }\n for (const [id, rec] of Object.entries(prior.foldersById)) {\n if (\n rec &&\n rec.source.kind === \"real\" &&\n !isUserVisibleFolderPath(rec.folderPath)\n ) {\n dispatch(removeFolder({ id }));\n }\n }\n\n // Build tree spine directly from the just-parsed rows.\n // the normalized slice back out, and avoids any race with batched dispatch.\n const fileIds = files.map((f) => f.id).filter(Boolean);\n const folderIds = folders.map((f) => f.id).filter(Boolean);\n const tree = buildTreeState({\n fileIds,\n folderIds,\n filesById: Object.fromEntries(\n files.map((f) => [\n f.id,\n {\n id: f.id,\n ownerId: f.ownerId,\n filePath: f.filePath,\n fileName: f.fileName,\n mimeType: f.mimeType ?? null,\n fileSize: f.fileSize ?? null,\n checksum: null,\n visibility: f.visibility,\n currentVersion: f.currentVersion,\n parentFolderId: f.parentFolderId ?? null,\n metadata: f.metadata ?? {},\n createdAt: f.createdAt,\n updatedAt: f.updatedAt,\n deletedAt: f.deletedAt,\n // Tree-spine reconstruction is internal — the computed URL fields\n // and the Phase-1b backend thumbnail_url are only populated when\n // records arrive via the REST API. Default all to null here; the\n // file grid (`MediaThumbnail`) falls through to `useFileAsset` →\n // `Asset.variants[\"thumbnail_url\"]` when these are absent.\n publicUrl: null,\n url: null,\n cdnUrl: null,\n downloadUrl: null,\n thumbnailUrl: null,\n source: { kind: \"real\" },\n _dirty: false,\n _dirtyFields: {},\n _fieldHistory: {},\n _loadedFields: {},\n _loading: false,\n _error: null,\n _pendingRequestIds: [],\n },\n ]),\n ),\n foldersById: Object.fromEntries(\n folders.map((f) => [\n f.id,\n {\n id: f.id,\n ownerId: f.ownerId,\n folderPath: f.folderPath,\n folderName: f.folderName,\n parentId: f.parentId ?? null,\n visibility: f.visibility,\n metadata: f.metadata ?? {},\n createdAt: f.createdAt,\n updatedAt: f.updatedAt,\n deletedAt: f.deletedAt,\n // Tree-spine reconstruction is internal — public_url is only\n // populated when records arrive via the API. Default to null\n // here; surfaces that need a CDN URL fetch via useFileSrc.\n publicUrl: null,\n source: { kind: \"real\" },\n _dirty: false,\n _dirtyFields: {},\n _fieldHistory: {},\n _loadedFields: {},\n _loading: false,\n _error: null,\n _pendingRequestIds: [],\n },\n ]),\n ),\n });\n dispatch(\n replaceTree({\n rootFolderIds: tree.rootFolderIds,\n rootFileIds: tree.rootFileIds,\n childrenByFolderId: tree.childrenByFolderId,\n // This RPC returns the complete discoverable tree, not one lazy level.\n // Mark every real folder so navigation never re-downloads up to 100k\n // rows merely because the user opened a different folder.\n fullyLoadedFolderIds: Object.fromEntries(\n folderIds.map((id) => [id, true] as const),\n ),\n }),\n );\n } catch (error) {\n // A load deferred by a hidden tab is not a failure: back to idle, and\n // the next consumer reloads. A visible timeout arrives as TimeoutError.\n if (isFileTreeLoadDeferred(error)) {\n dispatch(setTreeStatus({ status: \"idle\" }));\n throw error;\n }\n const message =\n error instanceof Error\n ? error.message\n : \"Could not load your file library.\";\n dispatch(setTreeStatus({ status: \"error\", error: message }));\n throw error;\n }\n })();\n\n _treeInFlight = run;\n _treeInFlightUserId = userId;\n try {\n await run;\n } finally {\n if (_treeInFlight === run) {\n _treeInFlight = null;\n _treeInFlightUserId = null;\n }\n }\n});\n\n/**\n * Re-run the tree load. Fired by the realtime middleware when the subscription\n * reconnects after an outage — guarantees we don't miss events.\n */\nexport const reconcileTree = createAsyncThunk<\n void,\n { userId: string },\n ThunkApi\n>(\"cloudFiles/reconcileTree\", async ({ userId }, { dispatch }) => {\n await dispatch(loadUserFileTree({ userId })).unwrap();\n});\n\nexport const loadFolderContents = createAsyncThunk<\n void,\n { folderId: string },\n ThunkApi\n>(\"cloudFiles/loadFolderContents\", async ({ folderId }, { dispatch }) => {\n // Virtual folders use their adapter's `list()` via `loadVirtualChildren`\n // — `cld_*` tables don't know about them and would 22P02 on the synthetic id.\n if (folderId.startsWith(\"vfs:\")) return;\n // RLS intentionally permits contextual reads for files conveyed through a\n // shared conversation, so a raw table enumeration here leaks those rows into\n // the global Files UI. Refresh through the discoverability-gated tree RPC —\n // one canonical list contract for roots and nested folders alike.\n const { data, error } = await getClaimsUser(supabase);\n if (error) throw error;\n if (!data.user?.id)\n throw new Error(\"Cannot load folder contents without a user\");\n await dispatch(loadUserFileTree({ userId: data.user.id })).unwrap();\n dispatch(markFolderFullyLoaded({ folderId }));\n});\n\n// Virtual ids look like `vfs:<adapter>:<vid>` and can't be queried against\n// the real `cld_*` tables — Postgres rejects them with `22P02 invalid input\n// syntax for type uuid`. The version / permission / share-link surfaces\n// aren't mapped to virtual sources in v1, so these thunks short-circuit\n// for synthetic ids. Consumers (FileVersionsList, useSharing) also render\n// a \"not supported here\" empty state so the UX is clear, not silent.\nfunction isVirtualResourceId(id: string): boolean {\n return id.startsWith(\"vfs:\");\n}\n\nexport const loadFileVersions = createAsyncThunk<\n void,\n { fileId: string },\n ThunkApi\n>(\"cloudFiles/loadFileVersions\", async ({ fileId }, { dispatch }) => {\n if (isVirtualResourceId(fileId)) return;\n const { data, error } = await filesDb(supabase)\n .from(\"file_versions\")\n .select(FILE_VERSIONS_TABLE_COLUMNS)\n .eq(\"file_id\", fileId)\n .order(\"version_number\", { ascending: false });\n if (error) throw pgErrorToError(error);\n const versions: CloudFileVersion[] = (data ?? []).map(\n dbRowToCloudFileVersion,\n );\n dispatch(upsertVersionsForFile({ fileId, versions }));\n});\n\nexport const loadPermissions = createAsyncThunk<\n void,\n { resourceId: string },\n ThunkApi\n>(\"cloudFiles/loadPermissions\", async ({ resourceId }, { dispatch }) => {\n if (isVirtualResourceId(resourceId)) return;\n // Canonical grant store is `public.permissions` (resource_type='file'),\n // NOT the legacy cld_ file-permission duplicate. RLS returns the rows the\n // caller is allowed to see (own grants / grants to them / org / public).\n const { data, error } = await supabase\n .schema(\"iam\")\n .from(\"permissions\")\n .select(\"*\")\n .eq(\"resource_type\", \"file\")\n .eq(\"resource_id\", resourceId);\n if (error) throw pgErrorToError(error);\n const permissions: CloudFilePermission[] = (data ?? []).map(\n dbRowToCloudFilePermission,\n );\n dispatch(upsertPermissionsForResource({ resourceId, permissions }));\n});\n\nexport const loadShareLinks = createAsyncThunk<\n void,\n { resourceId: string; resourceType?: ResourceType },\n ThunkApi\n>(\n \"cloudFiles/loadShareLinks\",\n async ({ resourceId, resourceType = \"file\" }, { dispatch }) => {\n if (isVirtualResourceId(resourceId)) return;\n // Canonical owner-gated list over platform.share_links.\n const links = await listCanonicalShareLinks(resourceType, resourceId);\n const shareLinks: CloudShareLink[] = links\n .filter((l) => l.isActive)\n .map((l) =>\n canonicalShareLinkToCloudShareLink(l, resourceType, resourceId),\n );\n dispatch(upsertShareLinksForResource({ resourceId, shareLinks }));\n },\n);\n\n// ---------------------------------------------------------------------------\n// Writes — folders\n// ---------------------------------------------------------------------------\n//\n// Folder-CRUD endpoints (`POST /folders`, `PATCH /folders/{id}`,\n// `DELETE /folders/{id}`) shipped 2026-04-26. These thunks now hit\n// the REST surface; legacy direct supabase-js writes were retired.\n\n/**\n * Create a folder via the Python REST contract. We hit `POST /folders` with\n * the explicit `{ folder_name, parent_id }` form because callers already\n * resolve the parent id from the tree. The path-style form (`folder_path`)\n * is preferred for upload-time auto-create, which `uploadFiles` handles.\n *\n * Architecturally important: this thunk no longer touches `cld_folders` from\n * the browser. Folder writes were the single biggest source of RLS recursion\n * regressions; routing them through the backend (which uses SECURITY DEFINER\n * helpers) makes the path uniform with file uploads.\n */\nexport const createFolder = createAsyncThunk<\n string,\n import(\"../types\").CreateFolderArg,\n ThunkApi\n>(\"cloudFiles/createFolder\", async (arg, { dispatch, getState }) => {\n const folderName = arg.folderName.trim();\n if (!folderName) {\n throw new Error(\"Folder name cannot be empty.\");\n }\n if (/[/\\\\]/.test(folderName)) {\n throw new Error(\"Folder names cannot contain '/' or '\\\\'.\");\n }\n // Reject NUL + ASCII control characters. These can be paste-injected\n // and confuse downstream object stores or any layer that materialises\n // the path on disk. Mirrors the rule in `validateRenameInput`.\n if (/[\\x00-\\x1f\\x7f]/.test(folderName)) {\n throw new Error(\"Folder names cannot contain control characters.\");\n }\n if (folderName.endsWith(\".\")) {\n throw new Error(\"Folder names cannot end with '.'.\");\n }\n // Sibling collision check — case-insensitive. Without this the thunk\n // happily issues `POST /folders` with a duplicate name and the backend\n // either returns a confusing UNIQUE-constraint error or (worse) silently\n // creates a sibling that's indistinguishable from the existing one.\n {\n const state = getState().cloudFiles;\n const lowered = folderName.toLowerCase();\n const collision = Object.values(state.foldersById).some(\n (f) =>\n !!f &&\n !f.deletedAt &&\n (f.parentId ?? null) === (arg.parentId ?? null) &&\n f.folderName.toLowerCase() === lowered,\n );\n if (collision) {\n throw new Error(\n `A folder named '${folderName}' already exists in this location.`,\n );\n }\n }\n\n const requestId = newRequestId();\n registerRequest({\n requestId,\n kind: \"folder-create\",\n resourceId: null,\n resourceType: \"folder\",\n });\n\n // Always use path-style: the backend rejects `{folder_name, parent_id}`\n // with `validation_error` (both at root with parent_id=null AND nested\n // with a parent uuid). Path-style creation is idempotent and creates\n // any missing intermediate segments. For nested creation we resolve the\n // parent's path from local state and append the new segment.\n let folderPath = folderName;\n if (arg.parentId !== null) {\n const parent = getState().cloudFiles.foldersById[arg.parentId];\n if (!parent) {\n // The parent isn't in this view's slice — a stale tree, not a missing\n // folder. Say what the user can do; the id stays out of the sentence\n // and in the rejected-thunk capture.\n throw new Error(\n \"We couldn't create the folder here — this view is out of date. Refresh and try again.\",\n );\n }\n folderPath = `${parent.folderPath}/${folderName}`;\n }\n const body = {\n folder_path: folderPath,\n visibility: arg.visibility ?? \"personal\",\n metadata: arg.metadata ?? null,\n };\n\n try {\n const { data: row } = await Folders.createFolder(body, { requestId });\n\n const folder = dbRowToCloudFolder(row);\n dispatch(upsertFolder(folder));\n dispatch(\n attachChildToFolder({\n parentFolderId: folder.parentId,\n kind: \"folder\",\n id: folder.id,\n }),\n );\n return folder.id;\n } catch (err) {\n if (process.env.NODE_ENV !== \"production\") {\n const e = err as {\n status?: number | null;\n code?: string;\n detail?: string;\n userMessage?: string;\n details?: unknown;\n };\n // Surface the full backend response so we can fix the contract drift\n // rather than show a generic \"request failed\" toast.\n console.error(\"[createFolder] backend error\", {\n status: e.status ?? null,\n code: e.code,\n detail: e.detail,\n userMessage: e.userMessage,\n details: e.details,\n sentBody: body,\n });\n }\n throw err;\n } finally {\n releaseRequest(requestId);\n }\n});\n\n/**\n * Update folder properties — rename, move (`parentId`), change visibility,\n * patch metadata. Optimistic: applies the patch locally before the REST call\n * and rolls back on failure.\n */\nexport const updateFolder = createAsyncThunk<\n void,\n import(\"../types\").UpdateFolderArg,\n ThunkApi\n>(\"cloudFiles/updateFolder\", async (arg, { dispatch, getState }) => {\n const state = getState();\n const folder = state.cloudFiles.foldersById[arg.folderId];\n if (!folder)\n throw new Error(\n \"We couldn't update that folder — this view is out of date. Refresh and try again.\",\n );\n\n const requestId = newRequestId();\n registerRequest({\n requestId,\n kind: \"folder-update\",\n resourceId: arg.folderId,\n resourceType: \"folder\",\n });\n\n // Optimistic: apply the patch locally.\n const patch = arg.patch;\n const optimistic: CloudFolder = {\n ...folder,\n ...(patch.folderName !== undefined ? { folderName: patch.folderName } : {}),\n ...(patch.parentId !== undefined ? { parentId: patch.parentId } : {}),\n ...(patch.visibility !== undefined ? { visibility: patch.visibility } : {}),\n ...(patch.metadata !== undefined ? { metadata: patch.metadata } : {}),\n };\n dispatch(upsertFolder(optimistic));\n\n // Track move in tree state — detach from old parent, attach under new.\n if (patch.parentId !== undefined && patch.parentId !== folder.parentId) {\n dispatch(\n detachChildFromFolder({\n parentFolderId: folder.parentId,\n kind: \"folder\",\n id: folder.id,\n }),\n );\n dispatch(\n attachChildToFolder({\n parentFolderId: patch.parentId,\n kind: \"folder\",\n id: folder.id,\n }),\n );\n }\n\n // The backend PATCH /folders honors folder_path / visibility / metadata ONLY;\n // it silently ignores folder_name / parent_id (same as POST /folders, which\n // rejects them). Rename AND move must therefore be expressed as the TARGET\n // folder_path — the server renames, reparents, and cascades descendants from\n // it. Sending folder_name/parent_id no-op'd server-side (rename/move reverted\n // on refresh). Compute the target path from the new name + new parent's path.\n let targetFolderPath: string | undefined;\n if (patch.folderName !== undefined || patch.parentId !== undefined) {\n const newName = patch.folderName ?? folder.folderName;\n const newParentId =\n patch.parentId !== undefined ? patch.parentId : folder.parentId;\n if (newParentId) {\n const newParent = state.cloudFiles.foldersById[newParentId];\n if (!newParent) {\n throw new Error(\n \"We couldn't move the folder there — this view is out of date. Refresh and try again.\",\n );\n }\n targetFolderPath = `${newParent.folderPath}/${newName}`;\n } else {\n targetFolderPath = newName;\n }\n }\n\n try {\n const { data: row } = await Folders.patchFolder(\n arg.folderId,\n {\n folder_path: targetFolderPath,\n visibility: patch.visibility,\n metadata: patch.metadata ?? null,\n },\n { requestId },\n );\n dispatch(upsertFolder(dbRowToCloudFolder(row)));\n } catch (err) {\n // Roll back to the pre-edit folder state and tree links (strip frozen\n // runtime fields — re-upserting a store record crashes the merge).\n dispatch(upsertFolder(toCloudFolderPartial(folder)));\n if (patch.parentId !== undefined && patch.parentId !== folder.parentId) {\n dispatch(\n detachChildFromFolder({\n parentFolderId: patch.parentId,\n kind: \"folder\",\n id: folder.id,\n }),\n );\n dispatch(\n attachChildToFolder({\n parentFolderId: folder.parentId,\n kind: \"folder\",\n id: folder.id,\n }),\n );\n }\n throw err;\n } finally {\n releaseRequest(requestId);\n }\n});\n\nexport const deleteFolder = createAsyncThunk<\n void,\n import(\"../types\").DeleteFolderArg,\n ThunkApi\n>(\"cloudFiles/deleteFolder\", async (arg, { dispatch, getState }) => {\n const state = getState();\n const folder = state.cloudFiles.foldersById[arg.folderId];\n if (!folder) return;\n\n const requestId = newRequestId();\n registerRequest({\n requestId,\n kind: \"folder-delete\",\n resourceId: arg.folderId,\n resourceType: \"folder\",\n });\n\n try {\n // Soft delete (Trash) cascades the subtree in SQL (soft_delete_folder) →\n // direct. There is no hard delete.\n await softDeleteFolderDirect(arg.folderId);\n dispatch(removeFolder({ id: arg.folderId }));\n } finally {\n releaseRequest(requestId);\n }\n});\n\n/**\n * Ensure every segment of `folderPath` exists; create any that don't.\n * Returns the leaf folder's id. Used for convention-based uploads like\n * \"save all pasted images under /Images\".\n */\nexport const ensureFolderPath = createAsyncThunk<\n string,\n import(\"../types\").EnsureFolderPathArg,\n ThunkApi\n>(\"cloudFiles/ensureFolderPath\", async (arg, { dispatch, getState }) => {\n const normalizedPath = arg.folderPath\n .split(\"/\")\n .map((s) => s.trim())\n .filter(Boolean)\n .join(\"/\");\n if (!normalizedPath) {\n throw new Error(\"folderPath cannot be empty.\");\n }\n\n // Realtime/tree state is the zero-request fast path.\n const existing = Object.values(getState().cloudFiles.foldersById).find(\n (folder) => folder.folderPath === normalizedPath && !folder.deletedAt,\n );\n if (existing) {\n return existing.id;\n }\n\n // The backend owns path traversal and race handling. One atomic request\n // replaces the obsolete per-segment files.folders PostgREST reads.\n const requestId = newRequestId();\n registerRequest({\n requestId,\n kind: \"folder-create\",\n resourceId: null,\n resourceType: \"folder\",\n });\n try {\n const { data: row } = await Folders.ensureFolderPath(\n normalizedPath,\n arg.visibility ?? \"personal\",\n { requestId },\n );\n const folder = dbRowToCloudFolder(row);\n dispatch(upsertFolder(folder));\n dispatch(\n attachChildToFolder({\n parentFolderId: folder.parentId,\n kind: \"folder\",\n id: folder.id,\n }),\n );\n return folder.id;\n } finally {\n releaseRequest(requestId);\n }\n});\n\n// ---------------------------------------------------------------------------\n// Writes — uploads (multi-file with progress)\n// ---------------------------------------------------------------------------\n\n/**\n * Say, on the screen the person is actually looking at, that an upload failed —\n * in the SERVER'S words.\n *\n * 🚨 THE 2026-09-19 SILENT FAILURE (`common-docs/systems/knowledge/ingestion/projects/acquisition-frontier/\n * own-files/VERIFICATION.md` §15/§16). A 21-file drop on a fresh Rulebook fired\n * 21 uploads and every one came back `400 matrx-files: this write carries no\n * organization`. Every sentence was captured correctly — into\n * `failed[].error` and into the upload slice — and then shown NOWHERE, because:\n *\n * - `mutation-toast-middleware` deliberately skips `uploadFiles`, on the\n * reasoning that \"uploads surface per-file errors inline in the\n * UploadProgressList\". That is true on the Files surface and FALSE\n * everywhere else — the Rulebook sources panel never renders that list.\n * - the capture toolbar that started the drop collapses N failures into\n * \"Failed to upload N files\", dropping every reason.\n *\n * So the person saw a card that still read \"Nothing attached yet\" and no reason\n * anywhere. An upload that failed must never be indistinguishable from an\n * upload nobody started.\n *\n * This announces from the THUNK, which every caller shares, so no surface can\n * forget. Reasons are deduped (21 identical 400s are one sentence, not 21\n * toasts), and the toast carries a stable id so the surrounding UploadProgressList\n * or capture toolbar cannot stack a second copy of the same news.\n */\nexport function announceUploadFailures(\n failed: ReadonlyArray<{ name: string; error: string }>,\n): void {\n if (failed.length === 0) return;\n const reasons = Array.from(\n new Set(failed.map((f) => f.error).filter((e) => e && e.trim().length > 0)),\n );\n const title =\n failed.length === 1\n ? `Couldn't upload ${failed[0].name}`\n : `Couldn't upload ${failed.length} files`;\n toast.error(title, {\n // The server's own sentence, never a sentence of ours. When several\n // distinct things went wrong, each one gets said.\n description:\n reasons.length > 0\n ? reasons.join(\" · \")\n : \"The server refused the upload and gave no reason.\",\n id: `upload-failed:${failed.map((f) => f.name).join(\"|\")}`,\n duration: 12000,\n });\n}\n\nexport const uploadFiles = createAsyncThunk<\n { uploaded: string[]; failed: Array<{ name: string; error: string }> },\n UploadFilesArg,\n ThunkApi\n>(\"cloudFiles/uploadFiles\", async (arg, { dispatch, getState }) => {\n const concurrency = Math.max(1, arg.concurrency ?? 3);\n const uploaded: string[] = [];\n // Track REAL error per file (not just filename). Without this, every\n // upload failure surfaces to callers as the file's name rather than the\n // backend's actual error code/message — which is what made the Phase\n // 11 migration look \"broken\" when really we just couldn't see why.\n const failed: Array<{ name: string; error: string }> = [];\n\n // Resolve logical path prefix.\n //\n // Order of preference:\n // 1. `folderPath` arg — passed directly (the Python backend\n // auto-creates the hierarchy server-side; the browser never has to\n // query `cld_folders` via supabase-js, which avoids the\n // well-known RLS recursion bug on the legacy file-permission table).\n // 2. `parentFolderId` — look up the folder in slice state (works when\n // the folder is already loaded from the tree RPC or realtime).\n // 3. Empty prefix — file lands at root.\n const state = getState();\n let prefix = \"\";\n if (arg.folderPath) {\n prefix = `${arg.folderPath.replace(/^\\/+|\\/+$/g, \"\")}/`;\n } else if (arg.parentFolderId) {\n const parentFolder = state.cloudFiles.foldersById[arg.parentFolderId];\n if (parentFolder) {\n prefix = `${parentFolder.folderPath}/`;\n }\n }\n\n // ─── Collision handling ───────────────────────────────────────────────\n //\n // Build a case-insensitive set of file names already present in the\n // destination. Used by `uniqueName` below to auto-rename uploads that\n // collide (\"report.pdf\" → \"report (1).pdf\"). Without this, the backend\n // either silently overwrites or version-bumps the existing file —\n // neither matches user expectations from Drive / Dropbox where a\n // duplicate upload becomes \"report (1).pdf\" by default.\n //\n // We only consider files in the SAME parent folder. The lookup is by\n // path-prefix because virtual folders may not have stable ids in\n // local state at upload time (e.g. `folderPath` arg with no\n // pre-loaded folder row).\n const existingNames = (() => {\n const out = new Set<string>();\n const targetPrefix = prefix; // captured outside; matches \"<path>/<name>\"\n for (const f of Object.values(state.cloudFiles.filesById)) {\n if (!f) continue;\n if (f.deletedAt) continue;\n // Match by storage path under the prefix. `f.filePath` is the\n // backend's canonical path including the file name.\n if (!f.filePath) continue;\n const normalized = f.filePath.replace(/^\\/+/, \"\");\n if (!normalized.startsWith(targetPrefix)) continue;\n const tail = normalized.slice(targetPrefix.length);\n // Skip files nested deeper than this folder.\n if (tail.includes(\"/\")) continue;\n if (tail) out.add(tail.toLowerCase());\n }\n return out;\n })();\n\n /**\n * Pick the next non-colliding name, mutating the queue:\n * \"report.pdf\" → \"report.pdf\" (no collision)\n * \"report.pdf\" → \"report (1).pdf\" (one collision)\n * \"report.pdf\" → \"report (2).pdf\" (two collisions)\n * \".env\" → \".env (1)\" (hidden file: suffix only)\n *\n * Reserves the chosen name in `existingNames` so back-to-back uploads\n * of the same source file (drop the same File twice) don't collide\n * with each other.\n */\n function uniqueName(originalName: string): string {\n const lowered = originalName.toLowerCase();\n if (!existingNames.has(lowered)) {\n existingNames.add(lowered);\n return originalName;\n }\n const dot = originalName.lastIndexOf(\".\");\n const isHidden = originalName.startsWith(\".\");\n const stem =\n dot > 0 && !isHidden ? originalName.slice(0, dot) : originalName;\n const ext = dot > 0 && !isHidden ? originalName.slice(dot) : \"\";\n for (let i = 1; i < 1000; i++) {\n const candidate = `${stem} (${i})${ext}`;\n if (!existingNames.has(candidate.toLowerCase())) {\n existingNames.add(candidate.toLowerCase());\n return candidate;\n }\n }\n // 1000 collisions — fall back to original name and let the backend\n // version-bump. Practically never reached.\n return originalName;\n }\n\n // ─── Per-file overrides from the duplicate-upload dialog ────────────\n //\n // When a user picks \"Overwrite\" on a duplicate, the dialog passes the\n // existing file's exact name back as a `filenameOverrides[index]`\n // entry. We honour the override verbatim — the auto \" (1)\" rename\n // would defeat the point. When they pick \"Skip\", the dialog adds the\n // index to `skipIndices` and we drop that file from the queue.\n const overrides = arg.filenameOverrides ?? {};\n const skipSet = new Set(arg.skipIndices ?? []);\n const forceNewCopySet = new Set(arg.forceNewCopyIndices ?? []);\n\n // Reserve override target names in `existingNames` BEFORE the loop\n // runs, so two overrides into the same path can't both win and so\n // a file with no override doesn't accidentally collide-rename onto\n // an override target.\n for (const [, name] of Object.entries(overrides)) {\n if (typeof name === \"string\") existingNames.add(name.toLowerCase());\n }\n\n // Build the queue as [originalIndex, file] pairs so workers can\n // look up overrides by index regardless of dequeue order.\n const queue: Array<{ index: number; file: File }> = arg.files\n .map((file, index) => ({ index, file }))\n .filter(({ index }) => !skipSet.has(index));\n\n async function worker(): Promise<void> {\n while (queue.length) {\n const next = queue.shift();\n if (!next) return;\n const { file, index } = next;\n const overrideName = overrides[index];\n const forcedCopy = forceNewCopySet.has(index);\n\n // Dedup guard (VERIFICATION.md §12, 2026-09-18): a caller-level retry\n // after a transient failure (dev-server connection reset, etc.) can\n // re-dispatch `uploadFiles` for a file whose FIRST attempt already\n // landed — the client has no proof it didn't, and the backend's own\n // `X-Idempotency-Key` only replays a 412, never a success (see\n // `features/files/upload/uploadDedupGuard.ts` for the full trace).\n // Only the plain path participates: an explicit \"Overwrite\" override\n // or \"force new copy\" means the caller WANTS a fresh write.\n const dedupKey =\n !overrideName && !forcedCopy\n ? uploadDedupKey(prefix, file.name, file.size)\n : null;\n let dedupClaim: UploadClaim | null = null;\n if (dedupKey) {\n const claim = claimUpload(dedupKey);\n if (claim.shared) {\n try {\n const outcome = await claim.promise;\n // Reuse the earlier attempt's outcome — no bytes sent again.\n uploaded.push(outcome.fileId);\n continue;\n } catch {\n // The earlier attempt genuinely failed, and the guard already\n // cleared its slot — fall through and upload for real below.\n }\n } else {\n dedupClaim = claim;\n }\n }\n\n const requestId = newRequestId();\n // Override (from duplicate dialog \"Overwrite\") wins. Otherwise\n // the auto \" (1)\" rename runs. We track the display name\n // separately from the original file.name so progress + telemetry\n // continue to use what the user dragged in.\n const targetName =\n typeof overrideName === \"string\" && overrideName\n ? overrideName\n : uniqueName(file.name);\n dispatch(\n trackUploadStart({\n requestId,\n fileName: targetName,\n fileSize: file.size,\n parentFolderId: arg.parentFolderId ?? null,\n folderPath: arg.folderPath ?? null,\n }),\n );\n registerRequest({\n requestId,\n kind: \"upload\",\n resourceId: null,\n resourceType: \"file\",\n });\n try {\n const { data } = await Files.uploadFileWithProgress(\n {\n file,\n filePath: `${prefix}${targetName}`,\n visibility: arg.visibility ?? \"personal\",\n shareWith: arg.shareWith,\n shareLevel: arg.shareLevel,\n changeSummary: arg.changeSummary,\n metadata: arg.metadata,\n options: arg.options,\n ...(forceNewCopySet.has(index)\n ? {\n intent: \"force_new_copy\" as const,\n reason:\n \"User selected Make a copy in the duplicate upload dialog\",\n }\n : {}),\n },\n (ev) =>\n dispatch(\n updateUploadProgress({\n requestId,\n bytesUploaded: ev.loaded,\n }),\n ),\n {\n requestId,\n // Idempotency key — same value across automatic retries of the\n // same intended upload. The backend stores it in\n // `metadata._idempotency_key` so retries don't create duplicate\n // version rows. We reuse `requestId` because it's generated\n // once per intended upload, before any retry.\n idempotencyKey: requestId,\n },\n );\n // Upsert file into slice from response.\n dispatch(\n upsertFile(\n apiFileRecordToCloudFile({\n id: data.file_id,\n owner_id: \"\",\n file_path: data.file_path,\n file_name: data.file_path.split(\"/\").pop() ?? targetName,\n mime_type: file.type || null,\n // Phase 0 rename — see docs/PYTHON_UPDATES.md §3.\n size_bytes: data.size_bytes,\n checksum: data.checksum,\n visibility: arg.visibility ?? \"personal\",\n current_version: data.version_number,\n parent_folder_id: arg.parentFolderId ?? null,\n metadata: arg.metadata ?? {},\n // P1-7: stamp the optimistic row with the current time instead of\n // null. The upload just happened, so \"now\" is accurate for the\n // recents filter / \"sort by modified\" until the realtime echo\n // delivers the authoritative server timestamp. Null sorted the\n // freshly-uploaded file to the wrong end of the list.\n created_at: new Date().toISOString(),\n updated_at: new Date().toISOString(),\n deleted_at: null,\n }),\n ),\n );\n // If this upload replaced an existing file (same logical path,\n // backend bumped current_version), the cached blob for the\n // original version is now stale. Invalidate so the next preview\n // open re-fetches the latest bytes.\n invalidateBlobCache(data.file_id);\n invalidateOfficeExtraction(data.file_id);\n dispatch(\n attachChildToFolder({\n parentFolderId: arg.parentFolderId ?? null,\n kind: \"file\",\n id: data.file_id,\n }),\n );\n dispatch(\n updateUploadStatus({\n requestId,\n status: \"success\",\n fileId: data.file_id,\n }),\n );\n uploaded.push(data.file_id);\n dedupClaim?.settle(\n Promise.resolve({ fileId: data.file_id, filePath: data.file_path }),\n );\n } catch (err) {\n const message = extractErrorMessage(err);\n dispatch(\n updateUploadStatus({\n requestId,\n status: \"error\",\n error: message,\n }),\n );\n failed.push({ name: file.name, error: message });\n dedupClaim?.settle(Promise.reject(err));\n } finally {\n releaseRequest(requestId);\n }\n }\n }\n\n await Promise.all(\n Array.from({ length: Math.min(concurrency, arg.files.length) }, () =>\n worker(),\n ),\n );\n\n announceUploadFailures(failed);\n return { uploaded, failed };\n});\n\nexport const clearUploadEntry = createAsyncThunk<\n void,\n { requestId: string },\n ThunkApi\n>(\"cloudFiles/clearUpload\", async ({ requestId }, { dispatch }) => {\n dispatch(clearUpload({ requestId }));\n});\n\n// ---------------------------------------------------------------------------\n// Writes — optimistic metadata updates\n// ---------------------------------------------------------------------------\n\nexport const renameFile = createAsyncThunk<void, RenameFileArg, ThunkApi>(\n \"cloudFiles/rename\",\n async ({ fileId, newName }, { dispatch, getState }) => {\n const record = getFileFromState(getState(), fileId);\n if (!record)\n throw new Error(\n \"We couldn't find that file in this view. Refresh and try again.\",\n );\n\n const requestId = newRequestId();\n // Per-resource op sequence — guards against an older response clobbering a\n // newer optimistic state (rename A→B→C out of order, or a double-click).\n const seq = beginResourceOp(fileId);\n const snapshot: CloudFileFieldSnapshot = {\n fileName: record.fileName,\n filePath: record.filePath,\n };\n\n // Compute new file path (replace final segment).\n const pathParts = record.filePath.split(\"/\");\n pathParts[pathParts.length - 1] = newName;\n const newPath = pathParts.join(\"/\");\n\n dispatch(setFileField({ id: fileId, field: \"fileName\", value: newName }));\n dispatch(setFileField({ id: fileId, field: \"filePath\", value: newPath }));\n dispatch(addFilePendingRequest({ id: fileId, requestId }));\n registerRequest({\n requestId,\n kind: \"rename\",\n resourceId: fileId,\n resourceType: \"file\",\n });\n\n try {\n // Use the dedicated rename endpoint — it accepts a full new path,\n // auto-creates parent folders if any segment is missing, and lets\n // the backend handle the file_path / storage-location update atomically.\n const { data } = await Files.renameFile(\n fileId,\n { new_path: newPath },\n { requestId },\n );\n // Only apply if no newer op for this file has begun since (out-of-order\n // guard); otherwise the newer op owns the state.\n if (isLatestResourceOp(fileId, seq)) {\n dispatch(upsertFile(apiFileRecordToCloudFile(data)));\n dispatch(markFileSaved({ id: fileId }));\n }\n } catch (err) {\n // Don't roll back if a newer op has superseded this one.\n if (isLatestResourceOp(fileId, seq)) {\n dispatch(rollbackFileOptimisticUpdate({ id: fileId, snapshot }));\n const msg = extractErrorMessage(err);\n dispatch(setFileError({ id: fileId, error: msg }));\n }\n throw err;\n } finally {\n dispatch(removeFilePendingRequest({ id: fileId, requestId }));\n releaseRequest(requestId);\n }\n },\n);\n\nexport const moveFile = createAsyncThunk<void, MoveFileArg, ThunkApi>(\n \"cloudFiles/move\",\n async ({ fileId, newParentFolderId }, { dispatch, getState }) => {\n const record = getFileFromState(getState(), fileId);\n if (!record)\n throw new Error(\n \"We couldn't find that file in this view. Refresh and try again.\",\n );\n const oldParentId = record.parentFolderId;\n const requestId = newRequestId();\n const seq = beginResourceOp(fileId);\n const snapshot: CloudFileFieldSnapshot = {\n parentFolderId: oldParentId,\n };\n\n dispatch(\n setFileField({\n id: fileId,\n field: \"parentFolderId\",\n value: newParentFolderId,\n }),\n );\n dispatch(\n detachChildFromFolder({\n parentFolderId: oldParentId,\n kind: \"file\",\n id: fileId,\n }),\n );\n dispatch(\n attachChildToFolder({\n parentFolderId: newParentFolderId,\n kind: \"file\",\n id: fileId,\n }),\n );\n dispatch(addFilePendingRequest({ id: fileId, requestId }));\n registerRequest({\n requestId,\n kind: \"move\",\n resourceId: fileId,\n resourceType: \"file\",\n });\n\n try {\n // Move = rename to a new full logical path. The backend's rename\n // endpoint auto-creates missing parent folders, so we just compute\n // `<targetFolderPath>/<filename>` (or `<filename>` for root) and\n // hand it over.\n const targetFolder =\n newParentFolderId === null\n ? null\n : (getState().cloudFiles.foldersById[newParentFolderId] ?? null);\n const targetPrefix = targetFolder ? `${targetFolder.folderPath}/` : \"\";\n const newPath = `${targetPrefix}${record.fileName}`;\n\n const { data } = await Files.renameFile(\n fileId,\n { new_path: newPath },\n { requestId },\n );\n if (isLatestResourceOp(fileId, seq)) {\n dispatch(upsertFile(apiFileRecordToCloudFile(data)));\n dispatch(markFileSaved({ id: fileId }));\n }\n } catch (err) {\n if (isLatestResourceOp(fileId, seq)) {\n // Rollback tree membership + field.\n dispatch(\n detachChildFromFolder({\n parentFolderId: newParentFolderId,\n kind: \"file\",\n id: fileId,\n }),\n );\n dispatch(\n attachChildToFolder({\n parentFolderId: oldParentId,\n kind: \"file\",\n id: fileId,\n }),\n );\n dispatch(rollbackFileOptimisticUpdate({ id: fileId, snapshot }));\n const msg = extractErrorMessage(err);\n dispatch(setFileError({ id: fileId, error: msg }));\n }\n throw err;\n } finally {\n dispatch(removeFilePendingRequest({ id: fileId, requestId }));\n releaseRequest(requestId);\n }\n },\n);\n\nexport const updateFileMetadata = createAsyncThunk<\n void,\n UpdateFileMetadataArg,\n ThunkApi\n>(\n \"cloudFiles/updateMetadata\",\n async ({ fileId, patch }, { dispatch, getState }) => {\n const record = getFileFromState(getState(), fileId);\n if (!record)\n throw new Error(\n \"We couldn't find that file in this view. Refresh and try again.\",\n );\n\n const requestId = newRequestId();\n const seq = beginResourceOp(fileId);\n const snapshot: CloudFileFieldSnapshot = {\n visibility: record.visibility,\n metadata: record.metadata,\n };\n\n if (patch.visibility !== undefined) {\n dispatch(\n setFileField({\n id: fileId,\n field: \"visibility\",\n value: patch.visibility,\n }),\n );\n }\n if (patch.metadata !== undefined) {\n dispatch(\n setFileField({\n id: fileId,\n field: \"metadata\",\n value: patch.metadata,\n }),\n );\n }\n dispatch(addFilePendingRequest({ id: fileId, requestId }));\n registerRequest({\n requestId,\n kind: \"update\",\n resourceId: fileId,\n resourceType: \"file\",\n });\n\n try {\n const { data } = await Files.patchFile(\n fileId,\n {\n visibility: patch.visibility,\n metadata: patch.metadata,\n },\n { requestId },\n );\n if (isLatestResourceOp(fileId, seq)) {\n dispatch(upsertFile(apiFileRecordToCloudFile(data)));\n dispatch(markFileSaved({ id: fileId }));\n }\n } catch (err) {\n if (isLatestResourceOp(fileId, seq)) {\n dispatch(rollbackFileOptimisticUpdate({ id: fileId, snapshot }));\n const msg = extractErrorMessage(err);\n dispatch(setFileError({ id: fileId, error: msg }));\n }\n throw err;\n } finally {\n dispatch(removeFilePendingRequest({ id: fileId, requestId }));\n releaseRequest(requestId);\n }\n },\n);\n\n// ---------------------------------------------------------------------------\n// Writes — delete\n// ---------------------------------------------------------------------------\n\nexport const deleteFile = createAsyncThunk<void, DeleteFileArg, ThunkApi>(\n \"cloudFiles/delete\",\n async ({ fileId }, { dispatch, getState }) => {\n const record = getFileFromState(getState(), fileId);\n if (!record) return; // nothing to do\n const parentFolderId = record.parentFolderId;\n const requestId = newRequestId();\n\n // Optimistic remove.\n dispatch(removeFile({ id: fileId }));\n dispatch(\n detachChildFromFolder({\n parentFolderId,\n kind: \"file\",\n id: fileId,\n }),\n );\n // Drop the cached blob bytes — the file is gone, no point holding\n // memory for something the user can't open anymore (and if the\n // delete is rolled back on error, the next open will re-fetch).\n invalidateBlobCache(fileId);\n invalidateOfficeExtraction(fileId);\n registerRequest({\n requestId,\n kind: \"delete\",\n resourceId: fileId,\n resourceType: \"file\",\n });\n\n try {\n // Soft delete (Trash) is a pure metadata write → direct (canonical).\n // There is no hard delete; the file stays restorable from Trash.\n await softDeleteFileDirect(fileId);\n } catch (err) {\n // Rollback — reinsert the record and reattach to its parent.\n dispatch(upsertFile(toCloudFilePartial(record)));\n dispatch(\n attachChildToFolder({\n parentFolderId,\n kind: \"file\",\n id: fileId,\n }),\n );\n throw err;\n } finally {\n releaseRequest(requestId);\n }\n },\n);\n\n// ---------------------------------------------------------------------------\n// Trash — hydrate, restore (delete means archive: no purge from the client)\n// ---------------------------------------------------------------------------\n\n/**\n * Hydrate the trash section. The main tree load runs with\n * `p_include_deleted: false` and every delete/echo REMOVES rows from the\n * store, so soft-deleted records never reach Redux on their own — this thunk\n * pages the same RPC with deleted rows included and upserts ONLY the trashed\n * ones (`deletedAt` set). It never touches the folder tree spine, so trashed\n * rows can't leak into live folder listings; the section filters in\n * PageShell/row-data pick them up by `deletedAt`.\n */\n\n/**\n * A store record carries immer-frozen runtime bookkeeping (`_loadedFields`,\n * `_dirtyFields`, ...). Re-dispatching it into `upsertFile` on the INSERT path\n * would adopt those frozen objects into the new draft and crash the next\n * merge (\"object is not extensible\") — strip to domain fields first. Used by\n * every optimistic-rollback site.\n */\nfunction toCloudFilePartial(record: CloudFileRecord): Partial<CloudFile> {\n const {\n _dirty,\n _dirtyFields,\n _fieldHistory,\n _loadedFields,\n _loading,\n _error,\n _pendingRequestIds,\n ...domain\n } = record;\n return domain;\n}\n\n/** Folder twin of `toCloudFilePartial` — same frozen-runtime-fields hazard. */\nfunction toCloudFolderPartial(record: CloudFolderRecord): Partial<CloudFolder> {\n const {\n _dirty,\n _dirtyFields,\n _fieldHistory,\n _loadedFields,\n _loading,\n _error,\n _pendingRequestIds,\n ...domain\n } = record;\n return domain;\n}\n\nexport const loadTrash = createAsyncThunk<void, { userId: string }, ThunkApi>(\n \"cloudFiles/loadTrash\",\n async ({ userId }, { dispatch }) => {\n const rows: ReturnType<typeof parseCloudTreeRows> = [];\n for (let page = 0; page < TREE_MAX_PAGES; page += 1) {\n const { data, error } = await supabase.rpc(\"get_user_file_tree\", {\n p_user_id: userId,\n p_limit: TREE_PAGE_SIZE,\n p_offset: page * TREE_PAGE_SIZE,\n p_include_folders: true,\n p_include_deleted: true,\n });\n if (error) throw error;\n const pageRows = parseCloudTreeRows(data);\n rows.push(...pageRows);\n if (pageRows.length < TREE_PAGE_SIZE) break;\n }\n\n const files: Partial<CloudFile>[] = [];\n const folders: Partial<CloudFolder>[] = [];\n for (const row of rows) {\n if (!row.deleted_at) continue;\n if (row.kind === \"file\") {\n // Already filtered by the RPC's predicate — see above.\n files.push({\n id: row.id,\n ownerId: row.owner_id,\n filePath: row.file_path,\n fileName: row.file_name,\n parentFolderId: row.parent_folder_id,\n mimeType: row.mime_type,\n fileSize: row.size_bytes,\n visibility: row.visibility,\n currentVersion: row.current_version,\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n deletedAt: row.deleted_at,\n originDeviceId: row.origin_device_id,\n });\n } else {\n folders.push({\n id: row.id,\n ownerId: row.owner_id,\n folderPath: row.folder_path,\n folderName: row.folder_name,\n parentId: row.parent_id,\n visibility: row.visibility,\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n deletedAt: row.deleted_at,\n });\n }\n }\n dispatch(upsertFiles(files));\n dispatch(upsertFolders(folders));\n },\n);\n\n/** Restore a trashed file: flip `deletedAt` off and reattach to its folder. */\nexport const restoreFile = createAsyncThunk<void, { fileId: string }, ThunkApi>(\n \"cloudFiles/restoreFile\",\n async ({ fileId }, { dispatch, getState }) => {\n const record = getFileFromState(getState(), fileId);\n if (!record?.deletedAt) return;\n const requestId = newRequestId();\n\n // Optimistic: back to live, back under its parent.\n dispatch(upsertFile({ ...toCloudFilePartial(record), deletedAt: null }));\n dispatch(\n attachChildToFolder({\n parentFolderId: record.parentFolderId,\n kind: \"file\",\n id: fileId,\n }),\n );\n registerRequest({\n requestId,\n kind: \"file-restore\",\n resourceId: fileId,\n resourceType: \"file\",\n });\n try {\n await restoreFileDirect(fileId);\n } catch (err) {\n dispatch(upsertFile(toCloudFilePartial(record))); // rollback to trashed (clone — state objects are frozen)\n dispatch(\n detachChildFromFolder({\n parentFolderId: record.parentFolderId,\n kind: \"file\",\n id: fileId,\n }),\n );\n throw err;\n } finally {\n releaseRequest(requestId);\n }\n },\n);\n\n/**\n * Restore a trashed folder + its subtree. The RPC un-deletes descendants in\n * SQL; rather than mirroring that cascade optimistically we reconcile by\n * reloading the live tree afterwards (folder restore is rare — correctness\n * over cleverness).\n */\nexport const restoreFolder = createAsyncThunk<\n void,\n { folderId: string },\n ThunkApi\n>(\"cloudFiles/restoreFolder\", async ({ folderId }, { dispatch, getState }) => {\n const folder = getState().cloudFiles.foldersById[folderId];\n if (!folder?.deletedAt) return;\n await restoreFolderDirect(folderId);\n dispatch(upsertFolder({ ...toCloudFolderPartial(folder), deletedAt: null }));\n const userId = folder.ownerId;\n await dispatch(loadUserFileTree({ userId }));\n await dispatch(loadTrash({ userId }));\n});\n\n// ---------------------------------------------------------------------------\n// Writes — versions\n// ---------------------------------------------------------------------------\n\nexport const restoreVersion = createAsyncThunk<\n void,\n RestoreVersionArg,\n ThunkApi\n>(\n \"cloudFiles/restoreVersion\",\n async ({ fileId, versionNumber }, { dispatch }) => {\n if (isVirtualResourceId(fileId)) return;\n dispatch(setFileLoading({ id: fileId, loading: true }));\n const requestId = newRequestId();\n registerRequest({\n requestId,\n kind: \"restore-version\",\n resourceId: fileId,\n resourceType: \"file\",\n });\n try {\n const { data } = await Versions.restoreVersion(fileId, versionNumber, {\n requestId,\n });\n dispatch(upsertFile(apiFileRecordToCloudFile(data)));\n // The current bytes just changed — drop any cached blob so the\n // next preview reads the restored version, not the in-memory\n // copy of the version that was active before restore.\n invalidateBlobCache(fileId);\n invalidateOfficeExtraction(fileId);\n // Reload version list to pick up the new synthetic version row.\n await dispatch(loadFileVersions({ fileId })).unwrap();\n } finally {\n dispatch(setFileLoading({ id: fileId, loading: false }));\n releaseRequest(requestId);\n }\n },\n);\n\n/**\n * THE edit-save path: write new content as the next version of the SAME file.\n *\n * Every editor that saves a file's text goes through here — never `uploadFiles`.\n * `uploadFiles` is the NEW-file path: it auto-renames a colliding name to\n * \"name (1).ext\" (Drive semantics for a dropped duplicate), so an editor that\n * re-uploaded its own file created a second file and left the original at\n * version 1 while the UI said \"Saved\" (2026-09-30).\n *\n * ONE path for everyone: `POST /files/{id}/versions` writes by file ID, so the\n * owner and an editor holding an edit grant on someone else's file save the\n * same way (the server checks the caller's write right, keeps the file's owner\n * and path, and records the caller + `changeSummary` on the version). The\n * answer is still REFUSED if it is not that same row (a different `file_id` or\n * `is_new: true`), so a save can never silently become a copy.\n */\nexport const saveFileNewVersion = createAsyncThunk<\n SaveFileNewVersionResult,\n SaveFileNewVersionArg,\n ThunkApi\n>(\n \"cloudFiles/saveFileNewVersion\",\n async ({ fileId, content, changeSummary }, { dispatch, getState }) => {\n if (isVirtualResourceId(fileId)) {\n throw new Error(\n \"saveFileNewVersion is for stored files; virtual files save through writeAny.\",\n );\n }\n let record: CloudFile | undefined = getState().cloudFiles.filesById[fileId];\n // A row hydrated for rendering only may not carry its name/mime — read the\n // file's own metadata rather than guessing.\n if (!record?.fileName) {\n const { data } = await Files.getFileMetadata(fileId);\n const fresh = apiFileRecordToCloudFile(data);\n dispatch(upsertFile(fresh));\n record = fresh;\n }\n const mimeType = record.mimeType ?? \"text/plain\";\n const body =\n typeof content === \"string\"\n ? new Blob([content], { type: mimeType })\n : content;\n const upload = new File([body], record.fileName, { type: mimeType });\n\n dispatch(setFileLoading({ id: fileId, loading: true }));\n const requestId = newRequestId();\n registerRequest({\n requestId,\n kind: \"upload\",\n resourceId: fileId,\n resourceType: \"file\",\n });\n try {\n const reqOpts = { requestId, idempotencyKey: requestId };\n const summary = changeSummary ?? \"Edited in place\";\n const { data } = await Files.uploadNewVersion(\n fileId,\n { file: upload, changeSummary: summary },\n reqOpts,\n );\n if (data.file_id !== fileId || data.is_new) {\n throw new Error(\n `Saving \"${record.fileName}\" did not update it — the files service wrote ` +\n `a separate file (${data.file_path}, id ${data.file_id}) instead of ` +\n `version ${record.currentVersion + 1}. Your original is unchanged; ` +\n `remove the extra file from the folder.`,\n );\n }\n dispatch(\n upsertFile({\n id: fileId,\n currentVersion: data.version_number,\n fileSize: data.size_bytes,\n checksum: data.checksum,\n updatedAt: new Date().toISOString(),\n }),\n );\n // The current bytes are the ones just uploaded — cache them as the new\n // version so every viewer shows it without downloading them back.\n replaceWithSavedBytes(fileId, body, data.version_number);\n invalidateOfficeExtraction(fileId);\n // Refresh the history without holding the save on it — the save has\n // landed, and \"Saving…\" must not wait on a list read.\n void dispatch(loadFileVersions({ fileId }))\n .unwrap()\n .catch((err: unknown) => {\n // The save landed; only the history refresh failed. Say so.\n console.error(\n \"[saveFileNewVersion] saved, but the version list did not refresh:\",\n err,\n );\n });\n return { fileId, versionNumber: data.version_number };\n } finally {\n dispatch(setFileLoading({ id: fileId, loading: false }));\n releaseRequest(requestId);\n }\n },\n);\n\n// ---------------------------------------------------------------------------\n// Writes — permissions\n// ---------------------------------------------------------------------------\n\n/**\n * The by-grantee REST grant/revoke endpoints (`GrantPermissionRequest`,\n * `?grantee_type=`) accept only `user | group`. PUBLIC access is not a\n * by-grantee grant — it is toggled on the resource's visibility. Reaching the\n * grant/revoke thunks with `granteeType: \"public\"` is a programming error, so\n * fail loudly (repo principle) rather than silently coercing it to a bogus\n * \"user\"/\"group\" call against an empty/row-id grantee.\n */\nfunction requireByGranteeType(\n granteeType: GranteeType | undefined,\n): \"user\" | \"group\" {\n if (granteeType === \"public\") {\n throw new Error(\n \"Public access is toggled via the resource's visibility, not the by-grantee grant/revoke path.\",\n );\n }\n return granteeType ?? \"user\";\n}\n\nexport const grantPermission = createAsyncThunk<\n void,\n GrantPermissionArg,\n ThunkApi\n>(\"cloudFiles/grantPermission\", async (arg, { dispatch }) => {\n if (isVirtualResourceId(arg.resourceId)) {\n throw new Error(\"Sharing isn't available for this source yet.\");\n }\n const requestId = newRequestId();\n registerRequest({\n requestId,\n kind: \"grant-permission\",\n resourceId: arg.resourceId,\n resourceType: arg.resourceType,\n });\n try {\n const body = {\n grantee_id: arg.granteeId,\n level: arg.level,\n grantee_type: requireByGranteeType(arg.granteeType),\n expires_at: arg.expiresAt ?? null,\n };\n const { data } =\n arg.resourceType === \"folder\"\n ? await Permissions.grantFolderPermission(arg.resourceId, body, {\n requestId,\n })\n : await Permissions.grantFilePermission(arg.resourceId, body, {\n requestId,\n });\n dispatch(\n upsertPermissionsForResource({\n resourceId: arg.resourceId,\n permissions: [rpcPermissionRowToCloudFilePermission(data)],\n }),\n );\n // P1-6: the returned row already makes the UI correct. Reconcile the full\n // list in the BACKGROUND (don't await) so the dialog doesn't stall on a\n // second round-trip after every grant.\n void dispatch(loadPermissions({ resourceId: arg.resourceId }));\n } finally {\n releaseRequest(requestId);\n }\n});\n\nexport const revokePermission = createAsyncThunk<\n void,\n RevokePermissionArg,\n ThunkApi\n>(\"cloudFiles/revokePermission\", async (arg, { dispatch }) => {\n if (isVirtualResourceId(arg.resourceId)) {\n throw new Error(\"Sharing isn't available for this source yet.\");\n }\n const requestId = newRequestId();\n registerRequest({\n requestId,\n kind: \"revoke-permission\",\n resourceId: arg.resourceId,\n resourceType: arg.resourceType,\n });\n try {\n const granteeType = requireByGranteeType(arg.granteeType);\n if (arg.resourceType === \"folder\") {\n await Permissions.revokeFolderPermission(\n arg.resourceId,\n arg.granteeId,\n { granteeType },\n { requestId },\n );\n } else {\n await Permissions.revokeFilePermission(\n arg.resourceId,\n arg.granteeId,\n { granteeType },\n { requestId },\n );\n }\n dispatch(\n removePermissionForResource({\n resourceId: arg.resourceId,\n granteeId: arg.granteeId,\n granteeType,\n }),\n );\n } finally {\n releaseRequest(requestId);\n }\n});\n\n// ---------------------------------------------------------------------------\n// Writes — share links\n// ---------------------------------------------------------------------------\n\nexport const createShareLink = createAsyncThunk<\n CloudShareLink,\n CreateShareLinkArg,\n ThunkApi\n>(\"cloudFiles/createShareLink\", async (arg, { dispatch, getState }) => {\n if (isVirtualResourceId(arg.resourceId)) {\n throw new Error(\"Share links aren't available for this source yet.\");\n }\n // P1-1: a second click while the first create is in flight would mint a\n // second token for the same resource. Suppress the duplicate submit.\n if (hasInFlight(arg.resourceId, \"create-share-link\")) {\n throw new Error(\"A share link is already being created for this item.\");\n }\n const requestId = newRequestId();\n registerRequest({\n requestId,\n kind: \"create-share-link\",\n resourceId: arg.resourceId,\n resourceType: arg.resourceType,\n });\n try {\n // Canonical owner-gated mint over platform.share_links.\n const created = await createCanonicalShareLink({\n resourceType: arg.resourceType,\n resourceId: arg.resourceId,\n permissionLevel: arg.permissionLevel,\n expiresAt: arg.expiresAt ?? null,\n maxUses: arg.maxUses ?? null,\n });\n if (!created.success || !created.token) {\n throw new Error(created.error ?? \"Failed to create share link\");\n }\n // Reload the full list — the create RPC returns only { token, id }.\n await dispatch(\n loadShareLinks({\n resourceId: arg.resourceId,\n resourceType: arg.resourceType,\n }),\n ).unwrap();\n const link = (\n getState().cloudFiles.shareLinksByResourceId[arg.resourceId] ?? []\n ).find((l) => l.shareToken === created.token);\n if (!link) {\n throw new Error(\"Share link was created but could not be loaded back\");\n }\n return link;\n } finally {\n releaseRequest(requestId);\n }\n});\n\nexport const revokeShareLink = createAsyncThunk<\n void,\n RevokeShareLinkArg,\n ThunkApi\n>(\"cloudFiles/revokeShareLink\", async ({ linkId }, { dispatch }) => {\n const requestId = newRequestId();\n registerRequest({\n requestId,\n kind: \"revoke-share-link\",\n resourceId: null,\n resourceType: null,\n });\n try {\n const result = await revokeCanonicalShareLink(linkId);\n if (!result.success) {\n throw new Error(result.error ?? \"Failed to revoke share link\");\n }\n dispatch(removeShareLink({ linkId }));\n } finally {\n releaseRequest(requestId);\n }\n});\n\n// ---------------------------------------------------------------------------\n// Utility — getFileUrl (no slice state change).\n//\n// Routes through the universal handler and returns the durable renderable\n// URL (permanent CDN for public files, the durable authenticated download\n// route otherwise). Durable URLs never expire.\n// ---------------------------------------------------------------------------\n\nexport const getFileUrl = createAsyncThunk<\n { url: string },\n { fileId: string },\n ThunkApi\n>(\"cloudFiles/getFileUrl\", async ({ fileId }) => {\n if (isVirtualResourceId(fileId)) {\n // Virtual files don't have stored bytes — there's no byte URL. Callers\n // already hide the buttons that surface URLs (Download, Copy link,\n // Open in new tab) for virtual rows; this guards the API path for any\n // remaining callers.\n throw new Error(\"Byte URLs aren't available for virtual sources\");\n }\n const url = await fileHandler\n .use({ kind: \"file_id\", fileId })\n .as({ kind: \"html_src\" });\n if (!url) {\n throw new Error(`Could not resolve a URL for file ${fileId}`);\n }\n return { url };\n});\n\n// ---------------------------------------------------------------------------\n// Bulk operations (Python P-7)\n// ---------------------------------------------------------------------------\n//\n// Each bulk thunk applies the optimistic local change up front, then makes a\n// single REST round-trip. The backend returns a per-item succeeded/failed\n// envelope; we re-apply the failed entries by re-upserting their pre-change\n// state from a snapshot taken before the optimistic update.\n\n/**\n * Move many files to Trash (soft delete) in one round-trip.\n *\n * Returns the standard `BulkResponse` envelope so the caller can decide\n * how to surface partial failures (toast vs. row-level error chips).\n * Successful ids are removed from the local store immediately for\n * snappy UI; failed ids are restored from the pre-change snapshot.\n */\n/**\n * P1-5: bulk ops resolve successfully even when some items fail (per-item\n * results in the envelope), so the rejected-thunk toast middleware never sees\n * them. Surface partial failures here so the user isn't left thinking every\n * item succeeded. Full success stays silent (the rows visibly changed).\n */\nfunction toastBulkPartialFailure(result: BulkResponse, verb: string): void {\n if (result.failed > 0) {\n const total = result.succeeded + result.failed;\n toast.error(`Couldn't ${verb} ${result.failed} of ${total} items`, {\n description:\n result.succeeded > 0\n ? `${result.succeeded} succeeded. The rest were restored.`\n : \"All items were restored.\",\n });\n }\n}\n\nexport const bulkDeleteFiles = createAsyncThunk<\n BulkResponse,\n BulkDeleteFilesArg,\n ThunkApi\n>(\"cloudFiles/bulkDeleteFiles\", async (arg, { dispatch, getState }) => {\n if (arg.fileIds.length === 0) {\n return { results: [], succeeded: 0, failed: 0 };\n }\n\n // Snapshot pre-change records for rollback on per-item failures.\n const state = getState();\n const snapshots = new Map<string, CloudFile>();\n for (const id of arg.fileIds) {\n const file = state.cloudFiles.filesById[id];\n if (file) snapshots.set(id, file);\n }\n\n const requestId = newRequestId();\n registerRequest({\n requestId,\n kind: \"bulk-delete-files\",\n resourceId: null,\n resourceType: \"file\",\n });\n\n // Optimistic: remove all targets from the local store and drop their\n // cached blobs (deletion makes the cache entry useless and keeping\n // bytes around for soft-deleted items wastes the LRU budget).\n for (const id of arg.fileIds) {\n dispatch(removeFile({ id }));\n invalidateBlobCache(id);\n invalidateOfficeExtraction(id);\n }\n\n try {\n const { data } = await Files.bulkDeleteFiles(\n { file_ids: arg.fileIds },\n { requestId },\n );\n const result: BulkResponse = data ?? {\n results: arg.fileIds.map((id) => ({ id, ok: true, error: null })),\n succeeded: arg.fileIds.length,\n failed: 0,\n };\n\n // Roll back any items the backend reports as failed.\n for (const r of result.results) {\n if (r.ok) continue;\n const snap = snapshots.get(r.id);\n if (snap) dispatch(upsertFile(snap));\n }\n toastBulkPartialFailure(result, \"delete\");\n return result;\n } catch (err) {\n // Whole-call failure — restore everything we removed.\n for (const snap of snapshots.values()) dispatch(upsertFile(snap));\n throw err;\n } finally {\n releaseRequest(requestId);\n }\n});\n\n/**\n * Move many files to a new parent folder in one round-trip. Pass `null` to\n * move to root.\n */\nexport const bulkMoveFiles = createAsyncThunk<\n BulkResponse,\n BulkMoveFilesArg,\n ThunkApi\n>(\"cloudFiles/bulkMoveFiles\", async (arg, { dispatch, getState }) => {\n if (arg.fileIds.length === 0) {\n return { results: [], succeeded: 0, failed: 0 };\n }\n\n const state = getState();\n const snapshots = new Map<string, CloudFile>();\n for (const id of arg.fileIds) {\n const file = state.cloudFiles.filesById[id];\n if (file) snapshots.set(id, file);\n }\n\n const requestId = newRequestId();\n registerRequest({\n requestId,\n kind: \"bulk-move-files\",\n resourceId: null,\n resourceType: \"file\",\n });\n\n // Optimistic: re-parent + retract from old parent / attach to new in tree.\n for (const [id, file] of snapshots) {\n if (file.parentFolderId === arg.newParentFolderId) continue;\n dispatch(\n detachChildFromFolder({\n parentFolderId: file.parentFolderId,\n kind: \"file\",\n id,\n }),\n );\n dispatch(\n attachChildToFolder({\n parentFolderId: arg.newParentFolderId,\n kind: \"file\",\n id,\n }),\n );\n dispatch(upsertFile({ ...file, parentFolderId: arg.newParentFolderId }));\n }\n\n try {\n const { data } = await Files.bulkMoveFiles(\n {\n file_ids: arg.fileIds,\n new_parent_folder_id: arg.newParentFolderId,\n },\n { requestId },\n );\n\n // Roll back per-item failures.\n for (const r of data.results) {\n if (r.ok) continue;\n const snap = snapshots.get(r.id);\n if (!snap || snap.parentFolderId === arg.newParentFolderId) continue;\n dispatch(\n detachChildFromFolder({\n parentFolderId: arg.newParentFolderId,\n kind: \"file\",\n id: snap.id,\n }),\n );\n dispatch(\n attachChildToFolder({\n parentFolderId: snap.parentFolderId,\n kind: \"file\",\n id: snap.id,\n }),\n );\n dispatch(upsertFile(snap));\n }\n toastBulkPartialFailure(data, \"move\");\n return data;\n } catch (err) {\n // Whole-call failure — restore everything.\n for (const snap of snapshots.values()) {\n if (snap.parentFolderId === arg.newParentFolderId) continue;\n dispatch(\n detachChildFromFolder({\n parentFolderId: arg.newParentFolderId,\n kind: \"file\",\n id: snap.id,\n }),\n );\n dispatch(\n attachChildToFolder({\n parentFolderId: snap.parentFolderId,\n kind: \"file\",\n id: snap.id,\n }),\n );\n dispatch(upsertFile(snap));\n }\n throw err;\n } finally {\n releaseRequest(requestId);\n }\n});\n\n/**\n * Move many folders to a new parent in one round-trip. Pass `null` to move\n * to root. The backend cascades `folder_path` updates to descendants.\n */\nexport const bulkMoveFolders = createAsyncThunk<\n BulkResponse,\n BulkMoveFoldersArg,\n ThunkApi\n>(\"cloudFiles/bulkMoveFolders\", async (arg, { dispatch, getState }) => {\n if (arg.folderIds.length === 0) {\n return { results: [], succeeded: 0, failed: 0 };\n }\n\n const state = getState();\n const snapshots = new Map<string, CloudFolder>();\n for (const id of arg.folderIds) {\n const folder = state.cloudFiles.foldersById[id];\n if (folder) snapshots.set(id, folder);\n }\n\n const requestId = newRequestId();\n registerRequest({\n requestId,\n kind: \"bulk-move-folders\",\n resourceId: null,\n resourceType: \"folder\",\n });\n\n // Optimistic: rewire each folder under the new parent.\n for (const [id, folder] of snapshots) {\n if (folder.parentId === arg.newParentId) continue;\n dispatch(\n detachChildFromFolder({\n parentFolderId: folder.parentId,\n kind: \"folder\",\n id,\n }),\n );\n dispatch(\n attachChildToFolder({\n parentFolderId: arg.newParentId,\n kind: \"folder\",\n id,\n }),\n );\n dispatch(upsertFolder({ ...folder, parentId: arg.newParentId }));\n }\n\n try {\n const { data } = await Folders.bulkMoveFolders(\n { folder_ids: arg.folderIds, new_parent_id: arg.newParentId },\n { requestId },\n );\n\n // Roll back per-item failures.\n for (const r of data.results) {\n if (r.ok) continue;\n const snap = snapshots.get(r.id);\n if (!snap || snap.parentId === arg.newParentId) continue;\n dispatch(\n detachChildFromFolder({\n parentFolderId: arg.newParentId,\n kind: \"folder\",\n id: snap.id,\n }),\n );\n dispatch(\n attachChildToFolder({\n parentFolderId: snap.parentId,\n kind: \"folder\",\n id: snap.id,\n }),\n );\n dispatch(upsertFolder(snap));\n }\n toastBulkPartialFailure(data, \"move\");\n return data;\n } catch (err) {\n for (const snap of snapshots.values()) {\n if (snap.parentId === arg.newParentId) continue;\n dispatch(\n detachChildFromFolder({\n parentFolderId: arg.newParentId,\n kind: \"folder\",\n id: snap.id,\n }),\n );\n dispatch(\n attachChildToFolder({\n parentFolderId: snap.parentId,\n kind: \"folder\",\n id: snap.id,\n }),\n );\n dispatch(upsertFolder(snap));\n }\n throw err;\n } finally {\n releaseRequest(requestId);\n }\n});\n"],"mappings":";AAgBA,SAAS,wBAAwB;AAQjC,SAAS,gBAAgB;AACzB,SAAS,SAAS,mCAAmC;AACrD,SAAS,sBAAsB;AAE/B,YAAY,WAAW;AACvB;AAAA,EACE;AAAA,EACA;AAAA,OAEK;AACP,YAAY,aAAa;AACzB,YAAY,iBAAiB;AAC7B,YAAY,cAAc;AAC1B;AAAA,EACE,mBAAmB;AAAA,EACnB,kBAAkB;AAAA,EAClB,mBAAmB;AAAA,OACd;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,mBAAmB;AAC5B,SAAS,oBAAoB;AAC7B,SAAS,2BAA2B;AACpC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,aAAa;AACtB,SAAS,sBAAsB;AAC/B;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE,cAAc;AAAA,EACd;AAAA,OACK;AACP,SAAS,kCAAkC;AAC3C;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,wBAAwB;AAgCjC,SAAS,qBAAqB;AAe9B,MAAM,6BAA6B,oBAAI,IAGrC;AAEF,eAAe,qBACb,QACA,QAC8D;AAC9D,QAAM,MAAM,GAAG,MAAM,IAAI,CAAC,GAAG,MAAM,EAAE,KAAK,EAAE,KAAK,GAAG,CAAC;AACrD,QAAM,WAAW,2BAA2B,IAAI,GAAG;AACnD,MAAI,SAAU,QAAO;AAErB,QAAM,WAAW,YAAY;AAQ3B,UAAM,EAAE,KAAK,IAAI,MAAM,MAAM,gBAAgB,MAAM;AACnD,UAAM,SAAS,yBAAyB,IAAI;AAC5C,QAAI,OAAO,UAAW,QAAO;AAE7B,QAAI,sBAAsB,MAAM,GAAG;AACjC,aAAO;AAAA,QACL,IAAI,OAAO;AAAA,QACX,UAAU,OAAO;AAAA,QACjB,UAAU,OAAO;AAAA,QACjB,UAAU,OAAO;AAAA,QACjB,YAAY,OAAO;AAAA,MACrB;AAAA,IACF;AACA,WAAO;AAAA,EACT,GAAG,EAAE,QAAQ,MAAM,2BAA2B,OAAO,GAAG,CAAC;AAEzD,6BAA2B,IAAI,KAAK,OAAO;AAC3C,SAAO;AACT;AAMO,MAAM,wBAAwB;AAAA,EAKnC;AAAA,EACA,OACE,EAAE,QAAQ,SAAS,uBAAuB,KAAK,GAC/C,EAAE,UAAU,SAAS,MAClB;AACH,aAAS,WAAW,2BAA2B,QAAQ,IAAI,CAAC,CAAC;AAE7D,QACE,yBAAyB,iBAAiB,SAAS,GAAG,MAAM,GAAG,MAAM,GACrE;AACA,aAAO;AAAA,IACT;AAEA,aAAS,eAAe,EAAE,IAAI,QAAQ,SAAS,KAAK,CAAC,CAAC;AACtD,aAAS,aAAa,EAAE,IAAI,QAAQ,OAAO,KAAK,CAAC,CAAC;AAClD,QAAI;AACF,YAAM,UAAU,MAAM,qBAAqB,QAAQ,MAAM;AACzD,UAAI,CAAC,QAAS,QAAO;AACrB,eAAS,WAAW,OAAO,CAAC;AAC5B,aAAO;AAAA,IACT,SAAS,OAAO;AACd,YAAM,aAAa,eAAe,KAAK;AACvC,eAAS,aAAa,EAAE,IAAI,QAAQ,OAAO,WAAW,QAAQ,CAAC,CAAC;AAChE,YAAM;AAAA,IACR,UAAE;AACA,eAAS,eAAe,EAAE,IAAI,QAAQ,SAAS,MAAM,CAAC,CAAC;AAAA,IACzD;AAAA,EACF;AACF;AASA,MAAM,iBAAiB;AAKvB,MAAM,iBAAiB;AAMvB,IAAI,gBAAsC;AAC1C,IAAI,sBAAqC;AAElC,MAAM,mBAAmB,iBAI9B,+BAA+B,OAAO,EAAE,OAAO,GAAG,EAAE,UAAU,SAAS,MAAM;AAK7E,MAAI,CAAE,MAAM,2BAA2B,MAAM,EAAI;AAQjD,MAAI,iBAAiB,wBAAwB,OAAQ,QAAO;AAE5D,QAAM,OAAO,YAAY;AACvB,aAAS,cAAc,EAAE,QAAQ,UAAU,CAAC,CAAC;AAa7C,QAAI;AAGF,YAAM,OAAO,MAAM,2BAA2B,OAAO,WAAW;AAC9D,cAAMA,QAA8C,CAAC;AACrD,iBAAS,OAAO,GAAG,OAAO,gBAAgB,QAAQ,GAAG;AACnD,gBAAM,EAAE,MAAM,MAAM,IAAI,MAAM;AAAA,YAA4B,MACxD,SACG,IAAI,sBAAsB;AAAA,cACzB,WAAW;AAAA,cACX,SAAS;AAAA,cACT,UAAU,OAAO;AAAA,cACjB,mBAAmB;AAAA,cACnB,mBAAmB;AAAA,YACrB,CAAC,EACA,YAAY,MAAM;AAAA,UACvB;AAEA,cAAI,OAAO;AAGT,gBAAI,CAAC,OAAO,SAAS;AACnB;AAAA,gBACE,cAAc,EAAE,QAAQ,SAAS,OAAO,MAAM,QAAQ,CAAC;AAAA,cACzD;AAAA,YACF;AACA,kBAAM;AAAA,UACR;AAEA,gBAAM,WAAW,mBAAmB,IAAI;AACxC,UAAAA,MAAK,KAAK,GAAG,QAAQ;AACrB,cAAI,SAAS,SAAS,eAAgB;AAAA,QACxC;AACA,eAAOA;AAAA,MACT,CAAC;AAgCD,YAAM,QAAkC,CAAC;AACzC,YAAM,UAAsC,CAAC;AAC7C,iBAAW,OAAO,MAAM;AACtB,YAAI,IAAI,SAAS,QAAQ;AAIvB,gBAAM,KAAK;AAAA,YACT,IAAI,IAAI;AAAA,YACR,SAAS,IAAI;AAAA,YACb,UAAU,IAAI;AAAA,YACd,UAAU,IAAI;AAAA,YACd,gBAAgB,IAAI;AAAA,YACpB,UAAU,IAAI;AAAA;AAAA;AAAA;AAAA,YAId,UAAU,IAAI;AAAA,YACd,YAAY,IAAI;AAAA,YAChB,gBAAgB,IAAI;AAAA,YACpB,WAAW,IAAI;AAAA,YACf,WAAW,IAAI;AAAA,YACf,WAAW,IAAI;AAAA,YACf,gBAAgB,IAAI;AAAA,YACpB,UAAU,IAAI;AAAA,UAChB,CAAC;AAAA,QACH,OAAO;AACL,kBAAQ,KAAK;AAAA,YACX,IAAI,IAAI;AAAA,YACR,SAAS,IAAI;AAAA,YACb,YAAY,IAAI;AAAA,YAChB,YAAY,IAAI;AAAA,YAChB,UAAU,IAAI;AAAA,YACd,YAAY,IAAI;AAAA,YAChB,WAAW,IAAI;AAAA,YACf,WAAW,IAAI;AAAA,YACf,WAAW,IAAI;AAAA,YACf,UAAU,IAAI;AAAA,UAChB,CAAC;AAAA,QACH;AAAA,MACF;AAEA,eAAS,YAAY,KAAK,CAAC;AAC3B,eAAS,cAAc,OAAO,CAAC;AAK/B,YAAM,QAAQ,SAAS,EAAE;AACzB,iBAAW,CAAC,IAAI,GAAG,KAAK,OAAO,QAAQ,MAAM,SAAS,GAAG;AACvD,YAAI,OAAO,CAAC,sBAAsB,IAAI,QAAQ,GAAG;AAC/C,mBAAS,WAAW,EAAE,GAAG,CAAC,CAAC;AAAA,QAC7B;AAAA,MACF;AACA,iBAAW,CAAC,IAAI,GAAG,KAAK,OAAO,QAAQ,MAAM,WAAW,GAAG;AACzD,YACE,OACA,IAAI,OAAO,SAAS,UACpB,CAAC,wBAAwB,IAAI,UAAU,GACvC;AACA,mBAAS,aAAa,EAAE,GAAG,CAAC,CAAC;AAAA,QAC/B;AAAA,MACF;AAIA,YAAM,UAAU,MAAM,IAAI,CAAC,MAAM,EAAE,EAAE,EAAE,OAAO,OAAO;AACrD,YAAM,YAAY,QAAQ,IAAI,CAAC,MAAM,EAAE,EAAE,EAAE,OAAO,OAAO;AACzD,YAAM,OAAO,eAAe;AAAA,QAC1B;AAAA,QACA;AAAA,QACA,WAAW,OAAO;AAAA,UAChB,MAAM,IAAI,CAAC,MAAM;AAAA,YACf,EAAE;AAAA,YACF;AAAA,cACE,IAAI,EAAE;AAAA,cACN,SAAS,EAAE;AAAA,cACX,UAAU,EAAE;AAAA,cACZ,UAAU,EAAE;AAAA,cACZ,UAAU,EAAE,YAAY;AAAA,cACxB,UAAU,EAAE,YAAY;AAAA,cACxB,UAAU;AAAA,cACV,YAAY,EAAE;AAAA,cACd,gBAAgB,EAAE;AAAA,cAClB,gBAAgB,EAAE,kBAAkB;AAAA,cACpC,UAAU,EAAE,YAAY,CAAC;AAAA,cACzB,WAAW,EAAE;AAAA,cACb,WAAW,EAAE;AAAA,cACb,WAAW,EAAE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,cAMb,WAAW;AAAA,cACX,KAAK;AAAA,cACL,QAAQ;AAAA,cACR,aAAa;AAAA,cACb,cAAc;AAAA,cACd,QAAQ,EAAE,MAAM,OAAO;AAAA,cACvB,QAAQ;AAAA,cACR,cAAc,CAAC;AAAA,cACf,eAAe,CAAC;AAAA,cAChB,eAAe,CAAC;AAAA,cAChB,UAAU;AAAA,cACV,QAAQ;AAAA,cACR,oBAAoB,CAAC;AAAA,YACvB;AAAA,UACF,CAAC;AAAA,QACH;AAAA,QACA,aAAa,OAAO;AAAA,UAClB,QAAQ,IAAI,CAAC,MAAM;AAAA,YACjB,EAAE;AAAA,YACF;AAAA,cACE,IAAI,EAAE;AAAA,cACN,SAAS,EAAE;AAAA,cACX,YAAY,EAAE;AAAA,cACd,YAAY,EAAE;AAAA,cACd,UAAU,EAAE,YAAY;AAAA,cACxB,YAAY,EAAE;AAAA,cACd,UAAU,EAAE,YAAY,CAAC;AAAA,cACzB,WAAW,EAAE;AAAA,cACb,WAAW,EAAE;AAAA,cACb,WAAW,EAAE;AAAA;AAAA;AAAA;AAAA,cAIb,WAAW;AAAA,cACX,QAAQ,EAAE,MAAM,OAAO;AAAA,cACvB,QAAQ;AAAA,cACR,cAAc,CAAC;AAAA,cACf,eAAe,CAAC;AAAA,cAChB,eAAe,CAAC;AAAA,cAChB,UAAU;AAAA,cACV,QAAQ;AAAA,cACR,oBAAoB,CAAC;AAAA,YACvB;AAAA,UACF,CAAC;AAAA,QACH;AAAA,MACF,CAAC;AACD;AAAA,QACE,YAAY;AAAA,UACV,eAAe,KAAK;AAAA,UACpB,aAAa,KAAK;AAAA,UAClB,oBAAoB,KAAK;AAAA;AAAA;AAAA;AAAA,UAIzB,sBAAsB,OAAO;AAAA,YAC3B,UAAU,IAAI,CAAC,OAAO,CAAC,IAAI,IAAI,CAAU;AAAA,UAC3C;AAAA,QACF,CAAC;AAAA,MACH;AAAA,IACF,SAAS,OAAO;AAGd,UAAI,uBAAuB,KAAK,GAAG;AACjC,iBAAS,cAAc,EAAE,QAAQ,OAAO,CAAC,CAAC;AAC1C,cAAM;AAAA,MACR;AACA,YAAM,UACJ,iBAAiB,QACb,MAAM,UACN;AACN,eAAS,cAAc,EAAE,QAAQ,SAAS,OAAO,QAAQ,CAAC,CAAC;AAC3D,YAAM;AAAA,IACR;AAAA,EACF,GAAG;AAEH,kBAAgB;AAChB,wBAAsB;AACtB,MAAI;AACF,UAAM;AAAA,EACR,UAAE;AACA,QAAI,kBAAkB,KAAK;AACzB,sBAAgB;AAChB,4BAAsB;AAAA,IACxB;AAAA,EACF;AACF,CAAC;AAMM,MAAM,gBAAgB,iBAI3B,4BAA4B,OAAO,EAAE,OAAO,GAAG,EAAE,SAAS,MAAM;AAChE,QAAM,SAAS,iBAAiB,EAAE,OAAO,CAAC,CAAC,EAAE,OAAO;AACtD,CAAC;AAEM,MAAM,qBAAqB,iBAIhC,iCAAiC,OAAO,EAAE,SAAS,GAAG,EAAE,SAAS,MAAM;AAGvE,MAAI,SAAS,WAAW,MAAM,EAAG;AAKjC,QAAM,EAAE,MAAM,MAAM,IAAI,MAAM,cAAc,QAAQ;AACpD,MAAI,MAAO,OAAM;AACjB,MAAI,CAAC,KAAK,MAAM;AACd,UAAM,IAAI,MAAM,4CAA4C;AAC9D,QAAM,SAAS,iBAAiB,EAAE,QAAQ,KAAK,KAAK,GAAG,CAAC,CAAC,EAAE,OAAO;AAClE,WAAS,sBAAsB,EAAE,SAAS,CAAC,CAAC;AAC9C,CAAC;AAQD,SAAS,oBAAoB,IAAqB;AAChD,SAAO,GAAG,WAAW,MAAM;AAC7B;AAEO,MAAM,mBAAmB,iBAI9B,+BAA+B,OAAO,EAAE,OAAO,GAAG,EAAE,SAAS,MAAM;AACnE,MAAI,oBAAoB,MAAM,EAAG;AACjC,QAAM,EAAE,MAAM,MAAM,IAAI,MAAM,QAAQ,QAAQ,EAC3C,KAAK,eAAe,EACpB,OAAO,2BAA2B,EAClC,GAAG,WAAW,MAAM,EACpB,MAAM,kBAAkB,EAAE,WAAW,MAAM,CAAC;AAC/C,MAAI,MAAO,OAAM,eAAe,KAAK;AACrC,QAAM,YAAgC,QAAQ,CAAC,GAAG;AAAA,IAChD;AAAA,EACF;AACA,WAAS,sBAAsB,EAAE,QAAQ,SAAS,CAAC,CAAC;AACtD,CAAC;AAEM,MAAM,kBAAkB,iBAI7B,8BAA8B,OAAO,EAAE,WAAW,GAAG,EAAE,SAAS,MAAM;AACtE,MAAI,oBAAoB,UAAU,EAAG;AAIrC,QAAM,EAAE,MAAM,MAAM,IAAI,MAAM,SAC3B,OAAO,KAAK,EACZ,KAAK,aAAa,EAClB,OAAO,GAAG,EACV,GAAG,iBAAiB,MAAM,EAC1B,GAAG,eAAe,UAAU;AAC/B,MAAI,MAAO,OAAM,eAAe,KAAK;AACrC,QAAM,eAAsC,QAAQ,CAAC,GAAG;AAAA,IACtD;AAAA,EACF;AACA,WAAS,6BAA6B,EAAE,YAAY,YAAY,CAAC,CAAC;AACpE,CAAC;AAEM,MAAM,iBAAiB;AAAA,EAK5B;AAAA,EACA,OAAO,EAAE,YAAY,eAAe,OAAO,GAAG,EAAE,SAAS,MAAM;AAC7D,QAAI,oBAAoB,UAAU,EAAG;AAErC,UAAM,QAAQ,MAAM,wBAAwB,cAAc,UAAU;AACpE,UAAM,aAA+B,MAClC,OAAO,CAAC,MAAM,EAAE,QAAQ,EACxB;AAAA,MAAI,CAAC,MACJ,mCAAmC,GAAG,cAAc,UAAU;AAAA,IAChE;AACF,aAAS,4BAA4B,EAAE,YAAY,WAAW,CAAC,CAAC;AAAA,EAClE;AACF;AAqBO,MAAM,eAAe,iBAI1B,2BAA2B,OAAO,KAAK,EAAE,UAAU,SAAS,MAAM;AAClE,QAAM,aAAa,IAAI,WAAW,KAAK;AACvC,MAAI,CAAC,YAAY;AACf,UAAM,IAAI,MAAM,8BAA8B;AAAA,EAChD;AACA,MAAI,QAAQ,KAAK,UAAU,GAAG;AAC5B,UAAM,IAAI,MAAM,0CAA0C;AAAA,EAC5D;AAIA,MAAI,kBAAkB,KAAK,UAAU,GAAG;AACtC,UAAM,IAAI,MAAM,iDAAiD;AAAA,EACnE;AACA,MAAI,WAAW,SAAS,GAAG,GAAG;AAC5B,UAAM,IAAI,MAAM,mCAAmC;AAAA,EACrD;AAKA;AACE,UAAM,QAAQ,SAAS,EAAE;AACzB,UAAM,UAAU,WAAW,YAAY;AACvC,UAAM,YAAY,OAAO,OAAO,MAAM,WAAW,EAAE;AAAA,MACjD,CAAC,MACC,CAAC,CAAC,KACF,CAAC,EAAE,cACF,EAAE,YAAY,WAAW,IAAI,YAAY,SAC1C,EAAE,WAAW,YAAY,MAAM;AAAA,IACnC;AACA,QAAI,WAAW;AACb,YAAM,IAAI;AAAA,QACR,mBAAmB,UAAU;AAAA,MAC/B;AAAA,IACF;AAAA,EACF;AAEA,QAAM,YAAY,aAAa;AAC/B,kBAAgB;AAAA,IACd;AAAA,IACA,MAAM;AAAA,IACN,YAAY;AAAA,IACZ,cAAc;AAAA,EAChB,CAAC;AAOD,MAAI,aAAa;AACjB,MAAI,IAAI,aAAa,MAAM;AACzB,UAAM,SAAS,SAAS,EAAE,WAAW,YAAY,IAAI,QAAQ;AAC7D,QAAI,CAAC,QAAQ;AAIX,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,iBAAa,GAAG,OAAO,UAAU,IAAI,UAAU;AAAA,EACjD;AACA,QAAM,OAAO;AAAA,IACX,aAAa;AAAA,IACb,YAAY,IAAI,cAAc;AAAA,IAC9B,UAAU,IAAI,YAAY;AAAA,EAC5B;AAEA,MAAI;AACF,UAAM,EAAE,MAAM,IAAI,IAAI,MAAM,QAAQ,aAAa,MAAM,EAAE,UAAU,CAAC;AAEpE,UAAM,SAAS,mBAAmB,GAAG;AACrC,aAAS,aAAa,MAAM,CAAC;AAC7B;AAAA,MACE,oBAAoB;AAAA,QAClB,gBAAgB,OAAO;AAAA,QACvB,MAAM;AAAA,QACN,IAAI,OAAO;AAAA,MACb,CAAC;AAAA,IACH;AACA,WAAO,OAAO;AAAA,EAChB,SAAS,KAAK;AACZ,QAAI,yBAAyB,cAAc;AACzC,YAAM,IAAI;AASV,cAAQ,MAAM,gCAAgC;AAAA,QAC5C,QAAQ,EAAE,UAAU;AAAA,QACpB,MAAM,EAAE;AAAA,QACR,QAAQ,EAAE;AAAA,QACV,aAAa,EAAE;AAAA,QACf,SAAS,EAAE;AAAA,QACX,UAAU;AAAA,MACZ,CAAC;AAAA,IACH;AACA,UAAM;AAAA,EACR,UAAE;AACA,mBAAe,SAAS;AAAA,EAC1B;AACF,CAAC;AAOM,MAAM,eAAe,iBAI1B,2BAA2B,OAAO,KAAK,EAAE,UAAU,SAAS,MAAM;AAClE,QAAM,QAAQ,SAAS;AACvB,QAAM,SAAS,MAAM,WAAW,YAAY,IAAI,QAAQ;AACxD,MAAI,CAAC;AACH,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAEF,QAAM,YAAY,aAAa;AAC/B,kBAAgB;AAAA,IACd;AAAA,IACA,MAAM;AAAA,IACN,YAAY,IAAI;AAAA,IAChB,cAAc;AAAA,EAChB,CAAC;AAGD,QAAM,QAAQ,IAAI;AAClB,QAAM,aAA0B;AAAA,IAC9B,GAAG;AAAA,IACH,GAAI,MAAM,eAAe,SAAY,EAAE,YAAY,MAAM,WAAW,IAAI,CAAC;AAAA,IACzE,GAAI,MAAM,aAAa,SAAY,EAAE,UAAU,MAAM,SAAS,IAAI,CAAC;AAAA,IACnE,GAAI,MAAM,eAAe,SAAY,EAAE,YAAY,MAAM,WAAW,IAAI,CAAC;AAAA,IACzE,GAAI,MAAM,aAAa,SAAY,EAAE,UAAU,MAAM,SAAS,IAAI,CAAC;AAAA,EACrE;AACA,WAAS,aAAa,UAAU,CAAC;AAGjC,MAAI,MAAM,aAAa,UAAa,MAAM,aAAa,OAAO,UAAU;AACtE;AAAA,MACE,sBAAsB;AAAA,QACpB,gBAAgB,OAAO;AAAA,QACvB,MAAM;AAAA,QACN,IAAI,OAAO;AAAA,MACb,CAAC;AAAA,IACH;AACA;AAAA,MACE,oBAAoB;AAAA,QAClB,gBAAgB,MAAM;AAAA,QACtB,MAAM;AAAA,QACN,IAAI,OAAO;AAAA,MACb,CAAC;AAAA,IACH;AAAA,EACF;AAQA,MAAI;AACJ,MAAI,MAAM,eAAe,UAAa,MAAM,aAAa,QAAW;AAClE,UAAM,UAAU,MAAM,cAAc,OAAO;AAC3C,UAAM,cACJ,MAAM,aAAa,SAAY,MAAM,WAAW,OAAO;AACzD,QAAI,aAAa;AACf,YAAM,YAAY,MAAM,WAAW,YAAY,WAAW;AAC1D,UAAI,CAAC,WAAW;AACd,cAAM,IAAI;AAAA,UACR;AAAA,QACF;AAAA,MACF;AACA,yBAAmB,GAAG,UAAU,UAAU,IAAI,OAAO;AAAA,IACvD,OAAO;AACL,yBAAmB;AAAA,IACrB;AAAA,EACF;AAEA,MAAI;AACF,UAAM,EAAE,MAAM,IAAI,IAAI,MAAM,QAAQ;AAAA,MAClC,IAAI;AAAA,MACJ;AAAA,QACE,aAAa;AAAA,QACb,YAAY,MAAM;AAAA,QAClB,UAAU,MAAM,YAAY;AAAA,MAC9B;AAAA,MACA,EAAE,UAAU;AAAA,IACd;AACA,aAAS,aAAa,mBAAmB,GAAG,CAAC,CAAC;AAAA,EAChD,SAAS,KAAK;AAGZ,aAAS,aAAa,qBAAqB,MAAM,CAAC,CAAC;AACnD,QAAI,MAAM,aAAa,UAAa,MAAM,aAAa,OAAO,UAAU;AACtE;AAAA,QACE,sBAAsB;AAAA,UACpB,gBAAgB,MAAM;AAAA,UACtB,MAAM;AAAA,UACN,IAAI,OAAO;AAAA,QACb,CAAC;AAAA,MACH;AACA;AAAA,QACE,oBAAoB;AAAA,UAClB,gBAAgB,OAAO;AAAA,UACvB,MAAM;AAAA,UACN,IAAI,OAAO;AAAA,QACb,CAAC;AAAA,MACH;AAAA,IACF;AACA,UAAM;AAAA,EACR,UAAE;AACA,mBAAe,SAAS;AAAA,EAC1B;AACF,CAAC;AAEM,MAAM,eAAe,iBAI1B,2BAA2B,OAAO,KAAK,EAAE,UAAU,SAAS,MAAM;AAClE,QAAM,QAAQ,SAAS;AACvB,QAAM,SAAS,MAAM,WAAW,YAAY,IAAI,QAAQ;AACxD,MAAI,CAAC,OAAQ;AAEb,QAAM,YAAY,aAAa;AAC/B,kBAAgB;AAAA,IACd;AAAA,IACA,MAAM;AAAA,IACN,YAAY,IAAI;AAAA,IAChB,cAAc;AAAA,EAChB,CAAC;AAED,MAAI;AAGF,UAAM,uBAAuB,IAAI,QAAQ;AACzC,aAAS,aAAa,EAAE,IAAI,IAAI,SAAS,CAAC,CAAC;AAAA,EAC7C,UAAE;AACA,mBAAe,SAAS;AAAA,EAC1B;AACF,CAAC;AAOM,MAAM,mBAAmB,iBAI9B,+BAA+B,OAAO,KAAK,EAAE,UAAU,SAAS,MAAM;AACtE,QAAM,iBAAiB,IAAI,WACxB,MAAM,GAAG,EACT,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EACnB,OAAO,OAAO,EACd,KAAK,GAAG;AACX,MAAI,CAAC,gBAAgB;AACnB,UAAM,IAAI,MAAM,6BAA6B;AAAA,EAC/C;AAGA,QAAM,WAAW,OAAO,OAAO,SAAS,EAAE,WAAW,WAAW,EAAE;AAAA,IAChE,CAAC,WAAW,OAAO,eAAe,kBAAkB,CAAC,OAAO;AAAA,EAC9D;AACA,MAAI,UAAU;AACZ,WAAO,SAAS;AAAA,EAClB;AAIA,QAAM,YAAY,aAAa;AAC/B,kBAAgB;AAAA,IACd;AAAA,IACA,MAAM;AAAA,IACN,YAAY;AAAA,IACZ,cAAc;AAAA,EAChB,CAAC;AACD,MAAI;AACF,UAAM,EAAE,MAAM,IAAI,IAAI,MAAM,QAAQ;AAAA,MAClC;AAAA,MACA,IAAI,cAAc;AAAA,MAClB,EAAE,UAAU;AAAA,IACd;AACA,UAAM,SAAS,mBAAmB,GAAG;AACrC,aAAS,aAAa,MAAM,CAAC;AAC7B;AAAA,MACE,oBAAoB;AAAA,QAClB,gBAAgB,OAAO;AAAA,QACvB,MAAM;AAAA,QACN,IAAI,OAAO;AAAA,MACb,CAAC;AAAA,IACH;AACA,WAAO,OAAO;AAAA,EAChB,UAAE;AACA,mBAAe,SAAS;AAAA,EAC1B;AACF,CAAC;AAgCM,SAAS,uBACd,QACM;AACN,MAAI,OAAO,WAAW,EAAG;AACzB,QAAM,UAAU,MAAM;AAAA,IACpB,IAAI,IAAI,OAAO,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,MAAM,KAAK,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC;AAAA,EAC5E;AACA,QAAM,QACJ,OAAO,WAAW,IACd,mBAAmB,OAAO,CAAC,EAAE,IAAI,KACjC,mBAAmB,OAAO,MAAM;AACtC,QAAM,MAAM,OAAO;AAAA;AAAA;AAAA,IAGjB,aACE,QAAQ,SAAS,IACb,QAAQ,KAAK,QAAK,IAClB;AAAA,IACN,IAAI,iBAAiB,OAAO,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,GAAG,CAAC;AAAA,IACxD,UAAU;AAAA,EACZ,CAAC;AACH;AAEO,MAAM,cAAc,iBAIzB,0BAA0B,OAAO,KAAK,EAAE,UAAU,SAAS,MAAM;AACjE,QAAM,cAAc,KAAK,IAAI,GAAG,IAAI,eAAe,CAAC;AACpD,QAAM,WAAqB,CAAC;AAK5B,QAAM,SAAiD,CAAC;AAYxD,QAAM,QAAQ,SAAS;AACvB,MAAI,SAAS;AACb,MAAI,IAAI,YAAY;AAClB,aAAS,GAAG,IAAI,WAAW,QAAQ,cAAc,EAAE,CAAC;AAAA,EACtD,WAAW,IAAI,gBAAgB;AAC7B,UAAM,eAAe,MAAM,WAAW,YAAY,IAAI,cAAc;AACpE,QAAI,cAAc;AAChB,eAAS,GAAG,aAAa,UAAU;AAAA,IACrC;AAAA,EACF;AAeA,QAAM,iBAAiB,MAAM;AAC3B,UAAM,MAAM,oBAAI,IAAY;AAC5B,UAAM,eAAe;AACrB,eAAW,KAAK,OAAO,OAAO,MAAM,WAAW,SAAS,GAAG;AACzD,UAAI,CAAC,EAAG;AACR,UAAI,EAAE,UAAW;AAGjB,UAAI,CAAC,EAAE,SAAU;AACjB,YAAM,aAAa,EAAE,SAAS,QAAQ,QAAQ,EAAE;AAChD,UAAI,CAAC,WAAW,WAAW,YAAY,EAAG;AAC1C,YAAM,OAAO,WAAW,MAAM,aAAa,MAAM;AAEjD,UAAI,KAAK,SAAS,GAAG,EAAG;AACxB,UAAI,KAAM,KAAI,IAAI,KAAK,YAAY,CAAC;AAAA,IACtC;AACA,WAAO;AAAA,EACT,GAAG;AAaH,WAAS,WAAW,cAA8B;AAChD,UAAM,UAAU,aAAa,YAAY;AACzC,QAAI,CAAC,cAAc,IAAI,OAAO,GAAG;AAC/B,oBAAc,IAAI,OAAO;AACzB,aAAO;AAAA,IACT;AACA,UAAM,MAAM,aAAa,YAAY,GAAG;AACxC,UAAM,WAAW,aAAa,WAAW,GAAG;AAC5C,UAAM,OACJ,MAAM,KAAK,CAAC,WAAW,aAAa,MAAM,GAAG,GAAG,IAAI;AACtD,UAAM,MAAM,MAAM,KAAK,CAAC,WAAW,aAAa,MAAM,GAAG,IAAI;AAC7D,aAAS,IAAI,GAAG,IAAI,KAAM,KAAK;AAC7B,YAAM,YAAY,GAAG,IAAI,KAAK,CAAC,IAAI,GAAG;AACtC,UAAI,CAAC,cAAc,IAAI,UAAU,YAAY,CAAC,GAAG;AAC/C,sBAAc,IAAI,UAAU,YAAY,CAAC;AACzC,eAAO;AAAA,MACT;AAAA,IACF;AAGA,WAAO;AAAA,EACT;AASA,QAAM,YAAY,IAAI,qBAAqB,CAAC;AAC5C,QAAM,UAAU,IAAI,IAAI,IAAI,eAAe,CAAC,CAAC;AAC7C,QAAM,kBAAkB,IAAI,IAAI,IAAI,uBAAuB,CAAC,CAAC;AAM7D,aAAW,CAAC,EAAE,IAAI,KAAK,OAAO,QAAQ,SAAS,GAAG;AAChD,QAAI,OAAO,SAAS,SAAU,eAAc,IAAI,KAAK,YAAY,CAAC;AAAA,EACpE;AAIA,QAAM,QAA8C,IAAI,MACrD,IAAI,CAAC,MAAM,WAAW,EAAE,OAAO,KAAK,EAAE,EACtC,OAAO,CAAC,EAAE,MAAM,MAAM,CAAC,QAAQ,IAAI,KAAK,CAAC;AAE5C,iBAAe,SAAwB;AACrC,WAAO,MAAM,QAAQ;AACnB,YAAM,OAAO,MAAM,MAAM;AACzB,UAAI,CAAC,KAAM;AACX,YAAM,EAAE,MAAM,MAAM,IAAI;AACxB,YAAM,eAAe,UAAU,KAAK;AACpC,YAAM,aAAa,gBAAgB,IAAI,KAAK;AAU5C,YAAM,WACJ,CAAC,gBAAgB,CAAC,aACd,eAAe,QAAQ,KAAK,MAAM,KAAK,IAAI,IAC3C;AACN,UAAI,aAAiC;AACrC,UAAI,UAAU;AACZ,cAAM,QAAQ,YAAY,QAAQ;AAClC,YAAI,MAAM,QAAQ;AAChB,cAAI;AACF,kBAAM,UAAU,MAAM,MAAM;AAE5B,qBAAS,KAAK,QAAQ,MAAM;AAC5B;AAAA,UACF,QAAQ;AAAA,UAGR;AAAA,QACF,OAAO;AACL,uBAAa;AAAA,QACf;AAAA,MACF;AAEA,YAAM,YAAY,aAAa;AAK/B,YAAM,aACJ,OAAO,iBAAiB,YAAY,eAChC,eACA,WAAW,KAAK,IAAI;AAC1B;AAAA,QACE,iBAAiB;AAAA,UACf;AAAA,UACA,UAAU;AAAA,UACV,UAAU,KAAK;AAAA,UACf,gBAAgB,IAAI,kBAAkB;AAAA,UACtC,YAAY,IAAI,cAAc;AAAA,QAChC,CAAC;AAAA,MACH;AACA,sBAAgB;AAAA,QACd;AAAA,QACA,MAAM;AAAA,QACN,YAAY;AAAA,QACZ,cAAc;AAAA,MAChB,CAAC;AACD,UAAI;AACF,cAAM,EAAE,KAAK,IAAI,MAAM,MAAM;AAAA,UAC3B;AAAA,YACE;AAAA,YACA,UAAU,GAAG,MAAM,GAAG,UAAU;AAAA,YAChC,YAAY,IAAI,cAAc;AAAA,YAC9B,WAAW,IAAI;AAAA,YACf,YAAY,IAAI;AAAA,YAChB,eAAe,IAAI;AAAA,YACnB,UAAU,IAAI;AAAA,YACd,SAAS,IAAI;AAAA,YACb,GAAI,gBAAgB,IAAI,KAAK,IACzB;AAAA,cACE,QAAQ;AAAA,cACR,QACE;AAAA,YACJ,IACA,CAAC;AAAA,UACP;AAAA,UACA,CAAC,OACC;AAAA,YACE,qBAAqB;AAAA,cACnB;AAAA,cACA,eAAe,GAAG;AAAA,YACpB,CAAC;AAAA,UACH;AAAA,UACF;AAAA,YACE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,YAMA,gBAAgB;AAAA,UAClB;AAAA,QACF;AAEA;AAAA,UACE;AAAA,YACE,yBAAyB;AAAA,cACvB,IAAI,KAAK;AAAA,cACT,UAAU;AAAA,cACV,WAAW,KAAK;AAAA,cAChB,WAAW,KAAK,UAAU,MAAM,GAAG,EAAE,IAAI,KAAK;AAAA,cAC9C,WAAW,KAAK,QAAQ;AAAA;AAAA,cAExB,YAAY,KAAK;AAAA,cACjB,UAAU,KAAK;AAAA,cACf,YAAY,IAAI,cAAc;AAAA,cAC9B,iBAAiB,KAAK;AAAA,cACtB,kBAAkB,IAAI,kBAAkB;AAAA,cACxC,UAAU,IAAI,YAAY,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,cAM3B,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,cACnC,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,cACnC,YAAY;AAAA,YACd,CAAC;AAAA,UACH;AAAA,QACF;AAKA,4BAAoB,KAAK,OAAO;AAChC,mCAA2B,KAAK,OAAO;AACvC;AAAA,UACE,oBAAoB;AAAA,YAClB,gBAAgB,IAAI,kBAAkB;AAAA,YACtC,MAAM;AAAA,YACN,IAAI,KAAK;AAAA,UACX,CAAC;AAAA,QACH;AACA;AAAA,UACE,mBAAmB;AAAA,YACjB;AAAA,YACA,QAAQ;AAAA,YACR,QAAQ,KAAK;AAAA,UACf,CAAC;AAAA,QACH;AACA,iBAAS,KAAK,KAAK,OAAO;AAC1B,oBAAY;AAAA,UACV,QAAQ,QAAQ,EAAE,QAAQ,KAAK,SAAS,UAAU,KAAK,UAAU,CAAC;AAAA,QACpE;AAAA,MACF,SAAS,KAAK;AACZ,cAAM,UAAU,oBAAoB,GAAG;AACvC;AAAA,UACE,mBAAmB;AAAA,YACjB;AAAA,YACA,QAAQ;AAAA,YACR,OAAO;AAAA,UACT,CAAC;AAAA,QACH;AACA,eAAO,KAAK,EAAE,MAAM,KAAK,MAAM,OAAO,QAAQ,CAAC;AAC/C,oBAAY,OAAO,QAAQ,OAAO,GAAG,CAAC;AAAA,MACxC,UAAE;AACA,uBAAe,SAAS;AAAA,MAC1B;AAAA,IACF;AAAA,EACF;AAEA,QAAM,QAAQ;AAAA,IACZ,MAAM;AAAA,MAAK,EAAE,QAAQ,KAAK,IAAI,aAAa,IAAI,MAAM,MAAM,EAAE;AAAA,MAAG,MAC9D,OAAO;AAAA,IACT;AAAA,EACF;AAEA,yBAAuB,MAAM;AAC7B,SAAO,EAAE,UAAU,OAAO;AAC5B,CAAC;AAEM,MAAM,mBAAmB,iBAI9B,0BAA0B,OAAO,EAAE,UAAU,GAAG,EAAE,SAAS,MAAM;AACjE,WAAS,YAAY,EAAE,UAAU,CAAC,CAAC;AACrC,CAAC;AAMM,MAAM,aAAa;AAAA,EACxB;AAAA,EACA,OAAO,EAAE,QAAQ,QAAQ,GAAG,EAAE,UAAU,SAAS,MAAM;AACrD,UAAM,SAAS,iBAAiB,SAAS,GAAG,MAAM;AAClD,QAAI,CAAC;AACH,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAEF,UAAM,YAAY,aAAa;AAG/B,UAAM,MAAM,gBAAgB,MAAM;AAClC,UAAM,WAAmC;AAAA,MACvC,UAAU,OAAO;AAAA,MACjB,UAAU,OAAO;AAAA,IACnB;AAGA,UAAM,YAAY,OAAO,SAAS,MAAM,GAAG;AAC3C,cAAU,UAAU,SAAS,CAAC,IAAI;AAClC,UAAM,UAAU,UAAU,KAAK,GAAG;AAElC,aAAS,aAAa,EAAE,IAAI,QAAQ,OAAO,YAAY,OAAO,QAAQ,CAAC,CAAC;AACxE,aAAS,aAAa,EAAE,IAAI,QAAQ,OAAO,YAAY,OAAO,QAAQ,CAAC,CAAC;AACxE,aAAS,sBAAsB,EAAE,IAAI,QAAQ,UAAU,CAAC,CAAC;AACzD,oBAAgB;AAAA,MACd;AAAA,MACA,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,cAAc;AAAA,IAChB,CAAC;AAED,QAAI;AAIF,YAAM,EAAE,KAAK,IAAI,MAAM,MAAM;AAAA,QAC3B;AAAA,QACA,EAAE,UAAU,QAAQ;AAAA,QACpB,EAAE,UAAU;AAAA,MACd;AAGA,UAAI,mBAAmB,QAAQ,GAAG,GAAG;AACnC,iBAAS,WAAW,yBAAyB,IAAI,CAAC,CAAC;AACnD,iBAAS,cAAc,EAAE,IAAI,OAAO,CAAC,CAAC;AAAA,MACxC;AAAA,IACF,SAAS,KAAK;AAEZ,UAAI,mBAAmB,QAAQ,GAAG,GAAG;AACnC,iBAAS,6BAA6B,EAAE,IAAI,QAAQ,SAAS,CAAC,CAAC;AAC/D,cAAM,MAAM,oBAAoB,GAAG;AACnC,iBAAS,aAAa,EAAE,IAAI,QAAQ,OAAO,IAAI,CAAC,CAAC;AAAA,MACnD;AACA,YAAM;AAAA,IACR,UAAE;AACA,eAAS,yBAAyB,EAAE,IAAI,QAAQ,UAAU,CAAC,CAAC;AAC5D,qBAAe,SAAS;AAAA,IAC1B;AAAA,EACF;AACF;AAEO,MAAM,WAAW;AAAA,EACtB;AAAA,EACA,OAAO,EAAE,QAAQ,kBAAkB,GAAG,EAAE,UAAU,SAAS,MAAM;AAC/D,UAAM,SAAS,iBAAiB,SAAS,GAAG,MAAM;AAClD,QAAI,CAAC;AACH,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AACF,UAAM,cAAc,OAAO;AAC3B,UAAM,YAAY,aAAa;AAC/B,UAAM,MAAM,gBAAgB,MAAM;AAClC,UAAM,WAAmC;AAAA,MACvC,gBAAgB;AAAA,IAClB;AAEA;AAAA,MACE,aAAa;AAAA,QACX,IAAI;AAAA,QACJ,OAAO;AAAA,QACP,OAAO;AAAA,MACT,CAAC;AAAA,IACH;AACA;AAAA,MACE,sBAAsB;AAAA,QACpB,gBAAgB;AAAA,QAChB,MAAM;AAAA,QACN,IAAI;AAAA,MACN,CAAC;AAAA,IACH;AACA;AAAA,MACE,oBAAoB;AAAA,QAClB,gBAAgB;AAAA,QAChB,MAAM;AAAA,QACN,IAAI;AAAA,MACN,CAAC;AAAA,IACH;AACA,aAAS,sBAAsB,EAAE,IAAI,QAAQ,UAAU,CAAC,CAAC;AACzD,oBAAgB;AAAA,MACd;AAAA,MACA,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,cAAc;AAAA,IAChB,CAAC;AAED,QAAI;AAKF,YAAM,eACJ,sBAAsB,OAClB,OACC,SAAS,EAAE,WAAW,YAAY,iBAAiB,KAAK;AAC/D,YAAM,eAAe,eAAe,GAAG,aAAa,UAAU,MAAM;AACpE,YAAM,UAAU,GAAG,YAAY,GAAG,OAAO,QAAQ;AAEjD,YAAM,EAAE,KAAK,IAAI,MAAM,MAAM;AAAA,QAC3B;AAAA,QACA,EAAE,UAAU,QAAQ;AAAA,QACpB,EAAE,UAAU;AAAA,MACd;AACA,UAAI,mBAAmB,QAAQ,GAAG,GAAG;AACnC,iBAAS,WAAW,yBAAyB,IAAI,CAAC,CAAC;AACnD,iBAAS,cAAc,EAAE,IAAI,OAAO,CAAC,CAAC;AAAA,MACxC;AAAA,IACF,SAAS,KAAK;AACZ,UAAI,mBAAmB,QAAQ,GAAG,GAAG;AAEnC;AAAA,UACE,sBAAsB;AAAA,YACpB,gBAAgB;AAAA,YAChB,MAAM;AAAA,YACN,IAAI;AAAA,UACN,CAAC;AAAA,QACH;AACA;AAAA,UACE,oBAAoB;AAAA,YAClB,gBAAgB;AAAA,YAChB,MAAM;AAAA,YACN,IAAI;AAAA,UACN,CAAC;AAAA,QACH;AACA,iBAAS,6BAA6B,EAAE,IAAI,QAAQ,SAAS,CAAC,CAAC;AAC/D,cAAM,MAAM,oBAAoB,GAAG;AACnC,iBAAS,aAAa,EAAE,IAAI,QAAQ,OAAO,IAAI,CAAC,CAAC;AAAA,MACnD;AACA,YAAM;AAAA,IACR,UAAE;AACA,eAAS,yBAAyB,EAAE,IAAI,QAAQ,UAAU,CAAC,CAAC;AAC5D,qBAAe,SAAS;AAAA,IAC1B;AAAA,EACF;AACF;AAEO,MAAM,qBAAqB;AAAA,EAKhC;AAAA,EACA,OAAO,EAAE,QAAQ,MAAM,GAAG,EAAE,UAAU,SAAS,MAAM;AACnD,UAAM,SAAS,iBAAiB,SAAS,GAAG,MAAM;AAClD,QAAI,CAAC;AACH,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAEF,UAAM,YAAY,aAAa;AAC/B,UAAM,MAAM,gBAAgB,MAAM;AAClC,UAAM,WAAmC;AAAA,MACvC,YAAY,OAAO;AAAA,MACnB,UAAU,OAAO;AAAA,IACnB;AAEA,QAAI,MAAM,eAAe,QAAW;AAClC;AAAA,QACE,aAAa;AAAA,UACX,IAAI;AAAA,UACJ,OAAO;AAAA,UACP,OAAO,MAAM;AAAA,QACf,CAAC;AAAA,MACH;AAAA,IACF;AACA,QAAI,MAAM,aAAa,QAAW;AAChC;AAAA,QACE,aAAa;AAAA,UACX,IAAI;AAAA,UACJ,OAAO;AAAA,UACP,OAAO,MAAM;AAAA,QACf,CAAC;AAAA,MACH;AAAA,IACF;AACA,aAAS,sBAAsB,EAAE,IAAI,QAAQ,UAAU,CAAC,CAAC;AACzD,oBAAgB;AAAA,MACd;AAAA,MACA,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,cAAc;AAAA,IAChB,CAAC;AAED,QAAI;AACF,YAAM,EAAE,KAAK,IAAI,MAAM,MAAM;AAAA,QAC3B;AAAA,QACA;AAAA,UACE,YAAY,MAAM;AAAA,UAClB,UAAU,MAAM;AAAA,QAClB;AAAA,QACA,EAAE,UAAU;AAAA,MACd;AACA,UAAI,mBAAmB,QAAQ,GAAG,GAAG;AACnC,iBAAS,WAAW,yBAAyB,IAAI,CAAC,CAAC;AACnD,iBAAS,cAAc,EAAE,IAAI,OAAO,CAAC,CAAC;AAAA,MACxC;AAAA,IACF,SAAS,KAAK;AACZ,UAAI,mBAAmB,QAAQ,GAAG,GAAG;AACnC,iBAAS,6BAA6B,EAAE,IAAI,QAAQ,SAAS,CAAC,CAAC;AAC/D,cAAM,MAAM,oBAAoB,GAAG;AACnC,iBAAS,aAAa,EAAE,IAAI,QAAQ,OAAO,IAAI,CAAC,CAAC;AAAA,MACnD;AACA,YAAM;AAAA,IACR,UAAE;AACA,eAAS,yBAAyB,EAAE,IAAI,QAAQ,UAAU,CAAC,CAAC;AAC5D,qBAAe,SAAS;AAAA,IAC1B;AAAA,EACF;AACF;AAMO,MAAM,aAAa;AAAA,EACxB;AAAA,EACA,OAAO,EAAE,OAAO,GAAG,EAAE,UAAU,SAAS,MAAM;AAC5C,UAAM,SAAS,iBAAiB,SAAS,GAAG,MAAM;AAClD,QAAI,CAAC,OAAQ;AACb,UAAM,iBAAiB,OAAO;AAC9B,UAAM,YAAY,aAAa;AAG/B,aAAS,WAAW,EAAE,IAAI,OAAO,CAAC,CAAC;AACnC;AAAA,MACE,sBAAsB;AAAA,QACpB;AAAA,QACA,MAAM;AAAA,QACN,IAAI;AAAA,MACN,CAAC;AAAA,IACH;AAIA,wBAAoB,MAAM;AAC1B,+BAA2B,MAAM;AACjC,oBAAgB;AAAA,MACd;AAAA,MACA,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,cAAc;AAAA,IAChB,CAAC;AAED,QAAI;AAGF,YAAM,qBAAqB,MAAM;AAAA,IACnC,SAAS,KAAK;AAEZ,eAAS,WAAW,mBAAmB,MAAM,CAAC,CAAC;AAC/C;AAAA,QACE,oBAAoB;AAAA,UAClB;AAAA,UACA,MAAM;AAAA,UACN,IAAI;AAAA,QACN,CAAC;AAAA,MACH;AACA,YAAM;AAAA,IACR,UAAE;AACA,qBAAe,SAAS;AAAA,IAC1B;AAAA,EACF;AACF;AAuBA,SAAS,mBAAmB,QAA6C;AACvE,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAG;AAAA,EACL,IAAI;AACJ,SAAO;AACT;AAGA,SAAS,qBAAqB,QAAiD;AAC7E,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAG;AAAA,EACL,IAAI;AACJ,SAAO;AACT;AAEO,MAAM,YAAY;AAAA,EACvB;AAAA,EACA,OAAO,EAAE,OAAO,GAAG,EAAE,SAAS,MAAM;AAClC,UAAM,OAA8C,CAAC;AACrD,aAAS,OAAO,GAAG,OAAO,gBAAgB,QAAQ,GAAG;AACnD,YAAM,EAAE,MAAM,MAAM,IAAI,MAAM,SAAS,IAAI,sBAAsB;AAAA,QAC/D,WAAW;AAAA,QACX,SAAS;AAAA,QACT,UAAU,OAAO;AAAA,QACjB,mBAAmB;AAAA,QACnB,mBAAmB;AAAA,MACrB,CAAC;AACD,UAAI,MAAO,OAAM;AACjB,YAAM,WAAW,mBAAmB,IAAI;AACxC,WAAK,KAAK,GAAG,QAAQ;AACrB,UAAI,SAAS,SAAS,eAAgB;AAAA,IACxC;AAEA,UAAM,QAA8B,CAAC;AACrC,UAAM,UAAkC,CAAC;AACzC,eAAW,OAAO,MAAM;AACtB,UAAI,CAAC,IAAI,WAAY;AACrB,UAAI,IAAI,SAAS,QAAQ;AAEvB,cAAM,KAAK;AAAA,UACT,IAAI,IAAI;AAAA,UACR,SAAS,IAAI;AAAA,UACb,UAAU,IAAI;AAAA,UACd,UAAU,IAAI;AAAA,UACd,gBAAgB,IAAI;AAAA,UACpB,UAAU,IAAI;AAAA,UACd,UAAU,IAAI;AAAA,UACd,YAAY,IAAI;AAAA,UAChB,gBAAgB,IAAI;AAAA,UACpB,WAAW,IAAI;AAAA,UACf,WAAW,IAAI;AAAA,UACf,WAAW,IAAI;AAAA,UACf,gBAAgB,IAAI;AAAA,QACtB,CAAC;AAAA,MACH,OAAO;AACL,gBAAQ,KAAK;AAAA,UACX,IAAI,IAAI;AAAA,UACR,SAAS,IAAI;AAAA,UACb,YAAY,IAAI;AAAA,UAChB,YAAY,IAAI;AAAA,UAChB,UAAU,IAAI;AAAA,UACd,YAAY,IAAI;AAAA,UAChB,WAAW,IAAI;AAAA,UACf,WAAW,IAAI;AAAA,UACf,WAAW,IAAI;AAAA,QACjB,CAAC;AAAA,MACH;AAAA,IACF;AACA,aAAS,YAAY,KAAK,CAAC;AAC3B,aAAS,cAAc,OAAO,CAAC;AAAA,EACjC;AACF;AAGO,MAAM,cAAc;AAAA,EACzB;AAAA,EACA,OAAO,EAAE,OAAO,GAAG,EAAE,UAAU,SAAS,MAAM;AAC5C,UAAM,SAAS,iBAAiB,SAAS,GAAG,MAAM;AAClD,QAAI,CAAC,QAAQ,UAAW;AACxB,UAAM,YAAY,aAAa;AAG/B,aAAS,WAAW,EAAE,GAAG,mBAAmB,MAAM,GAAG,WAAW,KAAK,CAAC,CAAC;AACvE;AAAA,MACE,oBAAoB;AAAA,QAClB,gBAAgB,OAAO;AAAA,QACvB,MAAM;AAAA,QACN,IAAI;AAAA,MACN,CAAC;AAAA,IACH;AACA,oBAAgB;AAAA,MACd;AAAA,MACA,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,cAAc;AAAA,IAChB,CAAC;AACD,QAAI;AACF,YAAM,kBAAkB,MAAM;AAAA,IAChC,SAAS,KAAK;AACZ,eAAS,WAAW,mBAAmB,MAAM,CAAC,CAAC;AAC/C;AAAA,QACE,sBAAsB;AAAA,UACpB,gBAAgB,OAAO;AAAA,UACvB,MAAM;AAAA,UACN,IAAI;AAAA,QACN,CAAC;AAAA,MACH;AACA,YAAM;AAAA,IACR,UAAE;AACA,qBAAe,SAAS;AAAA,IAC1B;AAAA,EACF;AACF;AAQO,MAAM,gBAAgB,iBAI3B,4BAA4B,OAAO,EAAE,SAAS,GAAG,EAAE,UAAU,SAAS,MAAM;AAC5E,QAAM,SAAS,SAAS,EAAE,WAAW,YAAY,QAAQ;AACzD,MAAI,CAAC,QAAQ,UAAW;AACxB,QAAM,oBAAoB,QAAQ;AAClC,WAAS,aAAa,EAAE,GAAG,qBAAqB,MAAM,GAAG,WAAW,KAAK,CAAC,CAAC;AAC3E,QAAM,SAAS,OAAO;AACtB,QAAM,SAAS,iBAAiB,EAAE,OAAO,CAAC,CAAC;AAC3C,QAAM,SAAS,UAAU,EAAE,OAAO,CAAC,CAAC;AACtC,CAAC;AAMM,MAAM,iBAAiB;AAAA,EAK5B;AAAA,EACA,OAAO,EAAE,QAAQ,cAAc,GAAG,EAAE,SAAS,MAAM;AACjD,QAAI,oBAAoB,MAAM,EAAG;AACjC,aAAS,eAAe,EAAE,IAAI,QAAQ,SAAS,KAAK,CAAC,CAAC;AACtD,UAAM,YAAY,aAAa;AAC/B,oBAAgB;AAAA,MACd;AAAA,MACA,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,cAAc;AAAA,IAChB,CAAC;AACD,QAAI;AACF,YAAM,EAAE,KAAK,IAAI,MAAM,SAAS,eAAe,QAAQ,eAAe;AAAA,QACpE;AAAA,MACF,CAAC;AACD,eAAS,WAAW,yBAAyB,IAAI,CAAC,CAAC;AAInD,0BAAoB,MAAM;AAC1B,iCAA2B,MAAM;AAEjC,YAAM,SAAS,iBAAiB,EAAE,OAAO,CAAC,CAAC,EAAE,OAAO;AAAA,IACtD,UAAE;AACA,eAAS,eAAe,EAAE,IAAI,QAAQ,SAAS,MAAM,CAAC,CAAC;AACvD,qBAAe,SAAS;AAAA,IAC1B;AAAA,EACF;AACF;AAkBO,MAAM,qBAAqB;AAAA,EAKhC;AAAA,EACA,OAAO,EAAE,QAAQ,SAAS,cAAc,GAAG,EAAE,UAAU,SAAS,MAAM;AACpE,QAAI,oBAAoB,MAAM,GAAG;AAC/B,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,QAAI,SAAgC,SAAS,EAAE,WAAW,UAAU,MAAM;AAG1E,QAAI,CAAC,QAAQ,UAAU;AACrB,YAAM,EAAE,KAAK,IAAI,MAAM,MAAM,gBAAgB,MAAM;AACnD,YAAM,QAAQ,yBAAyB,IAAI;AAC3C,eAAS,WAAW,KAAK,CAAC;AAC1B,eAAS;AAAA,IACX;AACA,UAAM,WAAW,OAAO,YAAY;AACpC,UAAM,OACJ,OAAO,YAAY,WACf,IAAI,KAAK,CAAC,OAAO,GAAG,EAAE,MAAM,SAAS,CAAC,IACtC;AACN,UAAM,SAAS,IAAI,KAAK,CAAC,IAAI,GAAG,OAAO,UAAU,EAAE,MAAM,SAAS,CAAC;AAEnE,aAAS,eAAe,EAAE,IAAI,QAAQ,SAAS,KAAK,CAAC,CAAC;AACtD,UAAM,YAAY,aAAa;AAC/B,oBAAgB;AAAA,MACd;AAAA,MACA,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,cAAc;AAAA,IAChB,CAAC;AACD,QAAI;AACF,YAAM,UAAU,EAAE,WAAW,gBAAgB,UAAU;AACvD,YAAM,UAAU,iBAAiB;AACjC,YAAM,EAAE,KAAK,IAAI,MAAM,MAAM;AAAA,QAC3B;AAAA,QACA,EAAE,MAAM,QAAQ,eAAe,QAAQ;AAAA,QACvC;AAAA,MACF;AACA,UAAI,KAAK,YAAY,UAAU,KAAK,QAAQ;AAC1C,cAAM,IAAI;AAAA,UACR,WAAW,OAAO,QAAQ,uEACJ,KAAK,SAAS,QAAQ,KAAK,OAAO,wBAC3C,OAAO,iBAAiB,CAAC;AAAA,QAExC;AAAA,MACF;AACA;AAAA,QACE,WAAW;AAAA,UACT,IAAI;AAAA,UACJ,gBAAgB,KAAK;AAAA,UACrB,UAAU,KAAK;AAAA,UACf,UAAU,KAAK;AAAA,UACf,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,QACpC,CAAC;AAAA,MACH;AAGA,4BAAsB,QAAQ,MAAM,KAAK,cAAc;AACvD,iCAA2B,MAAM;AAGjC,WAAK,SAAS,iBAAiB,EAAE,OAAO,CAAC,CAAC,EACvC,OAAO,EACP,MAAM,CAAC,QAAiB;AAEvB,gBAAQ;AAAA,UACN;AAAA,UACA;AAAA,QACF;AAAA,MACF,CAAC;AACH,aAAO,EAAE,QAAQ,eAAe,KAAK,eAAe;AAAA,IACtD,UAAE;AACA,eAAS,eAAe,EAAE,IAAI,QAAQ,SAAS,MAAM,CAAC,CAAC;AACvD,qBAAe,SAAS;AAAA,IAC1B;AAAA,EACF;AACF;AAcA,SAAS,qBACP,aACkB;AAClB,MAAI,gBAAgB,UAAU;AAC5B,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,SAAO,eAAe;AACxB;AAEO,MAAM,kBAAkB,iBAI7B,8BAA8B,OAAO,KAAK,EAAE,SAAS,MAAM;AAC3D,MAAI,oBAAoB,IAAI,UAAU,GAAG;AACvC,UAAM,IAAI,MAAM,8CAA8C;AAAA,EAChE;AACA,QAAM,YAAY,aAAa;AAC/B,kBAAgB;AAAA,IACd;AAAA,IACA,MAAM;AAAA,IACN,YAAY,IAAI;AAAA,IAChB,cAAc,IAAI;AAAA,EACpB,CAAC;AACD,MAAI;AACF,UAAM,OAAO;AAAA,MACX,YAAY,IAAI;AAAA,MAChB,OAAO,IAAI;AAAA,MACX,cAAc,qBAAqB,IAAI,WAAW;AAAA,MAClD,YAAY,IAAI,aAAa;AAAA,IAC/B;AACA,UAAM,EAAE,KAAK,IACX,IAAI,iBAAiB,WACjB,MAAM,YAAY,sBAAsB,IAAI,YAAY,MAAM;AAAA,MAC5D;AAAA,IACF,CAAC,IACD,MAAM,YAAY,oBAAoB,IAAI,YAAY,MAAM;AAAA,MAC1D;AAAA,IACF,CAAC;AACP;AAAA,MACE,6BAA6B;AAAA,QAC3B,YAAY,IAAI;AAAA,QAChB,aAAa,CAAC,sCAAsC,IAAI,CAAC;AAAA,MAC3D,CAAC;AAAA,IACH;AAIA,SAAK,SAAS,gBAAgB,EAAE,YAAY,IAAI,WAAW,CAAC,CAAC;AAAA,EAC/D,UAAE;AACA,mBAAe,SAAS;AAAA,EAC1B;AACF,CAAC;AAEM,MAAM,mBAAmB,iBAI9B,+BAA+B,OAAO,KAAK,EAAE,SAAS,MAAM;AAC5D,MAAI,oBAAoB,IAAI,UAAU,GAAG;AACvC,UAAM,IAAI,MAAM,8CAA8C;AAAA,EAChE;AACA,QAAM,YAAY,aAAa;AAC/B,kBAAgB;AAAA,IACd;AAAA,IACA,MAAM;AAAA,IACN,YAAY,IAAI;AAAA,IAChB,cAAc,IAAI;AAAA,EACpB,CAAC;AACD,MAAI;AACF,UAAM,cAAc,qBAAqB,IAAI,WAAW;AACxD,QAAI,IAAI,iBAAiB,UAAU;AACjC,YAAM,YAAY;AAAA,QAChB,IAAI;AAAA,QACJ,IAAI;AAAA,QACJ,EAAE,YAAY;AAAA,QACd,EAAE,UAAU;AAAA,MACd;AAAA,IACF,OAAO;AACL,YAAM,YAAY;AAAA,QAChB,IAAI;AAAA,QACJ,IAAI;AAAA,QACJ,EAAE,YAAY;AAAA,QACd,EAAE,UAAU;AAAA,MACd;AAAA,IACF;AACA;AAAA,MACE,4BAA4B;AAAA,QAC1B,YAAY,IAAI;AAAA,QAChB,WAAW,IAAI;AAAA,QACf;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF,UAAE;AACA,mBAAe,SAAS;AAAA,EAC1B;AACF,CAAC;AAMM,MAAM,kBAAkB,iBAI7B,8BAA8B,OAAO,KAAK,EAAE,UAAU,SAAS,MAAM;AACrE,MAAI,oBAAoB,IAAI,UAAU,GAAG;AACvC,UAAM,IAAI,MAAM,mDAAmD;AAAA,EACrE;AAGA,MAAI,YAAY,IAAI,YAAY,mBAAmB,GAAG;AACpD,UAAM,IAAI,MAAM,sDAAsD;AAAA,EACxE;AACA,QAAM,YAAY,aAAa;AAC/B,kBAAgB;AAAA,IACd;AAAA,IACA,MAAM;AAAA,IACN,YAAY,IAAI;AAAA,IAChB,cAAc,IAAI;AAAA,EACpB,CAAC;AACD,MAAI;AAEF,UAAM,UAAU,MAAM,yBAAyB;AAAA,MAC7C,cAAc,IAAI;AAAA,MAClB,YAAY,IAAI;AAAA,MAChB,iBAAiB,IAAI;AAAA,MACrB,WAAW,IAAI,aAAa;AAAA,MAC5B,SAAS,IAAI,WAAW;AAAA,IAC1B,CAAC;AACD,QAAI,CAAC,QAAQ,WAAW,CAAC,QAAQ,OAAO;AACtC,YAAM,IAAI,MAAM,QAAQ,SAAS,6BAA6B;AAAA,IAChE;AAEA,UAAM;AAAA,MACJ,eAAe;AAAA,QACb,YAAY,IAAI;AAAA,QAChB,cAAc,IAAI;AAAA,MACpB,CAAC;AAAA,IACH,EAAE,OAAO;AACT,UAAM,QACJ,SAAS,EAAE,WAAW,uBAAuB,IAAI,UAAU,KAAK,CAAC,GACjE,KAAK,CAAC,MAAM,EAAE,eAAe,QAAQ,KAAK;AAC5C,QAAI,CAAC,MAAM;AACT,YAAM,IAAI,MAAM,qDAAqD;AAAA,IACvE;AACA,WAAO;AAAA,EACT,UAAE;AACA,mBAAe,SAAS;AAAA,EAC1B;AACF,CAAC;AAEM,MAAM,kBAAkB,iBAI7B,8BAA8B,OAAO,EAAE,OAAO,GAAG,EAAE,SAAS,MAAM;AAClE,QAAM,YAAY,aAAa;AAC/B,kBAAgB;AAAA,IACd;AAAA,IACA,MAAM;AAAA,IACN,YAAY;AAAA,IACZ,cAAc;AAAA,EAChB,CAAC;AACD,MAAI;AACF,UAAM,SAAS,MAAM,yBAAyB,MAAM;AACpD,QAAI,CAAC,OAAO,SAAS;AACnB,YAAM,IAAI,MAAM,OAAO,SAAS,6BAA6B;AAAA,IAC/D;AACA,aAAS,gBAAgB,EAAE,OAAO,CAAC,CAAC;AAAA,EACtC,UAAE;AACA,mBAAe,SAAS;AAAA,EAC1B;AACF,CAAC;AAUM,MAAM,aAAa,iBAIxB,yBAAyB,OAAO,EAAE,OAAO,MAAM;AAC/C,MAAI,oBAAoB,MAAM,GAAG;AAK/B,UAAM,IAAI,MAAM,gDAAgD;AAAA,EAClE;AACA,QAAM,MAAM,MAAM,YACf,IAAI,EAAE,MAAM,WAAW,OAAO,CAAC,EAC/B,GAAG,EAAE,MAAM,WAAW,CAAC;AAC1B,MAAI,CAAC,KAAK;AACR,UAAM,IAAI,MAAM,oCAAoC,MAAM,EAAE;AAAA,EAC9D;AACA,SAAO,EAAE,IAAI;AACf,CAAC;AAyBD,SAAS,wBAAwB,QAAsB,MAAoB;AACzE,MAAI,OAAO,SAAS,GAAG;AACrB,UAAM,QAAQ,OAAO,YAAY,OAAO;AACxC,UAAM,MAAM,YAAY,IAAI,IAAI,OAAO,MAAM,OAAO,KAAK,UAAU;AAAA,MACjE,aACE,OAAO,YAAY,IACf,GAAG,OAAO,SAAS,wCACnB;AAAA,IACR,CAAC;AAAA,EACH;AACF;AAEO,MAAM,kBAAkB,iBAI7B,8BAA8B,OAAO,KAAK,EAAE,UAAU,SAAS,MAAM;AACrE,MAAI,IAAI,QAAQ,WAAW,GAAG;AAC5B,WAAO,EAAE,SAAS,CAAC,GAAG,WAAW,GAAG,QAAQ,EAAE;AAAA,EAChD;AAGA,QAAM,QAAQ,SAAS;AACvB,QAAM,YAAY,oBAAI,IAAuB;AAC7C,aAAW,MAAM,IAAI,SAAS;AAC5B,UAAM,OAAO,MAAM,WAAW,UAAU,EAAE;AAC1C,QAAI,KAAM,WAAU,IAAI,IAAI,IAAI;AAAA,EAClC;AAEA,QAAM,YAAY,aAAa;AAC/B,kBAAgB;AAAA,IACd;AAAA,IACA,MAAM;AAAA,IACN,YAAY;AAAA,IACZ,cAAc;AAAA,EAChB,CAAC;AAKD,aAAW,MAAM,IAAI,SAAS;AAC5B,aAAS,WAAW,EAAE,GAAG,CAAC,CAAC;AAC3B,wBAAoB,EAAE;AACtB,+BAA2B,EAAE;AAAA,EAC/B;AAEA,MAAI;AACF,UAAM,EAAE,KAAK,IAAI,MAAM,MAAM;AAAA,MAC3B,EAAE,UAAU,IAAI,QAAQ;AAAA,MACxB,EAAE,UAAU;AAAA,IACd;AACA,UAAM,SAAuB,QAAQ;AAAA,MACnC,SAAS,IAAI,QAAQ,IAAI,CAAC,QAAQ,EAAE,IAAI,IAAI,MAAM,OAAO,KAAK,EAAE;AAAA,MAChE,WAAW,IAAI,QAAQ;AAAA,MACvB,QAAQ;AAAA,IACV;AAGA,eAAW,KAAK,OAAO,SAAS;AAC9B,UAAI,EAAE,GAAI;AACV,YAAM,OAAO,UAAU,IAAI,EAAE,EAAE;AAC/B,UAAI,KAAM,UAAS,WAAW,IAAI,CAAC;AAAA,IACrC;AACA,4BAAwB,QAAQ,QAAQ;AACxC,WAAO;AAAA,EACT,SAAS,KAAK;AAEZ,eAAW,QAAQ,UAAU,OAAO,EAAG,UAAS,WAAW,IAAI,CAAC;AAChE,UAAM;AAAA,EACR,UAAE;AACA,mBAAe,SAAS;AAAA,EAC1B;AACF,CAAC;AAMM,MAAM,gBAAgB,iBAI3B,4BAA4B,OAAO,KAAK,EAAE,UAAU,SAAS,MAAM;AACnE,MAAI,IAAI,QAAQ,WAAW,GAAG;AAC5B,WAAO,EAAE,SAAS,CAAC,GAAG,WAAW,GAAG,QAAQ,EAAE;AAAA,EAChD;AAEA,QAAM,QAAQ,SAAS;AACvB,QAAM,YAAY,oBAAI,IAAuB;AAC7C,aAAW,MAAM,IAAI,SAAS;AAC5B,UAAM,OAAO,MAAM,WAAW,UAAU,EAAE;AAC1C,QAAI,KAAM,WAAU,IAAI,IAAI,IAAI;AAAA,EAClC;AAEA,QAAM,YAAY,aAAa;AAC/B,kBAAgB;AAAA,IACd;AAAA,IACA,MAAM;AAAA,IACN,YAAY;AAAA,IACZ,cAAc;AAAA,EAChB,CAAC;AAGD,aAAW,CAAC,IAAI,IAAI,KAAK,WAAW;AAClC,QAAI,KAAK,mBAAmB,IAAI,kBAAmB;AACnD;AAAA,MACE,sBAAsB;AAAA,QACpB,gBAAgB,KAAK;AAAA,QACrB,MAAM;AAAA,QACN;AAAA,MACF,CAAC;AAAA,IACH;AACA;AAAA,MACE,oBAAoB;AAAA,QAClB,gBAAgB,IAAI;AAAA,QACpB,MAAM;AAAA,QACN;AAAA,MACF,CAAC;AAAA,IACH;AACA,aAAS,WAAW,EAAE,GAAG,MAAM,gBAAgB,IAAI,kBAAkB,CAAC,CAAC;AAAA,EACzE;AAEA,MAAI;AACF,UAAM,EAAE,KAAK,IAAI,MAAM,MAAM;AAAA,MAC3B;AAAA,QACE,UAAU,IAAI;AAAA,QACd,sBAAsB,IAAI;AAAA,MAC5B;AAAA,MACA,EAAE,UAAU;AAAA,IACd;AAGA,eAAW,KAAK,KAAK,SAAS;AAC5B,UAAI,EAAE,GAAI;AACV,YAAM,OAAO,UAAU,IAAI,EAAE,EAAE;AAC/B,UAAI,CAAC,QAAQ,KAAK,mBAAmB,IAAI,kBAAmB;AAC5D;AAAA,QACE,sBAAsB;AAAA,UACpB,gBAAgB,IAAI;AAAA,UACpB,MAAM;AAAA,UACN,IAAI,KAAK;AAAA,QACX,CAAC;AAAA,MACH;AACA;AAAA,QACE,oBAAoB;AAAA,UAClB,gBAAgB,KAAK;AAAA,UACrB,MAAM;AAAA,UACN,IAAI,KAAK;AAAA,QACX,CAAC;AAAA,MACH;AACA,eAAS,WAAW,IAAI,CAAC;AAAA,IAC3B;AACA,4BAAwB,MAAM,MAAM;AACpC,WAAO;AAAA,EACT,SAAS,KAAK;AAEZ,eAAW,QAAQ,UAAU,OAAO,GAAG;AACrC,UAAI,KAAK,mBAAmB,IAAI,kBAAmB;AACnD;AAAA,QACE,sBAAsB;AAAA,UACpB,gBAAgB,IAAI;AAAA,UACpB,MAAM;AAAA,UACN,IAAI,KAAK;AAAA,QACX,CAAC;AAAA,MACH;AACA;AAAA,QACE,oBAAoB;AAAA,UAClB,gBAAgB,KAAK;AAAA,UACrB,MAAM;AAAA,UACN,IAAI,KAAK;AAAA,QACX,CAAC;AAAA,MACH;AACA,eAAS,WAAW,IAAI,CAAC;AAAA,IAC3B;AACA,UAAM;AAAA,EACR,UAAE;AACA,mBAAe,SAAS;AAAA,EAC1B;AACF,CAAC;AAMM,MAAM,kBAAkB,iBAI7B,8BAA8B,OAAO,KAAK,EAAE,UAAU,SAAS,MAAM;AACrE,MAAI,IAAI,UAAU,WAAW,GAAG;AAC9B,WAAO,EAAE,SAAS,CAAC,GAAG,WAAW,GAAG,QAAQ,EAAE;AAAA,EAChD;AAEA,QAAM,QAAQ,SAAS;AACvB,QAAM,YAAY,oBAAI,IAAyB;AAC/C,aAAW,MAAM,IAAI,WAAW;AAC9B,UAAM,SAAS,MAAM,WAAW,YAAY,EAAE;AAC9C,QAAI,OAAQ,WAAU,IAAI,IAAI,MAAM;AAAA,EACtC;AAEA,QAAM,YAAY,aAAa;AAC/B,kBAAgB;AAAA,IACd;AAAA,IACA,MAAM;AAAA,IACN,YAAY;AAAA,IACZ,cAAc;AAAA,EAChB,CAAC;AAGD,aAAW,CAAC,IAAI,MAAM,KAAK,WAAW;AACpC,QAAI,OAAO,aAAa,IAAI,YAAa;AACzC;AAAA,MACE,sBAAsB;AAAA,QACpB,gBAAgB,OAAO;AAAA,QACvB,MAAM;AAAA,QACN;AAAA,MACF,CAAC;AAAA,IACH;AACA;AAAA,MACE,oBAAoB;AAAA,QAClB,gBAAgB,IAAI;AAAA,QACpB,MAAM;AAAA,QACN;AAAA,MACF,CAAC;AAAA,IACH;AACA,aAAS,aAAa,EAAE,GAAG,QAAQ,UAAU,IAAI,YAAY,CAAC,CAAC;AAAA,EACjE;AAEA,MAAI;AACF,UAAM,EAAE,KAAK,IAAI,MAAM,QAAQ;AAAA,MAC7B,EAAE,YAAY,IAAI,WAAW,eAAe,IAAI,YAAY;AAAA,MAC5D,EAAE,UAAU;AAAA,IACd;AAGA,eAAW,KAAK,KAAK,SAAS;AAC5B,UAAI,EAAE,GAAI;AACV,YAAM,OAAO,UAAU,IAAI,EAAE,EAAE;AAC/B,UAAI,CAAC,QAAQ,KAAK,aAAa,IAAI,YAAa;AAChD;AAAA,QACE,sBAAsB;AAAA,UACpB,gBAAgB,IAAI;AAAA,UACpB,MAAM;AAAA,UACN,IAAI,KAAK;AAAA,QACX,CAAC;AAAA,MACH;AACA;AAAA,QACE,oBAAoB;AAAA,UAClB,gBAAgB,KAAK;AAAA,UACrB,MAAM;AAAA,UACN,IAAI,KAAK;AAAA,QACX,CAAC;AAAA,MACH;AACA,eAAS,aAAa,IAAI,CAAC;AAAA,IAC7B;AACA,4BAAwB,MAAM,MAAM;AACpC,WAAO;AAAA,EACT,SAAS,KAAK;AACZ,eAAW,QAAQ,UAAU,OAAO,GAAG;AACrC,UAAI,KAAK,aAAa,IAAI,YAAa;AACvC;AAAA,QACE,sBAAsB;AAAA,UACpB,gBAAgB,IAAI;AAAA,UACpB,MAAM;AAAA,UACN,IAAI,KAAK;AAAA,QACX,CAAC;AAAA,MACH;AACA;AAAA,QACE,oBAAoB;AAAA,UAClB,gBAAgB,KAAK;AAAA,UACrB,MAAM;AAAA,UACN,IAAI,KAAK;AAAA,QACX,CAAC;AAAA,MACH;AACA,eAAS,aAAa,IAAI,CAAC;AAAA,IAC7B;AACA,UAAM;AAAA,EACR,UAAE;AACA,mBAAe,SAAS;AAAA,EAC1B;AACF,CAAC;","names":["rows"]}